有没有正确的方法将d3.js图形集成到Facebook React应用程序中?

时间:2014-02-20 09:32:03

标签: javascript facebook d3.js reactjs

是的,我知道有反应艺术,但是it seems to be broken

问题是d3.js是关于改变浏览器DOM的,并且直接使用它,比如说在componentDidMount方法中,将无法正常工作,因为对浏览器DOM的所有更改都不会反映在React的虚拟DOM中。有什么想法吗?

4 个答案:

答案 0 :(得分:59)

一种策略可能是构建一个永远不会让React更新的黑盒组件。组件生命周期方法shouldComponentUpdate()旨在允许组件自行确定是否需要重新注册。如果始终从此方法返回false,React将永远不会潜入子元素(即,除非您调用forceUpdate()),因此这将充当一种防火墙反对React的深度更新系统。

使用对render()的第一次调用来生成图表的容器,然后在componentDidMount()方法中使用D3绘制图表本身。然后,它只是更新您的图表以响应React组件的更新。虽然您可能不会假设shouldComponentUpdate()中执行类似的操作,但我认为没有理由不能继续从那里调用D3更新(请参阅React组件的代码) _performUpdateIfNecessary())。

所以你的组件看起来像这样:

React.createClass({
    render: function() {
        return <svg></svg>;
    },
    componentDidMount: function() {
        d3.select(this.getDOMNode())
            .call(chart(this.props));
    },
    shouldComponentUpdate: function(props) {
        d3.select(this.getDOMNode())
            .call(chart(props));
        return false;
    }
});

请注意,您需要使用componentDidMount()方法(针对第一个渲染)以及shouldComponentUpdate()(用于后续更新)调用图表。另请注意,您需要一种方法将组件属性或状态传递给图表,并且它们是特定于上下文的:在第一次渲染中,它们已经在this.propsthis.state上设置,但是在以后的更新中,尚未在组件上设置新属性,因此您需要使用函数参数。

请参阅jsfiddle here

答案 1 :(得分:6)

React也可以直接渲染SVG元素。以下是一个示例:Ways of Integrating React.js and D3

答案 2 :(得分:2)

您可以使用D3作为实用程序 - 也就是说,DON使用D3来更改DOM。相反,使用D3作为它的比例和轴的东西,但是将这些D3函数的输出插入到html / svg中。

这是我项目的一个例子。

 scale = d3.time.scale()
   .range([ 0, 100 ])
   .clamp(true)

...通过道具传递给组件......

React.DOM.rect({
          x: "#{props.scale(start)}%"
          width: "#{Math.abs( props.scale(end) - props.scale(start)) }%"
         })

其中React将数据绑定到元素,而不是D3的选择。

答案 3 :(得分:0)

我正在使用 useRef -hook 来保存对 D3 可视化的引用,以便在页面中有其他“东西”时我可以控制何时只需要更新图表。

在 D3 中,我使用了可重复使用的图表模式,例如:https://www.toptal.com/d3-js/towards-reusable-d3-js-charts

import React, {useEffect, useRef, useState} from 'react';

import { select } from "d3";

const TestArea = () => {

    const [data, setData] = useState(['red']);
    const refElement = useRef(null);
    const visFunction = useRef(null);

    const initVis = () => {
        visFunction.current = d3Chart().data(data)
        select(refElement.current).call(visFunction.current)         
    }

    const updateVis = () => {
        visFunction.current.data(data);      
    }

    useEffect(() => {

        if(data && data.length){

            if(visFunction.current === null)
                initVis()
            else
                updateVis()

        }
        
    }, [data])

    return (
        <>
            <div ref={refElement} className="d3container"></div>
            <button onClick={() => setData(['blue'])}>Update</button>
        </>
    );
};


const d3Chart = () => {

    let svg;
    let gElem;
    let circles;

    let data = []
    let width = 200;
    let height = 200;

    let updateData;

    function chart(selection){

        selection.each(function(){

            svg = select(this)
                .append('svg')

            gElem = svg
                .append('g')
                .attr('transform', ` translate(${width/2},${height/2})`)

            circles = gElem
                .selectAll("circle")
                .data(data)
                .enter()
                .append("circle")
                    .style("stroke", "none")
                    .style("fill", d => d)
                    .attr("r", 10)
                    .attr("cx", 0)
                    .attr("cy", 0)

            updateData = function() {

                circles = gElem
                    .selectAll("circle")
                    .data(data)
                        .style("fill", d => d)

            }

        })
    }

    chart.data = function(val){

        if(!arguments.length) return data;

        data = val;

        if(typeof updateData === 'function')
            updateData();
   
        return chart

    }

    return chart

}


export default TestArea;