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 相关文章推荐
Mootools 1.2教程 正则表达式
Sep 15 Javascript
JS Pro-深入面向对象的程序设计之继承的详解
May 07 Javascript
jquery使用hide方法隐藏指定id的元素
Mar 30 Javascript
PHP捕捉异常中断的方法
Oct 24 Javascript
jQuery实现拼图小游戏(实例讲解)
Jul 24 jQuery
JS使用贪心算法解决找零问题示例
Nov 27 Javascript
vue通过cookie获取用户登录信息的思路详解
Oct 30 Javascript
layUI实现列表查询功能
Jul 27 Javascript
three.js 将图片马赛克化的示例代码
Jul 31 Javascript
js实现表格数据搜索
Aug 09 Javascript
vue项目打包为APP,静态资源正常显示,但API请求不到数据的操作
Sep 12 Javascript
JavaScript中arguments的使用方法详解
Dec 20 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
php数组函数序列之array_search()- 按元素值返回键名
2011/11/04 PHP
php防止用户重复提交表单
2015/11/02 PHP
PHP封装的page分页类定义与用法完整示例
2018/12/24 PHP
让您的菜单不离网站
2006/10/03 Javascript
Javascript模块化编程(三)require.js的用法及功能介绍
2013/01/17 Javascript
js操作table示例(个人心得)
2013/11/29 Javascript
javascript中几个容易混淆的概念总结
2015/04/14 Javascript
JS原型链怎么理解
2016/06/27 Javascript
JS结合bootstrap实现基本的增删改查功能
2016/07/22 Javascript
JS实现页面中所有img对象添加onclick事件及新窗口查看图片的方法
2016/12/27 Javascript
关于webpack代码拆分的解析
2017/07/20 Javascript
深入理解JavaScript 箭头函数
2019/05/30 Javascript
详细分析Node.js 模块系统
2020/06/28 Javascript
将Emacs打造成强大的Python代码编辑工具
2015/11/20 Python
Python性能提升之延迟初始化
2016/12/04 Python
Python编程之黑板上排列组合,你舍得解开吗
2017/10/30 Python
对python 数据处理中的LabelEncoder 和 OneHotEncoder详解
2018/07/11 Python
python实现dijkstra最短路由算法
2019/01/17 Python
对python numpy.array插入一行或一列的方法详解
2019/01/29 Python
python读取并定位excel数据坐标系详解
2019/06/26 Python
python opencv图片编码为h264文件的实例
2019/12/12 Python
python statsmodel的使用
2020/12/21 Python
New Era英国官网:美国棒球帽品牌
2018/03/21 全球购物
吉力贝官方网站:Jelly Belly
2019/03/11 全球购物
本科毕业生自我鉴定
2013/11/02 职场文书
主持词开场白
2014/03/17 职场文书
求职者怎样写自荐信
2014/04/13 职场文书
气象学专业个人求职信
2014/04/22 职场文书
分公司总经理岗位职责
2014/07/30 职场文书
消防志愿者活动方案
2014/08/23 职场文书
八一建军节演讲稿
2014/09/10 职场文书
完整版商业计划书
2014/09/15 职场文书
廉政承诺书范文
2015/04/28 职场文书
保护环境的宣传语
2015/07/13 职场文书
Python实现智慧校园自动评教全新版
2021/06/18 Python
SpringBoot深入分析讲解监听器模式下
2022/07/15 Java/Android