注
本文基於 D3.js 作者
Mike Bostock
的 例子
原文分為三部分, 在這裡筆者將其整合為了一篇方便閱讀.
該效果基於 D3.js, 主要使用到了 d3-selection. 如果對d3-selection的基本使用邏輯不太清楚可以參見 這篇文章.
效果圖
-
Step1 首先程式碼會隨機生成一個字串, 該字元以綠色進入畫面.
-
Step2 接下來, 程式碼隨機生成一個新字串, 新生成的字串會和原始字串進行對比:
2.1 新字串和原始字串中相同的字母,會變成黑色保留在螢幕上
2.2 原始字串中有, 而新字串中沒有的字母, 會變成紅色,被移除螢幕
2.3 新字串中有, 而原始字串中沒有的字母, 會變成綠色,被新增到螢幕
程式碼實現
1. 字元切換
第一步要完成的效果是:
- 完成基本字元切換
- 進入時為綠色, 不變時為黑色
- 被移除的字元直接被從介面中移除
先上程式碼, 點我執行
<script>
var alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(32," + (height / 2) + ")");
function update(data) {
// DATA JOIN
// Join new data with old elements, if any.
var text = g.selectAll("text")
.data(data);
// UPDATE
// Update old elements as needed.
text.attr("class", "update");
// ENTER
// Create new elements as needed.
//
// ENTER + UPDATE
// After merging the entered elements with the update selection,
// apply operations to both.
text.enter().append("text")
.attr("class", "enter")
.attr("x", function(d, i) { return i * 32; })
.attr("dy", ".35em")
.merge(text)
.text(function(d) { return d; });
// EXIT
// Remove old elements as needed.
text.exit().remove();
}
// The initial display.
update(alphabet);
// Grab a random sample of letters from the alphabet, in alphabetical order.
d3.interval(function() {
update(d3.shuffle(alphabet)
.slice(0, Math.floor(Math.random() * 26))
.sort());
}, 1500);
</script>
複製程式碼
程式碼不長, 接下來一步步分析程式碼邏輯:
首先, 獲取svg的寬高資訊. 並建立一個
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(32," + (height / 2) + ")");
複製程式碼
在 update()
方法中, 我們傳進來一個字串 data ** (data由26個字母中隨機選出一些組成). 首先我們選中所有的 text 元素並將其和data** 進行繫結:
var text = g.selectAll("text")
.data(data);
複製程式碼
然後我們處理enter集合 (也就是新加入的字元), 為每一個新字元新增一個 text 元素, 併為其新增class屬性, 使用index為其計算出橫縱座標:
text.enter().append("text") // 新增svg text元素
.attr("class", "enter") // 新增class屬性 (綠色)
.attr("x", function(d, i) { return i * 32; }) // 按每個字元32畫素, 為其計算x座標
.attr("dy", ".35em") // 設定y軸偏移量
複製程式碼
接下來同時處理 enter 和 update 集合, 將字元資料填入 text 元素中:
.merge(text)
.text(function(d) { return d; });
複製程式碼
最後處理 exit集合, 我們暫時直接將其從螢幕中移除:
text.exit().remove();
複製程式碼
我們用 d3.interval(callback, timeInterval)
定時呼叫update()
來實現字元重新整理. 現在我們就得到了下面的效果:
2. 為字元設定key值
如果你觀察仔細的話, 你可能已經發現第一步實現的效果中: 新加的字元總是出現在最後. 這顯然不是我們想要的效果, 新加的字元出現的位置應該是隨機的. 那麼出現現在這個效果的原因是什麼呢?
答案在於我們在繫結字元資料時: data(data)
並沒有指定data的key值, 那麼d3會預設使用index作為key, 這樣就是為什麼新增加的字元總是出現在最後面.
所以我們為字元加入key值的accessor:
var text = g.selectAll("text")
.data(data, function(d) { return d; });
複製程式碼
現在 key 值繫結好後, 已經存在的字元在update時text已經不會再改變, 但是座標需要重新計算, 所以我們做以下改動:
text.enter().append("text")
.attr("class", "enter")
.attr("dy", ".35em")
.text(function(d) { return d; }) // 將text賦值移動到 enter中
.merge(text)
.attr("x", function(d, i) { return i * 32; }); // 將座標計算移動到 merge後 (enter & update)
複製程式碼
現在我們的程式碼長這樣, 點我線上執行:
<script>
var alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(32," + (height / 2) + ")");
function update(data) {
// DATA JOIN
// Join new data with old elements, if any.
var text = g.selectAll("text")
.data(data, function(d) { return d; });
// UPDATE
// Update old elements as needed.
text.attr("class", "update");
// ENTER
// Create new elements as needed.
//
// ENTER + UPDATE
// After merging the entered elements with the update selection,
// apply operations to both.
text.enter().append("text")
.attr("class", "enter")
.attr("dy", ".35em")
.text(function(d) { return d; })
.merge(text)
.attr("x", function(d, i) { return i * 32; });
// EXIT
// Remove old elements as needed.
text.exit().remove();
}
// The initial display.
update(alphabet);
// Grab a random sample of letters from the alphabet, in alphabetical order.
d3.interval(function() {
update(d3.shuffle(alphabet)
.slice(0, Math.floor(Math.random() * 26))
.sort());
}, 1500);
</script>
複製程式碼
現在我們得到的效果:
3.新增動畫
動畫能讓我們更好的觀察元素的變化過程和狀態, 給不同狀態的元素賦予不同的動畫可以更直觀的展示我們的資料.
現在我們給字元的變化增加動畫效果, 並把字元移除時的顏色變化補上.
首先我們定義一個 transition變數, 並設定其動畫間隔為 750
var t = d3.transition()
.duration(750);
複製程式碼
在D3中使用動畫非常簡單, 在動畫前指定元素的一些屬性, 呼叫動畫, 再指定動畫後的一些屬性. D3會自動根據插值器生成動畫.
下面的程式碼對於離開介面的字元(exit selection)進行了處理:
text.exit()
.attr("class", "exit") // 動畫前, 設定class屬性, 字型變紅
.transition(t) // 設定動畫
.attr("y", 60) // 設定y座標, 使元素向下離開介面 (y: 0 => 60)
.style("fill-opacity", 1e-6) // 設定透明度, 使元素漸變消失 (opacity: 1 => 0)
.remove(); // 最後將其移出介面
複製程式碼
同樣的, 我們對 enter 和 update selection進行處理:
// UPDATE old elements present in new data.
text.attr("class", "update")
.attr("y", 0)
.style("fill-opacity", 1)
.transition(t)
.attr("x", function(d, i) { return i * 32; });
// ENTER new elements present in new data.
text.enter().append("text")
.attr("class", "enter")
.attr("dy", ".35em")
.attr("y", -60)
.attr("x", function(d, i) { return i * 32; })
.style("fill-opacity", 1e-6)
.text(function(d) { return d; })
.transition(t)
.attr("y", 0)
.style("fill-opacity", 1);
複製程式碼
最終我們的程式碼長這樣, 點我執行
<script>
var alphabet = "abcdefghijklmnopqrstuvwxyz".split("");
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
g = svg.append("g").attr("transform", "translate(32," + (height / 2) + ")");
function update(data) {
var t = d3.transition()
.duration(750);
// JOIN new data with old elements.
var text = g.selectAll("text")
.data(data, function(d) { return d; });
// EXIT old elements not present in new data.
text.exit()
.attr("class", "exit")
.transition(t)
.attr("y", 60)
.style("fill-opacity", 1e-6)
.remove();
// UPDATE old elements present in new data.
text.attr("class", "update")
.attr("y", 0)
.style("fill-opacity", 1)
.transition(t)
.attr("x", function(d, i) { return i * 32; });
// ENTER new elements present in new data.
text.enter().append("text")
.attr("class", "enter")
.attr("dy", ".35em")
.attr("y", -60)
.attr("x", function(d, i) { return i * 32; })
.style("fill-opacity", 1e-6)
.text(function(d) { return d; })
.transition(t)
.attr("y", 0)
.style("fill-opacity", 1);
}
// The initial display.
update(alphabet);
// Grab a random sample of letters from the alphabet, in alphabetical order.
d3.interval(function() {
update(d3.shuffle(alphabet)
.slice(0, Math.floor(Math.random() * 26))
.sort());
}, 1500);
</script>
複製程式碼
最終效果:
想繼續瞭解 D3.js ?
這裡是我的 D3.js 、 資料視覺化 的github 地址, 歡迎 start & fork :tada: