Description
I am trying to show a graph with parent and child nodes (representing clusters) and have a menu or button callback to remove/hide the parent nodes and leave the children (and appropriate edges.)
I've tried various approaches, but essentially, they seem to all return the same error.
The callback(s) return a whole new "elements" to Cytoscape and they inevitably result in a JS error in the browser like this example:
Can not create edge 7cf808c5-c6a2-4937-8ca8-8b43d1bd8893
with nonexistant source nyc
I have put together a small test/demo to show the problem, based directly on some of the examples from the documentation.
As far as I can see from the documentation examples, this should do what I want, but I still get the errors. I don't think I'm missing anything (this is pretty straightforwardly copying and pasting from the examples...) so I suspect there's a but here.
In my debugging efforts, I noticed that if one changes the IDs of the nodes in the callback instead of leaving them the same as the originals, then it seems to work. But that would be somewhat kludgey and cumbersome to do for a graph with lots of actual data.
(Note: I did try looking at the JS in the browser console tool. if I followed it correctly, it looks like it's removing all the nodes and edges, then trying to add back edges before creating the nodes... maybe...)
Thanks in advance for your help!
-Al
My Example CODE:
import dash
import dash_cytoscape as cyto
import dash_html_components as html
from dash.dependencies import Input, Output, State
app = dash.Dash(name)
app.config.suppress_callback_exceptions = True
app.layout = html.Div([
cyto.Cytoscape(
id='cytoscape-compound',
layout={'name': 'preset'},
style={'width': '100%', 'height': '450px'},
stylesheet=[
{
'selector': 'node',
'style': {'content': 'data(label)'}
},
{
'selector': '.countries',
'style': {'width': 5, 'visible': 'false' }
},
{
'selector': '.cities',
'style': {'line-style': 'dashed'}
}
],
elements=[
# Parent Nodes
{
'data': {'id': 'us', 'label': 'United States'}
},
{
'data': {'id': 'can', 'label': 'Canada'}
},
# Children Nodes
{
'data': {'id': 'nyc', 'label': 'New York', 'parent': 'us'},
'position': {'x': 100, 'y': 100}
},
{
'data': {'id': 'sf', 'label': 'San Francisco', 'parent': 'us'},
'position': {'x': 100, 'y': 200}
},
{
'data': {'id': 'mtl', 'label': 'Montreal', 'parent': 'can'},
'position': {'x': 400, 'y': 100}
},
# Edges
{
'data': {'source': 'can', 'target': 'us'},
'classes': 'countries'
},
{
'data': {'source': 'nyc', 'target': 'sf'},
'classes': 'cities'
},
{
'data': {'source': 'sf', 'target': 'mtl'},
'classes': 'cities'
}
]
),
html.Div([
html.Button('Add', id='btn-add-node-example', n_clicks_timestamp=0),
html.Button('Remove', id='btn-remove-node-example', n_clicks_timestamp=0)
])
])
@app.callback(Output('cytoscape-compound', 'elements'),
[Input('btn-add-node-example', 'n_clicks_timestamp'),
Input('btn-remove-node-example', 'n_clicks_timestamp')],
[State('cytoscape-compound', 'elements')])
def update_elements(btn_add, btn_remove, elements):
if int(btn_add) > int(btn_remove):
elements = [
# Children Nodes
{
'data': {'id': 'nyc', 'label': 'New York', 'parent': 'us'},
'position': {'x': 100, 'y': 100}
},
{
'data': {'id': 'sf', 'label': 'San Francisco', 'parent': 'us'},
'position': {'x': 100, 'y': 200}
},
{
'data': {'id': 'mtl', 'label': 'Montreal', 'parent': 'can'},
'position': {'x': 400, 'y': 100}
},
# Parent Nodes
{
'data': {'id': 'us', 'label': 'United States'}
},
{
'data': {'id': 'can', 'label': 'Canada'}
},
# Edges
{
'data': {'source': 'can', 'target': 'us'},
'classes': 'countries'
},
{
'data': {'source': 'nyc', 'target': 'sf'},
'classes': 'cities'
},
{
'data': {'source': 'sf', 'target': 'mtl'},
'classes': 'cities'
}
]
return elements
elif int(btn_remove) > int(btn_add):
elements = [
{
'data': {'id': 'nyc', 'label': 'New York'},
'position': {'x': 100, 'y': 100}
},
{
'data': {'id': 'sf', 'label': 'San Francisco'},
'position': {'x': 100, 'y': 200}
},
{
'data': {'id': 'mtl', 'label': 'Montreal'},
'position': {'x': 400, 'y': 100}
},
# Edges
{
'data': {'source': 'nyc', 'target': 'sf'},
'classes': 'cities'
},
{
'data': {'source': 'sf', 'target': 'mtl'},
'classes': 'cities'
}
]
return elements
return elements
if name == 'main':
app.run_server(debug=True)