2

Is there a simple way to form a new colormap by stacking together two existing ones?

What I'm trying to achieve is to make yet another color-coded scatter plot, where the color-mapped variable varies from large negative to large positive values, and I'd like to tone down the values around zero --- basically, I'd like to be able to pick colors from a stock colormap (say, cm.Blues_r) for negative values of the color-mapped variable, and from a different one (say, cm.Oranges) for positive values of that variable.

ev-br
  • 24,968
  • 9
  • 65
  • 78

2 Answers2

1

This isn't tested, but as a first pass I would try making a simple sub-class of colors.Colormap.

class split_cmap(colors.Colormap):
    def __init__(self, cmap_a, cmap_b, split=.5):
        '''Makes a split color map cmap_a is the low range, 
           cmap_b is the high range
           split is where to break the range
        '''
        self.cmap_a, self.cmap_b = cmap_a, cmap_b
        self.split = split

    def __call__(self, v):
        if v < self.split:
            return self.cmap_a(v) 
            # or you might want to use v / self.split
        else:
            return self.cmap_b(v) 
            # or you might want to use (v - self.split) / (1 - self.split)

    def set_bad(self,*args, **kwargs):
        self.cmap_a.set_bad(*args, **kwargs)
        self.cmap_b.set_bad(*args, **kwargs)

    def set_over(self, *args, **kwargs):
        self.cmap_a.set_over(*args, **kwargs) # not really needed
        self.cmap_b.set_over(*args, **kwargs)

    def set_under(self, *args, **kwargs):
        self.cmap_a.set_under(*args, **kwargs)
        self.cmap_b.set_under(*args, **kwargs) # not really needed

    def is_gray(self):
        return False

colors.Colormap class definition.

You are going to need to dig into the Normalize classes as well. The color maps only know about [0, 1], so you will have to make sure that your norm maps to .5 where you want the change over to happen.

You could probably generalize this to take a list of maps and split points and have as many color maps as you want. This also needs all manner of sanity checks.

If you re-normalize the input, you could also use this to make a periodic version of any existing color map by passing it the color map and it's reversed partner.

tacaswell
  • 84,579
  • 22
  • 210
  • 199
0

I think it is simpler to make the colormap yourself, especially when so few colors are involved. This one is orange-white-blue.

cdict = {'red':   [ (0.0,   0.0, 0.0),
                    (0.475, 1.0, 1.0),
                    (0.525, 1.0, 1.0),
                    (1.0,   1.0, 1.0)
                  ],
         'green': [ (0.0,   0.0, 0.0),
                    (0.475, 1.0, 1.0),
                    (0.525, 1.0, 1.0),
                    (1.0,   0.65, 0.0)
                  ],
         'blue':  [ (0.0,   1.0, 1.0),
                    (0.475, 1.0, 1.0),
                    (0.525, 1.0, 1.0),
                    (1.0,   0.0, 0.0)
                  ]
}
rwb_cmap = matplotlib.colors.LinearSegmentedColormap(name = 'rwb_colormap', colors = cdict, N = 256)

A colormap is a dictionary for the RGB values. For each color, a list of tupples gives the different segments. Each segment is a point along the z-axis, ranging from 0 to 1. The colors for the levels is interpolated from these segments.

segment z-axis  end      start
i       z[i]    v0[i]    v1[i]
i+1     z[i+1]  v0[i+1]  v1[i+1]   
i+2     z[i+2]  v0[i+2]  v1[i+2]   

Levels between z[i] and z[i+1] will have colors between v1[i] and v0[i+1] etc. This makes it possible to 'jump' colors. v0[0] and v1[-1] are not used. You can use as many segments as you want. (adapted from here: http://matplotlib.org/api/colors_api.html#matplotlib.colors.LinearSegmentedColormap)

N is the number of quantization levels. So for N = 256 it will interpolate the map for 256 levels. I use 256 out of laziness. I guess you have to be careful when you set N = 6 and you make 4 contours.

The 0.475 and 0.525 are to ensure that the middle contour is truly white. For the levels [-1.5, -0.5, 0.5, 1.5] the fill is now orange-white-blue. If I had used 0.5 instead the middle level would be an interpolation of blue-ish and orange-ish.

The RGB code for orange is 255-165-0 or 1-0.65-0 if the scale is 0-1.

Robbert
  • 2,684
  • 1
  • 15
  • 12
  • Well, I wanted to have was flexibility based on the already existing work, but OK, if we're at it, would you please explain 1) why `0.475` and `0.525`, 2) why 3x4 lists, 3) why `0.65` and 4) why `256`. Can make it a separate question if that'd be an extra incentive. – ev-br Mar 14 '13 at 09:32