4

I have created some nodes with Dash Cytoscapes. My final goal is that there should appear a Slider after clicking on a node. By selecting a value with this Slider the label of the node should be updated with the selected value. Since Im not able to display the slider after clicking a node I made it be displayed permanently below the nodes. Hopefully you got some ideas to reach my desire.

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output
import dash_cytoscape as cyto

app = dash.Dash(__name__)

app.layout = html.Div([
        cyto.Cytoscape(  
        id='node-callback-event',
        layout={'name': 'preset'},
        style={'width': '100%', 'height': '2000px', 'display': 'block'},
        elements=[
            {
                'data': {'id': 'root', 'label': 'test label'},
                'position': {'x': 750, 'y': 750},
                'locked': True
            }
        ]
    ),
        html.Label('Slider'),
        dcc.Slider(
            id='slider-update-value',
            min=0,
            max=100,
            value=0,
            step=0.01,
            tooltip = {"placement": "bottom", 'always_visible': False },
            included=False,
            updatemode='drag',
        ),
    ], 
    style={'padding': 1, 'flex': 1})

@app.callback(Output('node-callback-event', 'elements'),
              Input('node-callback-event', 'tapNodeData'),
              Input('slider-update-value', 'value'))
def displayTapNodeData(node, probability):
    if node:
        node['label'] += str(probability)


# how to assign the value to the nodes label? 

if __name__ == '__main__':
    app.run_server(debug=True)
Baflora
  • 119
  • 9

1 Answers1

2

You can wrap the slider in a div element, which you can hide by default using the hidden property :

html.Div(id='slider', hidden=True, children=[
    html.Label('Slider'),
    dcc.Slider(
        id='slider-update-value',
        # options...
    )
])

Then update that property when a node get selected using tapNodeData or selectedNodeData event, I use selectedNodeData here because the other is not fired when you unselect a node (ie. if you want to hide the slider back) :

@app.callback(
    Output('slider', 'hidden'),
    Input('node-callback-event', 'selectedNodeData'))
def displaySlider(data):
    return False if data else True

Now, given the Input value of the slider, you want to update the Output of the Cytoscape elements, given the State of the currently selected node(s) (tapNodeData or selectedNodeData again) :

@app.callback(Output('node-callback-event', 'elements'),
            Input('slider-update-value', 'value'),
            State('node-callback-event', 'elements'),
            State('node-callback-event', 'selectedNodeData'))
def updateNodeLabel(value, elements, selected):
    if selected:
        # Update only the selected element(s)
        ids = [nodeData['id'] for nodeData in selected]
        nodes = ((i,n) for i,n in enumerate(elements) if n['data']['id'] in ids)
        for i, node in nodes:
            elements[i]['data']['label'] = 'test-' + str(value)
    return elements

@see Cytoscape callbacks and events

EricLavault
  • 12,130
  • 3
  • 23
  • 45
  • Thank you, that did the trick! Is there any possibility to show the slider next to the Node which was clicked? Could I change the layout of the Div within the callback? – Baflora Nov 08 '21 at 10:10
  • As far as I know you can't display a component floating over the figure, though it should be possible doing some tricky javascript. Yes you can change the layout of anything in the callback, just add an Output and return the new value for the property you want to update (like 'children' to replace the whole content of the Div). Note that if more than one Output are used, you need to wrap them in an array : `@app.callback([Output(...), Output(...)], Input(...), ...)`, and return an array as well. – EricLavault Nov 08 '21 at 11:59
  • Maybe I could use your idea above to hide the Div and show it next to the node by changing the position of the Div in comparison to the position of the node which was clicked. Thanks for the advice. – Baflora Nov 08 '21 at 16:38
  • Yes, take a look at [clientside-callbacks](https://dash.plotly.com/clientside-callbacks), I think it's the best option to achieve that. – EricLavault Nov 08 '21 at 17:02