JavaScript 可视化库D3介绍
渲染器的选择
- SVG
- Canvas
- HTML element
一般来说,Canvas 更适合绘制图形元素数量较多(这一般是由数据量大导致)的图表(如热力图、地理坐标系或平行坐标系上的大规模线图或散点图等),也利于实现某些视觉 特效。但是,在不少场景中,SVG 具有重要的优势:它的内存占用更低(这对移动端尤其重要)、并且用户使用浏览器内置的缩放功能时不会模糊。 选择哪种渲染器,我们可以根据软硬件环境、数据量、功能需求综合考虑。 在软硬件环境较好,数据量不大的场景下,两种渲染器都可以适用,并不需要太多纠结。 在环境较差,出现性能问题需要优化的场景下,可以通过试验来确定使用哪种渲染器。比如有这些经验: 在须要创建很多 ECharts 实例且浏览器易崩溃的情况下(可能是因为 Canvas 数量多导致内存占用超出手机承受能力),可以使用 SVG 渲染器来进行改善。大略得说,如果图表运行在低端安卓机,或者我们在使用一些特定图表如 水球图 等,SVG 渲染器可能效果更好。 数据量较大(经验判断 > 1k)、较多交互时,建议选择 Canvas 渲染器。 我们强烈欢迎开发者们反馈给我们使用的体验和场景,帮助我们更好的做优化。
引入
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上暴露了大量的方法以供用户操作,方便对元素添加、更新、删除对应的节点
- d3.selection - select the root document element.
- d3.select - select an element from the document.
- d3.selectAll - select multiple elements from the document.
- selection.select - select a descendant element for each selected element.
- selection.selectAll - select multiple descendants for each selected element.
- selection.filter - filter elements based on data.
- selection.merge - merge this selection with another.
- selection.selectChild - select a child element for each selected element.
- selection.selectChildren - select the children elements for each selected element.
- selection.selection - return the selection.
- d3.matcher - test whether an element matches a selector.
- d3.selector - select an element.
- d3.selectorAll - select elements.
- d3.window - get a node’s owner window.
- d3.style - get a node’s current style value.
事件注册
早期版本的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当中的元素对应关系如下
如果没有为数据绑定提供一个 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 指定一个自定义插值器。
模块
- Arrays (Statistics, Search, Iterables, Sets, Transformations, Histograms, Interning)
- Axes
- Brushes
- Chords
- Colors
- Color Schemes
- Contours
- Voronoi Diagrams
- Dispatches
- Dragging
- Delimiter-Separated Values
- Easings
- Fetches
- Forces
- Number Formats
- Geographies (Paths, Projections, Spherical Math, Spherical Shapes, Streams, Transforms)
- Hierarchies
- Interpolators
- Paths
- Polygons
- Quadtrees
- Random Numbers
- Scales (Continuous, Sequential, Diverging, Quantize, Ordinal)
- Selections (Selecting, Modifying, Data, Events, Control, Local Variables, Namespaces)
- Shapes (Arcs, Pies, Lines, Areas, Curves, Links, Symbols, Stacks)
- Time Formats
- Time Intervals
- Timers
- Transitions
- Zooming
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的创始人
D3提供了一个在线的可视化平台 Observable 为一些简单的例子提供了在线可运行的编辑器,并且封装了一些交互式组件提供了极大的便利
在Observable中的notebooks中编写的代码是分块执行的,各个代码块组成的程序结构是一个有向无环图(DAG) 更改其中的代码块之后会触发相关联的代码块重新执行
每个代码块要求必须返回一个值,如果返回的时HTML元素,那么会在notebooks当中进行渲染, 一个典型的代码块是
{
// 返回对应HTML元素
return svg.node()
}
另外比较比较好用的功能是,Observable提供文件存储功能,可能利用它提供的FileAttachment(file_name)
来访问对应的文件,以便对程序当中的数据进行填充
更方便的是可以提供类似 Jupter Notebooks 的功能,在编辑中可以书写markdown文档,而且提供部分TeX的数学公式语法支持,如果需要自定义样式的,也可以在代码块当中书写HTML