Stock Explorer - Declarative API#

Before launching into the application code we will first declare some components of the app that will be shared, including the title of the app, a set of stock tickers, a function to return a dataframe given the stock ticker and the rolling mean window_size, and another function to return a plot given those same inputs:

import panel as pn
import pandas as pd
import altair as alt
import plotly.graph_objects as go

from bokeh.sampledata import stocks
from matplotlib.figure import Figure

pn.extension('plotly', 'vega', template='bootstrap')
import hvplot.pandas

tickers = ['AAPL', 'FB', 'GOOG', 'IBM', 'MSFT']

def get_df(ticker, window_size):
    df = pd.DataFrame(getattr(stocks, ticker))
    df['date'] = pd.to_datetime(df.date)
    return df.set_index('date').rolling(window=window_size).mean().reset_index()

def get_altair(ticker, window_size):
    df = get_df(ticker, window_size)
    return alt.Chart(df).mark_line().encode(x='date', y='close').properties(
        width="container", height=400
    )

def get_hvplot(ticker, window_size):
    df = get_df(ticker, window_size)
    return df.hvplot.line('date', 'close', grid=True, responsive=True, height=400)

def get_mpl(ticker, window_size):
    fig = Figure(figsize=(10, 6))
    ax = fig.subplots()
    df = get_df(ticker, window_size)
    df.plot.line('date', 'close', ax=ax)
    return fig

def get_plotly(ticker, window_size):
    df = get_df(ticker, window_size)
    return go.Scatter(x=df.date, y=df.close)

plot_fns = {
    'altair': get_altair,
	'hvplot': get_hvplot,
	'matplotlib': get_mpl,
	'plotly': get_plotly
}

This example demonstrates how APIs in Panel differ, to see the same app implemented using a different API visit:

The declarative API expresses the app entirely as a single Parameterized class with parameters to declare the inputs, rather than explicit widgets. The parameters are independent of any GUI code, which can be important for maintaining large codebases, with parameters and functionality defined separately from any GUI or panel code. Once again the depends decorator is used to express the dependencies, but in this case the dependencies are expressed as strings referencing class parameters, not parameters of widgets. The parameters and the plot method can then be laid out independently, with Panel used only for this very last step.

import param

class StockExplorer(param.Parameterized):

    backend = param.Selector(objects=plot_fns)

    ticker = param.Selector(objects=tickers)

    window_size = param.Integer(default=6, bounds=(1, 21))

    @param.depends('backend', 'ticker', 'window_size')
    def plot(self):
        return self.backend(self.ticker, self.window_size)

explorer = StockExplorer()

pn.Row(
    pn.Column(explorer.param),
    pn.panel(explorer.plot, sizing_mode='stretch_width'),
).servable()