インタラクティブな売上ダッシュボードのテーブル
ここまでで、総合的な売上ダッシュボードは大きく進化しました。
ダッシュボードは社内の幅広い部門に共有され、とても好意的な評価を得ています。一方で、表形式コンポーネントをもっとインタラクティブにしてほしいという要望も出ています。具体的には、特定の列を選択し、その選択に応じて下の散布図の軸が切り替わるようにしてほしい、というリクエストです。
この演習はコースの一部です
Dash と Plotly で作るダッシュボード
演習の手順
25行目の下で、rowSelectionパラメータを設定してgridで単一行選択を有効にします。63行目の下にコールバックを作成し、my_gridのselectedRowsを使用して、選択した国が大きな赤い点で強調表示されるようにscatter_compareの図を更新します。
実践的なインタラクティブ演習
このサンプルコードを完成させて、この演習に挑戦してみましょう。
from dash import Dash, dcc, html, Input, Output, callback
import plotly.express as px
import pandas as pd
from dash_ag_grid import AgGrid
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':'pink'})
money_fmt = {"function": ("params.value.toLocaleString('en-US', {style: 'currency', currency: 'USD'})")}
column_defs = [
{"field": "Country"},
{"field": "Total Sales ($)", "valueFormatter": money_fmt},
{"field": "Average Order Value ($)", "valueFormatter": money_fmt},
{"field": "Median Order Value ($)", "valueFormatter": money_fmt},
{"field": "Sales Volume"},
]
grid = AgGrid(
id="my_grid",
columnDefs=column_defs,
rowData=large_tb.to_dict("records"),
# Enable single-row selection
dashGridOptions={"____": "____"},
defaultColDef={
"cellStyle": {
"textAlign": "left",
"backgroundColor": "black",
"color": "white"}}
)
app = Dash()
app.layout = [
html.Img(src=logo_link,
style={'margin':'30px 0px 0px 0px' }),
html.H1('Sales breakdowns'),
html.Div([
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([
html.H3(id='chosen_major_cat_title'),
dcc.Graph(id='sales_line')],
style={'width':'700px', 'height':'380px','display':'inline-block', 'margin-bottom':'5px'}),
grid,
html.Br(),
html.Div([
dcc.Graph(id='scatter_compare'),
dcc.Graph(id='major_cat', figure=bar_fig_country, style={'display':'inline-block'}),
dcc.Graph(id='minor_cat', style={'display':'inline-block'})],
style={'width':'1000px', 'height':'650px','display':'inline-block'})
]
# Create a callback with the right input
@callback(
Output('scatter_compare', 'figure'),
Input('my_grid', '____')
)
def update_scatter(selected_rows):
fig = px.scatter(
large_tb,
x='Sales Volume',
y='Total Sales ($)',
color='Country',
title='Sales Volume vs Total Sales ($) — click a row to highlight')
if selected_rows:
sel_df = pd.DataFrame(selected_rows)
highlight = px.scatter(
sel_df,
x='Sales Volume',
y='Total Sales ($)',
color_discrete_sequence=['#ff0000'],
size=[15] * len(sel_df))
fig.add_trace(highlight.data[0])
return fig
@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
@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
@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(debug=True)