You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

321 lines
8.7 KiB
Vue

<script setup lang="ts">
import { onMounted, onUnmounted, ref, watch, nextTick } from "vue";
import * as echarts from "echarts";
// 定义组件接收的props
const props = defineProps({
xData: {
type: Array as PropType<Array<string>>,
default: () => [],
},
legendArr: {
type: Array as PropType<Array<string>>,
default: () => [],
},
datas: {
type: Array as PropType<Array<Array<number>>>,
default: () => [],
},
colorArr: {
type: Array as PropType<Array<Array<string>>>,
default: () => [],
},
});
const chartContainer = ref<HTMLDivElement | null>(null);
let chartInstance: echarts.ECharts | null = null;
// 自定义3D形状
const CubeLeft = echarts.graphic.extendShape({
shape: { x: 0, y: 0 },
buildPath: (ctx, shape) => {
const { xAxisPoint } = shape;
const [c0, c1, c2, c3] = [
[shape.x, shape.y],
[shape.x - 12, shape.y - 6],
[xAxisPoint[0] - 12, xAxisPoint[1] - 6],
[xAxisPoint[0], xAxisPoint[1]],
];
ctx
.moveTo(c0[0], c0[1])
.lineTo(c1[0], c1[1])
.lineTo(c2[0], c2[1])
.lineTo(c3[0], c3[1])
.closePath();
},
});
const CubeRight = echarts.graphic.extendShape({
shape: { x: 0, y: 0 },
buildPath: (ctx, shape) => {
const { xAxisPoint } = shape;
const [c1, c2, c3, c4] = [
[shape.x, shape.y],
[xAxisPoint[0], xAxisPoint[1]],
[xAxisPoint[0] + 12, xAxisPoint[1] - 6],
[shape.x + 12, shape.y - 6],
];
ctx
.moveTo(c1[0], c1[1])
.lineTo(c2[0], c2[1])
.lineTo(c3[0], c3[1])
.lineTo(c4[0], c4[1])
.closePath();
},
});
const CubeTop = echarts.graphic.extendShape({
shape: { x: 0, y: 0 },
buildPath: (ctx, shape) => {
const [c1, c2, c3, c4] = [
[shape.x, shape.y],
[shape.x + 12, shape.y - 6],
[shape.x, shape.y - 12],
[shape.x - 12, shape.y - 6],
];
ctx
.moveTo(c1[0], c1[1])
.lineTo(c2[0], c2[1])
.lineTo(c3[0], c3[1])
.lineTo(c4[0], c4[1])
.closePath();
},
});
// 注册三个面图形
echarts.graphic.registerShape("CubeLeft", CubeLeft);
echarts.graphic.registerShape("CubeRight", CubeRight);
echarts.graphic.registerShape("CubeTop", CubeTop);
// 初始化图表
const initChart = () => {
if (!chartContainer.value) return;
chartInstance = echarts.init(chartContainer.value);
setTimeout(() => {
chartInstance?.resize();
updateChart();
}, 500);
};
// 更新图表配置
const updateChart = () => {
if (!chartInstance) return;
const series = props.datas
.map((item, index) => [
{
type: "custom",
name: props.legendArr[index],
renderItem: (params, api) => ({
type: "group",
x: (index - props.datas.length / 2) * 30 + 15,
children: [
{
type: "CubeLeft",
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: api.coord([api.value(0), api.value(1)])[0],
y: api.coord([api.value(0), api.value(1)])[1],
xAxisPoint: api.coord([api.value(0), 0]),
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: props.colorArr[index % props.colorArr.length][1],
},
{
offset: 1,
color: props.colorArr[index % props.colorArr.length][0],
},
]),
},
},
{
type: "CubeRight",
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: api.coord([api.value(0), api.value(1)])[0],
y: api.coord([api.value(0), api.value(1)])[1],
xAxisPoint: api.coord([api.value(0), 0]),
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: props.colorArr[index % props.colorArr.length][1],
},
{
offset: 1,
color: props.colorArr[index % props.colorArr.length][0],
},
]),
},
},
{
type: "CubeTop",
shape: {
api,
xValue: api.value(0),
yValue: api.value(1),
x: api.coord([api.value(0), api.value(1)])[0],
y: api.coord([api.value(0), api.value(1)])[1],
xAxisPoint: api.coord([api.value(0), 0]),
},
style: {
fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: props.colorArr[index % props.colorArr.length][1],
},
{
offset: 1,
color: props.colorArr[index % props.colorArr.length][1],
},
]),
},
},
],
}),
data: item,
},
{
type: "bar",
name: props.legendArr[index],
barWidth: 25,
// label: {
// normal: {
// show: true,
// position: "top",
// fontSize: 16,
// color: "#fff",
// offset: [0, -10],
// },
// },
itemStyle: { color: "transparent" },
data: item,
yAxisIndex: 0, // 使用左y轴
},
{
type: "bar",
name: props.legendArr[index],
barWidth: 25,
itemStyle: { color: "transparent" },
data: item,
yAxisIndex: 1, // 使用左y轴
},
])
.flat();
chartInstance.setOption({
tooltip: {
trigger: "axis",
borderWidth: 0,
backgroundColor: "rgba(8,36,68,.9)",
color: "#fff",
textStyle: { color: "#fff" },
formatter: (params) => {
let str = params[0].name + "</br>";
params.forEach((item, index) => {
if (item.seriesType === "custom") {
str += `
<div style='display:flex;justify-content:space-between;align-items:center'>
<div style='margin-right:20px;'>
<span style="display:inline-block;width:10px;height:10px;border-radius:5px;background-color:${
props.colorArr[index % props.colorArr.length][0]
}"></span>
&nbsp;${item.seriesName}
</div>
<span>&nbsp;${item.value ? item.value : "-"}</span>
</div>`;
}
});
return str;
},
},
grid: {
left: "5%",
right: "5%",
top: "5%",
bottom: "10%",
containLabel: true,
},
legend: {
left: "center",
top: "90%",
itemWidth: 12, // 图例项宽度
itemHeight: 8,
textStyle: { color: "#fff", fontSize: 12 },
data: props.legendArr.map((name, index) => ({
name,
textStyle: { color: "#fff", fontSize: 12 },
itemStyle: { color: props.colorArr[index % props.colorArr.length][1] },
})),
},
xAxis: {
type: "category",
data: props.xData,
axisLine: { lineStyle: { color: "rgba(239, 247, 253, .1)" } },
axisLabel: { fontSize: 12, color: "#fff", margin: 12 },
},
yAxis: [
{
// name: "kWh",
// nameTextStyle: { color: "#fff", fontSize: 12 },
splitLine: {
lineStyle: { type: "dashed", color: "rgba(80,112,242,0.3)" },
},
axisLabel: { textStyle: { color: "#8C8C8C" }, fontSize: 12 },
// axisLine: { lineStyle: { color: "#8C8C8C" } },
// scale: true, // 同步刻度
},
{
// name: "kWh",
// nameTextStyle: { color: "#fff", fontSize: 12 },
splitLine: { lineStyle: { color: "transparent" } },
axisLabel: { textStyle: { color: "#8C8C8C" }, fontSize: 12 },
// axisLine: { lineStyle: { color: "#8C8C8C" } },
position: "right",
// scale: true, // 同步刻度
},
],
series,
});
};
const handleResize = () => {
chartInstance?.resize();
};
// 生命周期管理
onMounted(async () => {
await nextTick();
initChart();
});
onUnmounted(() => {
if (chartInstance) {
chartInstance.dispose();
chartInstance = null;
chartInstance?.resize();
}
});
// 响应式更新
watch(
() => props,
async () => {
await nextTick();
updateChart();
// delay(600).then(() => resize());
},
{
deep: true,
immediate: true,
}
);
</script>
<template>
<div ref="chartContainer" style="width: 100%; height: 100%" />
</template>