Building a Panel app for our Cook my Meat clone
In our previous post, we designed a thermal simulation to mimic some of the dynamics of cooking steak or roasts.
In this post, we focus on transforming the code we wrote into a smallish dashboard type of functions using panel
, a recent library designed for data science and Jupyter Notebooks.
A nice introduction to panel
can be found at this PyData Berlin 2019 talk by its lead develop Philipp Rudiger: https://www.youtube.com/watch?v=Ohr29FJjBi0.
Making a panel app¶
Let's first import our previously written functions.
from heat1D import *
And now, let's arrange everything together using some widgets and layouts. As you will see below, panel
provides a simple layouting system consisting of Rows and Columns as well as a decorator (@) system for specifying reactive plot dependencies.
import pandas as pd
import panel as pn
pn.extension()
hv.opts.defaults(hv.opts.QuadMesh(colorbar=True, tools=['hover'], cmap='viridis', responsive=True))
def make_dataframe(protein_grid):
count = pd.Series(protein_grid[:, -1]).value_counts()
count = count / count.sum() * 100
table = {}
for val in PROTEIN_STATES:
if val in count.index:
table[PROTEIN_STATES[val]] = count.loc[val]
else:
table[PROTEIN_STATES[val]] = 0
df = pd.Series(table, index=table.keys()).to_frame('Percentage')
df['Meat type'] = df.index
return df
recipes = {'four minutes each side': four_minutes_a_side,
'flip every 15 seconds': every_15_secs,
'sear then cook low': sear_then_cook_low,
'sous-vide then liquid nitrogen and searing': sous_vide_liquid_nitrogen,
'slow roast': slow_roast}
text = """# Cook my meat app (using [panel](https://panel.holoviz.org/))
Please select a recipe to see its outcome."""
selector = pn.widgets.RadioButtonGroup(name='selector',
value=list(recipes.keys())[0],
options=list(recipes.keys()))
@pn.depends(selector)
def show_plot(selector):
recipe = recipes[selector]
x, time_grid, snapshots = cook_recipe(recipe, n_snapshots=32, nx=30)
temp_grid = np.array(snapshots).T - KELVIN
protein_grid = np.array(snapshots_to_protein_state(snapshots)).T
qmt = hv.QuadMesh((time_grid, x, temp_grid),
kdims=['time', 'x'],
vdims='temperature', label=f"{selector} (temperature °C)").redim.range(temperature=(40, 80))
qmp = hv.QuadMesh((time_grid, x, protein_grid),
kdims=['time', 'x'],
vdims='protein_state', label=f"{selector} (protein state)").options(cmap='reds_r')
return pn.Row(qmt.redim.label(time='time (seconds)', x='steack thickness (mm)'),
qmp.redim.label(time='time (seconds)', x='steack thickness (mm)'), height=300, width=800)
@pn.depends(selector)
def show_table(selector):
recipe = recipes[selector]
x, time_grid, snapshots = cook_recipe(recipe, n_snapshots=32, nx=30)
protein_grid = np.array(snapshots_to_protein_state(snapshots)).T
df = make_dataframe(protein_grid)
return pn.Row(pn.Column("### Final cooking", hv.Table(df).opts(width=200)),
pn.Column("### Histogram", hv.Bars(df, kdims='Meat type', vdims='Percentage').opts(ylim=(0, 100),
width=600,
xrotation=30)),
pn.Spacer(), width=800)
widgets = pn.Column(text, selector)
app = pn.Column(widgets,
pn.Column(show_plot, show_table, width=800))
Finally, we just embed the result, meaning the app is pre-rendered, allowing some interaction without a server.
app.embed()
That's it! We've put together a simple app that allows users to explore results in an interactive fashion. If we wanted, we could host the computation to allow for more interactivity, for instance using MyBinder.
This post was entirely written using the Jupyter Notebook. Its content is BSD-licensed. You can see a static view or download this notebook with the help of nbviewer at 20200116_CookMyMeatPanelApp.ipynb.