0

I was looking to get a colorbar where the range 0 to 12000 is mapped using np.interp(x, [0, 100, 1000, 10000, 120000], [0, 0, 0.5, 1, 1]).

The idea is to get the band from 0 to 100 mapped to the same color, then map the range 10 to 1000 uniformly from the lowest color to the mid color, then 1000 to 10000 from mid color to highest color.

I want this mapping because I'm using a divergent colormap RdYlGn_r and the critical value is at 1000, and values above 10000 and 100 are kind of outliers.

So I tried to use ColorbarBase with my own subclass of Normalizer since LogNorm, Normalizer, SymLogNorm, BoundaryNorm and PowerNorm doesn't seem suited for this task.

But the plot is blank (no errors)

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

import traceback
import warnings
import sys


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

        clip=False
        result, is_scalar = self.process_value(value)


        self.autoscale_None(result)
        # Convert at least to float, without losing precision.
        (vmin,), _ = self.process_value(self.vmin)
        (vmax,), _ = self.process_value(self.vmax)


        if vmin == vmax:
            result.fill(0)   # Or should it be all masked?  Or 0.5?
        elif vmin > vmax:
            raise ValueError("minvalue must be less than or equal to maxvalue")
        else:
            if clip:
                mask = np.ma.getmask(result)
                result = np.ma.array(np.clip(result.filled(vmax), vmin, vmax),
                                     mask=mask)
            # ma division is very slow; we can take a shortcut
            resdat = result.data        
            resdat = np.interp(resdat, [0,100, 1000, 10000, 120000], [0.00, 0.00, 0.5, 1,1])

            result = np.ma.array(resdat, mask=result.mask, copy=False)


        if is_scalar:
            result = result[0]

        print 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])
cb1.set_clim(0,12000)
plt.show()

Not that I don't really want a colorbar made of discrete colors, otherwise BoundaryNorm will work just fine. I need a starting region of a discrete color, followed by gradients and a discrete color band at the end

RubenLaguna
  • 21,435
  • 13
  • 113
  • 151
  • Possible duplicate of [Matplotlib discrete colorbar](https://stackoverflow.com/questions/14777066/matplotlib-discrete-colorbar) – Yuca Aug 23 '18 at 17:10
  • @Yuca that uses BoundaryNorm so it can only be used to map integers to colors and not real numbers to colors. Also you don't get a gradient with BoundaryNorm. In my case I want bands (of the same color) and gradients – RubenLaguna Aug 23 '18 at 17:13

1 Answers1

0

I don't know if it's a bug in ColorbarBase but it seems that having a flat starting region will not work.

So mapping the range 0 to 100 to the colors 0 to 0 will not work but mapping the range 0 to 100 to color 0 to 0.0001 instead will work fine.

So by replacing the np.interp in the OP with

resdat = np.interp(resdat, [0,100, 1000, 10000, 120000], [0.00, 0.00001, 0.5, 1,1])

you get

colorbar

Note that having a flat region in the middle or in the end it's fine.

RubenLaguna
  • 21,435
  • 13
  • 113
  • 151