-1

I am trying to automatically give each Patch in a PatchCollection a color from a color map like tab20.

from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(5,5))
coords = [
    (0, 0),
    (1, 2),
    (1, 3),
    (2, 2),
]
patches = [plt.Circle(coords[i], 0.1) for i in range(len(coords))]
patch_collection = PatchCollection(patches, cmap='tab20', match_original=True)
ax.add_collection(patch_collection)

ax.set_xlim(-1, 3)
ax.set_ylim(-1, 4)
plt.axis('equal')

But the above code is drawing each circle using the same color. How can the colors be cycled?

enter image description here

gameveloster
  • 901
  • 1
  • 6
  • 18

2 Answers2

1

enter image description here

Here I've sampled the tab20 colormap, so that the RGBA array cmap.colors has exactly 20 different entries, then I've assigned this RGBA array to the keyword argument facecolors that every collection accepts.

Not just for cosmetics, I've added a colormap, so that it's possible to recognize the order in which the circles were drawn.

from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
from numpy.random import rand, seed

seed(20230104)
N = 20
coords = rand(N,2)*[2,1.2]
cmap = plt.get_cmap('tab20', N)

fig, ax = plt.subplots()
patches = [plt.Circle(coord, 0.06) for coord in coords]
# use facecolors=...
collection = PatchCollection(patches, facecolors=cmap.colors[:N-1])
ax.add_collection(collection)
cb = plt.colorbar(plt.cm.ScalarMappable(plt.Normalize(-0.5, N-0.5), cmap))
cb.set_ticks(range(N), labels=('%02d'%(n+1) for n in range(N)))
ax.autoscale(collection)
ax.set_aspect(1)

Overdone Version

enter image description here

from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
from numpy.random import rand, seed

seed(20230104)
N = 20
coords = rand(N, 2) * [2, 1.2]
cmap = plt.get_cmap("tab20", N)
patches = (plt.Circle(coord, 0.06) for coord in coords)

fig, ax = plt.subplots()
im = ax.add_collection(
    PatchCollection(
        patches,
        facecolors=cmap.colors,
        edgecolors="w",
        linewidth=2,
        cmap=cmap,
        norm=plt.Normalize(-0.50, N - 0.50),
    )
)

cb = plt.colorbar(
    im,
    location="bottom",
    fraction=0.05,
    aspect=50,
    drawedges=True,
)
cb.set_ticks(range(N), labels=("%02d" % (n + 1) for n in range(N)))
cb.dividers.set_color(ax._facecolor)
cb.dividers.set_linewidth(3)

ax.autoscale()
ax.set_aspect(1)

gboffi
  • 22,939
  • 8
  • 54
  • 85
0

This gives each patch its color from a fixed subset of colors in the selected colormap, repeating as necessary:

from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt

num_col = 3
cmap = plt.cm.tab20

fig, ax = plt.subplots(figsize=(5,5))
coords = [
    (0, 0),
    (1, 2),
    (1, 3),
    (2, 2),
]

patches = [plt.Circle(coords[i], 0.1) for i in range(len(coords))]
patch_collection = PatchCollection(patches, facecolor=cmap.colors[0:num_col])
ax.add_collection(patch_collection)

ax.set_xlim(-1, 3)
ax.set_ylim(-1, 4)
plt.axis('equal')

Output:

enter image description here

This gives a random color from the selected colormap by using numpy to generate a list of random numbers, then using the patch objects set_array method:

from matplotlib.collections import PatchCollection
import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(figsize=(5,5))
coords = [
    (0, 0),
    (1, 2),
    (1, 3),
    (2, 2),
]
patches = [plt.Circle(coords[i], 0.1) for i in range(len(coords))]
color_vals = np.random.rand(len(patches))
patch_collection = PatchCollection(patches, cmap='tab20', match_original=True)
patch_collection.set_array(color_vals)
ax.add_collection(patch_collection)

ax.set_xlim(-1, 3)
ax.set_ylim(-1, 4)
plt.axis('equal')

Output:

enter image description here

I don't think match_original=True is necessary as you want to change the default color of the original patches. I'm sure there other ways of doing this as well. This SO post was helpful: setting color range in matplotlib patchcollection