pythonplotlyplotly-dashdash-bootstrap-components

Set specific colour map to Bar-chart with callback - dash Plotly


I've got an interactive bar chart that displays counts from days of the week. I'm trying to set the color map so each day of the week maintains the same color. At the moment, if I remove any days, the color changes.

Separately, is it possible to keep the same dropdown values for days of the week and map the same color sequence but plot DATES instead of days of the week?

from dash import dcc
from dash import html
import dash_bootstrap_components as dbc
import dash
from dash.dependencies import Input, Output, State
from datetime import datetime
import plotly.graph_objs as go
import plotly.express as px
import pandas as pd
import numpy as np
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['TIMESTAMP'] = df['TIMESTAMP'].dt.floor('1min')
df['DATE'], df['TIME'] = zip(*[(d.date(), d.time()) for d in df['TIMESTAMP']])
df['DATE'] = pd.to_datetime(df['DATE'])
df['YEAR'] = df['DATE'].dt.year

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=[
    ################### Filter box ###################### 
    html.Div(children=[
        html.Label('Day of the week:', style={'paddingTop': '2rem'}),
        dcc.Dropdown(
            id='DOW',
            options=[
                {'label': x, 'value': x} for x in df['DOW'].unique()
            ],
            value=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday'],
            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(),
            ]),
            dbc.Row([
                dbc.Col(dcc.Graph(id = 'date-bar-chart'), style={
                        "padding-bottom": "10px",
                    },),
            ]),
            dbc.Row([
            ]),
        ], width=5),
        dbc.Col([
            dbc.Row([
                dbc.Col(),
            ]),
        ], width=5),
    ])
], fluid=True)


@app.callback(
    Output('date-bar-chart', 'figure'),
    [Input("DOW", "value"),
    ])     

def date_chart(dow):

    dff = df[df['DOW'].isin(dow)]
    count = dff['DOW'].value_counts()

    dff['Color'] = dff['DOW'].map(dict(zip(dff['DOW'].unique(),
                    px.colors.qualitative.Plotly[:len(dff['DOW'].unique())])))

    Color = dff['Color'].unique()
    Category = dff['DOW'].unique()

    cats = dict(zip(Color, Category)) 

    data = px.bar(x = count.index, 
                  y = count.values,
                  color = count.index,
                  color_discrete_map = cats,
                 )
              
    layout = go.Layout(title = 'Date')
    fig = go.Figure(data = data, layout = layout) 

    return fig


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

Solution

  • Because you are setting cats in your callback so each time your dff change based on filter, your dict will be changed. So that I think you should define your color_discrete_map with df, not dff. And I think you should change your cats to cats = dict(zip(Category,Color))

    from dash import dcc
    from dash import html
    import dash_bootstrap_components as dbc
    import dash
    from dash.dependencies import Input, Output, State
    from datetime import datetime
    import plotly.graph_objs as go
    import plotly.express as px
    import pandas as pd
    import numpy as np
    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['TIMESTAMP'] = df['TIMESTAMP'].dt.floor('1min')
    df['DATE'], df['TIME'] = zip(*[(d.date(), d.time()) for d in df['TIMESTAMP']])
    df['DATE'] = pd.to_datetime(df['DATE'])
    df['YEAR'] = df['DATE'].dt.year
    
    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['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()
    
    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=[
        ################### Filter box ###################### 
        html.Div(children=[
            html.Label('Day of the week:', style={'paddingTop': '2rem'}),
            dcc.Dropdown(
                id='DOW',
                options=[
                    {'label': x, 'value': x} for x in df['DOW'].unique()
                ],
                value=['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday','Sunday'],
                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(),
                ]),
                dbc.Row([
                    dbc.Col(dcc.Graph(id = 'date-bar-chart'), style={
                            "padding-bottom": "10px",
                        },),
                ]),
                dbc.Row([
                ]),
            ], width=5),
            dbc.Col([
                dbc.Row([
                    dbc.Col(),
                ]),
            ], width=5),
        ])
    ], fluid=True)
    
    
    @app.callback(
        Output('date-bar-chart', 'figure'),
        [Input("DOW", "value"),
        ])     
    
    def date_chart(dow):
    
        dff = df[df['DOW'].isin(dow)]
        count = dff['DOW'].value_counts()
        data = px.bar(x = count.index, 
                      y = count.values,
                      color = count.index,
                      color_discrete_map = cats,
                     )
                  
    
        return data
    
    
    if __name__ == '__main__':
        app.run_server(debug=False, port = 8051)
    

    enter image description here