JavaScript 視覺化庫D3介紹

語言: CN / TW / HK

JavaScript 視覺化庫D3介紹

渲染器的選擇

  • SVG
  • Canvas
  • HTML element

一般來說,Canvas 更適合繪製圖形元素數量較多(這一般是由資料量大導致)的圖表(如熱力圖、地理座標系或平行座標系上的大規模線圖或散點圖等),也利於實現某些視覺 特效。但是,在不少場景中,SVG 具有重要的優勢:它的記憶體佔用更低(這對移動端尤其重要)、並且使用者使用瀏覽器內建的縮放功能時不會模糊。 選擇哪種渲染器,我們可以根據軟硬體環境、資料量、功能需求綜合考慮。 在軟硬體環境較好,資料量不大的場景下,兩種渲染器都可以適用,並不需要太多糾結。 在環境較差,出現效能問題需要優化的場景下,可以通過試驗來確定使用哪種渲染器。比如有這些經驗: 在須要建立很多 ECharts 例項且瀏覽器易崩潰的情況下(可能是因為 Canvas 數量多導致記憶體佔用超出手機承受能力),可以使用 SVG 渲染器來進行改善。大略得說,如果圖表執行在低端安卓機,或者我們在使用一些特定圖表如 水球圖 等,SVG 渲染器可能效果更好。 資料量較大(經驗判斷 > 1k)、較多互動時,建議選擇 Canvas 渲染器。 我們強烈歡迎開發者們反饋給我們使用的體驗和場景,幫助我們更好的做優化。

來自echarts 最佳實踐 canvas vs svg

render.png

引入

import {scaleLinear} from "d3-scale";

import * as d3 from "d3";

const d3 = await import("d3");

d3當中的核心概念

最簡單的一個例子

d3.js當中主要使用 SVG 進行開發,相比直接操作惡心的HTML DOM,D3提供的一套宣告式語法更加舒服。

var paragraphs = document.getElementsByTagName("p"); for (var i = 0; i < paragraphs.length; i++) {  var paragraph = paragraphs.item(i);  paragraph.style.setProperty("color", "blue", null); }

顯然能夠降低心智負擔

d3.selectAll("p").style("color", "blue");

練習

d3.selectAll("p") .style("color", function(d, i) {  return i % 2 ? "blue" : "red"; });

selection

概念

d3當中大部分的操作都是針對selection進行的

  • 事件偵聽
  • style屬性設定
  • 資料繫結
  • ...

通過以下函式可以生成一個selection

d3.create() //創造一個元素 d3.select() //選擇匹配的第一個元素 d3.selectAll() //選擇一組元素

Selection物件的結構如下

{    export function Selection(groups, parents) {        this._groups = groups;        this._parents = parents;   } ​    function selection() {        return new Selection([[document.documentElement]], root);   } ​    function selection_selection() {        return this;   } ​    Selection.prototype = selection.prototype = {        constructor: Selection,        select: selection_select,        selectAll: selection_selectAll,        selectChild: selection_selectChild,        selectChildren: selection_selectChildren,        filter: selection_filter,        data: selection_data,        enter: selection_enter,        exit: selection_exit,        ...   }; }

實際生成 Selection 物件

生成的selection物件會有兩個隱藏屬性,來表示該selection的結構,selection具體是如何工作的,可以檢視 Mike Bostock 的 How Selection work

d3通過建立或者選擇DOM元素生成一個Selection

這些函式本質上也是呼叫的W3C DOM API中的Selector API

``` function empty() { return []; }

export default function(selector) { return selector == null ? empty : function() { // 呼叫元素的selector return this.querySelectorAll(selector); }; } ```

selection上暴露了大量的方法以供使用者操作,方便對元素新增、更新、刪除對應的節點


事件註冊

selection_work.png

早期版本的d3 是通過全域性的d3.event獲取當前事件資訊,在後續版本已經移除,直接在事件監聽器中傳遞當前的事件資訊,d3的版本可以通過全域性暴露的d3.version獲取

d3.selectAll("div") .on("mouseover", function(){ d3.select(this) .style("background-color", "orange"); // Get current event info console.log(d3.event); // Get x & y co-ordinates console.log(d3.mouse(this)); }) .on("mouseout", function(){ d3.select(this) .style("background-color", "steelblue") });

d3通過selection.on()方法註冊事件監聽器, 任何瀏覽器支援的標準事件型別都支援

d3.create("ul") .call(ul => ul.selectAll("li") .data(names) .join("li") .text(name => `My name is ${name}! `) .append("a") .attr("href", "#") .on("click", click) .text("Pick me.")) .node()

資料繫結

D3直接繫結資料到selection上,資料可以是任意陣列型別,給定一個數組和selection就可以將每個陣列元素追加到selection當中每個元素。

比如,一個number的陣列

[1,2,3,4,5];

或者一個二維陣列

const matrix = [ [11975, 5871, 8916, 2868], [ 1951, 10048, 2060, 6171], [ 8010, 16145, 8090, 8045], [ 1013, 990, 940, 6907] ];

或者一個物件陣列

const letters = [ {name: "A", frequency: .08167}, {name: "B", frequency: .01492}, {name: "C", frequency: .02780}, {name: "D", frequency: .04253}, {name: "E", frequency: .12702} ];

資料繫結會在對應的dom元素上掛載一個對應的__data__屬性

data當中的資料和selection當中的元素對應關係如下

data.png

如果沒有為資料繫結提供一個 key function 那麼會預設的根據元素在DOM當中的順序繫結資料

d3.selectAll("div") .data(data, function(d) { return d ? d.name : this.id; }) .text(d => d.number);

過渡

transition 是一個類 selection 的介面,用來對 DOM 進行動畫修改。這種修改不是立即修改,而是在規定的事件內平滑過渡到目標狀態。

應用過渡,首先要選中元素,然後呼叫 selection.transition,並且設定期望的改變,例如:

d3.select("body") .transition() .style("background-color", "red"); 過渡支援大多數選擇集的方法(比如 transition.attr 和 transition.style 對應 selection.attr 和 selection.style),但是並不是所有的方法都支援; 比如必須在對元素過渡之前 append 元素或者 bind data。transition.remove 操作可以在動畫結束時方便的移除元素。

為了計算過渡過程中的狀態,過渡集成了大量的 built-in interpolators。Colors, numbers, 以及 transforms 會被自動檢測。內嵌數字的 Strings 也會被檢測到,可以方便的對許多樣式(比如 padding 或 font size) 以及 path 進行過渡。可以使用 transition.attrTween, transition.styleTween 或 transition.tween 指定一個自定義插值器。

模組

d3是各個模組配合一起工作的,可以從collection of modules單獨引入,也可以一起使用。 如果是繪製柱狀圖、折線圖、這類圖表,可以選擇axes來提供定義座標的樣式,選擇Scales來定義比例尺。如果還有顏色和互動上的需求可以選擇 Zooming Drag 等模組 來縮放和拖拽...

還有一些有意思的模組

  • Forces,來提供力學模擬
  • Geographies 提供地理以及幾何的繪製

Forces

D3的力學模擬是通過韋爾萊積分法(Verlet Integration)實現,通過對資料當中的節點新增力學函式來實現特定的力學模擬,每次觸發tick事件時,會更新節點的各個屬性

const simulation = d3.forceSimulation(nodes) .force("link", forceLink) .force("charge", forceNode) .force("center", d3.forceCenter()) .on("tick", ticked);

線上的視覺化平臺Observable

d3的主要維護人員,同時也是Overvable的創始人

mike-bostock.jpg

D3提供了一個線上的視覺化平臺 Observable 為一些簡單的例子提供了線上可執行的編輯器,並且封裝了一些互動式元件提供了極大的便利

demo地址

observable-tower.png

在Observable中的notebooks中編寫的程式碼是分塊執行的,各個程式碼塊組成的程式結構是一個有向無環圖(DAG) 更改其中的程式碼塊之後會觸發相關聯的程式碼塊重新執行

每個程式碼塊要求必須返回一個值,如果返回的時HTML元素,那麼會在notebooks當中進行渲染, 一個典型的程式碼塊是

{ // 返回對應HTML元素 return svg.node() }

另外比較比較好用的功能是,Observable提供檔案儲存功能,可能利用它提供的FileAttachment(file_name)來訪問對應的檔案,以便對程式當中的資料進行填充

file_attachments.png

更方便的是可以提供類似 Jupter Notebooks 的功能,在編輯中可以書寫markdown文件,而且提供部分TeX的數學公式語法支援,如果需要自定義樣式的,也可以在程式碼塊當中書寫HTML

observable-fn.png