Reputation: 3986
I want to make a plot with ellipses as markers. Here is a sample code making one large ellipse that for now is actually a circle.
#! /usr/bin/env python3.2
import numpy as np
import pylab
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
from matplotlib.patches import Ellipse
PlotFileName="test.pdf"
pdf = PdfPages(PlotFileName)
fig=plt.figure(1)
ax1=fig.add_subplot(111)
x_lim=3
plt.xlim([0,x_lim])
plt.ylim([0,x_lim])
F=pylab.gcf()
DefSize = F.get_size_inches()
#These lines have a true angle of 45 degrees, only as a reference:
offset_along_x=x_lim-(x_lim/ax_ratio)
ax1.plot([offset_along_x/2, x_lim-(offset_along_x/2)], [0, x_lim], "b")
ax1.plot([offset_along_x/2, x_lim-(offset_along_x/2)], [x_lim, 0], "b")
e=0.0
theta=0
maj_ax=2
min_ax=maj_ax*np.sqrt(1-e**2)
xconst=(DefSize[1]/DefSize[0])*np.cos(theta*np.pi/180)-np.sin(theta*np.pi/180)
yconst=np.cos(theta*np.pi/180)+(DefSize[1]/DefSize[0])*np.sin(theta*np.pi/180)
print("xconstant= {}".format(xconst))
print("yconstant= {}".format(yconst))
ax1.add_artist(Ellipse((x_lim/2, x_lim/2), xconst*maj_ax, yconst*min_ax, angle=theta, facecolor="green", edgecolor="black",zorder=2, alpha=0.5))
pdf.savefig(fig)
pdf.close()
plt.close()
Although in this simplified case, ax1.axis("equal")
will give a pure circle, but in my final plot, this command will ruin the whole plot (the scales aren't equal). So I want to make a general purpose ellipse tool without using ax1.axis("equal")
. As you see, you can set the eccentricity and the inclination angle of the major axis in this program.
The problem:
The problem seems to be that I don't understand how matplotlib rotates its images. If you change the value of theta
here to a value other than 0 or 90, the object will no longer be a circle. with ax1.axis("equal")
the output is a circle now matter what the value of theta
is. So my first problem is this: what should I do to keep the output as a circle while changing theta
. I assume that once I fix this, it will also work for an ellipse. Could someone please help me with this? I would really appreciate it.
Upvotes: 1
Views: 2827
Reputation: 2333
In matplotlib the eclipse position and form is set by first scaling to the height and width then rotating around it's center and then translating to the required position. So rotating and scaling the height and with before may mork (like in your script), but is hard to get exactly right (you probably have to scale and invert the rotation, but my transformation math is rusty).
If you want to scale the ellipse's form correctly you'll need to subclass the Ellipse class and redefine the _recompute_transform
function:
from matplotlib import transforms
class TransformedEllipse(Ellipse):
def __init__(self, xy, width, height, angle=0.0, fix_x = 1.0, **kwargs):
Ellipse.__init__(self, xy, width, height, angle, **kwargs)
self.fix_x = fix_x
def _recompute_transform(self):
center = (self.convert_xunits(self.center[0]),
self.convert_yunits(self.center[1]))
width = self.convert_xunits(self.width)
height = self.convert_yunits(self.height)
self._patch_transform = transforms.Affine2D() \
.scale(width * 0.5, height * 0.5) \
.rotate_deg(self.angle) \
.scale(self.fix_x, 1) \
.translate(*center)
and use it like so:
fix_x = DefSize[1]/DefSize[0]/ax_ratio
ellipse = TransformedEllipse((x_lim/2.0, x_lim/2.0), maj_ax, min_ax, angle=theta, facecolor="green", edgecolor="black",zorder=2, alpha=0.5, fix_x = fix_x)
P.S. I assume the ax_ratio
to be y_lim / x_lim
!
Upvotes: 3