溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

d3.js實(shí)現(xiàn)立體柱圖的方法詳解

發(fā)布時間:2020-09-21 07:58:35 來源:腳本之家 閱讀:308 作者:勿忘初心夢于歌 欄目:web開發(fā)

前言

眾所周知隨著大數(shù)據(jù)時代的來臨,數(shù)據(jù)可視化的重要性也越來越凸顯,那么今天就基于d3.js今天給大家?guī)砜梢暬A(chǔ)圖表柱圖進(jìn)階:立體柱圖,之前介紹過了d3.js實(shí)現(xiàn)柱狀圖的文章,感興趣的朋友們可以看一看。

關(guān)于d3.js

d3.js是一個操作svg的圖表庫,d3封裝了圖表的各種算法.對d3不熟悉的朋友可以到d3.js官網(wǎng)學(xué)習(xí)d3.js.

另外感謝司機(jī)大傻(聲音像張學(xué)友一樣性感的一流裝逼手)和司機(jī)呆(呆萌女神)等人對d3.js進(jìn)行翻譯!

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 > h2 {
 font-size: 14px;
 }

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

JS

當(dāng)前使用d3.v4+版本

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

圖表所需數(shù)據(jù)

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"
 }
 }];

圖表的一些基礎(chǔ)配置數(shù)據(jù)

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

var svgWidth = 1000;
var svgHeight = 500;


//創(chuàng)建各個面的顏色數(shù)組
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');

創(chuàng)建X軸序數(shù)比例尺

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)//核心算法
 }

你可能注意到了,上面代碼中不僅使用了序數(shù)比例尺,還有地理路徑生成器,因?yàn)樾枰闪Ⅲw的柱圖,所以需要講原本的X軸刪除,自己重新進(jìn)行繪制.下圖是自己重新繪制出來的path路徑:

d3.js實(shí)現(xiàn)立體柱圖的方法詳解

創(chuàng)建Y軸線性比例尺

var yLinearScale;
 //創(chuàng)建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();
 }

創(chuàng)建Y軸時同樣需要把原來的路徑和tick刪除,下圖是效果:

d3.js實(shí)現(xiàn)立體柱圖的方法詳解

到這,我們的基礎(chǔ)搭建完畢,下面就是核心算法

核心算法

為了實(shí)現(xiàn)最終效果,我希望大家在理解的時候能把整個立體柱圖分解一下.

d3.js實(shí)現(xiàn)立體柱圖的方法詳解

我實(shí)現(xiàn)立體柱圖的思路是通過2個path路徑和一個rect進(jìn)行拼湊.

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

利用三角函數(shù),通過給定的angle角度計(jì)算上面的一個點(diǎn)就可以知道其他所有點(diǎn)的位置進(jìn)而進(jìn)行繪制.

d3.js實(shí)現(xiàn)立體柱圖的方法詳解

通過上圖可以看到,一個立體柱圖我們只需要知道7個點(diǎn)的位置就能夠繪制出來.

并且已知正面rect4個紅色點(diǎn)的位置.已知柱子的寬度和高度,那么只要求出Top面左上角點(diǎn)的位置,就可以知道余下綠色點(diǎn)的位置.具體算法如下:

//核心算法思路是Big boss教的,我借花獻(xiàn)佛
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
  };
 }
 }

渲染

最終我們還要鼠標(biāo)進(jìn)行交互,所以先添加tip生成函數(shù)

//tip的創(chuàng)建方法(方法來自敬愛的鳴哥)
 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("<h2>品種信息:" + info.letter + "</h2>");
 ary.push("<h3>成交量: " + 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;
  //獲取坐標(biāo)
  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"
  });
 }

由于需要考慮動畫,所以對渲染時的柱子位置進(jìn)行了處理.對這方面不理解的話可以留言討論.

d3.js實(shí)現(xiàn)立體柱圖的方法詳解

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI