{ "cells": [ { "cell_type": "raw", "metadata": { "raw_mimetype": "text/restructuredtext" }, "source": [ ".. _ellipse-visualization:\n", "\n", "Ellipse Visualization\n", "=====================\n", "\n", "Toyplot supports drawing oriented ellipses using :meth:`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:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
-505-505
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import toyplot\n", "\n", "canvas = toyplot.Canvas(width=400)\n", "axes = canvas.cartesian(aspect=\"fit-range\")\n", "axes.ellipse(0, 0, 5, 2);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When rotating ellipses, angles are specified in degrees with positive angles leading to clockwise rotation:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
-4-2024-4-2024
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "canvas = toyplot.Canvas(width=400)\n", "axes = canvas.cartesian(aspect=\"fit-range\")\n", "axes.ellipse(0, 0, 5, 2, 45);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Of course, you can use color, opacity, and style to alter the appearance of an ellipse:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
-4-2024-4-2024-4-2024-4-2024
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "canvas = toyplot.Canvas(width=600, height=300)\n", "axes = canvas.cartesian(grid=(1, 2, 0), aspect=\"fit-range\")\n", "axes.ellipse(0, 0, 5, 2, 45, color=\"crimson\", opacity=0.5)\n", "axes = canvas.cartesian(grid=(1, 2, 1), aspect=\"fit-range\")\n", "axes.ellipse(0, 0, 5, 2, 45, style={\"stroke\": \"black\", \"fill\":\"none\"});" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
-505-2-1012
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "canvas = toyplot.Canvas(width=300)\n", "axes = canvas.cartesian() # Allow Toyplot to choose the domain\n", "axes.ellipse(0, 0, 5, 2);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
010203040510152025Linear Y projection01020304010 010 110 2Nonlinear Y projection
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "canvas = toyplot.Canvas(width=600, height=300)\n", "axes = canvas.cartesian(grid=(1, 2, 0), label=\"Linear Y projection\")\n", "axes.ellipse(20, 15, 20, 5, 30)\n", "axes = canvas.cartesian(grid=(1, 2, 1), yscale=\"log\", label=\"Nonlinear Y projection\")\n", "axes.ellipse(20, 15, 20, 5, 30);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The distortion can become even more extreme if the ellipse straddles the origin:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
-20-1001020-10010Linear Y projection-20-1001020-10 2-10 1-10 0010 010 110 2Nonlinear Y projection
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "canvas = toyplot.Canvas(width=600, height=300)\n", "axes = canvas.cartesian(grid=(1, 2, 0), label=\"Linear Y projection\")\n", "axes.ellipse(0, 0, 20, 5, 30)\n", "axes = canvas.cartesian(grid=(1, 2, 1), yscale=\"log\", label=\"Nonlinear Y projection\")\n", "axes.ellipse(0, 0, 20, 5, 30);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
05100510
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import numpy\n", "numpy.random.seed(1234)\n", "\n", "mean = numpy.array([3, 5])\n", "covariance = numpy.array([[3, 1], [1, 1]])\n", "\n", "samples = numpy.random.multivariate_normal(mean, covariance, size=1000)\n", "\n", "canvas = toyplot.Canvas()\n", "axes = canvas.cartesian(aspect=\"fit-range\")\n", "axes.scatterplot(samples[:,0], samples[:,1]);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you wanted to draw ellipses at 1, 2, and 3-$\\sigma$ intervals, you might do the following:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
-303690510
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "eigenvalues, eigenvectors = numpy.linalg.eig(covariance)\n", "angle = numpy.degrees(numpy.arctan2(eigenvectors[1, 0], eigenvectors[0, 0]))\n", "\n", "xsigma = numpy.sqrt(eigenvalues[0])\n", "ysigma = numpy.sqrt(eigenvalues[1])\n", "\n", "canvas = toyplot.Canvas()\n", "axes = canvas.cartesian(aspect=\"fit-range\")\n", "axes.scatterplot(samples[:,0], samples[:,1])\n", "axes.ellipse(mean[0], mean[1], xsigma, ysigma, angle, style={\"stroke\":\"black\", \"fill\":\"none\"})\n", "axes.ellipse(mean[0], mean[1], xsigma * 2, ysigma * 2, angle, style={\"stroke\":\"black\", \"fill\":\"none\"})\n", "axes.ellipse(mean[0], mean[1], xsigma * 3, ysigma * 3, angle, style={\"stroke\":\"black\", \"fill\":\"none\"});" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, if you use a nonlinear projection on the Y axis, the ellipse needs to deform along with the rest of the data:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
-3036910 010 1
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "canvas = toyplot.Canvas(width=600, height=400)\n", "axes = canvas.cartesian(aspect=\"fit-range\", yscale=\"log\")\n", "axes.scatterplot(samples[:,0], samples[:,1])\n", "axes.ellipse(mean[0], mean[1], xsigma, ysigma, angle, style={\"stroke\":\"black\", \"fill\":\"none\"})\n", "axes.ellipse(mean[0], mean[1], xsigma * 2, ysigma * 2, angle, style={\"stroke\":\"black\", \"fill\":\"none\"})\n", "axes.ellipse(mean[0], mean[1], xsigma * 3, ysigma * 3, angle, style={\"stroke\":\"black\", \"fill\":\"none\"});" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
-505-4-2024
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "numpy.random.seed(14)\n", "\n", "x = numpy.random.uniform(-6, 6, size=10)\n", "y = numpy.random.uniform(-4, 4, size=10)\n", "rx = numpy.random.uniform(0.1, 1, size=10)\n", "ry = numpy.random.uniform(0.1, 1, size=10)\n", "angle = numpy.random.uniform(0, 180, size=10)\n", "\n", "canvas = toyplot.Canvas(width=600, height=400)\n", "axes = canvas.cartesian(aspect=\"fit-range\")\n", "axes.ellipse(x, y, rx, ry, angle, color=toyplot.color.brewer.map(\"Set1\"), style={\"stroke\":\"white\"});" ] } ], "metadata": { "celltoolbar": "Raw Cell Format", "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 1 }