Best way to render circles perfectly adjusted to data space

I have a figure in which I render rectlinear cells. The aspect ratio of my figure is expected not to be 1:1, or better to say a good deal of. Thus the cells are not squares.
Within some of the squares I want to render circles. These circles are supposed to appear as actual circles, not elongated ellipses, and they should be as big as possible without leaving the cell.

My first attempt was to go with going like

self.plot = figure(...
self.plot.image(...
radius = min(cell_size_x_in_dataspace, cell_size_y_in_dataspace)
self.plot.circle('x', 'y', source=self.my_source, radius=radius)

This did not work well - the size of the circle depended heavily on the aspect ratio.

So I tried some convoluted code in which I used ellipses and computed the screen space ratio of the cell. However, I for that I needed to access the inner dimensions of the plot and using code like width = self.plot.inner_width resulted in an bokeh.core.property.descriptors.UnsetValueError.

So I went and made this delayed:

def _routine_for_plotting(self):
    self.plot = figure(...
    self.plot.image(...
    ...
    self.plot.on_change('inner_width', self._create_ellipses)
    self.plot.on_change('inner_height', self._create_ellipses)

def _create_ellipses(self, attr, old, new):
    if not self._has_inner_sizes():
        return
    inner_width = self.plot.inner_width
    ...
    <do some calculations regarding the cell ratios>
    self.plot.ellipse('x', 'y', source=self.my_source, width=ellipse_width, height=ellipse_height)

def _has_inner_sizes(self) -> bool:
    try:
        width = self.plot.inner_width
        height = self.plot.inner_height
    except UnsetValueError:
        return False
    return True

Now this works, but it is not exactly clean code. I see no way to check whether the inner dimensions exist yet without either relying that inner_height is created last or doing that black magic regarding exceptions.

But I thought that maybe I made this far too complicated anyway and the latter part about checking whether the inner dimensions exist might be a XY-problem.

In any case, does anybody see a more elegant way to do this?

Just to try to simplify the problem statement: on the screen, you want circles inscribed as large as possible inside rectangles (that might be squares in data coordinates).

Unfortunately I can’t think of any especially good way to do that. I will mention that Circle glyphs (which always draw actual circles in screen space) do have a radius_dimension property that specifies whether to compute the radius (in pixels) by measuring in the x-direction or the y-direction. Ostensibly one of the directions should work to inscribe fully inside a data-space square. It’s a rather obscure and not-often used property, though, so I am not sure, for instance. that changing after the first render will trigger a re-render, in case that matters.

@Bryan:
Can show a picture that my current complicated solution produces:

Then I guess I stay with my approach of computing the ratios.

But still - is there a better way to do what I do in my _has_inner_sizes() method? I’m used to that exceptions should only ever exist if something actually went wrong, but here I use them to check whether I can access an attribute. That goes against all my programmer instincts. I’d hoped there’d be something akin to plot.inner_dimensions_exist() or even hasattr(plot, 'inner_width').

@Aziuth have you tried setting radius_dimension and using the half-width of the squares as the radius? That would compute the radius in pixels by measuring along the shorter y-direction producing a smaller circle that should fit in the cell. If your cells are always wider than they are tall, then this might just be a full solution. It’s not clear if whether cells being “wider than tall” is an invariant for your plot, or whether the aspect can vary wildly through some interaction, that would result in “tall than wide” cells at some point. If so, at a minimum you’d need to change radius_dimension whenever that occurs. But it still might just be simpler to do what you are doing now.

I’m used to that exceptions should only ever exist if something actually went wrong, but here I use them to check whether I can access an attribute.

Something has gone wrong—you’re trying to access an attribute that has not been computed yet. The fact that Bokeh is a dual-runtime library and that most of the actual work does not happen in the Python process means that some idiosyncratic usage is unavoidable. [1]

That said, more generally, it’s common in my experience for Python code to just try to do something, and catch any exception if it fails. This is referred to as the choice between “asking for permission” vs “asking for forgiveness”. The canonical example is checking for a key in a dict before accessing, or not. If you know something about the rate of “failure” then it’s actually determinable whether one or the other approach is more performant, e.g. in a tight loop. But for most devs most of the time, it’s just a matter of style.


  1. Specifically in this instance: we can’t just not-define the attributes until they are computed, since the properties are actually descriptors added by a metaclass that are part of the the automatic Python<–>JS serialization machinery and have to exist for that to function. We can’t support foo.attr == Undefined because that goes through the same descriptor protocol machinery as “getattr” and, based on experience, we want any access to an unset property to be a very loud, immediate error. So checking for the exception is what you are left with. ↩︎

@Bryan
Ah thank you, radius_dimension='min' did the trick.
With that, it also works when the aspect changes, which indeed happens in my program.

[In regards to exceptions, I guess I have some old concepts from when I wrote C++ present, since exceptions in there are costly and to be avoided unless something actually went wrong.]

1 Like