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

前端之家收集整理的这篇文章主要介绍了d3.js实现立体柱图的方法详解前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

前言

众所周知随着大数据时代的来临,数据可视化的重要性也越来越凸显,那么今天就基于d3.js今天给大家带来可视化基础图表柱图进阶:立体柱图,之前介绍过了文章,感兴趣的朋友们可以看一看。

关于d3.js

d3.js是一个操作svg的图表库,d3封装了图表的各种算法.对d3不熟悉的朋友可以到d3.js官网学习d3.js.

另外感谢司机大傻(声音像张学友一样性感的一流装逼手)和司机呆(呆萌女神)等人对d3.js进行翻译!

HTML+CSS

<Meta charset="UTF-8"> Title

JS

当前使用d3.v4+版本

图表所需数据

图表的一些基础配置数据

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轴序数比例尺

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,[svgWidth + 20 - margin.right - margin.left,[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轴线性比例尺

//定义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','#636363');
//删除原Y轴路径和tick
yAxisG.select("path").remove();
yAxisG.selectAll('line').remove();
}

创建Y轴时同样需要把原来的路径和tick删除,下图是效果:

到这,我们的基础搭建完毕,下面就是核心算法

核心算法

为了实现最终效果,我希望大家在理解的时候能把整个立体柱图分解一下.

我实现立体柱图的思路是通过2个path路径和一个rect进行拼凑.

正面是一个rect,上面和右面利用path路径生成.

利用三角函数,通过给定的angle角度计算上面的一个点就可以知道其他所有点的位置进而进行绘制.

通过上图可以看到,一个立体柱图我们只需要知道7个点的位置就能够绘制出来.

并且已知正面rect4个红色点的位置.已知柱子的宽度和高度,那么只要求出Top面左上角点的位置,就可以知道余下绿色点的位置.具体算法如下:

渲染

最终我们还要鼠标进行交互,所以先添加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("

");
ary.push("

品种信息:" + info.letter + "

");
ary.push("

成交量: " + info.child.value);
ary.push("

");
return ary.join("");
};

核心算法写完,就到了最终的渲染了

SEOver(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"," + (yLinearScale(d.child.value) + margin.bottom - 15) + ")"
});

g.append('rect')
.attr('x',0)
.attr('y',0)
.attr("class","transparentPath")
.attr('width',i) {
return d.ow;
})
.attr('height',function (d) {
return d.oh;
})
.style('fill',i) {
return mainColorList[i]
})
.transition()
.duration(2500)
.attr("height",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',i) {
return topColorList[i]
});

g.append('path')
.attr("class",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',i) {
return rightColorList[i]
})
.transition()
.duration(2500)
.attr("d",i) {
return "M" + d.ow + "," + (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"
});
}

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

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对编程之家的支持

猜你在找的JavaScript相关文章