前言
众所周知随着大数据时代的来临,数据可视化的重要性也越来越凸显,那么今天就基于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>
</div>
JS
当前使用d3.v4+版本
<script src="d3-4.js"></script></div>
图表所需数据
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"
}
}];
</div>
图表的一些基础配置数据
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');
</div>
创建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)//核心算法
}
</div>
你可能注意到了,上面代码中不仅使用了序数比例尺,还有地理路径生成器,因为需要生成立体的柱图,所以需要讲原本的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();
}
</div>
创建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
};
}
}
</div>
渲染
最终我们还要鼠标进行交互,所以先添加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.displayTex

