React+Dva环境中使用d3绘图
本文以D3中的Example为例,将其导入本项目中,项目基于React和Dva,作者假设读者已经能够通过Dva异步获取D3所需的数据。
文件代码结构如下:
// index.jsx
import { connect } from 'dva';
import React, { Component } from 'react';
import * as d3 from "d3";
class index extends Component {
// 构造函数
constructor(props) {
super(props)
}
// 使用D3js绘制
print = (data) => {}
// 处理下拉框事件响应
handleSelect({ target }) {}
// 绘制函数
render() {}
}
// ---------------------------------------------------------------
// 下面通过Dva的connect方法将视图与数据绑定,因此在index中可以通过props来获取
// mapStateToProps中的数据,即 const { alphabet } = this.props;
const mapStateToProps = state => {
return {
alphabet: state.demoPage.alphabet
};
};
const mapStateToDispatch = dispatch => ({});
export default connect(mapStateToProps, mapStateToDispatch)(index);
其中print
方法是绘图的核心部分,参考D3例子,直接将代码Copy进来即可。需要注意的是官网例子中chart = {}
需要改写成chart = () => {}
方法,变量名前加上const
或者let
声明。print
方法如下:
print = (data) => {
if (!data) {
// 数据为空时,需要返回空div
return document.createElement('div')
}
// 在Dva中的effects里通过异步请求已经拿到了数据,因此这里只需要将数组转为json即可
const _data = data.map(line => ({ name: line[0], value: line[1] }))
const margin = ({ top: 20, right: 0, bottom: 30, left: 40 }),
height = 150,
width = 300
// 注意假设变量声明标识符
const yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
const xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
const y = d3.scaleLinear()
.domain([0, d3.max(_data, d => d.value)]).nice()
.range([height - margin.bottom, margin.top])
const x = d3.scaleBand()
.domain(_data.map(d => d.name))
.range([margin.left, width - margin.right])
.padding(0.1)
const chart = () => {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
const bar = svg.append("g")
.attr("fill", "steelblue")
.selectAll("rect")
.data(_data)
.join("rect")
.style("mix-blend-mode", "multiply")
.attr("x", d => x(d.name))
.attr("y", d => y(d.value))
.attr("height", d => y(0) - y(d.value))
.attr("width", x.bandwidth());
const gx = svg.append("g")
.call(xAxis);
const gy = svg.append("g")
.call(yAxis);
return Object.assign(svg.node(), {
update(order) {
x.domain(_data.sort(order).map(d => d.name));
const t = svg.transition()
.duration(750);
bar.data(_data, d => d.name)
.order()
.transition(t)
.delay((d, i) => i * 20)
.attr("x", d => x(d.name));
gx.transition(t)
.call(xAxis)
.selectAll(".tick")
.delay((d, i) => i * 20);
}
});
}
return chart()
}
可以看出代码除了少部分改动外,与官网案例区别不大。
接下来在构造函数中添加下拉框选项及order
函数
constructor(props) {
super(props)
this.options = [
{ label: "Alphabetical", value: (a, b) => a.name.localeCompare(b.name) },
{ label: "Frequency, ascending", value: (a, b) => a.value - b.value },
{ label: "Frequency, descending", value: (a, b) => b.value - a.value }
]
}
handleSelect
方法设置选择响应事件,即调用print
方法返回的对象中的updata
方法,参数为排序算法order
。
handleSelect({ target }) {
this.chartEl.update(
this.options.filter(op => target.value === op.label)[0].value)
}
最后实现render
方法,注意ref={(ref) => { ref && ref.appendChild(chartEl) }}
将chartEl
作为子元素插入到了div
中,而不是使用JSX
插入。
render() {
const { alphabet } = this.props;
const chartEl = this.chartEl = this.print(alphabet)
return (
<div>
<select onChange={this.handleSelect.bind(this)}>
{
this.options.map(op => (<option key={op.label} value={op.label}>{op.label}</option>))
}
</select>
<div ref={(ref) => { ref && ref.appendChild(chartEl) }}></div>
</div>
)
}