Loading graph on Mapbox is so slow

I created a graph to plot connected nodes in a map with Mapbox. There are 4000 lines to connect about 2000 nodes in an area, e.g., NY state. Computing the ‘data’ for plotting takes only one second, but loading to a browser (either Safari or Chrome on a MacBook Pro) takes more than 10 minutes, if plotted.

Are some suggestions I should try to speed up.

Do you have some example code?

Yes, following are the codes to update Mapbox plot:

import dash

import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
import pandas as pd
import plotly.graph_objs as go
from dash.dependencies import Input, Output
import os
from app import app
from utilities import unix_time_hour, dataforstorm, branchdata
from apps.glob_path import LOCAL_DATA_STORE, PRESCIENT_SOURCE_DATA

import datetime

if ‘DYNO’ in os.environ:
app_name = os.environ[‘DASH_APP_NAME’]
else:
app_name = ‘dash-mapplot’

mapbox_access_token = “pk…”

dfbus = branchdata.busnameidlatlng()
df = pd.read_csv(os.path.join(LOCAL_DATA_STORE, ‘Line_Flow_all.csv’))

global_starttime = None
default_starttime = unix_time_hour.convert_sec(df.datetime.min())
global_starttime_second = None

layout = html.Div([
dbc.Row(
dbc.Col(html.Div(id=‘to-derecho-2’), width={“size”: 8, “offset”: 1}, ),
),
dbc.Row(
dbc.Col(html.Div(id=‘to-performance-2’), width={“size”: 8, “offset”: 1}, ),
),
dbc.Row(
dbc.Col(html.Div(id=‘to-regional-2’), width={“size”: 8, “offset”: 1}, ),
),
dbc.Row(
dbc.Col(html.Div(id=‘to-overshort-2’), width={“size”: 8, “offset”: 1}, ),
),
html.Div(
[
dbc.Row(
[
dbc.Col(html.Div(id=‘show-datetime’),
style={‘textAlign’: ‘center’, ‘colore’: ‘red’}
),

            ]
        ),

        dbc.Row(
            [
                dbc.Col(width=2),
                dbc.Col(
                    dbc.Col(dcc.Graph(id="map-graph2")), width=8),
                dbc.Col(width=2)
            ]
        ),

        dbc.Row(
            [
                dbc.Col(html.Div([dcc.RangeSlider(id="day-range2", updatemode='mouseup',
                                                  min=unix_time_hour.convert_sec(df.datetime.min()),
                                                  max=unix_time_hour.convert_sec(df.datetime.max()),

                                                  marks=unix_time_hour.get_marks_from_start_end_m(
                                                      df.datetime.min(),
                                                      df.datetime.max()),

                                                  value=[unix_time_hour.convert_sec(df.datetime.min()),
                                                         unix_time_hour.convert_sec(df.datetime.max())],
                                                  pushable=0
                                                  ),

                                  ],
                                 ),
                        width={"size": 8, "offset": 2},

                        ),
            ],

        ),
        dbc.Row(
            [
                dbc.Col(html.Div(html.P('   '))),
            ]
        ),
        dbc.Row(
            [
                dbc.Col(html.Div(html.P('   '))),
            ]
        ),
        dbc.Row(
            [
                dbc.Col(html.Div('<br>', id='output-datetime-range-slider'),
                        width={"size": 8, "offset": 2},
                        ),

            ]
        ),

    ],
),
html.Div(id='intermediate-value', style={'display': 'none'})

]
)

def rounder(t):
if t is not None:
if t.minute >= 30:
return t.replace(second=0, minute=0, hour=t.hour)
else:
return t.replace(second=0, minute=0)
else:
return t

def daytimeconvertor(seconds):
return datetime.datetime.fromtimestamp(seconds + 7 * 3600)

@app.callback(
Output(‘intermediate-value’, ‘children’), [Input(“day-range2”, “value”)])
def reset_global_starttime(timedate):
return get_global_starttime(timedate[0])

@app.callback(
Output(“map-graph2”, “figure”),
[Input(“day-range2”, “value”), Input(“map-graph2”, ‘hoverData’)])
def update_graph(hours, busname):

starttime = rounder(global_starttime)
dstart = df.loc[df.datetime == str(starttime)]


dstart['text'] = "Line: " + dstart['Line'] + "<br> " + "From " + dstart['From Bus'] + " To " + dstart[
    'To Bus']
trace_1 = []
start_build_data=time.tome()

 trace_1.append
        go.Scattermapbox(hoverinfo='text',
                         lat=[dstart['From lat'], dstart['To lat'],
                         lon=[dstart['From lng'], dstart['To lng'],

                         mode="lines+markers",
                         showlegend=True,
                         line=go.scattermapbox.Line(width=4, color='red'),
                         text=dstart.text + '<br>' + "Flow (mw): " + str(dstart.Flow),
                         marker=go.scattermapbox.Marker(
                             size=10,
                             color='rgb(255,10,150)',
                             opacity=0.7
                         ),
                         name=dstart['From Bus'] + "-" + dstart['To Bus'],
                         ),
    )
layout1 = go.Layout(hovermode='closest', height=940,
                    geo=dict(projection=dict(type="equirectangular"), ),
                    margin=go.layout.Margin(l=50, r=50, b=100, t=100, pad=4),
                    mapbox={'accesstoken': mapbox_access_token, 'bearing': 1, 'center': {'lat': 34, 'lon': -116},
                            'pitch': 1, 'zoom': 6, "style": 'light'})
print('time used to build data', time.time()-start_build_data)
return {"data": trace_1, "layout": layout1}

The number of the line is 3776.
Please let me know how I can improve the performance.
Thanks.
Pengchu

We need an example that we can actually run, your code has references to functions which you have not provided.

Please also reformat the code and remove everything that is not relevant to the specific problem.

I made a simple demo with 4000 points and 4000 lines. It’s not particularly slow.

sjtrny:
Thanks for this demo. It is indeed very fast in plotting the lines. However, the lines are plated in the sequence, i.e., first point connecting to the next point. My codes specifies what two points to be connected. I modified your demo to what I want to do, plotting becomes very slow. Please take a look:

import dash
import dash_core_components as dcc
import dash_html_components as html
import plotly.graph_objs as go
from dash.dependencies import Input, Output
import pandas as pd

app = dash.Dash(name)

mapbox_access_token = “pk.eyJ1Ijoic2p0cm55IiwiYSI6ImNrMWJrOGlueTA1ZzMzbHBjeGdtOTN4MHUifQ.V20gMVX6fBu3kyWZaMpI_g”

import numpy as np

n_points = 1000
center = {‘lat’: 40.730610, ‘lon’: -73.935242}

locations_from = np.random.randn(n_points, 2) * 0.1 + np.array([center[‘lat’], center[‘lon’]])
locations_to = np.random.randn(n_points, 2) * 0.1 + np.array([center[‘lat’], center[‘lon’]])
dict_0 = {‘lat_from’: locations_from[:, 0], ‘lon_from’: locations_from[:, 1],
‘lat_to’: locations_to[:, 0], ‘lon_to’: locations_to[:, 1]}

df = pd.DataFrame(dict_0)

print(df.head())
app.layout = html.Div([
dcc.Graph(id=“map-graph”),
])

@app.callback(
Output(“map-graph”, “figure”), [Input(“map-graph”, “hoverData”)]
)
def update_graph(value):
trace = []
for i in range(n_points):
trace.append(go.Scattermapbox(
hoverinfo=‘text’,
lat=[df[‘lat_from’][i], df[‘lat_to’][i]],
lon=[df[‘lon_from’][i], df[‘lon_to’][i]],
mode=“lines+markers”,
text=i,
showlegend=False,
line=go.scattermapbox.Line(width=1, color=‘red’),
marker=go.scattermapbox.Marker(
size=10,
color=‘rgb(255,10,150)’,
opacity=0.7
),
)
)
layout = go.Layout(
hovermode=‘closest’,
height=940,
geo=dict(projection=dict(type=“equirectangular”), ),
margin=go.layout.Margin(l=50, r=50, b=100, t=100, pad=4),
mapbox={
‘accesstoken’: mapbox_access_token,
‘bearing’: 1,
‘center’: center,
‘pitch’: 1,
‘zoom’: 9,
“style”: ‘outdoors’
}
)
return {“data”: trace, ‘layout’: layout}

if name == ‘main’:
app.run_server(host=“0.0.0.0”, debug=True)

sjtrny:
The codes I implemented is similar from Plotly’s example “Lines on maps”:


Pengchu

Ok this seems like a known issue with plotly not Dash. Many traces leads to poor performance. This is probably the worst possible case because there’s a trace for each pair of points.