一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第23天,点击查看活动详情。
我正在参加 码上掘金体验活动,详情:show出你的创意代码块
前言
上篇文章利用canvas实现了一个简单的柱状图,本文我们来写一版svg的。
具体实现
首先,我们基于代码的bug率来判断一个人写的代码质量。来回顾一下数据格式。
数据格式
const data = [
{name: 'zhangsan', one: 1, two: 1, three: 0, four: 2, lines: 900},
{name: 'lisi', one: 0, two: 0, three: 1, four: 1, lines: 300},
{name: 'wangwu', one: 1, two: 0, three: 6, four: 0, lines: 1000}
]
对数据进行处理
然后就是对数据的简单处理,变成一堆我们需要的数组,和canvas步骤相同,话不多说,直接上代码。
// 条形图的宽度
const chartWidth = 480;
// 条形图的高度
const chartHeight = 300;
// 条形图的外边距
const margin = 15;
// 容器的宽度
const containerWidth = chartWidth + margin * 2;
// 容器的高度
const containerHeight = chartHeight + margin * 2;
/*
布局
*/
// 将需要编码的属性对应的值提取出来
const names = Array.from(data, item => item.name);
const values = Array.from(data, item => {
const value = item.one * 5 + item.two * 3 + item.three * 1 + item.four * 0.1
const result = Math.floor(value*1000/item.lines)
return result
});
const indices = Array.from(data, (item,index) => index);
// 计算每一个条左下顶点的横坐标,位置和在数组中的index有关
const step = chartWidth / names.length;
const barWidth = step * 0.8;
const xs = Array.from(indices, i => i * step);
// 计算每一个条左下顶点的纵坐标,因为所有条底部都是对齐的,所以就是图表的高度
const y = chartHeight;
/*
映射
*/
// 获得每一个条的高度,条的高度应该和 value 线性相关
const vmax = Math.max(...values);
const barHeights = Array.from(values, item => chartHeight * (item / vmax));
//获得每一个条的颜色
const nameColor = {
'zhangsan': "#5b8ff9",
'lisi': "#61ddaa",
'wangwu': "#65789b"
};
const colors = Array.from(names, item => nameColor[item])
svg
接下来就是svg渲染了。回顾一下svg,里面有g元素,rect元素,text元素等。所以我们需要一个函数帮助我们创建这些元素。
function createSVGElement(type) {
return document.createElementNS("http://www.w3.org/2000/svg", type);
}
接下来就是渲染画布了,我们需要一个g元素来帮助我们处理柱状图。
const svg = document.getElementById("container-svg");
// 设置 svg 的坐标原点和大小
svg.setAttribute("width", containerWidth);
svg.setAttribute("height", containerHeight);
svg.setAttribute("viewBox", [0, 0, containerWidth, containerHeight]);
// 创建一个 g 元素用于平移
const g = createSVGElement("g");
g.setAttribute("transform", `translate(${margin}, ${margin})`);
svg.appendChild(g)
接下来就是将一条一条的柱子放进去。
for(const index of indices){
// 取得对应的属性
const color = colors[index]
const x = xs[index];
const barHeight = barHeights[index];
const value = values[index];
const xname = names[index]
// 绘制条
const rect = createSVGElement("rect");
rect.setAttribute("x", x);
rect.setAttribute("y", y - barHeight);
rect.setAttribute("fill", color);
rect.setAttribute("width", barWidth);
rect.setAttribute("height", barHeight);
g.appendChild(rect)
// 绘制值
const text = createSVGElement("text");
text.textContent = value
text.setAttribute("text-anchor", "middle");
text.setAttribute("fill", "white");
text.setAttribute("font-familyr", "PingFangSC-Regular, sans-serif");
text.setAttribute("font-size", 25);
text.setAttribute("alignment-baseline", "middle");
text.setAttribute("x", x + barWidth / 2);
text.setAttribute("y", y - barHeight / 2);
g.appendChild(text)
const xtext = createSVGElement("text");
xtext.textContent = xname
xtext.setAttribute("text-anchor", "middle");
xtext.setAttribute("fill", "black");
xtext.setAttribute("font-familyr", "PingFangSC-Regular, sans-serif");
xtext.setAttribute("font-size", 25);
xtext.setAttribute("alignment-baseline", "middle");
xtext.setAttribute("x", x + barWidth / 2);
xtext.setAttribute("y", y - 20);
g.appendChild(xtext)
}
来看下最终效果吧。嗯,和canvas渲染一致。
完整代码如下:
总结
本次实现了柱状图的svg版本,和canvas比起来,处理数据步骤都相同,对于canvas来说,不用频繁创建dom节点。写起来比较省事,对于svg来说,不必担心图像迷糊问题,写起来比较放心。