D3是一种数据可视化工具,数据的可视化其实就是把数据以图表等直观的方式展示给用户,让用户更直观的感受到数据的走势和变化。这种应用在项目中越来越多的被使用,话说,千句话不如一张图,说的一点都不为过。那么D3作为这种轻巧免费公开的图表制作工具,在应用中被越来越多的使用。D3有很多让人眼前一亮的功能,现在我们所处理的是一些基本的图表,比如柱状图,饼图,折线图等。
话不多说,这篇博客主要是在项目中用到的柱状图的画法。先看一下效果图:
在显示报表的时候我们都喜欢将数据按照横纵坐标来展示数据的变化,Y轴一般是数值,X轴一般都是时间,当然也可以是其他的字符串用于显示某种类别。
代码的创建方式大同小异,都是绑定一个div,在div里面使用svg来做文章
前端代码详见:
<div id="ham-guest-summary-common-group-chart" class="ham-summary-common-group-chart" style="width: 100%"></div>
我们创建了一个容器来存放生成的svg。
我们来创建画布的高宽以及边距:
//定义柱状图的宽高边距属性
var margin = {top: 20, right:40, bottom: 0, left: 40},
width = 900,
height = 300;
定义X,Y轴的比例尺和作用范围:
//定义X轴作用范围
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
//定义Y轴作用范围
var y = d3.scale.linear()
.range([height, 0]);
//定义X比例尺
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
//定义Y比例尺
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
定义柱状图颜色:
//定义标准颜色样式定义svg画布:
var colorRange = d3.scale.category20();
var color = d3.scale.ordinal()
.range(colorRange.range());
var svg = d3.select("#ham-guest-summary-common-group-chart").append("svg")
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("viewBox", "0 0 1000 350")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
这里preserveAspectRatio和viewBox可以让svg画布支持缩放功能,前提是该viewbox的宽高必须大于画布的宽高才能做到缩放的效果。
接下来我们来使用data方法来绑定数据,以及x的作用域和y的值域:
var options = d3.keys(dataset[0]).filter(function(key) { return key !== "label"; });
dataset.forEach(function(d) {
d.valores = options.map(function(name) { return {name: name, value: +d[name]}; });
});
x0.domain(dataset.map(function(d) { return d.label; }));
x1.domain(options).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(dataset, function(d) { return d3.max(d.valores, function(d) { return d.value; }); })]);
数据绑定之后需要创建x轴和y轴,使用前面定义到的比例尺和作用域和值域来构建x轴y轴:
//画X轴将数据绑定到画布区域中,每一个柱状图都成为bar:
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
//画Y轴
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Numbers");
var bar = svg.selectAll(".bar")每一个bar都是由多个rect矩形组成,所以我们需要跟rect绑定数值,label属性:
.data(dataset)
.enter().append("g")
.attr("class", "rect")
.attr("transform", function(d) { return "translate(" + x0(d.label) + ",0)"; });
bar.selectAll("rect")
.data(function(d) { return d.valores; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("value", function(d){return d.name;})
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
前面的代码就可以生成柱状图了,那么现在当我们鼠标移动到柱状图上时,我们需要提示当前bar的数值信息,需要添加tooltips功能,需要一些样式的支持:
var divTooltip = d3.select("#ham-guest-summary-common-group-chart").append("div").attr("class", "toolTip");
.ham-summary-common-group-chart .legend { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 60%;}.ham-summary-common-group-chart rect { stroke-width: 2;}.ham-summary-common-group-chart text { font: 10px sans-serif;}.ham-summary-common-group-chart .axis text { font: 10px sans-serif;}.ham-summary-common-group-chart .axis path{ fill: none; stroke: #000;}.ham-summary-common-group-chart .axis line { fill: none; stroke: #000; shape-rendering: crispEdges;}.ham-summary-common-group-chart .axis .tick line { stroke-width: 1; stroke: rgba(0, 0, 0, 0.2);}.ham-summary-common-group-chart .axisHorizontal path{ fill: none;}.ham-summary-common-group-chart .axisHorizontal line { fill: none; stroke: #000; shape-rendering: crispEdges;}.ham-summary-common-group-chart .axisHorizontal .tick line { stroke-width: 1; stroke: rgba(0, 0, 0, 0.2);}.ham-summary-common-group-chart .bar { fill: steelblue; fill-opacity: .9;}.ham-summary-common-group-chart .x.axis path { display: none;}
bar.on("mousemove", function(d){
var scoll = getScrollTop();
divTooltip.style("left", d3.event.pageX + "px");
divTooltip.style("top", d3.event.pageY - scoll + "px");
//divTooltip.style("top", d3.event.pageY+"px");
divTooltip.style("display", "inline-block");
var x = d3.event.pageX, y = d3.event.pageY;
var elements = document.querySelectorAll(':hover');
var l = elements.length;
l = l-1;
var elementData = elements[l].__data__;
var title_name = "";
if(elementData.name == "newCreatedAccount"){
title_name = "New Created Account";
}else if(elementData.name == "totalGuestAccount"){
title_name = "Total Guest Account";
}else if(elementData.name == "activeGuestAccount"){
title_name = "Active Guest Account";
}else if(elementData.name == "totalGuestDevice"){
title_name = "Total Guest Device";
}
divTooltip.html((d.label)+"<br>"+title_name+"<br>"+elementData.value);
});
bar.on("mouseout", function(d){
divTooltip.style("display", "none");
});
在这里提到一个问题,当Y轴方向出现滚动条的时候,鼠标悬浮的提示信息,会发生变化,跟鼠标的位置有差距,这个问题我在其他的博客中写到过,可以参考:
获取滚动条的上边距,然后当前的坐标减去上边距就行:
//get scroll distance to top后台返回数据格式:
function getScrollTop() {
var scrollPos;
if (window.pageYOffset) {
scrollPos = window.pageYOffset;
}else if (document.compatMode && document.compatMode != 'BackCompat') {
scrollPos = document.documentElement.scrollTop;
}else if (document.body) {
scrollPos = document.body.scrollTop;
}
return scrollPos;
}
{需要将后台的数据做处理,转为柱状图识别的json格式:
"result": "success",
"errorCode": 0,
"errorMessage": null,
"data": [
{
"id": 20,
"newCreatedAccount": 0,
"totalGuestAccount": 3,
"activeGuestAccount": 1,
"totalGuestDevice": 1,
"statisticalDate": "30/08/17"
},
{
"id": 21,
"newCreatedAccount": 0,
"totalGuestAccount": 3,
"activeGuestAccount": 1,
"totalGuestDevice": 1,
"statisticalDate": "31/08/17"
},
{
"id": null,
"newCreatedAccount": 0,
"totalGuestAccount": 0,
"activeGuestAccount": 0,
"totalGuestDevice": 0,
"statisticalDate": "01/09/17"
},
{
"id": null,
"newCreatedAccount": 0,
"totalGuestAccount": 0,
"activeGuestAccount": 0,
"totalGuestDevice": 0,
"statisticalDate": "02/09/17"
},
{
"id": null,
"newCreatedAccount": 0,
"totalGuestAccount": 0,
"activeGuestAccount": 0,
"totalGuestDevice": 0,
"statisticalDate": "03/09/17"
},
{
"id": null,
"newCreatedAccount": 0,
"totalGuestAccount": 0,
"activeGuestAccount": 0,
"totalGuestDevice": 0,
"statisticalDate": "04/09/17"
},
{
"id": null,
"newCreatedAccount": 0,
"totalGuestAccount": 0,
"activeGuestAccount": 0,
"totalGuestDevice": 0,
"statisticalDate": "05/09/17"
},
{
"id": null,
"newCreatedAccount": 0,
"totalGuestAccount": 0,
"activeGuestAccount": 0,
"totalGuestDevice": 0,
"statisticalDate": "06/09/17"
},
{
"id": null,
"newCreatedAccount": 0,
"totalGuestAccount": 0,
"activeGuestAccount": 0,
"totalGuestDevice": 0,
"statisticalDate": "07/09/17"
},
{
"id": null,
"newCreatedAccount": 0,
"totalGuestAccount": 3,
"activeGuestAccount": 1,
"totalGuestDevice": 1,
"statisticalDate": "08/09/17"
}
],
"translated": {}
}
function getAccountAndDeviceNumber() {
var dataset = [];
amGuestHomeService.getAccountAndDeviceNumber()
.then(function success(response) {
if (response.data != null && response.data.length > 0) {
angular.forEach(response.data, function (item) {
var data = {};
data.label = item.statisticalDate;
data.newCreatedAccount = item.newCreatedAccount;
data.totalGuestAccount = item.totalGuestAccount;
data.activeGuestAccount = item.activeGuestAccount;
data.totalGuestDevice = item.totalGuestDevice;
dataset.push(data);
})
amGuestHomeService.drawGroupChart(dataset);
}
})
.catch(function fail(/*e*/) {
//handle error
})
.finally(function () {
vm.hideLoading();
});
}
最后附上全部代码:
function drawGroupChart(dataset){
//定义柱状图的宽高边距属性
var margin = {top: 20, right:40, bottom: 0, left: 40},
width = 900,
height = 300;
//定义X轴作用范围
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
//定义Y轴作用范围
var y = d3.scale.linear()
.range([height, 0]);
//定义标准颜色样式
var colorRange = d3.scale.category20();
var color = d3.scale.ordinal()
.range(colorRange.range());
//定义X比例尺
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
//定义Y比例尺
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
//这里是牵扯到查询会不断切换柱状图,将之前生成的svg删除
d3.select("#ham-guest-summary-common-group-chart svg").remove();
//定义鼠标移动时提示信息
var divTooltip = d3.select("#ham-guest-summary-common-group-chart").append("div").attr("class", "toolTip");
//创建svg画布preserveAspectRatio,viewBox这两个属性可以支持画布的缩放功能
var svg = d3.select("#ham-guest-summary-common-group-chart").append("svg")
.attr("preserveAspectRatio", "xMidYMid meet")
.attr("viewBox", "0 0 1000 350")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
//获取label
var options = d3.keys(dataset[0]).filter(function(key) { return key !== "label"; });
dataset.forEach(function(d) {
d.valores = options.map(function(name) { return {name: name, value: +d[name]}; });
});
x0.domain(dataset.map(function(d) { return d.label; }));
x1.domain(options).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(dataset, function(d) { return d3.max(d.valores, function(d) { return d.value; }); })]);
//画X轴
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
//画Y轴
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Numbers");
var bar = svg.selectAll(".bar")
.data(dataset)
.enter().append("g")
.attr("class", "rect")
.attr("transform", function(d) { return "translate(" + x0(d.label) + ",0)"; });
bar.selectAll("rect")
.data(function(d) { return d.valores; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("value", function(d){return d.name;})
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
bar.on("mousemove", function(d){
var scoll = getScrollTop();
divTooltip.style("left", d3.event.pageX + "px");
divTooltip.style("top", d3.event.pageY - scoll + "px");
//divTooltip.style("top", d3.event.pageY+"px");
divTooltip.style("display", "inline-block");
var x = d3.event.pageX, y = d3.event.pageY;
var elements = document.querySelectorAll(':hover');
var l = elements.length;
l = l-1;
var elementData = elements[l].__data__;
var title_name = "";
if(elementData.name == "newCreatedAccount"){
title_name = "New Created Account";
}else if(elementData.name == "totalGuestAccount"){
title_name = "Total Guest Account";
}else if(elementData.name == "activeGuestAccount"){
title_name = "Active Guest Account";
}else if(elementData.name == "totalGuestDevice"){
title_name = "Total Guest Device";
}
divTooltip.html((d.label)+"<br>"+title_name+"<br>"+elementData.value);
});
bar.on("mouseout", function(d){
divTooltip.style("display", "none");
});
}