2

When plotting with hvplot from a script (not on jupyter) the usual workflow works like this:

import hvplot

hvplot.hvPlot(data)
hvplot.show()

Calling hvplot.show will cause the opening of a browser new tab to render the plot.

I want to be able to reuse the same browser tab to replot on the same figure, in a way similar of what is possible with matplotlib hold function.

I couldn't find anything on the docs about it. hvplot has support to streaming but it's too much overhead to use it outside jupyter notebook.

Does anyone knows how to do this?

fccoelho
  • 6,012
  • 10
  • 55
  • 67

1 Answers1

0

One way to update figures is to use a parameterized class and use a tornado PeriodicCallback, for details see the documentation of param, panel - param and tornado.ioloop.

from hvplot import hvPlot
import numpy as np
import pandas as pd
import param
import panel as pn
import tornado

class Plot(param.Parameterized):
    df = param.DataFrame(precedence=-1)

    def __init__(self, **params):
        super(Plot, self).__init__(**params)

        self.generate_df() # create a first random dataframe
        self.set_plot() # create a first plot with our new dataframe

        # Once the bokeh server (for serving our plot to a webpage) is started,
        # the underlying tornado server is taking control.
        # To be able to fetch (or here: generate) new data, we have to register
        # our method with the tornado ioloop.
        # To poll for new data, the PeriodicCallback can be used.
        self.cb = tornado.ioloop.PeriodicCallback(self.generate_df, 1000, .2)

        self.cb.start() # start the callback

        # self.callback.stop () ## In this example not implemented, callback runs indefinitely

    def generate_df(self):
        print('generate df')
        self.df = pd.DataFrame(np.random.randint(90,100,size=(5, 1)), columns=['1'])

    @param.depends('df', watch=True)
    def set_plot(self):
        self.plot = hvPlot(self.df)

    @param.depends('df', watch=True)
    def dashboard(self):
        return self.plot()

b = Plot(name="Plot")

app = pn.panel(b.dashboard)
server = app.show(threaded=False)

In this example the whole plot is redrawn each time the data frame is changed (e.g. zooming will be reset each time...). Further changes to the plot appearence would require additional callbacks.

If only the the data changes, you are back to Streams. Here I would switch from hvplot to holoviews, which offers in my opinion an easier interface to Streams (holoviews - Responding to Events).

The first approach is changed to

import holoviews as hv
from holoviews.streams import Stream, param
import numpy as np
import pandas as pd
import param
import panel as pn
import tornado

# returns an hv.Overlay of hv.Curves for each column 
# in a given data frame
def interactive_df(df):
    curve_list = []

    for column in df.columns.values:
        curve_list.append(hv.Curve(df[column]))
    overlay = hv.Overlay(curve_list)

    return overlay

class Plot2(param.Parameterized):

    df = param.DataFrame(precedence=-1)
    DF = Stream.define('DF', df=df) # definition of a stream class, source is df
    streamdf = DF() # instance of the stream class 'DF'

    # stream data are provided to a dynamic map
    dmap = hv.DynamicMap(interactive_df, streams=[streamdf])

    def __init__(self, **params):
        super(Plot2, self).__init__(**params)

        self.generate_df() # create a first random dataframe
        self.set_plot() # create a first plot with our new dataframe

        self.cb = tornado.ioloop.PeriodicCallback(self.generate_df, 1000, .2)
        self.cb.start() # start the callback
        # self.callback.stop () ## In this example not implemented, callback runs indefinitely

    def generate_df(self):
        self.df = pd.DataFrame(np.random.randint(90,100,size=(100, 1)), columns=['1'])

    @param.depends('df', watch=True)
    def set_plot(self):
        self.dmap.event(df=self.df)

s = Plot2(name="Plot Stream")

app = pn.panel(s.dmap)
server = app.show(threaded=False)

As only the data changes, box zoom etc can be used without any problems.

Besides: The documentation of matplotlib says, that hold is deprecated.

mdk
  • 398
  • 2
  • 8