Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

matplotlib: continuous colormap fill between two lines

It's possible to fill between lines with a color:

http://matplotlib.sourceforge.net/examples/pylab_examples/fill_between_demo.html

It's also possible to use a continuous colormap for a line:

http://matplotlib.sourceforge.net/examples/pylab_examples/multicolored_line.html

Is it possible (and reasonably easy) to use a continuous colormap for the colored fill between two lines? For example, the color fill may change along x based on the difference between the two lines at x (or based on another set of data).

like image 642
ewelch Avatar asked Jul 19 '12 15:07

ewelch


2 Answers

I found a solution to this problem. It builds on the brilliant but hacky solution of @Hooked. You create a 2D grid filed from lots of small boxes. It's not the fastest solution but it should be pretty flexible (more so than solutions which apply imshow to the patches).

import numpy as np
import pylab as plt

#Plot a rectangle
def rect(ax, x, y, w, h, c,**kwargs):
    #Varying only in x
    if len(c.shape) is 1:
        rect = plt.Rectangle((x, y), w, h, color=c, ec=c,**kwargs)
        ax.add_patch(rect)
    #Varying in x and y
    else:
        #Split into a number of bins
        N = c.shape[0]
        hb = h/float(N); yl = y
        for i in range(N):
            yl += hb
            rect = plt.Rectangle((x, yl), w, hb, 
                                 color=c[i,:], ec=c[i,:],**kwargs)
            ax.add_patch(rect)

#Fill a contour between two lines
def rainbow_fill_between(ax, X, Y1, Y2, colors=None, 
                         cmap=plt.get_cmap("Reds"),**kwargs):
    plt.plot(X,Y1,lw=0)  # Plot so the axes scale correctly

    dx = X[1]-X[0]
    N  = X.size

    #Pad a float or int to same size as x
    if (type(Y2) is float or type(Y2) is int):
        Y2 = np.array([Y2]*N)

    #No colors -- specify linear
    if colors is None:
        colors = []
        for n in range(N):
            colors.append(cmap(n/float(N)))
    #Varying only in x
    elif len(colors.shape) is 1:
        colors = cmap((colors-colors.min())
                      /(colors.max()-colors.min()))
    #Varying only in x and y
    else:
        cnp = np.array(colors)
        colors = np.empty([colors.shape[0],colors.shape[1],4])
        for i in range(colors.shape[0]):
            for j in range(colors.shape[1]):
                colors[i,j,:] = cmap((cnp[i,j]-cnp[:,:].min())
                                    /(cnp[:,:].max()-cnp[:,:].min()))

    colors = np.array(colors)

    #Create the patch objects
    for (color,x,y1,y2) in zip(colors,X,Y1,Y2):
        rect(ax,x,y2,dx,y1-y2,color,**kwargs)


# Some Test data    
X = np.linspace(0,10,100)
Y1 = .25*X**2 - X
Y2 = X
g = np.exp(-.3*(X-5)**2)

#Plot fill and curves changing in x only
fig, axs =plt.subplots(1,2)
colors = g
rainbow_fill_between(axs[0],X,Y1,Y2,colors=colors)
axs[0].plot(X,Y1,'k-',lw=4)
axs[0].plot(X,Y2,'k-',lw=4)

#Plot fill and curves changing in x and y
colors = np.outer(g,g)
rainbow_fill_between(axs[1],X,Y1,Y2,colors=colors)
axs[1].plot(X,Y1,'k-',lw=4)
axs[1].plot(X,Y2,'k-',lw=4)
plt.show()

The result is, enter image description here

like image 107
Ed Smith Avatar answered Nov 15 '22 20:11

Ed Smith


Your solution is great and flexible ! In particular the 2D case is really nice. Such a feature could be added to fill_between maybe if the colors kwargs of the function would accept an array of the same length of x and y ?

Here is a simpler case for the 1D case using the fill_between function. It does the same but as it use trapezes instead of rectangle the result is smoother.

import matplotlib as mpl
import matplotlib.pyplot as plt

import numpy as np
from scipy.stats import norm

# Select a color map
cmap = mpl.cm.bwr

# Some Test data
npts = 100
x = np.linspace(-4, 4, npts)
y = norm.pdf(x)
z = np.sin(2 * x)
normalize = mpl.colors.Normalize(vmin=z.min(), vmax=z.max())

# The plot
fig = plt.figure()
ax = fig.add_axes([0.12, 0.12, 0.68, 0.78])
plt.plot(x, y, color="gray")
for i in range(npts - 1):
    plt.fill_between([x[i], x[i+1]], [y[i], y[i+1]], color=cmap(normalize(z[i])))

cbax = fig.add_axes([0.85, 0.12, 0.05, 0.78])
cb = mpl.colorbar.ColorbarBase(cbax, cmap=cmap, norm=normalize, orientation='vertical')
cb.set_label("Sin function", rotation=270, labelpad=15)
plt.show()

enter image description here

like image 32
Ger Avatar answered Nov 15 '22 19:11

Ger