Click event on anywhere in a graph

Hello everyone,

I was wondering if it is possible to add a click event on a graph (specifically a map) that would capture any click the user makes, even if it is not on a known point.
I’ve searched numerous docs and forums but I couldn’t find anything that answers my question, even the plotly docs ( Event handlers in JavaScript) on event handlers in JS do not seem to have a suitable solution.
With the exemple below, you can see a callback linked to the clickData property of my scatter-geo graph. But if the user clicks somewhere on the map that is not associated with a point, I would need the app to display the coordinates anyway.

import dash
from dash import dcc, html
from dash.dependencies import Input, Output
import plotly.express as px

# Load the gapminder data for the year 2007
df = px.data.gapminder().query("year == 2007")

# Create the ScatterGeo plot with Plotly Express
fig = px.scatter_geo(
    df,
    locations="iso_alpha",
    size="pop",
    hover_name="country",  # display country name on hover
    title="Countries in 2007",
    template="plotly",
    color="continent",
)

app = dash.Dash(__name__)

app.layout = html.Div(
    [dcc.Graph(id="scatter-geo", figure=fig), html.Div(id="click-output")]
)


# Callback to display the clicked country
@app.callback(Output("click-output", "children"), Input("scatter-geo", "clickData"))
def display_click_data(click_data):
    if click_data is None:
        return "Click on a country to get more information."

    country = click_data["points"][0]
    return f"You clicked on: {country['hovertext']} with coordinates ({country['lat']}°, {country['lon']}°)"


if __name__ == "__main__":
    app.run(debug=True)

Hi @Galaktik we had this topic quite a few times, here for example

What is the use case for this?

I’m not sure there is a built-in way to do this. You would have to track the cursor within the div and then calculate the coordinates in your graph. If adding possible zoom state into the mix this could be really tricky.

1 Like

Hello @AIMPED, thank you for responding.

I’m trying to make a function to calculate the distance between two points that the user choose on the map. I already got a map with traces but the user must be able to place new points on the map as he wishes.

I already saw this post, but the problem is I’m working with a ScatterGeo plot, and as you can see in my previous exemple, the callback simply doesn’t fire with clickData as an input.
I was afraid that there was no easy way for me to do this, thanks for the tips.

I find that quite sad thought that ScatterGeo plots cannot track the clicks of user as well as Scatter plots.

Thanks you.

I understand. You are tying to implement something like this, right?

Not sure if one of these components might be helpful:

Yes this is what I’m trying to implement.

The problem is that I really need to implement it in a ScatterGeo plot so I can’t use the leaflet library.
I’ve looked into the link you gave me but it doesn’t seem like there is a component that suits me.

I have implemented a solution. I’ve looked at pretty much every properties of the graph object and I have found some useful info. Since the lonRange and latRange of the map doesn’t update when scrolling and zooming, we have to use the scale and the center point of the current view of the map to adjust our ranges.
However, i don’t know how precise this solution is, but I think it gets the work done.

Here is a minimal app implementing the solution for anyone willing to track the click of an user on a map
app.py

import dash
from dash import dcc, html
import plotly.graph_objects as go

app = dash.Dash(__name__, assets_folder="assets")

fig = go.Figure(go.Scattergeo(lat=[], lon=[], mode='markers'))
fig.update_layout(
    geo=dict(
        scope='world', 
        projection_type='mercator',
        showland=True,
        landcolor="lightgray",
        coastlinecolor="Black"
    ),
    margin=dict(l=0, r=0, t=0, b=0),
)

app.layout = html.Div([
    dcc.Graph(
        id="map",
        figure=fig,
        config={"scrollZoom": True},
        style={"height": "80vh"}
    ),
    html.Div("Click on the map", id="click-output", style={"fontSize": "20px", "marginTop": "10px"}),
])

if __name__ == "__main__":
    app.run(debug=True)

assets/clickMap.js

function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}

async function clickOnMap() {
    await sleep(5000);

    const graphDiv = document
        .getElementById("map")
        .getElementsByClassName("js-plotly-plot")[0];
    const outDiv = document.getElementById("click-output");

    graphDiv.addEventListener("click", function (event) {
        const bb = graphDiv.getBoundingClientRect();
        const xPx = event.clientX - bb.left;
        const yPx = event.clientY - bb.top;

        const gd = graphDiv._fullLayout;
        const geo = gd.geo;

        // Getting scale, center, and rotation info from geo projection
        const scale = geo.projection.scale;
        const centerLat = geo.center.lat;
        const centerLon = geo.center.lon;

        // Getting current ranges for lon and lat from the geo component
        const lonRange = geo.lonaxis.range;
        const latRange = geo.lataxis.range;

        // Adjust lonRange and latRange based on the center and scale
        const adjustedLonRange = [
            centerLon - (lonRange[1] - lonRange[0]) / (2 * scale),
            centerLon + (lonRange[1] - lonRange[0]) / (2 * scale),
        ];

        const adjustedLatRange = [
            centerLat - (latRange[1] - latRange[0]) / (2 * scale),
            centerLat + (latRange[1] - latRange[0]) / (2 * scale),
        ];

        // Calculate longitude and latitude based on click position
        const lon =
            adjustedLonRange[0] +
            (xPx / bb.width) * (adjustedLonRange[1] - adjustedLonRange[0]);
        const lat =
            adjustedLatRange[0] +
            (yPx / bb.height) * (adjustedLatRange[1] - adjustedLatRange[0]);

        // Display the coordinates
        outDiv.innerText = `Lat: ${lat.toFixed(5)}, Lon: ${lon.toFixed(5)}`;
        console.log("Coordinates:", lat, lon);
    });
}

clickOnMap();

1 Like