소스 검색

添加基础的图表元素

pipipi-pikachu 5 년 전
부모
커밋
f79712eef3

+ 108 - 0
src/components/Chart.vue

@@ -0,0 +1,108 @@
+<template>
+  <div class="chart">
+    <div 
+      class="chart-content"
+      ref="chartRef"
+      :style="{
+        width: width + 'px',
+        height: height + 'px',
+        transform: `scale(${1 / slideScale})`,
+      }"
+    ></div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, inject, onMounted, PropType, ref, Ref, watch } from 'vue'
+import upperFirst from 'lodash/upperFirst'
+import Chartist, {
+  IChartistLineChart,
+  IChartistBarChart,
+  IChartistPieChart,
+  ILineChartOptions,
+  IBarChartOptions,
+  IPieChartOptions,
+} from 'chartist'
+import { ChartData, ChartType } from '@/types/slides'
+
+import 'chartist/dist/scss/chartist.scss'
+
+export default defineComponent({
+  name: 'chart',
+  props: {
+    width: {
+      type: Number,
+      required: true,
+    },
+    height: {
+      type: Number,
+      required: true,
+    },
+    type: {
+      type: String as PropType<ChartType>,
+      required: true,
+    },
+    data: {
+      type: Object as PropType<ChartData>,
+      required: true,
+    },
+    options: {
+      type: Object as PropType<ILineChartOptions & IBarChartOptions & IPieChartOptions>,
+    },
+  },
+  setup(props) {
+    const chartRef = ref<HTMLElement | null>(null)
+    const slideScale: Ref<number> = inject('slideScale') || ref(1)
+
+    let chart: IChartistLineChart | IChartistBarChart | IChartistPieChart | undefined
+
+    const getDataAndOptions = () => {
+      const propsOptopns = props.options || {}
+      const options = {
+        ...propsOptopns,
+        width: props.width * slideScale.value,
+        height: props.height * slideScale.value,
+      }
+      const data = props.type === 'pie' ? { ...props.data, series: props.data.series[0] } : props.data
+      return { data, options }
+    }
+
+    const renderChart = () => {
+      if(!chartRef.value) return
+
+      const type = upperFirst(props.type)
+      const { data, options } = getDataAndOptions()
+      chart = new Chartist[type](chartRef.value, data, options)
+    }
+
+    const updateChart = () => {
+      if(!chart) {
+        renderChart()
+        return
+      }
+      const { data, options } = getDataAndOptions()
+      chart.update(data, options)
+    }
+
+    watch([
+      () => props.width,
+      () => props.height,
+      () => props.data,
+      slideScale,
+    ], updateChart)
+
+    onMounted(renderChart)
+
+    return {
+      slideScale,
+      chartRef,
+    }
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.chart-content {
+  transform-origin: 0 0;
+}
+</style>

+ 15 - 1
src/configs/element.ts

@@ -49,6 +49,20 @@ export const DEFAULT_TABLE = {
   outline: {
     width: 2,
     style: 'solid',
-    color: DEFAULT_COLOR
+    color: DEFAULT_COLOR,
   },
+}
+
+export const DEFAULT_FORMULA = {
+  left: 0,
+  top: 0,
+}
+
+export const MIN_SIZE = {
+  text: 20,
+  image: 20,
+  shape: 15,
+  chart: 200,
+  table: 20,
+  formula: 20,
 }

+ 38 - 0
src/mocks/index.ts

@@ -2,6 +2,44 @@ import { Slide } from '@/types/slides'
 
 export const slides: Slide[] = [
   {
+    id: 'xsxa123',
+    elements: [
+      {
+        id: 'sdasaxsxs',
+        type: 'chart',
+        left: 100,
+        top: 100,
+        width: 400,
+        height: 400,
+        chartType: 'pie',
+        data: {
+          labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
+          series: [
+            [5, 2, 4, 2, 10],
+          ],
+        },
+        options: {
+          donut: true,
+        },
+      },
+      {
+        id: 'sdasaxs',
+        type: 'chart',
+        left: 600,
+        top: 100,
+        width: 300,
+        height: 300,
+        chartType: 'line',
+        data: {
+          labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
+          series: [
+            [5, 2, 4, 2, 10],
+          ],
+        },
+      },
+    ],
+  },
+  {
     id: 'xxx1',
     background: {
       type: 'solid',

+ 17 - 2
src/types/slides.ts

@@ -1,3 +1,5 @@
+import { IBarChartOptions, ILineChartOptions, IPieChartOptions } from 'chartist'
+
 export interface PPTElementShadow {
   h: number;
   v: number;
@@ -105,7 +107,7 @@ export interface PPTLineElement {
   shadow?: PPTElementShadow;
 }
 
-export type ChartType = 'bar' | 'horizontalBar' | 'line' | 'pie' | 'doughnut' | 'polarArea' | 'radar'
+export type ChartType = 'bar' | 'line' | 'pie'
 export interface ChartData {
   labels: string[];
   series: number[][];
@@ -121,6 +123,7 @@ export interface PPTChartElement {
   height: number;
   chartType: ChartType;
   data: ChartData;
+  options?: ILineChartOptions & IBarChartOptions & IPieChartOptions;
   outline?: PPTElementOutline;
 }
 
@@ -145,8 +148,20 @@ export interface PPTTableElement {
   colSizes: number[];
   data: TableElementCell[][];
 }
+export interface PPTFormulaElement {
+  type: 'formula';
+  id: string;
+  left: number;
+  top: number;
+  lock?: boolean;
+  groupId?: string;
+  width: number;
+  height: number;
+  latex: string;
+  color?: string;
+}
 
-export type PPTElement = PPTTextElement | PPTImageElement | PPTShapeElement | PPTLineElement | PPTChartElement | PPTTableElement
+export type PPTElement = PPTTextElement | PPTImageElement | PPTShapeElement | PPTLineElement | PPTChartElement | PPTTableElement | PPTFormulaElement
 
 export interface PPTAnimation {
   elId: string;

+ 2 - 1
src/views/Editor/Canvas/hooks/useScaleElement.ts

@@ -4,6 +4,7 @@ import { State, MutationTypes } from '@/store'
 import { ElementTypes, PPTElement, PPTImageElement, PPTLineElement, PPTShapeElement } from '@/types/slides'
 import { OperateResizeHandlers, AlignmentLineProps, MultiSelectRange } from '@/types/edit'
 import { VIEWPORT_SIZE, VIEWPORT_ASPECT_RATIO } from '@/configs/canvas'
+import { MIN_SIZE } from '@/configs/element'
 import { AlignLine, uniqAlignLines } from '@/utils/element'
 import useHistorySnapshot from '@/hooks/useHistorySnapshot'
 
@@ -111,7 +112,7 @@ export default (
     const startPageX = e.pageX
     const startPageY = e.pageY
 
-    const minSize = 15
+    const minSize = MIN_SIZE[element.type] || 20
     const getSizeWithinRange = (size: number) => size < minSize ? minSize : size
 
     let points: ReturnType<typeof getRotateElementPoints>

+ 3 - 1
src/views/Editor/Canvas/index.vue

@@ -75,7 +75,7 @@
 </template>
 
 <script lang="ts">
-import { computed, defineComponent, Ref, ref, watch, watchEffect } from 'vue'
+import { computed, defineComponent, provide, Ref, ref, watch, watchEffect } from 'vue'
 import { useStore } from 'vuex'
 import throttle from 'lodash/throttle'
 import { State, MutationTypes } from '@/store'
@@ -215,6 +215,8 @@ export default defineComponent({
       ]
     }
 
+    provide('slideScale', canvasScale)
+
     return {
       elementList,
       activeElementIdList,

+ 9 - 2
src/views/Editor/Toolbar/ElementPositionPanel.vue

@@ -66,7 +66,7 @@
       <div class="row">
         <div style="flex: 3;">大小:</div>
         <InputNumber
-          :min="15"
+          :min="minSize"
           :max="1500"
           :step="5"
           :value="width"
@@ -83,7 +83,7 @@
         </template>
         <div style="flex: 1;" v-else></div>
         <InputNumber 
-          :min="15"
+          :min="minSize"
           :max="800"
           :step="5"
           :disabled="handleElement.type === 'text'" 
@@ -138,6 +138,7 @@ import { useStore } from 'vuex'
 import round from 'lodash/round'
 import { MutationTypes, State } from '@/store'
 import { PPTElement } from '@/types/slides'
+import { MIN_SIZE } from '@/configs/element'
 import useOrderElement from '@/hooks/useOrderElement'
 import useAlignElementToCanvas from '@/hooks/useAlignElementToCanvas'
 import useHistorySnapshot from '@/hooks/useHistorySnapshot'
@@ -155,6 +156,11 @@ export default defineComponent({
     const rotate = ref(0)
     const fixedRatio = ref(false)
 
+    const minSize = computed(() => {
+      if(!handleElement.value) return 20
+      return MIN_SIZE[handleElement.value.type] || 20
+    })
+
     watch(handleElement, () => {
       if(!handleElement.value) return
 
@@ -228,6 +234,7 @@ export default defineComponent({
       height,
       rotate,
       fixedRatio,
+      minSize,
       updateLeft,
       updateTop,
       updateWidth,

+ 2 - 2
src/views/Screen/ScreenElement.vue

@@ -23,7 +23,7 @@ import BaseImageElement from '@/views/components/element/ImageElement/BaseImageE
 import BaseTextElement from '@/views/components/element/TextElement/BaseTextElement.vue'
 import BaseShapeElement from '@/views/components/element/ShapeElement/BaseShapeElement.vue'
 import BaseLineElement from '@/views/components/element/LineElement/BaseLineElement.vue'
-import BaseChartElement from '@/views/components/element/ChartElement/BaseChartElement.vue'
+import ScreenChartElement from '@/views/components/element/ChartElement/ScreenChartElement.vue'
 
 export default defineComponent({
   name: 'screen-element',
@@ -48,7 +48,7 @@ export default defineComponent({
         [ElementTypes.TEXT]: BaseTextElement,
         [ElementTypes.SHAPE]: BaseShapeElement,
         [ElementTypes.LINE]: BaseLineElement,
-        [ElementTypes.CHART]: BaseChartElement,
+        [ElementTypes.CHART]: ScreenChartElement,
       }
       return elementTypeMap[props.elementInfo.type] || null
     })

+ 3 - 1
src/views/Screen/index.vue

@@ -59,7 +59,7 @@
 </template>
 
 <script lang="ts">
-import { computed, defineComponent, onMounted, onUnmounted, Ref, ref } from 'vue'
+import { computed, defineComponent, onMounted, onUnmounted, provide, Ref, ref } from 'vue'
 import { useStore } from 'vuex'
 import throttle from 'lodash/throttle'
 import { MutationTypes, State } from '@/store'
@@ -216,6 +216,8 @@ export default defineComponent({
         },
       ]
     }
+    
+    provide('slideScale', scale)
 
     return {
       slides,

+ 16 - 2
src/views/components/element/ChartElement/BaseChartElement.vue

@@ -13,13 +13,15 @@
         :height="elementInfo.height"
         :outline="elementInfo.outline"
       />
-      Chart
+      <IconChartLine fill="#d70206" strokeWidth="2" :size="size" v-if="elementInfo.chartType === 'line'" />
+      <IconChartHistogram fill="#d70206" strokeWidth="2" :size="size" v-else-if="elementInfo.chartType === 'bar'" />
+      <IconChartProportion fill="#d70206" strokeWidth="2" :size="size" v-else-if="elementInfo.chartType === 'pie'" />
     </div>
   </div>
 </template>
 
 <script lang="ts">
-import { defineComponent, PropType } from 'vue'
+import { computed, defineComponent, PropType } from 'vue'
 import { PPTChartElement } from '@/types/slides'
 
 import ElementOutline from '@/views/components/element/ElementOutline.vue'
@@ -35,6 +37,13 @@ export default defineComponent({
       required: true,
     },
   },
+  setup(props) {
+    const size = computed(() => Math.min(props.elementInfo.width, props.elementInfo.height))
+
+    return {
+      size,
+    }
+  },
 })
 </script>
 
@@ -46,5 +55,10 @@ export default defineComponent({
 .element-content {
   width: 100%;
   height: 100%;
+  opacity: .5;
+  background-color: rgba($color: #000, $alpha: .05);
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 </style>

+ 58 - 0
src/views/components/element/ChartElement/ScreenChartElement.vue

@@ -0,0 +1,58 @@
+<template>
+  <div class="screen-element-chart"
+    :style="{
+      top: elementInfo.top + 'px',
+      left: elementInfo.left + 'px',
+      width: elementInfo.width + 'px',
+      height: elementInfo.height + 'px',
+    }"
+  >
+    <div class="element-content">
+      <ElementOutline
+        :width="elementInfo.width"
+        :height="elementInfo.height"
+        :outline="elementInfo.outline"
+      />
+      <Chart
+        :width="elementInfo.width"
+        :height="elementInfo.height"
+        :type="elementInfo.chartType"
+        :data="elementInfo.data"
+        :options="elementInfo.options"
+      />
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, PropType } from 'vue'
+import { PPTChartElement } from '@/types/slides'
+
+import ElementOutline from '@/views/components/element/ElementOutline.vue'
+import Chart from '@/components/Chart.vue'
+
+export default defineComponent({
+  name: 'screen-element-chart',
+  components: {
+    ElementOutline,
+    Chart,
+  },
+  props: {
+    elementInfo: {
+      type: Object as PropType<PPTChartElement>,
+      required: true,
+    },
+  },
+})
+</script>
+
+<style lang="scss" scoped>
+.screen-element-chart {
+  position: absolute;
+}
+
+.element-content {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 9 - 1
src/views/components/element/ChartElement/index.vue

@@ -18,7 +18,13 @@
         :height="elementInfo.height"
         :outline="elementInfo.outline"
       />
-      Chart
+      <Chart
+        :width="elementInfo.width"
+        :height="elementInfo.height"
+        :type="elementInfo.chartType"
+        :data="elementInfo.data"
+        :options="elementInfo.options"
+      />
     </div>
   </div>
 </template>
@@ -29,11 +35,13 @@ import { PPTChartElement } from '@/types/slides'
 import { ContextmenuItem } from '@/components/Contextmenu/types'
 
 import ElementOutline from '@/views/components/element/ElementOutline.vue'
+import Chart from '@/components/Chart.vue'
 
 export default defineComponent({
   name: 'editable-element-chart',
   components: {
     ElementOutline,
+    Chart,
   },
   props: {
     elementInfo: {