最近接到一个需求,要求在echarts label上显示一个进度条,这玩意首先想到的是富文本+图片,但是让ui切图的话又太蠢了点,而且从0%到100%以精度0.1来算的话得让ui画1001个图,实在不怎么优雅,然后想能不能前端自己画图呢,进度条用svg应该很好实现,只要把svg转base64就能在echarts中使用了
关键代码:svg转base64
const svgDom: HTMLElement = svg.value as HTMLElement;
const s: string = new XMLSerializer().serializeToString(svgDom);
const encodedData: string = BASE_CODE + window.btoa(s);
直接上代码,echarts容器:
<template>
<div class="main_page">
<div id="chart" ref="chartBox"></div>
<div v-for="item in svgList" :key="item" v-show="false">
<svg-process :rate="item" @emitBase64="emitBase64"></svg-process>
</div>
</div>
</template>
<script lang="ts" setup>
import { SvgType } from '@/types/svgInterface';
import { onMounted, ref } from 'vue';
import { EChartsOption, init } from 'echarts';
import SvgProcess from '@/components/SvgProcess.vue';
const svgArr: Array<number> = [];
const axisData = ['Mon', 'Tue', 'Wed', 'Very Loooong Thu', 'Fri', 'Sat', 'Sun'];
const base64Map = new Map();
const svgList = ref([]);
const chartBox = ref();
const emitBase64 = (item: SvgType) => {
base64Map.set(item.objKey, item.value);
};
const data = axisData.map(function (_, i) {
const rate:number = Math.round(Math.random() * 1000) / 10;
svgArr.push(rate);
const value = Math.round(Math.random() * 1000 * (i + 1));
return {
value,
rate,
label: {}
};
});
(svgList.value as Array<number>) = [...new Set(svgArr)];
const links = data.map(function (_, i) {
return {
source: i,
target: i + 1
};
});
const getBase64ByKey = (key: number) => {
return new Promise((resolve) => {
const timer = setInterval(() => {
if (base64Map.has(key)) {
resolve(base64Map.get(key));
clearInterval(timer);
}
}, 50);
});
};
links.pop();
const initChart = async () => {
const chartDom: HTMLElement = chartBox.value as HTMLElement;
const chart = init(chartDom);
for (let i = 0, len = data.length; i < len; i++) {
const rate = data[i].rate;
let base64 = '';
if (base64Map.has(rate)) {
base64 = base64Map.get(rate);
} else {
await getBase64ByKey(rate);
base64 = base64Map.get(rate);
}
data[i].label = {
show: true,
formatter: (args: { value: number; }) => {
return `{text|${args.value}}\n{bar|}`;
},
position: [2, 0],
rich: {
bar: {
backgroundColor: {
image: base64
}
},
text: {
padding: [0, 0, 0, 5],
align: 'left',
width: 30,
height: 40
}
}
};
}
svgList.value = [];
const option: EChartsOption = {
title: {
text: '以进度条作为label显示'
},
tooltip: {},
xAxis: {
type: 'category',
boundaryGap: false,
data: axisData
},
yAxis: {
type: 'value'
},
series: [
{
type: 'graph',
layout: 'none',
coordinateSystem: 'cartesian2d',
symbolSize: 40,
edgeSymbol: ['circle', 'arrow'],
edgeSymbolSize: [4, 10],
data,
links,
lineStyle: {
color: '#2f4554'
}
}
]
};
option && chart.setOption(option);
};
onMounted(() => {
initChart();
});
</script>
<style lang="less" scoped>
.main_page {
width: 100vw;
height: 100vh;
#chart {
width: 100%;
height: 100%;
}
}
</style>
svg转base64的组件SvgProcess
<template>
<svg width="120" height="20" viewBox="0 0 120 20" ref="svg">
<path
d="M 5 10 L 55 10"
stroke="#ddd"
stroke-width="8"
stroke-linecap="round">
</path>
<path
class="progress"
d="M 5 10 L 55 10"
stroke="rgb(64, 158, 255)"
stroke-dasharray="50px"
:stroke-dashoffset="svgRate"
stroke-width="8"
stroke-linecap="round">
</path>
<text x="70" y="15" fill="#333" font-size="14">{{ props.rate }}%</text>
</svg>
</template>
<script setup lang="ts">
import { SvgType } from '@/types/svgInterface';
import { onMounted, ref, defineProps, computed } from 'vue';
const emit = defineEmits(['emitBase64']);
const BASE_CODE: string = 'data:image/svg+xml;base64,';
const props = defineProps({
rate: {
type: Number,
default: 0
}
});
const svg = ref();
const svgRate = computed(() => {
return 50 - props.rate / 2 + 'px';
});
// 初始化时获取svg元素,然后把svg转成base64,再把数据传出去
onMounted(() => {
const svgDom: HTMLElement = svg.value as HTMLElement;
const s: string = new XMLSerializer().serializeToString(svgDom);
const encodedData: string = BASE_CODE + window.btoa(s);
const params: SvgType = {
objKey: props.rate,
value: encodedData
};
emit('emitBase64', params);
});
</script>
效果图