最近項目組加班比較嚴重,D3的博客就一拖再拖,今天終於不用加班了,趕緊抽點時間寫完~~
今天就將D3數據的更新及動畫寫一寫~~
接着之前的博客寫~~
之前寫了一個散點圖的例子,下面可以自己寫一個柱狀圖的例子。
我就直接給代碼了,和散點圖差不多~~
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軸");
其效果如下
坐標軸有些粗,是因為CSS沒設置,如果設置上在之前博客里的CSS樣式,會好看一點~~
先添加一個事件來觸發數據的變化,在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的交互和布局~~