介绍
通常画可视化图的工具很多,除了d3.js,还有echarts.js等。
d3.js: 国外工程师做的,英文文档,不太好理解,本质是svg。例子丰富,但是没有细致的分类,所以找起来比较费劲。
https://github.com/d3/d3/wiki/Gallery
ECharts.js: 百度做的,中文文档,容易理解,基于HTML5的图形库,本质是canvas。例子也比较丰富,方便参考和开发。
https://echarts.baidu.com/echarts2/doc/example.html
通过比较,看起来ECharts.js更容易上手,但是因为我需要更灵活更符合个性定制化的工具,所以选了d3.js。
3D饼图(齿轮图)
经过一段时间的磨炼,从折线图、闭合路径图、蜂窝图、直角坐标、极坐标都玩了个遍。
那这次就来个3D的吧,其实d3.js做3D的图不是很容易的,有更好的选择,但我认准了d3.js,一条道走到黑吧(想起高中数学老师说的话,当你解题解到一半时发现有更好的办法,不,赶紧忘掉,接着当前的方法,只要方法没错,总能解出来,也许会傻一点,但是一定会有正确的结果;如果中途放弃,也许另一个方法更快更聪明,但也许更慢或者错误,不算到最后,谁都不知道谁最准确。我选择相信他的话,于是。。。我成了程序员O(∩_∩)O哈哈~)。
站在巨人肩上
有人鄙视拿来主义,要我说,你能拿来那是你的本事,如果还能在此基础上做出更好的东西,何乐而不为呢?
每个人时间有限,每个项目也有deadline,不可能从每一个螺丝钉怎么拧开始学起,不然怎么会有那么多五花八门的框架,会有封装好的组件和接口,正因为有人已经做了前期工作,所以时间才能省下来做更有意义的事情,这就是站在巨人肩上的道理所在吧。
但是我们得明白拿来的东西的原理,以及出了问题该怎么解决的能力。然后才能做出更厉害的东西。
谁是巨人
首选当然是官网的例子咯,目测搜了一圈,终于找到一个3D Donut。就是你了,我的巨人。
把该地址的donut3d.js拷贝下来作为画3D饼图的基础js,待会会在此基础上修改,以满足我的要求(长的像齿轮的要求)。
http://bl.ocks.org/NPashaP/9994181
那我们就一睹她的芳容吧。
如果这张图符合你的要求,那就打住,不用往下看了,直接看官网例子即可。
注意d3版本的问题,如果你用d3.v3.js,恭喜你,啥也不用改,直接拿来用;如果你用d3.v5.js,那稍微改下方法,比如d3.v5.js没有d3.layout,所以d3.layout.pie改成d3.pie。我就是那个不幸的人,用的d3.v5.js。没关系,改起来很快,运行下,看哪里有错,就改哪里,O(∩_∩)O哈哈~so easy!
如何添加自己的需求
还是先上个我已经改好之后的3D饼图(齿轮图)吧,方便说明。
其实显示的时候是个动态的,一节一节显示出齿轮的。
背景是黑乎乎的,据说现在流行黑乎乎的背景,显得有科技感,技术也要赶时髦啊,我这么fashion的人,做出来的东西也要fashion啊O(∩_∩)O~
分析:
- 显示百分比文字。
- 有好几个椭圆,空心椭圆和实心椭圆。
- 齿轮中间有缝隙,怎么实现这个缝隙呢?很容易想到的,透明嘛,transparent。
- 中间有了缝隙之后,那齿轮还完整吗?当中间没有缝隙时,看官网的原图,仔细看,发现其实只实现了3个面:上面、外面和里面;而有了缝隙后,看我做的图,一块立体小齿轮有6个面,想象下魔方,是不是6个面?那从实现和视觉效果上来说,不需要6个面完整实现,但是至少需要实现5个面:上面、外面、里面、左侧面和右侧面。
从以上分析可以看出,难啃的骨头在第4点。这个图断断续续花了3天时间才搞定,为啥是断断续续呢,因为还有其他工作要做嘛,你懂得。
那就按顺序一条一条实现,总有一天我们的愿望都能实现!
svg
首先新建svg及设置宽高。
var donutChart = document.getElementById(self.id);
if (!donutChart)
return
var width = donutChart.clientWidth;
var height = donutChart.clientHeight;
var pdata = 80; // 显示80%
donutChart.innerHTML = ""
var svg = d3.select("#" + self.id).append("svg")
.attr("width", width)
.attr("height", height)
.append("g");
显示百分比文字
// 画百分比
svg.append("text")
.attr("x", width / 2 - 20) //文本的起始x坐标
.attr("y", height / 6) //文本的起始x坐标
.attr("class", "percent_text")
.text(pdata + "%");
画椭圆
// 画椭圆
svg.append("ellipse")
.attr("cx", 0)
.attr("cy", 0)
.attr("rx", width / 2 - 20)
.attr("ry", height / 4)
.attr("class", "circle_line")
.attr("transform", "translate(" + width / 2 + "," + height * 1.2 / 2 + ")");
svg.append("ellipse")
.attr("cx", 0)
.attr("cy", 0)
.attr("rx", width / 8)
.attr("ry", height / 13)
.attr("class", "circle_line")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 5) + ")");
svg.append("ellipse")
.attr("cx", 0)
.attr("cy", 0)
.attr("rx", width / 15)
.attr("ry", height / 22)
.attr("class", "circle_line")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 5) + ")");
svg.append("ellipse")
.attr("cx", 0)
.attr("cy", 0)
.attr("rx", width / 25)
.attr("ry", height / 40)
.attr("fill", "#2D3C41") // 实心椭圆:填充颜色
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 5) + ")");
切分成32个小齿轮
我是切分成了32个小齿轮(包含透明的),如果你想分的更细,可以分成40或50个,只要你觉得好看就行。
既然要分成32个小立体快,那数据也要切分成32个。
不算透明的,那就是16块小齿轮。
暗颜色的小齿轮是底色,亮颜色小齿轮是百分比。
如果显示80%,经过计算(80/100)*16=12.8,四舍五入为13,所以有13个亮颜色的小齿轮。
var baseColor = "#2D3C41" // 暗颜色
var color = "#5D858D" // 亮颜色
var colorSize = Math.round(16.0*pdata/100.0) // 连颜色数量
var dataset = []
var index = 1;
while(index < colorSize*2) {
if (index % 2 == 0)
dataset.push({label: index.toString(), value: 3.125, color: "transparent"}) // 缝隙
else
dataset.push({label: index.toString(), value: 3.125, color: color}) // 亮颜色
index ++
}
while(index <= 32) {
if (index % 2 == 0)
dataset.push({label: index.toString(), value: 3.125, color: "transparent"}) // 缝隙
else
dataset.push({label: index.toString(), value: 3.125, color: baseColor}) // 暗颜色
index ++
}
通过以上处理,把数据整合成可以生成齿轮的完整数据dataset。
增加左侧面和右侧面
如果不增加左侧面和右侧面,那调用donut3d.js的draw方法后,会生成什么样的图形呢?
// 画3D饼图
svg.append("g").attr("id", "salesDonut");
Donut3D.draw("salesDonut", dataset, width / 2, height / 2, width / 2 - 30, height / 4, 10, 0.6);
请各位仔细看。
是不是有种被掏空的感觉?如果你觉得这样挺好看,那也行,打住吧,后面就不用再看了;如果你想补齐其他面,请耐心往下看。
经过观察和比较,增加左侧面和右侧面就能填满空虚的心啦啦啦~
这次要在donut3d.js这个巨人身上添砖加瓦咯。
// 左侧面
function pieLeftSide(d, rx, ry, h, ir) {
var startAngle = d.startAngle;
var endAngle = d.endAngle;
var sx = rx * Math.cos(startAngle),
sy = ry * Math.sin(startAngle),
ex = rx * Math.cos(endAngle),
ey = ry * Math.sin(endAngle);
var ret = [];
ret.push("M", ir * ex, h + ir * ey, "L", ir * ex, ir * ey, "L", ex, ey, "L", ex, h + ey, "z");
return ret.join(" ");
}
// 右侧面
function pieRightSide(d, rx, ry, h, ir) {
var startAngle = d.startAngle;
var endAngle = d.endAngle;
var sx = rx * Math.cos(startAngle),
sy = ry * Math.sin(startAngle),
ex = rx * Math.cos(endAngle),
ey = ry * Math.sin(endAngle);
var ret = [];
ret.push("M", sx, h + sy, "L", sx, sy, "L", ir * sx, ir * sy, "L", ir * sx, h + ir * sy, "z");
return ret.join(" ");
}
然后再用新增加的两个方法画出左右侧面。
slices.selectAll(".rightSideSlice").data(_data).enter().append("path").attr("class", "rightSideSlice")
.style("fill", function (d) {
if (d.data.color == "transparent")
return "transparent";
else
return d3.hsl(d.data.color).darker(1.5);
})
.attr("d", function (d) { return pieRightSide(d, rx, ry, h, ir); })
.each(function (d) { this._current = d; })
.attr("opacity", 0)
.transition()
.delay(function (d, i) { // 延时显示,最终效果是一节一节动态显示
return i * 50;
})
.duration(100)
.attr("opacity", 1);
slices.selectAll(".leftSideSlice").data(_data).enter().append("path").attr("class", "leftSideSlice")
.style("fill", function (d) {
if (d.data.color == "transparent")
return "transparent";
else
return d3.hsl(d.data.color).darker(1.5);
})
.attr("d", function (d) { return pieLeftSide(d, rx, ry, h, ir); })
.each(function (d) { this._current = d; })
.attr("opacity", 0)
.transition()
.delay(function (d, i) { // 延时显示,最终效果是一节一节动态显示
return i * 50;
})
.duration(100)
.attr("opacity", 1);
最终成品
终于填满需要的每一面,看上去像个立体齿轮图了。
这个图是很久之前做的,当时花了很长时间调试,每一个面有4条边,定位2个点,再加上高度和内半径,就可以计算出4个点,然后就可以画出4条边,最后填充颜色,一个面就完成了。
最近整理文档时觉得有必要写出来,方便以后查阅和探讨,也告诉自己积累是一个长期过程,不急不躁,慢慢来,一步一步完成既定目标,总有一天你会走遍技术的每个角落。
现在我整理成vue组件,传一个百分比的参数,就可以显示3D齿轮图了,我的3D齿轮图也成巨人啦。