用法
<Chart :type="['Bar']" :dataSet="dataSet" />
Chart 组件
<template>
<div ref="chartRef" class="chart"></div>
</template>
<script>
import BaseECharts from "@/components/Charts/baseChart";
import _ from "lodash";
export default {
name: "Chart",
props: {
type: {
type: Array,
default: () => [],
required: true,
},
dataSet: {
type: Object,
default: () => ({
xAxisData: [],
seriesData: [],
seriesName: [],
}),
},
beforeRender: {
type: Function,
default: () => {},
},
},
data() {
return {
chart: null,
chartOptions: {},
};
},
watch: {
dataSet: {
handler(nVal) {
setTimeout(() => {
const dataSet = _.cloneDeep(nVal);
// dataSetTransformToOptions 将data和 echarts options 关联
const op = this.chart.dataSetTransformToOptions(dataSet);
this.chartOptions = this.beforeRender ? this.beforeRender(op) : op;
this.chart.setOption(op, true);
});
},
deep: true,
},
},
mounted() {
this.chart = new BaseECharts(this.$refs.chartRef, this.type);
},
};
</script>
BaseECharts class 实例化图表
import * as echarts from "echarts";
import BaseOptionsManage from "@/components/Charts/baseChartOptionManage";
import { CHART_TYPE_ENUM } from "@/components/Charts/options";
class BaseECharts extends BaseOptionsManage {
constructor($el, type) {
super(type);
let myChart = echarts.getInstanceByDom($el)
? echarts.getInstanceByDom($el)
: echarts.init($el);
window.addEventListener("resize", function () {
myChart.resize();
});
this.charts = myChart;
setTimeout(() => myChart.resize());
setTimeout(() => {
myChart.setOption(CHART_TYPE_ENUM[_.head(this.type)].baseOptions, true);
});
}
setOption(op) {
this.charts.setOption(op);
setTimeout(() => {
this.charts.resize();
}, 10);
}
getOption() {
if (!this.charts) {
return null;
}
return this.charts.getOption();
}
}
export default BaseECharts;
options class
import _ from "lodash";
import { CHART_TYPE_ENUM } from "@/components/Charts/options";
import Utils from "./utils";
class BaseOptionsManage {
constructor(type) {
this.type = type;
}
mergeDataToOptions(dataSet) {
const [selfType, ...resetTypes] = this.type;
const selfChart = _.head(
Utils.chartGenerator(selfType, true).map((c) => {
return {
...c,
chart: {
...c.chart,
// 生成指令
drawCalls: Utils.drawGenerator(
c.chart.drawCalls,
dataSet.seriesData.length
),
},
};
})
);
const charts = Utils.chartGenerator(resetTypes).map((c) => {
return {
...c,
};
});
// 整合 series
const selfBaseOptions = _.set(selfChart.chart.baseOptions, "series", [
...selfChart.chart.baseOptions.series,
...charts,
]);
const drawCalls = selfChart.chart.drawCalls;
//运行指令
drawCalls.forEach((draw) => {
const { key, call, type } = draw;
// 解析指令
const index = key.match(/\[(.*?)\]/)?.[1] || 0;
if (key.includes("formatter")) {
const keyOptions = _.get(selfBaseOptions, key);
_.set(selfBaseOptions, key, (params) =>
// 将上下文回传
call({
options: keyOptions,
data: dataSet.seriesData[index],
dataSet,
index,
params,
})
);
} else {
const keyOptions = _.get(selfBaseOptions, key);
// 将上下文回传
_.set(
selfBaseOptions,
key,
call({
options: keyOptions,
data: dataSet.seriesData[index],
dataSet,
index,
})
);
}
});
return selfBaseOptions;
}
mergeData(dataSet) {
return this.mergeDataToOptions(dataSet);
}
dataSetTransformToOptions(
dataSet = {
xAxisData: [],
seriesData: [],
seriesName: [],
}
) {
const options = this.mergeData(dataSet);
return options;
}
}
export default BaseOptionsManage;
bar图定义
import { BaseDrawCalls } from "@/components/Charts/options/draw";
import { thousandSignNumber } from "@/utils/numeral";
import _ from "lodash";
import Utils from "../utils";
const baseOptions = {
color: ["#74A7FF"],
tooltip: {
trigger: "axis",
padding: 0,
backgroundColor: "rgba(255,255,255,0.95);",
axisPointer: {
type: "line",
lineStyle: {
width: "99",
type: "solid",
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "rgba(255,255,255,0.1)", // 0% 处的颜色
},
{
offset: 1,
color: "rgba(152,152,152,0.1)", // 100% 处的颜色
},
],
},
},
},
},
legend: {
itemWidth: 8,
itemHeight: 8,
show: true,
left: 0,
},
grid: {
left: 0,
right: 0,
bottom: 0,
containLabel: true,
top: 52,
},
xAxis: [
{
type: "category",
data: [],
axisTick: {
alignWithLabel: true,
lineStyle: {
color: "#8c8c8c",
},
},
axisLabel: {
margin: 13,
color: "#8c8c8c",
},
},
],
yAxis: [
{
type: "value",
axisTick: {
alignWithLabel: true,
},
axisLabel: {
color: "#8c8c8c",
},
splitLine: {
lineStyle: {
type: "dashed",
color: "#F0F0F0",
},
},
},
],
series: [
{
type: "bar",
data: [],
barWidth: 16,
splitLine: {
lineStyle: {
type: "dashed",
color: "#74A7FF",
},
},
},
],
};
// 指令集
const drawCalls = [
{
key: "series[Index].data",
type: Array,
call: (context) => {
return context.data;
},
},
{
key: "series[Index].name",
type: Array,
call: (context) => {
const { index } = context;
return context.dataSet.seriesName[index];
},
},
{
key: "xAxis[0].data",
call: (context) => {
return context.dataSet.xAxisData;
},
},
{
key: "yAxis[Index].min",
call: (context) => {
return 0;
},
},
{
key: "yAxis[Index].interval",
call: (context) => {
const [seriesData] = context.dataSet.seriesData;
const { interval } = Utils.formatAxis([...seriesData]);
return interval;
},
},
{
key: "yAxis[Index].max",
call: (context) => {
const [seriesData] = context.dataSet.seriesData;
const { max } = Utils.formatAxis([...seriesData]);
return max;
},
},
{
key: "tooltip.formatter",
call: (context) => {
const html = context.params.reduce((prev, series) => {
const { axisValue, color, seriesName, data } = series;
prev.x = axisValue;
if (prev.values?.length) {
prev.values.push({ ...data, seriesName, color });
} else {
prev.values = [];
prev.values.push({ ...data, seriesName, color });
}
return prev;
}, {});
return `<div class="wly-charts-tooltip-container">
<div class="x-axis">${html.x}</div>
${html?.values
?.map(({ value, seriesName, color, unit }) => {
const [int, float] = String(value).split(".");
return `<div class="row"><span class="circle" style="background-color:${color}"></span> <span class="text">${seriesName}</span><span class="value">${
value && float
? `${thousandSignNumber(int)?.value}.${float}`
: value
}</span> <span class="unit">${unit ? unit : ""}</span>
</div>`;
}, "")
.join("\n")}
</div>
`;
},
},
];
export default {
drawCalls,
baseOptions,
};
工具库
import _ from "lodash";
import { CHART_TYPE_ENUM } from "@/components/Charts/options";
const formatAxis = (axis) => {
const absAxis = axis.map((item) => Number(item.value));
const max = Math.max(...absAxis);
const min = Math.min(...absAxis);
if (max <= 5) {
return {
max: 5,
interval: 1,
};
}
const exponent = String(Math.floor(max)).length;
if (exponent < 3) {
return {
max: 100,
interval: 20,
};
}
if (min < 0) {
const twoNegativeDigit = String(Math.abs(Math.floor(min))).slice(0, 2);
const NegativeExponent = String(Math.floor(min)).length;
const minOfYaxis = 10 ** (NegativeExponent - 3);
const minNumber = (Number(twoNegativeDigit) + 1) * minOfYaxis;
const twoDigit = String(Math.floor(max + minNumber)).slice(0, 2);
const exponent = String(Math.floor(max)).length;
const maxOfYaxis = 10 ** (exponent - 2);
const maxNumber =
(Number(twoDigit) + 1) * maxOfYaxis + (minNumber < 1 ? 5 : minNumber);
return {
min: minNumber < 1 ? -5 : -minNumber,
max:
(Number(twoDigit) + 1) * maxOfYaxis < 1
? 5
: (Number(twoDigit) + 1) * maxOfYaxis,
interval: (maxNumber < 1 ? 5 : maxNumber) / 5,
};
} else {
const twoDigit = String(Math.floor(max)).slice(0, 2);
const exponent = String(Math.floor(max)).length;
const maxOfYaxis = 10 ** (exponent - 2);
return {
max: (Number(twoDigit) + 1) * maxOfYaxis,
interval: ((Number(twoDigit) + 1) * maxOfYaxis) / 5,
};
}
};
// 指令生成
const drawGenerator = (drawCalls, number) => {
const dcs = _.cloneDeep(drawCalls);
const result = [];
while (dcs.length) {
const item = dcs.pop();
if (item.key.includes("[Index]")) {
new Array(number).fill(0).forEach((_, index) => {
result.push({
...item,
key: `${item.key.replace("[Index]", `[${index}]`)}`,
});
});
} else {
result.push({
...item,
});
}
}
return result;
};
// 提取series
const chartGenerator = (types, isSelf = false) => {
if (isSelf) {
return [
{
input: types,
chart: CHART_TYPE_ENUM[types],
},
];
} else {
return types.map((type) => {
return _.head(CHART_TYPE_ENUM[type].baseOptions.series);
});
}
};
export default {
formatAxis,
drawGenerator,
chartGenerator,
};