Bokeh: New user's guide entry: Scrollable columns

Created on 14 Dec 2017  路  3Comments  路  Source: bokeh/bokeh

Putting figures or widgets within a scrollable column can be desireable in complex layouts with lots of content. For example it may make sense to have a static header with widgets which stay in view when you scroll down the list of figures they control.

This issue bugged me for a while and when I finally stumbled over the solution in the Google Group, at first it did not work for me. So as suggested by Bryan in that thread, I would like to contribute the working example I saved here to the user's guide. (Credit goes to the user Noah in the thread for posting the solution my example is based upon.)

Bryan said:

I'm not sure about adding it to examples/app, but this seems like it might be useful information to put into the User's guide section on layouts, and also linked from the reference guide entry for css_classes. Especially if a standalone (non-server) version can be made? Then we could include the example directly in the User's Guide itself. I'd suggest a GH issue to discuss further if you are interested in pursing this.

As far as I can tell, it is not possibe to produce a standalone version of this example since index.html has to be modified. So, (how) could the example be integrated? Could it go in the "howto" examples?

noaction discussion

Most helpful comment

I can't seem to edit these old posts of mine. I hadn't realized the link became outdated, so this is the code. I'm just putting this here in case anyone stumbles across this. The issue may stay closed.

<!DOCTYPE html>
<html lang="en">
    <head>
        <style>
            div.scrollable {
                overflow: auto;
            }
        </style>
        <meta charset="utf-8">
        {{ bokeh_css }}
        {{ bokeh_js }}
    </head>
    <body>
        {{ plot_div|indent(8) }}
        {{ plot_script|indent(8) }}
    </body>
</html>
'''
Modified slider widget example to demonstrate scrollable columns.

We have a widgetbox with some widgets and a grid with two plots. Both elements
will be placed in their own scrollable columns. The plots are arranged in a
grid with a merged toolbar, and we want the toolbar to always stay visible.

To use this example, create a folder 'scroll_test' with the following structure
and paste the two files 'main.py' and 'index.html':

scroll_test
   |
   +---main.py
   +---templates
        +---index.html

Use the ``bokeh serve`` command for the directory application by executing:
    bokeh serve scroll_test
at your command prompt. Then navigate to the URL
    http://localhost:5006/scroll_test
in your browser.

'''
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row, widgetbox, column, gridplot, Spacer
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Slider
from bokeh.plotting import figure


def update_data(attrname, old, new):
    '''Update the data source of the plots'''
    # Get the current slider values
    a = amplitude.value
    b = offset.value
    w = phase.value
    k = freq.value
    # Generate the new curve
    x = np.linspace(0, 4*np.pi, N)
    y = a*np.sin(k*x + w) + b
    z = a*np.cos(k*x + w) + b
    source.data = dict(x=x, y=y, z=z)


# Set up data
N = 200
x = np.linspace(0, 4*np.pi, N)
y = np.sin(x)
z = np.cos(x)
source = ColumnDataSource(data=dict(x=x, y=y, z=z))

# Set up plot
plot1 = figure(plot_height=400, plot_width=400, title="my sine wave",
               tools="crosshair,pan,reset,save,wheel_zoom",
               x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])
plot1.line('x', 'y', source=source, line_width=3, line_alpha=0.6)

plot2 = figure(plot_height=400, plot_width=400, title="my cosine wave",
               tools="crosshair,pan,reset,save,wheel_zoom",
               x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])
plot2.line('x', 'z', source=source, line_width=3, line_alpha=0.6)

# Set up the widgets and connect them to the function 'update_data()'
offset = Slider(title="offset", value=0.0, start=-5.0, end=5.0, step=0.1)
amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0, step=0.1)
phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1, step=0.1)
inputs = widgetbox(offset, amplitude, phase, freq)

for w in inputs.children:
    w.on_change('value', update_data)

'''
This is the important part of the example!
We can make the widgetbox scrollable by putting it in a column with an assigned
css_class. That css class name is used again in the index.html file, where an
extra style (overflow: auto) is defined.
We also need to fix the boundaries of this html DIV. The scrollbar appears
when the contents are too large (i.e. an overflow occurs).
'''
col_inputs = column(inputs, sizing_mode='fixed', height=130, width=350,
                    css_classes=['scrollable'])


# Group the plots in a gridplot with a merged toolbar
grid = gridplot([[plot1], [plot2]], toolbar_location='left')
'''
The plots shall become scrollable as well, but we want the ToolbarBox to stay
in place.
The children of 'grid' are the ToolbarBox [0] and a column containing
all the plots [1]. Columns can become scrollable, so we assign this column
the same CSS class 'scrollable' we used before.
'''
grid.children[1].css_classes = ['scrollable']
grid.children[1].sizing_mode = 'fixed'
grid.children[1].height = 400
grid.children[1].width = 500

# Set up layouts and add to document
curdoc().add_root(row(col_inputs, Spacer(width=50), grid))
curdoc().title = "Scrollable Columns"

All 3 comments

I updated my example to show how it is possible in a gridplot to keep the merged ToolbarBox static and only make the column of figures scrollable. For me, this makes the whole deal even more awesome!

It also occured to me that it may be possible to create a pseudo-standalone version: If the standalone version is embedded in a webpage, the scrolling could work if the webpage itself has
<style>div.scrollable {overflow: auto;}</style> integrated into its <head>. This way the example could become part of the User's guide.

I noticed one issue with the scrolling div in a large gridplot:
If you zoom or pan a figure it often happens that the y-axis range changes in such a way that the axis needs to be redrawn. For example when the y_range [9 - 99] changes to [10 - 100] the extra digit requires more space and the axis is moved to the right. When such a redraw occurs, the scrollbar jumps to the top of the div. However, I can currently not reproduce this issue with the example I provided, so... there's that.

Edit: The difference to my own project was the use of tabs. You can reproduce the issue if you take my example, import Panel, Tabs and replace the last 3 lines with:

# Set up layouts and add to document
a_row = row(col_inputs, Spacer(width=50), grid)
tab_1 = Panel(child=a_row, title='Test')
tabs = Tabs(tabs=[tab_1])

curdoc().add_root(tabs)
curdoc().title = "Scrollable Columns"

Previous links are 404s, and I am not really sure what's being asked for anymore, so I am going to close this with noaction

I can't seem to edit these old posts of mine. I hadn't realized the link became outdated, so this is the code. I'm just putting this here in case anyone stumbles across this. The issue may stay closed.

<!DOCTYPE html>
<html lang="en">
    <head>
        <style>
            div.scrollable {
                overflow: auto;
            }
        </style>
        <meta charset="utf-8">
        {{ bokeh_css }}
        {{ bokeh_js }}
    </head>
    <body>
        {{ plot_div|indent(8) }}
        {{ plot_script|indent(8) }}
    </body>
</html>
'''
Modified slider widget example to demonstrate scrollable columns.

We have a widgetbox with some widgets and a grid with two plots. Both elements
will be placed in their own scrollable columns. The plots are arranged in a
grid with a merged toolbar, and we want the toolbar to always stay visible.

To use this example, create a folder 'scroll_test' with the following structure
and paste the two files 'main.py' and 'index.html':

scroll_test
   |
   +---main.py
   +---templates
        +---index.html

Use the ``bokeh serve`` command for the directory application by executing:
    bokeh serve scroll_test
at your command prompt. Then navigate to the URL
    http://localhost:5006/scroll_test
in your browser.

'''
import numpy as np
from bokeh.io import curdoc
from bokeh.layouts import row, widgetbox, column, gridplot, Spacer
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Slider
from bokeh.plotting import figure


def update_data(attrname, old, new):
    '''Update the data source of the plots'''
    # Get the current slider values
    a = amplitude.value
    b = offset.value
    w = phase.value
    k = freq.value
    # Generate the new curve
    x = np.linspace(0, 4*np.pi, N)
    y = a*np.sin(k*x + w) + b
    z = a*np.cos(k*x + w) + b
    source.data = dict(x=x, y=y, z=z)


# Set up data
N = 200
x = np.linspace(0, 4*np.pi, N)
y = np.sin(x)
z = np.cos(x)
source = ColumnDataSource(data=dict(x=x, y=y, z=z))

# Set up plot
plot1 = figure(plot_height=400, plot_width=400, title="my sine wave",
               tools="crosshair,pan,reset,save,wheel_zoom",
               x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])
plot1.line('x', 'y', source=source, line_width=3, line_alpha=0.6)

plot2 = figure(plot_height=400, plot_width=400, title="my cosine wave",
               tools="crosshair,pan,reset,save,wheel_zoom",
               x_range=[0, 4*np.pi], y_range=[-2.5, 2.5])
plot2.line('x', 'z', source=source, line_width=3, line_alpha=0.6)

# Set up the widgets and connect them to the function 'update_data()'
offset = Slider(title="offset", value=0.0, start=-5.0, end=5.0, step=0.1)
amplitude = Slider(title="amplitude", value=1.0, start=-5.0, end=5.0, step=0.1)
phase = Slider(title="phase", value=0.0, start=0.0, end=2*np.pi)
freq = Slider(title="frequency", value=1.0, start=0.1, end=5.1, step=0.1)
inputs = widgetbox(offset, amplitude, phase, freq)

for w in inputs.children:
    w.on_change('value', update_data)

'''
This is the important part of the example!
We can make the widgetbox scrollable by putting it in a column with an assigned
css_class. That css class name is used again in the index.html file, where an
extra style (overflow: auto) is defined.
We also need to fix the boundaries of this html DIV. The scrollbar appears
when the contents are too large (i.e. an overflow occurs).
'''
col_inputs = column(inputs, sizing_mode='fixed', height=130, width=350,
                    css_classes=['scrollable'])


# Group the plots in a gridplot with a merged toolbar
grid = gridplot([[plot1], [plot2]], toolbar_location='left')
'''
The plots shall become scrollable as well, but we want the ToolbarBox to stay
in place.
The children of 'grid' are the ToolbarBox [0] and a column containing
all the plots [1]. Columns can become scrollable, so we assign this column
the same CSS class 'scrollable' we used before.
'''
grid.children[1].css_classes = ['scrollable']
grid.children[1].sizing_mode = 'fixed'
grid.children[1].height = 400
grid.children[1].width = 500

# Set up layouts and add to document
curdoc().add_root(row(col_inputs, Spacer(width=50), grid))
curdoc().title = "Scrollable Columns"
Was this page helpful?
0 / 5 - 0 ratings

Related issues

dhirschfeld picture dhirschfeld  路  4Comments

darribas picture darribas  路  3Comments

maeglin89273 picture maeglin89273  路  3Comments

cyrusmaher picture cyrusmaher  路  3Comments

js3711 picture js3711  路  4Comments