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?
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 LegendItem
s. 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. 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});