Open bumbeishvili opened 2 years ago
Greetings. I had to implement a draft for this for our use case via some monkeypatching. I have received explicit permission to share this code. @bumbeishvili Feel free to use this as a basis to implement your own solution and / or integrate something like this in the library.
// NOTE: this code uses TypeScript annotations with types based on @types/d3-org-chart
import { OrgChart } from "d3-org-chart"; // or use d3.OrgChart in traditional <script>-based JavaScript
// Toggle buttons are normally hidden for childless nodes.
// This is appended to update() to ensure they are always visible.
// A native implementation would probably avoid setting these attributes directly while building the chart.
const originalUpdate = OrgChart.prototype.update;
OrgChart.prototype.update = function (params: Parameters<OrgChart<any>["update"]>[0]) {
originalUpdate.call(this, params);
for (const node of document.querySelectorAll(".node-button-g")) {
node.removeAttribute("display");
node.removeAttribute("opacity");
}
}
// Bonus helper, just a version of OrgChart.addNode which allows adding multiple nodes to avoid triggering an update after every single addition
// This is not strictly needed but it should improve performance.
OrgChart.prototype.addNodes = function <T>(this: OrgChart<T>, nodes: T[]) {
for (const obj of nodes) {
const attrs = this.getChartState();
if (attrs.allNodes.some(({ data }) => attrs.nodeId(data) === attrs.nodeId(obj))) {
console.log(`ORG CHART - ADD - Node with id "${attrs.nodeId(obj)}" already exists in tree`);
return this;
}
if (!attrs.allNodes.some(({ data }) => attrs.nodeId(data) === attrs.parentNodeId(obj))) {
console.log(`ORG CHART - ADD - Parent node with id "${attrs.parentNodeId(obj)}" not found in the tree`);
return this;
}
if (obj["_centered"] && !obj["_expanded"])
obj["_expanded"] = true;
attrs.data.push(obj);
}
this.updateNodesState();
return this;
}
// Configures lazy loading for a chart instance.
// chart: the chart object
// hasChildren: a function which determines if node.data is supposed to have any children once expanded
// load: an async function which, given a node.data, loads its children
// A native implementation could introduce something like this directly in the configuration.
function setLazyLoading<T>(this: void, chart: OrgChart<T>, hasChildren: (d: T) => boolean, load: (d: T) => Promise<T[]>) {
const originalOnButtonClick = chart.onButtonClick;
chart.onButtonClick = async function (e: MouseEvent, d: any) {
if (!d.children && !d._children && hasChildren(d.data))
chart.addNodes((await load(d.data)).map(node => Object.assign(node, { _expanded: true })));
originalOnButtonClick.call(this, e, d);
}
}
// Usage example.
const chart = d3.OrgChart() /* ... configuration here ... */
setLazyLoading(chart, d => d.hasChildren, d => loadDataFromBackend("chartData/" + d.id + "/children"));
chart.render();
I have wrapped my code in a package and published it: https://github.com/m-gallesio/d3-org-chart-lazy https://www.npmjs.com/package/d3-org-chart-lazy
This is still in EARLY ALPHA and subject to breakage.
Interesting, looks simple, thank you, I am sure it will be useful
Lazy loading of nodes data