I'm creating a dashboard in PyViz panel, which uses bokeh under the hood as far as I understand. It is easy to use and I quickly get to a 90% solution, but the final layout is easier (for me) to just do with plain old CSS (grid layout or something). I can attach html ids and classes to each widget/plot/column/row etc from Python, but I have not found a way to stop bokeh from including a long string of inline css on each div it produces. I want the layout to be responsive, no hard coded width/height or such things.
I am already serving the panel code from main.ipynb
in a directory with a template/index.html
, so a solution involving templates is perfectly OK. I do not want to create the whole dashboard in many different roots that are added to the template if I don't need to, the generated html from rows and cols is perfectly fine for my purposes if I could just remove the generated inline css attributes.
I can write a Python function that traverses the panel/bokeh widget tree and modifies the widgets if that is what it takes. I can of course also traverse the DOM in Javascript and remove the style attributes from the divs as they are created, but that seems less elegant than hacking together some !important
css.
Aside: does anyone know how to stop bokeh from changing the html title attribute from Javascript, or how to set the title from panel?
An example dashboard:
This is a minimalistic dashboard without any custom styling. It produces hard-coded width and height on each div (as seen from Firefox devtools element inspector). The dashboard is left aligned and does not efficiently use the screen real estate (lots of white space to the left, the histogram does not span the same horizontal width as the row above). The layout is also not responsive when the page is resized.
import numpy
import pandas
import param
import panel
import holoviews
from IPython.display import display
panel.extension()
holoviews.extension('bokeh')
# Create syntetic dataset
N= 1000
age = numpy.random.gamma(10, 1.5, N)
defects = numpy.clip(numpy.random.chisquare(5, N) * age - 10, 0, 1e100).astype(int)
df = pandas.DataFrame({'age': age, 'defects': defects})
display(df.head(3))
# default sizing_mode
SM = 'stretch_width'
class DashboardDefinition(param.Parameterized):
age = param.Range((0, 20), (0, None), softbounds=(0, 30))
histogram = param.ObjectSelector('defects', ['defects', 'age'])
def filter_data(self, table):
return table.select(age=self.age)
@param.depends('age')
def make_scatter(self):
scatter = holoviews.Points(df, ['age', 'defects'])
selected = self.filter_data(scatter)
return panel.panel(selected, sizing_mode=SM)
@param.depends('age', 'histogram')
def make_histogram(self):
table = holoviews.Table(df, self.histogram)
table = self.filter_data(table)
frequencies, edges = numpy.histogram(table[self.histogram], 15)
hist = holoviews.Histogram((frequencies, edges))
return panel.panel(hist, sizing_mode=SM)
def layout(self):
return panel.Column(panel.Row(self.param,
self.make_scatter,
sizing_mode=SM),
self.make_histogram,
sizing_mode=SM)
dashboard = DashboardDefinition(name='Dashboard parameters')
dash_panel = dashboard.layout()
# Define the dashboard
template = """
{% extends base %}
{% block title %}Hello world{% endblock %}
{% block contents %}
<h1>My dashboard</h1>
<p>This is my currently unstyled dashboard</p>
<br>
{{ embed(roots.dash1) }}
{% endblock %}
"""
tmpl = panel.Template(template)
tmpl.add_panel('dash1', dash_panel)
tmpl.servable()
# Uncomment to preview dashboard in jupyter notebook
#dash_panel