Search code examples
pythonbokeh

Show only major category in bokeh legend


I'd like to have bokeh display a legend for categorical bar chart data, but control which level of category is shown in the legend.

e.g. using the bokeh sample code below, I'd like the legend to show only the years. So "2015", "2016", "2017", instead of the current "Apples, 2015" etc.

Additionally I'm trying to hide the years on display on the x axis, so it only shows the fruits.

I've searched through bokeh's documentation for a while but can't see how to do this. I suppose I need to set the legend attribute to some sort of format string when creating the vbar but I have no idea what formats are allowed. What's the proper way to do this?

from bokeh.io import show, output_file
from bokeh.models import ColumnDataSource, FactorRange
from bokeh.plotting import figure
from bokeh.transform import factor_cmap

output_file("bars.html")

fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
years = ['2015', '2016', '2017']

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

# this creates [ ("Apples", "2015"), ("Apples", "2016"), ("Apples", "2017"), ("Pears", "2015), ... ]
x = [ (fruit, year) for fruit in fruits for year in years ]
counts = sum(zip(data['2015'], data['2016'], data['2017']), ()) # like an hstack

source = ColumnDataSource(data=dict(x=x, counts=counts))

p = figure(x_range=FactorRange(*x), plot_height=250, title="Fruit Counts by Year",
           toolbar_location=None, tools="")

palette = ["Red", "Green", "Blue"]
#p.vbar(x='x', top='counts', width=0.9, source=source)
p.vbar(x='x', top='counts', width=0.9, source=source, line_color="white",
       fill_color=factor_cmap('x', palette=palette, factors=years, start=1, end=2),
       # legend='x[0]'
       legend='x',
      )


p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xaxis.major_label_orientation = 1
p.xgrid.grid_line_color = None
p.legend.location = "top_right"

show(p)

Solution

  • If you don't want the hierarchical axis labeling, then you will need to use the method decribed in the Visual Dodge section of the Handling Categorical Data chapter of the User's Guide.

    Unless I am mistaken, the example there is exactly what you are asking:

    from bokeh.core.properties import value
    from bokeh.io import show, output_file
    from bokeh.models import ColumnDataSource
    from bokeh.plotting import figure
    from bokeh.transform import dodge
    
    output_file("dodged_bars.html")
    
    fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']
    years = ['2015', '2016', '2017']
    
    data = {'fruits' : fruits,
            '2015'   : [2, 1, 4, 3, 2, 4],
            '2016'   : [5, 3, 3, 2, 4, 6],
            '2017'   : [3, 2, 4, 4, 5, 3]}
    
    source = ColumnDataSource(data=data)
    
    p = figure(x_range=fruits, y_range=(0, 10), plot_height=250, 
               title="Fruit Counts by Year", toolbar_location=None, tools="")
    
    p.vbar(x=dodge('fruits', -0.25, range=p.x_range), top='2015', width=0.2, 
           source=source, color="#c9d9d3", legend=value("2015"))
    
    p.vbar(x=dodge('fruits',  0.0,  range=p.x_range), top='2016', width=0.2, 
           source=source, color="#718dbf", legend=value("2016"))
    
    p.vbar(x=dodge('fruits',  0.25, range=p.x_range), top='2017', width=0.2, 
           source=source, color="#e84d60", legend=value("2017"))
    
    p.x_range.range_padding = 0.1
    p.xgrid.grid_line_color = None
    p.legend.location = "top_left"
    p.legend.orientation = "horizontal"
    
    show(p)
    

    enter image description here