Skip to content

ENH: Add Axes.colorbar() method#31029

Open
timhoffm wants to merge 2 commits intomatplotlib:mainfrom
timhoffm:axes-colorbar
Open

ENH: Add Axes.colorbar() method#31029
timhoffm wants to merge 2 commits intomatplotlib:mainfrom
timhoffm:axes-colorbar

Conversation

@timhoffm
Copy link
Member

@timhoffm timhoffm commented Jan 24, 2026

Up to now, we have pyplot.colorbar() and Figure.colorbar().

https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.colorbar.html is convenient in that one can do

plt.imshow(data)
plt.colorbar()

and it automatically uses the current image.

With https://matplotlib.org/stable/api/_as_gen/matplotlib.figure.Figure.colorbar.html one has to explicitly pass the image, leading to somewhat bulky

im = ax.imshow(data)
fig.colorbar(im)

This PR replicates the ease of use of plt.colorbar() in the OOP interface on an Axes: While we don't have the "current image" notion. Intent is still unambiguos if there is exactly one mappable in the Axes, which is the most common case. So one can do

ax.imshow(data)
ax.colorbar()

Note also that ax.colorbar() is somewhat analogous to ax.legend(): Add an explaining context Artist, based on the information that is already in the Axes; i.e. all parameters are optional.


Reviewing: This PR consists of to commits:

  • Introducing the actual method
  • Applying it to examples

Use the examples to get a feeling how this will work in practice.

Copy link
Contributor

@anntzer anntzer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed quite extensively at #25880 (in particular #25880 (comment), which is about parameterless Figure.colorbar(), though the same arguments also apply here) I really don't like this idea. I don't think it's worth starting a new thread here to rehash the arguments, so perhaps that can be sorted out in the original thread?

@story645 story645 added the status: needs comment/discussion needs consensus on next step label Jan 24, 2026
@github-actions github-actions bot added the Documentation: examples files in galleries/examples label Jan 25, 2026
@timhoffm
Copy link
Member Author

Thanks for bringing that discussion to attention. I agree today that a parameterless fig.colorbar() brings a number of issues with it and is not the way to go. I do not think though that the same arguments hold for parameterless ax.colorbar().

Since #25880 discusses various related topics and #25880 (comment) is burried deep in, I think it's cleaner to sort out the specific question here: Why does a parameterless ax.colorbar() not have the disadvantages of a parameterless fig.colorbar() as discussed in that thread:

Argument: Removing the mappable breaks the explicit connection to the Figure/Axes that the colorbar is placed in.

I'm not saying that it has nothing to do with the figure, but rather that the figure to which it gets attached is entirely independent of the figure object on which colorbar() is called, and rather entirely determined by [...] or by the mappable('s figure) otherwise.

Reply: This is true for fig.colorbar(), but not for ax.colorbar(). Let's do a thought experiment: Assume we make an Axes.colorbar method but keep the mappable mandatory. ax.colorbar(im) would be equivalent to fig.colorbar(im, ax=ax). But ax.colorbar(im) is much more concise. It leaves out the irrelevant fig, and we would read it as "add a colorbar to ax and map im".

We could stop here. But similar to ax.legend(): We can leave out the data to be annotated and infer them from the Axes if that is unambiguous (which is "there is exactly one mappable"). If somebody draws an image in an Axes and adds a colorbar, the only sane colorbar is to map the colors of the image.

Leaving out the mappable does not break the bond, because the strong and unique bond of ax.colorbar() is "placing a colorbar next to the Axes". The mappable can be ambiguous, in which case it still needs to be supplied. But if it's unambigous, we don't have to make the user state the obvious.

Note that both colorbar() and legend() are both data annotations, and the proposed semantics and behavior match: Add this type of data annoation to the Axes. You can pass parameters to define which data should be annoated. If not given, infer the data from the Axes. - The only difference here is that it can happen for colorbar() that we cannot unambigously infer the data. There's still a similar case for legend without any artists. We warn in that case (and could do with colorbar without artist) but for multiple artists we have to error out.

Argument: Brittle API:

In isolation, you cannot know whether a call to fig.colorbar() is valid, as you need to know the entire history of fig (more specifically, how many mappables have been added to it)

Reply: This is a valid concern on figures. It is much less severe on a single Axes for two reaons:

  1. Having multiple mappables in a figure can easily happen through subplots. It is much rarer to have multiple mappables in a single Axes
  2. The code to draw a single Axes/subplot is typically close together. I'd claim that overlooking the whole history of a single Axes is regularly possible.

Note that the parameterless plt.colorbar() essentially builts on these assumptions. You find that 74k times on GitHub, which leads me to the conclusion that it works well for people.

I hope I have correctly extracted the major arguments against parameterless fig.colorbar(). Please let me know if I have missed something.

For a better code feeling and detection of pitfalls, I've gone through the examples and adapted them to ax.colorbar(), see the second commit. Overall, this works out quite nicely. IMHO readability goes up for the simple cases, and even if there are multiple mappables like in axs[0] of https://matplotlib.org/stable/gallery/images_contours_and_fields/contour_image.html
axs[0].colorbar(cset1) reads better than fig.colorbar(cset1, ax=axs[0]).

@timhoffm timhoffm force-pushed the axes-colorbar branch 2 times, most recently from c17bf6a to 4f65ab4 Compare January 25, 2026 08:54
@github-actions github-actions bot added the Documentation: API files in lib/ and doc/api label Jan 25, 2026
@timhoffm timhoffm force-pushed the axes-colorbar branch 2 times, most recently from 66e50cf to e44ca23 Compare January 25, 2026 12:16
Copy link
Member

@jklymak jklymak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd approve this - I think this is a reasonable shortcut, doesn't preclude adding a mappable.colorbar (though I'm sort of against that), and I think erroring in the case of ambiguity is a good idea. I'd suggest that cax be kept and override like it does for the figure.colorbar.

This is another way to do something - I'm a little concerned that if we replace every example with this new shortcut is a bit too aggressive - every example on the internet etc has the old grammar so it makes us look inconsistent. I also am mildly concerned that people will miss the power of the figure.colorbar method, which can position colorbars at the expense of multiple axes, which is really quite powerful.

mappable = colormapped_artists[0]

return self.figure.colorbar(
mappable, ax=self, use_gridspec=use_gridspec, **kwargs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should allow the cax keyword argument like for fig.colorbar for manual placement.

@story645
Copy link
Member

story645 commented Jan 25, 2026

I also am mildly concerned that people will miss the power of the figure.colorbar method, which can position colorbars at the expense of multiple axes, which is really quite powerful.

I think changing every one axes example to use this method could highlight the power of fig.colorbar b/c the examples are then mostly limited to showing off this functionality. It allows for a clearer separation of concerns.

I'm less worried about the multiple ways to do things mostly b/c I think this method is so intuitive (akin to the axes.legend) that I tend to forget it doesn't exist.

@anntzer
Copy link
Contributor

anntzer commented Jan 25, 2026

Argument: Removing the mappable breaks the explicit connection to the Figure/Axes that the colorbar is placed in.

I'm not saying that it has nothing to do with the figure, but rather that the figure to which it gets attached is entirely independent of the figure object on which colorbar() is called, and rather entirely determined by [...] or by the mappable('s figure) otherwise.

Reply: This is true for fig.colorbar(), but not for ax.colorbar(). Let's do a thought experiment: Assume we make an Axes.colorbar method but keep the mappable mandatory. ax.colorbar(im) would be equivalent to fig.colorbar(im, ax=ax). But ax.colorbar(im) is much more concise. It leaves out the irrelevant fig, and we would read it as "add a colorbar to ax and map im".

Certainly, we all agree that fig.colorbar(im) is an awkward API, because of the redundant information (fig == im.axes.figure, always). But I would say that (as argued in that other thread) the fix is to provide im.colorbar(), which is fully explicit, not ax.colorbar().

We could stop here. But similar to ax.legend(): We can leave out the data to be annotated and infer them from the Axes if that is unambiguous (which is "there is exactly one mappable"). If somebody draws an image in an Axes and adds a colorbar, the only sane colorbar is to map the colors of the image.

There's a fundamental difference with ax.legend(), which is that legend is intended to cover the case of multiple artists (in fact with a single artist you often don't need a legend), which is the opposite of colorbar.

Argument: Brittle API:

In isolation, you cannot know whether a call to fig.colorbar() is valid, as you need to know the entire history of fig (more specifically, how many mappables have been added to it)

Reply: This is a valid concern on figures. It is much less severe on a single Axes for two reaons:

  1. Having multiple mappables in a figure can easily happen through subplots. It is much rarer to have multiple mappables in a single Axes
  2. The code to draw a single Axes/subplot is typically close together. I'd claim that overlooking the whole history of a single Axes is regularly possible.

These points are hard to argue about, because they are based on generalities ("usually", "typically"). I will however admit your feeling is likely correct here.

Note that the parameterless plt.colorbar() essentially builts on these assumptions. You find that 74k times on GitHub, which leads me to the conclusion that it works well for people.

I don't think these statistics mean much (despite my point just above); e.g. I can find ~4x more uses of plt.plot() than uses of ax.plot, but we can hopefully all agree that the latter is better.

I hope I have correctly extracted the major arguments against parameterless fig.colorbar(). Please let me know if I have missed something.

For a better code feeling and detection of pitfalls, I've gone through the examples and adapted them to ax.colorbar(), see the second commit. Overall, this works out quite nicely. IMHO readability goes up for the simple cases, and even if there are multiple mappables like in axs[0] of https://matplotlib.org/stable/gallery/images_contours_and_fields/contour_image.html axs[0].colorbar(cset1) reads better than fig.colorbar(cset1, ax=axs[0]).

But cset1.colorbar() would certainly read better?


There's another issue specifically regarding ax.colorbar() (hinted at by @jklymak above), which is that there's actually two possible axes: the axes from which one steals (to create a new axes hosting the colorbar), or the axes where one directly draws the colorbar (cax, in the current API). Writing ax.colorbar(cax=...) would seem a bit silly (I feel) as again ax basically plays no role here. (On the other hand, im.colorbar(cax=...) works just fine -- no information is redundant.)

@story645
Copy link
Member

story645 commented Jan 25, 2026

im.colorbar()

Just as a side note but this proposal should probably be colorizer.colorbar() b/c the colorizer interface is what holds the color mapping now

@timhoffm
Copy link
Member Author

I answer the two key points in detail and reply to the other arguements briefly after that.

Why ax.colorbar() should not support cax

I have intentionally not added a cax parameter, because that would muddy the water. It prevents the "there's actually two possible axes" point laid out by @anntzer.

Additionally, there's a hierarchy of generality in colorbar placement

  1. place the colorbar next to one Axes
  2. place the colorbar next to multiple Axes - this is fig.colorbar(..., ax=[ax1, ax2])
  3. place the colorbar freely - defined through fig.colorbar(..., cax=...)

(2.) on an axes method would be really weird (ax.colorbar(..., ax=[ax1, ax2])) and is thus off the table. But then supporting (1.) and (3.) as most specific and most general approach would also a bit awkward. Therefore, I propose a narrow scoping and only support (1.)

Comparing ax.colorbar() and mappable.colorbar()

axs[0].colorbar(cset1) reads better than fig.colorbar(cset1, ax=axs[0]).

But cset1.colorbar() would certainly read better?

The common case is a single mappable in the axes. We have to optimize primarily for that:

fig, ax = plt.subplots()
im = ax.imshow(data)
im.colorbar()
ax.set_title('some image')

vs.

fig, ax = plt.subplots()
ax.imshow(data)
ax.colorbar()
ax.set_title('some image')

Reasons for the latter:

  • IMHO it's nicer to have a straight sequence of Axes method calls and not have to juggle additional variables.
  • AFAIK, there is also no precedence that any Artist apart from Axes and Figure have methods that create new artists and add them to the figure/axes.

While you could advertize the pattern

fig, ax = plt.subplots()
ax.imshow(data).colorbar()
ax.set_title('some image')

to alleviate the first point, the second still holds, and the .colorbar() addition can easily be overlooked.

ax.colorbar() also increases similarity between pyplot and Axes. We have

  • plt.legend() --> ax.legend()
  • plt.grid() --> ax.grid()
  • plt.tick_params() --> ax.tick_params()

Then

  • plt.colorbar() --> ax.colorbar()

fits right in. Admittedly, there are a few differences in rarer cases (e.g. we don't want to support cax), but I think this is still much better than to go plt.colorbar() --> ax.imshow().colorbar() or plt.colorbar() --> im = ax.imshow(); im.colorbar().


We could stop here. But similar to ax.legend(): We can leave out the data to be annotated and infer them from the Axes if that is unambiguous (which is "there is exactly one mappable"). If somebody draws an image in an Axes and adds a colorbar, the only sane colorbar is to map the colors of the image.

There's a fundamental difference with ax.legend(), which is that legend is intended to cover the case of multiple artists (in fact with a single artist you often don't need a legend), which is the opposite of colorbar.

My point is that the central idea "infer associated data from the Axes" is possible for both. The fundamental difference you pointed out results in the fact that infering is not always possible. Because the most common case (one mappable) works, there is still value in infering. More complex cases need manual statement of the mappable.


Note that the parameterless plt.colorbar() essentially builts on these assumptions. You find that 74k times on GitHub, which leads me to the conclusion that it works well for people.

I don't think these statistics mean much (despite my point just above); e.g. I can find ~4x more uses of plt.plot() than uses of ax.plot, but we can hopefully all agree that the latter is better.

This argument is not about pyplot vs Axes. Instead this tells me two things:

  • "Parameterless colorbar() is heavily used. - There's apparently a need and utility for it" If the implict data concept was awkward, they could have added their mappable explicitly.
  • People are used to parameterless colorbar(), which makes adoption easier.

I'm a little concerned that if we replace every example with this new shortcut is a bit too aggressive - every example on the internet etc has the old grammar so it makes us look inconsistent. I also am mildly concerned that people will miss the power of the figure.colorbar method, which can position colorbars at the expense of multiple axes, which is really quite powerful.

I fully agree with @story645 #31029 (comment)

In addition this is a documentation / communication topic

I will do that as a follow-up once the topic has landed.

@jklymak
Copy link
Member

jklymak commented Jan 26, 2026

Re: cax: it might be worth aiming for feature parity here rather than introducing a hierarchy between APIs.

colorbar effectively does two things: it represents the colormap of an Artist and, optionally, creates an Axes to draw that representation in. Those feel fairly orthogonal.

From that perspective, allowing ax.colorbar(cax=myaxes) seems reasonable: users can rely on the implicit machinery to obtain the correct mappable/colorizer, while still choosing to place the Axes in a bespoke way.

@timhoffm
Copy link
Member Author

So far, I have thought of ax.colorbar as the (common) special case of having the colorbar next to a single Axes. fig.colorbar is then the generalization of placing a colorbar anywhere in the figure.
Feature parity would be a different design paradim, that could be considered. Eventually, we have to do a design decision for one or the other. If we go for feature parity, would you also support ax.colorbar(ax=[ax, ax2])?

@jklymak
Copy link
Member

jklymak commented Jan 26, 2026

If we go for feature parity, would you also support ax.colorbar(ax=[ax, ax2])?

No, I wouldn't support that because it'd be confusing to have that colorbar "belong" to ax but steal space from other axes.

For sure cax means you could put the colorbar anywhere a the figure (or even on other figures??). However I think 99% of the time people are either placing it manually beside the axes, or in a convenient location inside the axes. I do this pretty frequently where I'll do fig.colorbar(im, ax=ax), and then decide to save some space and put it inside the axes (I quite often have blank regions due to sea floor bathymetry so its a useful place to annotate things), and switch to fig.colorbar(im, cax=mycbarax).

If we switch this method, I'd do ax.colorbar(), but then if I decide that I want to put it inside the axes, I'd have to now get the handle for the mappable im, and then change the colorbar call to fig.colorbar(im, cax=mycbarax). With my proposal, if I change my mind I can skip two of those steps and just do ax.colorbar(cax=mycbarax).

It's definitely not the end of the world to do the extra steps (making mycbarax is always the tricky part since its manual), but it would be a convenience to have cax available at this level, and I don't think the convenience is too muddy for the rest of the proposal here.

@timhoffm
Copy link
Member Author

timhoffm commented Jan 26, 2026

However I think 99% of the time people are either placing it manually beside the axes, or in a convenient location inside the axes.

It's definitely not the end of the world to do the extra steps (making mycbarax is always the tricky part since its manual), but it would be a convenience to have cax available at this level.

Then, wouldn't it help more to have an easier way for creating inset colorbars built into ax.colorbar? Just spitballing, but we could e.g. support a coordinate tuple in axes coordinates in the position argument, so that you could do ax.colorbar(position=[0.3, 0.07, 0.4, 0.04]) instead of https://matplotlib.org/devdocs/users/explain/axes/colorbar_placement.html#using-inset-axes.

But generally, starting with a narrow API footprint leaves us the freedom to exapand in a suitable direction later. Whether that is cax, or position or something else or keeping it narrow.

@anntzer
Copy link
Contributor

anntzer commented Jan 27, 2026

"Parameterless colorbar() is heavily used. - There's apparently a need and utility for it" If the implict data concept was awkward, they could have added their mappable explicitly.

In the same way, plt.plot() is highly used; we know that many people find the explicit axes concept awkward and prefer the implicit API, but we don't want to encourage it, because we find the implicit API awkward.

ax.colorbar(cax=mycbarax)

That looks even worse than fig.colorbar(..., cax=mycbarax) to me -- at least when fig comes first it has something to do with the colorbar (it is the figure hosting the colorbar); when ax comes first the relationship becomes really thin (it is the axes hosting a single mappable for which the colorbar is drawn).


I don't want to rehash the same arguments again and again. I do feel quite strongly about this, so I propose (instead of fully blocking the PR) to take my no as a -1 vote and require 3 positive reviews instead of 2 to merge this. (By the way, I notice that the review procedure at https://matplotlib.org/devdocs/devel/pr_guide.html#approval says nothing about PR rejections, other than in the "championed PR" case.)

@timhoffm
Copy link
Member Author

PR rejections: I believe we have so far tried to reach a consensus - in more difficult cases and to get broader opinions through discussion in the weekly meeting. Happy to do that to get broader attention and feedback to the topic.

If no consensus can be reached, I think the eventual decision lies with the respective deputy project leader per:
https://matplotlib.org/governance/governance.html#deputy-project-leaders

Deputy Project Leaders (DPL) have pre-delegated authority to make decisions within their area of responsibility. As with the PL, the DPL should strive to reach consensus before invoking their authority. Decisions by the DPL can be appealed to the PL, but the presumption is that the PL will defer to the DPL except in extraordinary circumstances. Disputes between DPLs will be settled by the PL.

(Disclosure: I believe this is in the responsibility of the API leader, which happens to be me)

@jklymak
Copy link
Member

jklymak commented Jan 27, 2026

@anntzer do you object to a method on the axes or just the fact it is implicitly picking up the mappable in this implementation?

I personally don't have any objection to a shortcut to the main method. My preference would be for it to be as close to the figure method as possible.

There is ambiguous and unambiguous implicit API. If this implicitly chose the last mappable I would complain because that is arbitrary and can lead to confusion if the user changes the order of calls in their code. It is the same concern I have with the plt interface - the user needs to track what happened before to guess what the method will do. Here, the implicitness is unambiguous, or errors if ambiguous. So my only concern about it being implicit is that it doesn't teach users how to do more complex colorbar mappings.

With respect to mappable.colorbar that seems orthogonal in that you could have both ax.colorbar and mappable.colorbar.

@anntzer
Copy link
Contributor

anntzer commented Jan 27, 2026

To be clear, I'm not going to be mad at anyone if this ends up going in with just two positive reviews. It's not the end of the world at all, I just want to register my strong opposition to it.

I have objections both against the fact that it's an Axes method, and against the fact that it tries to pick up a single mappable. Both points have already been made above, but to summarize them here:

(a) The fact that it's an axes method is awkward because of the cax case, ax.colorbar(cax=cax) looks silly to me, as explained above, and if you don't support cax then you need a bigger refactoring when switching from automatic to manual axes creation.
(b) The fact that it tries to pick up a single mappable makes code brittle, because it is difficult tor reason locally as to whether an ax.colorbar() call is valid (you need to control everything that has ever been added to the axes).

Admittedly, the single mappable selection is likely to work well in practice, and thinking about it again it should be much less problematic than an argument-less Figure.colorbar() (for which I wrote a similar argument at #25880 (comment)). It's just annoying that the "better" scoping for problem (a) is to have a Figure method, whereas (b) suggests having an Axes method... I guess this means that the API I may be heading towards is something like ax.add_colorbar_axes(location=...).colorbar() where add_colorbar_axes(...) basically behaves like the current colorbar.make_axes() and ax.colorbar() draws a colorbar on ax itself but searches ax._colorbar_info["parents"] for the single mappable... (This way, one could likewise write cax.colorbar(mappable).)

@jklymak
Copy link
Member

jklymak commented Jan 28, 2026

(a) The fact that it's an axes method is awkward because of the cax case, ax.colorbar(cax=cax) looks silly to me, as explained above, and if you don't support cax then you need a bigger refactoring when switching from automatic to manual axes creation.

I agree that aesthetically it is a bit ugly, but I'd argue it's relatively rare, and doesn't hurt anything. cax is also an escape hatch for the fig method, and its pretty ugly to have conflicting kwargs (cax and ax) in my opinion.

(b) The fact that it tries to pick up a single mappable makes code brittle, because it is difficult tor reason locally as to whether an ax.colorbar() call is valid (you need to control everything that has ever been added to the axes).

I guess I'm not 100% sure what you mean here. I guess for interactive use it become brittle in that if someone does

ax.imshow(X)
ax.colorbar()
ax.pcolor(Y, alpha=0.3)

it doesn't error out and the colorbar is for X whereas if they do

ax.imshow(X)
ax.pcolor(Y, alpha=0.3)
ax.colorbar()

they get an error? I guess that is a bit ambiguous but I'm not convinced it means we should throw out the idea of an implicit mappable.

@timhoffm
Copy link
Member Author

timhoffm commented Jan 28, 2026

Generally, order of matplotlib commands does matter:

  • colors (or generally properties) from the prop_cycle are assigned in order
  • Draw order of artists with same zorder depends on insertion order
  • legend() picks up only the labeled artists that have been defined so far - this is very the same type of behavior as here with colorbar()

Such state-dependent behavior is deeply baked in. One would need a completely different architecture to prevent this.
OTOH, it's not too bad from a UX perspective. The way we define plots is more imperative (construct a plot through a sequence of commands) rather than declarative, and programmers know that order of commands can matter.

@anntzer
Copy link
Contributor

anntzer commented Jan 28, 2026

My point regarding brittleness is the following:
Say e.g. you write a helper function that draws a mappable and adds a colorbar to the axes. This is realistic, we have such an example at https://matplotlib.org/stable/gallery/images_contours_and_fields/image_annotated_heatmap.html#using-the-helper-function-code-style Note that the function takes ax as argument (defaulting to gca()). Now, if the implementer of the function used argument-less ax.colorbar() to set up the colorbar (unlike the current function, which uses fig.colorbar(mappable), then the caller must be careful to pass in an Axes where there's no mappable yet, as the ax.colorbar() call would otherwise break -- even though this limitation wouldn't be obvious either to the function implementer (unless he has thought about this specific case) nor to the caller (after all the implementer could well have been careful). Effectively, argument-less ax.colorbar() turned out to be a footgun for the implementer (or at least a convenient trap to make his function less robust).
Now it is true that in the image_annotated_heatmap example, it would be quite unlikely to have another mappable on the same axes -- I only picked that example because it was a convenient one, already present in the matplotlib docs, that also sets up the colorbar as part of the helper function. But you can easily imagine more reasonable cases -- e.g., scatter()-plots are mappables too (regardless of whether they are monochrome or not), and you can certainly have multiples of them on a plot.


In fact, the case of scatter() makes me realize another issue with parameter-less ax.colorbar(). As it is implemented right now,

ax.scatter(x0, y0)
ax.scatter(x1, y1, c=...)
ax.colorbar()

will break, even though the user intent is clearly that there's only one mappable here (the one with c set). I'm very much against the use of scatter when not actually using it as a mappable (see discussion at #14174) but that kind of use is certainly widespread too...

@jklymak
Copy link
Member

jklymak commented Jan 28, 2026

I think there are lots of cases where a user and a helper function can conflict with each other. If I were using a helper that asked for an axes, I would not make assumptions about what would survive the helper. Conversely, if I were writing a helper, I'd try and avoid implicit assumptions if I could.

In this case, I still feel the helper function would error in a sensible way, and either the user or the author of the helper could each fix from their ends.

(The use of scatter with and without a colormapping is definitely an edge case, but again, is pretty easy to fix).

@timhoffm
Copy link
Member Author

timhoffm commented Jan 28, 2026

unsafe helper functions

The helper function is still easily fixable and can be made safe by passing the mappable ax.colorbar(im) - this is exactly equivalent to fig.colorbar(im, ax=ax). We could possibly be more explicit on when or when not to rely on the implicit mappable detection. But note that you get the error message

Axes.colormap() requires exactly one colormapped Artist in the Axes, but found 2. In ambiguous cases, please specify the mappable for the colorbar explicitly.

So even if that was missed initially, it's straight forward to recover from.

I believe, the implicit mappable detection is valuable enough for simple cases so that we shouldn't exclude it just because someone can - but doesn't have to - use is insecurely in more complex scenarios.


issues with non-mapping images and collections

I had already thought about the scatter case. Both, images and Collections can use colormapping, but don't have to. It's "opt-in" for collections by providing data (set_array). Images can also be created using RGB data, and then don't colormap.

The correct approach is to detect "is there exactly one artist that uses colormapping". This is in principle possible, we just don't have a nice API to query an Artist whether it uses colormapping. I didn't want to load the PR and discussion with that extra compexity. This is rather an edge case, and the worst that can happen is that the implicit detection complains and you have to explicity pass the mappable, which is not making things worse compared to now. We can later refine the detection, and that's actually my plan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Documentation: API files in lib/ and doc/api Documentation: examples files in galleries/examples status: needs comment/discussion needs consensus on next step

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants