Python Dash Tutorial: Build a Filterable Sales Dashboard

PYTHON Published Updated 4 mins read By Leon Wei
Python Dash Tutorial: Build a Filterable Sales Dashboard cover image

Quick summary

Summarize this blog with AI

Dash connects Python functions to browser controls and Plotly charts. This tutorial builds one complete sales dashboard with a region selector and an empty state. The four rows are fictional; no external dataset, database, or API key is required.

Create an environment and install the tested versions

On macOS or Linux, run:

python3 -m venv .venv
.venv/bin/python -m pip install "dash==4.4.1" "plotly==7.1.0"

In Windows PowerShell, use:

py -m venv .venv
.venv\Scripts\python.exe -m pip install "dash==4.4.1" "plotly==7.1.0"

The example was tested with Python 3.12.14, Dash 4.4.1, and Plotly 7.1.0. If you use a different version, repeat the callback and browser checks rather than assuming matching behavior. Use the pip guide to select the correct environment interpreter.

Define the question and expected totals

The chart answers: “How much revenue did each product generate in the selected regions?” Each fixture row is a region/product total in whole US dollars. East contributes 120 + 80 = 200; West contributes 150 + 50 = 200. With both selected, Books totals 270, Games 130, and the overall revenue 400. Selecting no regions should not silently show all data.

Save this complete application as app.py

from dash import Dash, Input, Output, dcc, html
import plotly.graph_objects as go

SALES = [
    {"region": "East", "product": "Books", "revenue": 120},
    {"region": "East", "product": "Games", "revenue": 80},
    {"region": "West", "product": "Books", "revenue": 150},
    {"region": "West", "product": "Games", "revenue": 50},
]

def sales_figure(regions):
    selected = set(regions or []) & {"East", "West"}
    rows = [row for row in SALES if row["region"] in selected]
    figure = go.Figure()
    if not rows:
        figure.add_annotation(
            text="No regions selected", showarrow=False,
            x=0.5, y=0.5, xref="paper", yref="paper",
        )
        figure.update_layout(title="Sales by product")
        return figure, "No regions selected."
    totals = {}
    for row in rows:
        totals[row["product"]] = totals.get(row["product"], 0) + row["revenue"]
    products = sorted(totals)
    figure.add_bar(x=products, y=[totals[p] for p in products])
    figure.update_layout(
        title="Sales by product", yaxis_title="Revenue (USD)",
        margin=dict(l=45, r=15, t=55, b=45),
    )
    summary = f"{len(rows)} records; total revenue ${sum(totals.values())}."
    return figure, summary

app = Dash(__name__)
app.layout = html.Main([
    html.H1("Regional sales"),
    html.Label("Regions", htmlFor="regions"),
    dcc.Dropdown(
        id="regions", options=["East", "West"],
        value=["East", "West"], multi=True, clearable=True,
    ),
    html.P(id="summary", role="status"),
    dcc.Graph(id="sales", responsive=True),
], style={"maxWidth": "800px", "margin": "auto", "padding": "16px"})

@app.callback(
    Output("sales", "figure"), Output("summary", "children"),
    Input("regions", "value"),
)
def update_sales(regions):
    return sales_figure(regions)

if __name__ == "__main__":
    app.run(debug=False)

Run .venv/bin/python app.py on macOS/Linux or .venv\Scripts\python.exe app.py in Windows PowerShell. Open the local URL printed by Dash, normally http://127.0.0.1:8050. Keep this development server local. The example neither authenticates users nor supplies a production hosting configuration.

Understand the callback contract

The dropdown's value is an input. The callback returns two values in the order declared by its outputs: the figure and the summary text. The calculation is in a separate ordinary function, so it can be tested without clicking the browser. The fixture remains unchanged when a selection changes; each call builds its own totals and figure.

Dash's callback guide explains this input/output relationship. Current components are imported from dash, and the local runner is app.run(). The old dash_core_components/dash_html_components imports and app.run_server() are not the pattern to copy; see the migration guide.

Check the calculation and the empty state

from app import sales_figure

figure, summary = sales_figure(["East", "West"])
assert list(figure.data[0].x) == ["Books", "Games"]
assert list(figure.data[0].y) == [270, 130]
assert summary == "4 records; total revenue $400."

figure, summary = sales_figure(["East"])
assert list(figure.data[0].y) == [120, 80]
assert summary == "2 records; total revenue $200."

figure, summary = sales_figure([])
assert len(figure.data) == 0
assert summary == "No regions selected."

Also clear the selector in the browser, reselect one region, and use the keyboard to change it. Unknown region values are excluded by the calculation. Treat this as a fixed-data demo: a real application must validate its data contract, define missing-value and currency rules, and enforce authorization on the server.

Extend one layer at a time

Before replacing the fixture with a CSV, decide which columns are required and how duplicate records, missing revenue, and invalid region labels should be handled. Do not automatically drop rows or fill revenue with zero without a business rule. Add a table or download only when it helps the reader answer the dashboard's stated question.

Validation scope: callback results, empty/unknown selections, layout endpoints, and callback HTTP responses were tested locally. The development app was launched for browser inspection. This is a teaching application, not a production deployment benchmark. For a related data-preparation lesson, see cleaning data with NumPy and pandas.

Interview Prep

Begin Your SQL, Python, and R Journey

Master 230 interview-style coding questions and build the data skills needed for analyst, scientist, and engineering roles.

Related Articles

All Articles
Pandas read write files cover image
python Oct 17, 2026

Pandas read write files

Explore the essentials of Pandas for data analysis in Python. Learn how it simplifies data manipulation and analysis with its robust data struct…

Python kwargs and args cover image
python Apr 29, 2024

Python kwargs and args

Discover Python function arguments. Learn the essentials of *args and **kwargs. Master the art of crafting versatile APIs, handling positional a…

Python mock library cover image
python Apr 29, 2024

Python mock library

Master the Python Mock Library for robust unit testing. Learn to simulate dependencies with Mock, MagicMock, and patch, ensuring reliable and is…

Top python game engines cover image
python Apr 29, 2024

Top python game engines

Explore game creation with Python! Discover how its simplicity & powerful libraries like Pygame and Panda3D make Python a viable choice for indi…