I have replicated and simplified my problems on CodeSandbox which can be viewed here: https://codesandbox.io/p/sandbox/d3-org-chart-react-functional-org-chart-gvfz4l?file=%2Fsrc%2Fcomponents%2FdataToggle.js%3A9%2C11
Current issues are...
- (SOLVED) If the swap layout button at the top has been hit AND a node's expand/collapse button is hit, the chart defaults back to its original layout (in this case, left to right layout hierarchy). On second click of a node's expand/collapse button, it will actually perform the desired expand/collapse. Relevant code shown here...
//swapButton.js
const SwapButton = forwardRef(({ onSwapLayout }, ref) => {
// const [isCycleLayoutOn, setIsCycleLayoutOn] = useState(false);
const swapLayout = (event) => {
event.preventDefault();
onSwapLayout(event);
};
return (
<div>
<button
className="context-menu-btns"
style={{
backgroundColor: "#4caf50",
color: "white",
borderRadius: "0.5rem",
paddingTop: "0.5rem",
paddingBottom: "0.5rem",
paddingLeft: "1rem",
paddingRight: "1rem",
}}
onClick={(event) => swapLayout(event)}
ref={ref}
>
<FaRetweet size="2rem" />
</button>
</div>
);
});
export default SwapButton;
//orgChart.js
const OrganizationalChart = (props) => {
const d3Container = useRef(null);
const chartRef = useRef(null);
const [layoutIndex, setLayoutIndex] = useState(0);
const handleSwapLayout = useCallback((event) => {
if (event) {
setLayoutIndex((prevIndex) => (prevIndex + 1) % 4);
return layoutIndex;
}
return layoutIndex;
});
useLayoutEffect(() => {
const handleLayout = () => handleSwapLayout();
const chart = new OrgChart();
if (props.data && d3Container.current) {
chart
.container(d3Container.current)
.data(props.data)
.nodeWidth((d) => 300)
.nodeHeight((d) => 150)
.layout(["left", "top", "right", "bottom"][handleLayout()])
.compactMarginBetween((d) => 80)
.onNodeClick((d) => {
toggleDetailsCard(d);
})
.buttonContent((node, state) => {
return ReactDOMServer.renderToStaticMarkup(
<CustomExpandButton {...node.node} />
);
})
.nodeContent((d) => {
return ReactDOMServer.renderToStaticMarkup(
<CustomNodeContent {...d} />
);
})
.render();
chartRef.current = chart;
}
}, [props, props.data, handleSwapLayout]);
return (
<div style={styles.orgChart} ref={d3Container}>
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: "0.5rem",
}}
>
<SwapButton onSwapLayout={handleSwapLayout} ref={d3Container} />
</div>
);
};
export default OrganizationalChart;
- (SOLVED) If the toggle switch is clicked, it will take 3-4 times of clicking the change dataset button to get the nodes to update. When it updates, it ends up updating backwards. Furthermore, when the expand/collapse button is clicked, it will go back to its original layout.
//dataToggle.js
const DataToggle = ({ onDataChange, isToggleOn }) => {
const handleChange = (event) => {
event.preventDefault();
onDataChange(event);
};
return (
<div>
<span className={!isToggleOn ? "label_txt_unchecked" : "label_txt"}>OFF</span>
<label className="switch">
<input
checked={isToggleOn}
type={"checkbox"}
onChange={(event) => handleChange(event)}
/>
<span className="slider"></span>
</label>
<span className={isToggleOn ? "label_txt_checked" : "label_txt"}>ON</span>
</div>
);
};
export default DataToggle;
//App.js
const App = () => {
const [data, setData] = useState(employees);
const [isToggleOn, setIsToggleOn] = useState(false);
const handleDataChange = (event) => {
event.preventDefault();
setIsToggleOn(!isToggleOn);
if (isToggleOn) {
setData(revisedEmployees);
} else {
setData(employees);
}
};
return (
<React.StrictMode>
<>
<h1 style={styles.title}>Organization Chart</h1>
<div>
<DataToggle onDataChange={handleDataChange} isToggleOn={isToggleOn}/>
</div>
<OrganizationalChart data={data} />
</>
</React.StrictMode>
);
};//
export default App;
I suspect these issues are related to adding new information into the DOM while not clearing out the old info in the DOM properly. It feels like it is trying to render two different charts or not saving/updating correctly.
I have tried using...
d3-org-chart
methods:.connectionsUpdate()
,.linkUpdate()
,.nodeContent()
,.nodeUpdate()
,.updateNodesState()
, and.update()
with no success (I'm not confident I'm even employing them correctly). Here is where I got the d3-org-chart from: https://github.com/bumbeishvili/org-chart?tab=readme-ov-fileVarying combinations of
d3
's methods such as.select()
,.selectAll()
,.enter()
,.append()
,.join()
,.merge()
,.exit()
, and.remove()
... all with no success.useState()
,useRef()
,useCallback()
,useEffect()
,useLayoutEffect()
,andforwardRef()
--no success.
The original template I started this project with is here: https://codesandbox.io/s/org-chart-fnx0zi?file=/src/components/orgChart.js
Ideally, I would like the chart to not render to it's original layout when buttons or clicked unless the webpage is refreshed. Furthermore, I would like it to not take 3-4 clicks of the change dataset button for the nodes position to update under the correct parent node. If there is a way to accomplish the update via d3-org-chart
methods, I would prefer that. However, at this point, I'd take any solution, considering I haven't found a solution over the course of 4 months.
All issues resolved on Code Sandbox here... https://codesandbox.io/p/sandbox/d3-org-chart-react-functional-org-chart-forked-cylywl?file=%2Fsrc%2FApp.js%3A23%2C11
To fix issue 1, put
const chartRef = useRef(new OrgChart())
at the beginning and removeconst chart = new OrgChart()
fromuseLayoutEffect()
.According to the author of the bumbeishvili
d3-org-chart
, this is a common mistake when using react. Most likely the org chart was being created multiple times. His solution is posted here: https://codesandbox.io/p/sandbox/d3-org-chart-react-functional-org-chart-forked-kmsd96?file=%2Fsrc%2Fcomponents%2ForgChart.js%3A8%2C39To fix issue 2, logic is incorrect on toggle switch. To fix follow CSS and ensure logic is correct which is shown below...