bumbeishvili / org-chart

Highly customizable org chart. Integrations available for Angular, React, Vue
https://stackblitz.com/edit/web-platform-o5t1ha
MIT License
916 stars 328 forks source link

Lazy Loading #147

Open bumbeishvili opened 2 years ago

bumbeishvili commented 2 years ago

Lazy loading of nodes data

m-gallesio commented 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();
m-gallesio commented 1 year ago

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.

bumbeishvili commented 1 year ago

Interesting, looks simple, thank you, I am sure it will be useful