Hyperlink to markers on map

I’m having difficulties inserting a hyperlink to the map markers.

This is the map I created of NYC schools: map_of_schools

my code:

schools=[]

for i in interest_areas:
    df_sub=df4[df4.interest1==i]
    school = dict(
        type = 'scattermapbox',
        locationmode = 'USA-states',
        lon = df_sub['Longitude'],
        lat = df_sub['Latitude'],
        sizemode = 'diameter',
        mode='markers',
        marker = dict(
            size = (df_sub['Performance']*10)**1.8,
            line = dict(width = 2,color = 'black')
        ),
        # legend name
        name = df_sub['interest1'].values[0], #legend interest name
        hovertext=df_sub['text'],
        hoverlabel=dict(namelength=-1),
        hoverinfo='name+text'
    )
    schools.append(school)

layout = dict(
    autosize=True,
    hovermode='closest',
    hoverdistance=2,
    title='NYC High Schools<br>(bubble sized according to school performance relative to city average)',
    showlegend = True,
    mapbox=dict(
        accesstoken=mapbox_access_token,
        bearing=25,
        style='light',
        center=dict(
            lat=40.705005,
            lon=-73.93879
        ),
        pitch=20,
        zoom=11.5,
    ),

)

fig = dict( data=schools, layout=layout)
py.iplot( fig, validate=False, filename='bubble-map-nyc-schools')

I tried adding “<a href” text to the marker itself, but it won’t translate to a clickable hyperlink.

The Plotly community appears to suggest Click Events as a solution, but I’m not sure how to combine the code in the example with my code above. I’m also not sure how to make a Click Event a hyperlink, so that clicking on a marker (school) will result in opening a new website or new tab.

Does anyone know a workaround for having clickable markers on a scattermapbox?

2 Likes

Hi @adamschroeder,

I don’t think it’s possible to add a true hyperlink to a marker trace. If you’re working in the Jupyter Notebook you could simulate the effect by using the on_click callback mechanism of a FigureWidget as you mentioned. To actually open the browser tab you could have your callback use the Python webbrowser module (https://docs.python.org/3/library/webbrowser.html).

Here’s an example of that approach:

import webbrowser
import pandas as pd
import plotly.graph_objs as go
df = pd.DataFrame({'x': [1, 2, 3],
                   'y': [1, 3, 2],
                   'link': ['https://google.com', 'https://bing.com', 'https://duckduckgo.com']})

fig = go.FigureWidget(layout={'hovermode': 'closest'})
scatter = fig.add_scatter(x=df.x, y=df.y, mode='markers', marker={'size': 20})

def do_click(trace, points, state):
    if points.point_inds:
        ind = points.point_inds[0]
        url = df.link.iloc[ind]
        webbrowser.open_new_tab(url)
        
scatter.on_click(do_click)
fig

Hope that helps!
-Jon

4 Likes

Thank you Jon, @jmmease . I was able to make it work thanks to your example (full code below). The only challenge I have left to overcome is to make it work on plotly. The on_click callback doesn’t work for me when I try to run code through plotly:

py.iplot(fig)

The map appears fine, but the the markers can’t be clicked anymore.

If I leave the FigureWidget (fig) outside py.plotly as such:

school.on_click(do_click)
fig

the clickable function works beautifully.

Do you know how I can implement this in plotly offline or online?

fig = go.FigureWidget(layout = dict(
    autosize=True,
    hovermode='closest',
    hoverdistance=2,
    title='NYC High Schools<br>(bubble sized according to school performance relative to city average)',
    showlegend = True,
    mapbox=dict(
        accesstoken=mapbox_access_token,
        bearing=25,
        style='light',
        center=dict(
            lat=40.705005,
            lon=-73.93879
        ),
        pitch=20,
        zoom=11.5,
    ),
))

def do_click(trace, points, state):
    if points.point_inds:
        ind = points.point_inds[0]
        url = df.link.iloc[ind]
        webbrowser.open_new_tab(url)

school=[]
for i in interest_areas:
    df_sub=df[df.interest1==i]
    school = fig.add_scattermapbox(
        lon = df_sub['Longitude'],
        lat = df_sub['Latitude'],
        mode='markers',
        marker = dict(
            size = (df_sub['Performance']*10)**1.8
                     ),
        hovertext=df_sub['text'],
        hoverlabel=dict(namelength=-1),
        hoverinfo='name+text',
        name = df_sub['interest1'].values[0], #legend interest name

    )
    school.on_click(do_click)
fig

If it’s not possible in plotly, I’ll try to find a way to share my jupyter notebook with the public in a way that the callback click_on still works. (Ideally, NYC parents would use this map to find a good high school for their children)

Thank you,

Hi @adamschroeder,

Yes, unfortunately this FigureWidget approach will only work in the Jupyter Notebook with a live running python kernel. I don’t think there is a way to host a figure with custom callbacks on the online Chart Studio.

I don’t know if this would help with your particular use case, but it should be possible to output a standalone HTML file that contains the figure and the callback specified as a JavaScript function. If approach would be helpful, I can create a new version of the example above (probably tomorrow).

-Jon

Thank you @jmmease,
that would be very helpful if you could create it. In the meantime, I’m trying other things like using kaggle or brython, but nothing yet.

My goal is to send the html link to parents in my organization and around the city, so they could use it in their search for high-performing high schools. Ideally, it would look like this (high_school_map), but with the clickable function you helped me create. And I would update the map and its data on a bi-annual basis, as the Department of Education releases new stats.

Thank you Jon,

Hi @adamschroeder,

Here’s an example of the approach I mentioned

from plotly.offline import plot
import pandas as pd
import plotly.graph_objs as go

# mapbox_access_token = '...'

# Build scattermapbox trace and store URL's in the customdata
# property. The values of this list will be easy to get to in the
# JavaScript callback below
data = [
    go.Scattermapbox(
        lat=['45.5017', '45.7', '45.3'],
        lon=['-73.5673', '-73.7', '-73.3'],
        mode='markers',
        marker=dict(
            size=14
        ),
        name='mapbox 1',
        text=['Montreal'],
        customdata=['https://google.com', 'https://bing.com', 'https://duckduckgo.com']
    )
]

# Build layout
layout = go.Layout(
    hovermode='closest',
    mapbox=dict(
    accesstoken=mapbox_access_token,
    bearing=0,
    center=dict(
        lat=45,
        lon=-73
    ),
    pitch=0,
    zoom=6,
    )
)

# Build Figure
fig = go.Figure(
    data=data,
    layout=layout,
)

# Get HTML representation of plotly.js and this figure
plot_div = plot(fig, output_type='div', include_plotlyjs=True)

# Get id of html div element that looks like
# <div id="301d22ab-bfba-4621-8f5d-dc4fd855bb33" ... >
res = re.search('<div id="([^"]*)"', plot_div)
div_id = res.groups()[0]

# Build JavaScript callback for handling clicks
# and opening the URL in the trace's customdata 
js_callback = """
<script>
var plot_element = document.getElementById("{div_id}");
plot_element.on('plotly_click', function(data){{
    console.log(data);
    var point = data.points[0];
    if (point) {{
        console.log(point.customdata);
        window.open(point.customdata);
    }}
}})
</script>
""".format(div_id=div_id)

# Build HTML string
html_str = """
<html>
<body>
{plot_div}
{js_callback}
</body>
</html>
""".format(plot_div=plot_div, js_callback=js_callback)

# Write out HTML file
with open('hyperlink_fig.html', 'w') as f:
    f.write(html_str)

This will produce an html file in the current directory named hyperlink_fig.html. This file is standalone and doesn’t require Python and it’s something you could email out or host on your own website.

Hope that helps or at least gives you some more ideas :slight_smile:

-Jon

5 Likes

Thank you very much @jmmease, this works perfect.

If I’d like to do similar maps and interactive visualizations with callbacks in the future, would you recommend I learn Dash or try to learn basic JavaScript so I can do what you did?

Great!

Dash is definitely the more mature solution and you get pretty much unlimited control since you can write all of the callbacks/backend login in Python, and you can use a really nice suite of widget controls. But this does require the webapp to be hosted on a webserver somewhere (The dash docs have instructions for hosting on Heroku). The only real advantage of the approach I used above is that you can create standalone HTML files that don’t require a server.

-Jon

Hi @jmmease, I tried following this pattern, and am able to inject the script tag into my html, and get a div in plot_element. The div has class=“plotly-graph-div js-plotly-plot”. However, the plot_click event never seems to fire. Any thoughts on why the event may not be firing? Fwiw, the plot_element definitely has an on() function.

Perhaps some more helpful details. Here’s the script tag injected into the html:

<script type="text/javascript">

    
    var plot_element = document.getElementById('3f56277f-e84b-4b68-ae85-91d3cd62d01c');
    console.log('callback js has been added to page. did we get plot_element?');
    console.log(plot_element);
    
    plot_element.on('plotly_click', function(data){
        console.log('Im inside the plotly_click!!');
        console.log(data);
        var point = data.points[0];
        if (point) {
            console.log(point.customdata);
            window.open(point.customdata);
        }
    })
    
</script>

The first two console logs before the event listener succeed, but when I click on points in my scatter plot the event never seems to fire.

FWIW, the issue was in my layout. I had 'clickmode': 'select', which screwed things up. Removing that enabled events to fire. This example was extremely helpful!

Hi jmmease,

Can you please let me know # mapbox_access_token = ‘…’ is ?
I am trying to implement clickable URL for line charts, but couldn’t find any solution.
can you please help me out here ?

Attaching sample line plotly offline chart -

import plotly.offline as pyo
import plotly.graph_objs as go

import pandas as pd

df = pd.read_csv(‘data.csv’)

create traces

trace0 = go.Scatter(
x=df[‘Date’],
y=df[‘Average’],
mode=‘lines’,
name=‘Average duration per day’,
fill=‘tonexty’
)

data = [trace0] # assign traces to data
layout = go.Layout(
title=‘Line chart showing three different modes’,
showlegend=True,
xaxis=dict(
title=‘Day’,
titlefont=dict(
family=‘Courier New, monospace’,
size=18,
color=’#7f7f7f’
)
),
yaxis=dict(
title=‘Average Duration’,
titlefont=dict(
family=‘Courier New, monospace’,
size=18,
color=’#7f7f7f’
)
)
)
fig = go.Figure(data=traces, layout=layout)
pyo.plot(fig, filename=‘line3.html’)

@pavanambure

To get a mapbox_access_token, go to: https://chart-studio.plot.ly/feed/#/
and click Sign up (upper right corner). After creating your account with username pavanambure, go to:
https://plot.ly/~pavanambure#/, sign in, and select Settings from the upper right dropdown menu

At the left side of the page opened after clicking Settings you’ll see a menu like this one:


Click on Mapbox Access Tokens and generate one.

Click on

1 Like

I test the code you posted here, and I got an error:

AttributeError                            Traceback (most recent call last)
<ipython-input-4-f5be7a3d4c8c> in <module>
     15         webbrowser.open_new_tab(url)
     16 
---> 17 scatter.on_click(do_click)
     18 fig.show()

AttributeError: 'FigureWidget' object has no attribute 'on_click'

I get the same error. @lee Do you get the solution? @jmmease Could you have a look? Thank you .

Hi @jmmease,
Thank you for providing your example. However, when I run this example I get the error AttributeError: ‘FigureWidget’ object has no attribute ‘on_click’. I was wondering if you can help solve this issue.
Best,
Tilly

for

AttributeError: ‘FigureWidget’ object has no attribute ‘on_click’.

Try

fig = px.scatter(...)
fig.data[0].on_click(...)

Hi @jmmease this code works really good for me apart from one issue. I see that if I put negative values for x-axis or y-axis, it comes with some absurd prefix like ÂćÂ^Â’60 for -60.

Could you please suggest a solution for this.

Thanks
Nitin

Hi @nsingal, welcome to the forums.

Which plotly version are you using?

We have a similar case here: