bokeh layout for plot and widget arrangement

jofroe picture jofroe · Dec 15, 2016 · Viewed 9.1k times · Source

I have a specific design in mind for my bokeh app. I am using bokeh 0.12.3 and a bokeh server to keep everything in sync. Please, have a look at my mockup:

enter image description here

On the left-hand side, there is a static navigation bar, the right part of the view will consist of plots, that are manually added. The amount of plot columns on the right-hand side shall change w.r.t. window size. I am well aware of the bokeh layout documentation laying out plots and widgets, but it's a bit more complicated. This is the layout I currently have:

doc_layout = layout(children=[[column(radio_buttons,
                                      cbx_buttons,
                                      div,
                                      data_table,
                                      plot,
                                      button)]],
                    sizing_mode='scale_width')
curdoc().add_root(doc_layout)

In order to add new plots I use:

doc_layout.children[-1].children.append(plot)
# appends plot to layout children [[column(..), plot]]

But the behavior is very strange, and not at all what I actually want to achieve. New plots are added on top of the column (menu panel) instead.

Here, a short example where you can try out to see what I mean:

from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.models.sources import ColumnDataSource
from bokeh.models.widgets import Button, DataTable, TableColumn
from bokeh.layouts import layout, widgetbox, column, row

WIDTH = 200
HEIGHT = 200

def add_plot():
    p = figure(width=WIDTH, height=HEIGHT, tools=[], toolbar_location=None)
    p.line([0, 1, 2, 3, 4, 5], [0, 1, 4, 9, 16, 25])
    doc_layout.children[-1].children.append(p)

src = ColumnDataSource(dict(x=[0, 1, 2, 3, 4, 5], y=[0, 1, 4, 9, 16, 25]))
t1 = DataTable(source=src, width=WIDTH, height=HEIGHT,
               columns=[TableColumn(field='x', title='x'),
                        TableColumn(field='y', title='y')])
b = Button(label='add plot')
b.on_click(add_plot)

doc_layout = layout([[widgetbox(b, t1)]], sizing_mode='scale_width')
curdoc().add_root(doc_layout)

I am not sure what is the best solution to overcome this problem. I've tried several things, from layout() with different sizing modes, gridplot(), column()/row() in different combinations. In my previous version, where the navigation menu was placed on the top of the page instead on the left-hand side, everything seemed to work:

layout(children=[[widgetbox(radio_button, cbx_button),
                  widgetbox(data_table),
                  widgetbox(div),
                  widgetbox(button)],
                  [Spacer()]],
       sizing_mode='scale_width')

Answer

ryanjdillon picture ryanjdillon · Jul 17, 2017

You can change the last line in your callback to:

doc_layout.children[0].children[-1].children.append(p)

And change your layout to:

doc_layout = layout(sizing_mode='scale_width') 
doc_layout.children.append(row(column(widgetbox(b, t1)), column()))

But then then don't propagate exactly as you'd like. I think for that you would need to do some custom css styling.

Assuming yours is a directory format app, one option would be to make a template/index.html file where you can add a style block in the header where you could try to override css to make your plots inline-blocks or something.

<style>
  .bk-whatever-class {
    ...
  }
</style>

Use a developer tool on your browser to find the appropriate classes and toy with them. But perhaps not the best solution....

For widgets there is a css_classes attribute, where you could specify a class for that widget to use, but that unfortunately doesn't help with the plot canvases.

mycol = column(css_classes=['myclass'])