I have a chain of balls. On each ball I have an arrow.
I want to represent the above graphic with plotly,such that I can rotate it, but I am completely lost. How can I position an arrow on the ball?
I started to first 4 arrows as follows:
import plotly.graph_objs as go
import plotly
plotly.offline.init_notebook_mode()
x = [10.1219, 10.42579, 15.21396, 15.42468, 20.29639,20.46268, 25.36298, 25.49156]
y = [5.0545, 5.180104, 5.0545, 5.20337, 5.0545, 5.194271, 5.0545, 5.231627]
z = [5.2713, 5.231409, 5.2713, 5.231409, 5.2713 , 5.235852, 5.2713, 5.231627]
pairs = [(0,1), (2,3),(4,5), (6,7)]
trace1 = go.Scatter3d(
x=x,
y=y,
z=z,
mode='markers',
name='markers'
)
x_lines = list()
y_lines = list()
z_lines = list()
for p in pairs:
for i in range(2):
x_lines.append(x[p[i]])
y_lines.append(y[p[i]])
z_lines.append(z[p[i]])
x_lines.append(None)
y_lines.append(None)
z_lines.append(None)
trace2 = go.Scatter3d(
x=x_lines,
y=y_lines,
z=z_lines,
line = dict(width = 2, color = 'rgb(255, 0,0)'))
)
fig = go.Figure(data=[trace1, trace2])
plotly.offline.iplot(fig, filename='simple-3d-scatter')
But instead of lines that connects the pair-points I would like to have arrows.
My first thought was that you could use annotations to draw arrows as a workaround, but annotations can only draw arrows in x and y dimensions, as described in answers to a similar question. Plotly has not implemented annotation arrows where you can specify all three dimensions.
However, @Abdul Saboor suggested a clever hack which is to draw a cone at the end of your line segments using a basic cone plot. The parameters x, y, z
give you the starting location for the base of the cone, and the parameters u, v, w
give you the vector field for the direction that the cone will point in.
Therefore, for each pair of points, we can set the starting point for the base of the cone to be, say 95% of the way between the starting and ending ball, and the direction of the cone will be the vector pointing from the starting to the ending points, and the magnitude can be whatever you like, say 10% of the distance between the points so the cone is small and looks nice. You can adjust these using arrow_tip_ratio
and arrow_starting_ratio
that I set to these arbitrary values.
EDIT: to plot only the starting point and not the ending point, you can modify trace1
by accessing only the first x, y, z coordinate for each starting and ending ball using a list comprehension. Then you should specify the parameter mode='lines'
for trace2
so that you only plot the line over the first of each starting and ending point.
import plotly.graph_objs as go
# plotly.offline.init_notebook_mode()
x = [10.1219, 10.42579, 15.21396, 15.42468, 20.29639,20.46268, 25.36298, 25.49156]
y = [5.0545, 5.180104, 5.0545, 5.20337, 5.0545, 5.194271, 5.0545, 5.231627]
z = [5.2713, 5.231409, 5.2713, 5.231409, 5.2713 , 5.235852, 5.2713, 5.231627]
pairs = [(0,1), (2,3),(4,5), (6,7)]
## plot ONLY the first ball in each pair of balls
trace1 = go.Scatter3d(
x=[x[p[0]] for p in pairs],
y=[y[p[0]] for p in pairs],
z=[z[p[0]] for p in pairs],
mode='markers',
name='markers',
line=dict(color='red')
)
x_lines = list()
y_lines = list()
z_lines = list()
for p in pairs:
for i in range(2):
x_lines.append(x[p[i]])
y_lines.append(y[p[i]])
z_lines.append(z[p[i]])
x_lines.append(None)
y_lines.append(None)
z_lines.append(None)
## set the mode to lines to plot only the lines and not the balls/markers
trace2 = go.Scatter3d(
x=x_lines,
y=y_lines,
z=z_lines,
mode='lines',
line = dict(width = 2, color = 'rgb(255, 0,0)')
)
fig = go.Figure(data=[trace1, trace2])
arrow_tip_ratio = 0.1
arrow_starting_ratio = 0.98
## the cone will point in the direction of vector field u, v, w
## so we take this to be the difference between each pair
## then hack the colorscale to force it to display the same color
## by setting the starting and ending colors to be the same
for p in pairs:
fig.add_trace(go.Cone(
x=[x[p[0]] + arrow_starting_ratio*(x[p[1]] - x[p[0]])],
y=[y[p[0]] + arrow_starting_ratio*(y[p[1]] - y[p[0]])],
z=[z[p[0]] + arrow_starting_ratio*(z[p[1]] - z[p[0]])],
u=[arrow_tip_ratio*(x[p[1]] - x[p[0]])],
v=[arrow_tip_ratio*(y[p[1]] - y[p[0]])],
w=[arrow_tip_ratio*(z[p[1]] - z[p[0]])],
showlegend=False,
showscale=False,
colorscale=[[0, 'rgb(255,0,0)'], [1, 'rgb(255,0,0)']]
))
fig.show()
# plotly.offline.iplot(fig, filename='simple-3d-scatter')