内容简介:在进行前端可视化图定制开发的时候,我们往往会使用d3.js来进行开发,其自包含了针对数据集合的处理以及操作节点集合的方式。下面我们将介绍在配合React使用时,借用React的思路,充分发挥React的组件化,声明式特性,来优化D3的开发过程。d3提供了d3-selection,来使用数据对节点进行驱动。我们可以根据数据拿到需要对节点进行的变更。往往我们会在componentDidMount中来进行节点操作,大概分为几个步骤:这种方式虽然完成了任务,但是我们依旧会感觉有不舒服的地方:
在进行前端可视化图定制开发的时候,我们往往会使用d3.js来进行开发,其自包含了针对数据集合的处理以及操作节点集合的方式。下面我们将介绍在配合React使用时,借用React的思路,充分发挥React的组件化,声明式特性,来优化D3的开发过程。
旧的方式
d3提供了d3-selection,来使用数据对节点进行驱动。我们可以根据数据拿到需要对节点进行的变更。往往我们会在componentDidMount中来进行节点操作,大概分为几个步骤:
- 根据数据和容器宽度,获取比例尺
- 根据data join的enter和exit选择集,来添加,删除或更新元素
export default class Graphextends React.Component{ componentDidMount () { const { data = [] } = this.props this.renderBar(data) } renderBar (data) { // 拿到比例尺 const scale = d3.scaleLinear() .domain(d3.extent(data)) .range([0, 180]) const wrap = d3.select('#qps-graph') wrap.selectAll(`g.bar`) .data(data) .enter() .append('g') .attr('class', 'bar') .append('rect') .attr('class', 'bar-rect') wrap.selectAll(`g.bar`) .data(data) .attr("transform", (d, i) => `translate(${i * 20},${180 - scale(d)})`) .select('rect') .style('height', d => scale(d)) wrap.selectAll(`g.bar`) .data(data) .exit() .remove() } render() { return <svgheight="180"width="1000"id="qps-graph"></svg> } }
这种方式虽然完成了任务,但是我们依旧会感觉有不舒服的地方:
- 我们很难对图形进行复用,图形稍微改动一点,就需要改动代码
- 在声明式代码中掺杂了很多命令式的操作过程,不直观,不利于维护
- 针对事件处理,会很难进行,我们往往需要使用d3创建新的元素,来控制它的内容、显示和隐藏
新的方式
我们会发现,我们根据data join的enter和exit选择集,来添加,删除或更新元素,这个步骤,其实React也可以进行,并且可以使用更加直观的声明式来书写,类似这样:
render(){ const scale = d3.scaleLinear() .domain(d3.extent(data)) .range([0, 180]) const h = d => scale(d) const y = (d, i) => `translate(${i * 20},${180 - scale(d)})` return <svgheight="180"width="1000"> {data.map((d, i) => { return ( <gkey={`bar-${i}`}transform={y(d,i)}> <rect height={h(d)} width={10} fill='#fc2e1c' /> </g> ); })} </svg> }
是不是很简单!同时,我们可以利用React的组件化特性,来对我们的每个单元模块进行封装,独立功能,方便各个组件的复用,来让我们的可视化代码更加直观,类似这样:
render(){ const scale = d3.scaleLinear() .domain(d3.extent(data)) .range([0, 180]) const h = d => scale(d) const y = (d, i) => `translate(${i * 20},${180 - scale(d)})` return <svgheight="180"width="1000"> {data.map((d, i) => { return ( <Groupkey={`bar-${i}`}left={i*20}top={180-scale(d)}> <Bar height={h(d)} width={10} fill='#fc2e1c' /> </Group> ); })} </svg> }
我们有幸看到已经有人做了这个工作: vx ,作者已经封装了很多的常用图形,我们的任务就是,对这些图形加上数据,进行拼接就可以了。同时,我们可以对其进行二次封装,以适应我们的项目。
经过一系列封装,我们写一个图形,这样操作即可:
// 定义getter const x = d => new Date(d.date); const y = d => +d.close; // 定义容器配置 const padding = { top: 20, left: 40, bottom: 20, right: 20 }; export default class LineGraph{ render() { return ( <Container width={1000} height={200} padding={padding} x={x} y={y}> {({ width, height }) => { // 根据数据定义x,y轴的scale const xScale = scaleTime({ rangeRound: [0, width], domain: d3.extent(data, x) }); const yScale = scaleLinear({ rangeRound: [height, 0], domain: d3.extent(data, y) }); return ( <Group> <Grid width={width} height={height} xScale={xScale} yScale={yScale} /> <AxisBottom top={height} scale={xScale} /> <AxisLeft scale={yScale} /> <AreaClosed data={data} xScale={xScale} yScale={yScale} x={x} y={y} /> </Group> ); }} </Container> ); } }
More
其实不止这些基本的图形可以用这种方式,当我们进行更加复杂图形的书写时,也可以使用这种方式。
例如我们最近做了一个复杂的树形结构,其每个节点都包含了很复杂的内容和交互,我们可以将其进行简化成以下方式:
render () { return <Container> <Defs/> <RootNode/> <TopTree/> <BottomTree/> </Container> }
其中Tree可以进行继续的封装,拼接:
render () { const nodeElements = nodes.map((node, index) => ( <Node direction={direction} node={node} methods={methods} /> )); const linkElements = links.map((link, index) => { return ( <Path active={active} item={item} direction={direction} s={link.source} d={link.target} activeItem={activeItem} /> ); }); return <Group> {nodeElements} {linkElements} </Group> }
然后我们对Node,和Path组件,进行详细的书写,这样非常的简洁明了,不是吗?
同时,当我们其他可视化组件需要这样的Path活Node或者Tree时,我们直接拿来用即可!
以上所述就是小编给大家介绍的《React+D3 声明式可视化展示》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。