makhlaghi
makhlaghi

Reputation: 3986

ellipses with various angles and eccentricities in matplotlib

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

Answers (1)

arjenve
arjenve

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

Related Questions