Postpone the initialization of plotly figures

Hey everyone, I’m having some kind of dashboard initialization issue.

I have a dashboard layout defined, which contains some base html tags and dcc.Graphs - without figure (only id) for now.
And what I want to achieve, each of my figures must first query the database, download the data, adapt them to the given graph, and only then return the ready plotly figure.

What is my issue here?
In the first approach, I was loading data before dashboard initialization - it was bad because sometimes I needed to wait 20-30 seconds until the dashboard will fetch all the data from the database, and render all charts. And it’s bad user experience.

Now I am trying to achieve something like this:

  • I will have defined my dashboard layout, and at the application start it will render layout without any figures (only dcc.Graph(id=‘somechart’))
  • And in multi-threads I will try to load data, and create figures. And whenever given charts will be ready they will show up on the chart.

That’s my initial idea, but currently, I have no idea how to post-pone chart creation. I think I need here callback, but don’t know what should be my input there.

Hi @jakubpluta, I think that dcc.Loading will solve your issue.

Here’s an example in how to apply it

dbc.Col(
	dcc.Loading(id='loader',
			       type='dot', # type of loader 
			       color='#000000', 	# optional
					children=[
					           dcc.Graph(id='your_graph'), # your graph goes here
					], 
	),
),

By making the graph a child of dcc.Loading, the graph will only show when all the process is done.
You can read more about dcc.Loading here

Hope this helps!

2 Likes

Thank you @jgomes_eu

I know about dcc.Loading component, but I think that unfortunately, it won’t solve my case.

Let’s say I have dashboard layout e.g.

class Chart1:
     def __init__(self):
           self.data = self._get_data()

    def _get_data(self):
         "some SQL query"
         return pd.read_sql(.....)
   
    def figure(self):
        return px.bar(self.data)


class Chart2:
     def __init__(self):
           self.data = self._get_data()

    def _get_data(self):
         "some SQL query"
         return pd.read_sql(.....)
   
    def figure(self):
        return px.pie(self.data)



class Chart3:
     def __init__(self):
           self.data = self._get_data()

    def _get_data(self):
         "some SQL query"
         return pd.read_sql(.....)
   
    def figure(self):
        return px.line(self.data)




app = dash.Dash()

def get_layout():
       return  html.Div([
          html.Div(id='title'),
          dcc.Dropdown(['DE', 'PL', 'US'], 'US', id='country-dropdown'),
          html.Div(dcc.Graph(id='chart1'),
          html.Div(dcc.Graph(id='chart2'),
          html.Div(dcc.Graph(id='chart3'),
          html.Div(dcc.Graph(id='chart4')
      ]



@app.callback(
    Output('page-content', 'children'),
    Input('url', 'pathname'))
def display(pathname):
    if pathname == 'test': 
       return get_layout()

@app.callback(Output('chart1', 'figure'),
        Input('country-dropdown', 'value')):
def render_chart_1(country):
       chart = Chart1()       
       chart.data = data[data['country'] == country]
       return chart.figure()


@app.callback(Output('chart2', 'figure'),
        Input('country-dropdown', 'value')):
def render_chart_2(country):
       chart = Chart2()       
       chart.data = data[data['country'] == country]
       return chart.figure()


@app.callback(Output('chart3', 'figure'),
        Input('country-dropdown', 'value')):
def render_chart_3(country):
       chart = Chart3()       
       chart.data = data[data['country'] == country]
       return chart.figure()


@app.callback(Output('chart4', 'figure'),
        Input('chart4', 'id')):
def render_chart_1(id):
       chart = Chart4()
       return chart.figure()

    

Here is some very very basic example. So as you see I have a lot of Chart objects, which every single one
has its own logic of fetching data from the database and how the figure object should look like.
I want at the stage when the application is starting to render only the dashboard layout:

@app.callback(
    Output('page-content', 'children'),
    Input('url', 'pathname'))
def display(pathname):
    if pathname == 'test': 
       return get_layout()

So use will see after 1-second dashboard layout with empty chart containers (dcc.Graph)
and after that, I want that every single chart will be instantiated in a separate thread.
So for example for the first chart it will take 3 seconds to get data from DB and render it so after 3 seconds empty container dcc.Graph(‘chart1’) will re-render with proper figure (with data)
for chart 2 it will take 1 second, and the same story for others.

So in the end I don’t want user to wait till every single chart will be ready to show (data will be fetched from database),
and only after that he will see the dashboard (even if there will be a loading spinner). But I want that he will see dashboard without charts, and whenever the given chart will be ready it will show up on the dashboard.

I was looking on long_callbacks, but I would say that multiprocessing is not a need for that.
Probably there should be some way to separate dashboard initialization without figures from figures initialization.
From what I understand, the only way to do that is that every single chart needs to have seperate callback (so then it will spawn separate thread)

I hope that I explained it better now.

Hi again!
I’m really not sure I understand the problem, but that is probably me, since I’m pretty new to Dash and you seem to be way more advanced than I am :slight_smile:
I’ve been using dcc.Loading exactly the avoid the empty chart issue that you seem to mention and if the charts are accessing different data sources, I usually use different callbacks (but I do it for a matter of (personal) organization, mind you).

1 Like

I think you are on the right track. To achieve that behavior, charts should be initialized empty (wrapped in a loading component), and on load a callback should be fired for each chart (as you note, each chart should be rendered in a separate callback).

2 Likes

Also - Run your app with gunicorn and multiple processes so that the callbacks are executed in parallel (within each process). If there is separate data queries for each graph, split out each graph into its own callback for parallelization.

gunicorn app:server —workers 4
1 Like

Thank you all. It’s very helpful.

Just wanted to ask one more thing, and it’s more about what I would say “best practices” in Dash.
If I will initialize the chart as an empty dcc.Graph(id='chart1') component and I will prepare my callback which will load the data, create Plotly figure and in the end return it - if I don’t have any e.g. filtering (dropdowns etc) for my chart. What should be an Input in my callback ? Is it bad practice to just create a fake object e.g. empty div which I will use as my Input for my callback?


app.layout = html.Div([
          html.Div(id='fake-div'),
          dcc.Dropdown(['DE', 'PL', 'US'], 'US', id='country-dropdown'),
          html.Div(dcc.Graph(id='chart1'),
          html.Div(dcc.Graph(id='chart2'),
          html.Div(dcc.Graph(id='chart3'),
          html.Div(dcc.Graph(id='chart4')
      ]


@app.callback(Output('chart1', 'figure'),
        Input('fake-div', 'id')):
def render_chart_1(id):
       chart = Chart1()       
       return chart.figure()

Is it the proper way to do that? Or for this kind of case when I need a callback only for the loading chart I should use the loading component.