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:
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:
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:
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.
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()