Issue:
In the code below, I want a user to be able to select any node in a Cytoscape graph and delete that node by clicking on a “Delete” button. This works for nodes that do not have children, however, when deleting a “parent” node, the associated “children” nodes are no longer displayed (even though they are still in the elements
list for the Cytoscape component).
In the code, I delete the parent
key for all children of the deleting parent node, and this is reflecting in the “print” statements of the elements list.
# 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)