Hugo Alain Oliva
Hugo Alain Oliva

Reputation: 247

Bokeh Slider Stacked Bar Chart

I want to be able to only show the initial top 3 bars and then show more or less bars using the slider. I don't know if this is possible using the slider widget of bokeh or if there is a better method.

from bokeh.models.widgets import Slider
from bokeh.layouts import widgetbox, column
from bokeh.models import CustomJS, Slider


fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
years = ["2015", "2016", "2017"]
colors = ["#c9d9d3", "#718dbf", "#e84d60"]

data = {'fruits' : fruits,
        '2015'   : [2, 1, 4, 3, 2, 4],
        '2016'   : [5, 3, 4, 2, 4, 6],
        '2017'   : [3, 2, 4, 4, 5, 3]}

source = ColumnDataSource(data=data)

p = figure(x_range=fruits, plot_height=350, title="Fruit Counts by Year",
           toolbar_location=None, tools="")

p.vbar_stack(years, x='fruits', width=0.9, color=colors, source=source,
                         legend=[value(x) for x in years], name=years)

callback = CustomJS(args=dict(source = source), code="""
var data = source.data;
var start = slider.value;
var x = data['OOC_EQP'];
x_range.setv({"start":start, "end":start+10});
""")

p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xgrid.grid_line_color = None
p.axis.minor_tick_line_color = None
p.outline_line_color = None
p.legend.location = "top_left"
p.legend.orientation = "horizontal"

slider = Slider(start=1, end=6, value=3, step=1, title="Range")
callback.args["slider"] = slider

layout = column(p, widgetbox(slider))

show(layout)

Upvotes: 0

Views: 1075

Answers (1)

Aritesh
Aritesh

Reputation: 2103

I don't see a need to use javascript callback here. A native python on_change function can be used here. The function will change the x-axis range without changing any back-end data, and will be really fast -

from bokeh.models.widgets import Slider
from bokeh.layouts import widgetbox, column
from bokeh.models import Slider, ColumnDataSource
from bokeh.plotting import figure, curdoc
from bokeh.core.properties import value
from bokeh.models.ranges import FactorRange


fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
years = ["2015", "2016", "2017"]
colors = ["#c9d9d3", "#718dbf", "#e84d60"]

data = {'fruits' : fruits,
        '2015'   : [2, 1, 4, 3, 2, 4],
        '2016'   : [5, 3, 4, 2, 4, 6],
        '2017'   : [3, 2, 4, 4, 5, 3]}

source = ColumnDataSource(data=data)

## Note [0:3] for first 3 fruits only in default graph
p = figure(x_range=fruits[0:3], plot_height=350, title="Fruit Counts by Year",
           toolbar_location=None, tools="")

renderers= p.vbar_stack(years, x='fruits', width=0.9, color=colors, source=source, \
                         legend=[value(x) for x in years], name=years)


p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xgrid.grid_line_color = None
p.axis.minor_tick_line_color = None
p.outline_line_color = None
p.legend.location = "top_left"
p.legend.orientation = "horizontal"

slider = Slider(start=1, end=6, value=3, step=1, title="Range")

#function to update axis range
def update_axis(attrname, old, new):
    a=slider.value
    p.x_range.factors=fruits[0:a]

#Adding fucntion to on_change
slider.on_change('value', update_axis)
layout = column(p, widgetbox(slider))

#use curdoc to be used with bokeh server - use 'bokeh serve --show <filename>.py'
curdoc().add_root(layout)

Call this using Bokeh serve, as it need python backend to run the function

In case you want to use Javascript only, and not the bokeh server, even in that case, I will suggest to change only x_range, and not the data. See code below -

from bokeh.models.widgets import Slider
from bokeh.layouts import widgetbox, column
from bokeh.models import Slider, ColumnDataSource, CustomJS
from bokeh.plotting import figure, curdoc
from bokeh.core.properties import value
from bokeh.models.ranges import FactorRange
from bokeh.plotting import figure, output_file, show, ColumnDataSource



fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
years = ["2015", "2016", "2017"]
colors = ["#c9d9d3", "#718dbf", "#e84d60"]

data = {'fruits' : fruits,
        '2015'   : [2, 1, 4, 3, 2, 4],
        '2016'   : [5, 3, 4, 2, 4, 6],
        '2017'   : [3, 2, 4, 4, 5, 3]}

source = ColumnDataSource(data=data)

## Note [0:3] for first 3 fruits only in default graph
p = figure(x_range=fruits[0:3], plot_height=350, title="Fruit Counts by Year",
           toolbar_location=None, tools="")

renderers= p.vbar_stack(years, x='fruits', width=0.9, color=colors, source=source, \
                         legend=[value(x) for x in years], name=years)


p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xgrid.grid_line_color = None
p.axis.minor_tick_line_color = None
p.outline_line_color = None
p.legend.location = "top_left"
p.legend.orientation = "horizontal"

print(renderers[0].data_source.data['fruits'])

callback = CustomJS(args=dict(fig=p, xr=renderers[0].data_source.data['fruits']), code="""
    var A = slider.value;
    fig.x_range.factors = [];
    for (i = 0; i < A; i++) {
      fig.x_range.factors.push(xr[i])
    }
""")

p.x_range.js_on_change('factors', callback)
slider = Slider(start=1, end=6, value=3, step=1, title="Range", callback=callback)
callback.args["slider"] = slider
layout = column(p, widgetbox(slider))

output_file("ChangenumCat.html")

show(layout) 

Upvotes: 1

Related Questions