您好,登錄后才能下訂單哦!
前言
眾所周知隨著大數據時代的來臨,數據可視化的重要性也越來越凸顯,那么今天就基于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 > h2 { font-size: 14px; } div.tip-hill-div > h3 { 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路徑:
創建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刪除,下圖是效果:
到這,我們的基礎搭建完畢,下面就是核心算法
核心算法
為了實現最終效果,我希望大家在理解的時候能把整個立體柱圖分解一下.
我實現立體柱圖的思路是通過2個path路徑和一個rect進行拼湊.
正面是一個rect,上面和右面利用path路徑生成.
利用三角函數,通過給定的angle角度計算上面的一個點就可以知道其他所有點的位置進而進行繪制.
通過上圖可以看到,一個立體柱圖我們只需要知道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("<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; //獲取坐標 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" }); }
由于需要考慮動畫,所以對渲染時的柱子位置進行了處理.對這方面不理解的話可以留言討論.
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。