Hello everyone,
I try to make a cytoscape graph(based on an exemple from Dash) which can expand and collapse. I added a button “reset” which will collapse the graph to the first node. However if I use this button in a specific pattern, the tapNodeData input will not trigger anymore for the specific node.
It happen, when I click on the first node “TestCorp”, then when I click on “reset” button and when I want to re expand my graph by clicking on “TestCorp” node.
If I click on “reset” button then on node “TestCorp” it will work.
If I click on “TestCorp” node, then “aaa” node or “bbb” node, then “reset” button and finally on “TestCorp” node, the tapNodeData input will work.
You will find a test code below which reproduce my issue :
import requests
import dash
import dash_table
import pandas as pd
import pymongo
import datetime
import dash_html_components as html
import dash_bootstrap_components as dbc
import dash_core_components as dcc
import json
import dash_cytoscape as cyto
from dash.dependencies import Input, Output, State
#enable svg
cyto.load_extra_layouts()
app = dash.Dash(__name__)
server = app.server
Data = []
Exemple_data = [
{'provider': 'TestCorp', 'ID': 'V-aaaaa', 'Description': {'Name': 'aaa', 'Type': 'Vequipement', 'Region': 'us'}},
{'provider': 'TestCorp', 'ID': 'V-bbbbb', 'Description': {'Name': 'bbb', 'Type': 'Vequipement', 'Region': 'us'}},
]
for x in Exemple_data:
if x['provider'] == "TestCorp":
Data.append([x['ID'],'TestCorp',x['Description']['Type'],x['Description']['Name']])
df = pd.DataFrame(Data,columns=['ID','Linked','Type','Name'])
nodes = set()
node_children = {} # user id -> list of followers (cy_node format)
edge_children = {} # user id -> list of cy edges ending at user id
cy_edges, cy_nodes = [], []
edges = df
colors = ['red', 'blue', 'green', 'yellow', 'pink']
###### Create first node
nodes.add('TestCorp')
cy_nodes.append({"data": {"id": 'TestCorp', "label": 'TestCorp',"Level": 0,"expanded": 'No'}})
for edge in edges.iterrows():
if edge[1]['Linked'] != '':
Type = edge[1]['Type']
name = edge[1]['Name']
source = edge[1]['ID']
target = edge[1]['Linked']
if edge[1]['Type'] == 'Vequipement':
cy_source = {"data": {"id": source, "label": name,'Level':1,'Linked':target,'expanded':'No','Type':Type}}
else:
cy_source = {"data": {"id": source, "label": name,'Level':2,'Linked':target,'expanded':'No','Type':Type}}
cy_target = {"data": {"id": target, "label": target,'Level':cy_source['data']['Level'],'expanded':'No'}}
cy_edge = {'data': {'id': source+target, 'source': source, 'target': target, 'Level':cy_source['data']['Level'],'expanded':'No'}}
if source not in nodes: # Add the source node
nodes.add(source)
cy_nodes.append(cy_source)
if target not in nodes: # Add the target node
nodes.add(target)
cy_nodes.append(target)
# Process dictionary of followers
if not node_children.get(target):
node_children[target] = []
if not edge_children.get(target):
edge_children[target] = []
node_children[target].append(cy_source)
edge_children[target].append(cy_edge)
genesis_node = cy_nodes[0]
genesis_node['classes'] = "genesis"
default_elements = [genesis_node]
# ################################# APP LAYOUT ################################
styles = {
'json-output': {
'overflow-y': 'scroll',
'overflow-wrap': 'break-word',
'height': 'calc(50% - 25px)',
'border': 'thin lightgrey solid'
},
'tab': {'height': 'calc(98vh - 80px)'}
}
app.layout = html.Div([
html.Button('Reset', id='bt-reset'),
html.Div(id='my-output'),
html.Div(className='eight columns', children=[
cyto.Cytoscape(
id='cytoscape',
elements=default_elements,
style={
'height': '95vh',
'width': 'calc(100% - 250px)',
'float' : 'left',
},
)
])
])
########################### START OF DELETE FUNCTION ###########################
def delete_node(nodeData,elements):
node_childrens = []
if 'id' in nodeData:
for linked in elements:
if 'Linked' in linked['data'] and linked['data']['Linked'] == nodeData['id']:#####supression nodes
node_childrens.append(linked)
if 'target' in linked['data'] and linked['data']['target'] == nodeData['id']:####### supression edge
node_childrens.append(linked)
elif 'id' in nodeData['data']:
for linked in elements:
if 'Linked' in linked['data'] and linked['data']['Linked'] == nodeData['data']['id']:#####supression nodes
node_childrens.append(linked)
if 'target' in linked['data'] and linked['data']['target'] == nodeData['data']['id']:####### supression edge
node_childrens.append(linked)
if(node_childrens == []):
return
for children in node_childrens:
delete_node(children,elements)
elements.remove(children)
i = 0
for element in elements:
if 'id' in nodeData:
if element['data']['id'] == nodeData['id']:
elements[i]['data']['expanded'] = 'No'
elif 'id' in nodeData['data']:
if element['data']['id'] == nodeData['data']['id']:
elements[i]['data']['expanded'] = 'No'
i+=1
return 0
###########################
END OF DELETE FUNCTION
##########################
###############################
CALLBACKS
####################################
################### Callback n°1 Expand/Collapse nodes
@app.callback(Output("cytoscape", "elements"), [Input("cytoscape", "tapNodeData"),Input("bt-reset", "n_clicks")],State("cytoscape", "elements"))
def modification_on_elements(nodeData,n_clicks,elements):
if not nodeData:
return default_elements
ctx = dash.callback_context
if ctx.triggered:
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
if button_id == "bt-reset":
return default_elements
elif button_id == "cytoscape":
if 'Yes' == nodeData.get('expanded'):
delete_node(nodeData,elements)
return elements
if 'No' == nodeData.get('expanded'):
i = 0
for element in elements:
if nodeData['id'] == element.get('data').get('id'):
elements[i]['data']['expanded'] = 'Yes'
break
i+=1
node_childrens = node_children.get(nodeData['id'])
edge_childrens = edge_children.get(nodeData['id'])
if node_childrens:
elements.extend(node_childrens)
if edge_childrens:
elements.extend(edge_childrens)
return elements
if __name__ == '__main__':
app.run_server(debug=True)
Thank and have a nice day.
Duaran