Plot bar chart with separate color legend – dash Plotly

Question:

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

Asked By: Chopin

||

Answers:

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

Answered By: hoa tran