Selecting all nodes / Multi-selection
Description
Hey guys, first a huge thx for your work. The cytoscape project is truly awesome! However, I've stumbled above this problem and couldn't find any posts about it in the community chat room, so I thought I give it a go here :)
So the problem is: When I try to implement a button "Select all nodes", that allows the user to select all nodes (via setting class 'selected': True in all nodes), the system gets buggy.
Concrete: The user can play around and do stuff, but if
- she clicks on "Select all nodes" -> all nodes are selected
- she clicks anywhere outside of the nodes -> all selections are dropped (but the class 'selected': True actually stays in the nodes)
- hence, when she now clicks again on the button "Select all nodes", nothing happens, cause the class-attributes are already set.
Steps/Code to Reproduce
import dash
import dash_cytoscape as cyto
import dash_html_components as html
from dash.dependencies import Output, Input, State
import dash_bootstrap_components as dbc
from django_plotly_dash import DjangoDash
app = DjangoDash('Minimal_example_for_select_bug')
app.layout = html.Div([
cyto.Cytoscape(
elements=[
{
'data': {'id': 1, 'label': 1},
'selected': True
},
{
'data': {'id': 2, 'label': 2}
},
{
'data': {'id': 3, 'label': 3}
},
],
id='graph'
),
dbc.Button("Select all", color="info", id="btn_exploration_select_all", className="m-1",
n_clicks=0, disabled=False),
dbc.Button("Deselect all", color="warning", id="btn_exploration_deselect_all", className="m-1",
n_clicks=0, disabled=False),
])
@app.callback(Output('graph', 'elements'),
# Exploration tab buttons
Input("btn_exploration_select_all", "n_clicks"),
Input("btn_exploration_deselect_all", "n_clicks"),
State("graph", "elements"),
)
def update_network(btn1, btn2, network_elements):
ctx = dash.callback_context # Dash context to figure out, which button has been pressed
if not ctx.triggered: # Initial execution
return network_elements
else:
print("elements:", network_elements)
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
if button_id == "btn_exploration_select_all":
for elem in network_elements:
elem["selected"] = True
elif button_id == "btn_exploration_deselect_all":
for elem in network_elements:
elem["selected"] = False
print("Now:", network_elements)
return network_elements
Expected Results
I would expect, that also when the user clicks outside of the nodes and therefore changes the selection, it is somehow represented in the cytoscape-elements, so that it can be manipulated with respective functions.
Actual Results
User selection of specific nodes can not be accessed via the given Callback-Options.
Versions
Django 3.2.7 Dash 2.0.0 Dash Core Components 2.0.0 Dash HTML Components 2.0.0 Dash Renderer 1.9.1 Dash HTML Components 0.2.0
Using Firefox
I've found a work-around:
- I also take "selectedNodeData" as input
- Then every time, before I further work with the network nodes, I update the "selected"-classes based on the selected Nodes that are returned through
Input("graph", "selectedNodeData").
So basically I make sure, that what is being displayed is then also reflected in the selection classes of the network elements.
@app.callback(Output('graph', 'elements'),
# Exploration tab buttons
Input("btn_exploration_select_all", "n_clicks"),
Input("btn_exploration_deselect_all", "n_clicks"),
Input("graph", "selectedNodeData"),
State("graph", "elements"),
)
def update_network(btn1, btn2, selected_elems, network_elements):
network_elements = reset_selection_classes(network_elements, selected_elems)
ctx = dash.callback_context # Dash context to figure out, which button has been pressed
if not ctx.triggered: # Initial execution
return network_elements
else:
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
if button_id == "btn_exploration_select_all":
for elem in network_elements:
elem["selected"] = True
elif button_id == "btn_exploration_deselect_all":
for elem in network_elements:
elem["selected"] = False
return network_elements
def reset_selection_classes(network_elems, selected_elems):
if selected_elems or selected_elems == []:
selected_node_ids = [entry["id"] for entry in selected_elems]
for elem in network_elems:
if elem["data"]["id"] in selected_node_ids:
elem["selected"] = True
else:
elem["selected"] = False
return network_elems
else:
return network_elems```