I'm trying to get a react component working which uses the d3 sunburst chart. The problem I'm facing is I need a way to update the zoom level of the sunburst component, as a trigger from an external component. I'm sending the node to be zoomed to via the props to the sunburst component, and that changes each time there is an external input for a different component.
Here is the pseudocode I have so far but, each the the props changes.
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>
);
}
A lot of the code base code is from here: http://bl.ocks.org/metmajer/5480307
Right now each time the prop is updated, the entire component is re-rendered. How do i make it so that it only updates the existing svg container, when the props.navigateTo is changed externally.
Right now, the rendering of your component depends on a change in any element in your props. In order to make it only depend on the change of the prop
navigateTo, you would need two things: 1-navigateTowould need to be a state created with something likeconst [navigateTo, setNavigateTo] = UseState("");in the parent component, and passed down as a prop. (I'm just putting this here to make sure you are doing this) So something like:2- To make your code clearer, you can decompose the props to make the rendering depending on only a certain element of it:
This ensures that the component is re-rendered only on changes of
navigateTo, and not when something like data or any other prop is changed. In case you also want it to re-render every time data is changed, for example, you can just add it to the array at the end of the UseEffect hookRegarding the re-rendering of only the
SVGelement, any useEffect hook will cause everything in your return to be re-rendered, so theSVGwould have to be the only thing your component returns in order to only re-render that. I can't see why you would mind re-rendering the enclosingdivthough