Search code examples
pythonpython-3.xbokeh

Add Band object to legend in Bokeh?


I want to add a Band to the layout in a plot. Minimal example:

df = pd.DataFrame(dict(x = np.arange(100),
                       y1 = np.random.rand(100),
                       y2 = np.random.rand(100) + 10)
                  )
p = figure(y_range = (df.y1.min(), df.y2.max()), plot_height = 400)
source = ColumnDataSource(df)
band = Band(base='x', lower='y1', upper='y2', source=source)
p.add_layout(band)
df.loc[:, "mean_line"] = (df.y1 + df.y2)/2
p.line(x = df.x, y = df.mean_line, legend = "mean")
p.legend.click_policy = "hide"
show(p)

example of band plot In the above example, when you click on "mean" in the legend then the mean line will hide itself. However, I can't find a way to hide the "Band" object.

I tried the solution suggested here e.g. li1 = LegendItem(label='red', renderers=[p.renderers[0]]) etc. except I don't think the "Band" has a renederer in the figure as it's added as a layout?

How would I be able to add the band to the legend so I can hide it as I do with the line?


Solution

  • The solutions is to use a virtical area instead of a band.

    Here is the code for your example:

    import pandas as pd
    import numpy as np
    
    from bokeh.plotting import figure, show, output_notebook
    from bokeh.models import ColumnDataSource
    
    output_notebook()
    
    df = pd.DataFrame(dict(x = np.arange(100),
                           y1 = np.random.rand(100),
                           y2 = np.random.rand(100) + 10)
                      )
    
    p = figure(y_range = (df.y1.min(), df.y2.max()), plot_height = 400)
    
    source = ColumnDataSource(df)
    # band = Band(base='x', lower='y1', upper='y2', source=source)
    # p.append(band)
    p.varea(x='x', y1='y1', y2='y2', source=source,
            fill_alpha=0.1, fill_color='yellow', legend_label = "area")
    df.loc[:, "mean_line"] = (df.y1 + df.y2)/2
    p.line(x = df.x, y = df.mean_line, legend_label = "mean")
    p.legend.click_policy = "hide"
    show(p)
    

    This is the output: Area is hideable

    If you look very carfully than you can see that the border (fine gray line) is missing. You can add this by drawing two more lines and group them as one legend item. This could look like this:

    import pandas as pd
    import numpy as np
    
    from bokeh.plotting import figure, show, output_notebook
    from bokeh.models import ColumnDataSource, Legend, LegendItem
    
    output_notebook()
    
    df = pd.DataFrame(dict(x = np.arange(100),
                           y1 = np.random.rand(100),
                           y2 = np.random.rand(100) + 10)
                      )
    
    p = figure(y_range = (df.y1.min(), df.y2.max()), plot_height = 400)
    
    source = ColumnDataSource(df)
    # band = Band(base='x', lower='y1', upper='y2', source=source)
    # p.append(band)
    my_area = []
    my_area_legend_item = []
    my_area.append(p.varea(x='x', y1='y1', y2='y2', 
                           source=source, fill_alpha=0.1, fill_color='yellow'))
    my_area.append(p.line(x ='x', y ='y1', color='gray', line_alpha=0.2, source=source))
    my_area.append(p.line(x ='x', y ='y2', color='gray', line_alpha=0.2, source=source))
    my_area_legend_item.append(LegendItem(label='area', 
                                          renderers=[my_area[n] for n in range(len(my_area))]
                                         )) 
    
    
    df.loc[:, "mean_line"] = (df.y1 + df.y2)/2
    p.line(x = df.x, y = df.mean_line, legend_label = "mean")
    
    p.add_layout(Legend(items=p.legend[0].items+my_area_legend_item, click_policy="hide"))
    show(p)
    

    Area with gray borders