Mini Project — Sales Visualisation Dashboard¶
Build a publication-quality sales analytics dashboard using matplotlib and seaborn. This integrates everything from the visualisation session.
Time: 45 minutes
Deliverable: A multi-panel figure saved as sales_dashboard.png
Setup¶
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import seaborn as sns
# Style
sns.set_theme(style="white")
plt.rcParams.update({
"font.family": "sans-serif",
"axes.spines.top": False,
"axes.spines.right": False,
"axes.grid": True,
"grid.alpha": 0.3,
"grid.linestyle": "--",
})
# Load data
df = pd.read_csv("orders_clean.csv", parse_dates=["order_date"])
df["total"] = df["quantity"] * df["unit_price"]
completed = df[df["status"] == "completed"].copy()
completed["month"] = completed["order_date"].dt.month_name()
completed["dow"] = completed["order_date"].dt.day_name()
Build the Dashboard¶
fig = plt.figure(figsize=(18, 12))
gs = gridspec.GridSpec(3, 3, figure=fig, hspace=0.5, wspace=0.4)
# ── KPI row ────────────────────────────────────────────────────
for i, (title, value, change) in enumerate([
("Total Revenue", f"£{completed['total'].sum():,.0f}", "+18.3%"),
("Completed Orders", f"{len(completed):,}", "+12.1%"),
("Avg Order Value", f"£{completed['total'].mean():,.2f}", "+5.2%"),
]):
ax = fig.add_subplot(gs[0, i])
ax.axis("off")
ax.text(0.5, 0.65, value, ha="center", va="center", fontsize=22,
fontweight="bold", color="#0D9488")
ax.text(0.5, 0.35, change, ha="center", va="center", fontsize=13,
color="#0D9488")
ax.text(0.5, 0.1, title, ha="center", va="center", fontsize=10,
color="#94A3B8", style="italic")
ax.set_facecolor("#F8FAFC")
# ── Revenue trend (full width) ─────────────────────────────────
ax_trend = fig.add_subplot(gs[1, :])
monthly = completed.groupby("month")["total"].sum()
month_order = ["January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"]
monthly = monthly.reindex([m for m in month_order if m in monthly.index])
bars = ax_trend.bar(monthly.index, monthly.values / 1000, color="#0D9488", alpha=0.8, edgecolor="none")
ax_trend.axhline(y=monthly.mean() / 1000, color="red", linestyle="--", alpha=0.6, label="Monthly avg")
for bar in bars:
h = bar.get_height()
ax_trend.text(bar.get_x() + bar.get_width()/2, h + 0.5, f"£{h:.0f}k",
ha="center", va="bottom", fontsize=8, color="#475569")
ax_trend.set_title("Monthly Revenue — Period Selected", pad=10)
ax_trend.set_ylabel("Revenue (£k)")
ax_trend.tick_params(axis="x", rotation=30)
ax_trend.legend()
# ── Category breakdown ─────────────────────────────────────────
ax_cat = fig.add_subplot(gs[2, :2])
cat_rev = completed.groupby("category")["total"].sum().sort_values(ascending=True)
colors = ["#CBD5E1"] * len(cat_rev)
colors[-1] = "#0D9488" # highlight top category
ax_cat.barh(cat_rev.index, cat_rev.values / 1000, color=colors, edgecolor="none")
ax_cat.set_title("Revenue by Category")
ax_cat.set_xlabel("Revenue (£k)")
for i, (idx, val) in enumerate(cat_rev.items()):
ax_cat.text(val / 1000 + 0.3, i, f"£{val/1000:.0f}k", va="center", fontsize=9)
# ── Status pie ─────────────────────────────────────────────────
ax_pie = fig.add_subplot(gs[2, 2])
status_counts = df["status"].value_counts()
wedge_colors = ["#0D9488", "#F59E0B", "#EF4444", "#94A3B8"]
ax_pie.pie(status_counts.values, labels=status_counts.index,
autopct="%1.1f%%", colors=wedge_colors[:len(status_counts)],
startangle=90, wedgeprops={"edgecolor": "white", "linewidth": 2})
ax_pie.set_title("Order Status Mix")
# ── Title ─────────────────────────────────────────────────────
fig.suptitle("Sales Performance Dashboard", fontsize=18, fontweight="bold", y=1.02)
fig.text(0.5, 0.99, "Completed orders | Data source: orders_clean.csv",
ha="center", fontsize=10, color="#94A3B8")
plt.savefig("sales_dashboard.png", dpi=150, bbox_inches="tight",
facecolor="white", edgecolor="none")
plt.show()
print("Dashboard saved to sales_dashboard.png")
What to Review¶
After building your dashboard:
- 5-second test: Can someone read the main insight in 5 seconds?
- Data-ink audit: Any chart elements that don't encode data?
- Colour check: Are you using colour as signal or decoration?
- Annotation check: Does each chart need an annotation to highlight the key finding?
- Title check: Does each chart title state a finding, not just name the chart?
Extensions¶
If you finish early:
- Add a day-of-week bar chart showing average order value
- Add target lines to the monthly revenue chart
- Save to PDF (for a client report)
- Add a footnote with the data source and date range
← Visualisation Best Practices · Next: Interview Questions →