欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-01-27 11:11:58
前言 众所周知随着大数据时代的来临,数据可视化的重要性也越来越凸显,那么今天就基于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实现立体柱图的方法详解

总结

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