d3.js實(shí)現(xiàn)立體柱圖的方法詳解
前言
眾所周知隨著大數(shù)據(jù)時(shí)代的來(lái)臨,數(shù)據(jù)可視化的重要性也越來(lái)越凸顯,那么今天就基于d3.js今天給大家?guī)?lái)可視化基礎(chǔ)圖表柱圖進(jìn)階:立體柱圖,之前介紹過(guò)了d3.js實(shí)現(xiàn)柱狀圖的文章,感興趣的朋友們可以看一看。
關(guān)于d3.js
d3.js是一個(gè)操作svg的圖表庫(kù),d3封裝了圖表的各種算法.對(duì)d3不熟悉的朋友可以到d3.js官網(wǎng)學(xué)習(xí)d3.js.
另外感謝司機(jī)大傻和司機(jī)呆等人對(duì)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 > h1 { font-size: 14px; } div.tip-hill-div > h2 { 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)建各個(gè)面的顏色數(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)行繪制.下圖是自己重新繪制出來(lái)的path路徑:
創(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軸時(shí)同樣需要把原來(lái)的路徑和tick刪除,下圖是效果:
到這,我們的基礎(chǔ)搭建完畢,下面就是核心算法
核心算法
為了實(shí)現(xiàn)最終效果,我希望大家在理解的時(shí)候能把整個(gè)立體柱圖分解一下.
我實(shí)現(xiàn)立體柱圖的思路是通過(guò)2個(gè)path路徑和一個(gè)rect進(jìn)行拼湊.
正面是一個(gè)rect,上面和右面利用path路徑生成.
利用三角函數(shù),通過(guò)給定的angle角度計(jì)算上面的一個(gè)點(diǎn)就可以知道其他所有點(diǎn)的位置進(jìn)而進(jìn)行繪制.
通過(guò)上圖可以看到,一個(gè)立體柱圖我們只需要知道7個(gè)點(diǎn)的位置就能夠繪制出來(lái).
并且已知正面rect4個(gè)紅色點(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)建方法(方法來(lái)自敬愛(ài)的鳴哥) 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; //獲取坐標(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" }); }
由于需要考慮動(dòng)畫(huà),所以對(duì)渲染時(shí)的柱子位置進(jìn)行了處理.對(duì)這方面不理解的話可以留言討論.
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家學(xué)習(xí)或者工作能帶來(lái)一定的幫助,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
谷歌Chrome瀏覽器擴(kuò)展程序開(kāi)發(fā)小記
本文給大家記錄的是一次谷歌Chrome瀏覽器擴(kuò)展程序的開(kāi)發(fā)過(guò)程,非常的細(xì)致,有類似開(kāi)發(fā)念頭的小伙伴們可以來(lái)參考下2016-01-01用javascript替換URL中的參數(shù)值示例代碼
本篇文章主要是對(duì)用javascript替換URL中的參數(shù)值示例代碼進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2014-01-01JS switch判斷 三目運(yùn)算 while 及 屬性操作代碼
這篇文章主要介紹了JS switch判斷 三目運(yùn)算 while 及 屬性操作代碼,需要的朋友可以參考下2017-09-09如何實(shí)現(xiàn)一個(gè)webpack模塊解析器
這篇文章主要介紹了如何實(shí)現(xiàn)一個(gè)webpack模塊解析器,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-10-10jquery將標(biāo)簽元素的高設(shè)為屏幕的百分比
這篇文章主要介紹了js將標(biāo)簽元素的高設(shè)為屏幕的百分比,需要的朋友可以參考下2017-04-04JS運(yùn)動(dòng)特效之完美運(yùn)動(dòng)框架實(shí)例分析
這篇文章主要介紹了JS運(yùn)動(dòng)特效之完美運(yùn)動(dòng)框架,結(jié)合實(shí)例形式分析了javascript針對(duì)運(yùn)動(dòng)中的元素屬性檢測(cè)與判斷相關(guān)操作技巧,需要的朋友可以參考下2018-01-01JS實(shí)現(xiàn)刷新父頁(yè)面不彈出提示框的方法
這篇文章主要介紹了JS實(shí)現(xiàn)刷新父頁(yè)面不彈出提示框的方法,實(shí)例分析了javascript子窗口的打開(kāi)以及子窗口與父窗口的交互操作技巧,需要的朋友可以參考下2016-06-06JavaScript隨機(jī)打亂數(shù)組順序之隨機(jī)洗牌算法
這篇文章主要介紹了JavaScript隨機(jī)打亂數(shù)組順序之隨機(jī)洗牌算法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08JavaScript 學(xué)習(xí)筆記(七)字符串的連接
javascript 字符串的連接效率問(wèn)題,需要的朋友可以參考下。2009-12-12