D3与ES6、TypeScript及相关应用实践
1. SVG编辑与导入
在处理SVG文件时,并非所有SVG程序在编辑导出的SVG文件时都能完美工作。例如,当在Adobe Illustrator中打开某些导出的SVG文件时,可能会出现问题。这是因为Adobe Illustrator虽然支持我们在导出时添加的嵌入式样式,但它只支持非常特定的类名。若将所有类名从
.link
更改为
cls - 1
形式,就可以使用Illustrator加载导出的SVG文件。
下面介绍如何从Inkscape导入SVG并在D3中使用。以一个老虎的SVG图像为例,先使用Inkscape将其旋转,然后通过以下代码导入并展示:
d3.xml('data/tiger.svg', loaded);
function loaded(err, tiger) {
var tigerSVG =
d3.select(tiger.documentElement.querySelector("g")).attr("transform", null).node();
svg.append("g").node().appendChild(tigerSVG);
}
在浏览器中打开该示例,就能看到旋转后的老虎图像,之后便可以使用D3对这个SVG进行修改。
2. 使用画笔选择元素
可以使用
d3.brush
轻松选择SVG元素上的一个区域。打开示例
<DVD3>/src/chapter - 08/src/D08 - 06.js
,会看到200个随机放置的符号。使用鼠标左键点击并拖动,就能选择一个区域,这个正方形区域就是
d3.brush
,可以通过鼠标轻松调整其大小和位置。
以下是实现该功能的详细代码:
// 创建符号数组
var symbols = [
{name: 'Cross', symbol: d3.symbolCross},
{name: 'Circle', symbol: d3.symbolCircle},
{name: 'Diamond', symbol: d3.symbolDiamond},
{name: 'Square', symbol: d3.symbolSquare},
{name: 'Star', symbol: d3.symbolStar},
{name: 'Triangle', symbol: d3.symbolTriangle},
{name: 'Wye', symbol: d3.symbolWye}
];
// 生成数据
var data = d3.range(0, 200).map(function (d) {
return {
n: d,
x: Math.random() * width,
y: Math.random() * height,
item: symbols[d % 7]
}
});
// 定义颜色比例尺
var color = d3.scaleOrdinal()
.domain(symbols.map(function (s) {return s.name}))
.range(d3.schemeCategory10);
// 添加200个元素
svg.selectAll("symbol")
.data(data).enter()
.append("g")
.attr("transform", function (d) {return "translate(" + d.x + " " + d.y + " )"})
.attr("class", "symbol")
.append("path")
.attr("d", function (d) {return d3.symbol().type(d.item.symbol)()})
.attr("fill", function (d) { return color(d.item.name)});
// 创建画笔并监听事件
var brush = d3.brush()
.on("brush", brushed)
svg.append("g")
.attr("class", "brush")
.call(brush);
function brushed() {
var extent = d3.event.selection;
var x0 = extent[0][0],
y0 = extent[0][1],
x1 = extent[1][0],
y1 = extent[1][1];
data.forEach(function (d, i) {
if (x0 <= d.x && d.x <= x1 && y0 <= d.y && d.y <= y1) {
d3.selectAll(".symbol:nth-child(" + (i + 1) + ")")
.select("path")
.attr("stroke", "black")
.attr("stroke-width", "2");
} else {
d3.selectAll(".symbol:nth-child(" + (i + 1) + ")")
.select("path")
.attr("stroke", null)
.attr("stroke-width", null);
}
});
}
在上述代码中,首先创建了200个随机放置的符号并为其着色。然后创建了一个
d3.brush
并将其附加到SVG元素上,同时定义了一个回调函数
brushed
,当画笔移动、调整大小或关闭时会调用该函数。在
brushed
函数中,获取选择区域的坐标,遍历数据元素,判断哪些元素落在选择区域内,若在则为其添加边框高亮显示,否则移除边框。
需要注意的是,
d3.zoom
和
d3.brush
都需要响应鼠标点击和拖动事件,若要同时使用这两个功能,可参考示例https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172 中的方法。
3. D3与ES6的结合使用
3.1 开发环境搭建
在开发ES6时,要考虑目标浏览器的支持情况。大多数现代浏览器对ES6标准有很好的支持,但模块支持方面几乎没有。因此,需要将ES6代码转换为ES5代码,这一过程通常称为转译。可以使用webpack来实现代码的转译和依赖打包。
在目录
<DVD3>/chapter - 09/es6/
中可以找到支持代码转译和打包的所有配置。按照以下步骤操作:
1. 确保安装了正确的依赖:
$ npm install
- 启动webpack服务器:
$ npm run start
执行上述命令后,会看到类似以下的输出:
> d3.js - es6@1.0.0 start /Users/jos/dev/git/dataviz - d3js/src/chapter - 09/es6
> webpack - dev - server --progress
10% building modules 1/1 modules 0 active
Project is running at http://localhost:8080/
webpack output is served from /
Version: webpack 2.2.1
Time: 4043ms
Asset Size Chunks Chunk Names
main.bundle.js 1.04 MB 0 [emitted] [big] main
main.bundle.js.map 2.08 MB 0 [emitted] main
index.html 185 bytes [emitted]
app.css 8 bytes [emitted]
chunk {0} main.bundle.js, main.bundle.js.map (main) 1.01 MB [entry]
[rendered]
[21] ./~/d3 - scale/src/linear.js 1.59 kB {0} [built]
[104] ./app/js/index.js 2.47 kB {0} [built]
[105] (webpack) - dev - server/client?http://localhost:8080 5.03 kB {0}
[built]
[149] ./~/d3 - scale/index.js 5.01 kB {0} [built]
[167] ./~/d3 - selection/index.js 3.17 kB {0} [built]
[169] ./~/d3 - selection/src/local.js 660 bytes {0} [built]
[200] ./~/d3 - selection/src/touch.js 734 bytes {0} [built]
[201] ./~/d3 - selection/src/touches.js 650 bytes {0} [built]
[217] ./~/d3/build/d3.js 488 kB {0} [built]
[256] ./~/strip - ansi/index.js 161 bytes {0} [built]
[258] ./~/url/url.js 23.1 kB {0} [built]
[260] (webpack) - dev - server/client/overlay.js 3.6 kB {0} [built]
[261] (webpack) - dev - server/client/socket.js 872 bytes {0} [built]
[262] (webpack)/hot/emitter.js 89 bytes {0} [built]
[263] multi (webpack) - dev - server/client?http://localhost:8080
./app/js/index.js 40 bytes {0} [built]
+ 249 hidden modules
webpack: Compiled successfully.
这样就完成了代码的转译,并启动了一个简单的Web服务器,可以直接在浏览器中查看ES6应用。
3.2 导入模块
D3的发行版被分成多个模块。在没有ES6的情况下,需要在HTML文件顶部添加各个库作为依赖,例如:
<script src="../libs/d3 - selection.v1.js"></script>
<script src="../libs/d3 - scale - chromatic.v1.min.js"></script>
<script src="../libs/d3 - geo - projection.v1.min.js"></script>
然后在JavaScript文件中使用
d3
命名空间调用函数:
d3.select('.chart');
d3.geoGringortenQuincuncial();
使用ES6和模块时,可以在JavaScript文件中使用
import
语句导入所需模块,例如:
import {scaleOrdinal} from "d3 - scale";
import {select} from "d3 - selection";
这样就可以直接使用这些函数,而无需使用
d3.
前缀。代码示例如下:
// 标准方式
var svg = d3.select(".chart")
var color = d3.scaleOrdinal()...
// 使用ES6
var svg = select(".chart")
var color = scaleOrdinal()...
若要导入完整的D3库,可使用以下方式:
import * as d3 from "d3";
导入D3(或其部分模块)的另一个优点是,编辑器能更方便地提供正确的代码补全功能。例如,使用
import * as d3scale from "d3 - scale"
导入
d3 - scale
模块后,就能轻松查看和访问所有不同的比例尺。
更多关于ES6模块支持的信息可参考https://github.com/lukehoban/es6features#modules 。
3.3 箭头函数和方法简写
ES6中可以使用箭头函数定义函数,这是一个非常实用的特性。在传统的JavaScript和D3中,定义函数的方式如下:
symbolGroups.append("path")
.attr("fill", function (d) {
return color(d.name)
})
.attr("d", function (d) {
return d3.symbol()
.size(2400)
.type(d.symbol)();
});
使用箭头函数可以将其改写为:
symbolGroups.append("path")
.attr("fill", d => color(d.name))
.attr("d", d => {
return d3.symbol()
.size(2400)
.type(d.symbol)()
});
对于
fill
属性,由于只有一个表达式,可直接返回结果;对于
d
属性,使用花括号包裹函数体时需要添加
return
语句。这种方式能使代码更加简洁,尤其在处理D3的各种回调函数时,能让代码更加易读。
需要注意的是,在箭头函数中使用
this
时,它会使用周围代码的作用域。在D3中,有时会使用
this
传递信息,例如访问正在处理的当前节点。以下代码展示了这种差异:
symbolGroups.append("path")
.attr("fill", d => color(d.name))
.attr("attr1", d => { console.log(this); return "attr1"})
.attr("attr2", function(d) { console.log(this); return "attr1" })
.attr("d", d => {
return d3.symbol()
.size(2400)
.type(d.symbol)()
});
运行该代码会发现,箭头函数中的
this
是未定义的,而普通函数中的
this
是有定义的。在这种情况下,仍需要使用普通函数来访问传递的
this
上下文。
ES6还为在对象上定义函数提供了简写方式,例如:
var obj = {
doSomething(name = 'd3') {
},
doSomethingElse(count = 100) {
}
}
这里定义了两个函数
doSomething
和
doSomethingElse
,并展示了ES6允许为函数定义默认参数的特性。
3.4 块级作用域绑定(let + const)
在JavaScript中,使用
var
声明变量是函数作用域的,这可能导致一些难以发现的错误。例如以下代码:
function varTest() {
for (i = 10 ; i < 20 ; i++) {
console.log(i)
}
if (i < 10) { var i; }
}
由于
var i
声明会被提升到函数顶部,这段代码是合法的,但这种行为不符合传统编程习惯。
ES6引入了
let
和
const
关键字。
let
确保变量仅在定义它的代码块中使用,
const
也是块级作用域,用于定义不可重新声明的值。示例代码如下:
const margin = {top: 20, bottom: 20, right: 20, left: 30};
const width = 600 - margin.left - margin.right;
const height = 200 - margin.top - margin.bottom;
let svg = select(".chart")
虽然这样的代码在可读性上没有明显提升,但能避免许多难以捕获的错误。
3.5 字符串插值
在D3中,经常使用
translate
属性将
g
元素移动到特定位置,传统代码如下:
let svg = select(".chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
使用箭头函数时,代码如下:
var symbolGroups = svg.selectAll(".symbol").data(symbols)
.enter()
.append("g")
.attr("class", "symbol")
.attr("transform", d => "translate(" + xBand(d.name) + " 40)")
使用ES6的字符串插值功能,可以避免使用
+
来拼接字符串和变量。代码改写为:
...
.attr("transform", `translate(${margin.left}, ${margin.top})`);
...
.attr("transform", d => `translate(${xBand(d.name)} 40)`)
通过在字符串前后添加单反引号,并使用
${variable}
引用变量或表达式,使代码更加简洁易读。
综上所述,D3与ES6的结合使用能为数据可视化开发带来诸多便利,提高开发效率和代码质量。同时,使用
d3.brush
和从Inkscape导入SVG等功能也为数据可视化的实现提供了更多的可能性。
4. D3与TypeScript的结合
TypeScript 为 JavaScript 添加了类型系统,使得开发更加容易,并能避免许多常见的运行时错误。在使用 D3 和 TypeScript 时,同样有一些要点需要关注。
首先,TypeScript 需要进行编译,将其转换为普通的 JavaScript 代码才能在浏览器中运行。可以使用
tsc
(TypeScript 编译器)来完成这个任务。
在项目中使用 D3 和 TypeScript 时,需要安装 D3 的类型定义文件,这样 TypeScript 编译器才能理解 D3 的 API。可以通过以下命令安装:
npm install @types/d3
以下是一个简单的使用 D3 和 TypeScript 的示例:
import * as d3 from 'd3';
const width = 600;
const height = 400;
const svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
const data = [10, 20, 30, 40, 50];
const scale = d3.scaleLinear()
.domain([0, d3.max(data) as number])
.range([0, width]);
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', 0)
.attr('y', (d, i) => i * 25)
.attr('width', (d) => scale(d))
.attr('height', 20)
.attr('fill', 'blue');
在上述代码中,我们使用 TypeScript 编写了一个简单的柱状图。通过导入 D3 并使用类型定义,TypeScript 编译器可以检查代码中的类型错误,提高代码的可靠性。
5. 外部 D3 图表库
有许多基于 D3 的外部图表库,下面介绍几个常见的库。
| 库名 | 特点 |
|---|---|
| nvd3 | 提供了一系列预定义的可交互图表,如折线图、柱状图、饼图等,易于使用,适合快速创建常见类型的图表。 |
| C3 | 基于 D3 的简单图表库,具有简洁的 API,支持多种图表类型,并且可以方便地与数据绑定。 |
| Dimple.js | 专注于简化 D3 的使用,通过简单的配置即可创建复杂的图表,适合初学者。 |
| MetricsGraphics.js | 专门用于创建时间序列和统计图表,具有良好的性能和交互性。 |
这些库都建立在 D3 的基础之上,为开发者提供了更高级的抽象和更简单的 API,能够帮助开发者更快地创建出高质量的可视化图表。
6. D3 命令行工具
D3 提供了一些命令行工具,可以直接从命令行访问部分 API。下面介绍如何使用这些工具创建一个地图。
首先,确保已经安装了 D3 命令行工具:
npm install -g d3-command-line
创建地图的步骤如下:
1. 准备地理数据,通常是 GeoJSON 格式。可以从公开数据源获取,例如 Natural Earth 提供的地理数据。
2. 使用 D3 命令行工具将 GeoJSON 数据转换为 SVG 地图。以下是一个简单的示例命令:
d3-geo-projection -w 800 -h 600 < input.geojson > output.svg
这个命令将输入的 GeoJSON 文件
input.geojson
转换为一个宽度为 800 像素、高度为 600 像素的 SVG 地图,并保存为
output.svg
。
-
可以进一步使用 D3 的命令行工具对 SVG 地图进行样式调整和交互添加。例如,使用
d3-selection来选择 SVG 元素并添加样式:
d3-selection -f output.svg -s 'path { fill: lightblue; stroke: black; }' > styled-output.svg
这个命令将选择
output.svg
中的所有
path
元素,并为它们添加填充颜色和边框颜色,最终保存为
styled-output.svg
。
通过使用 D3 命令行工具,可以在不编写大量 JavaScript 代码的情况下,快速创建和定制地图等可视化图表。
7. 总结
D3 是一个强大的数据可视化库,与 ES6、TypeScript 的结合使用以及外部图表库和命令行工具的应用,为数据可视化开发提供了更多的选择和便利。
- 与 ES6 结合 :通过模块导入、箭头函数、块级作用域绑定和字符串插值等特性,使代码更加简洁、易读和易于维护。
- 与 TypeScript 结合 :利用类型系统提高代码的可靠性,减少运行时错误。
- 外部图表库 :提供了更高级的抽象和简单的 API,适合快速创建常见类型的图表。
- 命令行工具 :可以直接从命令行访问部分 D3 API,快速创建和定制可视化图表。
在实际开发中,可以根据项目的需求和特点,选择合适的技术和工具,以实现高质量的数据可视化效果。
graph LR
A[D3] --> B[ES6]
A --> C[TypeScript]
A --> D[外部图表库]
A --> E[命令行工具]
B --> F[模块导入]
B --> G[箭头函数]
B --> H[块级作用域]
B --> I[字符串插值]
C --> J[类型检查]
D --> K[nvd3]
D --> L[C3]
D --> M[Dimple.js]
D --> N[MetricsGraphics.js]
E --> O[创建地图]
这个流程图展示了 D3 与 ES6、TypeScript、外部图表库和命令行工具之间的关系,以及它们各自的一些关键特性和应用场景。通过综合运用这些技术和工具,可以充分发挥 D3 的优势,实现丰富多样的数据可视化效果。
超级会员免费看
18

被折叠的 条评论
为什么被折叠?



