How to get the width of an axis or an added layout?

When adding a legend outside the plot area, the length of the axis is automatically reduced to fit the legend. I would like to expand the figure width so that the length of the axis stays the same as without the added layout. This would be relatively straightforward if I could retrieve the current width of the either the axis or the added layout/legend, but I can’t figure out how to do that.

Example where I would like the plot section of each figure to be as wide, currently the rightmost figure looks compressed horizontally:

from bokeh.layouts import gridplot
from bokeh.models import CategoricalColorMapper, Legend
from bokeh.palettes import Category10
from bokeh.plotting import figure, show
from bokeh.sampledata.iris import flowers


color_mapper = CategoricalColorMapper(
    factors=[x for x in flowers['species'].unique()], palette=Category10[10])

p1 = figure(height=350, width=350)
p1.circle("petal_length", "sepal_length", source=flowers,
          color=dict(field='species', transform=color_mapper))

p2 = figure(height=350, width=350)
p2.add_layout(Legend(), 'right')
p2.circle("petal_length", "petal_width", source=flowers, legend_group='species',
         color=dict(field='species', transform=color_mapper))

show(gridplot([p1, p2], ncols=2))

In my actual code, the legend label length will change so I can’t just optimize the value once manually, it needs to adapt to the actual width of the legend. The only semi-solution I can think of right now is to count the characters in the text labels and expand the figure with a set amount per character, but this will be imperfect since different characters have different width. Is there a more elegant solution?

@joelostblom

Without direct access to dimensions of interest to enable adjustments to the axis length, the following is the only thing that directly comes to mind.

Consider adding a third chart, with a single circle glyph for each species in your example (to minimize the amount of additional data). Add the legend to that chart. Make the axes, grid, glyphs all invisible and minimum size so that you’re left with the appearance of a legend associated with the two graphs you actually care about.

I’d argue that this is inelegant and even back-alley surgery, but it might work.

@joelostblom

Here’s a solution that is closer to what you probably want. I based it on the following Bokeh discourse topic. https://discourse.bokeh.org/t/how-to-set-the-width-of-a-legend/2332

Basically set the label_width property in the legend constructor to something that will encompass the largest entry you expect. Expand the width of the corresponding plot to account for that label entry. I also added some padding to undo additional compression due to borders and such. If you wanted to be precise you might be able to come up with a delta based on the actual contributing factors like margins, etc. I just eyeballed it to give a starting point.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
"""

from bokeh.layouts import gridplot
from bokeh.models import CategoricalColorMapper, Legend
from bokeh.palettes import Category10
from bokeh.plotting import figure, show
from bokeh.sampledata.iris import flowers


color_mapper = CategoricalColorMapper(
    factors=[x for x in flowers['species'].unique()], palette=Category10[10])

p1 = figure(height=350, width=350)
p1.circle("petal_length", "sepal_length", source=flowers,
          color=dict(field='species', transform=color_mapper))

llpx = 100
dpx = 65
p2 = figure(height=350, width=350+llpx+dpx)
p2.add_layout(Legend(label_width=llpx), 'right')
p2.circle("petal_length", "petal_width", source=flowers, legend_group='species',
         color=dict(field='species', transform=color_mapper))

show(gridplot([p1, p2], ncols=2))

Thanks for your replies and for taking the time to include examples @_jm!

This is the issue for me, I am building a dashboard for my colleagues and the largest entry will be anything they chose to name their samples, which I can’t predict. It sounds like adding a fixed factor per character in the longest legend label (plus some constant padding as you showed) might be the best approximation for now and I will give that a go.

I just did some testing and it seems like a minimum legend pad of 70 px gives good results with empty legends (also works for colorbars where the ticklabels are 3 chars long). To those 70 px, I add around 5 px per character in the longest label and get pretty decent consistence for the plot proportion of the figure.