I am trying to do a very basic shape connecting 4 points. I have a plot that looks like this currently. However I cannot seem to find a module that can appropriately generate an oval that connects all four points. The four points will all vary (they are results of a test, intended to show how much of each attribute someone has). I have found many modules to create rectangles, ellipses or circles, but none of these shapes will consistently align with the points. Is there a more generic module that can connect all four points in a shape more like an oval?
I've also seen some modules that could create an arc between two points, which I thought might be useful, I could do this for all four points. When I tried to do this, it required inputs related to the arc angle, which I would not know how to calculate. Any help on this is much appreciated!
Here is the code that I am using to generate the image shown.
import matplotlib.pyplot as plt
import numpy as np
# Axes go from 0 to 100%
fig,ax = plt.subplots(figsize=(15,12))
x = [-40,-20,20,20]
y = [-40,20,20,-20]
plt.scatter(x,y)
ax.fill_between([-100, 0],-100,0,alpha=0.3, color='#1F98D0') # blue
ax.fill_between([0, 100], -100, 0, alpha=0.3, color='#F9D307') # yellow
ax.fill_between([-100, 0], 0, 100, alpha=0.3, color='#F38D25') # orange
ax.fill_between([0, 100], 0, 100, alpha=0.3, color='#DA383D') # red
plt.axis('off')
plt.show()
The following approach creates an interpolating spline through the 4 points to create a "basic form". An advantage is that it also works when there are 3 or more than 4 points.
import matplotlib.pyplot as plt
from scipy import interpolate
import numpy as np
fig, ax = plt.subplots(figsize=(15, 12))
x = [-40, -20, 20, 20]
y = [-40, 20, 20, -20]
ax.scatter(x, y, zorder=3)
ax.fill_between([-100, 0], -100, 0, alpha=0.3, color='#1F98D0') #blue
ax.fill_between([0, 100], -100, 0, alpha=0.3, color='#F9D307') #yellow
ax.fill_between([-100, 0], 0, 100, alpha=0.3, color='#F38D25') #orange
ax.fill_between([0, 100], 0, 100, alpha=0.3, color='#DA383D') #red
tck, u = interpolate.splprep([x + x[:1], y + y[:1]], s=0, per=True)
unew = np.linspace(0, 1, 100)
basic_form = interpolate.splev(unew, tck)
ax.plot(basic_form[0], basic_form[1], color='lime', lw=2)
ax.fill(basic_form[0], basic_form[1], color='lime', alpha=0.3)
ax.axis('off')
plt.show()
The same approach also works when an oval form would be impossible to fit, or would be much too large:
Note that to uniquely define an ellipse, 5 points are needed (but not every 5 random points can fit an ellipse). If you really need an ellipse, to find the smallest one through 4 points is quite cumbersome.
Here is the start of an approach which defines an ellipse as the points where the sum of the distances to the two foci ((x0,y0)
and (x1,y1)
) is a constant a
. Then some minimal a
for which a solution exists could be searched for.
from sympy import symbols, sqrt, Eq, nsolve
a, x0, y0, x1, y1, xi, yi = symbols('a x0 y0 x1 y1 xi yi', real=True)
ellipse = Eq(sqrt((xi - x0) ** 2 + (yi - y0) ** 2) + sqrt((xi - x1) ** 2 + (yi - y1) ** 2), a)
x = [-40, -20, 20, 20]
y = [-40, 20, 20, -20]
ai = 85
sol = nsolve([ellipse.subs(xi, i).subs(yi, j).subs(a, ai) for i, j in zip(x, y)], (x0, y0, x1, y1), (-10, 0, 10, 0))