Skip to content

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:

  1. 5-second test: Can someone read the main insight in 5 seconds?
  2. Data-ink audit: Any chart elements that don't encode data?
  3. Colour check: Are you using colour as signal or decoration?
  4. Annotation check: Does each chart need an annotation to highlight the key finding?
  5. 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 →