首页 > 解决方案 > D3 可缩放的 Sunburst 和 React 通过道具更新

问题描述

我正在尝试让使用 d3 sunburst 图表的反应组件工作。我面临的问题是我需要一种方法来更新 sunburst 组件的缩放级别,作为来自外部组件的触发器。我通过道具将要缩放的节点发送到 sunburst 组件,并且每次有不同组件的外部输入时都会改变。

这是我到目前为止的伪代码,但是每个道具都会发生变化。

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

const SunburstSmooth = (props) => {
  const prevProps = usePrevious(props);

  useEffect(() => {
    if (!isEqual(prevProps, props)) {
      if (props.navigateTo) {
        zoomToSunburst(props.navigateTo);
      } else {
        if (props.data) {
          renderSunburstSmooth();
          update();
        }

      }
    }
  }, [props])

  // Global Variables
  let root, node;
  let gWidth, gHeight, radius, svg;
  let color;
  let x, y, arc, partition;

  const svgRef = useRef();

const zoomToSunburst = (nodeToRender) => {
    const gWidth = props.width;
    const gHeight = props.height;
    const radius = (Math.min(gWidth, gHeight) / 2) - 10
    const svg = d3.select(svgRef.current)
    const x = d3.scaleLinear().range([0, 2 * Math.PI])
    const y = d3.scaleSqrt().range([0, radius])
    const partition = d3.partition()
    const arc = d3.arc()
      // ....

    root = d3.hierarchy(nodeToRender);
    node = nodeToRender;
    svg.selectAll("path")
      .transition("update")
      .duration(1000)
      .attrTween("d", (d, i) =>
        arcTweenPath(d, i, radius, x, y, arc));
}


const update = () => {
    root.sum(d => d[props.value]);

    let gSlices = svg.selectAll("g")
      .data(partition(root).descendants())
      .enter()
      .append("g");
    gSlices.exit().remove();
    gSlices.append("path")
      .style('fill', (d) => {
        let hue;
        const current = d;

        if (current.depth === 0) {
          return '#c6bebe';
        }

        return color((current.children ? current.x0 : current.parent.x0));
      })     
      .attr('stroke', '#fff')
      .attr('stroke-width', '1')
 

    svg.selectAll("path")
      .transition("update")
      .duration(750)
      .attrTween("d", (d, i) =>
        arcTweenPath(d, i, radius, x, y, arc));
  }

// Sets up the initial sunburst
const renderSunburstSmooth = () => {
   // Structure
    gWidth = props.width;
    gHeight = props.height;
    radius = (Math.min(gWidth, gHeight) / 2) - 10;

    // Size our <svg> element, add a <g> element, and move translate 0,0 to the center of the element.
    svg = d3.select(svgRef.current)
      .append("g")
      .attr("id", "bigG")
      .attr("transform", `translate(${gWidth / 2},${gHeight / 2})`);

    x = d3.scaleLinear().range([0, 2 * Math.PI]);
    y = d3.scaleSqrt().range([0, radius]);

    // Calculate the d path for each slice.
    arc = d3.arc()
       // .....   

    // Create our sunburst data structure
    partition = d3.partition();

    // Root Data
    root = d3.hierarchy(props.data);
    node = props.navigateTo || root;
}


return (
    <div id={props.keyId}>
      <svg ref={svgRef}/>
    </div>
  );
}

很多代码库代码来自这里: http ://bl.ocks.org/metmajer/5480307

现在每次更新 prop 时,都会重新渲染整个组件。当 props.navigateTo 在外部更改时,我如何使其仅更新现有的 svg 容器。

标签: javascriptreactjsd3.jsreact-hooksreact-d3

解决方案


现在,组件的渲染取决于道具中任何元素的变化。为了使其仅依赖于 prop 的更改navigateTo,您需要两件事:1-navigateTo需要是使用const [navigateTo, setNavigateTo] = UseState("");父组件中类似的东西创建的状态,并作为 prop 传递下来。(我只是把它放在这里以确保你这样做)所以像:

const parent = (props) => {
    const [navigateTo, setNavigateTo] = UseState("");
    <<any other code>>
    return <SunburstSmooth
            navigateTo={navigateTo}
            data={data}
           >
}

2-为了使您的代码更清晰,您可以分解道具以仅根据其中的某个元素进行渲染:

const SunburstSmooth = (props) => {
  const {navigateTo, data, ...rest} = props;

  useEffect(() => {
    if (data) {
       renderSunburstSmooth();
       update();
    }
  }, [navigateTo])
  <<rest of your code>>

这确保了组件仅在更改时重新渲染navigateTo,而不是在更改数据或任何其他道具时重新渲染。如果您还希望它在每次数据更改时重新渲染,例如,您可以将其添加到 UseEffect 钩子末尾的数组中

useEffect(() => {...}, [navigateTo, data])

关于仅重新渲染SVG元素,任何 useEffect 钩子都会导致您返回的所有内容都被重新渲染,因此SVG必须是您的组件返回的唯一内容才能重新渲染它。div我不明白为什么你会介意重新渲染封闭


推荐阅读