Crossfilter between `line_mapbox` and second plot

Viewed 306

I'm new to plotly and would like to visualize data from a running activity. Suppose I have a dataframe with the following columns:

df = pd.DataFrame(
    {
        "time": time,
        "latitude": latitude,
        "longitude": longitude,
        "altitude": altitude,
        "heartrate": heartrate,
    }
)

I'd like to have two plots, once a map that plots latitude vs longitude, and then another plot that plots time vs heartrate (or altitude). But I want both plots to be linked. So If I select a y-range in my second plot, I'd like to only see those latitude-longitude-pairs on the map, where their respective time value is in the range that I selected in the second plot. Likewise, if I select points on the map, I want to look for the minimal and maximal time value in that selection of points and wish to plot only those points in the second plot.

Here's a screenshot with some dummy data: enter image description here

I don't know how to link the two plots, so any help is appreciated! The source code is given here:

import dash
import dash_core_components as dcc
import dash_html_components as html
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objs as go

app = dash.Dash()
server = app.server

np.random.seed(0)
# Random dummy data
n = 100
time = np.linspace(0, 1, n)
latitude = 50 + 0.001 * np.cumsum(np.random.randn(n))
longitude = 2 + 0.001 * np.cumsum(np.random.randn(n))
altitude = (time - 0.5) ** 2
heartrate = 100 + np.cumsum(np.random.randn(n))
df = pd.DataFrame(
    {
        "time": time,
        "latitude": latitude,
        "longitude": longitude,
        "altitude": altitude,
        "heartrate": heartrate,
    }
)

fig = px.line_mapbox(df, lat="latitude", lon="longitude", zoom=12, height=800)
fig.update_layout(mapbox_style="stamen-terrain")


app.layout = html.Div(
    [
        html.Div(
            [
                dcc.Graph(id="mymap", figure=fig),
            ]
        ),
        html.Div(
            [
                dcc.Graph(id="time-series"),
                dcc.Dropdown(
                    id="column",
                    options=[
                        {"label": i, "value": i} for i in ["altitude", "heartrate"]
                    ],
                    value="altitude",
                ),
            ]
        ),
    ]
)


def lineplot(x, y, title="", axis_type="Linear"):
    return {
        "data": [go.Scatter(x=x, y=y, mode="lines")],
    }


@app.callback(
    dash.dependencies.Output("time-series", "figure"),
    [
        dash.dependencies.Input("column", "value"),
    ],
)
def update_timeseries(column):
    x = df["time"]
    y = df[column]
    return lineplot(x, y)


app.css.append_css({"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})


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

1 Answers

well here is one option I change the line mapbox to Scattermapbox so you can have box select

and see done the code for selected data in map plot and filter in line chart

import dash
from dash import dcc
from dash import html
from dash.dependencies import Input, Output, State
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objs as go

app = dash.Dash()
server = app.server

np.random.seed(0)
# Random dummy data
n = 100
time = np.linspace(0, 1, n)
latitude = 50 + 0.001 * np.cumsum(np.random.randn(n))
longitude = 2 + 0.001 * np.cumsum(np.random.randn(n))
altitude = (time - 0.5) ** 2
heartrate = 100 + np.cumsum(np.random.randn(n))
df = pd.DataFrame(
    {
        "time": time,
        "latitude": latitude,
        "longitude": longitude,
        "altitude": altitude,
        "heartrate": heartrate,
    }
)

fig = go.Figure(go.Scattermapbox(
    mode = "markers+lines",
    lon = df.longitude,
    lat = df.latitude,
    marker = {'size': 10}))
fig.update_layout(
    mapbox={
        'style': "stamen-terrain",
        'center' : dict(
            lat=50,
            lon=2
        ),
        'zoom': 12})


app.layout = html.Div(
    [
        html.Div(
            [
                dcc.Graph(id="mymap", figure=fig),
            ]
        ),
        html.Div(
            [
                dcc.Graph(id="time-series"),
                dcc.Dropdown(
                    id="column",
                    options=[
                        {"label": i, "value": i} for i in ["altitude", "heartrate"]
                    ],
                    value="altitude",
                ),
            ]
        ),
    ]
)


def lineplot(x, y, title="", axis_type="Linear"):
    return {
        "data": [go.Scatter(x=x, y=y, mode="lines")],
    }


@app.callback(
    Output("time-series", "figure"),
    [
        Input("column", "value"),
        Input("mymap", "selectedData")
    ],
)
def update_timeseries(column, selectedData):
    # add filter data by selectData points
    temp = df
    if selectedData is not None:
        sel_data = pd.DataFrame(selectedData['points'])
        temp = df.loc[(df.latitude.isin(sel_data.lat)) & (df.longitude.isin(sel_data.lon))]
    x = temp["time"]
    y = temp[column]
    return lineplot(x, y)


app.css.append_css({"external_url": "https://codepen.io/chriddyp/pen/bWLwgP.css"})


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

enter image description here

Related