d3.js实现立体柱图的方法详解


Posted in Javascript onApril 28, 2017

前言

众所周知随着大数据时代的来临,数据可视化的重要性也越来越凸显,那么今天就基于d3.js今天给大家带来可视化基础图表柱图进阶:立体柱图,之前介绍过了d3.js实现柱状图的文章,感兴趣的朋友们可以看一看。

关于d3.js

d3.js是一个操作svg的图表库,d3封装了图表的各种算法.对d3不熟悉的朋友可以到d3.js官网学习d3.js.

另外感谢司机大傻(声音像张学友一样性感的一流装逼手)和司机呆(呆萌女神)等人对d3.js进行翻译!

HTML+CSS

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title>Title</title>
 <style>
 * {
 margin: 0;
 padding: 0;
 }

 div.tip-hill-div {
 background: rgba(0, 0, 0, 0.7);
 color: #fff;
 padding: 10px;
 border-radius: 5px;
 font-family: Microsoft Yahei;
 }

 div.tip-hill-div > h1 {
 font-size: 14px;
 }

 div.tip-hill-div > h2 {
 font-size: 12px;
 }
 </style>
</head>
<body>
<div id="chart"></div>
</body>
</html>

JS

当前使用d3.v4+版本

<script src="d3-4.js"></script>

图表所需数据

var data = [{
 "letter": "白皮鸡蛋",
 "child": {
 "category": "0",
 "value": "459.00"
 }
 }, {
 "letter": "红皮鸡蛋",
 "child": {
 "category": "0",
 "value": "389.00"
 }
 }, {
 "letter": "鸡蛋",
 "child": {
 "category": "0",
 "value": "336.00"
 }
 }, {
 "letter": "牛肉",
 "child": {
 "category": "0",
 "value": "282.00"
 }
 }, {
 "letter": "羊肉",
 "child": {
 "category": "0",
 "value": "249.00"
 }
 }, {
 "letter": "鸭蛋",
 "child": {
 "category": "0",
 "value": "242.00"
 }
 }, {
 "letter": "红薯",
 "child": {
 "category": "0",
 "value": "222.00"
 }
 }, {
 "letter": "白菜",
 "child": {
 "category": "0",
 "value": "182.00"
 }
 }, {
 "letter": "鸡肉",
 "child": {
 "category": "0",
 "value": "102.00"
 }
 }];

图表的一些基础配置数据

var margin = {
 top: 20,
 right: 50,
 bottom: 50,
 left: 90
 };

var svgWidth = 1000;
var svgHeight = 500;


//创建各个面的颜色数组
var mainColorList = ['#f6e242', '#ebec5b', '#d2ef5f', '#b1d894','#97d5ad', '#82d1c0', '#70cfd2', '#63c8ce', '#50bab8', '#38a99d'];
var topColorList = ['#e9d748', '#d1d252', '#c0d75f', '#a2d37d','#83d09e', '#68ccb6', '#5bc8cb', '#59c0c6', '#3aadab', '#2da094'];
var rightColorList = ['#dfce51', '#d9db59', '#b9d54a', '#9ece7c','#8ac69f', '#70c3b1', '#65c5c8', '#57bac0', '#42aba9', '#2c9b8f'];

var svg = d3.select('#chart')
 .append('svg')
 .attr('width', svgWidth)
 .attr('height', svgHeight)
 .attr('id', 'svg-column');

创建X轴序数比例尺

function addXAxis() {
 var transform = d3.geoTransform({
 point: function (x, y) {
 this.stream.point(x, y)
 }
 });
 //定义几何路径
 var path = d3.geoPath()
 .projection(transform);

 xLinearScale = d3.scaleBand()
 .domain(data.map(function (d) {
  return d.letter;
 }))
 .range([0, svgWidth - margin.right - margin.left], 0.1);
 var xAxis = d3.axisBottom(xLinearScale)
 .ticks(data.length);
 //绘制X轴
 var xAxisG = svg.append("g")
 .call(xAxis)
 .attr("transform", "translate(" + (margin.left) + "," + (svgHeight - margin.bottom) + ")");

 //删除原X轴
 xAxisG.select("path").remove();
 xAxisG.selectAll('line').remove();
 //绘制新的立体X轴
 xAxisG.append("path")
 .datum({
  type: "Polygon",
  coordinates: [
  [
  [20, 0],
  [0, 15],
  [svgWidth - margin.right - margin.left, 15],
  [svgWidth + 20 - margin.right - margin.left, 0],
  [20, 0]
  ]
  ]
 })
 .attr("d", path)
 .attr('fill', 'rgb(187,187,187)');
 xAxisG.selectAll('text')
 .attr('font-size', '18px')
 .attr('fill', '#646464')
 .attr('transform', 'translate(0,20)');

 dataProcessing(xLinearScale)//核心算法
 }

你可能注意到了,上面代码中不仅使用了序数比例尺,还有地理路径生成器,因为需要生成立体的柱图,所以需要讲原本的X轴删除,自己重新进行绘制.下图是自己重新绘制出来的path路径:

d3.js实现立体柱图的方法详解

创建Y轴线性比例尺

var yLinearScale;
 //创建y轴的比例尺渲染y轴
 function addYScale() {
 yLinearScale = d3.scaleLinear()
 .domain([0, d3.max(data, function (d, i) {
  return d.child.value * 1;
 }) * 1.2])
 .range([svgHeight - margin.top - margin.bottom, 0]);

 //定义Y轴比例尺以及刻度
 var yAxis = d3.axisLeft(yLinearScale)
 .ticks(6);

 //绘制Y轴
 var yAxisG = svg.append("g")
 .call(yAxis)
 .attr('transform', 'translate(' + (margin.left + 10) + "," + margin.top + ")");
 yAxisG.selectAll('text')
 .attr('font-size', '18px')
 .attr('fill', '#636363');
 //删除原Y轴路径和tick
 yAxisG.select("path").remove();
 yAxisG.selectAll('line').remove();
 }

创建Y轴时同样需要把原来的路径和tick删除,下图是效果:

d3.js实现立体柱图的方法详解

到这,我们的基础搭建完毕,下面就是核心算法

核心算法

为了实现最终效果,我希望大家在理解的时候能把整个立体柱图分解一下.

d3.js实现立体柱图的方法详解

我实现立体柱图的思路是通过2个path路径和一个rect进行拼凑.

正面是一个rect,上面和右面利用path路径生成.

利用三角函数,通过给定的angle角度计算上面的一个点就可以知道其他所有点的位置进而进行绘制.

d3.js实现立体柱图的方法详解

通过上图可以看到,一个立体柱图我们只需要知道7个点的位置就能够绘制出来.

并且已知正面rect4个红色点的位置.已知柱子的宽度和高度,那么只要求出Top面左上角点的位置,就可以知道余下绿色点的位置.具体算法如下:

//核心算法思路是Big boss教的,我借花献佛
function dataProcessing(xLinearScale) {
 var angle = Math.PI / 2.3;
 for (var i = 0; i < data.length; i++) {
  var d = data[i];
  var depth = 10; 
  d.ow = xLinearScale.bandwidth() * 0.7;
  d.ox = xLinearScale(d.letter);
  d.oh = 1;
  d.p1 = {
  x: Math.cos(angle) * d.ow,
  y: -Math.sin(angle) - depth
  };
  d.p2 = {
  x: d.p1.x + d.ow,
  y: d.p1.y
  };
  d.p3 = {
  x: d.p2.x,
  y: d.p2.y + d.oh
  };
 }
 }

渲染

最终我们还要鼠标进行交互,所以先添加tip生成函数

//tip的创建方法(方法来自敬爱的鸣哥)
 var tipTimerConfig = {
 longer: 0,
 target: null,
 exist: false,
 winEvent: window.event,
 boxHeight: 398,
 boxWidth: 376,
 maxWidth: 376,
 maxHeight: 398,
 tooltip: null,

 showTime: 3500,
 hoverTime: 300,
 displayText: "",
 show: function (val, e) {
  "use strict";
  var me = this;

  if (e != null) {
  me.winEvent = e;
  }

  me.displayText = val;

  me.calculateBoxAndShow();

  me.createTimer();
 },
 calculateBoxAndShow: function () {
  "use strict";
  var me = this;
  var _x = 0;
  var _y = 0;
  var _w = document.documentElement.scrollWidth;
  var _h = document.documentElement.scrollHeight;
  var wScrollX = window.scrollX || document.body.scrollLeft;
  var wScrollY = window.scrollY || document.body.scrollTop;
  var xMouse = me.winEvent.x + wScrollX;
  if (_w - xMouse < me.boxWidth) {
  _x = xMouse - me.boxWidth - 10;
  } else {
  _x = xMouse;
  }

  var _yMouse = me.winEvent.y + wScrollY;
  if (_h - _yMouse < me.boxHeight + 18) {
  _y = _yMouse - me.boxHeight - 25;
  } else {

  _y = _yMouse + 18;
  }

  me.addTooltip(_x, _y);
 },
 addTooltip: function (page_x, page_y) {
  "use strict";
  var me = this;

  me.tooltip = document.createElement("div");
  me.tooltip.style.left = page_x + "px";
  me.tooltip.style.top = page_y + "px";
  me.tooltip.style.position = "absolute";

  me.tooltip.style.width = me.boxWidth + "px";
  me.tooltip.style.height = me.boxHeight + "px";
  me.tooltip.className = "three-tooltip";

  var divInnerHeader = me.createInner();
  divInnerHeader.innerHTML = me.displayText;
  me.tooltip.appendChild(divInnerHeader);

  document.body.appendChild(me.tooltip);
 },
 createInner: function () {
  "use strict";
  var me = this;
  var divInnerHeader = document.createElement('div');
  divInnerHeader.style.width = me.boxWidth + "px";
  divInnerHeader.style.height = me.boxHeight + "px";
  return divInnerHeader;
 },
 ClearDiv: function () {
  "use strict";
  var delDiv = document.body.getElementsByClassName("three-tooltip");
  for (var i = delDiv.length - 1; i >= 0; i--) {
  document.body.removeChild(delDiv[i]);
  }
 },
 createTimer: function (delTarget) {
  "use strict";
  var me = this;
  var delTip = me.tooltip;
  var delTarget = tipTimerConfig.target;
  var removeTimer = window.setTimeout(function () {
  try {
   if (delTip != null) {
   document.body.removeChild(delTip);
   if (tipTimerConfig.target == delTarget) {
    me.exist = false;
   }
   }
   clearTimeout(removeTimer);
  } catch (e) {
   clearTimeout(removeTimer);
  }
  }, me.showTime);
 },
 hoverTimerFn: function (showTip, showTarget) {
  "use strict";
  var me = this;

  var showTarget = tipTimerConfig.target;

  var hoverTimer = window.setInterval(function () {
  try {
   if (tipTimerConfig.target != showTarget) {
   clearInterval(hoverTimer);
   } else if (!tipTimerConfig.exist && (new Date()).getTime() - me.longer > me.hoverTime) {
   //show
   tipTimerConfig.show(showTip);
   tipTimerConfig.exist = true;
   clearInterval(hoverTimer);
   }
  } catch (e) {
   clearInterval(hoverTimer);
  }
  }, tipTimerConfig.hoverTime);
 }
 };

 var createTooltipTableData = function (info) {
 var ary = [];
 ary.push("<div class='tip-hill-div'>");
 ary.push("<h1>品种信息:" + info.letter + "</h1>");
 ary.push("<h2>成交量: " + info.child.value);
 ary.push("</div>");
 return ary.join("");
 };

核心算法写完,就到了最终的渲染了

function addColumn() {
 function clumnMouseover(d) {
  d3.select(this).selectAll(".transparentPath").attr("opacity", 0.8);
  // 添加 div
  tipTimerConfig.target = this;
  tipTimerConfig.longer = new Date().getTime();
  tipTimerConfig.exist = false;
  //获取坐标
  tipTimerConfig.winEvent = {
  x: event.clientX - 100,
  y: event.clientY
  };
  tipTimerConfig.boxHeight = 50;
  tipTimerConfig.boxWidth = 140;

  //hide
  tipTimerConfig.ClearDiv();
  //show
  tipTimerConfig.hoverTimerFn(createTooltipTableData(d));
 }

 function clumnMouseout(d) {
  d3.select(this).selectAll(".transparentPath").attr("opacity", 1);
  tipTimerConfig.target = null;
  tipTimerConfig.ClearDiv();
 }

 var g = svg.selectAll('.g')
  .data(data)
  .enter()
  .append('g')
  .on("mouseover", clumnMouseover)
  .on("mouseout", clumnMouseout)
  .attr('transform', function (d) {
   return "translate(" + (d.ox + margin.left + 20) + "," + (svgHeight - margin.bottom + 15) + ")"
  });
 g.transition()
  .duration(2500)
  .attr("transform", function (d) {
   return "translate(" + (d.ox + margin.left + 20) + ", " + (yLinearScale(d.child.value) + margin.bottom - 15) + ")"
  });

 g.append('rect')
  .attr('x', 0)
  .attr('y', 0)
  .attr("class", "transparentPath")
  .attr('width', function (d, i) {
   return d.ow;
  })
  .attr('height', function (d) {
   return d.oh;
  })
  .style('fill', function (d, i) {
   return mainColorList[i]
  })
  .transition()
  .duration(2500)
  .attr("height", function (d, i) {
   return svgHeight - margin.bottom - margin.top - yLinearScale(d.child.value);
  });

 g.append('path')
  .attr("class", "transparentPath")
  .attr('d', function (d) {
   return "M0,0 L" + d.p1.x + "," + d.p1.y + " L" + d.p2.x + "," + d.p2.y + " L" + d.ow + ",0 L0,0";
  })
  .style('fill', function (d, i) {
   return topColorList[i]
  });

 g.append('path')
  .attr("class", "transparentPath")
  .attr('d', function (d) {
   return "M" + d.ow + ",0 L" + d.p2.x + "," + d.p2.y + " L" + d.p3.x + "," + d.p3.y + " L" + d.ow + "," + d.oh + " L" + d.ow + ",0"
  })
  .style('fill', function (d, i) {
   return rightColorList[i]
  })
  .transition()
  .duration(2500)
  .attr("d", function (d, i) {
   return "M" + d.ow + ",0 L" + d.p2.x + "," + d.p2.y + " L" + d.p3.x + "," + (d.p3.y + svgHeight - margin.top - margin.bottom - yLinearScale(d.child.value)) + " L" + d.ow + "," + (svgHeight - margin.top - margin.bottom - yLinearScale(d.child.value)) + " L" + d.ow + ",0"
  });
 }

由于需要考虑动画,所以对渲染时的柱子位置进行了处理.对这方面不理解的话可以留言讨论.

d3.js实现立体柱图的方法详解

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
实例:尽可能写友好的Javascript代码
Oct 09 Javascript
显示js对象所有属性和方法的函数
Oct 16 Javascript
5款Javascript颜色选择器
Oct 25 Javascript
深入领悟JavaScript中的面向对象
Nov 18 Javascript
jQuery Ajax()方法使用指南
Nov 19 Javascript
JavaScript中setMonth()方法的使用详解
Jun 11 Javascript
javascript表单处理具体实现代码(表单、链接、按钮)
May 07 Javascript
Google 地图获取API Key详细教程
Aug 06 Javascript
JavaScript和jQuery获取input框的绝对位置实现方法
Oct 13 Javascript
Bootstrap源码解读网格系统(3)
Dec 22 Javascript
详解webpack es6 to es5支持配置
May 04 Javascript
vue axios登录请求拦截器
Apr 02 Javascript
JS基于正则表达式的替换操作(replace)用法示例
Apr 28 #Javascript
vue调用高德地图实例代码
Apr 28 #Javascript
vue省市区三联动下拉选择组件的实现
Apr 28 #Javascript
AngulaJS路由 ui-router 传参实例
Apr 28 #Javascript
Angular.Js之Scope作用域的学习教程
Apr 27 #Javascript
JS简单实现点击按钮或文字显示遮罩层的方法
Apr 27 #Javascript
JavaScript通过改变文字透明度实现的文字闪烁效果实例
Apr 27 #Javascript
You might like
在普通HTTP上安全地传输密码
2007/07/21 PHP
php设计模式 Builder(建造者模式)
2011/06/26 PHP
php fsockopen伪造post与get方法的详解
2013/06/14 PHP
php实现使用正则将文本中的网址转换成链接标签
2014/12/03 PHP
php页面缓存方法小结
2015/01/10 PHP
jquery 框架使用教程 AJAX篇
2009/10/11 Javascript
需要做特殊处理的DOM元素属性的访问
2010/11/05 Javascript
jquery实现隐藏与显示动画效果/输入框字符动态递减/导航按钮切换
2013/07/01 Javascript
div浮层,滚动条移动,位置保持不变的4种方法汇总
2013/12/11 Javascript
jQuery使用hide方法隐藏页面上指定元素的方法
2015/03/30 Javascript
使用jquery提交form表单并自定义action的实现代码
2016/05/25 Javascript
javascript自执行函数
2017/02/10 Javascript
基于BootStrap multiselect.js实现的下拉框联动效果
2017/07/28 Javascript
JS继承与闭包及JS实现继承的三种方式
2017/10/15 Javascript
详解React-Router中Url参数改变页面不刷新的解决办法
2018/05/08 Javascript
vue.js指令v-for使用以及下标索引的获取
2019/01/31 Javascript
微信小程序位置授权处理方法
2019/06/13 Javascript
[01:01:25]DOTA2上海特级锦标赛B组资格赛#2 Fnatic VS Spirit第三局
2016/02/27 DOTA
python正则分组的应用
2013/11/10 Python
Python读写文件方法总结
2015/06/09 Python
selenium+python截图不成功的解决方法
2019/01/30 Python
python和c语言的主要区别总结
2019/07/07 Python
Python matplotlib绘制饼状图功能示例
2019/09/10 Python
pytorch 实现删除tensor中的指定行列
2020/01/13 Python
Tensorflow实现部分参数梯度更新操作
2020/01/23 Python
俄罗斯最大的在线珠宝大卖场:Nebo
2019/12/08 全球购物
JAVA和C++的区别
2013/10/06 面试题
说一下Linux下有关用户和组管理的命令
2016/01/04 面试题
中文专业学生自我评价范文
2014/02/06 职场文书
中华美德颂演讲稿
2014/05/20 职场文书
党员批评与自我批评(5篇)
2014/09/23 职场文书
小学班主任经验交流材料
2014/12/16 职场文书
个人工作违纪检讨书
2015/05/05 职场文书
校运会加油稿大全
2015/07/22 职场文书
图书馆义工感想
2015/08/07 职场文书
详解PHP Swoole与TCP三次握手
2021/05/27 PHP