haroldchoi
haroldchoi

Reputation: 53

plotly dash range slider with datetime and scatterplot interaction

I would like to add a range slider along with my dropdown, and make the range slider the 'Wallclock' datetime along with an interaction that allows the range slider to chose the datetime for that capsules based on the dropdown value. I managed to find several ways that other people have done this but none seems to work for my situation especially the callback and the update of the graph. Thank you!

Data looks like this.

enter image description here

Dash looks like this.

enter image description here

Code looks like this.

import pandas as pd
import plotly.express as px  # (version 4.7.0)
import plotly.graph_objects as go
import numpy as np

import openpyxl
import dash  # (version 1.12.0) pip install dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate

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

df = pd.read_excel("tcd vs rh 2.xlsx")
print(df)

capsuleID = df['Capsule_ID'].unique()
print(capsuleID)

capsuleID_names = sorted(list(capsuleID))
print(capsuleID_names)

capsuleID_names_1 = [{'label': k, 'value': k} for k in sorted(capsuleID)]
capsuleID_names_2 = [{'label': '(Select All)', 'value': 'All'}]
capsuleID_names_all = capsuleID_names_1 + capsuleID_names_2

app.layout = html.Div([

    html.H1("Relative Humidity vs TCD", style={'text-align': 'center'}),

    dcc.Dropdown(id="capsule_select",
                 options=capsuleID_names_all,
                 optionHeight=25,
                 multi=True,
                 searchable=True,
                 placeholder='Please select...',
                 clearable=True,
                 value=['All'],
                 style={'width': "100%"}
                 ),

    dcc.RangeSlider(id='slider',
                    min=df['Wallclock'].min(),
                    max=df['Wallclock'].max(),
                    value=[df.iloc[-101]['Wallclock'].timestamp(), df.iloc[-1]['Wallclock'].timestamp()]
                    ),

    html.Div([
        dcc.Graph(id="the_graph"),
    ]),

])

# -----------------------------------------------------------
@app.callback(
    Output('the_graph', 'figure'),
    Output('capsule_select', 'value'),
    Input('capsule_select', 'value'),
    Input('slider', 'value'),
)
def update_graph(capsule_chosen):
    lBound = pd.to_datetime(value[0], unit='s')
    uBound = pd.to_datetime(value[1], unit='s')
    filteredData = df.loc[(df['date'] >= lBound) & (df['date'] <= uBound)]

    dropdown_values = capsule_chosen

    if "All" in capsule_chosen:
        dropdown_values = capsuleID_names
        dff = df
    else:
        dff = df[df['Capsule_ID'].isin(capsule_chosen)]  # filter all rows where capsule ID is the capsule ID selected

    scatterplot = px.scatter(
        data_frame=dff,
        x="tcd",
        y="humidity",
        hover_name="Wallclock",
    )

    scatterplot.update_traces(textposition='top center')

    return scatterplot, dropdown_values


# ------------------------------------------------------------------------------

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

Upvotes: 1

Views: 2789

Answers (1)

Rob Raymond
Rob Raymond

Reputation: 31146

  • obviously I don't have access to your Excel spreadsheet so generated a data frame with same shape
  • taken approach of using a second figure with a rangeslider for slider capability
  • updated callback to use this figure as input for date range
  • used jupyter dash inline, this can be changed back to your setup (commented lines)

generate some sample data

import pandas as pd
import numpy as np
df = pd.DataFrame(
    {
        "Wallclock": pd.date_range(
            "22-dec-2020 00:01:36", freq="5min", periods=2000
        ),
        "tcd": np.linspace(3434, 3505, 2000) *np.random.uniform(.9,1.1, 2000),
        "humidity": np.linspace(63, 96, 2000),
    }
).pipe(lambda d: d.assign(Capsule_ID=(d.index // (len(d)//16))+2100015))

slider is a figure with a rangeslider

import pandas as pd
import plotly.express as px  # (version 4.7.0)
import plotly.graph_objects as go
import numpy as np

import openpyxl
import dash  # (version 1.12.0) pip install dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from jupyter_dash import JupyterDash

# app = dash.Dash(__name__)
# server = app.server
app = JupyterDash(__name__)


# df = pd.read_excel("tcd vs rh 2.xlsx")
# print(df)

capsuleID = df["Capsule_ID"].unique()
# print(capsuleID)

capsuleID_names = sorted(list(capsuleID))
# print(capsuleID_names)

capsuleID_names_1 = [{"label": k, "value": k} for k in sorted(capsuleID)]
capsuleID_names_2 = [{"label": "(Select All)", "value": "All"}]
capsuleID_names_all = capsuleID_names_1 + capsuleID_names_2

def slider_fig(df):
    return px.scatter(
                df.groupby("Wallclock", as_index=False).size(), x="Wallclock", y="size"
            ).update_layout(
                xaxis={"rangeslider": {"visible": True}, "title":None},
                height=125,
                yaxis={"tickmode": "array", "tickvals": [], "title": None},
                margin={"l": 0, "r": 0, "t": 0, "b": 0},
            )

app.layout = html.Div(
    [
        html.H1("Relative Humidity vs TCD", style={"text-align": "center"}),
        dcc.Dropdown(
            id="capsule_select",
            options=capsuleID_names_all,
            optionHeight=25,
            multi=True,
            searchable=True,
            placeholder="Please select...",
            clearable=True,
            value=["All"],
            style={"width": "100%"},
        ),
        dcc.Graph(
            id="slider",
            figure=slider_fig(df),
        ),
        html.Div(
            [
                dcc.Graph(id="the_graph"),
            ]
        ),
    ]
)

# -----------------------------------------------------------
@app.callback(
    Output("the_graph", "figure"),
    Output("capsule_select", "value"),
    Output("slider", "figure"),
    Input("capsule_select", "value"),
    Input('slider', 'relayoutData'),
    State("slider", "figure")
)
def update_graph(capsule_chosen, slider, sfig):
    dropdown_values = capsule_chosen

    if "All" in capsule_chosen:
        dropdown_values = capsuleID_names
        dff = df
    else:
        dff = df[
            df["Capsule_ID"].isin(capsule_chosen)
        ]  # filter all rows where capsule ID is the capsule ID selected

    
    if slider and "xaxis.range" in slider.keys():
        dff = dff.loc[dff["Wallclock"].between(*slider["xaxis.range"])]
    else:
        # update slider based on selected capsules
        sfig = slider_fig(dff)
        
    scatterplot = px.scatter(
        data_frame=dff,
        x="tcd",
        y="humidity",
        hover_name="Wallclock",
    )

    scatterplot.update_traces(textposition="top center")

    return scatterplot, dropdown_values, sfig


# ------------------------------------------------------------------------------

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

Upvotes: 3

Related Questions