Search code examples
pythonplotplotlyfill

Plot graph with area of two data set and get probability


I am trying to plot a path of rotating rod ends as it moves forward in space. I made a vector that rotates around the centre with certain rotational speed and then move the vector to the place next place with certain translational speed. I made the whole function work with time-steps (for loop as time-step), so I can make it more accurate if want. So the result is this: enter image description here

def rotate_vector(vector, angle):
    x = vector[0] * math.cos(angle) - vector[1] * math.sin(angle)
    y = vector[0] * math.sin(angle) + vector[1] * math.cos(angle)
    return [x, y]


fig = go.Figure()
rod_1 = np.array([0, -(1.0/2)])
rod_2 = np.array([0, (1.0/2)])
center = np.array([-0.5,0.0])
fig.update_layout(yaxis_range=[-5, 5])
fig.update_layout(xaxis_range=[-2,12])
fig.update_layout(width=1000, height=500)
fig.update_layout(showlegend=False)
#plt.autoscale(False)
speed = 13 #m/s
rot_speed = (math.pi*2*1.5)/(11.50 -10.69) #deg/s
step_size = 0.01
rod_end_1 = {"x": [], "y": []}
rod_end_2 = {"x": [], "y": []}
for i in np.arange(0, 1, step_size): #s
   fig.add_trace(go.Scatter(x = [center[0] ,rod_1[0] + center[0]], y = [center[1], rod_1[1]]))
   fig.add_trace(go.Scatter(x = [center[0] ,rod_2[0] + center[0]], y = [center[1], rod_2[1]]))
   rod_end_1["x"].append(rod_1[0] + center[0])
   rod_end_1["y"].append(rod_1[1] + center[1])
   rod_end_2["x"].append(rod_2[0] + center[0])
   rod_end_2["y"].append(rod_2[1] + center[1])
   rod_1 = rotate_vector(rod_1, rot_speed * step_size)
   rod_2 = rotate_vector(rod_2, rot_speed * step_size)
   center[0] += step_size * speed
fig.show()

With this I get the paths of the rod ends. What I want to do, is fill this whole path, but I can't get it like I want it: enter image description here I want to "fill" look like the first picture with fill, but it doesn't look like it. Here is the code with using plotly:

fig2 = go.Figure()
fig2.update_layout(yaxis_range=[-1, 1])
fig2.update_layout(xaxis_range=[-1,15])
fig2.update_layout(width=1000, height=500)
fig2.add_trace(go.Scatter(x = rod_end_1["x"], y = rod_end_1["y"], fill='tonexty'))
fig2.add_trace(go.Scatter(x = rod_end_2["x"], y = rod_end_2["y"], fill='tonexty'))
fig2.show()

Any good ideas how to make it look like the first one with fill?

In the end I would like to get the "probability" or precent of area covert from rods moving point of view. So in the centre it would be 100% and on the positive y-axis it would be little lower than in negative y-axis. Like Gaussian plot.

If anyone has a good idea on how to implement this, or any improvements to the code I've currently written, that would be much appreciated. Thanks!

Here is an example what I'm searching:

enter image description here

So the area that rod covers by moving, will be filled and in the bottom there would be a present level of how much area is covered.


Solution

  • You'll want to plot the coordinates of one end of rod 1 in order (over time), then plot the coordinates of the other end of rod 1, then fill the area in between those two traces. Then do the same for rod 2.

    Determining the probabilities is significantly more difficult as you have to find the area under the curves for the areas swept out by both rod 1 and rod 2. The code is wrote is hacky in the sense that there are hard-coded quantities and edge cases to account for, but it applies the trapezoid rule to find the area between consecutive points along each curve individually, then adds them together. I also correct for the region where rod 1 and rod 2 entirely overlap. It's not perfect, but it's about as close I can get for the time being.

    import math
    from math import isclose
    import numpy as np
    import plotly.graph_objects as go
    from plotly.subplots import make_subplots
    
    def rotate_vector(vector, angle):
        x = vector[0] * math.cos(angle) - vector[1] * math.sin(angle)
        y = vector[0] * math.sin(angle) + vector[1] * math.cos(angle)
        return [x, y]
    
    fig = go.Figure()
    rod_1 = np.array([0, -(1.0/2)])
    rod_2 = np.array([0, (1.0/2)])
    center = np.array([-0.5,0.0])
    fig.update_layout(yaxis_range=[-2, 13])
    fig.update_layout(width=1000, height=500)
    fig.update_layout(showlegend=False)
    #plt.autoscale(False)
    speed = 13 #m/s
    rot_speed = (math.pi*2*1.5)/(11.50 -10.69) #deg/s
    step_size = 0.01
    rod_end_1 = {"x": [], "y": []}
    rod_end_2 = {"x": [], "y": []}
    centers = {"x": [], "y": []}
    for i in np.arange(0, 1, step_size): #s
        # swap x and y coordinates
        rod_end_1["y"].append(rod_1[0] + center[0]) 
        rod_end_1["x"].append(rod_1[1] + center[1])
        rod_end_2["y"].append(rod_2[0] + center[0])
        rod_end_2["x"].append(rod_2[1] + center[1])
        rod_1 = rotate_vector(rod_1, rot_speed * step_size)
        rod_2 = rotate_vector(rod_2, rot_speed * step_size)
        center[0] += step_size * speed
        centers["y"].append(center[0])
        centers["x"].append(center[1]) # this coordinate doesn't change, but in case it does later, you'll want to store it
    
    fig = make_subplots(rows=2, cols=1)
    
    ## traces for rod_1
    fig.add_trace(go.Scatter(
        x=rod_end_1['x'],
        y=rod_end_1['y'],
        marker=dict(color='#636EFA'),
    ), row=1, col=1)
    fig.add_trace(go.Scatter(
        x=centers['x'],
        y=centers['y'],
        marker=dict(color='#636EFA'),
        fill='tonexty',
    ), row=1, col=1)
    
    ## traces for rod_2
    fig.add_trace(go.Scatter(
        x=rod_end_2['x'],
        y=rod_end_2['y'],
        marker=dict(color='#EF553B'),
    ), row=1, col=1)
    fig.add_trace(go.Scatter(
        x=centers['x'],
        y=centers['y'],
        marker=dict(color='#EF553B'),
        fill='tonexty',
    ), row=1, col=1)
    
    ## the area curve is difficult
    ## the following method might not be easily generalizable 
    
    rod_end_1['x_arr'] = np.array(rod_end_1['x'])
    rod_end_1['y_arr'] = np.array(rod_end_1['y'])
    rod_end_2['x_arr'] = np.array(rod_end_2['x'])
    rod_end_2['y_arr'] = np.array(rod_end_2['y'])
    
    fig.add_shape(type="line",
        x0=-0.6, y0=-0.5, x1=0.6, y1=-0.5,
        line=dict(
            width=2,
            dash="dot",
        )
    )
    fig.add_shape(type="line",
        x0=-0.6, y0=12, x1=0.6, y1=12,
        line=dict(
            width=2,
            dash="dot",
        )
    )
    fig.add_shape(type="line",
        x0=-0.5, y0=-1, x1=-0.5, y1=13,
        line=dict(
            width=2,
            dash="dot",
        )
    )
    fig.add_shape(type="line",
        x0=0.5, y0=-1, x1=0.5, y1=13,
        line=dict(
            width=2,
            dash="dot",
        )
    )
    
    rod_intersections = [rod_end_1['x_arr'][10], 0]
    
    fig.add_shape(type="line",
        x0=rod_intersections[0], y0=-1, x1=rod_intersections[0], y1=13,
        line=dict(
            width=2,
            dash="dot",
        )
    )
    
    
    # first_intersection = rod_end_1['x'][17]
    # trapezoid area formula = h(a+b)/2
    bins_areas = {}
    for i,x in enumerate(rod_end_1['x']):
        if isclose(rod_end_1['x'][i], 0.5):
            break
        else:
            bin_start, bin_end = rod_end_1['x'][i],rod_end_1['x'][i+1]
            bins_areas[(bin_start, bin_end)] = 0
    
    ## go through first two regions swept out by rod 1
    for bin in bins_areas.keys():
        bin_start, bin_end = bin
        h = bin_end - bin_start
        start_idx = [isclose(x, bin_start) for x in rod_end_1['x_arr']]
        end_idx = [isclose(x, bin_end) for x in rod_end_1['x_arr']]
        y_vals_start = rod_end_1['y_arr'][start_idx]
        y_vals_end = rod_end_1['y_arr'][end_idx]
        if y_vals_end[0] <= 1.68915408:
            a = y_vals_start[0] - (-0.5)
            b = y_vals_end[0] - (-0.5)
        elif isclose(bin_end, 0.5):
            a = y_vals_start[1] - y_vals_start[0]
            b = 0 # we have reached a point on the other side
        else:
            a = y_vals_start[1] - y_vals_start[0]
            b = y_vals_end[1] - y_vals_end[0]
        area = h*(a+b)/2
        bins_areas[bin] += area
    
    ## go through next two regions swept out by rod 1
    for bin in bins_areas.keys():
        bin_start, bin_end = bin
        h = bin_end - bin_start
        start_idx = [isclose(x, bin_start) for x in rod_end_1['x_arr']]
        end_idx = [isclose(x, bin_end) for x in rod_end_1['x_arr']]
        y_vals_start = rod_end_1['y_arr'][start_idx]
        y_vals_end = rod_end_1['y_arr'][end_idx]
        if isclose(bin_start, -0.5):
            a = 0
            b = y_vals_end[2] - y_vals_end[1]
        elif y_vals_end[0] <= 1.68915408:
            a = y_vals_start[2] - y_vals_start[1] # 3rd point up - 2nd point up
            b = y_vals_end[2] - y_vals_end[1]
        elif isclose(bin_end, 0.5):
            a = y_vals_start[3] - y_vals_start[2]
            b = 0 # we have reached a point on the other side
        else:
            a = y_vals_start[3] - y_vals_start[2] # 4th point up - 3rd point up
            b = y_vals_end[3] - y_vals_end[2]
        area = h*(a+b)/2
        bins_areas[bin] += area
    
    ## go through each area swept out by rod 2
    for bin in reversed(bins_areas.keys()):
        bin_start, bin_end = bin
        h = bin_end - bin_start
        start_idx = [isclose(x, bin_start) for x in rod_end_2['x_arr']]
        end_idx = [isclose(x, bin_end) for x in rod_end_2['x_arr']]
        y_vals_start = rod_end_2['y_arr'][start_idx]
        y_vals_end = rod_end_2['y_arr'][end_idx]
        if isclose(bin_end, 0.5):
            a = y_vals_start[0] - (-0.5)
            b = 0
        elif y_vals_start[0] <= 0.82:
            a = y_vals_start[0] - (-0.5)
            b = y_vals_end[0] - (-0.5)
        elif isclose(bin_end, -0.5):
            a = 0
            b = y_vals_start[1] - y_vals_start[0] # we have reached a point on the other side
        else:
            a = y_vals_start[1] - y_vals_start[0]
            b = y_vals_end[1] - y_vals_end[0]
        area = h*(a+b)/2
        bins_areas[bin] += area
    
    ## highest two areas swept out by rod 2
    for bin in reversed(bins_areas.keys()):
        bin_start, bin_end = bin
        h = bin_end - bin_start
        start_idx = [isclose(x, bin_start) for x in rod_end_2['x_arr']]
        end_idx = [isclose(x, bin_end) for x in rod_end_2['x_arr']]
        y_vals_start = rod_end_2['y_arr'][start_idx]
        y_vals_end = rod_end_2['y_arr'][end_idx]
        if isclose(bin_end, 0.5):
            a = y_vals_start[2] - y_vals_start[1]
            b = 0
        elif y_vals_start[0] <= 0.82:
            a = y_vals_start[2] - y_vals_start[1]
            b = y_vals_end[2] - y_vals_end[1]
        elif isclose(bin_start, -0.5):
            a = 0
            b = y_vals_end[3] - y_vals_end[2] # we have reached a point on the other side
        else:
            a = y_vals_start[3] - y_vals_start[2]
            b = y_vals_end[3] - y_vals_end[2]
        area = h*(a+b)/2
        bins_areas[bin] += area
    
    for bin,area in bins_areas.items():
        bin_start, bin_end = bin
        if (bin_start >= -0.2) and (bin_start <= 0):
            bin_area_corrected = (bin_end - bin_start)*(12 - (-0.5))
            bins_areas[bin] = bin_area_corrected
    
    bin_probabilities = {}
    for bin,area in bins_areas.items():
        bin_start, bin_end = bin
        total_area = (bin_end - bin_start)*(12 - (-0.5))
        bin_probabilities[bin] = area / total_area
    
    bin_medians = [(x1+x2)/2 for x1,x2 in bins_areas.keys()]
    # custom width for bars?
    fig.add_trace(go.Scatter(
        x=bin_medians,
        y=list(bin_probabilities.values()),
    ), row=2, col=1)
    
    fig.update_layout(
        xaxis1=dict(range=[-0.6,0.6]),
        xaxis2=dict(range=[-0.6,0.6]),
        showlegend=False,
    )
    
    fig.show()
    

    enter image description here