pythonbokehinteractive

Overlay multiple lines with bokeh


How can overlay multiple lines on the same figure in bokeh?

This is what I've tried; consider the fallowing data.

import pandas as pd

df = pd.DataFrame(
    {
        "seq": list(range(5)) + list(range(5)),
        "a": ["foo"] * 5 + ["bar"] * 5,
        "b": np.concatenate(
            (np.linspace(1, 5, 5, dtype=int), np.linspace(1, 5, 5, dtype=int) + 3)
        ),
    }
)

I can draw a scatter plot like this:

cds1 = ColumnDataSource(df)

myfig = figure(y_range=(-1, 10), height=400, width=400)
myfig.scatter(
    "seq",
    "b",
    source=cds1,
    marker=factor_mark("a", ["circle", "diamond"], ["foo", "bar"]),
    color=factor_cmap("a", Category10[3], ["foo", "bar"]),
    size=10,
)

show(myfig)

enter image description here

I would like the points to be connected, but I cannot use factor_cmap with figure.line. So I tried creating views and calling one at a time like this:

foo = GroupFilter(column_name="a", group="foo")
bar = GroupFilter(column_name="a", group="bar")

myfig2 = figure(y_range=(-1, 10), height=400, width=400)
myfig2.line("seq", "b", source=cds1, view=CDSView(filter=foo), color=Category10[3][0])
myfig2.line("seq", "b", source=cds1, view=CDSView(filter=bar), color=Category10[3][1])

show(myfig2)

But then I get errors like this: ERROR:bokeh.core.validation.check:E-1024 (CDSVIEW_FILTERS_WITH_CONNECTED): CDSView filters are not compatible with glyphs with connected topology such as Line or Patch: GlyphRenderer(id='p1094', ...).

I would rather not use figure.multi_line as I would like to toggle the visibility of each line from a CustomJS callback by resetting cds1.data in JS. Any ideas how I can proceed? Or maybe there is a better way to achieve this besides resetting cds1.data?


Solution

  • You can use pandas groupby and draw a scatter and a line renderer for each group. Using the legend you can link the renderers and with the click_policy of the legend object you can define how it behaves.

    from bokeh.models import ColumnDataSource, FactorRange
    from bokeh.plotting import figure, show, output_notebook
    output_notebook()
    
    p = figure(y_range=(-1, 10), height=400, width=400)
    for name, group in df.groupby('a'):
        if name == 'foo':
            color = 'blue'
            marker = 'circle'
        else:
            color = 'orange'
            marker = 'diamond'
        p.line('seq', 'b', source=group, color=color, legend_label=name)
        p.scatter('seq', 'b', source=group, marker=marker, color=color, size=7, legend_label=name)
        
    p.legend.click_policy="hide"
    
    show(p)
    

    connected renderers