d3.js實(shí)現(xiàn)立體柱圖的方法詳解
前言
眾所周知隨著大數(shù)據(jù)時(shí)代的來臨,數(shù)據(jù)可視化的重要性也越來越凸顯,那么今天就基于d3.js今天給大家?guī)砜梢暬A(chǔ)圖表柱圖進(jìn)階:立體柱圖,之前介紹過了d3.js實(shí)現(xiàn)柱狀圖的文章,感興趣的朋友們可以看一看。
關(guān)于d3.js
d3.js是一個(gè)操作svg的圖表庫,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)行繪制.下圖是自己重新繪制出來的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í)同樣需要把原來的路徑和tick刪除,下圖是效果:

到這,我們的基礎(chǔ)搭建完畢,下面就是核心算法
核心算法
為了實(shí)現(xiàn)最終效果,我希望大家在理解的時(shí)候能把整個(gè)立體柱圖分解一下.

我實(shí)現(xiàn)立體柱圖的思路是通過2個(gè)path路徑和一個(gè)rect進(jìn)行拼湊.
正面是一個(gè)rect,上面和右面利用path路徑生成.
利用三角函數(shù),通過給定的angle角度計(jì)算上面的一個(gè)點(diǎn)就可以知道其他所有點(diǎn)的位置進(jìn)而進(jìn)行繪制.

通過上圖可以看到,一個(gè)立體柱圖我們只需要知道7個(gè)點(diǎn)的位置就能夠繪制出來.
并且已知正面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)建方法(方法來自敬愛的鳴哥)
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)畫,所以對(duì)渲染時(shí)的柱子位置進(jìn)行了處理.對(duì)這方面不理解的話可以留言討論.

總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對(duì)大家學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
用javascript替換URL中的參數(shù)值示例代碼
本篇文章主要是對(duì)用javascript替換URL中的參數(shù)值示例代碼進(jìn)行了介紹,需要的朋友可以過來參考下,希望對(duì)大家有所幫助2014-01-01
JS switch判斷 三目運(yùn)算 while 及 屬性操作代碼
這篇文章主要介紹了JS switch判斷 三目運(yùn)算 while 及 屬性操作代碼,需要的朋友可以參考下2017-09-09
如何實(shí)現(xiàn)一個(gè)webpack模塊解析器
這篇文章主要介紹了如何實(shí)現(xiàn)一個(gè)webpack模塊解析器,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-10-10
jquery將標(biāo)簽元素的高設(shè)為屏幕的百分比
這篇文章主要介紹了js將標(biāo)簽元素的高設(shè)為屏幕的百分比,需要的朋友可以參考下2017-04-04
JS運(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)中的元素屬性檢測與判斷相關(guān)操作技巧,需要的朋友可以參考下2018-01-01
JavaScript隨機(jī)打亂數(shù)組順序之隨機(jī)洗牌算法
這篇文章主要介紹了JavaScript隨機(jī)打亂數(shù)組順序之隨機(jī)洗牌算法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08
JavaScript 學(xué)習(xí)筆記(七)字符串的連接
javascript 字符串的連接效率問題,需要的朋友可以參考下。2009-12-12

