添加基础的图表元素

This commit is contained in:
pipipi-pikachu 2021-01-16 15:05:57 +08:00
parent ae4d82e701
commit f79712eef3
12 changed files with 280 additions and 13 deletions

108
src/components/Chart.vue Normal file
View File

@ -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>

View File

@ -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,
}

View File

@ -1,6 +1,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: {

View File

@ -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;

View File

@ -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>

View File

@ -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,

View File

@ -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,

View File

@ -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
})

View File

@ -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,

View File

@ -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>

View File

@ -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>

View File

@ -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: {