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

Question:

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)
Asked By: Chopin

||

Answers:

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

Answered By: hoa tran

The accepted answer is too complicated and doesn’t answer the second question.

For the first question on the color_discrete_map parameter, you should define a proper dictionary with keys as week day names and values as colors like this. You don’t need df['Color'], Color, Category, etc.

import pandas as pd
...
color_map = {
    pd.Timestamp(2023, 2, 13+i).day_name(): # 2-13 was monday.
        px.colors.qualitative.Plotly[i]
            for i in range(7)
}
...
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 = color_map,
    )
    ...

For the second question, you need a logic to choose one of the rows matching a given week day. The following is one example which chooses the first one.(This uses the group_by(), so the value_counts() is not necessary.)

def date_chart(dow):
    dff = df[df['DOW'].isin(dow)]
    g = dff.groupby('DOW', as_index=False)
    g_df = g.head(1).merge(g.size())
    data = px.bar(x = g_df['DATE'],
                  y = g_df['size'],
                  color = g_df['DOW'],
                  color_discrete_map = color_map,
    )
    ...
Answered By: relent95