# Dash Interactive Dashboard Guide (Updated)
This guide explains, step by step, how the `dashboard.py` script works to
create a powerful, interactive dashboard. It now includes advanced
features like **interactive KPI cards** and **dynamic chart updates**.
This guide is designed for beginners and covers both the code and the
concepts behind it.
---
## Table of Contents
1. [What is Dash?](#what-is-dash)
2. [Project Overview (with new features)](#project-overview)
3. [Step 1: Loading and Merging Data](#step-1-loading-and-merging-data)
4. [Step 2: Setting Up the Dash App and State
Management](#step-2-setting-up-the-dash-app-and-state-management)
5. [Step 3: Building a Flexible
Layout](#step-3-building-a-flexible-layout)
6. [Step 4: Advanced Interactivity with Multiple
Callbacks](#step-4-advanced-interactivity-with-multiple-callbacks)
- 4.1: The State-Update Callback (Card Clicks)
- 4.2: The Main Content Callback (Updating Everything)
7. [Step 5: Running the App](#step-5-running-the-app)
8. [Key Learnings and Advanced
Concepts](#key-learnings-and-advanced-concepts)
---
## What is Dash?
**Dash** is a Python framework for building interactive web applications
and dashboards. It's built on top of Flask, Plotly, and React.js, but you
only need to know Python to use it. Dash lets you create web apps with
interactive charts, tables, and controls (like dropdowns and sliders)
entirely in Python.
---
## Project Overview
This dashboard visualizes business data from multiple Excel sheets. It
allows users to filter data using dropdowns and now features **three
interactive KPI cards** at the top right.
### New Core Features:
- **Interactive KPI Cards**: Displays total "Order Amount", "Revenue
Amount", and "Cash Amount".
- **Click-to-Update**: When a user clicks a KPI card, that metric (e.g.,
"Revenue Amount") becomes the **primary measure** for the entire
dashboard.
- **Dynamic Charts**: All charts on the page instantly update to visualize
data based on the selected KPI card.
- **Active KPI Highlight**: The currently selected KPI card is visually
highlighted with a blue border.
---
## Step 1: Loading and Merging Data
First, we use pandas to load data from three different sheets in an Excel
file.
```python
orders = pd.read_excel("excel_data_model_fixed.xlsx",
sheet_name="Orders_Fact")
revenues = pd.read_excel("excel_data_model_fixed.xlsx",
sheet_name="Revenues_Fact")
cash = pd.read_excel("excel_data_model_fixed.xlsx",
sheet_name="Cash_Fact")
```
To analyze all facts together, we merge them. The merge strategy is now an
**outer join**.
```python
common_cols = [col for col in [...] if col in orders.columns and ...]
merged = orders.merge(revenues, on=common_cols, how="outer", ...)
merged = merged.merge(cash, on=common_cols, how="outer", ...)
```
- **`how="outer"`**: This is an **outer join**. It ensures that all rows
from *all three tables* are included in the final `merged` DataFrame. If a
row from one table doesn't have a match in the others, it's still
included, with `NaN` (Not a Number) values in the columns from the other
tables. This prevents data loss.
---
## Step 2: Setting Up the Dash App and State Management
The app is initialized as before, but now we add a crucial component for
managing the state of our dashboard: `dcc.Store`.
```python
app = dash.Dash(__name__, suppress_callback_exceptions=True)
# In the layout:
dcc.Store(id='measure-store', data=measure_cols["Order Amount"])
```
- **`dcc.Store`**: This is a component that stores data in the user's web
browser without displaying it. It's perfect for keeping track of the
application's "state," such as which KPI card is currently selected.
- **`id='measure-store'`**: This ID lets our callbacks read from and write
to the store.
- **`data=...`**: We initialize it to hold the column name for "Order
Amount", making it the default metric when the app loads.
---
## Step 3: Building a Flexible Layout
The layout is now more complex, using `flexbox` for alignment. It is split
into a filter section on the left and a KPI card section on the right.
A helper function, `create_kpi_card`, is used to avoid repetitive code.
```python
def create_kpi_card(title, value_id, card_id):
# Returns a clickable html.Div component
...
def page1():
return html.Div([
dcc.Store(...),
html.H2("Dashboard Page 1"),
html.Div([
# Left side: Dropdown filters
html.Div([...], style={'flex': '0.7'}),
# Right side: KPI cards
html.Div([
create_kpi_card("Order Amount", ...),
# ... other cards
], style={'display': 'flex', 'flex': '0.3', ...}),
], style={'display': 'flex', ...}),
html.Hr(),
html.Div(id="page1-cards") # For the charts
])
```
- **`style={'display': 'flex'}`**: This CSS property makes it easy to
align items horizontally or vertically. We use it to place the filters and
cards side-by-side.
---
## Step 4: Advanced Interactivity with Multiple Callbacks
The core of the new interactivity lies in two linked callbacks.
### 4.1: The State-Update Callback (Card Clicks)
This callback's only job is to listen for clicks on the KPI cards and
update the `dcc.Store` with the new selected measure.
```python
@app.callback(
Output('measure-store', 'data'),
[Input('orders-card', 'n_clicks'),
Input('revenues-card', 'n_clicks'),
Input('cash-card', 'n_clicks')],
prevent_initial_call=True
)
def update_selected_measure(c1, c2, c3):
ctx = dash.callback_context
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
if button_id == 'revenues-card':
return measure_cols["Revenue Amount"]
# ... other cards
```
- **`callback_context`**: A Dash variable that tells you which `Input`
triggered the callback. We use it to find out exactly which card was
clicked.
- **`Output('measure-store', 'data')`**: The callback's output updates the
hidden `dcc.Store` component. It does not change anything the user can see
directly.
### 4.2: The Main Content Callback (Updating Everything)
This massive callback updates all the visible components: the charts, the
KPI card values, and the card styles.
```python
@app.callback(
[Output("page1-cards", "children"),
Output("orders-card-value", "children"), ...
Output('orders-card', 'style'), ...],
[Input('dropdown1', 'value'), ...
Input('measure-store', 'data')] # <-- Key Input!
)
def update_page_content(d1, d2, d3, d4, selected_measure):
# 1. Filter data based on dropdowns
filtered_df = merged.copy()
# ... filtering logic ...
# 2. Calculate KPI values from the filtered data
total_orders = filtered_df[measure_cols["Order Amount"]].sum()
# ...
# 3. Generate charts using the selected_measure
fig1 = px.bar(filtered_df, y=selected_measure, ...)
# ... create other figs ...
# 4. Define styles for active vs. inactive cards
active_style = {'border': '2px solid #007BFF', ...}
# ... logic to apply style to the selected card ...
# 5. Format KPI values with the Euro symbol
formatted_orders = f"€{total_orders:,.0f}"
# 6. Return all the new values and components
return charts, formatted_orders, ..., orders_style, ...
```
- **`Input('measure-store', 'data')`**: This is the key. Whenever the
`dcc.Store` is updated (by the first callback), this main callback
re-runs, using the new `selected_measure` to rebuild all the charts.
- **Multiple Outputs**: This callback updates many components at once,
making the dashboard feel highly responsive.
---
## Step 5: Running the App
This part remains the same. Run the script and navigate to
`http://127.0.0.1:8050/`.
```python
if __name__ == "__main__":
app.run_server(debug=True)
```
---
## Key Learnings and Advanced Concepts
### 1. **State Management with `dcc.Store`**
- Use `dcc.Store` to share data between callbacks without cluttering the
user interface. It's the standard way to manage application state in Dash.
### 2. **Advanced Callbacks**
- **Chained Callbacks**: One callback's output can be another's input.
This allows you to create complex, multi-step interactions.
- **`dash.callback_context`**: Essential for determining which of many
inputs triggered a callback, allowing you to create components like
clickable cards.
### 3. **Building Dynamic UIs**
- You can dynamically change the style of components (like highlighting an
active card) by making the `style` property an `Output` of a callback.
### 4. **Outer Joins in Pandas (`how="outer"`)**
- Use outer joins to combine datasets when you want to keep all
information from all tables, even if there are no matches. This is crucial
for data completeness.
### 5. **Formatted Strings (f-strings)**
- Python's f-strings provide a concise and powerful way to format text,
including adding currency symbols and comma separators to numbers (e.g.,
`f"€{total_cash:,.0f}"`).
Happy dashboarding!