d3.js实现立体柱图的方法详解

前言

众所周知随着大数据时代的来临,数据可视化的重要性也越来越凸显,那么今天就基于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>

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("<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;

//获取坐标

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"

});

}

由于需要考虑动画,所以对渲染时的柱子位置进行了处理.对这方面不理解的话可以留言讨论.

总结

以上是 d3.js实现立体柱图的方法详解 的全部内容, 来源链接: utcz.com/z/324926.html

回到顶部