v0.0.4 to v0.0.4

5 added, 18 removed. Audit A to B.

---
- name: dashboard-beautify
description: >
- Build beautiful, production-grade data dashboards and infographics using
- Dash and Plotly Python. Use this skill when the user wants to create a
- dashboard, build an infographic, visualise data, make a chart, display KPIs,
- or build an analytics app. Also trigger when the user mentions Dash, Plotly,
- data visualisation, or wants to improve the quality or appearance of existing
- charts.
- Covers single polished infographics through to multi-panel interactive
- dashboards. Read this skill before writing any Dash or Plotly code — it
- contains required project structure, coding standards, and a pre-ship
- checklist.
+ name: dashboard-beautify
metadata:
- version: 0.0.4
- author: Marie-Lynne Block
- tags:
- - Dash
- - Plotly
- - Dashboard
- - Data Visualisation
+ skill-author: 'Marie-Lynne Block'
+ version: 0.0.4
+ author: Marie-Lynne Block
+ tags:
---
# Data Atelier
Production-grade dashboards and infographics with Dash and Plotly Python.
**Philosophy**: form follows function, colour communicates, every design decision earns its place.
A dashboard is a curated argument made from data — not a chart dump.
**Default target**: production delivery. Optimise for correctness, maintainability, and accessibility
from the start — not as an afterthought.
---
## Before writing any code — establish the brief
1. **What is the primary question this dashboard answers?** The answer must be visible in < 3 seconds.
2. **Who is the audience?** Executive / analyst / operational — determines density, annotation level, interactivity depth.
3. **Single infographic or multi-panel dashboard?** Different layout and scope strategies apply.
4. **What is the data shape?** Time series / categories / relationships / distributions / compositions.
5. **What is the data source and refresh cadence?** Determines caching strategy and performance approach.
If any of these are unanswered, ask before writing code.
---
## Step 1 — Data quality gate
Run this before any design work. A beautiful dashboard built on bad data is worse than no dashboard.
Read `references/data-quality.md` for full checks. Minimum required:
- **Missingness**: identify null rates per column — flag anything > 5% to the user before proceeding
- **Duplicates**: check for duplicate rows on the natural key; deduplicate or explain why not
- **Outliers**: surface extreme values — confirm they are real, not ingestion errors
- **Unit consistency**: confirm all numeric columns use consistent units (£ vs $, thousands vs millions)
- **Metric definitions**: confirm calculated fields (rates, ratios, aggregations) match the agreed definition
- **Timezone normalisation**: all timestamps must be in a single consistent timezone before display
---
## Step 2 — Choose the right chart
| Data intent | Recommended | Never |
|---|---|---|
| Change over time | Line, area, connected scatter | Bar (unless discrete periods only) |
| Compare categories | Horizontal bar (many labels), column (few) | Line (implies continuity between categories) |
| Part of a whole | Stacked bar, treemap, sunburst | Pie with > 4 slices |
| Distribution | Histogram, violin, box plot | Bar chart of means (hides spread) |
| Correlation / relationship | Scatter, bubble (3rd variable as size) | Line |
| Flow / transfer | Sankey | — |
| Multi-variable dense | Heatmap, parallel coordinates | Radar/spider (angle distorts magnitude) |
| Single KPI | Styled `html.Div` card — no chart | Any chart type |
| Multiple series, different scales | Small multiples (facets) | Dual-axis (almost always misleading) |
**Hard anti-patterns — flag to user unless explicitly overridden:**
- 3D charts (perspective distortion destroys accuracy)
- Rainbow / Jet colormaps on continuous data (use Viridis, Cividis, Magma)
- Pie charts with > 4 slices
- Dual-axis charts
Read `references/chart-selection.md` for edge cases and the full decision framework.
---
## Step 3 — Colour strategy
Read `references/colour-theory.md` for the full palette library and per-theme Plotly template code.
This skill is **theme-agnostic**. Select the appropriate theme for the project and apply it consistently — do not mix themes within a project. Available themes documented in the reference:
- **Nord** — soft, cool-toned, professional; well-suited to extended use and healthcare/public sector
- **Okabe-Ito** — colourblind-safe, vivid; good default when no brand constraint applies
- **Muted Professional** — desaturated enterprise palette; suits financial and government contexts
**Colour rules — apply regardless of theme:**
1. Maximum 6–7 distinct colours per chart — beyond that, use grouping or small multiples.
2. Signal colours (red = bad, green = good, amber = warning) are reserved for status encoding only.
3. Colour is **never the only encoding** for critical distinctions — always pair with shape, dash, or pattern. See `references/colourblindness.md`.
4. Sequential palettes for ordered/continuous data; diverging palettes for data with a meaningful midpoint.
5. Never hard-code hex values inline — import from a central `colours.py` module.
---
## Step 4 — Layout and composition
Read `references/layout-principles.md` for grid recipes and typography scale.
- Use `dbc.Container` / `dbc.Row` / `dbc.Col` from `dash-bootstrap-components` for all grid work.
- **F-pattern reading order**: most important content top-left. KPI cards → primary chart → supporting charts.
- Wrap every chart in `dbc.Card` with consistent internal padding (`p-3`).
- Consistent chart heights within a row — mixed heights break visual rhythm.
- White space is structural, not waste.
- Infographic titles state the insight, not the chart type: `"Revenue grew 34% after the Q2 pricing change"` not `"Revenue over time"`.
---
## Step 5 — Plotly template
Register a named template once in `app.py` before the layout import. Every figure inherits it automatically.
Fill palette values from `colours.py` for the selected theme. Full per-theme template code is in `references/colour-theory.md`.
```python
import plotly.graph_objects as go
import plotly.io as pio
from colours import PALETTE, SURFACE, TEXT_PRIMARY, TEXT_MUTED, GRID, HOVER_BG, HOVER_BORDER
pio.templates["project_theme"] = go.layout.Template(
layout=go.Layout(
font=dict(family="Inter, system-ui, sans-serif", size=13, color=TEXT_PRIMARY),
paper_bgcolor="rgba(0,0,0,0)", # transparent — card handles background
plot_bgcolor=SURFACE,
colorway=PALETTE,
margin=dict(l=40, r=20, t=55, b=40),
title=dict(
font=dict(size=16), # use html.B() in title string for bold — weight is not a valid Plotly font property
x=0.0,
xanchor="left",
),
xaxis=dict(gridcolor=GRID, linecolor=GRID,
tickfont=dict(size=11, color=TEXT_MUTED), zeroline=False),
yaxis=dict(gridcolor=GRID, linecolor=GRID,
tickfont=dict(size=11, color=TEXT_MUTED), zeroline=False),
legend=dict(orientation="h", y=1.02, x=0,
bgcolor="rgba(0,0,0,0)", borderwidth=0),
hoverlabel=dict(bgcolor=HOVER_BG, bordercolor=HOVER_BORDER,
font=dict(size=12, color=TEXT_PRIMARY)),
)
)
pio.templates.default = "project_theme"
```
Chart functions live in `components/charts.py` and are **pure**: DataFrame in, `go.Figure` out — no side effects, no globals, no Dash logic inside figure functions.
---
## Step 6 — Project structure
```
project/
├── app.py # Dash init, template registration, server export
├── layout.py # Full app layout
├── callbacks.py # All @callback functions
├── colours.py # Single source of truth for all colour constants
├── components/
│ ├── charts.py # Pure figure-building functions
│ └── cards.py # KPI cards and reusable UI components
├── data/
│ └── loader.py # Data loading, preprocessing, caching
└── assets/
└── custom.css # Minimal overrides only
```
---
## Step 7 — Performance
Read `references/performance.md` before building against real data at scale. Minimum requirements:
- Server-side aggregation — never send row-level data to the browser when aggregated data suffices
- Cache expensive loader functions (Flask-Caching or `functools.lru_cache`)
- WebGL traces (`go.Scattergl`, `go.Heatmapgl`) for > 10k data points
- `dcc.Loading` wrapper on every chart that involves a data fetch callback
- Empty-state handling in every callback — never return a blank figure without a user-facing message
---
## Step 8 — Interaction design
Read `references/interaction-design.md` for full rules. Core principles:
- The **default view** must answer the primary question with zero interaction.
- Filters go **above** the charts they affect — never below.
- Maximum **3 filter controls** on a single view before reconsidering the dashboard scope.
- Use **drill-down** when detail replaces the summary; **cross-filter** when users compare sub-populations.
- `dcc.Store` for shared state — never global Python variables in callbacks.
- One callback per logical interaction; return `dash.no_update` for unchanged outputs.
---
## Step 9 — Formatting and localisation
Read `references/formatting.md` for full conventions. Non-negotiable defaults:
- Dates: `DD Mon YYYY` (e.g. `15 Jan 2024`) — never ambiguous `MM/DD/YY`
- Timestamps: single named timezone throughout; label it in the axis title or chart subtitle
- Currency: symbol prefix, thousands separator, 2dp for transactions, 0–1dp for aggregated totals (`£4.2M`)
- Percentages: 1 decimal place (`68.3%`)
- Large numbers: abbreviate at 1K / 1M / 1B thresholds — define the breakpoints in `formatting.py`
---
## Step 10 — Pre-ship checklist
All boxes must be checked before release.
**Data correctness**
```
□ Key metrics reconciled against source of truth
□ Filters tested for empty results — empty state handled gracefully
□ Date range edge cases tested (first day, last day, single day)
□ Aggregations spot-checked against raw data for a known sample
```
**Visual and UX**
```
□ Primary question answered in < 3 seconds on default view
□ Chart titles state the insight, not the chart type
□ Axis labels include units where not obvious from context
□ hovertemplate set on all traces (value + label + unit)
□ Consistent chart heights within each row
□ Filters positioned above the charts they affect
□ Empty-state message shown when a filter returns no data
□ Layout checked at 375px mobile viewport width
```
**Accessibility**
```
□ Colour is not the only encoding for any critical distinction
□ Red/green signal pairs have redundant encoding (shape / dash / pattern)
□ WCAG AA contrast met for all text (4.5:1 normal, 3:1 large/bold)
□ config={"responsive": True} on all dcc.Graph components
□ Tab order is logical — filters before charts, charts in reading order
□ Key chart insights have a visible text summary (not hover-only)
□ Simulated under deuteranopia — no information is lost
```
**Code quality**
```
□ No hex colour literals inline — all colours imported from colours.py
□ All chart functions are pure (DataFrame in, Figure out)
□ No global mutable state in callbacks
□ dcc.Loading on all data-fetch callbacks
□ weight not used in any Plotly font dict
```
---
## Reference files
| File | When to read |
|---|---|
| `references/chart-selection.md` | Choosing chart type, edge cases, anti-patterns |
| `references/colour-theory.md` | Theme selection, full palette library, per-theme template code |
| `references/colourblindness.md` | CVD simulation, redundant encoding, contrast checking, keyboard/screen-reader |
| `references/layout-principles.md` | Grid recipes, typography scale, infographic anatomy |
| `references/data-quality.md` | Pre-design data checks, missingness, outliers, metric definitions |
| `references/performance.md` | Large data, WebGL, caching, callback memoization |
| `references/interaction-design.md` | Filter rules, drill-down vs cross-filter, control count limits |
| `references/formatting.md` | Dates, timezones, currency, number abbreviation conventions |
| `references/observability.md` | Structured logging, callback timing, error handling patterns |
| `references/testing.md` | Figure function tests, callback integration tests, snapshot regression |
| `references/validation.md` | Pre-ship QA checklist, value reconciliation, axis/unit sanity checks |