Categorical group separator spans

I have a horizontal bar chart with nested categories where each bar is an interval. The data is added dynamically, so new categories and sub-categories can be added that did not exist in the original data. Since the intervals can be spaced very far apart, it is sometimes unclear at a glance which top-level category a bar belongs to.

It would be helpful to be able to draw the equivalent of a span at the same vertical location as the group separators. This would help to more clearly separate the interval bars belonging to different groups.

I have tried drawing spans, but I am not certain how this would work since the categories are dynamic and can be added (and visibility toggled) throughout the lifetime of the chart. I am certainly open to workarounds that accomplish something similar!

Attached is a plot with dummy data showing the lines I was hoping to draw in red.

I think you can use Whisker annotation where you can use the dimension argument in order to have it horizontal. Also, the Whisker annotation supports source hence you just update the source when the data is updated.
In order to use the Whisker annotation you need to specify lowerand upper (in you case start and end of the x_range) that the separator should cover.
You also need to offset the Whisker in order to place it at the group separator. You can read about offset for factor_range in the documentation. I am using the range property group_padding in order to calculate the offset. For my example it gives the following

from bokeh.models import ColumnDataSource, FactorRange, Whisker
from bokeh.plotting import figure, save
from bokeh.transform import dodge

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), height=350, title="Fruit Counts by Year",
           toolbar_location=None, tools="")

p.vbar(x='x', top='counts', width=0.9, source=source)
p.y_range.start = 0
p.x_range.range_padding = 0.1
p.xaxis.major_label_orientation = 1
p.xgrid.grid_line_color = None

grp_pad = p.x_range.group_padding
sep_cat = [(f, y, 1+(grp_pad-1.0)/2.0) for (f, y) in x[0:-1] if y == '2017']

src_sep = ColumnDataSource(
    data = {
        "base": sep_cat,
        "lower": [0]*len(sep_cat),
        "upper": [max(counts)*1.05]*len(sep_cat)
    })
sep = Whisker(
    base="base", upper="upper", lower="lower", source=src_sep,
    level="annotation", line_width=2, line_color = "red",
    dimension = "height", upper_head=None, lower_head=None
    )

p.add_layout(sep)

save(p)
2 Likes

A Span annotation may also work for this purpose, at least it’s location property says that categorical factors are permissible (but I have not personally tried).

@Jonas_Grave_Kristens thank you so much! I haven’t gotten a chance to test it yet but I think this would work for me.

@Bryan Spans are essentially what I am looking for in terms of display features, but I need a variable number of them at variable intervals. From what I understand the Whisker annotation can be based on a data source and be conveniently updated as that source changes, but it looks like Spans are configured to work with a single location value rather than an input data source. I could be missing something though - I am new to Bokeh :slight_smile:

FYI the next release (5-6 weeks away) will have new vpsan and hspan glyphs that can draw multiple spans at once:

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.