Legends in bar charts - BokehJS

Some days ago I made this post BokehJS Stacked Bars Chart with Legend about the possibility to put legends in bars charts using bokehjs.

Reading the documentation i found out that the class bar has a label attribute (shown below), but the BarChartOpts has no legend options.

export function bar(data: BarChartData, opts: BarChartOpts = {}): Plot {
         const column_names = data[0]
         const row_data = data.slice(1)
         const col_data = transpose(row_data)
         const labels = col_data[0].map((v) => v.toString())

Is it just lack of knowledge from my part or it is impossible to add legends to bars charts in bokehjs?

It is possible, but you will have to go outside of the bounds set by the Charts API. You can either create the whole plot user a lower level set of Bokeh constructs, or just modify the plot created with Charts API by adding a legend to it manually.

Thank you for the answer, however i couldn’t find any example of a handmade legend using the annotations/ legend.ts model. Do you know any example?

Sure: Adding annotations — Bokeh 2.4.2 Documentation

Apparently it doesn’t work:

<script>
   var plt = Bokeh.Plotting;

    var bar_data = [
        ['City', '2010 Population', '2000 Population'],
        ['NYC', 8175000, 8008000],
        ['LA', 3792000, 3694000],
        ['Chicago', 2695000, 2896000],
        ['Houston', 2099000, 1953000],
        ['Philadelphia', 1526000, 1517000],
    ];

    var p1 = Bokeh.Charts.bar(bar_data, {
        axis_number_format: "0.[00]a",
        orientation: "vertical",
        stacked: true
    });
    var item1 = new Bokeh.LegendItem({label:'label1', renderers:[p1], index:0});
    var item2 = new Bokeh.LegendItem({label:'label2', renderers:[p1], index:1});
    var legend = new Bokeh.Legend({items:[item1, item2]})
    p1.add_layout(legend)
    plt.show(p1);
</script>

Try replacing

var item1 = new Bokeh.LegendItem(...);

with

var LegendItem = Bokeh.Models('LegendItem');
var item1 = new LegendItem(...);

Same for other similar lines.

It almost worked. Here is the actual code:

 var plt = Bokeh.Plotting;

    var bar_data = [
        ['City', '2010 Population', '2000 Population'],
        ['NYC', 8175000, 8008000],
        ['LA', 3792000, 3694000],
        ['Chicago', 2695000, 2896000],
        ['Houston', 2099000, 1953000],
        ['Philadelphia', 1526000, 1517000],
    ];
    
    var p1 = Bokeh.Charts.bar(bar_data, {
        axis_number_format: "0.[00]a",
        orientation: "vertical",
        stacked: true
    });
    var LegendItem = Bokeh.Models('LegendItem');
    var Legend = Bokeh.Models('Legend');
    var item1 = new LegendItem({label:'label1', renderers:[p1], index:0});
    var item2 = new LegendItem({label:'label2', renderers:[p1], index:1});
    var legend = new Legend({items:[item1, item2]})

    p1.add_layout(legend)

    plt.show(p1);

It shows only the ‘label1’ on the top_right corner, without any colors on the legend. The ‘label2’ was not shown. I got this error:

bokeh-2.1.0.min.js:304 Uncaught (in promise) TypeError: Cannot read property ‘draw_legend’ of undefined
at g._draw_legend_items (bokeh-2.1.0.min.js:304)
at g.render (bokeh-2.1.0.min.js:304)
at P._paint_levels (bokeh-2.1.0.min.js:472)
at P.paint (bokeh-2.1.0.min.js:472)
at P.after_layout (bokeh-2.1.0.min.js:472)
at P.compute_layout (bokeh-2.1.0.min.js:421)
at P.build (bokeh-2.1.0.min.js:421)
at P.renderTo (bokeh-2.1.0.min.js:421)
at r (bokeh-2.1.0.min.js:548)
at async Object.t.add_document_standalone (bokeh-2.1.0.min.js:548)

Ah, right. Bokeh.Charts.bar returns a plot, but LegendItem accepts a list of renderers.

You will have to inspect p1.renderers for objects that are instances of GlyphRenderer, extract them, and pass the appropriate ones to the corresponding LegendItems. I think the order is stable w.r.t. the input data, so you can rely on it.

And that’s exactly why I don’t use the Charts API. :slight_smile: The bar function is just 150 lines, quite a few lines are just copied over from the Plotting API (this one I do use), and most of the lines are split into stacked and non-stacked branches. So in reality you end up writing just about 50 lines of code instead of a single call to Bokeh.Charts.bar, but you end up having the utmost flexibility possible.

Ah, and don’t forget that with Plotting you can create the legend almost automatically by passing e.g. legend_label to the glyph functions.

It worked! Thank you so much! The color order is correct. I just substituted the lines:

var item1 = new LegendItem({label:'2010 Population', renderers:[p1.renderers[0]], index:0});
var item2 = new LegendItem({label:'2000 Population', renderers:[p1.renderers[1]], index:1});