Skip to content

Feature Request: manually set colorbar without mappable #3644

Closed

Description

@ahwillia

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:

plt.colorbar(cmap=cm.jet,min=0.0,max=1.0)

Activity

  1. added this to the milestone on Oct 14, 2014
  2. maxalbert commented on Dec 5, 2014

    @maxalbert
    Contributor

    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 im so that they reflect the new values passed to colorbar.

    I'm unsure which one is better. Any opinions?

  3. WeatherGod commented on Dec 5, 2014

    @WeatherGod
    Member

    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 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 pltimport 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 im so that they reflect the new
    values passed to colorbar.

    I'm unsure which one is better. Any opinions?

    —
    Reply to this email directly or view it on GitHub
    #3644 (comment)
    .

  4. Phillip-M-Feldman commented on Jan 16, 2016

    @Phillip-M-Feldman

    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

  5. efiring commented on Jan 16, 2016

    @efiring
    Member

    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()
  6. Phillip-M-Feldman commented on Jan 16, 2016

    @Phillip-M-Feldman

    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.

  7. Phillip-M-Feldman commented on Jan 16, 2016

    @Phillip-M-Feldman

    I've been trying to find something that I can read that explains the concept of 'mappables'. Any pointers will be appreciated.

  8. efiring commented on Jan 16, 2016

    @efiring
    Member

    Example of making a colorbar that is not tied to anything else:
    http://matplotlib.org/examples/api/colorbar_only.html
    "Mappable" refers to an Artist subclass that inherits from the ScalarMappable mixin:
    http://matplotlib.org/api/cm_api.html

  9. Phillip-M-Feldman commented on Jan 17, 2016

    @Phillip-M-Feldman

    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.

  10. efiring commented on Jan 17, 2016

    @efiring
    Member

    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, clevs in my example. You can set those to anything you like, regardless of the actual data values being contoured. In particular, you can set them to cover a smaller range, and use the extend='both' kwarg to contourf so that out-of-range values are colored with the "over" and "under" values of the colormap, and the colorbar has triangular ends to show these over and under regions.

  11. Phillip-M-Feldman commented on Jan 17, 2016

    @Phillip-M-Feldman

    This sounds perfect. I will experiment with this approach. Thanks!

  12. efiring commented on Jan 17, 2016

    @efiring
    Member

    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?

  13. ahwillia commented on Jan 17, 2016

    @ahwillia
    Author

    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 ScalarMappable is).

  14. 37 remaining items

  15. ahwillia commented on Mar 20, 2018

    @ahwillia
    Author

    While I agree that sm.set_array([]) is pretty annoying in there

    I think plt.cm.ScalarMappable is also a bit cumbersome for a new user. The idea of a colormap is very intuitive, especially when you can just pass it as a string, e.g. plt.imshow(data, cmap='viridis'). But a ScalarMappable is a bit mysterious and the docs only seem to give a very brief description.

    Couldn't this just be documented in a useful fashion?

    I'd be happy with that as well.

  16. efiring commented on Mar 20, 2018

    @efiring
    Member

    @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.
    @dopplershift, see https://matplotlib.org/gallery/images_contours_and_fields/multi_image.html#sphx-glr-gallery-images-contours-and-fields-multi-image-py.
    Also see https://matplotlib.org/tutorials/intermediate/constrainedlayout_guide.html#sphx-glr-tutorials-intermediate-constrainedlayout-guide-py, but those examples need to be modified to be correct--they need to specify the same norm and cmap for each image, as in the multi-image gallery example. As they stand, they will lead people astray. Attn @jklymak...

  17. ImportanceOfBeingErnest commented on Mar 20, 2018

    @ImportanceOfBeingErnest
    Member

    they also want the automatic construction of the colorbar axes using space stolen from a primary axes.

    @efiring This is precisely why one would use fig.colorbar() and not mpl.colorbar.ColorbarBase(). Because fig.colorbar provides the arguments ax and cax which can be used to have the colorbar either attached to an ax (steeling space as usual) or created inside a cax.

    ax = fig.add_subplot(332)
    manual_colorbar(fig, norm, cmap, ax=ax)
    

    or

    cax = fig.add_axes([....])
    manual_colorbar(fig, norm, cmap, cax=cax)
    

    The second case it pretty easily achieved with mpl.colorbar.ColorbarBase() as well, but for the first you would need to define your own gridspec or call the colorbar.make_axes method or use the axes_divider.

    @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 plt.colorbar() actually producing something without any argument in many of the examples and I was then rather relieved reading about it taking an argument, namely the ScalarMappable.

    At the end I think this can all be handled nicely in a tutorial about colormaps and colorbars.

  18. ahwillia commented on Mar 20, 2018

    @ahwillia
    Author

    except maybe the name

    Yes, this is what makes it confusing.

    At the end I think this can all be handled nicely in a tutorial about colormaps and colorbars.

    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!

  19. dopplershift commented on Mar 20, 2018

    @dopplershift
    Contributor

    @efiring I had no idea that you could give colorbar() an array of Axes. I feel like my knowledge about how to lay things out (e.g. constrained layout, grid spec) and all the things that work with it is out of date.

    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 ScalarMappable (they allude to it).

  20. jklymak commented on Mar 20, 2018

    @jklymak
    Member

    @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 😉

  21. efiring commented on Mar 20, 2018

    @efiring
    Member

    @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.

  22. ImportanceOfBeingErnest commented on Mar 20, 2018

    @ImportanceOfBeingErnest
    Member

    Would someone be able to comment on why exactly a ScalarMappable is initialized with self._A = None?

    self._A = None

    What would be the implication if that is changed to self._A = []?

  23. jklymak commented on Mar 21, 2018

    @jklymak
    Member

    Do it and run the tests!

  24. ahwillia commented on Apr 25, 2018

    @ahwillia
    Author

    Just want to add a cross-reference to: #6040

    Implementing support for multicolor lines (similar to plt.scatter) would make @efiring's suggestion to use LineCollections to circumvent the need for manual_colorbar much easier!

  25. modified the milestones: , v3.1 on Jan 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions