为什么matplotlib将我的圆圈绘制为椭圆形?

时间:2012-02-10 15:41:45

标签: python matplotlib

有没有办法让matplotlib绘制一个完美的圆圈?它们看起来更像椭圆形。

3 个答案:

答案 0 :(得分:34)

只是为了扩展DSM的正确答案。默认情况下,绘图沿一个轴在另一个轴上具有更多像素。添加圆圈时,传统上会添加数据单元。如果您的轴具有对称范围,则意味着沿x轴的一步将包含与沿y轴一步不同的像素数。因此,数据单元中的对称圆在您的像素单位中是不对称的(您实际看到的内容)。

正如DSM正确指出的那样,您可以强制x和y轴每个数据单元具有相同数量的像素。这是使用plt.axis("equal")ax.axis("equal")方法完成的(其中axAxes的实例)。

您还可以绘制一个Ellipse,使其适当缩放,使其在您的绘图上看起来像一个圆圈。以下是这种情况的一个例子:

import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse, Circle


fig = plt.figure()
ax1 = fig.add_subplot(211)
# calculate asymmetry of x and y axes:
x0, y0 = ax1.transAxes.transform((0, 0)) # lower left in pixels
x1, y1 = ax1.transAxes.transform((1, 1)) # upper right in pixes
dx = x1 - x0
dy = y1 - y0
maxd = max(dx, dy)
width = .15 * maxd / dx
height = .15 * maxd / dy

# a circle you expect to be a circle, but it is not
ax1.add_artist(Circle((.5, .5), .15))
# an ellipse you expect to be an ellipse, but it's a circle
ax1.add_artist(Ellipse((.75, .75), width, height))
ax2 = fig.add_subplot(212)

ax2.axis('equal')
# a circle you expect to be a circle, and it is
ax2.add_artist(Circle((.5, .5), .15))
# an ellipse you expect to be an ellipse, and it is
ax2.add_artist(Ellipse((.75, .75), width, height))

fig.savefig('perfectCircle1.png')

产生了这个数字:

enter image description here

或者,您可以调整您的数字,以便Axes为正方形:

# calculate dimensions of axes 1 in figure units
x0, y0, dx, dy = ax1.get_position().bounds
maxd = max(dx, dy)
width = 6 * maxd / dx
height = 6 * maxd / dy

fig.set_size_inches((width, height))

fig.savefig('perfectCircle2.png')

导致:

enter image description here

注意具有axis("equal")选项的第二个轴现在具有相同的x和y轴范围。该图已经缩放,以便每个的日期单位由相同数量的像素表示。

您也可以将轴调整为方形,即使数字不是。或者您可以将圆的默认变换更改为None,这意味着使用的单位是像素。我现在很难成功地做到这一点(圆圈是一个圆圈,但不是我想要它的地方)。

答案 1 :(得分:3)

我相信最简单的方法是添加以下内容:

ax.set_aspect('equal')

答案 2 :(得分:2)

我今天遇到了同样的问题,我想我可能会有更灵活的解决方案。前一个答案仍然存在两个主要问题(如果您不使用等宽方程功能)。首先,如果您调整整个图形的大小,则比例将不会相同,因为像素数将发生变化。第二点,如果你对xaxis和yaxis没有相同的lim,这个技巧就不起作用了。

此解决方案使用自定义对象欺骗mpl。实际上,每当您更改一个轴lim或图形大小时,mpl将调用一个内部函数,该函数将椭圆的宽度和高度值乘以变换函数值。由于width和height值存储在ellipse对象中,因此一种方法是根据当前ax属性创建一个自定义对象,并在调用函数时更新值:

import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse

class GraphDist() :
    def __init__(self, size, ax, x=True) :
        self.size = size
        self.ax = ax
        self.x = x

    @property
    def dist_real(self) :
        x0, y0 = self.ax.transAxes.transform((0, 0)) # lower left in pixels
        x1, y1 = self.ax.transAxes.transform((1, 1)) # upper right in pixes
        value = x1 - x0 if self.x else y1 - y0
        return value

    @property
    def dist_abs(self) :
        bounds = self.ax.get_xlim() if self.x else self.ax.get_ylim()
        return bounds[0] - bounds[1]

    @property
    def value(self) :
        return (self.size / self.dist_real) * self.dist_abs

    def __mul__(self, obj) :
        return self.value * obj

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_xlim((0,10))
ax.set_ylim((0,5))
width = GraphDist(10, ax, True)
height = GraphDist(10, ax, False)
ax.add_artist(Ellipse((1, 3), width, height))
plt.show()