d3入門篇(四):繪製完整柱狀圖添加過渡效果

這篇學習筆記是入門篇的最後一部分,將前幾篇的內容整合到一起,繪製帶過渡效果的柱狀圖,這次先給大家看一下結果圖。

結果

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 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);
})

附錄

接下來會寫進階篇的學習筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章