I'm plotting a network using Dash-Cytoscape, using the breadthfirst
layout, as documented here.
I would like to control the order of appearance of elements on the same level. The JS API has the depthSort
argument to achieve this, but I couldn't figure out how to pass a callback in Python that the frontend can live with.
Things I've tried:
"depthSort": lambda a,b: a - b
"depthSort": "(a,b) => a - b"
"depthSort": "function (a,b) { return a - b}"
I would like to get this:
1
3 2
but what I'm getting is this:
1
2 3
from dash import Dash
import dash_cytoscape as cyto
app = Dash(__name__)
app.layout = cyto.Cytoscape(
elements=[
{"data": {"id": "1", "label": "1"}},
{"data": {"id": "2", "label": "2"}},
{"data": {"id": "3", "label": "3"}},
{"data": {"source": "1", "target": "2"}},
{"data": {"source": "1", "target": "3"}},
],
layout={
"name": "breadthfirst",
"roots": ["1"],
# "depthSort": ?
},
)
app.run_server(debug=True)
Options that expect a JS function are not supported in Python, because it implies passing the function as a string, and thus it would require Cytoscape.js to eval
uate arbitrary strings, which is probably something the maintainers don't want for security reasons.
That said, Dash supports clientside callbacks (JS) so we can still assign the function within a callback :
from dash import Dash, Output, Input, State
import dash_cytoscape as cyto
app = Dash(__name__)
app.layout = cyto.Cytoscape(
id="cyto",
elements=[
{"data": {"id": "1", "label": "1"}},
{"data": {"id": "2", "label": "2"}},
{"data": {"id": "3", "label": "3"}},
{"data": {"source": "1", "target": "2"}},
{"data": {"source": "1", "target": "3"}},
],
layout={
"name": "breadthfirst",
"roots": ["1"]
}
)
app.clientside_callback(
"""
function (id, layout) {
layout.depthSort = (a, b) => b.data('id') - a.data('id');
cy.layout(layout).run();
return layout;
}
""",
Output('cyto', 'layout'), # update the (dash) cytoscape component's layout
Input('cyto', 'id'), # trigger the function when the Cytoscape component loads (1)
State('cyto', 'layout'), # grab the layout so we can update it in the function
prevent_initial_call=False # ensure (1) (needed if True at the app level)
)
app.run_server(debug=True)
NB. We need to execute cy.layout(layout).run()
because no element are added/removed so it won't run automatically.