plotly / dash-cytoscape

Interactive network visualization in Python and Dash, powered by Cytoscape.js
https://dash.plot.ly/cytoscape
MIT License
587 stars 120 forks source link

Performance of CyLeaflet for larger numbers of nodes (>1000) #210

Closed emilykl closed 2 months ago

emilykl commented 4 months ago

Description

The below code instantiates a CyLeaflet instance with 1000 nodes. It takes about 3 seconds to load in my browser (Mac OS 12.6; Chrome 120.0). A plain cyto.Cytoscape instance with the same 1000 nodes loads in <0.5s.

The profiler shows the code is spending a lot of time in this event handler:

        cy.on('dragfree add remove', (_) => {
            this.props.setProps({
                elements: cy.elements('').map((item) => {
                    if (item.json().group === 'nodes') {
                        return {
                            data: item.json().data,
                            position: item.json().position,
                        };
                    }
                    return {
                        data: item.json().data,
                        position: void 0,
                    };
                }),
            });
        });

which is triggered each time a node is added, removed, or dragged.

It makes sense that the event handler would be triggered while nodes are being added to the layout, but not clear why it's taking so much longer when part of cyto.CyLeaflet compared to just cyto.Cytoscape. More investigation needed.

Steps/Code to Reproduce

import dash
from dash import html, dcc, callback, Input, Output
import dash_cytoscape as cyto
import dash_leaflet as dl

lat_init = 45.5
lon_init = -73.576

print("Preparing elements")
elements = [
    {"data": {"id": str(x), "lat": lat_init + x / 1000, "lon": lon_init}}
    for x in range(1000)
]
print("Elements created.")

stylesheet = [
    {
        "selector": "node",
        "style": {
            "width": 1000,
            "height": 100,
            "background-color": "red",
        },
    },
]

def serve_layout():
    return html.Div(
        children=[
            html.H1("DASH CYTOSCAPE - Maximum number of nodes"),
            html.Hr(),
            dcc.Markdown(
                """
            * Displaying up to 1000 elements seems doable, but more than that the browser may crash.
            """
            ),
            cyto.CyLeaflet(
                id="cyleaflet",
                cytoscape_props=dict(
                    elements=elements,
                    stylesheet=stylesheet,
                ),
            ),
        ],
    )

app = dash.Dash(__name__)
server = app.server
app.layout = serve_layout

if __name__ == "__main__":
    app.run_server(debug=True)

Expected Results

Actual Results

Versions

Farkites commented 2 months ago

The event ('dragfree add remove') gets trigger for each individual node added. So initially it would get triggered 1000 times for this code you have provided. Adding a debounce should fix this without changing any functionality.