How to draw ellipse on top of scatter plot?

I want to draw ellipse on top of my scatter plot like this ScatterEllipse03

I can do it with matplotlib but I don’t know how to do it with plotly since the shapes/draw in plotly is only have circle object with given parameter x0,y0,x1,y1 I can’t use that to draw my ellipse

what I actually need is a method like this matplotlib method https://matplotlib.org/api/_as_gen/matplotlib.patches.Ellipse.html

since the parameter is x coordinate, y coordinate, width(diameter), and height(diameter)

2 Likes

@mutia

This is a plotly.py question. Please change its category from Plotly.js to Plotly.py.

Here is a function that draws the ellipse as a line:

import numpy as np
from numpy import pi, sin, cos
import plotly.graph_objects as go

def ellipse(x_center=0, y_center=0, ax1 = [1, 0],  ax2 = [0,1], a=1, b =1,  N=100):
    # x_center, y_center the coordinates of ellipse center
    # ax1 ax2 two orthonormal vectors representing the ellipse axis directions
    # a, b the ellipse parameters
    if np.linalg.norm(ax1) != 1 or np.linalg.norm(ax2) != 1:
        raise ValueError('ax1, ax2 must be unit vectors')
    if  abs(np.dot(ax1, ax2)) > 1e-06:
        raise ValueError('ax1, ax2 must be orthogonal vectors')
    t = np.linspace(0, 2*pi, N)
    #ellipse parameterization with respect to a system of axes of directions a1, a2
    xs = a * cos(t)
    ys = b * sin(t)
    #rotation matrix
    R = np.array([ax1, ax2]).T
    # coordinate of the  ellipse points with respect to the system of axes [1, 0], [0,1] with origin (0,0)
    xp, yp = np.dot(R, [xs, ys])
    x = xp + x_center 
    y = yp + y_center
    return x, y
    
#EXAMPLE
fig = go.Figure()
x_center=1
y_center=0.75
x, y = ellipse_arc(x_center=x_center, y_center=y_center, 
                   ax1 =[cos(pi/6), sin(pi/6)],  ax2=[-sin(pi/6),cos(pi/6)],
                   a=3, b =2)
fig.add_scatter(
            x=x,
            y=y,
            mode = 'lines')
fig.update_layout(width =600, height=525)
1 Like

@empet Thank you for the great function. I am using your function to draw an ellipsoid around several lines. Its work fine with vertical and horizontal lines when using it as:

ellipse(x_center=x_center, y_center=y_center, ax1 =[cos(pi/2), sin(pi/2)], ax2=[-sin(pi/2),cos(pi/2)], a=a8, b =b8)

However, when using it with titled lines, the ellipsoids shifted slightly and appeared not perfectly aligned with the lines. Please see the image attached for more clarification. So my question is can we consider the rotation of the lines so the ellipse fit with the lines by using the Xs and Ys of the lines? And even without having the rotation angle of the lines? Or how we can calculate it and incorporated it


into the function?

Any help to fix this problem will be much appreciated. Many thanks for considering my request.

Ramy

@RamySaleem

Could you, please, provide your data for the dots you plotted inside ellipses? To give an answer I need to know what role and coordinates have the blue (dots) in your plot.
Your ax1 has the direction of positive yaxis, while ax2 of the negative xaxis. I suspect that there is a conflict between the dot coordinates given with respect to the reference frame consisting in positive xaxis and positive yaxis, and the reverted axes for ellipses.

The initial function is lacking a test on the detrminat of R. This is the updated function:

import numpy as np
from numpy import pi, sin, cos
import plotly.graph_objects as go

def ellipse(x_center=0, y_center=0, ax1 = [1, 0],  ax2 = [0,1], a=1, b =1,  N=100):
    # x_center, y_center the coordinates of ellipse center
    # ax1 ax2 two orthonormal vectors representing the ellipse axis directions
    # a, b the ellipse parameters
    if np.linalg.norm(ax1) != 1 or np.linalg.norm(ax2) != 1:
        raise ValueError('ax1, ax2 must be unit vectors')
    if  abs(np.dot(ax1, ax2)) > 1e-06:
        raise ValueError('ax1, ax2 must be orthogonal vectors')
    #rotation matrix   
    R = np.array([ax1, ax2]).T
    if np.linalg.det(R) <0: 
        raise ValueError("the det(R) must be positive to get a  positively oriented ellipse reference frame")
    t = np.linspace(0, 2*pi, N)
    #ellipse parameterization with respect to a system of axes of directions a1, a2
    xs = a * cos(t)
    ys = b * sin(t)
    
    # coordinate of the  ellipse points with respect to the system of axes [1, 0], [0,1] with origin (0,0)
    xp, yp = np.dot(R, [xs, ys])
    x = xp + x_center 
    y = yp + y_center
    return x, y

Your det(R) is positive as required, but as I said, I suspect a conflict between the coordinate system associated to dots, respectively to the ellipse.

I really appreciated it! @empet Thank you so much for your quick reply and discussion. Yes, I am using geological data to model uncertainty in the subsurface interpretations, so the Y-axis represent the depth and parts of the Y-axis that are above the earth surface is positive and going down to the subsurface the Y-axis became negative. However, all the x-axis are positive. All the data are from the same coordinate system and exported together, so the X & Y values should be correct. Yeah, maybe the conflict is between the data coordinates and the frame generated, although I can’t figure out how I overcome this! Please have a look at my data points:

x z Name
127.732 -24.5685 Tunnel_Well
127.7654 -25.2623 Tunnel_Well
127.7978 -25.935 Tunnel_Well
127.8282 -26.5662 Tunnel_Well
127.8556 -27.1367 Tunnel_Well
127.8793 -27.6291 Tunnel_Well
127.8986 -28.0286 Tunnel_Well
127.9127 -28.3229 Tunnel_Well
127.9214 -28.5032 Tunnel_Well
127.9243 -28.5639 Tunnel_Well
127.9214 -28.5032 Tunnel_Well
127.9127 -28.3229 Tunnel_Well
127.8986 -28.0286 Tunnel_Well
127.8793 -27.6291 Tunnel_Well
127.8556 -27.1367 Tunnel_Well
127.8282 -26.5662 Tunnel_Well
127.7978 -25.935 Tunnel_Well
127.7654 -25.2623 Tunnel_Well
127.732 -24.5685 Tunnel_Well
127.6986 -23.8747 Tunnel_Well
127.6662 -23.202 Tunnel_Well
127.6358 -22.5708 Tunnel_Well
127.6083 -22.0003 Tunnel_Well
127.5846 -21.5078 Tunnel_Well
127.5654 -21.1084 Tunnel_Well
127.5512 -20.8141 Tunnel_Well
127.5425 -20.6338 Tunnel_Well
127.5396 -20.5731 Tunnel_Well
127.5425 -20.6338 Tunnel_Well
127.5512 -20.8141 Tunnel_Well
127.5654 -21.1084 Tunnel_Well
127.5846 -21.5078 Tunnel_Well
127.6083 -22.0003 Tunnel_Well
127.6358 -22.5708 Tunnel_Well
127.6662 -23.202 Tunnel_Well
127.6986 -23.8747 Tunnel_Well
238.2299 -19.2482 Tunnel_Well
238.2633 -19.942 Tunnel_Well
238.2956 -20.6147 Tunnel_Well
238.326 -21.2459 Tunnel_Well
238.3535 -21.8164 Tunnel_Well
238.3772 -22.3088 Tunnel_Well
238.3965 -22.7083 Tunnel_Well
238.4106 -23.0026 Tunnel_Well
238.4193 -23.1829 Tunnel_Well
238.4222 -23.2436 Tunnel_Well
238.4193 -23.1829 Tunnel_Well
238.4106 -23.0026 Tunnel_Well
238.3965 -22.7083 Tunnel_Well
238.3772 -22.3088 Tunnel_Well
238.3535 -21.8164 Tunnel_Well
238.326 -21.2459 Tunnel_Well
238.2956 -20.6147 Tunnel_Well
238.2633 -19.942 Tunnel_Well
238.2299 -19.2482 Tunnel_Well
238.1964 -18.5544 Tunnel_Well
238.1641 -17.8817 Tunnel_Well
238.1337 -17.2505 Tunnel_Well
238.1062 -16.68 Tunnel_Well
238.0825 -16.1876 Tunnel_Well
238.0633 -15.7881 Tunnel_Well
238.0491 -15.4938 Tunnel_Well
238.0404 -15.3135 Tunnel_Well
238.0375 -15.2528 Tunnel_Well
238.0404 -15.3135 Tunnel_Well
238.0491 -15.4938 Tunnel_Well
238.0633 -15.7881 Tunnel_Well
238.0825 -16.1876 Tunnel_Well
238.1062 -16.68 Tunnel_Well
238.1337 -17.2505 Tunnel_Well
238.1641 -17.8817 Tunnel_Well
238.1964 -18.5544 Tunnel_Well
348.7277 -13.9279 Tunnel_Well
348.7611 -14.6217 Tunnel_Well
348.7935 -15.2944 Tunnel_Well
348.8239 -15.9256 Tunnel_Well
348.8514 -16.4961 Tunnel_Well
348.8751 -16.9886 Tunnel_Well
348.8943 -17.388 Tunnel_Well
348.9085 -17.6824 Tunnel_Well
348.9172 -17.8626 Tunnel_Well
348.9201 -17.9233 Tunnel_Well
348.9172 -17.8626 Tunnel_Well
348.9085 -17.6824 Tunnel_Well
348.8943 -17.388 Tunnel_Well
348.8751 -16.9886 Tunnel_Well
348.8514 -16.4961 Tunnel_Well
348.8239 -15.9256 Tunnel_Well
348.7935 -15.2944 Tunnel_Well
348.7611 -14.6217 Tunnel_Well
348.7277 -13.9279 Tunnel_Well
348.6943 -13.2342 Tunnel_Well
348.6619 -12.5614 Tunnel_Well
348.6316 -11.9303 Tunnel_Well
348.6041 -11.3598 Tunnel_Well
348.5804 -10.8673 Tunnel_Well
348.5611 -10.4678 Tunnel_Well
348.547 -10.1735 Tunnel_Well
348.5383 -9.9933 Tunnel_Well
348.5354 -9.9326 Tunnel_Well
348.5383 -9.9933 Tunnel_Well
348.547 -10.1735 Tunnel_Well
348.5611 -10.4678 Tunnel_Well
348.5804 -10.8673 Tunnel_Well
348.6041 -11.3598 Tunnel_Well
348.6316 -11.9303 Tunnel_Well
348.6619 -12.5614 Tunnel_Well
348.6943 -13.2342 Tunnel_Well
459.2256 -8.6077 Tunnel_Well
459.259 -9.3015 Tunnel_Well
459.2914 -9.9742 Tunnel_Well
459.3218 -10.6054 Tunnel_Well
459.3493 -11.1758 Tunnel_Well
459.373 -11.6683 Tunnel_Well
459.3922 -12.0678 Tunnel_Well
459.4064 -12.3621 Tunnel_Well
459.4151 -12.5423 Tunnel_Well
459.418 -12.603 Tunnel_Well
459.4151 -12.5423 Tunnel_Well
459.4064 -12.3621 Tunnel_Well
459.3922 -12.0678 Tunnel_Well
459.373 -11.6683 Tunnel_Well
459.3493 -11.1758 Tunnel_Well
459.3218 -10.6054 Tunnel_Well
459.2914 -9.9742 Tunnel_Well
459.259 -9.3015 Tunnel_Well
459.2256 -8.6077 Tunnel_Well
459.1922 -7.9139 Tunnel_Well
459.1598 -7.2412 Tunnel_Well
459.1294 -6.61 Tunnel_Well
459.102 -6.0395 Tunnel_Well
459.0783 -5.547 Tunnel_Well
459.059 -5.1476 Tunnel_Well
459.0449 -4.8533 Tunnel_Well
459.0362 -4.673 Tunnel_Well
459.0333 -4.6123 Tunnel_Well
459.0362 -4.673 Tunnel_Well
459.0449 -4.8533 Tunnel_Well
459.059 -5.1476 Tunnel_Well
459.0783 -5.547 Tunnel_Well
459.102 -6.0395 Tunnel_Well
459.1294 -6.61 Tunnel_Well
459.1598 -7.2412 Tunnel_Well
459.1922 -7.9139 Tunnel_Well
569.7235 -3.2874 Tunnel_Well
569.7569 -3.9812 Tunnel_Well
569.7893 -4.6539 Tunnel_Well
569.8197 -5.2851 Tunnel_Well
569.8472 -5.8556 Tunnel_Well
569.8709 -6.348 Tunnel_Well
569.8901 -6.7475 Tunnel_Well
569.9043 -7.0418 Tunnel_Well
569.913 -7.2221 Tunnel_Well
569.9159 -7.2828 Tunnel_Well
569.913 -7.2221 Tunnel_Well
569.9043 -7.0418 Tunnel_Well
569.8901 -6.7475 Tunnel_Well
569.8709 -6.348 Tunnel_Well
569.8472 -5.8556 Tunnel_Well
569.8197 -5.2851 Tunnel_Well
569.7893 -4.6539 Tunnel_Well
569.7569 -3.9812 Tunnel_Well
569.7235 -3.2874 Tunnel_Well
569.6901 -2.5936 Tunnel_Well
569.6577 -1.9209 Tunnel_Well
569.6273 -1.2897 Tunnel_Well
569.5999 -0.7192 Tunnel_Well
569.5762 -0.2268 Tunnel_Well
569.5569 0.1727 Tunnel_Well
569.5427 0.467 Tunnel_Well
569.5341 0.6473 Tunnel_Well
569.5311 0.708 Tunnel_Well
569.5341 0.6473 Tunnel_Well
569.5427 0.467 Tunnel_Well
569.5569 0.1727 Tunnel_Well
569.5762 -0.2268 Tunnel_Well
569.5999 -0.7192 Tunnel_Well
569.6273 -1.2897 Tunnel_Well
569.6577 -1.9209 Tunnel_Well
569.6901 -2.5936 Tunnel_Well
680.2214 2.0329 Tunnel_Well
680.2548 1.3391 Tunnel_Well
680.2872 0.6664 Tunnel_Well
680.3176 0.0352 Tunnel_Well
680.3451 -0.5353 Tunnel_Well
680.3688 -1.0278 Tunnel_Well
680.388 -1.4272 Tunnel_Well
680.4022 -1.7216 Tunnel_Well
680.4108 -1.9018 Tunnel_Well
680.4138 -1.9625 Tunnel_Well
680.4108 -1.9018 Tunnel_Well
680.4022 -1.7216 Tunnel_Well
680.388 -1.4272 Tunnel_Well
680.3688 -1.0278 Tunnel_Well
680.3451 -0.5353 Tunnel_Well
680.3176 0.0352 Tunnel_Well
680.2872 0.6664 Tunnel_Well
680.2548 1.3391 Tunnel_Well
680.2214 2.0329 Tunnel_Well
680.188 2.7267 Tunnel_Well
680.1556 3.3994 Tunnel_Well
680.1252 4.0305 Tunnel_Well
680.0977 4.601 Tunnel_Well
680.074 5.0935 Tunnel_Well
680.0548 5.493 Tunnel_Well
680.0406 5.7873 Tunnel_Well
680.032 5.9675 Tunnel_Well
680.029 6.0282 Tunnel_Well
680.032 5.9675 Tunnel_Well
680.0406 5.7873 Tunnel_Well
680.0548 5.493 Tunnel_Well
680.074 5.0935 Tunnel_Well
680.0977 4.601 Tunnel_Well
680.1252 4.0305 Tunnel_Well
680.1556 3.3994 Tunnel_Well
680.188 2.7267 Tunnel_Well

Many thanks

Please post x_center, y_center, and a8, b8, to reproduce exactly your code and data.

@empet Sure:
x_center(h1)=403.97665, y_center(k1)=-11.26785, a1=24.374465, b1=390.936924.

For more lines:

x z Name
452.1491 -8.9484 Tunnel_Well
451.4558 -8.9903 Tunnel_Well
450.7835 -9.0309 Tunnel_Well
450.1527 -9.069 Tunnel_Well
449.5826 -9.1035 Tunnel_Well
449.0905 -9.1332 Tunnel_Well
448.6913 -9.1573 Tunnel_Well
448.3972 -9.1751 Tunnel_Well
448.217 -9.186 Tunnel_Well
448.1564 -9.1897 Tunnel_Well
448.217 -9.186 Tunnel_Well
448.3972 -9.1751 Tunnel_Well
448.6913 -9.1573 Tunnel_Well
449.0905 -9.1332 Tunnel_Well
449.5826 -9.1035 Tunnel_Well
450.1527 -9.069 Tunnel_Well
450.7835 -9.0309 Tunnel_Well
451.4558 -8.9903 Tunnel_Well
452.1491 -8.9484 Tunnel_Well
452.8424 -8.9065 Tunnel_Well
453.5147 -8.8659 Tunnel_Well
454.1455 -8.8278 Tunnel_Well
454.7156 -8.7933 Tunnel_Well
455.2077 -8.7636 Tunnel_Well
455.6069 -8.7395 Tunnel_Well
455.901 -8.7217 Tunnel_Well
456.0812 -8.7108 Tunnel_Well
456.1418 -8.7071 Tunnel_Well
456.0812 -8.7108 Tunnel_Well
455.901 -8.7217 Tunnel_Well
455.6069 -8.7395 Tunnel_Well
455.2077 -8.7636 Tunnel_Well
454.7156 -8.7933 Tunnel_Well
454.1455 -8.8278 Tunnel_Well
453.5147 -8.8659 Tunnel_Well
452.8424 -8.9065 Tunnel_Well
455.193 -59.3234 Tunnel_Well
454.4997 -59.3653 Tunnel_Well
453.8274 -59.4059 Tunnel_Well
453.1967 -59.444 Tunnel_Well
452.6266 -59.4785 Tunnel_Well
452.1344 -59.5082 Tunnel_Well
451.7352 -59.5324 Tunnel_Well
451.4411 -59.5501 Tunnel_Well
451.261 -59.561 Tunnel_Well
451.2003 -59.5647 Tunnel_Well
451.261 -59.561 Tunnel_Well
451.4411 -59.5501 Tunnel_Well
451.7352 -59.5324 Tunnel_Well
452.1344 -59.5082 Tunnel_Well
452.6266 -59.4785 Tunnel_Well
453.1967 -59.444 Tunnel_Well
453.8274 -59.4059 Tunnel_Well
454.4997 -59.3653 Tunnel_Well
455.193 -59.3234 Tunnel_Well
455.8864 -59.2815 Tunnel_Well
456.5586 -59.2409 Tunnel_Well
457.1894 -59.2028 Tunnel_Well
457.7595 -59.1683 Tunnel_Well
458.2516 -59.1386 Tunnel_Well
458.6508 -59.1145 Tunnel_Well
458.945 -59.0967 Tunnel_Well
459.1251 -59.0858 Tunnel_Well
459.1858 -59.0822 Tunnel_Well
459.1251 -59.0858 Tunnel_Well
458.945 -59.0967 Tunnel_Well
458.6508 -59.1145 Tunnel_Well
458.2516 -59.1386 Tunnel_Well
457.7595 -59.1683 Tunnel_Well
457.1894 -59.2028 Tunnel_Well
456.5586 -59.2409 Tunnel_Well
455.8864 -59.2815 Tunnel_Well

And the X_center and y_center, a and b:
h3=453.67105, k3=-34.1359, a3=35.956521, b3=7.713191

@RamySaleem
I took only a few data, because it takes time to copy, paste and format data (insering comma after each value, adding [ ], for each row, etc).
I didn’t hit any row in the copied data that didn’t line up.

vals = np.array([[455.901, -8.7217],
[456.081, -8.7108],
[456.141, -8.7071],
[456.0812, -8.7108],
[455.6069, -8.7395],
[455.2077, -8.7636],
[454.7156, -8.7933],
[454.1455,-8.8278],
[453.5147,-8.8659],
[452.8424,-8.9065],
[449.5826,-9.1035],
[449.0905,-9.1332],
[448.6913,-9.1573],
[448.3972,-9.1751],
[448.217,-9.186],
[448.1564,-9.1897],
[448.217,-9.186]])
fig = go.Figure()

x_center=453.67105
y_center=-34.1359
a3=35.956521
b3=7.713191
x, y = ellipse(x_center=x_center, y_center=y_center, 
                   ax1 =[cos(pi/2), sin(pi/2)], ax2=[-sin(pi/2),cos(pi/2)],
                   a=a3, b =b3)
fig.add_scatter(
            x=x,
            y=y,
            mode = 'lines')
fig.add_scatter(x= vals[:, 0], y=vals[:, 1],  mode="markers", marker_size=6)
fig.update_layout(width =600, height=525)

Yeah, with small data in the midlle it looks fine but if we select data from the start and end we will see that the generated elipside unfit. I have posted to you few raws show the problem, please have a look:

([[127.9214, -28.5032],
[127.9127, -28.3229],
[127.8986,-28.0286],
[127.8793, -27.6291],
[127.8556, -27.1367],
[127.8282, -26.5662],
[680.0548, 5.493],
[680.074, 5.0935],
[680.0977, 4.601],
[680.1252, 4.0305],
[680.1556, 3.3994],
[680.188, 2.7267]])

The orange ellipsoid shows it’s not perfectly covering the data points. I also realised that if we plot just one ellipsoid is not clearly showing the problem. Check the orange ellipsoid on the first image not showing clearly the rotation problem because of the vertical exaggeration but it’s still there?


Are you sure that all data coordinates are inside some ellipse? I cannot reproduce your plot, because I have only dthe center and axes for a single ellipse.

Your problem isn’t the Plotly trace definition, but your data.

Yes they are inside the same ellipse, and I belevie my data is correct. I think we need to consider the roation angle of the ellipsod! This is the code I use to generat the plot:

Create figure
fig = go.Figure()
fig.add_trace(go.Scatter(x=x11, y=z11, name="line-1", mode="markers", marker_color='gray'))

well-1
x_center=404.0081
y_center=-11.505099999999999
x1, y1 = ellipse_new(x_center=x_center, y_center=y_center, ax1 =[cos(pi/2), sin(pi/2)], ax2=[-sin(pi/2),cos(pi/2)], a=24.038943, b =390.577360)

fig.add_scatter( x=x1, y=y1, mode = 'lines')

fig.update_layout(showlegend=True)
fig.show()

I just add this to make the plot longer in the y-axis and make the probelm clearer
fig.add_scatter( x=x8, y=y8, mode = 'lines')

The ellipse_new is your new function and x11, and z


11 is the data I posted before.
If you change pi/6 to be pi/2, the ellipsoid will rotate and become horizontal.
I hope you can reproduce this now! Thanks in Advance!

I have also plotted the same data on matplotlib using the following function:

def confidence_ellipse(x, y, ax, n_std=3.0, facecolor='none', **kwargs): if x.size != y.size: raise ValueError("x and y must be the same size") cov = np.cov(x, y) pearson = cov[0, 1]/np.sqrt(cov[0, 0] * cov[1, 1]) ell_radius_x = np.sqrt(1 + pearson) ell_radius_y = np.sqrt(1 - pearson) ellipse = Ellipse((0, 0), width=ell_radius_x * 2, height=ell_radius_y * 2, facecolor=facecolor, **kwargs) scale_x = np.sqrt(cov[0, 0]) * n_std mean_x = np.mean(x) scale_y = np.sqrt(cov[1, 1]) * n_std mean_y = np.mean(y) transf = transforms.Affine2D() \ .rotate_deg(45) \ .scale(scale_x, scale_y) \ .translate(mean_x, mean_y) ellipse.set_transform(transf + ax.transData) return ax.add_patch(ellipse)

The code as follow:

fig, ax_kwargs = plt.subplots(figsize=(16, 16)) ax_kwargs.axvline(c='grey', lw=1) ax_kwargs.axhline(c='grey', lw=1)

x, y = x11, z11 xmin=min(x) ymin=min(y) xmax=max(x) ymax=max(y) h = (xmin + xmax)/2 k = (ymin + ymax)/2 mu = h, k

confidence_ellipse(x, y, ax_kwargs, alpha=0.5, facecolor='pink', edgecolor='purple', zorder=0)

`ax_kwargs.scatter(x, y, s=0.5)
ax_kwargs.scatter(mu[0], mu[1], c=‘red’, s=3)
ax_kwargs.set_title(‘Uncertainty Modelling’)

fig.subplots_adjust(hspace=0.25) plt.show()

Please see the resulting image.

I really want to use Plotly since its interactivity, and I can easily get the ellipsoid coordinates by your function to apply further analysis!

Hi!
i’m really interested in this topic too, thank’s both for maintain alive this thread.
I was trying to plot a covariance ellipse in a plotly polar plot with no luck yet.

I just find an article that could help (code is at the bottom) on this question:
://www.visiondummy.com/2014/04/draw-error-ellipse-representing-covariance-matrix/

1 Like

Hi Laikus, thanks for the interst! Yeah, I think it is important to draw the ellipsoid with the correct rotational angle to the data points! In the example, you can get the max and min of your point then get the rectangle height and wedith. Then get the x-centre and y-centre, a and b to draw your ellipsoid. I believe empet function will plot your ellipsoid perfectly since the rotational angle is not that important as in my case, You can use:

 def get_centre_a_b(source_df):
    x_source = source_df['x']
    z_source = source_df['z']
    xmin=min(x_source)
    ymin=min(z_source)
    xmax=max(x_source)
    ymax=max(z_source)
    h = (xmin + xmax)/2
    k = (ymin + ymax)/2
    rh = ymax - ymin
    a=rh/math.sqrt(2)
    rw = xmax - xmin
    b=rw/math.sqrt(2)
    
    return h, k, a, b, rh, rw 

Then apply empt function mentioned above.

@empet I have also put my data in a g-drive to make it easy to re-produce my plots.

https://drive.google.com/drive/folders/1JGKdnfqu9aZy8YFtL_2kL-NnJ-fJHcMM?usp=sharing

One of the solutions I found is to include the rotational angel when we creating the ellipsoids.

When we call the function, and we define the rotational angle as follow:

Example-1:

Notice that we define pi/1.93, which will result in the rotational angel in my model. This will be user define and verified visually by the user.


# well-4

x_center = h4

y_center = k4

angel_4 = pi/1.93

x, y = ellipse(x_center=x_center, y_center=y_center, ax1 =[cos(angel_4), sin(angel_4)], ax2=[-sin(angel_4),cos(angel_4)], a=a4, b =b4)

fig.add_scatter(x=x, y=y, mode = 'lines', name='Zone-1 - well-4', fill='toself', opacity=0.5)

Example-2:

Notice that we define pi/1.935 to rotate the ellipse.


# well-5

x_center = h5

y_center = k5

angel_5 = pi/1.935

x, y = ellipse(x_center=x_center, y_center=y_center, ax1 =[cos(angel_5), sin(angel_5)], ax2=[-sin(angel_5),cos(angel_5)], a=a5-20, b =b5-140)

fig.add_scatter(x=x, y=y, mode = 'lines', name='Zone-1 - well-5', fill='toself', opacity=0.5)

Example-3

Notice that we define pi/1.921 to rotate the ellipse.


# well-6

x_center=h6

y_center=k6

angel_6 = pi/1.921

x, y = ellipse(x_center=x_center, y_center=y_center, ax1 =[cos(angel_6), sin(angel_6)], ax2=[-sin(angel_6),cos(angel_6)], a=a6-25, b =b6-140)

fig.add_scatter(x=x, y=y, mode = 'lines', name='Zone-1 - well-6', fill='toself', opacity=0.8)

Please see the attached figure as an example.