Plotly arrow in scattergeo with text on the line

I have the following code to generate the plot with Plotly in Python 3.7. I am trying to make the line an arrow instead that is pointing to Fort Lauderdale. Also, in the center of the line, I want to have some text. There are many references but no proper answer available.

import numpy as np
import pandas as pd
import plotly.graph_objects as go

np.random.seed(1)
I_list = list(np.random.randint(0,3229,10))
skiplist = set(range(1, 3229)) - set(I_list)
locations = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/2014_us_cities.csv', skiprows=skiplist)

scale = 5000
fig = go.Figure()
fig.add_trace(go.Scattergeo(
        locationmode = 'USA-states',
        lon = locations['lon'],
        lat = locations['lat'],
        text = locations['name'],
        textfont = {"color": 'black',
                    "family":'Times New Roman',
                    "size":14},
        textposition="top center",
        name = "Candidate Facility",
        mode ="markers+text",
        marker = dict(
            size = 10,
            color = "black",
            line_color='black',
            line_width=0.5,
            sizemode = 'area')))

fig.add_trace(go.Scattergeo(
    lat = [locations['lat'][5], locations['lat'][0]],
    lon = [locations['lon'][5], locations['lon'][0]],
    mode = 'lines',
    line = dict(width = 2, color = 'blue'),
))

fig.update_layout(
        showlegend = False,
        geo = dict(
            scope = 'usa',
            landcolor = 'rgb(217, 217, 217)'))
fig.show()

enter image description here

2 Likes

Hi @tcokyasar,

Plotly does not provide an arrow marker but with a few lines of code we get it:

import numpy as np
import pandas as pd
import plotly.graph_objects as go

np.random.seed(1)
I_list = list(np.random.randint(0,3229,10))
skiplist = set(range(1, 3229)) - set(I_list)
locations = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/2014_us_cities.csv', skiprows=skiplist)

scale = 5000
fig = go.Figure()
fig.add_trace(go.Scattergeo(
        locationmode = 'USA-states',
        lon = locations['lon'],
        lat = locations['lat'],
        text = locations['name'],
        textfont = {"color": 'black',
                    "family":'Times New Roman',
                    "size":14},
        textposition="top center",
        name = "Candidate Facility",
        mode ="markers+text",
        marker = dict(
            size = 10,
            color = "black",
            line_color='black',
            line_width=0.5,
            sizemode = 'area')))

fig.add_trace(go.Scattergeo(
    lat = [locations['lat'][5], locations['lat'][0]], 
    lon = [locations['lon'][5], locations['lon'][0]],
    mode = 'lines',
    line = dict(width = 1.5, color = 'blue'),
))

#Workaround to get the arrow at the end of an edge AB

l = 1.1  # the arrow length
widh =0.035  #2*widh is the width of the arrow base as triangle

A = np.array([locations['lon'][5], locations['lat'][5]])
B = np.array([locations['lon'][0], locations['lat'][0]])
v = B-A
w = v/np.linalg.norm(v)     
u  =np.array([-v[1], v[0]])  #u orthogonal on  w
         
P = B-l*w
S = P - widh*u
T = P + widh*u

fig.add_trace(go.Scattergeo(lon = [S[0], T[0], B[0], S[0]], 
                            lat =[S[1], T[1], B[1], S[1]], 
                            mode='lines', 
                            fill='toself', 
                            fillcolor='blue', 
                            line_color='blue'))
#------Display your text at the middle of the segment AB
fig.add_trace(go.Scattergeo(lon =[0.5*(A+B)[0]], lat = [0.5*(A+B)[1]], mode='text', text='               Your text'))

fig.update_layout(width=900, height=750,
        showlegend = False,
        geo = dict(
            scope = 'usa',
            landcolor = 'rgb(217, 217, 217)'))

1 Like

@empet I really appreciate for this response. I also wonder if there is any way to use self pointing (loop) arrows for some locations, e.g., Hobbs? Should I open a new topic or can you also help me with this problem as well?

@tcokyasar I’ll give the answer here, because it is also a graph edge. But let me try first.

I wanted to note for anyone looking at this that the width of the arrows grow as the distance between points grow with the current solution. Judging from the comments in code I think @empet may have accidentally typed ‘v’ instead of ‘w’ when calculating ‘u’.

Original showing all states ( u =np.array([-v[1], v[0]]) ):
screenshot1

Version replacing u = np.array([-v[1], v[0]]) with u = np.array([-w[1], w[0]])*10
image

@empet @mpkrass Thanks for this solution…
I do have an unfortunate artifact… See in the example below. The arrows don’t follow the ‘great circle’ sort of lines instead seems to be pointed at the direct line. So short distances work okay, but longer ones not. (e.g. the red is very obvious, or if you zoom in )

any suggestion how I can have hem point along the line

image
image

image

note as projection I have:

fig.update_layout(
    showlegend = True,
    geo = dict(
    #scope = 'world',
    #projection_type = 'azimuthal equal area',
    projection_type="natural earth",
    showland = True,
    landcolor = 'rgb(243, 243, 243)',
    subunitcolor = "rgb(217, 217, 217)",
    countrycolor = "rgb(217, 217, 217)",
    countrywidth = 1,
    subunitwidth = 0.5,
    showcountries=True,  
     ),
)

@mrv
Your arrows have a wrong direction because almost surely their end points are not placed on the line giving the corresponding aeeow direction.
For shortest path between two points on a map it’s a bit more complicated to choose two points on that line (i.e. the curved line on your map).
Some time ago I explained on this forum how is parameterized the shortest path between two points, by the slerp (spherical linear interpolation). With that parameterization you will be able to select two points on one of its ends to draw the arrow between them.

@empet Perhaps you have the link for that explanation on the parametrization of the shortest path? I’m doing some work where the arrows are the most important aspect of the world chart!

In this thread Scattermapbox plot curved lines like Scattergeo you can find the functions point_sphere, and slerp that implement the shortest path on maps.
To get more details on slerp just google for this term.