How to show a loading indication during computation

How can I add a loading indicator on a user interaction, while the plot is getting ready in the dashboard below?

class DataExplorer(param.Parameterized):
    smoothing_fraction = param.Number(default=0.7, bounds=(0, 1), inclusive_bounds=(True, True), step=0.05)
    
    def load_data(self):
        cache = pn.state.cache
        df = cache['data'] if 'data' in cache else load_data_from_disk()
        cache['data'] = df
        return df

    @param.depends('smoothing_fraction')
    def make_view(self):
        df = self.load_data()
        points = hv.Points(df) 
        scatter = (dynspread(datashade(points), threshold=self.smoothing_fraction) * rasterize(points, width=20, height=20, streams=[RangeXY]).apply(hv.QuadMesh))
        return scatter.opts(width=900, height=350, show_legend=False).opts(opts.QuadMesh(tools=['hover'], alpha=0, hover_alpha=0.2))

explorer = DataExplorer(name="")
dashboard = pn.Column(explorer.param, explorer.make_view)

Thank you.

Hi @Nithanaroy

You can use the panel.widgets.Progress or a Holoviews.Div to convey progress.

I’ve created the below example showcasing both.

You can find a live version in the gallery at awesome-panel.org.

Hope it helps.

"""This example was created by as response to

<a href=https://discourse.holoviz.org/t/how-to-show-a-loading-indication-during-computation/508"

target="_blank"> Discourse 508</a> <strong>How to show a loading indication during

computation</strong>. You can find a live version in the Gallery at

<a href="https://awesome-panel.org">awesome-panel.org</a>.

"""

import math

import time

import holoviews as hv

import hvplot.pandas  # pylint: disable=unused-import

import pandas as pd

import panel as pn

import param

EMPTY_DATAFRAME = pd.DataFrame(columns=["x", "y"])

EMPTY_PLOT = hv.Div("Click UPDATE to load!")

SPINNER_URL = (

   "https://github.com/MarcSkovMadsen/awesome-panel/blob/master/"

   "src/pages/gallery/dataexplorer_loading/spinner.gif?raw=true"

)

SPINNER_HTML = f"<img src='{SPINNER_URL}' style='width:100%'"

class DataExplorer(param.Parameterized):

   """The DataExplorer App illustrates a progress and loading message"""

   load_time = param.Integer(default=8, bounds=(1, 4 * 16), label="Load Time (seconds)")

   data = param.DataFrame()

   update_action = param.Action(label="UPDATE")

   def __init__(self, **params):

       super().__init__(**params)

       self.plot_pane = pn.pane.HoloViews(EMPTY_PLOT, sizing_mode="stretch_both", min_height=300)

       self.update_action = self.load_data

       self.progress_widget = pn.widgets.Progress(

           name="Progress", sizing_mode="stretch_width", value=0

       )

   def set_hv_loading_message(self, message: str):

       """Replaces the plot with a loading message"""

       message_plot = hv.Div(SPINNER_HTML + f"<p align='center'><strong>{message}<strong></p>")

       self.plot_pane.object = message_plot

   def load_data(self, _):

       """Loads the data"""

       steps = self.load_time * 4

       self.progress_widget.max = steps

       xdata = [0]

       ydata = [math.sin(0)]

       for i in range(0, steps):

           xdata.append(i / 16)

           ydata.append(math.sin(i / 16 * 2 * math.pi))

           message = f"Loading ({i+1}/{steps})"

           self.set_hv_loading_message(message)

           self.progress_widget.value = i

           time.sleep(1 / 4)

       self.data = pd.DataFrame({"x": xdata, "y": ydata})

       self.progress_widget.value = 0

   @param.depends("data", watch=True)

   def update_plot(self):

       """Updates the plot"""

       plot = self.data.hvplot.line(x="x", y="y", color="green").opts(

           responsive=True, line_width=4

       )

       self.plot_pane.object = plot

   def view(self):

       """Returns the application view"""

       return pn.Column(

           pn.pane.Markdown(__doc__),

           pn.pane.Markdown("#### Settings"),

           pn.Param(

               self,

               parameters=["load_time", "update_action"],

               show_name=False,

               sizing_mode="stretch_width",

               widgets={"update_action": {"button_type": "success"}},

           ),

           pn.pane.Markdown("#### Progress"),

           self.progress_widget,

           pn.pane.Markdown("#### Plot"),

           self.plot_pane,

           sizing_mode="stretch_both",

       )

def view():

   """Serves the app.

   Needed for inclusion to awesome-panel.org Gallery

   """

   component = DataExplorer()

   return component.view()

You can find any updated version of the code here https://github.com/MarcSkovMadsen/awesome-panel/blob/master/src/pages/gallery/dataexplorer_loading/dataexplorer_loading.py

1 Like

One add on question I have is how to show the loading message and progress during initial load?

That I have not tried yet. But being able to quickly load the application with a loading message is important I believe.

My guess is that we would have to load the data in the background (seperate thread).

@philippjfr?

Thank you Marc. This is super cool! Will checkout others in the gallery as well :slight_smile:

1 Like

To extend this thought of engaging experiences, I tried streaming visualizations with the intention to show some output to the user and progressively update it. This is something like progressive JPEG, where something is painted on the screen as early as possible during background network download. However, I hit a roadblock as Buffer started growing drastically making my implementation unusable. I feel it may be possible with Dask’s delayed parallel execution scheme but I’m not sure how to achieve this with holoviews. Any ideas or examples on how to implement this @Marc or @philippjfr ?

1 Like

Hi @Nithanaroy

I have no experience in this. But if you create a new post (not reusing this :-)) Sombody might take a look.

And please provide a small code example, screenshot or something else to make it more specific.

Sure. Thanks Mark.

1 Like

If you need to have a the plot in a row with settings on the right the hv.Div idea above does not work that well.

There is a workaround in this Github Issue for now.

1 Like