React.js 中使用 D3.js 动态更新数据
这两天尝试下 React.js,将上篇文章的 loadleveler 小应用重写一遍。
最近几天有点儿迷茫,不知道前进方向。去研究 js 库不是个好现象,应该去思考业务相关的事情,而不是一个又一个地看新东西,没了解多少后就抛在一边。
回归正题,需要重写的应用使用 D3 绘制条形图,并动态更新。React 中每个组件都有一个状态 state,当 state 改变时,就会重新绘制组件。而我希望使用 D3 的过渡动画来体现数据变化,不需要重新绘制组件。在 StackOverflow 上找到我需要的方法,初学 React 的我,感觉到 React 的强大。
创建 React 组件
var LoadlevelerTypeChartBoard = React.createClass({
render: function(){
return (
<svg></svg>
);
}
});
React 在绘制组件时调用 render 函数,这里我只需要一个 svg 标签,剩下内容由 d3 代码生成。
设计动态数据
设计 React 组件最好的方式是先创建一个静态组件,再添加动态数据。我改写应用时不了解这点,直接使用 state 保存私有数据,代码不够美观,以后花时间再改改看吧。
var LoadlevelerTypeChartBoard = React.createClass({
getInitialState: function() {
var component = this;
var llq_type_chart_svg_attr = {
height: 180,
width: 550
};
var llq_type_chart_label_width = 120;
var margin = 10;
var llq_type_chart_attr = {
max_width: llq_type_chart_svg_attr.width - llq_type_chart_label_width - margin,
bar_height: 20,
bar_step: 30
};
return {
llq_info: [
{name: 'total', value: 0},
{name: 'waiting', value: 0},
{name: 'pending', value: 0},
{name: 'running', value: 0},
{name: 'held', value: 0},
{name: 'preempted', value: 0}
],
llq_type_chart_attr: llq_type_chart_attr,
llq_type_chart_label_width: llq_type_chart_label_width,
margin: margin,
llq_type_chart_svg_attr: llq_type_chart_svg_attr
}
}
getInitialState
函数设置初始状态数据,上述代码中只有 llq_info 变量会变化,其它变量不会变化,应该保存在类似 props 的变量中,但我没找到设置 props 的方法,只好保存在 state 中。
绘制初始组件
可以在 render 中绘制初始组件,我则在 componentDidMount
中绘制组件,该函数在 render 结束后调用一次。
var LoadlevelerTypeChartBoard = React.createClass({
//...
componentDidMount: function(){
var component = this;
var llq_type_chart_svg = d3.select(this.getDOMNode())
.attr('id', 'llq_type_chart_2')
.attr('height', this.state.llq_type_chart_svg_attr.height)
.attr('width', this.state.llq_type_chart_svg_attr.width);
var llq_type_chart_bar_group = llq_type_chart_svg;
var llq_type_chart_label_group = llq_type_chart_svg;
var bar_chart_label_data = llq_type_chart_label_group.selectAll('.llq-type-label').data(this.state.llq_info);
var bar_chart_label_enter = bar_chart_label_data
.enter()
.append('text')
.attr('x', this.state.llq_type_chart_label_width - this.state.margin)
.attr('y', function(d,i){
return i*component.state.llq_type_chart_attr.bar_step + component.state.llq_type_chart_attr.bar_height;
})
.attr('text-anchor', 'end')
.style('font-size', this.state.llq_type_chart_attr.bar_height)
.text(function(d){ return d.name; });
//...
},
// ...
});
绘制初始svg图。但这样的图是不会变化的,需要在数据变化时修改组件的 state 值。我使用 socket.io 接受变化的数据,在 componentDidMount 函数中添加 socket 事件响应。
var LoadlevelerTypeChartBoard = React.createClass({
//...
componentDidMount: function(){
//...
socket.on('send_llq_info', function(msg){
var total = parseInt(msg.in_queue);
var waiting = parseInt(msg.waiting);
var held = parseInt(msg.held);
var running = parseInt(msg.running);
var pending = parseInt(msg.pending);
var preempted = parseInt(msg.preempted);
component.setState({
llq_info: [
{name: 'total', value: total},
{name: 'waiting', value: waiting},
{name: 'pending', value: pending},
{name: 'running', value: running},
{name: 'held', value: held},
{name: 'preempted', value: preempted}
]
});
});
},
//...
});
使用 setState 修改组件的状态。
控制组件重绘
默认情况下,state 改变会导致组件重绘。React 提供 shouldComponentUpdate 属性控制组件是否重绘,在不需要重绘的情况下返回 false。我使用 D3 更新 svg,所以在 shouldComponentUpdate 函数中使用 D3 更新图形并返回 false。
var LoadlevelerTypeChartBoard = React.createClass({
//...
shouldComponentUpdate: function(nextProps, nextState){
var component = this;
var llq_type_chart_svg = d3.select(this.getDOMNode());
var llq_type_chart_bar_group = llq_type_chart_svg;
var llq_type_chart_label_group = llq_type_chart_svg;
var max_number = d3.max(nextState.llq_info, function(d){ return d.value; });
var x_scale = d3.scale.linear().domain([0, max_number]).range([0, this.state.llq_type_chart_attr.max_width]);
var bar_chart_data = llq_type_chart_bar_group.selectAll('.llq-type-bar').data(nextState.llq_info);
var bar_chart_data_enter = bar_chart_data
.enter()
.append('rect')
.attr('class', 'llq-type-bar')
.attr('height', this.state.llq_type_chart_attr.bar_height)
.attr('width', function(d){
return x_scale(d.value);
})
.attr('x', this.state.llq_type_chart_label_width)
.attr('y', function(d,i){
return i*component.state.llq_type_chart_attr.bar_step;
})
.attr('fill', '#ddd');
var bar_chart_data_modify = bar_chart_data
.transition()
.duration(800)
.attr('width', function(d){
return x_scale(d.value);
});
return false;
},
// ...
});
这样就可以实现在 React 中使用 D3 更新数据。