3

I have a chain of balls. On each ball I have an arrow.

enter image description here

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.

enter image description here

Derek O
  • 16,770
  • 4
  • 24
  • 43
Suslik
  • 929
  • 8
  • 28
  • 2
    Could you give an error message or something, so we can help you out from there, instead of just giving a problem and saying: "How to solve this?" Please try out some different techniques than just one. You can check out https://stackoverflow.com/help/how-to-ask – root Mar 24 '21 at 21:54
  • I am a beginner with Plotly. I thought I can use a similar method like add_trace to connect points in the 3d plot with arrows. Then instead of using the mode "lines" I thought I could use the mode "arrows". But it seems there is no such function. I try to reformulate my question. – Suslik Mar 24 '21 at 22:02
  • 2
    I think that's a bit harsh, @lukas. The poster made a reasonable effort to solve the problem, and Plotly isn't the easiest library to use. In this instance there isn't going to be an error message but the method used to create arrows isn't going to give the poster the chart they want - probably an annotation will work as intended. – Derek O Mar 25 '21 at 02:50
  • 1
    Hey @DerekO. You are right :). Thanks, man, I will reconsider and think about you next time, I see something, where people need help, and it looks like a quiz question -ish :) Have a great day. – root Mar 25 '21 at 19:34

1 Answers1

10

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')

![enter image description here

Derek O
  • 16,770
  • 4
  • 24
  • 43
  • 1
    Thank you very much for the answer. Is it correct that it is not possible to remove the marker points on one side (for example with opacity=0.01) of the connection-line, since one can just modify the line and the markers of the scatter plot at the same time. – Suslik Mar 25 '21 at 09:55
  • 1
    Since you are plotting the starting and ending markers in `trace1` and the lines in `trace2`, you can instead plot only the starting markers in `trace1`. I'll modify my answer when I have a moment – Derek O Mar 25 '21 at 16:57
  • 1
    I have updated my answer to only show the starting marker and the line to the ending marker, but no ending marker. Let me know if this looks good! – Derek O Mar 25 '21 at 19:02
  • 1
    Wow! Thank you so much for your help and your time. Now it is perfect!! All the best for you! – Suslik Mar 25 '21 at 22:37