plotly / plotly/dash

Mapbox coordinates for visible extent are incorrect and change with browser zoom

Open
#1,529 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug P3
Dominant language
Python
Stars
24.4k
Forks
2.3k
Avg merge
2d 7h
Merged PRs (30d)
13

Description

Please let me know if this issue is better placed in https://github.com/plotly/plotly.py/issues !
Thank you for maintaining such a useful tool :)

Describe your context

dash                      1.18.1
dash-bootstrap-components 0.11.1
dash-core-components      1.14.1
dash-html-components      1.1.1
dash-renderer             1.8.3
dash-table                4.11.1
- OS: macOS Catalina
- Browser Reproduced in Chrome (87.0.4280.141) and Firefox (84.0.2)

Describe the bug

The mapbox._derived.coordinates properties accessed through relayoutData of a Scattermapbox figure interacts with the zoom level of a browser in an unexpected way, reporting an incorrect value for the maximum longitude visible in the map extent unless the browser zoom is set to 50%.

Expected behavior

I've tried to create a minimal reproducible example below illustrating my goal. I would like to plot some points on a map and show more detail in a table below, only for the plots that are currently visible in the map. I use a callback to access data['mapbox._derived']['coordinates'] from relayoutData to do the filtering, but have found that the bounding box reported is much larger than what is actually visible.

If I zoom out in my browser the discrepancy becomes smaller, until it looks correct at 50% zoom.

I've played with settings for the layout like autosize=True|False, explicitly setting and unsetting width and height, and fluid=True|False to try isolate what is causing the discrepancy without luck. It seems like the figure thinks the map is a lot bigger than it actually is, but gets the location of the top left corner correct. There's a reasonable chance this is not a bug and I've missed some important step or scaling setting, but couldn't find much on the docs on this topic.

Screenshots

https://user-images.githubusercontent.com/1023566/104812997-293b6480-57fe-11eb-9640-1af87e3cd1a7.mp4

Example code

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table
import pandas as pd
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import plotly.graph_objects as go

app = dash.Dash(__name__)
df = pd.DataFrame(
    {'location.latitude': [51.48717, 51.48717, 51.514085, 51.514085],
     'location.longitude': [-0.116738, -0.07087, -0.116738, -0.07087]}
)

map_graph = dcc.Graph(
    id='map_graph',
    figure=go.Figure(
        data=go.Scattermapbox(
            lat=df['location.latitude'],
            lon=df['location.longitude'],
            mode='markers',
            marker=go.scattermapbox.Marker(
                size=14
            )
        ),
        layout=go.Layout(
            showlegend=False,
            mapbox=dict(
                accesstoken=open(".mapbox_token").read(),
                bearing=0,
                center=dict(
                    lat=51.5,
                    lon=0
                ),
                pitch=0,
                zoom=10
            ),
            height=600,
            margin={'l': 0, 'r': 0, 'b': 0, 't': 0, 'pad': 4},
        )
    ) 
) 

app.layout = dbc.Container(children=[
    html.H1(children='Map example'),
    html.Hr(),
    map_graph,
    html.Div(id='my-output'),
    dash_table.DataTable(
        id='output_table',
        columns=[{"name": i, "id": i} for i in df.columns],
        data=df.to_dict('records'),
    ),
    html.Table([
        html.Tr([html.Td('Lat min:'), html.Td(id='lat_min')]),
        html.Tr([html.Td('Lat max:'), html.Td(id='lat_max')]),
        html.Tr([html.Td('Lon min:'), html.Td(id='lon_min')]),
        html.Tr([html.Td('Lon max:'), html.Td(id='lon_max')])
    ])],
    fluid=True    
)

@app.callback(
    Output(component_id='output_table', component_property='data'),
    Output(component_id='lat_min', component_property='children'),
    Output(component_id='lat_max', component_property='children'),
    Output(component_id='lon_min', component_property='children'),
    Output(component_id='lon_max', component_property='children'),
    Input(component_id='map_graph', component_property='relayoutData')
)
def map_extents_changed(data):
    try:
        print(data)
        coords = data['mapbox._derived']['coordinates']
        lon_min = coords[0][0]
        lon_max = coords[1][0]
        lat_min = coords[2][1]
        lat_max = coords[1][1]

        return(
            df.loc[(df['location.longitude'] > lon_min) & 
                      (df['location.longitude'] < lon_max) &
                      (df['location.latitude'] > lat_min) &
                      (df['location.latitude'] < lat_max)].to_dict('records'),
            lat_min,
            lat_max,
            lon_min,
            lon_max
        )
            
    except:
        return(df.to_dict('records'), 0, 0, 0, 0)

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Run the supplied Dash example with the stated dash and browser versions, then inspect relayoutData and mapbox._derived.coordinates at several browser zoom levels. Compare the reported bounds with the visible map and determine whether the discrepancy is in the example's coordinate handling or the map rendering; done means the bounds match the visible extent across browser zoom levels.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
data-visualization, frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.