Dash Cytoscape - returning node positions from layout

Hi @xhlu. I’m having alot of fun using dash and cytoscape to build an interactive project tree at the moment (thanks for all the hard work !). I’m currently using the Klay layout under extra_layouts, however after applying styling to nodes to make them different sizes some overlap or are hidden beneath other nodes.

As a work around I was thinking I could read the node positions (in a callback), update the positions to elements as needed, and regenerate the cytoscape plot with layout={‘name’: ‘preset’}.

Is there a way to return all the node positions generated from the layout run? I’ve noted that ‘tapNode’ can be used to return the positions from Json data, but not sure how to get out all the nodes positions together to update the layout accordingly and prefereably so can be updated upfront without clicking on nodes required.

Any suggestions welcome ! Thanks in advance.

1 Like

I’m on a similar quest!

After applying a layout (Cose, in my case), the nodes are too crowded and I’d just like to space them out a bit. I thought I’d just simply add a slider bar that the user can manipulate, and then grab [‘position’] from each node element in “elements”, scale the positions appropriately and return the network. But elements don’t seem to have a ‘positions’, i get a KeyError: ‘position’.

Probably helpful, but I don’t quite understand it:

Hi,

I have stumbled upon the same problem: I would like to access the positions of the nodes after they where computed by some layout (for example, ‘cose-bilkent’).

I have tried to implement some solution based on the cited stack overflow post

But the problem with this solution is that I cannot create a ‘cytoscape’ (from cytoscape.js) object using already existing graph generated and rendered by Dash Cytoscape (and the answer shows how we can create a graph from the scratch using cytoscape.js). I also believe there is no such capability coming from ‘cytoscape.js’.

I was wondering if there is a solution for this problem from the Dash Cytoscape side.

1 Like

You can try this, which works for me. ‘pip install visdcc’. import the module

import visdcc

in the app layout add this

dcc.Download(id='Get Data'),
html.Button('Export', id='export_button'),
visdcc.Run_js(id= 'javascript'),

in the callback section add this

@app.callback(
Output(‘javascript’, ‘run’),
Input(‘export_button’, ‘n_clicks’),
prevent_initial_call=True
)
def print_page(x):
return ‘’’
console.log(‘Nodes:’, cy.nodes().jsons());
console.log(‘Edges:’, cy.edges().jsons());
console.log(‘Elements:’, cy.elements().jsons());
‘’’

this will print the node and edge data to the console

image

How can I get data from console in python?

If you set the x/y positions initially in your node elements to 0/0 when using force directed like code/cola, then you can grab the cose/cola updates node positions from the cytoscape elements in a callback

from dash import Dash, html, Input, Output, State, dcc, ctx
import dash_bootstrap_components as dbc
import dash_cytoscape as cyto
from dash.dependencies import Input, Output, State
import json
from dash.exceptions import PreventUpdate


app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP]) 

cyto.load_extra_layouts()

app.layout = html.Div([
    cyto.Cytoscape(
        id='cytoscape',
        elements=[
            {'data': {'id': 'one', 'label': 'Node 1'},
             'position': {'x': 0, 'y': 0}},
            {'data': {'id': 'two', 'label': 'Node 2'},
             'position': {'x': 0, 'y': 0}},
            {'data': {'id': 'three', 'label': 'Node 3'},
             'position': {'x': 0, 'y': 0}},
            {'data': {'id': 'four', 'label': 'Node 4'},
             'position': {'x': 0, 'y': 0}},
            {'data': {'source': 'one', 'target': 'two', 'label': '1 to 2'}},
            {'data': {'source': 'two', 'target': 'three', 'label': '2 to 3'}},
            {'data': {'source': 'three', 'target': 'one', 'label': '1 to 3'}},
            {'data': {'source': 'three', 'target': 'four', 'label': '3 to 4'}}
        ],
        layout={'name': 'cose'}
    ),
    html.Button("Print elements JSONified", id="button-cytoscape"),
    html.Div(id="html-cytoscape"),
])

@app.callback(
    Output("html-cytoscape", "children"),
    [Input("button-cytoscape", "n_clicks")],
    [State("cytoscape", "elements")],
)
def testCytoscape(n_clicks, elements):
    if n_clicks:
        return json.dumps(elements)


if __name__ == '__main__':
    app.run_server(debug=True)