Skip to content

ColorbarBase fails to show if the first two values map to the same result #11923

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

Closed
ecerulm opened this issue Aug 23, 2018 · 4 comments
Closed

Comments

@ecerulm
Copy link

ecerulm commented Aug 23, 2018

Bug report

When ColorbarBase is used with a custom Normalizer, if the normalizer maps the first two elements of the input to the same output value then it will raise an ValueError: Both *x_transform* and *y_transform* must be 2D affine transforms

Code for reproduction

%matplotlib inline

import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np


plt.close('all')
fig,ax = plt.subplots(1,1)

class MyNorm(mpl.colors.Normalize):

    def __call__(self, value, clip=True):

        if clip is None:
            clip = self.clip

        result, is_scalar = self.process_value(value)

        fixed_result = np.linspace(0,1, len(result))
        fixed_result[1] = fixed_result[0]
        #fixed_result = np.interp(fixed_result, [0,0.0001, 1], [0,0, 1])
        result = np.ma.array(fixed_result)
        return result 
        
        
norm = MyNorm(vmax=12000, vmin=0, clip=False)
#norm = mpl.colors.Normalize(vmin=0, vmax=12000, clip=True)
cmap = plt.get_cmap('RdYlGn_r')  # From red to green
sm = mpl.cm.ScalarMappable(norm=norm, cmap=cmap)
cb1 = mpl.colorbar.ColorbarBase(ax, cmap=cmap, norm=norm, orientation='vertical',
                                ticks=[100, 1000, 10000])
plt.show()

Actual outcome

empty figure canvas

If using module://ipykernel.pylab.backend_inline then console show

# on  module://ipympl.backend_nbagg
C:\appl\Anaconda3\envs\fluidprediction27\lib\site-packages\matplotlib\colorbar.py:890: RuntimeWarning: invalid value encountered in true_divide
  z = np.take(y, i0) + (xn - np.take(b, i0)) * dy / db

# on module://ipykernel.pylab.backend_inline
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
C:\appl\Anaconda3\envs\fluidprediction27\lib\site-packages\IPython\core\formatters.pyc in __call__(self, obj)
    332                 pass
    333             else:
--> 334                 return printer(obj)
    335             # Finally look for special method names
    336             method = get_real_method(obj, self.print_method)

C:\appl\Anaconda3\envs\fluidprediction27\lib\site-packages\IPython\core\pylabtools.pyc in <lambda>(fig)
    245 
    246     if 'png' in formats:
--> 247         png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
    248     if 'retina' in formats or 'png2x' in formats:
    249         png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))

C:\appl\Anaconda3\envs\fluidprediction27\lib\site-packages\IPython\core\pylabtools.pyc in print_figure(fig, fmt, bbox_inches, **kwargs)
    129 
    130     bytes_io = BytesIO()
--> 131     fig.canvas.print_figure(bytes_io, **kw)
    132     data = bytes_io.getvalue()
    133     if fmt == 'svg':

C:\appl\Anaconda3\envs\fluidprediction27\lib\site-packages\matplotlib\backend_bases.pyc in print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, **kwargs)
   2220                 bbox_filtered = []
   2221                 for a in bbox_artists:
-> 2222                     bbox = a.get_window_extent(renderer)
   2223                     if a.get_clip_on():
   2224                         clip_box = a.get_clip_box()

C:\appl\Anaconda3\envs\fluidprediction27\lib\site-packages\matplotlib\patches.pyc in get_window_extent(self, renderer)
    569 
    570     def get_window_extent(self, renderer=None):
--> 571         return self.get_path().get_extents(self.get_transform())
    572 
    573 

C:\appl\Anaconda3\envs\fluidprediction27\lib\site-packages\matplotlib\path.pyc in get_extents(self, transform)
    531         path = self
    532         if transform is not None:
--> 533             transform = transform.frozen()
    534             if not transform.is_affine:
    535                 path = self.transformed(transform)

C:\appl\Anaconda3\envs\fluidprediction27\lib\site-packages\matplotlib\transforms.pyc in frozen(self)
   2240 
   2241     def frozen(self):
-> 2242         return blended_transform_factory(self._x.frozen(), self._y.frozen())
   2243     frozen.__doc__ = Transform.frozen.__doc__
   2244 

C:\appl\Anaconda3\envs\fluidprediction27\lib\site-packages\matplotlib\transforms.pyc in blended_transform_factory(x_transform, y_transform)
   2387     if (isinstance(x_transform, Affine2DBase)
   2388         and isinstance(y_transform, Affine2DBase)):
-> 2389         return BlendedAffine2D(x_transform, y_transform)
   2390     return BlendedGenericTransform(x_transform, y_transform)
   2391 

C:\appl\Anaconda3\envs\fluidprediction27\lib\site-packages\matplotlib\transforms.pyc in __init__(self, x_transform, y_transform, **kwargs)
   2328         is_correct = is_affine and is_separable
   2329         if not is_correct:
-> 2330             raise ValueError("Both *x_transform* and *y_transform* must be 2D "
   2331                              "affine transforms")
   2332 

ValueError: Both *x_transform* and *y_transform* must be 2D affine transforms


Expected outcome

A colorbar where the bottom two lines are the same color.

In the actual case where I encountered I have a custom normalizer in which I wanted to map certain ranges of values to a single color (instead of doing a regular gradient). It works fine unless the first two map to the same color. As an example if you replace fixed_result[1] = fixed_result[0] with fixed_result[2:200] = fixed_result[1] it works.

Matplotlib version

  • Operating system: Windows 7
  • Matplotlib version: 2.2.2
  • Matplotlib backend (print(matplotlib.get_backend())): module://ipympl.backend_nbagg and module://ipykernel.pylab.backend_inline
  • Python version: 2.7.15
  • Jupyter version (if applicable): 5.'5.0
  • Other libraries: module://ipympl.backend_nbagg

Installed via conda official

@jklymak
Copy link
Member

jklymak commented Aug 23, 2018

Any chance you have a less perverse example you could give us? The norm you gave above cares about the order of x, and the norm really shouldn't care about that (i.e. it makes the second element always be the same as the first); you should be doing that sort of thing in the data array before it goes to norm.

I assume you've seen the BoundaryNorm.

It may be possible your norm doesn't work with colorbar and or you may need to provide a locator and formatter.

@ecerulm
Copy link
Author

ecerulm commented Aug 24, 2018

This a (I believe) less contrived example:

%matplotlib inline

import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np


plt.close('all')
fig,ax = plt.subplots(1,1)

class MyNorm(mpl.colors.Normalize):

    def __call__(self, value, clip=True):

        if clip is None:
            clip = self.clip

        result, is_scalar = self.process_value(value)

        result_dat = np.interp(result.data, [0,200, 1000, 12000], [0,0.00, 0.3,1])
        result = np.ma.array(result_dat)
        return result 
        
        
norm = MyNorm(vmax=12000, vmin=0, clip=False)
#norm = mpl.colors.Normalize(vmin=0, vmax=12000, clip=True)
cmap = plt.get_cmap('RdYlGn_r')  # From red to green
sm = mpl.cm.ScalarMappable(norm=norm, cmap=cmap)
cb1 = mpl.colorbar.ColorbarBase(ax, cmap=cmap, norm=norm, orientation='vertical',
                                ticks=[100, 1000, 10000])
plt.show()

What I'm trying to achieve an effect similar to BoundaryNorm where only certain bands are "solid" the rest are gradients. It kinds of work as long as the band is not in the very beginning (although it messes up the tick locations).

EDIT: By the way , I guess you could say for the case of the band on the beginning and end the extend='both' in ColorbarBase will provide a visual effect that replaces the need for bands at those points.

@tacaswell tacaswell added this to the v3.1 milestone Aug 24, 2018
@jklymak
Copy link
Member

jklymak commented Aug 24, 2018

Thanks - in the meantime would changing the colormap to have bands be an alternative solution for you? See #11905 for a description of how to manipulate colormaps...

@jklymak
Copy link
Member

jklymak commented Mar 4, 2019

A norm needs to have an inverse defined for colorbar to work properly, and you had one, but it was the inverse for Normalize, not the norm you had written, so naturally colorbar had problems.

Secondly, you probably want to define your "y" in your norm with unique numbers,

I'm going to close because I don't think there is much we can do from our end with this.

import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np

fig,ax = plt.subplots(1,1)

class MyNorm(mpl.colors.Normalize):

    def __call__(self, value, clip=True):

        if clip is None:
            clip = self.clip

        result, is_scalar = self.process_value(value)

        result_dat = np.interp(result.data, [0,1000, 2000, 12000], [0, 0.0000001, 0.3,1])
        print(result_dat)
        result = np.ma.array(result_dat)
        print(result)
        return result

    def inverse(self, value):
        return np.interp(value, [0, 0.00000001,  0.3,1],[0, 1000, 2000, 12000]  )

norm = MyNorm(vmax=12000, vmin=0, clip=False)
#norm = mpl.colors.Normalize(vmin=0, vmax=12000, clip=True)
cmap = plt.get_cmap('RdYlGn_r')  # From red to green

sm = ax.pcolormesh(np.random.rand(3, 3)*10000, norm=norm)
cb = fig.colorbar(sm, ax=ax)

plt.show()

@jklymak jklymak closed this as completed Mar 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants