共计 4470 个字符,预计需要花费 12 分钟才能阅读完成。
这篇学习笔记是入门篇的最后一部分,将前几篇的内容整合到一起,绘制带过渡效果的柱状图,这次先给大家看一下结果图。
结果
前言
先放结果图是想反馈一下在整合基础知识绘制完整柱状图遇到的几个问题:
整个柱状图的布局,比如哪些元素包在一个 <g> 标签下,哪些元素是嵌套关系;
如果不采用 translate,transform 翻转 height 属性的值,如何让矩形正常方向显示;
如何绘制文字;
如何为柱状图添加过渡效果;
坐标轴的位置如何确定,x 轴如何划分刻度,如何让刻度显示在矩形的正下方;
!!!!接下来将逐个解决上述出现的问题!!!!
Problem1: 柱状图的整体布局
Solution
(1)为了绘制时,图形四周留有空白区域,我们首先设置一个 padding 值;
var padding={top:40,bottom:40,left:40,right:40};// 定义间隔
(2)我们考虑在 svg 画布上进行绘制,采用如下的结构进行绘图:
<svg>
// 将 x 轴包裹在一个 g 标签下
<g></g>
// 将 y 轴包裹在一个 g 标签下
<g></g>
// 将整个柱状图的矩形及文字包裹在一个 g 标签下
<g>
// 将柱形图的每个矩形与它相应的值包裹在一个 g 标签下
<g>
<rect>
<text>
</g>
</g>
</svg>
Problem2: 如何按照垂直向上的方向显示矩形
Solution 之前的几篇文章我都是通过 transform 变换实现了矩形的翻转,这篇文章介绍一个新的思路。首先确定一个矩形需要四要素(x,y,width,height),同时我们需要注意,画布的坐标轴方向为水平向右和垂直向下。height 是我们数据可视化的展示部分,即数据的绑定部分,x,y 确定了绘制矩形的左上角坐标。这里提供一个思路:如果按照正常垂直向下的方向绘制矩形时,要求矩形的 bottom 处在同一水平线上,y+height== 固定值;也就是数据(height)大的部分,我们希望矩形的绘制起始点(y)的值较小,数据小(height)的部分,我们希望矩形的绘制起始点(y)的值较大。因此我们可以通过定义比例尺完成这个功能,将 dataset 中大的数值,映射出 range 中小的数值。
// 定义 y 方向比例尺
var yScale=d3.scaleLinear()
.domain([0,d3.max(dataset)])
.range([height-padding.top-padding.bottom,0]);
// 定义 y 的值
.attr(“y”,function (d,i) {
return yScale(d)
})
// 定义 height
.attr(“height”,function (d,i) {
return height-padding.top-padding.bottom-yScale(d);
})
可以看出来‘y’+‘height’==height-padding.top-padding.bottom(这是一个固定的值)
Problem3: 如何绘制文字
Solution 在 Problem1 中已经解决的布局方案问题,我们的方法是将矩形与文字包在一个 g 标签下,所以绘制文字与绘制矩形的方法相同,在 <g> 标签下添加 <text> 标签,同时需要设定:(1) 文字的显示位置:x,y (2) 文本信息:text (3) 文字位置的偏移值:dx,dy
graph.append(“text”)
.style(“fill”,”pink”)
.attr(“x”,function(d,i){
return xScale(i);
})
.text(function (d) {
return d
})
.attr(“y”,function (d,i) {
return yScale(d);
})
Problem4: 如何为柱状图添加过渡效果
Solution 为柱状图添加过渡效果,我们需要调用以下 API:
.transition(): 为这个元素添加过渡;
.duration():设定元素从起始状态到终止状态的过渡时间;
.delay():设定元素执行过渡效果的时间间隔;
.ease():设定过渡的动画效果;
在为元素添加过渡效果时,初始状态,终止状态尤为重要,柱状图为例分析一下元素的两个状态:
明确柱形图为每个矩形添加过渡时,只有两个属性值需要改变,一个是 y 的值,一个是 height 的值;
起始状态:柱状图的起始状态非常好理解,就是矩形不显示的状态,即 y 值设定为前文提到的固定值,height 设定为 0;终止状态:柱状图的终止状态应该是矩形元素和文字都可视化固定显示出来,即为正常绑定元素时设定的相关属性值。
// 为矩形添加过渡效果
.attr(“y”,function (d) {
var min=yScale.domain()[0];
return yScale(min);
})
.attr(“height”,function(d,i){
return 0;
})
.transition()
.duration(2000)
.delay(function(d,i){
return i*400;
})
.ease(d3.easeBackOut)
.attr(“y”,function (d,i) {
return yScale(d)
})
.attr(“height”,function (d,i) {
return height-padding.top-padding.bottom-yScale(d);
})
Problem5: 格式化显示坐标轴
Solution 在开始学习坐标轴的时候,只实现了添加 y 轴,在这次完整柱状图实现中,尝试添加 x 轴却遇到了问题。在这个例子中我们一共绑定了 8 个数据,那么如何让 x 轴的刻度均匀的显示在每个矩形的下方呢?在定义 x 轴的时候我用了 ScaleBand() 这个方法:
// 在 range 返回等差数列
var xScale=d3.scaleBand()
.domain(d3.range(dataset.length))
.rangeRound([0,dataset.length*(rectWidth+(rectPadding/2))]);
var xAxis=d3.axisBottom(xScale)
.ticks(5);
既然比例尺返回一个等差数列,所以我们要求在柱状图区域,每个矩形和空白间隔这个整体是相同的,所以我的实现是每个矩形左右是半个 rectPadding。先设置 x 的值,然后 width 设置成矩形宽度减去半个间隔。(不理解的可以自己画一张图就可以了)
.attr(“x”,function (d,i) {
return (i*rectWidth)+(i+1)*(rectPadding/2);
})
.attr(“width”,rectWidth-rectPadding/2)
代码部分
import * as d3 from “d3”;
var dataset = [45, 70, 12, 79, 4, 127, 33, 150];
var width = 600;//svg 画布宽
var height = 600;//svg 画布高
var rectWidth = 50;// 每个矩形的默认宽度
var rectPadding=10;// 每个矩形间的间隔
var padding={top:40,bottom:40,left:40,right:40};// 定义间隔
// 定义画布
var svg = d3.select(“body”)
.append(“svg”)
.attr(“width”, width)
.attr(“height”, height)
.style(“background-color”, “yellow”);
// 定义矩形比例尺
var yScale=d3.scaleLinear()
.domain([0,d3.max(dataset)])
.range([height-padding.top-padding.bottom,0]);
var yAxis=d3.axisLeft(yScale)
.ticks(5);
svg.append(“g”)
.attr(“transform”,`translate(${padding.top},${padding.left})`)
.call(yAxis);
var xScale=d3.scaleBand()
.domain(d3.range(dataset.length))
.rangeRound([0,dataset.length*(rectWidth+(rectPadding/2))]);
var xAxis=d3.axisBottom(xScale)
.ticks(5);
svg.append(“g”)
.attr(“transform”,`translate(${padding.left},${height-padding.top})`)
.call(xAxis);
// 定义矩形
var g=d3.selectAll(“svg”)
.append(“g”)
.attr(“transform”,`translate(${padding.top},${padding.left})`);
var graph=g.selectAll(“rect”)
.data(dataset)
.enter()
.append(“g”);
graph.append(“rect”)
.style(“fill”,”blue”)
.attr(“x”,function (d,i) {
return (i*rectWidth)+(i+1)*(rectPadding/2);
})
.attr(“width”,rectWidth-rectPadding/2)
.attr(“y”,function (d) {
var min=yScale.domain()[0];
return yScale(min);
})
.attr(“height”,function(d,i){
return 0;
})
.transition()
.duration(2000)
.delay(function(d,i){
return i*400;
})
//.ease(d3.easeBackOut)
.attr(“y”,function (d,i) {
return yScale(d)
})
.attr(“height”,function (d,i) {
return height-padding.top-padding.bottom-yScale(d);
})
graph.append(“text”)
.style(“fill”,”pink”)
.attr(“x”,function(d,i){
return xScale(i);
})
.attr(“y”,function (d) {
var min=d3.min(dataset);
return yScale(min)
})
.text(function (d) {
return d
})
.transition()
.duration(2000)
.delay(function(d,i){
return i*400;
})
.attr(“y”,function (d,i) {
return yScale(d);
})
附录
接下来会写进阶篇的学习笔记