Lazy Loading for Dynamic Graphs in Dash

Hi,

I’m trying to create graphs dynamically. Basically, I load some json data from a server and I want to make a LOT of graphs out of it (I want to split them up by dropdowns and pages later but for now we have at least 2,000 we are looking at. I’m planning to allow the user to search by name later).

I get the data from an API which gives it to me all at once, so I’m forced to wait for the data, but I want the user to see a blank graph of all the graphs that are being created/loading this data. I don’t want them all to be forced to load at once (making the user wait quite a long time). Is there a way I can break up this data over a loop and create these graphs dynamically with callbacks? The graph-creating process is slow enough that lazy loading them seems like a good idea.

So far I’m trying this:

app.config['suppress_callback_exceptions']=True

app.layout = html.Div([
    html.H1(children='Web Metrics for ArcGIS Usage at ----', style={
        'textAlign': 'center'
    }), 
    dcc.Tabs(id="tabs", value="online", children=[
        dcc.Tab(label='ArcGIS Online', value="online"), 
        dcc.Tab(label='ArcGIS Enterprise', value="enterprise"),
    ], className="four columns"), 
    html.Div([
        dcc.Dropdown(
            options=[
                {'label': 'Maps', 'value': 'maps'}, 
                {'label': 'Apps', 'value': 'apps'}, 
                {'label': 'Layers', 'value': 'layers'}, 
                {'label': 'Tools', 'value': 'tools'}, 
                {'label': 'Data Files', 'value': 'files'},
                {'label': 'All', 'value': 'all'}
            ], 
            value='maps'
        )
    ]
    ), 
    html.Div(id="loaded", children=html.H1("loaded"), style={'display':'none'}), 
    #dcc.Loading(id="loadingOnline", children=[html.Div(id="loading-output-online")], type="default"),
    html.Div(id='dataLoadedSignal', 
        style={'display':'none'}),
    html.Div(id="onlineGraphs", style={'display':'none'}), #Layout for adding online graphs later
    html.Div(id="nothing", style={'display':'none'})
])

@app.callback(Output('dataLoadedSignal', 'children'), 
    [Input('loaded', 'children')])
def load(something):
    print("data loaded")
    return getData("url", 
        getCreds('arcgisOnlineUsername'), getCreds('arcgisOnlinePassword'))

@app.callback(Output('nothing', 'children'),
    [Input('dataLoadedSignal', 'children')],
)
def drawLayout(data):
    #generate the layout graphs & their callbacks
    print('draw layout')
    count = 0 
    for i in data: 
        try: 
            @app.callback(Output(i['id'] + "_graph", 'children'),[
                Input('dataLoadedSignal', 'children'),
            ])
            def createGraph(data):
                return html.Div(html.H3("hello")) 
            #Give it another callback to populate the data 
            # @app.callback(Output(i['id'] + "_graph", 'children'), [
            #     Input(i['id'] + "_graph", 'children'), 
            # ])
            # def graph_update(i):
            #     print("calling graph usage") 
            #     return graphItemUsage(i, 'Date', 'Views', "Item Usage ArcGIS Online", "four columns")
        
        except (TypeError, RuntimeError) as e:     
            print("No usage data for: ")
            print(i)
            print(e)
        count += 1 
    print("layout complete")
    return html.Div()

I have no way to pass an index (i) so it knows what data to load from the data I’ve downloaded. Am I going about this wrong? And I keep getting this error:

An object was provided as children instead of a component, string, or number (or list of those). Check the children property that looks something like:

The answer is: Dash is not meant to be dynamic. Graph locations will need to be known beforehand. (Dash is great for dashboards and pre-configured layouts.) If you need to create dynamic graphs via lazy loading, you’ll need to use something else. I ended up having to write my own small web server and serving unlimited lazy-loading graphs with chartjs.