go.Scatter() plot with oriented markers (for example tadpole markers)


I am trying to create a go.Scatter() plot with marker symbols (for example tadpoles) that are oriented.
In the doc, I didn’t find any property to modify the orientation of marker symbols.

Is there perhaps a workaround?

I found that in Matplotlib, there is such a functionality (link).

Thanks a lot in advance!

Hi @vont,
This is the Plotly version for oriented markers:

import plotly.graph_objects as go
import numpy as np
tris= ["triangle-up", "triangle-down", "triangle-left", "triangle-right"]
fig=go.Figure(go.Scatter(x=[1,2,3,4,5,6], y=np.random.randint(1, 13, 6), 
                         mode="markers", marker_size=15,
                         marker_symbol= [tris[k] for k in np.random.randint(0, 4, 6)], 
                         marker_color=["red", "blue", "orange", "green", "yellow", "black"])
                         #or marker=dict(size=15, symbol=[tris[k] for k in np.random.randint(0, 4, 6)], color=["red"])

fig.update_layout(width=500, height=450)

or single color markers, defined as a dict in the commented line:

1 Like

Hello empet

Thanks a lot for your reply. It’s not super important but my intention was to add azimuth information to the markers, so that I could have the markers be oriented in any direction, depending of what azimuth is provided. Such plots are used in geology to illustrate the orientation of borehole fracture data.

If the azimuth is indicated at the marker with a small tail sticking out from let’s say a round marker, it looks a bit like a tadpole which explains the name of these plots.

Would it be difficult to develop such a functionality inside Plotly?

Best wishes and again many thanks for your reply,

Hello @vont,
It is possible to draw a shape as a filled closed Bezier curve, that looks like a tadpole. At the beginning of the next week I’ll post the code, that generates such a Bezier curve. Just now I am in a sort of vacation.

Hi Empet

Looking good there with all the sun you have. Enjoy your stay and I am looking forward to seeing how this works with the Bezier curves.

Best wishes,

Hello @vont,
Here are the pseudo-tadpoles. You can customize their shape changing the parameters and constants in the function
control_polygon. Initially I suggested to define tadpoles as plotly shapes, but after a few experiments I realised that drawing them as filled closed curves is much faster than defining the curve as a svg path in a shape definition. Since so far I haven’t seen a plot with tadpoles I didn’t know whether the tadpole tail is pointing to a an element or its head. I chose the tail.

On the other hand, if a tadpole could be defined by only 4 points with coincident first and last points, then there was no need to use the de Casteljau algorithm because plotly offers the possibility to define a cubic Bezier curve (i.e. with 4 control points) as a plotly shape. But with only 3 distinct points a tadpole would look like a pin for geographical location: https://chart-studio.plotly.com/~empet/15362

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

def rot(theta):
    return np.array([[cos(theta), -sin(theta)], [sin(theta), cos(theta)]])

def control_polygon(x=0, y=0,  width=0.1, height=0.18):
    # defines the bezier control points for a standard tadpole with tail at (0,0), and axis Oy as a symmetry line
    # width is the largest width measured in the horizontal direction 
    # height is the height of curve between the first control point and intersection point with the vertical through it
    # The parameters and constants in the ctrl elements definition have been set by trial and error
    ctrl=  np.array([[x, y],
                     [x - 0.1*width, y + 1.65*height],       
                     [x - sqrt(3)*width, y + 4*height/3], 
                     [x + sqrt(3)*width, y + 4*height/3], 
                     [x + 0.1*width, y + 1.65*height],                
                     [x, y]])
    return ctrl

def deCasteljau(ctrl, t): #de Casteljau algorithm to evaluste a point on a Bezier curve
    #ctrl an array of shape (6, 2) 
    #t a number in [0,1]
    #returns the point on the Bezier curve, corresponding to t
    N = ctrl.shape[0] 
    if N != 6:
        raise ValueError("A tadpole must be defined by a closed Bezier curve of 6 ctrl points") 
    a = np.copy(ctrl) 
    for r in range(1, N): 
        a[:N-r, :] = (1-t) * a[:N-r, :] + t * a[1:N-r+1, :]# convex combinations in the r^th step                                
    return a[0, :]

def cBezier(ctrl, nr=30):# evaluates nr points on the Bezier curve of control, points ctrl
    t = np.linspace(0, 1, nr)
    return np.asarray([deCasteljau(ctrl, t[k]) for k in range(nr)]) 

#Draw tadpoles pointing to markers from a prescribed direction (angle) 

X = [1, 2, 3]
Y = [1, 1.3, 2.4]
Theta = [-30, 45, 60]
fig = go.Figure(go.Scatter(x=X, y=Y, mode="markers", marker_color="red", 
                           marker_size=4.5, name="my markers"))
tadpole_x = []
tadpole_y = []
for x, y, t in zip(X, Y, Theta):
    c = control_polygon() #standard ctrl polygon 
    c = (rot(t) @ c.T).T + np.array([x,y]) #rotate the control polygon with t degrees and translate it in the direction (x,y)
    b = cBezier(c) # the Bezier curve as the boundary of a tadpole
    tadpole_x.extend(list(b[:, 0])+[None]) #insert a None after each tadpole
    tadpole_y.extend(list(b[:, 1])+[None])
fig.add_scatter(x=tadpole_x, y=tadpole_y, fill="toself", 
                fillcolor="RoyalBlue", line_color="RoyalBlue", showlegend=False)
fig.update_layout(width=600, height=450, yaxis_scaleratio=1, yaxis_scaleanchor="x")


Hi empet

Many thanks for your reply! This time I am en route - not vacation - but just not at the desk.
This looks brilliant - I’ll have a more detailed look at your code next time I’m at home.

Many thanks again and have a good weekend!

Hello @empet,
It is very close to what I want below:


It will be great if more marks were implemented in plotly.