plotly / dash-cytoscape

Interactive network visualization in Python and Dash, powered by Cytoscape.js
https://dash.plot.ly/cytoscape
MIT License
601 stars 119 forks source link

[BUG] CyLeaflet: Updating tile layer causes map to be initially blue before pan/zoom #206

Closed emilykl closed 2 months ago

emilykl commented 8 months ago

Description

When the tile layer of a CyLeaflet component is updated via callback, the map shows initially blue before manual pan/zoom. After manual pan/zoom, the map renders normally.

This happens whether the tile layer is updated by re-instantiating the entire CyLeaflet component, or by using a callback to update just the children of the underlying Leaflet component.

Initially (after callback): image005

After zooming out then in: image006

Steps/Code to Reproduce

import dash
from dash import html, dcc, callback, Input, Output
import dash_cytoscape as cyto
import dash_leaflet as dl

CARTO_TILES = dl.TileLayer(
    url="https://{s}.basemaps.cartocdn.com/rastertiles/voyager_labels_under/{z}/{x}/{y}{r}.png",
    maxZoom = 30,
    attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors &copy; <a href="https://carto.com/attributions">CARTO</a>',
)

ELEMENTS = [
    {"data": {"id": "a", "label": "Node A", "lat": 45.519, "lon": -73.576}},
    {"data": {"id": "b", "label": "Node B", "lat": 45.521, "lon": -73.574}},
    {"data": {"id": "c", "label": "Node C", "lat": 45.520, "lon": -73.572}},
    {"data": {"id": "ab", "source": "a", "target": "b"}},
]

cyleaflet_leaflet_id= {
    "id":"cyleaflet_tiles_from_callback", 
    "component":"cyleaflet",
    "sub": "leaf",
}

def serve_layout():
    return html.Div(
        children=[
            html.Div('Tiles dropdown'),
            dcc.Dropdown(id='tiles_dropdown',
                options=[{'label': x, 'value': x} for x in ['OSM', 'CARTO']],
                value='CARTO',
            ),
            cyto.CyLeaflet(
                id="cyleaflet_tiles_from_callback",
                cytoscape_props=dict(
                    elements=ELEMENTS,
                ),
            ),
        ],
    )

app = dash.Dash(__name__)
server = app.server
app.layout = serve_layout

@callback(
    Output(cyleaflet_leaflet_id, "children"),
    Input("tiles_dropdown", "value"),
)
def update_tiles(tiles):
    if tiles == 'OSM':
        return cyto.CyLeaflet.OSM
    else:
        return CARTO_TILES

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

Versions

dash_cytoscape==1.0.0

Farkites commented 5 months ago

I've been doing some tests and I've seen that if the browser inspector is open on the side the tiles update works as expected the first 2 times (not including the initial load). If the browser inspector is open on a different window or is not opened at all I can see it failing on the first update. I haven't been able to reproduce the first behaviour by changing the window size.

https://github.com/plotly/dash-cytoscape/assets/35932204/43a2f9d4-cbfe-40a7-9035-384c834a4487

In the Network traffic I see that for each update of updateLeafBounds it first tries to load tiles that are wrong (default position in the map) and then loads the correct tiles according to the latitude and longitude, cancelling the first tiles. The defect we see here is because leaflet is not requesting the correct tiles so either the wrong tiles are loaded (for CARTO tiles) or the request for the tiles fails (for OSM tiles).

Farkites commented 4 months ago

This bug is originated in dash_leaflet, I have opened an issue here. The issue is that the URL to requests tiles is built incorrectly after the TileLayer is updated. This URL has the format 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png' where z is the zoom level, which is an integer value from 1 to the maximum allowed by the tiles provider. In the incorrect requests the URL uses the zoom value, which can have decimal numbers, instead of the zoom level, which is always an integer.