4
这篇学习笔记是入门篇的最后一部分,将前几篇的内容整合到一起,绘制带过渡效果的柱状图,这次先给大家看一下结果图。

结果

clipboard.png

前言

先放结果图是想反馈一下在整合基础知识绘制完整柱状图遇到的几个问题:

  • 整个柱状图的布局,比如哪些元素包在一个<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 (i * rectWidth) + (i + 1) * (rectPadding / 2);
})
.attr("dx", 10)
.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);
})

附录

接下来会写进阶篇的学习笔记


winteraq
68 声望4 粉丝