|
@@ -11,7 +11,7 @@
|
|
|
|
|
|
const props = withDefaults(
|
|
|
defineProps<{
|
|
|
- chartData: Record<string, any> | Record<string, any>[];
|
|
|
+ chartData: Record<string, any>[] | Record<string, any>;
|
|
|
chartConfig: Config['moduleData']['chart']['0'];
|
|
|
height?: string;
|
|
|
width?: string;
|
|
@@ -25,27 +25,63 @@
|
|
|
|
|
|
const chartRef = ref<HTMLDivElement | null>(null);
|
|
|
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
|
|
|
+ function getData(raw, readFrom) {
|
|
|
+ if (readFrom) {
|
|
|
+ return get(raw, readFrom);
|
|
|
+ }
|
|
|
+ return raw;
|
|
|
+ }
|
|
|
|
|
|
// 核心方法,生成适用与 echart 的选项,这个方法需要适配 chart 类型的每种子类型
|
|
|
const genChartOption = () => {
|
|
|
// 依据每一个图表配置生成图表选项
|
|
|
- const { yAxis = [], xAxis = [], order, type, sortBy, series } = props.chartConfig;
|
|
|
- const isArray = Array.isArray(props.chartData);
|
|
|
+ const { yAxis = [], xAxis = [], legend, order, type, sortBy, series } = props.chartConfig;
|
|
|
+
|
|
|
+ let sorttedData: any[] = [];
|
|
|
+ if (Array.isArray(props.chartData)) {
|
|
|
+ sorttedData = props.chartData;
|
|
|
+ } else {
|
|
|
+ sorttedData = [props.chartData];
|
|
|
+ }
|
|
|
+ // 如果这个配置指明了需要排序则执行排序
|
|
|
+ if (sortBy && order) {
|
|
|
+ sorttedData.sort((pre, cur) => {
|
|
|
+ if (order === 'asc') {
|
|
|
+ return get(pre, sortBy, 0) - get(cur, sortBy, 0);
|
|
|
+ } else {
|
|
|
+ return get(cur, sortBy, 0) - get(pre, sortBy, 0);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- // 饼状图通常依赖单个对象的不同字段绘制
|
|
|
- if (type === 'pie' && !isArray) {
|
|
|
+ // 该项作为下面所有图表依赖的基准系列数据
|
|
|
+ const baseSeries: { name: string; data: { name: string; value: any }[] }[] = sorttedData.reduce((res: any[], baseData) => {
|
|
|
+ series.forEach((serie) => {
|
|
|
+ res.push({
|
|
|
+ name: getFormattedText(baseData, serie.label),
|
|
|
+ data: (getData(baseData, serie.readFrom) as any[]).map((data) => {
|
|
|
+ // return [getData(data, serie.xprop), getData(data, serie.yprop)]; /** x y */
|
|
|
+ return { name: getData(data, serie.xprop), value: getData(data, serie.yprop) }; /** x y */
|
|
|
+ }),
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ return res;
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ if (type === 'pie') {
|
|
|
return {
|
|
|
- legend: { show: false },
|
|
|
+ legend,
|
|
|
color: ['#d9a1ff', '#00d1ff', '#82fe78'],
|
|
|
- series: [
|
|
|
- {
|
|
|
+ series: baseSeries.map((serie) => {
|
|
|
+ return {
|
|
|
type: 'pie',
|
|
|
radius: ['50%', '75%'],
|
|
|
center: ['50%', '55%'],
|
|
|
- data: series.map((serie) => {
|
|
|
+ name: serie.name,
|
|
|
+ data: serie.data.map((serie) => {
|
|
|
return {
|
|
|
- name: serie.label,
|
|
|
- value: get(props.chartData, serie.prop, 0),
|
|
|
+ ...serie,
|
|
|
labelLine: { show: false },
|
|
|
label: { show: false },
|
|
|
itemStyle: {
|
|
@@ -54,48 +90,28 @@
|
|
|
},
|
|
|
};
|
|
|
}),
|
|
|
- },
|
|
|
- ],
|
|
|
+ };
|
|
|
+ }),
|
|
|
};
|
|
|
}
|
|
|
- if (!isArray) return {};
|
|
|
-
|
|
|
- let sorttedData = [...(props.chartData as any[])];
|
|
|
|
|
|
- // 如果这个配置指明了需要排序则执行排序
|
|
|
- if (sortBy && order) {
|
|
|
- sorttedData.sort((pre, cur) => {
|
|
|
- if (order === 'asc') {
|
|
|
- return get(pre, sortBy, 0) - get(cur, sortBy, 0);
|
|
|
- } else {
|
|
|
- return get(cur, sortBy, 0) - get(pre, sortBy, 0);
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- // 柱状图则要求使用数组形式的数据作依赖
|
|
|
+ // 柱状图
|
|
|
if (type === 'bar') {
|
|
|
return {
|
|
|
grid: {
|
|
|
top: 50,
|
|
|
- height: 150,
|
|
|
- },
|
|
|
- textStyle: {
|
|
|
- color: '#fff',
|
|
|
+ bottom: 30,
|
|
|
},
|
|
|
- legend: { show: false },
|
|
|
+ legend,
|
|
|
xAxis: xAxis.map((e) => {
|
|
|
return {
|
|
|
+ ...e,
|
|
|
type: 'category',
|
|
|
- data: sorttedData.map((d) => {
|
|
|
- return getFormattedText(d, e.label);
|
|
|
- }),
|
|
|
};
|
|
|
}),
|
|
|
yAxis: yAxis.map((e) => {
|
|
|
return {
|
|
|
- name: e.label,
|
|
|
- position: e.align,
|
|
|
+ ...e,
|
|
|
splitLine: {
|
|
|
lineStyle: {
|
|
|
color: '#ffffff',
|
|
@@ -104,17 +120,10 @@
|
|
|
},
|
|
|
};
|
|
|
}),
|
|
|
- series: series.reduce((curr: EChartsOption[], serie, index) => {
|
|
|
+ series: baseSeries.reduce((curr: EChartsOption[], serie, index) => {
|
|
|
const colors = ['#66ffff', '#ffff66'];
|
|
|
- // 系列选项,如果指定了x轴配置则以x轴配置优先
|
|
|
- const data = sorttedData.map((d) => {
|
|
|
- return {
|
|
|
- name: serie.label,
|
|
|
- value: get(d, serie.prop, 0),
|
|
|
- };
|
|
|
- });
|
|
|
curr.push({
|
|
|
- name: 'pictorial element',
|
|
|
+ ...serie,
|
|
|
type: 'pictorialBar',
|
|
|
symbol: 'circle',
|
|
|
symbolPosition: 'end',
|
|
@@ -124,13 +133,14 @@
|
|
|
itemStyle: {
|
|
|
color: colors[index % colors.length],
|
|
|
},
|
|
|
- data,
|
|
|
});
|
|
|
curr.push({
|
|
|
- name: 'reference bar',
|
|
|
+ ...serie,
|
|
|
type: 'bar',
|
|
|
silent: true,
|
|
|
yAxisIndex: index,
|
|
|
+ tooltip: { show: false },
|
|
|
+ barWidth: 8,
|
|
|
itemStyle: {
|
|
|
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
{ offset: 0, color: colors[index % colors.length] },
|
|
@@ -138,9 +148,6 @@
|
|
|
{ offset: 1, color: `${colors[index % colors.length]}22` },
|
|
|
]),
|
|
|
},
|
|
|
- tooltip: { show: false },
|
|
|
- barWidth: 8,
|
|
|
- data,
|
|
|
});
|
|
|
|
|
|
return curr;
|
|
@@ -152,16 +159,13 @@
|
|
|
if (type === 'line') {
|
|
|
return {
|
|
|
legend: {
|
|
|
+ show: legend.show,
|
|
|
top: 10,
|
|
|
right: 10,
|
|
|
textStyle: {
|
|
|
color: '#fff',
|
|
|
},
|
|
|
},
|
|
|
- // backgroundColor: '#081f33',
|
|
|
- textStyle: {
|
|
|
- color: '#fff',
|
|
|
- },
|
|
|
grid: {
|
|
|
left: 60,
|
|
|
top: 50,
|
|
@@ -170,16 +174,13 @@
|
|
|
},
|
|
|
xAxis: xAxis.map((e) => {
|
|
|
return {
|
|
|
+ ...e,
|
|
|
type: 'category',
|
|
|
- data: sorttedData.map((d) => {
|
|
|
- return getFormattedText(d, e.label);
|
|
|
- }),
|
|
|
};
|
|
|
}),
|
|
|
yAxis: yAxis.map((e) => {
|
|
|
return {
|
|
|
- name: e.label,
|
|
|
- position: e.align,
|
|
|
+ ...e,
|
|
|
splitLine: {
|
|
|
lineStyle: {
|
|
|
color: '#fff',
|
|
@@ -188,54 +189,39 @@
|
|
|
},
|
|
|
};
|
|
|
}),
|
|
|
- series: series.map((serie) => {
|
|
|
- const data = sorttedData.map((d) => {
|
|
|
- return {
|
|
|
- name: serie.label,
|
|
|
- value: get(d, serie.prop, 0),
|
|
|
- };
|
|
|
- });
|
|
|
-
|
|
|
+ series: baseSeries.map((serie) => {
|
|
|
return {
|
|
|
+ ...serie,
|
|
|
type: 'line',
|
|
|
- data,
|
|
|
};
|
|
|
}),
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- // 折线图和上面的柱状图类似
|
|
|
+ // 平滑曲线图
|
|
|
if (type === 'line_smooth') {
|
|
|
return {
|
|
|
legend: {
|
|
|
+ show: legend.show,
|
|
|
top: 10,
|
|
|
- right: 10,
|
|
|
textStyle: {
|
|
|
color: '#fff',
|
|
|
},
|
|
|
},
|
|
|
- // backgroundColor: '#081f33',
|
|
|
- textStyle: {
|
|
|
- color: '#fff',
|
|
|
- },
|
|
|
grid: {
|
|
|
- left: 50,
|
|
|
- top: 50,
|
|
|
- right: 50,
|
|
|
- bottom: 50,
|
|
|
+ left: 60,
|
|
|
+ right: 60,
|
|
|
+ bottom: 30,
|
|
|
},
|
|
|
xAxis: xAxis.map((e) => {
|
|
|
return {
|
|
|
+ ...e,
|
|
|
type: 'category',
|
|
|
- data: sorttedData.map((d) => {
|
|
|
- return getFormattedText(d, e.label);
|
|
|
- }),
|
|
|
};
|
|
|
}),
|
|
|
yAxis: yAxis.map((e) => {
|
|
|
return {
|
|
|
- name: e.label,
|
|
|
- position: e.align,
|
|
|
+ ...e,
|
|
|
splitLine: {
|
|
|
lineStyle: {
|
|
|
opacity: 0.3,
|
|
@@ -243,33 +229,23 @@
|
|
|
},
|
|
|
};
|
|
|
}),
|
|
|
- series: series.map((serie) => {
|
|
|
- const data = sorttedData.map((d) => {
|
|
|
- return {
|
|
|
- name: serie.label,
|
|
|
- value: get(d, serie.prop, 0),
|
|
|
- };
|
|
|
- });
|
|
|
-
|
|
|
+ series: baseSeries.map((serie) => {
|
|
|
return {
|
|
|
+ ...serie,
|
|
|
type: 'line',
|
|
|
- data,
|
|
|
smooth: true,
|
|
|
+ itemStyle: {
|
|
|
+ opacity: 0,
|
|
|
+ },
|
|
|
};
|
|
|
}),
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- // 折线图和上面的柱状图类似
|
|
|
+ // 折线区域图,即折线下面的区域填满
|
|
|
if (type === 'line_area') {
|
|
|
return {
|
|
|
- legend: {
|
|
|
- show: false,
|
|
|
- },
|
|
|
- // backgroundColor: '#081f33',
|
|
|
- textStyle: {
|
|
|
- color: '#fff',
|
|
|
- },
|
|
|
+ legend,
|
|
|
grid: {
|
|
|
left: 50,
|
|
|
top: 50,
|
|
@@ -278,17 +254,14 @@
|
|
|
},
|
|
|
xAxis: xAxis.map((e) => {
|
|
|
return {
|
|
|
+ ...e,
|
|
|
type: 'category',
|
|
|
boundaryGap: false,
|
|
|
- data: sorttedData.map((d) => {
|
|
|
- return getFormattedText(d, e.label);
|
|
|
- }),
|
|
|
};
|
|
|
}),
|
|
|
yAxis: yAxis.map((e) => {
|
|
|
return {
|
|
|
- name: e.label,
|
|
|
- position: e.align,
|
|
|
+ ...e,
|
|
|
splitLine: {
|
|
|
lineStyle: {
|
|
|
color: '#fff',
|
|
@@ -297,21 +270,14 @@
|
|
|
},
|
|
|
};
|
|
|
}),
|
|
|
- series: series.map((serie, index) => {
|
|
|
+ series: baseSeries.map((serie, index) => {
|
|
|
const colors = ['#66ffff', '#6666ff'];
|
|
|
- const data = sorttedData.map((d) => {
|
|
|
- return {
|
|
|
- name: serie.label,
|
|
|
- value: get(d, serie.prop, 0),
|
|
|
- };
|
|
|
- });
|
|
|
|
|
|
return {
|
|
|
+ ...serie,
|
|
|
type: 'line',
|
|
|
- data,
|
|
|
symbol: 'none',
|
|
|
endLabel: { distance: 0 },
|
|
|
- // itemStyle: { show: false, opacity: 0 },
|
|
|
lineStyle: { color: colors[index % colors.length] },
|
|
|
areaStyle: {
|
|
|
origin: 'auto',
|
|
@@ -329,16 +295,13 @@
|
|
|
if (type === 'line_bar') {
|
|
|
return {
|
|
|
legend: {
|
|
|
+ show: legend.show,
|
|
|
top: 10,
|
|
|
right: 10,
|
|
|
textStyle: {
|
|
|
color: '#fff',
|
|
|
},
|
|
|
},
|
|
|
- // backgroundColor: '#081f33',
|
|
|
- textStyle: {
|
|
|
- color: '#fff',
|
|
|
- },
|
|
|
grid: {
|
|
|
left: 40,
|
|
|
top: 50,
|
|
@@ -346,112 +309,28 @@
|
|
|
bottom: 10,
|
|
|
show: false,
|
|
|
},
|
|
|
- xAxis: xAxis.map(() => {
|
|
|
+ xAxis: xAxis.map((e) => {
|
|
|
return {
|
|
|
+ ...e,
|
|
|
type: 'category',
|
|
|
- show: false,
|
|
|
};
|
|
|
}),
|
|
|
- yAxis: yAxis.map(() => {
|
|
|
+ yAxis: yAxis.map((e) => {
|
|
|
return {
|
|
|
- show: false,
|
|
|
- // name: e.label,
|
|
|
- // position: e.align,
|
|
|
+ ...e,
|
|
|
};
|
|
|
}),
|
|
|
- series: series.map((serie, i) => {
|
|
|
- const data = sorttedData.map((d) => {
|
|
|
- return {
|
|
|
- name: serie.label,
|
|
|
- value: get(d, serie.prop, 0),
|
|
|
- label: {
|
|
|
- show: true,
|
|
|
- position: 'top',
|
|
|
- borderWidth: 0,
|
|
|
- color: '#fff',
|
|
|
- },
|
|
|
- itemStyle: {
|
|
|
- color: new graphic.LinearGradient(0, 0, 0, 1, [
|
|
|
- { offset: 0, color: '#66ffff' },
|
|
|
- { offset: 0.2, color: '#66ffff' },
|
|
|
- { offset: 1, color: '#66ffff22' },
|
|
|
- ]),
|
|
|
- },
|
|
|
- };
|
|
|
- });
|
|
|
-
|
|
|
+ series: baseSeries.map((serie, i) => {
|
|
|
return {
|
|
|
+ ...serie,
|
|
|
type: i % 2 ? 'line' : 'bar',
|
|
|
smooth: true,
|
|
|
- data,
|
|
|
barWidth: 20,
|
|
|
};
|
|
|
}),
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- // 折线图和上面的柱状图类似
|
|
|
- if (type === 'line_smooth_complex') {
|
|
|
- // 基于以下情况作处理:
|
|
|
- // 数据示例:arr1: [ { arr2: [ { val: 2 } ] } ]
|
|
|
- // readFrom 指向 arr1,即 sorttedData 指向 arr1,则 prop 配置应为 arr2|val
|
|
|
- const seriesData = sorttedData.map((data) => {
|
|
|
- const serie = series[0];
|
|
|
- const [p1, p2] = serie.prop.split('|');
|
|
|
- // 从 readFrom 指向的数组项中,取出指定的子数组
|
|
|
- const arr = get(data, p1);
|
|
|
-
|
|
|
- return {
|
|
|
- type: 'line',
|
|
|
- data: arr.map((e) => {
|
|
|
- return {
|
|
|
- name: getFormattedText(e, serie.label),
|
|
|
- value: get(e, p2, 0),
|
|
|
- };
|
|
|
- }),
|
|
|
- smooth: true,
|
|
|
- };
|
|
|
- });
|
|
|
-
|
|
|
- return {
|
|
|
- legend: {
|
|
|
- top: 10,
|
|
|
- right: 10,
|
|
|
- textStyle: {
|
|
|
- color: '#fff',
|
|
|
- },
|
|
|
- },
|
|
|
- // backgroundColor: '#081f33',
|
|
|
- textStyle: {
|
|
|
- color: '#fff',
|
|
|
- },
|
|
|
- grid: {
|
|
|
- left: 50,
|
|
|
- top: 50,
|
|
|
- right: 50,
|
|
|
- bottom: 50,
|
|
|
- },
|
|
|
- xAxis: xAxis.map(() => {
|
|
|
- return {
|
|
|
- type: 'category',
|
|
|
- data: get(seriesData, '[0].data', []).map((d: any) => d.name),
|
|
|
- };
|
|
|
- }),
|
|
|
- yAxis: yAxis.map((e) => {
|
|
|
- return {
|
|
|
- name: e.label,
|
|
|
- position: e.align,
|
|
|
- splitLine: {
|
|
|
- lineStyle: {
|
|
|
- opacity: 0.3,
|
|
|
- },
|
|
|
- },
|
|
|
- };
|
|
|
- }),
|
|
|
- series: seriesData,
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
return {};
|
|
|
};
|
|
|
|
|
@@ -467,7 +346,6 @@
|
|
|
|
|
|
function initCharts() {
|
|
|
const o = genChartOption();
|
|
|
- console.log('debug initchart');
|
|
|
setOptions(o as EChartsOption, false);
|
|
|
}
|
|
|
</script>
|