Search code examples
pythonplotlyplotly-dashdash-bootstrap-components

Plot bar chart with separate color legend - dash Plotly


I'm trying to plot certain values using dash as a bar chart but use a separate column to map the colors. Using below, I'm plotting dates as a bar-chart that corresponds to a drop down bar.

Is it possible to keep the dates as the x-axis but use DOW (day of the week) as a discrete color map? I'll attach the current output below. The dates get plotted but there are a few issues.

  1. The string formatting for each date in the dropdown isn't in date time

  2. The color map isn't sequenced to the day of the week

    import dash
    from dash import dcc
    from dash import html
    from dash.dependencies import Input, Output
    import dash_bootstrap_components as dbc
    import plotly.express as px
    import plotly.graph_objs as go
    import pandas as pd
    from datetime import datetime as dt
    
    df = pd.DataFrame({
           'Type': ['A','B','B','B','C','C','D','E','E','E','E','F','F','F'],
           })
    
    N = 30
    df = pd.concat([df] * N, ignore_index=True)
    
    df['TIMESTAMP'] = pd.date_range(start='2022/01/01 07:30', end='2022/01/30 08:30', periods=len(df))
    df['TIMESTAMP'] = pd.to_datetime(df['TIMESTAMP'], dayfirst = True).sort_values()
    
    df['DATE'], df['TIME'] = zip(*[(d.date(), d.time()) for d in df['TIMESTAMP']])
    df['DATE'] = pd.to_datetime(df['DATE'])
    
    df = df.sort_values(by = 'DATE')
    
    df['DOW'] = df['DATE'].dt.weekday
    df = df.sort_values('DOW').reset_index(drop=True)
    df['DOW'] = df['DATE'].dt.day_name()
    
    
    external_stylesheets = [dbc.themes.SPACELAB, dbc.icons.BOOTSTRAP]
    
    app = dash.Dash(__name__, external_stylesheets = external_stylesheets)
    
    filter_box = html.Div(children=[
    
        html.Div(children=[
            html.Label('Day of the week:', style={'paddingTop': '2rem'}),
            dcc.Dropdown(
                id='DATE',
                options=[
                    {'label': x, 'value': x} for x in df['DATE'].unique()
                ],
                value=df['DATE'].unique(),
                multi=True
            ),
    
        ], className="four columns",
        style={'padding':'2rem', 'margin':'1rem'} )
    
    ])
    
    app.layout = dbc.Container([
        dbc.Row([
            dbc.Col(html.Div(filter_box, className="bg-secondary h-100"), width=2),
            dbc.Col([
                dbc.Row([
                    dbc.Col(dcc.Graph(id = 'date-bar-chart'), 
                        ),
                ]),
            ], width=5),
        ])
    ], fluid=True)
    
    
    @app.callback(
        Output('date-bar-chart', 'figure'),
        [Input("DATE", "value"),
        ])     
    
    def date_chart(date):
    
        dff = df[df['DATE'].isin(date)]
        count = dff['DATE'].value_counts()
    
        data = px.bar(x = count.index, 
                      y = count.values,
                      #color = dff['DOW'],
                      )
    
        layout = go.Layout(title = 'Date')
        fig = go.Figure(data = data, layout = layout) 
    
        return fig
    
    
    if __name__ == '__main__':
        app.run_server(debug=True, port = 8051)
    

enter image description here


Solution

  • Not sure why do you want to use DATE as Dropdown but I think you should change it to string and then pass it to dcc.Dropdown. Used the previous way to add color_discrete_map I think you can revise your code as below:

    import dash
    from dash import dcc
    from dash import html
    from dash.dependencies import Input, Output
    import dash_bootstrap_components as dbc
    import plotly.express as px
    import plotly.graph_objs as go
    import pandas as pd
    from datetime import datetime as dt
    from dash.exceptions import PreventUpdate
    
    df = pd.DataFrame({
           'Type': ['A','B','B','B','C','C','D','E','E','E','E','F','F','F'],
           })
    
    N = 30
    df = pd.concat([df] * N, ignore_index=True)
    
    df['TIMESTAMP'] = pd.date_range(start='2022/01/01 07:30', end='2022/01/30 08:30', periods=len(df))
    df['TIMESTAMP'] = pd.to_datetime(df['TIMESTAMP'], dayfirst = True).sort_values()
    
    df['DATE'], df['TIME'] = zip(*[(d.date(), d.time()) for d in df['TIMESTAMP']])
    df['DATE'] = pd.to_datetime(df['DATE'],format='%Y-%m-%d')
    
    df = df.sort_values(by = 'DATE')
    
    df['DOW'] = df['DATE'].dt.weekday
    df = df.sort_values('DOW').reset_index(drop=True)
    df['DOW'] = df['DATE'].dt.day_name()
    df['DATE'] = df['DATE'].astype(str)
    df['Color'] = df['DOW'].map(dict(zip(df['DOW'].unique(),
                        px.colors.qualitative.Plotly[:len(df['DOW'].unique())])))
    
    Color = df['Color'].unique()
    Category = df['DOW'].unique()
    
    Color = df['Color'].unique()
    Category = df['DOW'].unique()
    
    cats = dict(zip(Category,Color)) 
    external_stylesheets = [dbc.themes.SPACELAB, dbc.icons.BOOTSTRAP]
    
    app = dash.Dash(__name__, external_stylesheets = external_stylesheets)
    
    filter_box = html.Div(children=[
    
        html.Div(children=[
            html.Label('Day of the week:', style={'paddingTop': '2rem'}),
            dcc.Dropdown(
                id='DATE',
                options=[
                    {'label': x, 'value': x} for x in df['DATE'].unique()
                ],
                value=df['DATE'].unique(),
                multi=True
            ),
    
        ], className="four columns",
        style={'padding':'2rem', 'margin':'1rem'} )
    
    ])
    
    app.layout = dbc.Container([
        dbc.Row([
            dbc.Col(html.Div(filter_box, className="bg-secondary h-100"), width=2),
            dbc.Col([
                dbc.Row([
                    dbc.Col(dcc.Graph(id = 'date-bar-chart'), 
                        ),
                ]),
            ], width=5),
        ])
    ], fluid=True)
    
    
    @app.callback(
        Output('date-bar-chart', 'figure'),
        [Input("DATE", "value"),
        ])     
    
    def date_chart(date):
        if date:
            dff = df[df['DATE'].isin(date)]
            count = dff.groupby(['DATE',"DOW"])['DATE'].count().reset_index(name='counts')
            data = px.bar(x = count['DATE'], 
                      y = count['counts'],
                      color = count['DOW'],
                      color_discrete_map = cats,
                      )
    
            layout = go.Layout(title = 'Date')
            fig = go.Figure(data = data, layout = layout) 
    
            return fig
        else:
            raise PreventUpdate
    
    
    if __name__ == '__main__':
        app.run_server(debug=False, port = 8051)
    

    I used groupby in callback to return new dataframe and then use it to make graph. Hope this help.

    enter image description here