mirror of
https://github.com/pipipi-pikachu/PPTist.git
synced 2025-04-15 02:20:00 +08:00
refactor: 使用EChart重构图表元素
This commit is contained in:
parent
352ac2601d
commit
0a78197a1d
45
package-lock.json
generated
45
package-lock.json
generated
@ -14,6 +14,7 @@
|
||||
"clipboard": "^2.0.11",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dexie": "3.0.3",
|
||||
"echarts": "^5.5.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"hfmath": "^0.0.2",
|
||||
"html-to-image": "^1.11.11",
|
||||
@ -2317,6 +2318,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/echarts": {
|
||||
"version": "5.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz",
|
||||
"integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0",
|
||||
"zrender": "5.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@ -4933,6 +4943,11 @@
|
||||
"typescript": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||
},
|
||||
"node_modules/txml": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/txml/-/txml-5.1.1.tgz",
|
||||
@ -5250,6 +5265,14 @@
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz",
|
||||
"integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
|
||||
"dependencies": {
|
||||
"tslib": "2.3.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@ -6850,6 +6873,15 @@
|
||||
"is-obj": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"echarts": {
|
||||
"version": "5.5.1",
|
||||
"resolved": "https://registry.npmmirror.com/echarts/-/echarts-5.5.1.tgz",
|
||||
"integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==",
|
||||
"requires": {
|
||||
"tslib": "2.3.0",
|
||||
"zrender": "5.6.0"
|
||||
}
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@ -8929,6 +8961,11 @@
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
|
||||
"integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg=="
|
||||
},
|
||||
"txml": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmmirror.com/txml/-/txml-5.1.1.tgz",
|
||||
@ -9137,6 +9174,14 @@
|
||||
"resolved": "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"zrender": {
|
||||
"version": "5.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz",
|
||||
"integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==",
|
||||
"requires": {
|
||||
"tslib": "2.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@
|
||||
"clipboard": "^2.0.11",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dexie": "3.0.3",
|
||||
"echarts": "^5.5.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"hfmath": "^0.0.2",
|
||||
"html-to-image": "^1.11.11",
|
||||
|
54
src/configs/chart.ts
Normal file
54
src/configs/chart.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import type { ChartData } from '@/types/slides'
|
||||
|
||||
export const CHART_DEFAULT_DATA: { [key: string]: ChartData } = {
|
||||
'bar': {
|
||||
labels: ['类别1', '类别2', '类别3', '类别4', '类别5'],
|
||||
legends: ['系列1', '系列2'],
|
||||
series: [[12, 19, 5, 2, 18], [7, 11, 13, 21, 9]],
|
||||
},
|
||||
'column': {
|
||||
labels: ['类别1', '类别2', '类别3', '类别4', '类别5'],
|
||||
legends: ['系列1', '系列2'],
|
||||
series: [[12, 19, 5, 2, 18], [7, 11, 13, 21, 9]],
|
||||
},
|
||||
'line': {
|
||||
labels: ['类别1', '类别2', '类别3', '类别4', '类别5'],
|
||||
legends: ['系列1', '系列2'],
|
||||
series: [[12, 19, 5, 2, 18], [7, 11, 13, 21, 9]],
|
||||
},
|
||||
'pie': {
|
||||
labels: ['类别1', '类别2', '类别3', '类别4', '类别5'],
|
||||
legends: ['值'],
|
||||
series: [[12, 19, 5, 2, 18]],
|
||||
},
|
||||
'ring': {
|
||||
labels: ['类别1', '类别2', '类别3', '类别4', '类别5'],
|
||||
legends: ['值'],
|
||||
series: [[12, 19, 5, 2, 18]],
|
||||
},
|
||||
'area': {
|
||||
labels: ['类别1', '类别2', '类别3', '类别4', '类别5'],
|
||||
legends: ['系列1', '系列2'],
|
||||
series: [[12, 19, 5, 2, 18], [7, 11, 13, 21, 9]],
|
||||
},
|
||||
'scatter': {
|
||||
labels: ['坐标1', '坐标2', '坐标3', '坐标4', '坐标5'],
|
||||
legends: ['X', 'Y'],
|
||||
series: [[12, 19, 5, 2, 18], [7, 11, 13, 21, 9]],
|
||||
},
|
||||
}
|
||||
|
||||
export const CHART_PRESET_THEMES = [
|
||||
['#d87c7c', '#919e8b', '#d7ab82', '#6e7074', '#61a0a8', '#efa18d'],
|
||||
['#dd6b66', '#759aa0', '#e69d87', '#8dc1a9', '#ea7e53', '#eedd78'],
|
||||
['#516b91', '#59c4e6', '#edafda', '#93b7e3', '#a5e7f0', '#cbb0e3'],
|
||||
['#893448', '#d95850', '#eb8146', '#ffb248', '#f2d643', '#ebdba4'],
|
||||
['#4ea397', '#22c3aa', '#7bd9a5', '#d0648a', '#f58db2', '#f2b3c9'],
|
||||
['#3fb1e3', '#6be6c1', '#626c91', '#a0a7e6', '#c4ebad', '#96dee8'],
|
||||
['#fc97af', '#87f7cf', '#f7f494', '#72ccff', '#f7c5a0', '#d4a4eb'],
|
||||
['#c1232b', '#27727b', '#fcce10', '#e87c25', '#b5c334', '#fe8463'],
|
||||
['#2ec7c9', '#b6a2de', '#5ab1ef', '#ffb980', '#d87a80', '#8d98b3'],
|
||||
['#e01f54', '#001852', '#f5e8c8', '#b8d2c7', '#c6b38e', '#a4d8c2'],
|
||||
['#c12e34', '#e6b600', '#0098d9', '#2b821d', '#005eaa', '#339ca8'],
|
||||
['#8a7ca8', '#e098c7', '#8fd3e8', '#71669e', '#cc70af', '#7cb4cc'],
|
||||
]
|
@ -1,15 +0,0 @@
|
||||
import type { ChartType } from '@/types/slides'
|
||||
|
||||
interface ChartTypes {
|
||||
[propName: string]: ChartType
|
||||
}
|
||||
|
||||
export const CHART_TYPES: ChartTypes = {
|
||||
bar: 'bar',
|
||||
horizontalBar: 'bar',
|
||||
line: 'line',
|
||||
area: 'line',
|
||||
scatter: 'line',
|
||||
pie: 'pie',
|
||||
ring: 'pie',
|
||||
}
|
@ -2,10 +2,10 @@ import { storeToRefs } from 'pinia'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import { getImageSize } from '@/utils/image'
|
||||
import type { PPTLineElement, PPTElement, TableCell, TableCellStyle, PPTShapeElement, PPTChartElement, ChartOptions, PresetChartType } from '@/types/slides'
|
||||
import type { PPTLineElement, PPTElement, TableCell, TableCellStyle, PPTShapeElement, ChartType } from '@/types/slides'
|
||||
import { type ShapePoolItem, SHAPE_PATH_FORMULAS } from '@/configs/shapes'
|
||||
import type { LinePoolItem } from '@/configs/lines'
|
||||
import { CHART_TYPES } from '@/configs/chartTypes'
|
||||
import { CHART_DEFAULT_DATA } from '@/configs/chart'
|
||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||
|
||||
interface CommonElementPosition {
|
||||
@ -86,40 +86,19 @@ export default () => {
|
||||
* 创建图表元素
|
||||
* @param chartType 图表类型
|
||||
*/
|
||||
const createChartElement = (type: PresetChartType) => {
|
||||
const newElement: PPTChartElement = {
|
||||
const createChartElement = (type: ChartType) => {
|
||||
createElement({
|
||||
type: 'chart',
|
||||
id: nanoid(10),
|
||||
chartType: CHART_TYPES[type],
|
||||
chartType: type,
|
||||
left: 300,
|
||||
top: 81.25,
|
||||
width: 400,
|
||||
height: 400,
|
||||
rotate: 0,
|
||||
themeColor: [theme.value.themeColor],
|
||||
gridColor: theme.value.fontColor,
|
||||
data: {
|
||||
labels: ['类别1', '类别2', '类别3', '类别4', '类别5'],
|
||||
legends: ['系列1'],
|
||||
series: [
|
||||
[12, 19, 5, 2, 18],
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
const options: ChartOptions = {
|
||||
...(type === 'bar' ? { horizontalBars: false, stackBars: false } : {}),
|
||||
...(type === 'horizontalBar' ? { horizontalBars: true, stackBars: false } : {}),
|
||||
...(type === 'line' ? { showLine: true, lineSmooth: true, showArea: false } : {}),
|
||||
...(type === 'area' ? { showLine: true, lineSmooth: true, showArea: true } : {}),
|
||||
...(type === 'scatter' ? { showLine: false, lineSmooth: true, showArea: false } : {}),
|
||||
...(type === 'pie' ? { donut: false } : {}),
|
||||
...(type === 'ring' ? { donut: true } : {}),
|
||||
}
|
||||
|
||||
createElement({
|
||||
...newElement,
|
||||
options,
|
||||
themeColors: [theme.value.themeColor],
|
||||
textColor: theme.value.fontColor,
|
||||
data: CHART_DEFAULT_DATA[type],
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -611,12 +611,12 @@ export default () => {
|
||||
}
|
||||
|
||||
let chartColors: string[] = []
|
||||
if (el.themeColor.length === 10) chartColors = el.themeColor.map(color => formatColor(color).color)
|
||||
else if (el.themeColor.length === 1) chartColors = tinycolor(el.themeColor[0]).analogous(10).map(color => formatColor(color.toHexString()).color)
|
||||
if (el.themeColors.length === 10) chartColors = el.themeColors.map(color => formatColor(color).color)
|
||||
else if (el.themeColors.length === 1) chartColors = tinycolor(el.themeColors[0]).analogous(10).map(color => formatColor(color.toHexString()).color)
|
||||
else {
|
||||
const len = el.themeColor.length
|
||||
const supplement = tinycolor(el.themeColor[len - 1]).analogous(10 + 1 - len).map(color => color.toHexString())
|
||||
chartColors = [...el.themeColor.slice(0, len - 1), ...supplement].map(color => formatColor(color).color)
|
||||
const len = el.themeColors.length
|
||||
const supplement = tinycolor(el.themeColors[len - 1]).analogous(10 + 1 - len).map(color => color.toHexString())
|
||||
chartColors = [...el.themeColors.slice(0, len - 1), ...supplement].map(color => formatColor(color).color)
|
||||
}
|
||||
|
||||
const options: pptxgen.IChartOpts = {
|
||||
@ -624,40 +624,63 @@ export default () => {
|
||||
y: el.top / ratioPx2Inch.value,
|
||||
w: el.width / ratioPx2Inch.value,
|
||||
h: el.height / ratioPx2Inch.value,
|
||||
chartColors: el.chartType === 'pie' ? chartColors : chartColors.slice(0, el.data.series.length),
|
||||
chartColors: (el.chartType === 'pie' || el.chartType === 'ring') ? chartColors : chartColors.slice(0, el.data.series.length),
|
||||
}
|
||||
|
||||
if (el.fill) options.plotArea = { fill: { color: formatColor(el.fill).color } }
|
||||
if (el.legend) {
|
||||
const textColor = formatColor(el.textColor || '#000000').color
|
||||
options.catAxisLabelColor = textColor
|
||||
options.valAxisLabelColor = textColor
|
||||
|
||||
const fontSize = 14 / ratioPx2Pt.value
|
||||
options.catAxisLabelFontSize = fontSize
|
||||
options.valAxisLabelFontSize = fontSize
|
||||
|
||||
if (el.fill || el.outline) {
|
||||
const plotArea: pptxgen.IChartPropsFillLine = {}
|
||||
if (el.fill) {
|
||||
plotArea.fill = { color: formatColor(el.fill).color }
|
||||
}
|
||||
if (el.outline) {
|
||||
plotArea.border = {
|
||||
pt: el.outline.width! / ratioPx2Pt.value,
|
||||
color: formatColor(el.outline.color!).color,
|
||||
}
|
||||
}
|
||||
options.plotArea = plotArea
|
||||
}
|
||||
|
||||
if ((el.data.series.length > 1 && el.chartType !== 'scatter') || el.chartType === 'pie' || el.chartType === 'ring') {
|
||||
options.showLegend = true
|
||||
options.legendPos = el.legend === 'top' ? 't' : 'b'
|
||||
options.legendColor = formatColor(el.gridColor || '#000000').color
|
||||
options.legendFontSize = 14 / ratioPx2Pt.value
|
||||
options.legendPos = 'b'
|
||||
options.legendColor = textColor
|
||||
options.legendFontSize = fontSize
|
||||
}
|
||||
|
||||
let type = pptx.ChartType.bar
|
||||
if (el.chartType === 'bar') {
|
||||
type = pptx.ChartType.bar
|
||||
options.barDir = el.options?.horizontalBars ? 'bar' : 'col'
|
||||
options.barDir = 'col'
|
||||
}
|
||||
else if (el.chartType === 'column') {
|
||||
type = pptx.ChartType.bar
|
||||
options.barDir = 'bar'
|
||||
}
|
||||
else if (el.chartType === 'line') {
|
||||
if (el.options?.showArea) type = pptx.ChartType.area
|
||||
else if (el.options?.showLine === false) {
|
||||
type = pptx.ChartType.line
|
||||
}
|
||||
else if (el.chartType === 'area') {
|
||||
type = pptx.ChartType.area
|
||||
}
|
||||
else if (el.chartType === 'scatter') {
|
||||
type = pptx.ChartType.scatter
|
||||
|
||||
chartData.unshift({ name: 'X-Axis', values: Array(el.data.series[0].length).fill(0).map((v, i) => i) })
|
||||
options.lineSize = 0
|
||||
}
|
||||
else type = pptx.ChartType.line
|
||||
|
||||
if (el.options?.lineSmooth) options.lineSmooth = true
|
||||
}
|
||||
else if (el.chartType === 'pie') {
|
||||
if (el.options?.donut) {
|
||||
type = pptx.ChartType.doughnut
|
||||
options.holeSize = 75
|
||||
type = pptx.ChartType.pie
|
||||
}
|
||||
else type = pptx.ChartType.pie
|
||||
else if (el.chartType === 'ring') {
|
||||
type = pptx.ChartType.doughnut
|
||||
options.holeSize = 60
|
||||
}
|
||||
|
||||
pptxSlide.addChart(type, chartData, options)
|
||||
|
@ -14,7 +14,6 @@ import type {
|
||||
TableCellStyle,
|
||||
TableCell,
|
||||
ChartType,
|
||||
ChartOptions,
|
||||
SlideBackground,
|
||||
PPTShapeElement,
|
||||
PPTLineElement,
|
||||
@ -389,10 +388,9 @@ export default () => {
|
||||
let series: number[][]
|
||||
|
||||
if (el.chartType === 'scatterChart' || el.chartType === 'bubbleChart') {
|
||||
const data = el.data
|
||||
labels = data[0].map(item => item + '')
|
||||
legends = ['系列1']
|
||||
series = [data[1]]
|
||||
labels = el.data[0].map((item, index) => `坐标${index + 1}`)
|
||||
legends = ['X', 'Y']
|
||||
series = el.data
|
||||
}
|
||||
else {
|
||||
const data = el.data as ChartItem[]
|
||||
@ -401,32 +399,32 @@ export default () => {
|
||||
series = data.map(item => item.values.map(v => v.y))
|
||||
}
|
||||
|
||||
const options: ChartOptions = {}
|
||||
|
||||
let chartType: ChartType = 'bar'
|
||||
|
||||
switch (el.chartType) {
|
||||
case 'barChart':
|
||||
case 'bar3DChart':
|
||||
chartType = 'bar'
|
||||
if (el.barDir === 'bar') options.horizontalBars = true
|
||||
if (el.grouping === 'stacked' || el.grouping === 'percentStacked') options.stackBars = true
|
||||
if (el.barDir === 'bar') chartType = 'column'
|
||||
break
|
||||
case 'lineChart':
|
||||
case 'line3DChart':
|
||||
chartType = 'line'
|
||||
break
|
||||
case 'areaChart':
|
||||
case 'area3DChart':
|
||||
chartType = 'area'
|
||||
break
|
||||
case 'scatterChart':
|
||||
case 'bubbleChart':
|
||||
chartType = 'line'
|
||||
if (el.chartType === 'areaChart' || el.chartType === 'area3DChart') options.showArea = true
|
||||
if (el.chartType === 'scatterChart' || el.chartType === 'bubbleChart') options.showLine = false
|
||||
chartType = 'scatter'
|
||||
break
|
||||
case 'pieChart':
|
||||
case 'pie3DChart':
|
||||
case 'doughnutChart':
|
||||
chartType = 'pie'
|
||||
if (el.chartType === 'doughnutChart') options.donut = true
|
||||
break
|
||||
case 'doughnutChart':
|
||||
chartType = 'ring'
|
||||
break
|
||||
default:
|
||||
}
|
||||
@ -440,14 +438,13 @@ export default () => {
|
||||
left: el.left,
|
||||
top: el.top,
|
||||
rotate: 0,
|
||||
themeColor: [theme.value.themeColor],
|
||||
gridColor: theme.value.fontColor,
|
||||
themeColors: [theme.value.themeColor],
|
||||
textColor: theme.value.fontColor,
|
||||
data: {
|
||||
labels,
|
||||
legends,
|
||||
series,
|
||||
},
|
||||
options,
|
||||
})
|
||||
}
|
||||
else if (el.type === 'group' || el.type === 'diagram') {
|
||||
|
@ -395,9 +395,7 @@ export interface PPTLineElement extends Omit<PPTBaseElement, 'height' | 'rotate'
|
||||
}
|
||||
|
||||
|
||||
export type PresetChartType = 'bar' | 'horizontalBar' | 'line' | 'area' | 'scatter' | 'pie' | 'ring'
|
||||
export type ChartType = 'bar' | 'line' | 'pie'
|
||||
export type ChartOptions = LineChartOptions & BarChartOptions & PieChartOptions
|
||||
export type ChartType = 'bar' | 'column' | 'line' | 'pie' | 'ring' | 'area' | 'scatter'
|
||||
export interface ChartData {
|
||||
labels: string[]
|
||||
legends: string[]
|
||||
@ -415,26 +413,20 @@ export interface ChartData {
|
||||
*
|
||||
* data: 图表数据
|
||||
*
|
||||
* options?: 图表配置项
|
||||
*
|
||||
* outline?: 边框
|
||||
*
|
||||
* themeColor: 主题色
|
||||
* themeColors: 主题色
|
||||
*
|
||||
* gridColor?: 网格&坐标颜色
|
||||
*
|
||||
* legend?: 图例/位置
|
||||
* textColor?: 文字颜色
|
||||
*/
|
||||
export interface PPTChartElement extends PPTBaseElement {
|
||||
type: 'chart'
|
||||
fill?: string
|
||||
chartType: ChartType
|
||||
data: ChartData
|
||||
options?: ChartOptions
|
||||
outline?: PPTElementOutline
|
||||
themeColor: string[]
|
||||
gridColor?: string
|
||||
legend?: '' | 'top' | 'bottom'
|
||||
themeColors: string[]
|
||||
textColor?: string
|
||||
}
|
||||
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
<IconChartLine size="24" v-if="chart === 'line'" />
|
||||
<IconChartHistogram size="24" v-else-if="chart === 'bar'" />
|
||||
<IconChartPie size="24" v-else-if="chart === 'pie'" />
|
||||
<IconChartHistogramOne size="24" v-else-if="chart === 'horizontalBar'" />
|
||||
<IconChartHistogramOne size="24" v-else-if="chart === 'column'" />
|
||||
<IconChartLineArea size="24" v-else-if="chart === 'area'" />
|
||||
<IconChartRing size="24" v-else-if="chart === 'ring'" />
|
||||
<IconChartScatter size="24" v-else-if="chart === 'scatter'" />
|
||||
@ -15,15 +15,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { PresetChartType } from '@/types/slides'
|
||||
import type { ChartType } from '@/types/slides'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'select', payload: PresetChartType): void
|
||||
(event: 'select', payload: ChartType): void
|
||||
}>()
|
||||
|
||||
const chartList: PresetChartType[] = ['bar', 'horizontalBar', 'line', 'area', 'scatter', 'pie', 'ring']
|
||||
const chartList: ChartType[] = ['bar', 'column', 'line', 'area', 'scatter', 'pie', 'ring']
|
||||
|
||||
const selectChart = (chart: PresetChartType) => {
|
||||
const selectChart = (chart: ChartType) => {
|
||||
emit('select', chart)
|
||||
}
|
||||
</script>
|
||||
|
@ -6,63 +6,6 @@
|
||||
|
||||
<Divider />
|
||||
|
||||
<template v-if="handleChartElement.chartType === 'line'">
|
||||
<div class="row">
|
||||
<Checkbox
|
||||
@update:value="value => updateOptions({ showArea: value })"
|
||||
:value="showArea"
|
||||
style="flex: 1;"
|
||||
>面积图样式</Checkbox>
|
||||
<Checkbox
|
||||
@update:value="value => updateOptions({ showLine: value })"
|
||||
:value="!showLine"
|
||||
style="flex: 1;"
|
||||
>散点图样式</Checkbox>
|
||||
</div>
|
||||
<div class="row">
|
||||
<Checkbox
|
||||
@update:value="value => updateOptions({ lineSmooth: value })"
|
||||
:value="lineSmooth"
|
||||
>使用平滑曲线</Checkbox>
|
||||
</div>
|
||||
</template>
|
||||
<div class="row" v-if="handleChartElement.chartType === 'bar'">
|
||||
<Checkbox
|
||||
@update:value="value => updateOptions({ horizontalBars: value })"
|
||||
:value="horizontalBars"
|
||||
style="flex: 1;"
|
||||
>条形图样式</Checkbox>
|
||||
<Checkbox
|
||||
@update:value="value => updateOptions({ stackBars: value })"
|
||||
:value="stackBars"
|
||||
style="flex: 1;"
|
||||
>堆叠样式</Checkbox>
|
||||
</div>
|
||||
<div class="row" v-if="handleChartElement.chartType === 'pie'">
|
||||
<Checkbox
|
||||
@update:value="value => updateOptions({ donut: value })"
|
||||
:value="donut"
|
||||
>环形图样式</Checkbox>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div class="row">
|
||||
<div style="width: 40%;">图例:</div>
|
||||
<Select
|
||||
style="width: 60%;"
|
||||
:value="legend"
|
||||
@update:value="value => updateLegend(value as '' | 'top' | 'bottom')"
|
||||
:options="[
|
||||
{ label: '不显示', value: '' },
|
||||
{ label: '显示在上方', value: 'top' },
|
||||
{ label: '显示在下方', value: 'bottom' },
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div class="row">
|
||||
<div style="width: 40%;">背景填充:</div>
|
||||
<Popover trigger="click" style="width: 60%;">
|
||||
@ -76,21 +19,21 @@
|
||||
</Popover>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div style="width: 40%;">网格颜色:</div>
|
||||
<div style="width: 40%;">文字颜色:</div>
|
||||
<Popover trigger="click" style="width: 60%;">
|
||||
<template #content>
|
||||
<ColorPicker
|
||||
:modelValue="gridColor"
|
||||
@update:modelValue="value => updateGridColor(value)"
|
||||
:modelValue="textColor"
|
||||
@update:modelValue="value => updateTextColor(value)"
|
||||
/>
|
||||
</template>
|
||||
<ColorButton :color="gridColor" />
|
||||
<ColorButton :color="textColor" />
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div class="row" v-for="(color, index) in themeColor" :key="index">
|
||||
<div class="row" v-for="(color, index) in themeColors" :key="index">
|
||||
<div style="width: 40%;">{{index === 0 ? '主题配色:' : ''}}</div>
|
||||
<Popover trigger="click" style="width: 60%;">
|
||||
<template #content>
|
||||
@ -109,7 +52,7 @@
|
||||
<Popover trigger="click" v-model:open="presetThemesVisible" style="width: 40%;">
|
||||
<template #content>
|
||||
<div class="preset-themes">
|
||||
<div class="preset-theme" v-for="(item, index) in presetChartThemes" :key="index">
|
||||
<div class="preset-theme" v-for="(item, index) in CHART_PRESET_THEMES" :key="index">
|
||||
<div
|
||||
class="preset-theme-color"
|
||||
:class="{ 'select': presetThemeColorHoverIndex[0] === index && itemIndex <= presetThemeColorHoverIndex[1] }"
|
||||
@ -127,7 +70,7 @@
|
||||
</Popover>
|
||||
<Button
|
||||
last
|
||||
:disabled="themeColor.length >= 10"
|
||||
:disabled="themeColors.length >= 10"
|
||||
style="width: 60%;"
|
||||
@click="addThemeColor()"
|
||||
>
|
||||
@ -156,9 +99,10 @@
|
||||
import { onUnmounted, ref, watch, type Ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useMainStore, useSlidesStore } from '@/store'
|
||||
import type { ChartData, ChartOptions, PPTChartElement } from '@/types/slides'
|
||||
import type { ChartData, PPTChartElement } from '@/types/slides'
|
||||
import emitter, { EmitterEvents } from '@/utils/emitter'
|
||||
import useHistorySnapshot from '@/hooks/useHistorySnapshot'
|
||||
import { CHART_PRESET_THEMES } from '@/configs/chart'
|
||||
|
||||
import ElementOutline from '../../common/ElementOutline.vue'
|
||||
import ChartDataEditor from './ChartDataEditor.vue'
|
||||
@ -166,27 +110,10 @@ import ColorButton from '@/components/ColorButton.vue'
|
||||
import ColorPicker from '@/components/ColorPicker/index.vue'
|
||||
import Modal from '@/components/Modal.vue'
|
||||
import Divider from '@/components/Divider.vue'
|
||||
import Checkbox from '@/components/Checkbox.vue'
|
||||
import Button from '@/components/Button.vue'
|
||||
import ButtonGroup from '@/components/ButtonGroup.vue'
|
||||
import Select from '@/components/Select.vue'
|
||||
import Popover from '@/components/Popover.vue'
|
||||
|
||||
const presetChartThemes = [
|
||||
['#d87c7c', '#919e8b', '#d7ab82', '#6e7074', '#61a0a8', '#efa18d'],
|
||||
['#dd6b66', '#759aa0', '#e69d87', '#8dc1a9', '#ea7e53', '#eedd78'],
|
||||
['#516b91', '#59c4e6', '#edafda', '#93b7e3', '#a5e7f0', '#cbb0e3'],
|
||||
['#893448', '#d95850', '#eb8146', '#ffb248', '#f2d643', '#ebdba4'],
|
||||
['#4ea397', '#22c3aa', '#7bd9a5', '#d0648a', '#f58db2', '#f2b3c9'],
|
||||
['#3fb1e3', '#6be6c1', '#626c91', '#a0a7e6', '#c4ebad', '#96dee8'],
|
||||
['#fc97af', '#87f7cf', '#f7f494', '#72ccff', '#f7c5a0', '#d4a4eb'],
|
||||
['#c1232b', '#27727b', '#fcce10', '#e87c25', '#b5c334', '#fe8463'],
|
||||
['#2ec7c9', '#b6a2de', '#5ab1ef', '#ffb980', '#d87a80', '#8d98b3'],
|
||||
['#e01f54', '#001852', '#f5e8c8', '#b8d2c7', '#c6b38e', '#a4d8c2'],
|
||||
['#c12e34', '#e6b600', '#0098d9', '#2b821d', '#005eaa', '#339ca8'],
|
||||
['#8a7ca8', '#e098c7', '#8fd3e8', '#71669e', '#cc70af', '#7cb4cc'],
|
||||
]
|
||||
|
||||
const mainStore = useMainStore()
|
||||
const slidesStore = useSlidesStore()
|
||||
const { handleElement, handleElementId } = storeToRefs(mainStore)
|
||||
@ -202,42 +129,15 @@ const { addHistorySnapshot } = useHistorySnapshot()
|
||||
|
||||
const fill = ref<string>('#000')
|
||||
|
||||
const themeColor = ref<string[]>([])
|
||||
const gridColor = ref('')
|
||||
const legend = ref('')
|
||||
|
||||
const lineSmooth = ref(true)
|
||||
const showLine = ref(true)
|
||||
const showArea = ref(false)
|
||||
const horizontalBars = ref(false)
|
||||
const donut = ref(false)
|
||||
const stackBars = ref(false)
|
||||
const themeColors = ref<string[]>([])
|
||||
const textColor = ref('')
|
||||
|
||||
watch(handleElement, () => {
|
||||
if (!handleElement.value || handleElement.value.type !== 'chart') return
|
||||
fill.value = handleElement.value.fill || '#fff'
|
||||
|
||||
if (handleElement.value.options) {
|
||||
const {
|
||||
lineSmooth: _lineSmooth,
|
||||
showLine: _showLine,
|
||||
showArea: _showArea,
|
||||
horizontalBars: _horizontalBars,
|
||||
donut: _donut,
|
||||
stackBars: _stackBars,
|
||||
} = handleElement.value.options
|
||||
|
||||
lineSmooth.value = !!_lineSmooth
|
||||
showLine.value = !!_showLine
|
||||
showArea.value = !!_showArea
|
||||
horizontalBars.value = !!_horizontalBars
|
||||
donut.value = !!_donut
|
||||
stackBars.value = !!_stackBars
|
||||
}
|
||||
|
||||
themeColor.value = handleElement.value.themeColor
|
||||
gridColor.value = handleElement.value.gridColor || '#333'
|
||||
legend.value = handleElement.value.legend || ''
|
||||
themeColors.value = handleElement.value.themeColors
|
||||
textColor.value = handleElement.value.textColor || '#333'
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
const updateElement = (props: Partial<PPTChartElement>) => {
|
||||
@ -256,18 +156,10 @@ const updateFill = (value: string) => {
|
||||
updateElement({ fill: value })
|
||||
}
|
||||
|
||||
// 设置其他选项:柱状图转条形图、折线图转面积图、折线图转散点图、饼图转环形图、折线图开关平滑曲线
|
||||
const updateOptions = (optionProps: ChartOptions) => {
|
||||
const _handleElement = handleElement.value as PPTChartElement
|
||||
|
||||
const newOptions = { ..._handleElement.options, ...optionProps }
|
||||
updateElement({ options: newOptions })
|
||||
}
|
||||
|
||||
// 设置主题色
|
||||
const updateTheme = (color: string, index: number) => {
|
||||
const props = {
|
||||
themeColor: themeColor.value.map((c, i) => i === index ? color : c),
|
||||
themeColors: themeColors.value.map((c, i) => i === index ? color : c),
|
||||
}
|
||||
updateElement(props)
|
||||
}
|
||||
@ -275,34 +167,29 @@ const updateTheme = (color: string, index: number) => {
|
||||
// 添加主题色
|
||||
const addThemeColor = () => {
|
||||
const props = {
|
||||
themeColor: [...themeColor.value, theme.value.themeColor],
|
||||
themeColors: [...themeColors.value, theme.value.themeColor],
|
||||
}
|
||||
updateElement(props)
|
||||
}
|
||||
|
||||
// 使用预置主题配色
|
||||
const applyPresetTheme = (colors: string[], index: number) => {
|
||||
const themeColor = colors.slice(0, index + 1)
|
||||
updateElement({ themeColor })
|
||||
const themeColors = colors.slice(0, index + 1)
|
||||
updateElement({ themeColors })
|
||||
presetThemesVisible.value = false
|
||||
}
|
||||
|
||||
// 删除主题色
|
||||
const deleteThemeColor = (index: number) => {
|
||||
const props = {
|
||||
themeColor: themeColor.value.filter((c, i) => i !== index),
|
||||
themeColors: themeColors.value.filter((c, i) => i !== index),
|
||||
}
|
||||
updateElement(props)
|
||||
}
|
||||
|
||||
// 设置网格颜色
|
||||
const updateGridColor = (gridColor: string) => {
|
||||
updateElement({ gridColor })
|
||||
}
|
||||
|
||||
// 设置图例位置/不显示
|
||||
const updateLegend = (legend: '' | 'top' | 'bottom') => {
|
||||
updateElement({ legend })
|
||||
// 设置文字颜色
|
||||
const updateTextColor = (textColor: string) => {
|
||||
updateElement({ textColor })
|
||||
}
|
||||
|
||||
const openDataEditor = () => chartDataEditorVisible.value = true
|
||||
|
@ -23,16 +23,13 @@
|
||||
:outline="elementInfo.outline"
|
||||
/>
|
||||
<Chart
|
||||
:width="elementInfo.width * zoom"
|
||||
:height="elementInfo.height * zoom"
|
||||
:width="elementInfo.width"
|
||||
:height="elementInfo.height"
|
||||
:type="elementInfo.chartType"
|
||||
:data="elementInfo.data"
|
||||
:options="elementInfo.options"
|
||||
:themeColor="elementInfo.themeColor"
|
||||
:gridColor="elementInfo.gridColor"
|
||||
:themeColors="elementInfo.themeColors"
|
||||
:textColor="elementInfo.textColor"
|
||||
:legends="elementInfo.data.legends"
|
||||
:legend="elementInfo.legend || ''"
|
||||
:style="{ zoom: 1 / zoom }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -40,9 +37,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref } from 'vue'
|
||||
import type { PPTChartElement } from '@/types/slides'
|
||||
import { injectKeySlideScale } from '@/types/injectKey'
|
||||
|
||||
import ElementOutline from '@/views/components/element/ElementOutline.vue'
|
||||
import Chart from './Chart.vue'
|
||||
@ -50,11 +45,6 @@ import Chart from './Chart.vue'
|
||||
defineProps<{
|
||||
elementInfo: PPTChartElement
|
||||
}>()
|
||||
|
||||
const slideScale = inject(injectKeySlideScale) || ref(1)
|
||||
|
||||
const needScaleSize = computed(() => slideScale.value < 1)
|
||||
const zoom = computed(() => needScaleSize.value ? 1 / slideScale.value : 1)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -1,37 +1,13 @@
|
||||
<template>
|
||||
<div
|
||||
class="chart"
|
||||
:style="{ flexDirection: legend === 'top' ? 'column-reverse' : 'column' }"
|
||||
>
|
||||
<div
|
||||
class="chart-content"
|
||||
ref="chartRef"
|
||||
:style="{
|
||||
width: width + 'px',
|
||||
height: chartHeight + 'px',
|
||||
transform: `scale(${1 / slideScale})`,
|
||||
}"
|
||||
></div>
|
||||
<div class="legends" :style="{ transform: `scale(${1 / slideScale})` }" v-if="legend">
|
||||
<div
|
||||
class="legend"
|
||||
v-for="(legend, index) in legends"
|
||||
:key="index"
|
||||
:style="{ color: gridColor }"
|
||||
>
|
||||
<div class="block" :style="{ backgroundColor: themeColors[index] }"></div>
|
||||
{{legend}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart" ref="chartRef"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import { onMounted, ref, computed, watch } from 'vue'
|
||||
import * as echarts from 'echarts'
|
||||
import tinycolor from 'tinycolor2'
|
||||
import { BarChart, LineChart, PieChart } from 'chartist'
|
||||
import type { ChartData, ChartOptions, ChartType } from '@/types/slides'
|
||||
import { injectKeySlideScale } from '@/types/injectKey'
|
||||
import type { ChartData, ChartType } from '@/types/slides'
|
||||
import { getChartOption } from './chartOption'
|
||||
|
||||
import 'chartist/dist/index.css'
|
||||
|
||||
@ -40,178 +16,54 @@ const props = defineProps<{
|
||||
height: number
|
||||
type: ChartType
|
||||
data: ChartData
|
||||
themeColor: string[]
|
||||
themeColors: string[]
|
||||
legends: string[]
|
||||
options?: ChartOptions
|
||||
gridColor?: string
|
||||
legend?: '' | 'top' | 'bottom'
|
||||
textColor?: string
|
||||
}>()
|
||||
|
||||
let chart: echarts.ECharts | null = null
|
||||
const chartRef = ref<HTMLElement>()
|
||||
const slideScale = inject(injectKeySlideScale) || ref(1)
|
||||
|
||||
let chart: LineChart | BarChart | PieChart | undefined
|
||||
|
||||
const chartHeight = computed(() => {
|
||||
if (props.legend) return props.height - 20
|
||||
return props.height
|
||||
})
|
||||
|
||||
const getPieChartData = () => ({ ...props.data, series: props.data.series[0] })
|
||||
|
||||
const getOptions = () => {
|
||||
const propsOptopns = props.options || {}
|
||||
return {
|
||||
...propsOptopns,
|
||||
width: props.width * slideScale.value,
|
||||
height: chartHeight.value * slideScale.value,
|
||||
}
|
||||
}
|
||||
|
||||
const renderChart = () => {
|
||||
if (!chartRef.value) return
|
||||
|
||||
const options = getOptions()
|
||||
if (props.type === 'bar') chart = new BarChart(chartRef.value, props.data, options)
|
||||
if (props.type === 'line') chart = new LineChart(chartRef.value, props.data, options)
|
||||
if (props.type === 'pie') chart = new PieChart(chartRef.value, getPieChartData(), options)
|
||||
}
|
||||
|
||||
const updateChart = () => {
|
||||
nextTick(() => {
|
||||
if (!chart) {
|
||||
renderChart()
|
||||
return
|
||||
}
|
||||
const options = getOptions()
|
||||
const data = props.type === 'pie' ? getPieChartData() : props.data
|
||||
chart.update(data, options)
|
||||
})
|
||||
}
|
||||
|
||||
watch([
|
||||
() => props.width,
|
||||
() => props.height,
|
||||
() => props.data,
|
||||
() => props.options,
|
||||
slideScale,
|
||||
], updateChart)
|
||||
|
||||
onMounted(renderChart)
|
||||
|
||||
const themeColors = computed(() => {
|
||||
let colors: string[] = []
|
||||
if (props.themeColor.length >= 10) colors = props.themeColor
|
||||
else if (props.themeColor.length === 1) colors = tinycolor(props.themeColor[0]).analogous(10).map(color => color.toRgbString())
|
||||
if (props.themeColors.length >= 10) colors = props.themeColors
|
||||
else if (props.themeColors.length === 1) colors = tinycolor(props.themeColors[0]).analogous(10).map(color => color.toRgbString())
|
||||
else {
|
||||
const len = props.themeColor.length
|
||||
const supplement = tinycolor(props.themeColor[len - 1]).analogous(10 + 1 - len).map(color => color.toRgbString())
|
||||
colors = [...props.themeColor.slice(0, len - 1), ...supplement]
|
||||
const len = props.themeColors.length
|
||||
const supplement = tinycolor(props.themeColors[len - 1]).analogous(10 + 1 - len).map(color => color.toRgbString())
|
||||
colors = [...props.themeColors.slice(0, len - 1), ...supplement]
|
||||
}
|
||||
return colors
|
||||
})
|
||||
|
||||
// 更新主题配色:
|
||||
// 如果当前所设置的主题色数小于10,剩余部分获取最后一个主题色的相近颜色作为配色
|
||||
const updateTheme = () => {
|
||||
if (!chartRef.value) return
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
chartRef.value.style.setProperty(`--theme-color-${i + 1}`, themeColors.value[i])
|
||||
}
|
||||
const updateOption = () => {
|
||||
const option = getChartOption({
|
||||
type: props.type,
|
||||
data: props.data,
|
||||
themeColors: themeColors.value,
|
||||
textColor: props.textColor,
|
||||
})
|
||||
if (option) chart!.setOption(option, true)
|
||||
}
|
||||
|
||||
watch(themeColors, updateTheme)
|
||||
onMounted(updateTheme)
|
||||
onMounted(() => {
|
||||
chart = echarts.init(chartRef.value, null, { renderer: 'svg' })
|
||||
updateOption()
|
||||
|
||||
// 更新网格颜色,包括坐标的文字部分
|
||||
const updateGridColor = () => {
|
||||
if (!chartRef.value) return
|
||||
if (props.gridColor) chartRef.value.style.setProperty(`--grid-color`, props.gridColor)
|
||||
}
|
||||
const resizeListener = () => chart!.resize()
|
||||
const resizeObserver = new ResizeObserver(resizeListener)
|
||||
resizeObserver.observe(chartRef.value!)
|
||||
})
|
||||
|
||||
watch(() => props.gridColor, updateGridColor)
|
||||
onMounted(updateGridColor)
|
||||
watch(() => props.type, updateOption)
|
||||
watch(() => props.data, updateOption)
|
||||
watch(() => props.themeColors, updateOption)
|
||||
watch(() => props.textColor, updateOption)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chart {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chart-content {
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.chart-content {
|
||||
$ct-series-names: (a, b, c, d, e, f, g, h, i, j);
|
||||
|
||||
--theme-color-1: #666;
|
||||
--theme-color-2: #666;
|
||||
--theme-color-3: #666;
|
||||
--theme-color-4: #666;
|
||||
--theme-color-5: #666;
|
||||
--theme-color-6: #666;
|
||||
--theme-color-7: #666;
|
||||
--theme-color-8: #666;
|
||||
--theme-color-9: #666;
|
||||
--theme-color-10: #666;
|
||||
|
||||
@for $i from 1 to length($ct-series-names) {
|
||||
$color: var(--theme-color-#{$i});
|
||||
|
||||
.ct-series-#{nth($ct-series-names, $i)} .ct-line {
|
||||
stroke: $color;
|
||||
}
|
||||
.ct-series-#{nth($ct-series-names, $i)} .ct-point {
|
||||
stroke: $color;
|
||||
}
|
||||
.ct-series-#{nth($ct-series-names, $i)} .ct-area {
|
||||
fill: $color;
|
||||
}
|
||||
.ct-series-#{nth($ct-series-names, $i)} .ct-bar {
|
||||
stroke: $color;
|
||||
}
|
||||
.ct-series-#{nth($ct-series-names, $i)} .ct-slice-pie {
|
||||
fill: $color;
|
||||
}
|
||||
.ct-series-#{nth($ct-series-names, $i)} .ct-slice-donut {
|
||||
stroke: $color;
|
||||
}
|
||||
}
|
||||
|
||||
--grid-color: rgba(0, 0, 0, 0.4);
|
||||
|
||||
.ct-grid {
|
||||
stroke: var(--grid-color);
|
||||
}
|
||||
.ct-label {
|
||||
fill: var(--grid-color);
|
||||
color: var(--grid-color);
|
||||
}
|
||||
}
|
||||
|
||||
.legends {
|
||||
height: 20px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
.legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
& + .legend {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.block {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
276
src/views/components/element/ChartElement/chartOption.ts
Normal file
276
src/views/components/element/ChartElement/chartOption.ts
Normal file
@ -0,0 +1,276 @@
|
||||
import * as echarts from 'echarts'
|
||||
import type { ChartData, ChartType } from '@/types/slides'
|
||||
|
||||
export interface ChartOptionPayload {
|
||||
type: ChartType
|
||||
data: ChartData
|
||||
themeColors: string[]
|
||||
textColor?: string
|
||||
}
|
||||
|
||||
export const getChartOption = ({
|
||||
type,
|
||||
data,
|
||||
themeColors,
|
||||
textColor,
|
||||
}: ChartOptionPayload): echarts.EChartsOption | null => {
|
||||
if(type === 'bar') {
|
||||
return {
|
||||
color: themeColors,
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
legend: data.series.length > 1 ? {
|
||||
top: 'bottom',
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
} : undefined,
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: data.labels,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: data.series.map((item, index) => ({
|
||||
data: item,
|
||||
name: data.legends[index],
|
||||
type: 'bar',
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
})),
|
||||
}
|
||||
}
|
||||
if(type === 'column') {
|
||||
return {
|
||||
color: themeColors,
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
legend: data.series.length > 1 ? {
|
||||
top: 'bottom',
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
} : undefined,
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: data.labels,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: data.series.map((item, index) => ({
|
||||
data: item,
|
||||
name: data.legends[index],
|
||||
type: 'bar',
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
})),
|
||||
}
|
||||
}
|
||||
if(type === 'line') {
|
||||
return {
|
||||
color: themeColors,
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
legend: data.series.length > 1 ? {
|
||||
top: 'bottom',
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
} : undefined,
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: data.labels,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: data.series.map((item, index) => ({
|
||||
data: item,
|
||||
name: data.legends[index],
|
||||
type: 'line',
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
})),
|
||||
}
|
||||
}
|
||||
if(type === 'pie') {
|
||||
return {
|
||||
color: themeColors,
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
top: 'bottom',
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: data.series[0].map((item, index) => ({ value: item, name: data.labels[index] })),
|
||||
label: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
type: 'pie',
|
||||
radius: '70%',
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowOffsetX: 0,
|
||||
shadowColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
if(type === 'ring') {
|
||||
return {
|
||||
color: themeColors,
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
top: 'bottom',
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: data.series[0].map((item, index) => ({ value: item, name: data.labels[index] })),
|
||||
label: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
avoidLabelOverlap: false,
|
||||
itemStyle: {
|
||||
borderRadius: 10,
|
||||
borderColor: '#fff',
|
||||
borderWidth: 2
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold'
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
if(type === 'area') {
|
||||
return {
|
||||
color: themeColors,
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
legend: data.series.length > 1 ? {
|
||||
top: 'bottom',
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
} : undefined,
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: data.labels,
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
},
|
||||
series: data.series.map((item, index) => ({
|
||||
data: item,
|
||||
name: data.legends[index],
|
||||
type: 'line',
|
||||
areaStyle: {},
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
})),
|
||||
}
|
||||
}
|
||||
if(type === 'scatter') {
|
||||
const formatedData = []
|
||||
for(let i = 0; i < data.series[0].length; i++) {
|
||||
const x = data.series[0][i]
|
||||
const y = data.series[1] ? data.series[1][i] : x
|
||||
formatedData.push([x, y])
|
||||
}
|
||||
|
||||
return {
|
||||
color: themeColors,
|
||||
textStyle: textColor ? {
|
||||
color: textColor,
|
||||
} : {},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
axisPointer: {
|
||||
type: 'shadow',
|
||||
},
|
||||
},
|
||||
xAxis: {},
|
||||
yAxis: {},
|
||||
series: [
|
||||
{
|
||||
symbolSize: 12,
|
||||
data: formatedData,
|
||||
type: 'scatter',
|
||||
}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
@ -32,11 +32,9 @@
|
||||
:height="elementInfo.height"
|
||||
:type="elementInfo.chartType"
|
||||
:data="elementInfo.data"
|
||||
:options="elementInfo.options"
|
||||
:themeColor="elementInfo.themeColor"
|
||||
:gridColor="elementInfo.gridColor"
|
||||
:themeColors="elementInfo.themeColors"
|
||||
:textColor="elementInfo.textColor"
|
||||
:legends="elementInfo.data.legends"
|
||||
:legend="elementInfo.legend || ''"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user