Search code examples
python-3.xmatplotlibcolorbar

Change colors in colormap based on range of values


Is it possible to set the lower and/or upper parts of a colorbar based on ranges of values? For example, given the ROYGBIV colormap below and optionally an offset and a range value, I'd like to change the colors below offset and/or above range. In other words, suppose offset = 20 and range = 72, I'd like to color all the values less than or equal to 20 in black and all values greater than or equal to 72 in white. I'm aware of the methods set_under and set_over, but they require changing the parameters vmin and vmax (as far as I know), which is not what I want. I want to keep the original minimum and maximum values (e.g., vmin = 0 and vmax = 100), and only (optionally) change the colors of the extremities.

ROYGBIV = {
    "blue": ((0.0, 1.0, 1.0),
             (0.167, 1.0, 1.0),
             (0.333, 1.0, 1.0),
             (0.5, 0.0, 0.0),
             (0.667, 0.0, 0.0),
             (0.833, 0.0, 0.0),
             (1.0, 0.0, 0.0)),
    "green": ((0.0, 0.0, 0.0),
              (0.167, 0.0, 0.0),
              (0.333, 0.0, 0.0),
              (0.5, 1.0, 1.0),
              (0.667, 1.0, 1.0),
              (0.833, 0.498, 0.498),
              (1.0, 0.0, 0.0)),
    "red": ((0.0, 0.5608, 0.5608),
            (0.167, 0.4353, 0.4353),
            (0.333, 0.0, 0.0),
            (0.5, 0.0, 0.0),
            (0.667, 1.0, 1.0),
            (0.833, 1.0, 1.0),
            (1.0, 1.0, 1.0))
}

rainbow_mod = matplotlib.colors.LinearSegmentedColormap("rainbow_mod", ROYGBIV, 256)

Original colorbar


Solution

  • I found one way to do it using ListedColormap as explained here. The basic idea is to obtain the RGBA lists/tuples of the colors in the LinearSegmentedColormap object (numpy array) and replace the first or last few lists with replicates of the desired color.

    It looks something like this:

    under_color = [0.0, 0.0, 0.0, 1.0]  # black (alpha = 1.0)
    over_color = [1.0, 1.0, 1.0, 1.0]  # white (alpha = 1.0)
    
    all_colors = rainbow_mod(np.linspace(0, 1, 256))
    
    vmin = 0.0
    vmax = 100.0
    
    all_colors[:int(np.round((20.0 - vmin) / (vmax - vmin) * 256)), :] = under_color
    all_colors[int(np.round((72.0 - vmin) / (vmax - vmin) * 256)):, :] = over_color
    
    rainbow_mod_list = matplotlib.colors.ListedColormap(all_colors.tolist())
    

    Modified colorbar