ClimateUnboxed
ClimateUnboxed

Reputation: 8077

how can I specify a list of colors for rectangles using patches.PathPatch?

I want to make a plot consisting of coloured rectangles, and have been using this example https://matplotlib.org/gallery/misc/histogram_path.html#sphx-glr-gallery-misc-histogram-path-py as a guide:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as path

fig, ax = plt.subplots()

# Fixing random state for reproducibility
np.random.seed(19680801)

data = np.random.randn(1000)
n, bins = np.histogram(data, 50)

# get the corners of the rectangles for the histogram
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n

XY = np.array([[left, left, right, right], [bottom, top, top, bottom]]).T

# get the Path object
barpath = path.Path.make_compound_path_from_polys(XY)

# make a patch out of it
patch = patches.PathPatch(barpath)
ax.add_patch(patch)

# update the view limits
ax.set_xlim(left[0], right[-1])
ax.set_ylim(bottom.min(), top.max())

plt.show()

I would like a different colour for each rectangle and thought I could do this using a list of floats in cols and setting

patch=patches.PathPatch(barpath,fill=True,color=cols
,cmap='CMRmap')

But I get "Invalid rgba arg", does this routine only recognize RGB lists?

Upvotes: 4

Views: 1681

Answers (2)

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339170

A matplotlib.patches.PathPatch has a single facecolor. It cannot be used to colorize parts of it differently. This is mentioned in the example

can draw collections of regularly shaped objects with homogeneous properties more efficiently with a PathCollection.

So the motivation to use this strategy instead of creating a usual histogram and colorize its bars is efficiency. A slightly less efficient way of creating a bar plot, but still faster than the usual bars is to use a PolyCollection.

So let's look at three different solutions below.

import numpy as np; np.random.seed(19680801)
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.path as path
import matplotlib.collections
import matplotlib.cm

data = np.random.randn(1000)


def compoundpathhist(data, nbins=50):
    n, bins = np.histogram(data, nbins)
    # get the corners of the rectangles for the histogram
    left = np.array(bins[:-1])
    right = np.array(bins[1:])
    bottom = np.zeros(len(left))
    top = bottom + n
    
    
    # we need a (numrects x numsides x 2) numpy array for the path helper
    # function to build a compound path
    XY = np.array([[left, left, right, right], [bottom, top, top, bottom]]).T
    
    # get the Path object
    barpath = path.Path.make_compound_path_from_polys(XY)
    
    # make a patch out of it
    patch = patches.PathPatch(barpath)
    fig, ax = plt.subplots()
    ax.add_patch(patch)
    # update the view limits
    ax.set_xlim(left[0], right[-1])
    ax.set_ylim(bottom.min(), top.max())

    fig.savefig("bartest.png")
        

def polyhist(data, nbins=50, colors=True):
    n, bins = np.histogram(data, nbins)
    # get the corners of the rectangles for the histogram
    left = np.array(bins[:-1])
    right = np.array(bins[1:])
    bottom = np.zeros(len(left))
    top = bottom + n
    # we need a (numrects x numsides x 2) numpy array to be used as 
    # vertices for the PolyCollection
    XY = np.array([[left, left, right, right], [bottom, top, top, bottom]]).T
    
    c=None
    if colors:
        c = matplotlib.cm.RdYlBu(n/n.max())
    pc = matplotlib.collections.PolyCollection(XY, facecolors=c)
    
    fig, ax = plt.subplots()
    ax.add_collection(pc)
    # update the view limits
    ax.set_xlim(left[0], right[-1])
    ax.set_ylim(bottom.min(), top.max())
    
    fig.savefig("bartest.png")
        


def hist_c(data, nbins=50, colors=True):

    fig, ax = plt.subplots()
    n, bins, patches = ax.hist(data, nbins)

    if colors:
        cols = matplotlib.cm.RdYlBu(n/n.max())
        for p,c in zip(patches, cols):
            p.set_facecolor(c)

    # update the view limits
    ax.set_xlim(bins.min(), bins.max())
    ax.set_ylim(n.min(), n.max())

    fig.savefig("bartest.png")

        
compoundpathhist(data, nbins=50)
polyhist(data, nbins=50)
hist_c(data, nbins=50, colors=True)

plt.show()

enter image description here

  • The first (compoundpathhist) is the one from the linked example. It is fast, but cannot show color.
  • The second (polyhist) doesn't use a single patch, but instead a PolyCollection. The PolyCollection's facecolors can be set via a colormap.
  • The third (hist_c) is the adapted usual solution to colorize individual bars.

Now we can time the three functions. I'm using 90000 data points and 50, 500 and 5000 bins.

enter image description here

We observe that for a usual number of ~50 bins, there is essentially no difference between those methods. However for larger number of bins, the hist method takes significantly longer. Even for 5000 bins, there is almost no difference between the compoundpathhist method (which cannot use color) and the polyhist, which can use color. This is hence a useful alternative without sacrificing efficiency. There is by the way almost no difference between using color or not using color in the respective functions.

Upvotes: 3

Sheldore
Sheldore

Reputation: 39042

I think you are following a longer unnecessary route to color each bar differently. Simply use the following (shorter) approach. Using PathPatch and Path is not at all required. You can remove edgecolor='black' if you don't want the black edges.

Explanation: Get the patches object directly from the plt.hist. Then, create a list of colors based on the colormap CMRmap. Finally loop over the patches and set the color of each patch.

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()

# Fixing random state for reproducibility
np.random.seed(19680801)

data = np.random.randn(1000)
N, bins, patches = plt.hist(data, bins=50, edgecolor='black')

cm = plt.get_cmap('CMRmap')
for i in range(len(patches)):
    color = cm(1.*i/len(patches))  
    patches[i].set_facecolor(color)

enter image description here

Upvotes: 3

Related Questions