數據可視化(8)--D3數據的更新及動畫

最近項目組加班比較嚴重,D3的博客就一拖再拖,今天終於不用加班了,趕忙抽點時間寫完~~html

今天就將D3數據的更新及動畫寫一寫~~git

接着以前的博客寫~~github

以前寫了一個散點圖的例子,下面能夠本身寫一個柱狀圖的例子。數組

我就直接給代碼了,和散點圖差很少~~app

var margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;
var dataset = [ 11, 12, 15, 20, 18, 17, 16, 18, 23, 25, 8, 10, 13, 19, 21, 25, 22, 18, 15, 13];
// 使用了d3.scale.ordinal() 它支持範圍分檔。與定量比例尺(如d3.scale.linear())返回連續的範圍值不一樣,序數比例尺使用的是離散範圍值,也就是輸出值是事先就肯定好的。
// 映射範圍時,可使用range(),也可使用rangeBands()。後者接收一個最小值和一個最大值,而後根據輸入值域的長度自動將其切分紅相等的塊或「檔」。0.2也就是檔間距爲每一檔寬度的20%。
var x = d3.scale.ordinal()
    .domain(d3.range(dataset.length))
    .rangeBands([0, width], 0.2);

var y = d3.scale.linear()
    .domain([0, d3.max(dataset, function(d) { return d; })])
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

var svg = d3.select("body").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");


svg.selectAll("rect")// 插入的不是circle了,改成rect
    .data(dataset)
    .enter()
    .append("rect")
    .attr("x", function(d,i) {
        return x(i);
    })
    .attr("y", function(d) {
        return y(d);
    })
    .attr("width", x.rangeBand())
    .attr("height", function(d) {
        return height - y(d);
    })
    .attr("fill", function(d){
      return "rgb(60, 127, " + d * 10 + ")";// 根據值的大小獲取顏色
    });
svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis)
    .append("text")
      .attr("class", "label")
      .attr("x", width)
      .attr("y", -6)
      .style("text-anchor", "end")
      .text("X軸");

  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("class", "label")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("dy", ".71em")
      .style("text-anchor", "end")
      .text("Y軸");

其效果以下dom

座標軸有些粗,是由於CSS沒設置,若是設置上在以前博客裏的CSS樣式,會好看一點~~svg

先添加一個事件來觸發數據的變化,在html中body標籤裏添加一個button函數

<button>Update</button>

爲button綁定事件,並在事件中添加數據的更新,代碼以下佈局

// 單擊的時候,更新數據
d3.select("button").on("click", function() {
    // 新數據集
    dataset = [ 21, 22, 25, 10, 18, 17, 6, 8, 13, 15, 15, 20, 23, 19, 11,15, 25, 8, 25, 23 ];
    // 更新全部矩形
    svg.selectAll("rect")
        .data(dataset)
        .attr("y", function(d) {
            return y(d);
        })
        .attr("height", function(d) {
            return height - y(d);
        });
});

 

運行代碼,點擊update按鈕,chart發生變化,以下動畫

若是你足夠細心的話,就會發現,顏色跟原來的同樣,沒有根據長度發生變化,只要把原來針對fill 編寫的代碼複製到事件的代碼裏。

svg.selectAll("rect")
    .data(dataset)
    .attr("y", function(d) {
        return y(d);
    })
    .attr("height", function(d) {
        return height - y(d); 
    })
    .attr("fill", function(d){ 
        return "rgb(60, 127, " + d * 10 + ")";// 根據值的大小獲取顏色
    });

 

是否是顏色也跟着變化了。

是否是以爲,就這麼變化有些太坑了,那咱們就給它加個動畫,過渡一下就OK了,其實現只需簡單的一行代碼 .transition()

注:在方法鏈上,要把這個調用插到選擇元素以後,改變任何屬性以前

svg.selectAll("rect")
    .data(dataset)
    .transition() // <-- 這是新代碼,其餘都保持不變。
    .attr("y", function(d) {
        return y(d);
    })
    .attr("height", function(d) {
        return height - y(d);
    })
    .attr("fill", function(d){
        return "rgb(60, 127, " + d * 10 + ")";// 根據值的大小獲取顏色
    });

 

是否是有了動畫,就不會以爲變化有些忽然了~~

以後,我以爲想控制一下動畫的時間,要讓他快一點或者慢一點,其實現也只須要一行代碼 .duration(2000) 

svg.selectAll("rect")
    .data(dataset)
    .transition()
    .duration(2000) // <-- 這是新代碼,其餘都保持不變。2000 毫秒,即2 秒
    .attr("y", function(d) {
        return y(d);
    })
    .attr("height", function(d) {
        return height - y(d);
    })
    .attr("fill", function(d){
        return "rgb(60, 127, " + d * 10 + ")";// 根據值的大小獲取顏色
    });

 

你延長了一下動畫時間,發現它的動畫一開始很是慢,而後逐漸加速,最後在達到預約高度以前速度再次慢了下來。換句話說,動畫的速度不是線性不變的,而是有加減速變化的。

若是,我想要均勻的變化怎麼辦?

在D3 中,可使用ease() 指定不一樣的緩動類型。默認的緩動效果是"cubic-inout",產生的就是咱們剛剛看到的那種逐漸加速而後再逐漸減速的效果。

因此咱們只須要設置一下ease("linear")就能夠了。咱們要在transition() 以後、attr() 以前指定ease()。事實上,ease()在duration() 以前以後都沒問題,但先過渡再設置緩動彷佛更瓜熟蒂落。

... // 選擇元素的代碼
.transition()
.duration(2000)
.ease("linear")
... //attr() 的代碼

 

linear是線性緩動,就是沒有逐漸加速和減速的變化,全部元素都按照一個速度變化,變化到最終值時戛然而止。

除此以外,還有不少緩動函數可供選擇。下面只是幾個,不是所有
• circle
逐漸進入並加速,而後忽然中止。
• elastic
描述這個效果的一個最恰當的詞是「有彈性」。
• bounce
像皮球落地同樣反覆彈跳,慢慢停下來。

詳細能夠查看:https://github.com/mbostock/d3/wiki/Transitions#wiki-d3_ease

咱們再來想一下,若是這個動畫,我不想讓它一開始就跑起來,我想讓它晚點跑起來,好比說2秒後。

那咱們只須要在添加一個方法.delay(2000),就OK了

...
.transition()
.delay(2000) //2000 毫秒,即2 秒
.duration(2000) //2000 毫秒,即2 秒
...

與使用duration() 和ease() 同樣,把delay() 放到哪裏並無十分嚴格的限制,但我更喜歡把它放在duration() 前面。由於是先設定延遲時間,而後過渡動畫纔開始計時,這樣比較符合邏輯。

上面的代碼是靜態延時,靜態延遲時間只是一種延遲方式,更有意思的是能夠動態計算延遲時間。動態延遲的一個常見用途就是建立交錯延遲的效果,讓某些過渡在另外一個過渡以前發生。交錯延遲對人的感知有利,由於當相鄰元素的變化不那麼同步時,人眼更容易注意到每一個元素的變化。要設置動態延遲,就別給delay() 傳遞靜態值,而是給它傳入一個函數,按照D3 的慣例……對,就是傳入一個匿名函數。

...
.transition()
.delay(function(d, i) {
    return i * 100;
})
.duration(500)
...

 

在匿名函數中,與當前元素綁定的數據值是以d 傳入的,而這個元素的位置是以i傳入的。所以,這裏的意思是讓D3 循環遍歷每一個元素,把它們的動畫延遲時間設定爲i * 100,也就是後一個元素的動畫開始時間總比前一個元素晚100 毫秒。

到這裏,也許你會提出一個疑問,若是變化的時候數據超出了原來的範圍,怎麼辦?

那咱們就須要更新比例尺了,更新比例尺的代碼很簡單。

// 更新比例尺的值域
y.domain([0, d3.max(dataset)]);

 

今天就先到這裏,寫博客時間長了,以爲有點腰疼。

等有時間再繼續更新吧~~

繼續寫這篇博客,就不另起一篇了~~

 

迄今爲止,只要更新數據,咱們採用的都是「整批整包」的方式:改變數據集數組中 的值,而後從新綁定修改後的值,覆蓋原始值對DOM 元素的綁定。這種方式很是適合全部值都會改變,並且數據集長度(即數據值的數量)不變的情形。但是咱們知道,現實中的數據可沒那麼簡單。這就對代碼的靈活 性提出了更高要求,好比只更新一兩個值,或者支持增長值和減小值。

添加值(和元素)

首先咱們須要在數據中插入一個值,並更新一下數軸的值域。

dataset.push(10);
x.domain(d3.range(dataset.length));

而後選出以前的元素,而後插入,插入的代碼跟剛開始的代碼很像。

// 加入……
var bars = svg.selectAll("rect")
    .data(dataset);// 選擇出以前的元素
bars.data(dataset)
    .enter()
    .append("rect")
    .attr("x", x(dataset.length - 1))//這行代碼設定了新條形的水平位置,讓它剛好位於SVG 區域的最右邊。
    .attr("y", function(d) {
        return y(d);
    })
    .attr("width", x.rangeBand())
    .attr("height", function(d) {
        return height - y(d);
    })
    .attr("fill", function(d){
      return "rgb(60, 127, " + d * 10 + ")";
    });

最後,再更新全部的數據就OK了

bars.transition()
    .duration(500)
    .attr("x", function(d, i) {
    return x(i);
    })
    .attr("y", function(d) {
        return y(d);
    })
    .attr("width", x.rangeBand())
    .attr("height", function(d) {
        return height - y(d);
    })
    .attr("fill", function(d){
      return "rgb(60, 127, " + d * 10 + ")";
    });

這樣,動態的添加一條數據就實現了,而後你會發現數據的座標軸的刻度沒有變,那就須要再去更新一下座標軸的刻度。在更新值域的代碼後面,添加以下代碼

xAxis.scale(x);
svg.select("g.x.axis")
    .call(xAxis);

你會發現座標軸的刻度也跟着變化了~~

刪除值(和元素)

把每次單擊時添值到數據集,改成使用shift() 方法從數組中刪除第一個元素。

// 從數據集中刪除一個值
dataset.shift();

而後取得退出元素集,而後把它們過渡到右邊,最後,刪除它們

// 退出……
bars.exit()
    .transition()
    .duration(500)
    .attr("x", w)
    .remove();

remove() 是一特殊的過渡方法,它會在過渡完成後從DOM 中永遠地刪除元素。移除以後座標軸的更新和以前的添加元素相同。

經過鍵聯結數據

在把數據綁定到DOM 元素時(即調用data() 時),就會發生數據聯結。默認的聯結是按照索引順序,即第一個值綁定到元素集中第一個DOM 元素,第二個值綁定到元素集中第二個DOM 元素,依此類推。若是數據值與DOM 元素的順序不同呢?那就得告訴D3 怎麼實現值和元素間的聯結或配對。好在,經過定義鍵函數(key:function),能夠指定相應的規則。

先準備數據,以前,咱們的數據集就是包含簡單數值的數組。而爲了使用鍵函數,每一個值必須有對應的鍵。

var dataset = [ 
{ key: 0, value: 5 }, { key: 1, value: 10 }, { key: 2, value: 13 }, { key: 3, value: 19 }, { key: 4, value: 21 }, { key: 5, value: 25 }, { key: 6, value: 22 }, { key: 7, value: 18 }, { key: 8, value: 15 }, { key: 9, value: 13 }, { key: 10, value: 11 }, { key: 11, value: 12 }, { key: 12, value: 15 }, { key: 13, value: 20 }, { key: 14, value: 18 }, { key: 15, value: 17 }, { key: 16, value: 16 }, { key: 17, value: 18 }, { key: 18, value: 23 }, { key: 19, value: 25 }
];

更新引用,許多之前用d的地方要修改成d.value,如

var y = d3.scale.linear()
    .domain([0, d3.max(dataset, function(d) { return d.value;})])
    .range([0, height]);

連接鍵函數,在data一個數據集時,使用以下代碼,就OK了。

.data(dataset, function(d) {
    return d.key;
})

 

 關於數據的更新和動畫就先到這裏爲止了,以後會寫D3的交互和佈局~~

相關文章
相關標籤/搜索