| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
There was a problem hiding this comment.
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?
Sorry, something went wrong.
|
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.
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:
Reply: This is a valid concern on figures. It is much less severe on a single Axes for two reaons:
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 |
Sorry, something went wrong.
There was a problem hiding this comment.
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.
Sorry, something went wrong.
| mappable = colormapped_artists[0] | ||
|
|
||
| return self.figure.colorbar( | ||
| mappable, ax=self, use_gridspec=use_gridspec, **kwargs |
There was a problem hiding this comment.
I think we should allow the cax keyword argument like for fig.colorbar for manual placement.
Sorry, something went wrong.
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. |
Sorry, something went wrong.
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().
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.
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.
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.
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.) |
Sorry, something went wrong.
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 |
Sorry, something went wrong.
|
I answer the two key points in detail and reply to the other arguements briefly after that. Why ax.colorbar() should not support caxI 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
(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()
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:
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
Then
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().
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.
This argument is not about pyplot vs Axes. Instead this tells me two things:
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. |
Sorry, something went wrong.
|
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. |
Sorry, something went wrong.
|
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. |
Sorry, something went wrong.
|
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. |
Sorry, something went wrong.
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. |
Sorry, something went wrong.
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.
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.) |
Sorry, something went wrong.
|
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:
(Disclosure: I believe this is in the responsibility of the API leader, which happens to be me) |
Sorry, something went wrong.
|
@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. |
Sorry, something went wrong.
|
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. 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).) |
Sorry, something went wrong.
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.
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. |
Sorry, something went wrong.
|
Generally, order of matplotlib commands does matter:
Such state-dependent behavior is deeply baked in. One would need a completely different architecture to prevent this. |
Sorry, something went wrong.
|
My point regarding brittleness is the following: 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... |
Sorry, something went wrong.
|
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). |
Sorry, something went wrong.
|
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
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. |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
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
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
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
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:
Use the examples to get a feeling how this will work in practice.