d3.js 与 react.js
本文简单总结一下 d3(v4) 在 react 中如何使用,如果你还不知道 d3.js 是什么,请移步 d3.js。
d3.js 官方样例中的用法都是类似
d3.select("body")
.selectAll("p")
.data([4, 8, 15, 16, 23, 42])
.enter().append("p")
.text(function(d) { return "I’m number " + d + "!"; });
确实很简明,但是在 react 中却没法下手,所以在 react 中,只需将 d3.js 视为提供图形算法的库即可,不需要掌握如何使用 d3.js 操作 dom。
使用 svg 画一个 bar 图
我们先使用 svg 来画一个最简单的没有坐标轴的 bar 图,理解一下 d3 与 react 的分工。
首先假设我们的 bar 图大小为 1280(px) * 800(px),数据是
const data = [
{ city: '北京', amount: 1000, },
{ city: '上海', amount: 803, },
{ city: '广州', amount: 440, },
{ city: '深圳', amount: 780, },
];
然后我们需要理解 bar 图是如何构成(画)的,bar 图有两个坐标轴:
- 横轴为分组,对应 city ,决定每根柱子的 x 位置
- 竖轴为数值,对应 amount,决定每根柱子的高度即 y 的位置
d3 处理图形的算法和坐标计算很方便,使用 d3.scaleBand 将 city 与图的宽度(1280px) 做出映射关系:
const xAxis = d3.scaleBand()
.range([0, 1280])
.domain(['北京', '上海', '广州', '深圳'])
就可以用 xAxis('上海')
得到上海
这根柱子的 x 位置。每根柱子的宽度使用 xAxis.bandwidth()
获得。
使用 d3.scaleLinear 将 amount 与图的高度(800px) 做出映射关系:
const yAxis = d3.scaleLinear()
.range([800, 0])
.domain([0, 1000])
就可以用 yAxis(803)
得到上海
这根柱子的高度 y 位置。请注意 range([800, 0])
,之所以是反向的,是因为 svg 的纵坐标系是反向的,而且 canvas 的纵坐标系也是反向的。
接下来创建我们的组件
import * as d3 from 'd3';
import React, { PureComponent } from 'react';
const width = 1280;
const height = 800;
const data = [
{ city: '北京', amount: 1000, },
{ city: '上海', amount: 803, },
{ city: '广州', amount: 440, },
{ city: '深圳', amount: 780, },
];
const xAxis = d3.scaleBand()
.range([0, 1280])
.domain(['北京', '上海', '广州', '深圳']);
const yAxis = d3.scaleLinear()
.range([800, 0])
.domain([0, 1000]);
const barWidth = xAxis.bandwidth();
class Bar extends PureComponent {
render() {
return <svg width={width} height={height}>
{data.map(({ city, amount }, index) => {
const x = xAxis(city);
const y = yAxis(amount);
const barHeight = height - y;
return <rect
key={index}
x={x}
y={y}
width={barWidth}
height={barHeight}
fill={'#bada55'} />;
})}
</svg>;
}
}
这样就完成了一个最简单的(正数) bar 图,需要注意的是其中计算 barHeight 的时候并没有判断 amount 为负数的情况,且没有处理各种边界错误。
性能
上边的例子中我们是直接 map
数据,创建了 n 个 rect
元素,当数据量上千后,性能会下降,不如改为 canvas
画法,使用 canvas
画的逻辑也是一样的,d3.js 只负责图形算法和坐标计算。
不同的是 canvas
无法像 svg
一样直接在 render
函数中画,需要在 componentDidMount 之后执行画图的逻辑。
class Bar extends PureComponent {
render() {
return <canvas
width={width}
height={height}
ref={r => this.canvas = r} />;
}
componentDidMount() {
const ctx = this.canvas.getContext('2d');
ctx.fillStyle = '#bada55';
data.map(({ city, amount }, index) => {
const x = xAxis(city);
const y = yAxis(amount);
const barHeight = height - y;
ctx.beginPath();
ctx.fillRect(x, y, barWidth, barHeight);
ctx.closePath();
});
}
}
总结
通过上边两个例子,相信你已经可以使用 svg
或 canvas
通过 d3.js
在 react
中画出常用的基本可视化图形了。
在项目中,无论是你是要用 d3 自己画图,还是用 echarts/g2/highcharts,重要的是理解图形是如何组成的,如何画出来的,方法有很多。