plotly / dash-cytoscape

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

[BUG] "Children" nodes not displaying after deleting the "parent" node #145

Open hibachi2021 opened 3 years ago

hibachi2021 commented 3 years ago

[BUG] "Children" nodes not displaying after deleting the "parent" node

Expected Results

The example script below has a Delete button that should remove an element from the Cytoscape element list when clicked, and if it has "children", should delete the 'parent' key from the 'data', but retain the "children" nodes in the list AND display the remaining "children" nodes in the Cytoscape graph.

Actual Results

The code works for deleting element nodes without children, however, when clicking on a "parent" element node, all the children are no longer displayed even though the "children" elements are still in the list (see Print statements)

Thanks for this great library!

Steps/Code to Reproduce


# Dash-related modules
from dash import Dash
import dash_cytoscape as cyto

import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate

app = Dash(__name__,
                title='Example Cyto',
                external_stylesheets=[dbc.themes.BOOTSTRAP],
                suppress_callback_exceptions=True)

elements =[
    # parent
    {'data':{'id':'node1', 'label':'node1'}, 'position':{'x':100, 'y':100}},
    # children
    {'data':{'id':'node1A', 'label':'node1A', 'parent':'node1'}, 'position':{'x':100, 'y':100}},
    {'data':{'id':'node1B', 'label':'node1B', 'parent':'node1'}, 'position':{'x':100, 'y':100}},
]

# Create cytoscape graph object
cytoscape = html.Div([
    cyto.Cytoscape(
        id='cytoscape',
        layout={'name': 'preset', 'fit':True},
        style={
            'width': '100vh', 
            'height': '600px'
            },
        responsive=True,
        autoRefreshLayout=True,
        elements=elements,
    )
], style={
    'display':'flex', 
    'width':'auto', 
    'height':'auto'
    })

app.layout = html.Div([

    dbc.Button("Delete node", id='delete-bt', color="danger"),
    cytoscape,
    dcc.Store('cytoscape-elements-store', data=elements)

])

# Update Cytoscape graph
@app.callback(
    Output("cytoscape", "elements"), 
    [Input("cytoscape-elements-store", "modified_timestamp")], 
    [State("cytoscape-elements-store", "data")])
def _update_cyto_elements(n, data):

    if n:
        data = data or []
        print(f"FINAL_ELEMENTS: {data}")
        return data

    else:
        raise PreventUpdate

# Update elements store
@app.callback(
    Output("cytoscape-elements-store", "data"), 
    [Input("delete-bt", "n_clicks"),],
    [State('cytoscape', 'tapNodeData'), State("cytoscape-elements-store", "data"),])
def _delete_node(n, tapNode, elements):

    if n and tapNode:

        el_id = tapNode['id'] 
        print(f"TAPNODE: {tapNode}")

        # Get list of selected node and children
        remove_idxs = []
        for i, el in enumerate(elements):
            if "source" not in el:
                # find selected node
                if el['data']['id'] == el_id:
                    remove_idxs.append(i)
                # find children of selected node and remove 'parent' key
                elif 'parent' in el['data']:
                    if el['data']['parent'] == el_id:
                        del el['data']['parent']

        elements = [el for el in elements if el['data']['id'] != el_id]

        return elements

    else:
        raise PreventUpdate

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

Versions