-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Feature Request: manually set colorbar without mappable #3644
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I started implementing this and nearly submitted a PR but then it occurred to me that I'm not sure how this should behave in case there is a mappable in the plot already. For example, what should the following code snippet do? import matplotlib.pyplot as plt
import matplotlib.cm as cm
im = plt.imshow(np.random.random((40, 40)), cmap=cm.coolwarm)
plt.colorbar(cmap=cm.hot, vmin=1.2, vmax=4.3) The two options I see are: (i) Ignore the existing mappable and create a colorbar that is independent of the data in the plot. (ii) Change the colormap and limits of I'm unsure which one is better. Any opinions? |
Even better, what should happen if there is already a colorbar? On Fri, Dec 5, 2014 at 12:44 PM, maxalbert notifications@github.com wrote:
|
I have not been able to find anything online that clearly explains what the term "mappable" means. If one creates something like a filled contour plot, the colors on the colorbar must correspond to the colors appearing in the contour plot. But, one might not want these colors to cover the limits of the data. An important use case is the following: One creates a group of contour plots, and wants to then create a colorbar that is common to all of the plots. It appears that there is currently no good mechanism for doing this. Phillip M. Feldman |
Here is a modification of http://matplotlib.org/examples/pylab_examples/multi_image.html. Does this do what you want? #!/usr/bin/env python
'''
Make a set of contour plots with a single colormap and colorbar.
'''
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.font_manager import FontProperties
from numpy.random import rand
cmap = plt.get_cmap('Blues')
clevs = np.arange(0, 4.001, 0.2)
Nr = 3
Nc = 2
fig = plt.figure()
figtitle = 'Multiple contour plots, same levels'
t = fig.text(0.5, 0.95, figtitle,
horizontalalignment='center',
fontproperties=FontProperties(size=16))
cax = fig.add_axes([0.2, 0.08, 0.6, 0.04])
w = 0.4
h = 0.22
contour_sets = []
for i in range(Nr):
for j in range(Nc):
pos = [0.075 + j*1.1*w, 0.18 + i*1.2*h, w, h]
ax = fig.add_axes(pos)
if i > 0:
ax.set_xticklabels([])
if j > 0:
ax.set_yticklabels([])
ax.locator_params(axis='y', nbins=5)
# Make some fake data with a range that varies
# somewhat from one plot to the next.
data = i + j + rand(10, 20)
cs = ax.contourf(data, levels=clevs, cmap=cmap)
contour_sets.append(cs)
# The colorbar can be based on any of the mappables (the ContourSet
# objects) because they are all using the same levels and cmap.
fig.colorbar(contour_sets[0], cax, orientation='horizontal')
plt.show() |
Having a common colorbar for multiple axes is part of what I was looking for, but what I'd really like is the ability to explicitly set the limits of the colorbar via arguments to the colorbar method. |
I've been trying to find something that I can read that explains the concept of 'mappables'. Any pointers will be appreciated. |
Example of making a colorbar that is not tied to anything else: |
Your example based on http://matplotlib.org/examples/pylab_examples/multi_image.html was closer to what I've been looking for. Ideally, I'd like to be able to make a colorbar that shows the mapping between data values and colors appearing in one or more contour plots, but with the limits of the colorbar not tied to the limits of the data. |
You can do that with the example I gave. For a filled contour plot, the limits of the colorbar are take from the top and bottom values of the region boundaries, |
This sounds perfect. I will experiment with this approach. Thanks! |
I'm inclined to close this; I don't support any of the suggestions made earlier in the discussion. Am I missing something? Is there an important use case that our present code can't handle adequately? |
Close if you want to -- but just to clarify: The point of the original issue was to request that the interface be simplified. Yes, there are ways to create a colorbar on e.g. line plots: sm = plt.cm.ScalarMappable(cmap=my_cmap, norm=plt.Normalize(vmin=0, vmax=1))
sm._A = []
plt.colorbar(sm) But this isn't intuitive/easy for users -- you more or less need to go to stackoverflow or dig deep into the documentation/examples to figure this out. So the proposal/feature request was to make this a one-liner and have matplotlib execute something like the above code under the hood (without the user needing to know what a |
What is the use case for a colorbar with a line plot? Is it something other than what one would do with a LineCollection, e.g., |
Well my use case was an animation where the lines were changing color over time. For example, representing flow in a network, or electricity. I wanted a colorbar on the side to show the range of the colormap I was using. So, for example, if the maximum flow allowable was plt.colorbar(cmap=cm.jet,min=0.0,max=1.0) Edit: Also, while the |
My use case is in some respects similar. I want to be able to produce a Phillip On Sun, Jan 17, 2016 at 1:25 PM, Alex Williams notifications@github.com
|
On 2016/01/17 6:13 PM, Phillip M. Feldman wrote:
It's downright bizarre. Either it is trying to do something I still |
On 2016/01/17 11:25 AM, Alex Williams wrote:
OK, I can see how an animation of a line with changing color makes the |
copy-paste of the code to get highlighting (because us youngins can not deal otherwise). """
contour_demo.py
OVERVIEW
matplotlib currently provides no good mechanism for creating a filled contour
plot with an accompanying colorbar whose limits are set independently of the
limits of the data. The need for this functionality is particularly important
when one creates either a sequence of plots or an array of subplots with a
common colorbar. It is hoped that matplotlib feature request #3644, 'manually
set colorbar without mappable', which was opened Oct 13, 2014, will eventually
lead to a clean solution.
In the meantime, this Python script demonstrates an ugly but effective solution
to the problem. The script was created with the assistance of Jim Corson at
Enthought.
AUTHOR
Phillip M. Feldman
"""
from numpy import linspace, mgrid, sqrt
from matplotlib import colors, pyplot
# In following statement, we change the background color (`facecolor`) of the
# margin area of the figure from the default dark gray to white. (Black text on
# a dark gray background gives poor contrast).
fig= pyplot.figure(figsize=(9, 7), facecolor=[1, 1, 1])
# The purpose of the following block of code is to create a "mappable object"
# and the associated colorbar range. `N_bands` is the number of color bands
# desired.
cmin, cmax= (0, 4)
N_bands= 12
Z= [[0,0],[0,0]]
# `numpy.linspace` produces an array of `num` uniformly-spaced values. Because
# linspace by default produces a sequence that includes both the initial
# (`cmin`) and final (`cmax`) points, the number the of values must be one
# greater than the number of bands:
levels= linspace(cmin, cmax, num=N_bands+1)
cbar_range= pyplot.contourf(Z, levels)
# The following statement clears the figure, but the mappable object still
# exists:
fig.clf()
# Create a single axes that (except for labels) fills the entire figure space:
axes= fig.add_subplot(111)
x, y= mgrid[0:4:100j, -3:3:200j]
z= sqrt(x**2 + y**2)
xvec= mgrid[-3:3:200j] # 200 values including -3 and 3
yvec= mgrid[0:4:100j] # 100 values including 0 and 2
axes.contourf(xvec, yvec, z, N_bands)
axes.set_xlabel('x', fontsize=14)
axes.set_ylabel('y', fontsize=14)
axes.grid()
cbar= fig.colorbar(cbar_range)
title= axes.set_title("matplotlib feature request #3644\n"
"(manually set colorbar)", fontsize= 16, y=1.02)
cbar.set_label('power')
cbar.set_ticks([0, 1, 2, 3, 4])
pyplot.show()
fig.savefig("contour_demo.png") |
Hm. You may have a point. I've just realized that On Sun, Jan 17, 2016 at 10:34 PM, Eric Firing notifications@github.com
|
I think you want to be doing something like: import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
x, y = np.mgrid[0:4:100j, -3:3:200j]
z = 2 * np.sqrt(x**2 + y**2)
xvec = np.mgrid[-3:3:200j] # 200 values including -3 and 3
yvec = np.mgrid[0:4:100j] # 100 values including 0 and 2
bands = np.linspace(0, 4, 17, endpoint=True)
cf = ax.contourf(xvec, yvec, z, bands, extend='both', cmap='viridis')
ax.set_xlabel('x', fontsize=14)
ax.set_ylabel('y', fontsize=14)
ax.grid()
cbar = fig.colorbar(cf)
cbar.set_label('power')
plt.show()
fig.savefig("contour_demo.png") |
After replacing In reading the documentation for contour/contourf, I missed the fact that Thanks! Phillip On Mon, Jan 18, 2016 at 6:17 AM, Thomas A Caswell notifications@github.com
|
See also
|
Is there a way to use contour instead of contourf to create the colorbar this way? Z= [[0,0],[0,0]] I was using it to get a bar with line markers but it doesn't work in matplotlib 2.2.0 anymore. |
import matplotlib.pyplot as plt
plt.ion()
fig, ax = plt.subplots()
Z = [[0, 1],[3, 0]]
cbar_range= ax.contour(Z, 10)
cbar= fig.colorbar(cbar_range, ax=ax) seems to be working fine for me with Matplotlib 2.2.0 from pip. I guess that the Z array with only null values is somehow causing your issue. IIRC, some dev (maybe @efiring or @jklymak) recently fixed an issue with those kind of all-null input in contour plots. |
While this was left in an unresolved state a few years ago, I'd support a PR for this because currently there's no great way to do a colorbar on a collection of filled polygons (like a choropleth)-the way the tutorial recommends (https://matplotlib.org/gallery/api/patch_collection.html#sphx-glr-gallery-api-patch-collection-py) doesn't work that great for geographic polygons so the usual suggested run around is to construct an empty scalermappable to pass into colorbar code. Being able to build it directly would be far more straightforward. |
I’d be against adding kwargs to colorbar to do this. vmin and vmax as kwargs that act only on null colorbars would be quite confusing because you know folks would try to use them on normal colorbars as well. On the other hand a new method (nullcorbar ?) makes sense to me. |
What would be to problem with people using them on normal colourbars as well? I think it would be sensible to assume that if vmin/vmax are set, then they should override the detected values. |
@ahwillia A LineCollection is a ScalarMappable (and is also much faster than multiple plot calls), so I recommend that you use it with a normal colorbar call as in this example: |
Cool thanks! I did not know about that.
…On Mar 19, 2018 10:23 AM, "Eric Firing" ***@***.***> wrote:
@ahwillia <https://github.com/ahwillia> A LineCollection is a
ScalarMappable (and is also *much* faster than multiple plot calls), so I
recommend that you use it with a normal colorbar call as in this example:
https://matplotlib.org/gallery/lines_bars_and_markers/multicolored_line.
html?highlight=linecollection
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#3644 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAm20fnAch26nciQe0C_Mkind2SZgkCuks5tf-mDgaJpZM4CuR-9>
.
|
so @phobson led me down to the solution to my problem, which is probably a general solution but also not a thing I love: world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
cmap = mpl.cm.viridis_r
norm = mpl.colors.Normalize(vmin=0, vmax=1.25e9)
fig, ax = plt.subplots()
world.plot(column='pop_est', cmap=cmap, norm=norm, ax=ax)
plt.colorbar(ax.collections[0], shrink=.60)
ax.set_aspect('equal') |
Is the philosophy of matplotlib that there should be one and only one way
to do something or are multiple solutions to the same problem ok?
Would adding something like "manual_colorbar" be feasible and not
confusing? If not I'm happy to see this closed.
…On Mar 19, 2018 12:27 PM, "hannah" ***@***.***> wrote:
so @phobson <https://github.com/phobson> led me down to the solution to
my problem, which is probably a general solution but also not a thing I
love:
world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))
cmap = mpl.cm.viridis_r
norm = mpl.colors.Normalize(vmin=0, vmax=1.25e9)
fig, ax = plt.subplots()
world.plot(column='pop_est', cmap=cmap, norm=norm, ax=ax)
plt.colorbar(ax.collections[0], shrink=.60)
ax.set_aspect('equal')
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#3644 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAm20VZyxVtv8TvUZj_MwVM4D9zyPVmxks5tgAaMgaJpZM4CuR-9>
.
|
A new feature has to be in demand and well thought out. Then someone has to see it through to a PR. I think a well thought-out PR for this would be very seriously considered. |
@ahwillia We certainly have multiple ways of doing things. The most general example is using pyplot functions versus Axes methods. The point of the conventional colorbar is to connect the primary plot to the colorbar, so that changing a cmap or norm in the former also changes it in the latter. This is good for maintaining the integrity of the figure, so we want to encourage it whenever possible. And in most cases it is possible. |
@efiring What about the case of one colorbar for multiple subplots? |
... right now one colorbar for multiple subplots is controlled by just one scalar mappable. The "multiple axes" is just a layout consideration. Not sure how else it could work.... |
If I understand correctly what people want as
While I agree that Couldn't this just be documented in a useful fashion? |
Yeah, or added to |
I think
I'd be happy with that as well. |
@ImportanceOfBeingErnest That's not exactly what they want; they also want the automatic construction of the colorbar axes using space stolen from a primary axes. I don't think your version would run--it doesn't provide an axes or a way of making one. |
@efiring This is precisely why one would use
or
The second case it pretty easily achieved with @ahwillia I don't know what would be confusing about a ScalarMappable - except maybe the name. If you create a colorbar from an image you supply the image to the colorbar function. If you have no image you need to supply something else, and that is a ScalarMapple, i.e. the image without visual representation on screen. I remember that for me it was much more confusing to see At the end I think this can all be handled nicely in a tutorial about colormaps and colorbars. |
Yes, this is what makes it confusing.
Would be happy to see this as an addition! I just hope it ends up being easy for someone to find via google. The docs are pretty dense... Thanks for the continued interest in improving this! |
@efiring I had no idea that you could give I think saying "Make a ScalarMappable" is an acceptable solution...provided we properly document that this as a straightforward solution. I myself, despite my experience, had no idea it was so easy to make one. This gap in my knowledge probably relates to the fact that none of our examples or tutorials actually ever show the manual instantiation of a |
@efiring I think I see what you mean about the norm and cmap. But I think enforcing all that would just make the examples too complicated. I've made many many plots with shared colorbars and managed to keep my colormap and vmax/vmin the same between axes w/o invoking higher level machinery 😉 |
@jklymak, I'm sure you have, but I strongly suspect that there will be users looking at your examples in that tutorial, using them as a model with too little modification, and getting the wrong result. The key is that vmin and vmax must be explicitly set to the same values in all plots, either via the corresponding kwargs in the function or via vmin and vmax kwargs in a single norm instance used in all of the plots. Otherwise, unless the image data in each panel happens to have the same range, the colorbar will be misleading for all but the plot to which it is directly connected. |
Would someone be able to comment on why exactly a matplotlib/lib/matplotlib/cm.py Line 199 in 1d73e73
What would be the implication if that is changed to |
Do it and run the tests! |
…sing a colorbar and develops a colorbar developing the colorbar was a bit tricky without a mappable object. This matplotlib issue helped: matplotlib/matplotlib#3644
…sing a colorbar and develops a colorbar developing the colorbar was a bit tricky without a mappable object. This matplotlib issue helped: matplotlib/matplotlib#3644
There are some hacks for doing this: http://stackoverflow.com/questions/8342549/matplotlib-add-colorbar-to-a-sequence-of-line-plots
But it would be nice if this was just a single line of code:
The text was updated successfully, but these errors were encountered: