在项目中有产品需要使用带有样式的柱状统计图,查找echarts并没有可以设置的样式,因此通过svg标签来实现,记录自己的编写过程,防止忘记。
统计图样式如下,需要有椭圆样式以及渐变色(背景可以svg标签外包div通过设置css背景图片实现)
1.html部分
<svg id = "tongji" width = "690" :height='tongheight'>
<def id = "defs"></def>
</svg>
2.js部分开始进行绘制统计图
// 定义统计图数据
const = formData = ref(
list:[
{
name:'数据1',
increase:100, //柱状数据
zhuheight:20, //柱状条高度
upcolor1:'#ffb583', // 涨幅渐变色1
upcolor2:'#ff755f', // 涨幅渐变色2
downcolor1:'#94BFBB', // 跌幅渐变色1
downcolor2:'#7B8F93', // 跌幅渐变色2
tuocolor1:'#fbe5c0', // 正值椭圆颜色配置
tuocolor2:'#D2E0DF' // 负值椭圆颜色配置
},
...
]
);
// 定义svg画布的高度 (统计图数据的高低定义动态画布高度)
const = tongheight = ref(0);
// 定义绘制方法
const = drawColum = () => {
// 找出一组数据中的最小值和最大值,方便确定svg的画布高度
let min = Math.min.apply(Math,formData.value.list.map(item =>{
return item.zhuheight
}));
let max = Math.min.apply(Math,formData.value.list.map(item =>{
return item.zhuheight
}));
// 根据最大值和最小值确定画布高度
if (min <= 0 && max > 0) {
tongheight.value= Math.abs(min)+max
console.log("数值存在负值时,取最大和最小值的绝对值之和");
}else if (min > 0 && max > 0) {
tongheight.value=max
console.log("数值只有正值时,取最大值");
} else if(max < 0 && min < 0){
tongheight.value=Math.abs(min)
console.log("数值只有负值时,取最小值的绝对值");
}
// 获取svg标签
let childs = document.getElementById('tongjitu');
// 数据更改,统计图进行更新时,需将上一张绘制好的统计图进行销毁,否则会造成无用标签的逐步增加,多张统计图重叠在一起
if (document.getElementById('defs').childNodes.length>0) { // 判断def标签中是否存在颜色过滤器
let child = document.getElementById('defs');
for (let i = child.childNodes.length-1;i>=0; i--) {
// 删除def标签中柱状图颜色过滤器
if (child.childNodes[i].nodeName=='linearGradient') {
document.getElementById('defs').removeChild(child.children[i])
}
}
}
// 删除柱状条、数据显示、修饰样式(如ellipse标签:椭圆标签)、x轴
if (childs.childNodes.length>1) {
for (let i = childs.childNodes.length-1;i>=0; i--) {
// rect为矩形标签
if (childs.childNodes[i].nodeName=='rect') {
childs.removeChild(childs.children[i])
}
}
for (let j = childs.childNodes.length-1;j>=0; j--) {
// text为字体标签
if (childs.childNodes[j].nodeName=='text') {
childs.removeChild(childs.children[j])
}
}
for (let a = childs.childNodes.length-1;a>=0; a--) {
// ellipse为椭圆标签
if (childs.childNodes[a].nodeName=='ellipse') {
childs.removeChild(childs.children[a])
}
}
for (let a = childs.childNodes.length-1;a>=0; a--) {
// g包裹的为X轴
if (childs.childNodes[a].nodeName=='g') {
childs.removeChild(childs.children[a])
}
}
}
// 开始进行统计图的绘制
var data = formData.value.list; // 获取统计图数据
var w = 690; // 定义宽度
var h = 600; // 定义高度
// 计算每个柱子的高度
// (w-200)确定第一个柱状条距离x轴起始点的最小距离,需要统计图更靠左边
var colwidth = (w-200)/(data.length*2+1);
// 开始设置每个柱状条的样式规格
for(var i = 0; i < data.length; i++){
var obj = data[i];
var rw = colwidth; // 柱子宽度
var rh =Math.abs(obj.zhuheight)*3; // 柱子高度
var x = 110 + colwidth * (2 * i + 1); // 柱状条在X轴的位置
var y = 600 -48 - rh; // 柱状条在Y轴的位置
// 设置渐变色,线性渐变,数据为正值和负值时确定两个颜色组成渐变色
var color =
`<linearGradient id = 'g${i}' x1 = '0' y1 = '0' x2 = '0' y2 = '100%'>
<stop offset = '0' stop-color = '${obj.increase>0?obj.upcolor1:obj.downcolor1}'></stop>
<stop offset = '100%' stop-color = '${obj.increase>0?obj.upcolor2:obj.downcolor2}'></stop>
</linearGradient>`;
document.getElementById('defs').innerHTML += color;
// 动态创建矩形
var rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
rect.setAttribute('width',rw); // 柱状条宽度
rect.setAttribute('height',rh); // 柱状条高度
rect.setAttribute('x',x); // 柱状条在X轴的起始位置
// y轴位置设置成动态显示
// y轴根据柱状条的高度为正值还是负值进行判断起始位置,(3.71*(100-max))此计算公式是根据我所绘制的统计图得来,有其他需求可以10个单位为数值进行换算
rect.setAttribute('y',obj.zhuheight>0?y-3.71*(100-max):y+rh-4-3.71*(100-max));
rect.setAttribute('rx','5'); // 柱状条的圆角设置
rect.setAttribute('ry','5'); // 柱状条的圆角设置
rect.setAttribute('fill',`url(#g${i})`); // 配置对应的线性渐变颜色,最开始设置的
document.getElementById('tongjitu').appendChild(rect); // 将配置好的柱状条加入到标签中
// 动态创建椭圆样式 (椭圆样式根据需要进行绘制)
var ellipse =document.createElementNS('http://www.w3.org/2000/svg','ellipse');
ellipse.setAttribute('rx',rw/2); // rx椭圆水平半径为柱状条宽度的一半
ellipse.setAttribute('ry','8'); // ry为椭圆的高度
ellipse.setAttribute('cx',x+rw/2); // cx椭圆位置为柱状条位置再多出一半的距离
// y轴位置设置成动态显示,与矩形在y轴的距离一样,根据样式进行位置的调整(我这里负值时在位置上进行了‘-7’的处理)
ellipse.setAttribute('cy',obj.zhuheight>0?y-3.71*(100-max):y+rh*2-3.71*(100-max)-7);
// 椭圆的颜色配置
ellipse.setAttribute('fill',`${obj.zhuheight>0?obj.tuocolor1:obj.tuocolor2}`);
document.getElementById('tongjitu').appendChild(ellipse);
// 动态数据显示
var txt = document.createElementNS("http://www.w3.org/2000/svg", "text");
txt.setAttribute("x", x-10);
// y轴位置设置成动态显示,与椭圆样式思路一样,需要在原有位置基础上添加或者减去一段距离
txt.setAttribute("y", obj.increase>0?y-20-3.71*(100-max):y+rh*2-3.71*(100-max)+35);
txt.innerHTML = obj.increase+'%';
txt.setAttribute('font-size',i==0?28:26) // 根据需求定义字体大小,加粗,颜色
txt.setAttribute('font-weight',i==0?700:400)
txt.setAttribute("style", i==0?'fill:#e8843c':'fill:#888888' );
document.getElementById('tongjitu').appendChild(txt);
}
// 动态设置统计图的X轴(注:因每次绘制后先前标签会被删除,浏览器从上往下识别,柱状条会遮盖住X轴,为保持X轴永远在柱状条之上,进行动态添加设置)
var g = document.createElementNS('http://www.w3.org/2000/svg','g');
g.setAttribute('stroke','#cccccc');
g.setAttribute('stroke-width',5); //设置轴线的宽度,因柱状条不支持单独两个圆角的设置,使用X轴遮挡住处在轴线上的柱状条的圆角样式
g.setAttribute('id','g'); // 设置唯一值id
document.getElementById('tongjitu').appendChild(g);
// 设置X轴轴线
var line = document.createElementNS('http://www.w3.org/2000/svg','line');
line.setAttribute('x1','30');
line.setAttribute('y1',min<=0?`${550-(100-max)*3.71}`:`${550-3.71*(100-tongheight.value)}`);
line.setAttribute('x2','660');
line.setAttribute('y2',min<=0?`${550-(100-max)*3.71}`:`${550-3.71*(100-tongheight.value)}`);
document.getElementById('g').appendChild(line);
}
// 调用绘制方法
onBeforeMount(() => {
drawColum();
});