abinitio
abinitio

Reputation: 815

Is it possible to use zoom from one graph in a Dash app to select input for second graph

I have a dash app that plots a dataframe which has a date component, and an entry that is either true or false. There are two graphs in the dashboard, one with the data vs date, and one with a percentage of True/False like below:

dash app

I can zoom in on the date range and select a subset clicking with the mouse.

I would like to feed this range back into the second graph.

At the moment to produce the above dashboard the relevant part of the code looks like:

from re import template

import pandas as pd
import plotly.express as px
from dash import Dash, Input, Output, dcc, html
from flask import globals


def init_dashboard(server):


    evicted_df = pd.read_csv("app/data/evicted_jobs_node.csv", sep="\t")
    all_df = pd.read_csv("app/data/all_jobs_node.csv", sep="\t")
    all_df["datetime"] = pd.to_datetime(all_df["datetime"])
    all_df = all_df.set_index(["datetime"])
    all_df["evicted"] = all_df["id_job"].isin(evicted_df["id_job"])

    app = Dash(__name__, server=server, routes_pathname_prefix="/dash/")

    app.layout = html.Div(
        [
            html.Div(
                className="row",
                children=[
                    html.Div(
                        className="six columns",
                        children=[dcc.Graph(id="graph-with-dropdown")],
                        style=dict(width="75%"),
                    ),
                    html.Div(
                        className="six columns",
                        children=[dcc.Graph(id="graph-with-dropdown2")],
                        style=dict(width="25%"),
                    ),
                ],
                style=dict(display="flex"),
            ),
            html.Div(
                className="row",
                children=[
                    html.Div(
                        className="six columns",
                        children=[
                            dcc.Dropdown(
                                id="partition-dropdown",
                                options=[
                                    "Partition (default is all)",
                                    *all_df["partition"].unique(),
                                ],
                                value="Partition (default is all)",
                                clearable=False,
                                searchable=False,
                            )
                        ],
                        style={
                            "width": "50%",
                            "justify-content": "center",
                        },
                    ),
                    html.Div(
                        className="six columns",
                        children=[
                            dcc.Dropdown(
                                id="node-dropdown",
                                options=[
                                    "Number of Nodes (default is all)",
                                    *sorted(
                                        [
                                            int(nodes)
                                            for nodes in all_df["nodes_alloc"].unique()
                                        ]
                                    ),
                                ],
                                value="Number of Nodes (default is all)", 
                                clearable=False,
                                searchable=False,
                            )
                        ],
                        style=dict(width="50%"),
                    ),
                ],
                style=dict(display="flex"),
            ),
        ]
    )
    init_callbacks(app, df, all_df)
    return app.server


def init_callbacks(app, df, all_df):

    @app.callback(
        Output("graph-with-dropdown2", "figure"),
        [Input("node-dropdown", "value"), Input("partition-dropdown", "value")],
    )
    def update_evicted_fig(selected_nodes, selected_partition):
        if selected_nodes != "Number of Nodes (default is all)":
            filtered_df = all_df[all_df["nodes_alloc"] == selected_nodes]
        else:
            filtered_df = all_df

        if selected_partition != "Partition (default is all)":
            filtered_df = filtered_df[filtered_df["partition"] == selected_partition]
        x = ["Not Evicted", "Evicted"]

        df1 = filtered_df.groupby(["evicted"]).count().reset_index()

        fig = px.bar(
            df1,
            y=[
                100
                * filtered_df[filtered_df["evicted"] == False].size
                / filtered_df.size,
                100
                * filtered_df[filtered_df["evicted"] == True].size
                / filtered_df.size,
            ],
            x=x,
            color="evicted",
            color_discrete_map={True: "red", False: "green"},
            labels={"x": "Job Status", "y": "% of Jobs"},
        )
        fig.update_layout(transition_duration=500)

        return fig

    @app.callback(
        Output("graph-with-dropdown", "figure"),
        [Input("node-dropdown", "value"), Input("partition-dropdown", "value")],
    )
    def update_evicted_fig(selected_nodes, selected_partition):
        if selected_nodes != "Number of Nodes (default is all)":
            filtered_df = all_df[all_df["nodes_alloc"] == selected_nodes]
        else:
            filtered_df = all_df

        if selected_partition != "Partition (default is all)":
            filtered_df = filtered_df[filtered_df["partition"] == selected_partition]

        print(
            filtered_df[filtered_df["evicted"] == True]
            .groupby([pd.Grouper(freq="6H")])
            .sum(numeric_only=True)["node_hours"]
        )

        fig = px.bar(
            x=filtered_df[filtered_df["evicted"] == False]
            .groupby([pd.Grouper(freq="6H")])
            .sum(numeric_only=True)["node_hours"]
            .index,
            y=filtered_df[filtered_df["evicted"] == False]
            .groupby([pd.Grouper(freq="6H")])
            .sum(numeric_only=True)["node_hours"],
            labels={
                "x": "Date",
                "y": "Node hours",
            },
            title="Job Status",
            barmode="stack",
        )

        fig.add_bar(
            name="Evicted",
            x=filtered_df[filtered_df["evicted"] == True]
            .groupby([pd.Grouper(freq="6H")])
            .sum(numeric_only=True)["node_hours"]
            .index,
            y=filtered_df[filtered_df["evicted"] == True]
            .groupby([pd.Grouper(freq="6H")])
            .sum(numeric_only=True)["node_hours"],
        )

        fig.update_layout(transition_duration=500)

        return fig

    return app.server

Is what I am hoping to do possible, and if so is there some documentation or a worked example someone could highlight for me?

Upvotes: 1

Views: 188

Answers (1)

hoa tran
hoa tran

Reputation: 1711

I don't have you df so maybe you can refer my code to revise yours:

import pandas as pd
import numpy as np
import plotly.express as px
import dash
import dash_html_components as html
from dash import dcc
from dash_extensions.enrich import Input, Output, State, ServersideOutput
import dash_bootstrap_components as dbc
from dash.exceptions import PreventUpdate

df_2 = df[(df['BAS_DT'] >= '2022-01-01')]
df5 = df_2.pivot_table(values='USD_XC_BL',
                       index=['BAS_DT'],
                       aggfunc=np.sum).reset_index()
fig_3 = px.bar(df5,
               x='BAS_DT',
               y='USD_XC_BL',
               labels='BAS_DT',
               hover_name='BAS_DT', color_discrete_sequence=px.colors.qualitative.Alphabet)

fig_3.update_layout(xaxis_title="", yaxis_title="", plot_bgcolor='rgba(0,0,0,0)', margin=dict(l=0, r=0, t=0, b=0))
fig_3.update_xaxes(showline=False, showgrid=False),
fig_3.update_yaxes(showline=False, showgrid=False, separatethousands=True, tickformat=',.0f')

app = dash.Dash(__name__)

app.layout = html.Div([
    dbc.Row([
        dbc.Col([
            dbc.Card([
                dbc.CardBody([
                    dbc.Row([
                        dbc.Col([
                            html.H5('Amount by Currency', style={"text-align": "center"}),
                            dcc.Loading(children=[dcc.Graph(id='histogram_map', figure=fig_3)], color='#119DFF',
                                        type='dot')
                        ], width={'size': 12, 'offset': 0, 'order': 2}, style={"text-align": "left"}),
                    ]),
                ])
            ]),
        ], xs=6),

        dbc.Col([
            dbc.Card([
                dbc.CardBody([
                    dbc.Row([
                        dbc.Col([
                            html.H5('Overdue Status', style={"text-align": "center"}),
                            dcc.Loading(children=[dcc.Graph(id='overdue_map', figure={})], color='#119DFF', type='dot')
                        ], width={'size': 12, 'offset': 0, 'order': 2}, style={"text-align": "left"}),
                    ]),
                ])
            ]),
        ], xs=6),
    ], className='p-2 align-items-stretch')
])


@app.callback(
    Output('overdue_map', 'figure'),
    Input('histogram_map', 'clickData'))
def update_y_timeseries(clickData):
    if clickData:
        country_name = clickData['points'][0]['hovertext']
        df_3 = df[df['BAS_DT'] == country_name]
        df_4 = df_3.pivot_table(values='CLOC_CUR_XC_BL',
                                index=['APL_DTL_NAME'],
                                aggfunc=pd.Series.nunique).reset_index()
        fig = px.bar(df_4,
                     x='APL_DTL_NAME',
                     y='CLOC_CUR_XC_BL'
                     , color_discrete_sequence=px.colors.qualitative.Alphabet)
        fig.update_layout(xaxis_title="", yaxis_title="", plot_bgcolor='rgba(0,0,0,0)')  # plot_bgcolor='rgba(0,0,0,0)'
        fig.update_xaxes(showline=False, showgrid=False),
        fig.update_yaxes(showline=False, showgrid=False, separatethousands=True)
        fig.update_traces(width=0.3)
        return fig
    else:
        raise PreventUpdate


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

I'm using clickData to return point as date, and then use this date to filter data and then make new bar graph.

Hope this help.

Upvotes: 1

Related Questions