Plotly Dash, Leaflet and Cytoscape together

I have a geospatial app and would like to add Cytoscape over it to show network relationships in my dataset, similar to this. I’m wondering if anyone knows a MVE that shows how to accomplish this task.

well … isn’t this timely :slight_smile: @lorenzo
check out this

  • #200 Add CyLeaflet all-in-one component for network graphs overlaid on Leaflet maps. Requires the dash-leaflet package, which can be installed as an extra with this package: pip install dash-cytoscape[leaflet].

And thank Hydro-Quebec who underwrote the development of it

2 Likes

oh … and please report back here w what you get up to … I’m keen to see practical use cases from such!

Hi @DaveG,

Thank you very much for sharing the link; it looks promising. However, when I attempted to run the example from dash-cytoscape , I encountered the following error:

TypeError: Cannot read properties of undefined (reading 'updateLeafBounds')

    at _callee2$ (http://127.0.0.1:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_13_0m1702540705.dev.js:580:56)

    at tryCatch (http://127.0.0.1:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_13_0m1702540705.dev.js:406:1357)

    at Generator.<anonymous> (http://127.0.0.1:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_13_0m1702540705.dev.js:406:4187)

    at Generator.next (http://127.0.0.1:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_13_0m1702540705.dev.js:406:2208)

    at asyncGeneratorStep (http://127.0.0.1:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_13_0m1702540705.dev.js:412:103)

    at _next (http://127.0.0.1:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_13_0m1702540705.dev.js:413:194)

    at http://127.0.0.1:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_13_0m1702540705.dev.js:413:364

    at new Promise (<anonymous>)

    at http://127.0.0.1:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_13_0m1702540705.dev.js:413:97

    at handleClientside (http://127.0.0.1:8050/_dash-component-suites/dash/dash-renderer/build/dash_renderer.v2_13_0m1702540705.dev.js:533:28)

I’m unsure if this issue is a bug or if it’s related to the versions of dash-cytoscape and dash-leaflet I have installed, which are:

dash-cytoscape==1.0.0
dash-leaflet==1.0.15

Any insights or assistance would be greatly appreciated.


requirements.txt:

blinker==1.7.0
certifi==2024.2.2
charset-normalizer==3.3.2
click==8.1.7
colorama==0.4.6
dash==2.15.0
dash-core-components==2.0.0
dash-cytoscape==1.0.0
dash-html-components==2.0.0
dash-leaflet==1.0.15
dash-table==5.0.0
Flask==3.0.2
idna==3.6
importlib-metadata==7.0.1
itsdangerous==2.1.2
Jinja2==3.1.3
MarkupSafe==2.1.5
nest-asyncio==1.6.0
packaging==23.2
plotly==5.18.0
requests==2.31.0
retrying==1.3.4
six==1.16.0
tenacity==8.2.3
typing_extensions==4.9.0
urllib3==2.2.0
Werkzeug==3.0.1
zipp==3.17.0

@PipInstallPython, thanks, i’m able to reproduce it.

I now have two other questions. I have this code which plot a network of nodes over a Leaflet map:

import dash
from dash import html
import dash_cytoscape as cyto
import dash_leaflet as dl
import json
# Initialize Dash app
app = dash.Dash(__name__)

master_node_id = '10002423' 

# Read from the JSON file
with open('data/elements_' + str(master_node_id) +'.json', 'r') as file:
    elements = json.load(file)

cy_stylesheet = [
    {"selector": "node", "style": {"width": "20px", "height": "20px"}},
    {"selector": "edge", "style": {"width": "5px"}},
    {"selector": "node", "style": {"label": "data(label)"}},
]

# Read from the JSON file
with open('data/elements_' + str(master_node_id) +'.json', 'r') as file:
    elements = json.load(file)
# `elements` is now a list of elements that can be used in Dash Cytoscape

node_ids = [59149772, 59149765, 59149769, 59149767, 59149771, 59149774, 61934176, 59149766, 59149768, 59149770, 59149773, 59149771]


# Extract unique labels
unique_labels = list(set(node['data']['label'] for node in elements))
total_labels = len(unique_labels)

# Create a selector string that matches any of the specified node IDs
selector = ', '.join([f'node[id = "{node_id}"]' for node_id in node_ids])

cy_stylesheet = [
    {"selector": "node",
      "style": {
          "width": "5px",      
          "height": "5px"
          }},
    
    {"selector": "node", # Targets all nodes
     "style": {
         "label": "data(label)", # Use the 'label' field from each node's data for the label text
         'font-size': '0px'  # Decrease font size to 10px
         }},

    # Style for all nodes
    {
        'selector': 'node',
        'style': {
            'width': '5px',  # Reduce the width of the node
            'height': '5px',
            'content': 'data(label)',
            'font-size': '0px',  # Example font size
            'background-color': '#0074D9',  # Example default node color
            'text-valign': 'center',
            'text-halign': 'center',
            'color': '#000000',  # Example text color 
        }
    },     

    # Special style for the master unit node
    {
        'selector': f'node[id = "{master_node_id}"]',  # Targeting master node by its ID
        'style': {
            'background-color': '#FF4136',  # Different color for the master node
            'border-color': 'black',  # Optional: add a border to make it stand out
            'border-width': 1,
            'width': '20px',  # Reduce the width of the node
            'height': '20px',
        }
    },

    {"selector": "edge",
      "style": {
          "width": "1px"
          }},

]

# Dash layout with Cytoscape component
app.layout = html.Div([

    cyto.CyLeaflet(
            id="network",
            cytoscape_props={
                "elements": elements,
                "stylesheet": cy_stylesheet,
                "autoungrabify": True,
              
            },
            leaflet_props={
                    "children": [dl.TileLayer(
                        url='https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',
                        )],
                    "maxZoom": 22,
                    "minZoom": 12

                },
            width=600,
            height=800,

        )

])

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

which results in this preview:

the dataset to reproduce the code is available here and represents a master unit (in red) with connected children nodes. My questions now are:

  1. I would like to limit the zoom to 12 (minZoom) and 22 (maxZoom) of the Leaflet map. It seems that the configuration in the code is not taken into account.
  2. I have multiple children nodes at the same location. I would like to plot them in a way that can be well represented. For example, draw markers in a spider formation. Does someone have any suggestions to achieve that?