dizcza
dizcza

Reputation: 698

matplotlib preserve aspect ratio in Wedge patches (pie charts)

I want to plot colored pie charts at specific positions without distorting their circular aspect ratio. I'm using Wedge patches because I could not find a better solution. Here is the code

import matplotlib.pyplot as plt
import numpy as np
from matplotlib import patches, collections

fig, axes = plt.subplots()
for i in range(20):
    x = np.random.uniform(low=0, high=1, size=10).cumsum()
    axes.scatter(x=x, y=np.repeat(i, x.shape[0]), c='gray', s=1)

pies = []
N = 4
cmap = plt.cm.get_cmap("hsv", N + 1)
colors = list(map(cmap, range(N)))
print(colors)

for i in range(2, 2 + N):
    thetas = np.linspace(0, 360, num=i)
    assert len(thetas) - 1 <= len(colors)
    for theta1, theta2, c in zip(thetas[:-1], thetas[1:], colors):
        wedge = patches.Wedge((i, i), r=i / 10, theta1=theta1, theta2=theta2,
                              color=c)
        pies.append(wedge)

axes.add_collection(collections.PatchCollection(pies,
                                                match_original=True))
plt.show()

enter image description here

How to preserve the aspect ratio of pie charts? Setting axes.set_aspect("equal") is NOT an option because it squeezes the plot completely when I have more data points.

I've been looking at how to draw circles and preserve the aspect ratio but the solution cannot be adopted here - I'm plotting Wedges/pie charts, not Circles.

I also looked at matplotlib transforms but couldn't find the answer there either.

Upvotes: 0

Views: 556

Answers (2)

Frank Vel
Frank Vel

Reputation: 1208

I tried the same thing, and matplotlib really doesn't try to make this easy for you, but I found a solution that you should be able to use.

You need to separate the centers from the wedges and add them to the PatchCollection as offsets. Then you can apply different transforms to the offsets (transOffset) and shape (transform).

Notice that I have changed the r-value (radius). This value is no longer in data coordinates, so it should always be the same size, regardless of how much you zoom, but it is too small to be visible at i/10.

from matplotlib import patches, collections, transforms

offsets = []
for i in range(2, 2 + N):
    thetas = np.linspace(0, 360, num=i)
    assert len(thetas) - 1 <= len(colors)
    for theta1, theta2, c in zip(thetas[:-1], thetas[1:], colors):
        wedge = patches.Wedge((0, 0), r=10, theta1=theta1, theta2=theta2,
                              color=c)
        offsets.append((i, i))
        pies.append(wedge)

coll = collections.PatchCollection(
    pies, match_original=True, offsets=offsets,
    transform=transforms.IdentityTransform(),
    transOffset=axes.transData
)

Upvotes: 1

Miguel Gonzalez
Miguel Gonzalez

Reputation: 773

It works fine for me when I set set_aspect('equal'):

Image is narrowed because y-range is longer than x-range I think.

If you'd set y_lim between 0 and a number lower than y_max, you'd see it better:


import matplotlib.pyplot as plt
import numpy as np
from matplotlib import patches, collections

fig, axes = plt.subplots()
for i in range(20):
    x = np.random.uniform(low=0, high=1, size=10).cumsum()
    axes.scatter(x=x, y=np.repeat(i, x.shape[0]), c='gray', s=1)

pies = []
N = 4
cmap = plt.cm.get_cmap("hsv", N + 1)
colors = list(map(cmap, range(N)))
print(colors)

for i in range(2, 2 + N):
    thetas = np.linspace(0, 360, num=i)
    assert len(thetas) - 1 <= len(colors)
    for theta1, theta2, c in zip(thetas[:-1], thetas[1:], colors):
        wedge = patches.Wedge((i, i), r=i / 10, theta1=theta1, theta2=theta2,
                              color=c)
        pies.append(wedge)

axes.add_collection(collections.PatchCollection(pies,
                                                match_original=True))
axes.set_aspect('equal')
axes.set_ylim(0,7.5)
plt.show()

Upvotes: 0

Related Questions