D3.js画图:3D动态饼图(齿轮图)

介绍

通常画可视化图的工具很多,除了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!

donut3d.png

如何添加自己的需求

还是先上个我已经改好之后的3D饼图(齿轮图)吧,方便说明。
其实显示的时候是个动态的,一节一节显示出齿轮的。
背景是黑乎乎的,据说现在流行黑乎乎的背景,显得有科技感,技术也要赶时髦啊,我这么fashion的人,做出来的东西也要fashion啊O(∩_∩)O~

donut3d-sq.png

分析:

  1. 显示百分比文字。
  2. 有好几个椭圆,空心椭圆和实心椭圆。
  3. 齿轮中间有缝隙,怎么实现这个缝隙呢?很容易想到的,透明嘛,transparent。
  4. 中间有了缝隙之后,那齿轮还完整吗?当中间没有缝隙时,看官网的原图,仔细看,发现其实只实现了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-sq1.png

是不是有种被掏空的感觉?如果你觉得这样挺好看,那也行,打住吧,后面就不用再看了;如果你想补齐其他面,请耐心往下看。
经过观察和比较,增加左侧面和右侧面就能填满空虚的心啦啦啦~
这次要在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齿轮图也成巨人啦。

donut3d-sq.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • D3.js 为什么学习D3 D3.js和threejs的应用场景完全不一样。threejs主要应用与基于webGL...
    强某某阅读 13,627评论 0 15
  • 动态效果图: 细看上面的动态效果图,可以发现: 一个值变换到一个新的值时,是一个渐变的过程; 圆弧末尾有一个竖线,...
    Evelynzzz阅读 6,694评论 2 5
  • 调研了一下知识图谱的前端显示方案。最简单的就是直接用Echarts等封装好的关系图来绘制,但难以个性化的设定。所以...
    alue阅读 697评论 0 1
  • 我是2015年开始学习D3.js,这几年也经常写D3的代码,想给大家分享一下我D3学习的过程及用到的资源。...
    广陵啸_e613阅读 2,320评论 0 6
  • 打个小广告:如果你想获取更多前端干货、鹅厂工程师的前端面试指南,欢迎关注我的个人微信公众号:前端夜谈 Github...
    ssthouse阅读 1,849评论 0 1