_images/toyplot.png

Ellipse VisualizationΒΆ

Toyplot supports drawing oriented ellipses using toyplot.coordinates.Cartesian.ellipse(). Ellipses are defined using their center, x and y radius, and angle with respect to the X axis. For example, to create an unrotated ellipse centered at the origin:

[1]:
import toyplot

canvas = toyplot.Canvas(width=400)
axes = canvas.cartesian(aspect="fit-range")
axes.ellipse(0, 0, 5, 2);
-505-505

When rotating ellipses, angles are specified in degrees with positive angles leading to clockwise rotation:

[2]:
canvas = toyplot.Canvas(width=400)
axes = canvas.cartesian(aspect="fit-range")
axes.ellipse(0, 0, 5, 2, 45);
-4-2024-4-2024

Of course, you can use color, opacity, and style to alter the appearance of an ellipse:

[3]:
canvas = toyplot.Canvas(width=600, height=300)
axes = canvas.cartesian(grid=(1, 2, 0), aspect="fit-range")
axes.ellipse(0, 0, 5, 2, 45, color="crimson", opacity=0.5)
axes = canvas.cartesian(grid=(1, 2, 1), aspect="fit-range")
axes.ellipse(0, 0, 5, 2, 45, style={"stroke": "black", "fill":"none"});
-4-2024-4-2024-4-2024-4-2024

One thing to keep in mind when working with ellipses is that, if you don’t specify an explicit domain for your axes, Toyplot’s default axis scaling will tend to turn an ellipse into a circle:

[4]:
canvas = toyplot.Canvas(width=300)
axes = canvas.cartesian() # Allow Toyplot to choose the domain
axes.ellipse(0, 0, 5, 2);
-505-2-1012

This is because Toyplot treats your ellipses as data - that is, ellipses aren’t just symbols drawn atop the canvas, they are embedded in the space defined by the axes. Thus, an ellipse will be distorted when viewed using a nonlinear projection such as log axes. This is by design:

[5]:
canvas = toyplot.Canvas(width=600, height=300)
axes = canvas.cartesian(grid=(1, 2, 0), label="Linear Y projection")
axes.ellipse(20, 15, 20, 5, 30)
axes = canvas.cartesian(grid=(1, 2, 1), yscale="log", label="Nonlinear Y projection")
axes.ellipse(20, 15, 20, 5, 30);
010203040510152025Linear Y projection01020304010 010 110 2Nonlinear Y projection

The distortion can become even more extreme if the ellipse straddles the origin:

[6]:
canvas = toyplot.Canvas(width=600, height=300)
axes = canvas.cartesian(grid=(1, 2, 0), label="Linear Y projection")
axes.ellipse(0, 0, 20, 5, 30)
axes = canvas.cartesian(grid=(1, 2, 1), yscale="log", label="Nonlinear Y projection")
axes.ellipse(0, 0, 20, 5, 30);
-20-1001020-10010Linear Y projection-20-1001020-10 2-10 1-10 0010 010 110 2Nonlinear Y projection

While this behavior may seem nonintuitive at first, it makes sense when you’re using an ellipse to visualize confidence or other bounds for sampled data. For example, suppose you’re drawing samples from a bivariate normal distribution:

[7]:
import numpy
numpy.random.seed(1234)

mean = numpy.array([3, 5])
covariance = numpy.array([[3, 1], [1, 1]])

samples = numpy.random.multivariate_normal(mean, covariance, size=1000)

canvas = toyplot.Canvas()
axes = canvas.cartesian(aspect="fit-range")
axes.scatterplot(samples[:,0], samples[:,1]);
05100510

If you wanted to draw ellipses at 1, 2, and 3-\(\sigma\) intervals, you might do the following:

[8]:
eigenvalues, eigenvectors = numpy.linalg.eig(covariance)
angle = numpy.degrees(numpy.arctan2(eigenvectors[1, 0], eigenvectors[0, 0]))

xsigma = numpy.sqrt(eigenvalues[0])
ysigma = numpy.sqrt(eigenvalues[1])

canvas = toyplot.Canvas()
axes = canvas.cartesian(aspect="fit-range")
axes.scatterplot(samples[:,0], samples[:,1])
axes.ellipse(mean[0], mean[1], xsigma, ysigma, angle, style={"stroke":"black", "fill":"none"})
axes.ellipse(mean[0], mean[1], xsigma * 2, ysigma * 2, angle, style={"stroke":"black", "fill":"none"})
axes.ellipse(mean[0], mean[1], xsigma * 3, ysigma * 3, angle, style={"stroke":"black", "fill":"none"});
-303690510

However, if you use a nonlinear projection on the Y axis, the ellipse needs to deform along with the rest of the data:

[9]:
canvas = toyplot.Canvas(width=600, height=400)
axes = canvas.cartesian(aspect="fit-range", yscale="log")
axes.scatterplot(samples[:,0], samples[:,1])
axes.ellipse(mean[0], mean[1], xsigma, ysigma, angle, style={"stroke":"black", "fill":"none"})
axes.ellipse(mean[0], mean[1], xsigma * 2, ysigma * 2, angle, style={"stroke":"black", "fill":"none"})
axes.ellipse(mean[0], mean[1], xsigma * 3, ysigma * 3, angle, style={"stroke":"black", "fill":"none"});
-3036910 010 1

Finally, as with other Toyplot visualizations, you are not limited to creating individual ellipses one-at-a-time - you can create a series of ellipses in a single call:

[10]:
numpy.random.seed(14)

x = numpy.random.uniform(-6, 6, size=10)
y = numpy.random.uniform(-4, 4, size=10)
rx = numpy.random.uniform(0.1, 1, size=10)
ry = numpy.random.uniform(0.1, 1, size=10)
angle = numpy.random.uniform(0, 180, size=10)

canvas = toyplot.Canvas(width=600, height=400)
axes = canvas.cartesian(aspect="fit-range")
axes.ellipse(x, y, rx, ry, angle, color=toyplot.color.brewer.map("Set1"), style={"stroke":"white"});
-505-4-2024