Search code examples
pythondrawpoint

How to draw a point on an ellipse from general form


The following program draws a rotated ellipse from an equation given in general form:

import matplotlib as mpl
import matplotlib.pyplot as plt

x_range = np.linspace(-6, +6)
y_range = np.linspace(-10, +1)
x, y = np.meshgrid(x_range, y_range)

A, B, C, D, E, F = 0.25, 0.40, 0.25, 1.50, 2.00, 1.50

f = lambda x, y, A, B, C, D, E, F: A*x**2 + B*x*y +C*y**2 + D*x + E*y + F
equation = f(x, y, A, B, C, D, E, F)

fig, ax = plt.subplots(1, 1, figsize = (4, 4), tight_layout = True)

ax.contour(x, y, equation, levels = [0], colors = color)

plt.show()

Is there a simple way to draw a point on this ellipse,no matter where it is ?

My question comes from the fact that I use Geogebra sometimes and, in Geogebra, if you draw an ellipse by the input 0.25*x^2 + 0.40*x*y + 0.25*y^2 + 1.5*x + 2*y + 1.5 = 0, you can easily put a point where you want on the ellipse (and even move it), using the simple instruction Point on Object...

I wonder if something similar could be implemented in Python, starting from the small program that I wrote a little above?


Solution

  • Your code draws the ellipse in quite a roundabout way -- it draws a contour of f == 0 for the values in the 2d array equation, which is obtained by calculating the value of f at every point on the grid defined by x and y.

    That said, ax.contour returns a QuadContourSet from which you can extract the coordinates being plotted like so:

    qcs = ax.contour(x, y, equation, levels = [0], colors = color)
    pts = qcs.collections[0].get_paths()[0].vertices
    

    pts is a 2d array of shape (N, 2). Any row of this array is a point on your ellipse. For example:

    ax.plot(pts[5, 0], pts[5, 1], 'xk')
    

    plots the black x below:

    enter image description here


    If you want to find a point for any arbitrary x value, you simply need to obtain the y value which solves the equation f(x, ...) == 0. scipy.optimize.fsolve can do this for you. Since fsolve takes a function F and solves the problem F(X, args) == 0, we need to redefine our function so that the first argument is the unknown coordinate we're trying to find, and supply the rest as args to fsolve:

    from scipy.optimize import fsolve
    def get_ellipse_point(x, A, B, C, D, E, F, y0):
    
        def ellipse_func(y, x, A, B, C, D, E, F):
            return A*x**2 + B*x*y +C*y**2 + D*x + E*y + F
    
        y_pt = fsolve(ellipse_func, y0, (x, A, B, C, D, E, F)
        return (x, y_pt[0])
    

    y0 is your initial guess of the point's y. Calling this function, for example, to find points with x == 0 gives:

    # Initial guess of y is -10, which will find the point at the bottom of the ellipse
    pt_x0_bottom = get_ellipse_point(0, A, B, C, D, E, F, -10)  # (0, -7.16227766016838)
    
    # Initial guess of y is +10, which will find the point at the top of the ellipse
    pt_x0_top = get_ellipse_point(0, A, B, C, D, E, F, 10)  # (0, -0.8377223398316207)
    

    We can plot these points to make sure we have the correct answer:

    ax.plot(*pt_x0_bottom, 'ob') # Plot bottom point with blue circle
    ax.plot(*pt_x0_top, '^g')    # Plot top point with green triangle
    

    enter image description here


    Note that I used the def keyword to define the function of the ellipse. I could have defined it with a lambda like you did to get the same result, but that is not a good practice (See Is it pythonic: naming lambdas)