Interactive sales dashboard tables

The overarching sales dashboard has come such a long way since you began.

The dashboard has been sent far and wide in the company with some very positive reviews. However, there have been some requests to make the tabular component more interactive. There has been a request to add the ability to select a certain column and have this change the axis on the scatter plot below.

Este exercício faz parte do curso

Building Dashboards with Dash and Plotly

Ver Curso

Instruções de exercício

  • Make sure the three money columns are able to be selected below lines 23, 29, and 35.
  • Allow only one column to be selected at a time below line 46.
  • Create a callback that is triggered on selecting a column that will return an adjusted scatter plot figure below line 92
  • In the callback function, below line 100 extract the selected column's name from the first index in selected_columns then use it below line 107 as the y-axis in the created scatter plot.

Exercício interativo prático

Experimente este exercício preenchendo este código de exemplo.

import dash
from dash import dcc, html
import plotly.express as px
import dash_table
from dash_table import DataTable, FormatTemplate
import pandas as pd
from dash.dependencies import Input, Output
ecom_sales = pd.read_csv('/usr/local/share/datasets/ecom_sales.csv')
logo_link = 'https://assets.datacamp.com/production/repositories/5893/datasets/fdbe0accd2581a0c505dab4b29ebb66cf72a1803/e-comlogo.png'
major_categories = list(ecom_sales['Major Category'].unique())
large_tb = ecom_sales.groupby(['Country'])['OrderValue'].agg(['sum', 'count', 'mean', 'median']).reset_index().rename(columns={'count':'Sales Volume', 'sum':'Total Sales ($)', 'mean':'Average Order Value ($)', 'median':'Median Order Value ($)'})
ecom_country = ecom_sales.groupby('Country')['OrderValue'].agg('sum').reset_index(name='Total Sales ($)')
bar_fig_country = px.bar(ecom_country, x='Total Sales ($)', y='Country', width=500, height=450, title='Total Sales by Country (Hover to filter the Minor Category bar chart!)', custom_data=['Country'], color='Country', color_discrete_map={'United Kingdom':'lightblue', 'Germany':'orange', 'France':'darkblue', 'Australia':'green', 'Hong Kong':'red'})

money_format = FormatTemplate.money(2)
money_cols = ['Total Sales ($)', 'Average Order Value ($)', 'Median Order Value ($)']
d_columns = [{'name':x, 'id':x} for x in large_tb.columns if x not in money_cols]
d_columns += [
    {'name':'Total Sales ($)', 'id':'Total Sales ($)', 
    'type':'numeric', 
    'format':money_format
     # Allow columns to be selected
    , '____':____
    },
    {'name':'Average Order Value ($)', 'id':'Average Order Value ($)', 
    'type':'numeric', 
    'format':money_format
     # Allow columns to be selected
    , '____':____
    },
    {'name':'Median Order Value ($)', 'id':'Median Order Value ($)', 
    'type':'numeric', 
    'format':money_format
     # Allow columns to be selected
    , '____':____
    }]


d_table = DataTable(
  			id='my_dt',
            columns=d_columns,
            data=large_tb.to_dict('records'),
            cell_selectable=False,
            sort_action='native',
  			# Make single columns selectable
            column_selectable='____'
            )

app = dash.Dash(__name__)

app.layout = html.Div([
  html.Img(src=logo_link, 
        style={'margin':'30px 0px 0px 0px' }),
  html.H1('Sales breakdowns'),
  html.Div(
    children=[
    html.Div(
        children=[
        html.H2('Controls'),
        html.Br(),
        html.H3('Major Category Select'),
        dcc.Dropdown(id='major_cat_dd',
        options=[{'label':category, 'value':category} for category in major_categories],
            style={'width':'200px', 'margin':'0 auto'}),
        html.Br(),
        html.H3('Minor Category Select'),
        dcc.Dropdown(id='minor_cat_dd',
            style={'width':'200px', 'margin':'0 auto'})
        ],
        style={'width':'350px', 'height':'360px', 'display':'inline-block', 'vertical-align':'top', 'border':'1px solid black', 'padding':'20px'}),
    html.Div(children=[
            html.H3(id='chosen_major_cat_title'),
            dcc.Graph(id='sales_line')
            ],
             style={'width':'700px', 'height':'380px','display':'inline-block', 'margin-bottom':'5px'}
             )
    ]),
    html.Div(
            d_table
        , style={'width':'1000px', 'height':'200px', 'margin':'10px auto', 'padding-right':'30px'}),
  html.Div(children=[
      dcc.Graph(id='scatter_compare'),
      html.Div(dcc.Graph(id='major_cat', figure=bar_fig_country), style={'display':'inline-block'}),
      html.Div(dcc.Graph(id='minor_cat'), style={'display':'inline-block'})
            ],
             style={'width':'1000px', 'height':'650px','display':'inline-block'}
             ),
  ],style={'text-align':'center', 'display':'inline-block', 'width':'100%'}
  )

# Create a callback triggered by selecting a column
@app.callback(
    Output('scatter_compare', '____'),
    Input('my_dt', '____'))

def table_country(selected_columns):
    comparison_col = 'Total Sales ($)'
	
    # Extract comparison col using its index
    if selected_columns:
        comparison_col = selected_columns[____]

    scatter_fig = px.scatter(
        data_frame=large_tb,
        x='Sales Volume',
      	# Use comparison col in figure
        y=____,
        color='Country',
        title=f'Sales Volume vs {comparison_col} by country'
    )

    return scatter_fig

@app.callback(
   Output('minor_cat_dd', 'options'),
   Output('chosen_major_cat_title', 'children'),
   Input('major_cat_dd', 'value'))

def update_dd(major_cat_dd):
    major_minor = ecom_sales[['Major Category', 'Minor Category']].drop_duplicates()
    relevant_minor = major_minor[major_minor['Major Category'] == major_cat_dd]['Minor Category'].values.tolist()
    minor_options = [dict(label=x, value=x) for x in relevant_minor]

    if not major_cat_dd:
        major_cat_dd = 'ALL'
    
    major_cat_title = f'This is in the Major Category of : {major_cat_dd}'

    return minor_options, major_cat_title

@app.callback(
    Output('sales_line', 'figure'),
    Input('minor_cat_dd', 'value'))

def update_line(minor_cat):
    minor_cat_title = 'All'
    ecom_line = ecom_sales.copy()
    if minor_cat:
        minor_cat_title = minor_cat
        ecom_line = ecom_line[ecom_line['Minor Category'] == minor_cat]
    ecom_line = ecom_line.groupby('Year-Month')['OrderValue'].agg('sum').reset_index(name='Total Sales ($)')
    line_graph = px.line(ecom_line, x='Year-Month',  y='Total Sales ($)', title=f'Total Sales by Month for Minor Category: {minor_cat_title}', height=350)
    
    return line_graph

@app.callback(
    Output('minor_cat', 'figure'),
    Input('major_cat', 'hoverData'))

def update_min_cat_hover(hoverData):
    hover_country = 'Australia'
    
    if hoverData:
        hover_country = hoverData['points'][0]['customdata'][0]

    minor_cat_df = ecom_sales[ecom_sales['Country'] == hover_country]
    minor_cat_agg = minor_cat_df.groupby('Minor Category')['OrderValue'].agg('sum').reset_index(name='Total Sales ($)')
    ecom_bar_minor_cat = px.bar(minor_cat_agg, x='Total Sales ($)', y='Minor Category', orientation='h', height=450, width=480,title=f'Sales by Minor Category for: {hover_country}')
    ecom_bar_minor_cat.update_layout({'yaxis':{'dtick':1, 'categoryorder':'total ascending'}, 'title':{'x':0.5}})

    return ecom_bar_minor_cat


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