Visualisation Best Practices¶
Good charts are honest, clear, and purposeful. This file covers the principles that separate charts that inform from charts that mislead — and gives you a practical checklist for every visualisation you create.
Learning Objectives¶
- Apply colour theory: signal vs. decoration, accessibility
- Use titles and labels that do the work of explanation
- Avoid misleading chart constructions
- Use annotation to draw attention to insights
- Apply the data-ink ratio principle
Principle 1 — Data-Ink Ratio (Edward Tufte)¶
Data-ink ratio = ink used to show data / total ink used.
Maximise the ratio: every mark on the chart should encode information. Remove anything that doesn't.
# Before — too much chart furniture
fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(categories, revenue)
# Default style has heavy grid, chunky borders, etc.
# After — clean, high data-ink ratio
fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(categories, revenue, color="#0D9488", edgecolor="none", alpha=0.9)
ax.spines["top"].set_visible(False) # remove top border
ax.spines["right"].set_visible(False) # remove right border
ax.grid(axis="y", alpha=0.2, linestyle="--") # subtle horizontal guide lines
ax.tick_params(left=False) # remove tick marks
Principle 2 — Titles That State the Insight¶
Don't name the chart — tell the reader what to think.
# Bad title — just names the chart
ax.set_title("Revenue by Category")
# Good title — states the finding
ax.set_title("Electronics Drives 42% of Total Revenue")
The subtitle (secondary text) can add context: "Q1 2024 · Completed orders only"
Principle 3 — Colour as a Signal, Not Decoration¶
Use colour intentionally: - Teal/brand colour → highlight or positive - Red → alert, negative, below target - Grey → background, context, inactive - Avoid rainbow — too many colours mean nothing is emphasised
# BAD — every bar is a different colour (decorative)
colors = ["red", "blue", "green", "purple", "orange"]
ax.bar(categories, revenue, color=colors)
# GOOD — one colour, highlight the most important
colors = ["#0D9488" if cat == "Electronics" else "#CBD5E1" for cat in categories]
ax.bar(categories, revenue, color=colors)
ax.annotate("Top category", xy=(0, 52000), xytext=(0.5, 55000),
arrowprops=dict(arrowstyle="->", color="black"), fontsize=10)
Principle 4 — Accessibility¶
~8% of males are colour-blind (most commonly red-green). Design for everyone.
# Colour-blind safe palette
safe_colors = ["#0072B2", "#E69F00", "#009E73", "#CC79A7", "#56B4E9"]
# Always add a second encoding (shape, label, or pattern)
ax.scatter(x, y, c=groups, marker="o") # bad — colour is the only encoding
ax.scatter(x, y, c=groups, marker=markers[group]) # better — shape + colour
Test your charts at https://coblis.com/colorblind/ (upload a screenshot) or use seaborn.color_palette("colorblind").
Principle 5 — Annotation¶
The chart shows the data. The annotation draws the eye to the insight.
fig, ax = plt.subplots(figsize=(12, 5))
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
revenue = [48250, 52100, 55800, 51200, 58900, 61400]
ax.plot(months, revenue, marker="o", color="#0D9488", linewidth=2)
# Annotate the lowest month
min_idx = revenue.index(min(revenue))
ax.annotate(
f"Post-holiday dip\n£{revenue[min_idx]:,}",
xy=(min_idx, revenue[min_idx]),
xytext=(min_idx + 0.5, revenue[min_idx] - 3000),
arrowprops=dict(arrowstyle="->", color="#64748B"),
fontsize=9, color="#64748B",
)
# Annotate record month
max_idx = revenue.index(max(revenue))
ax.annotate(
f"New record: £{revenue[max_idx]:,}",
xy=(max_idx, revenue[max_idx]),
xytext=(max_idx - 1.2, revenue[max_idx] + 1000),
arrowprops=dict(arrowstyle="->", color="#0D9488"),
fontsize=9, color="#0D9488", fontweight="bold",
)
plt.tight_layout()
plt.show()
Misleading Chart Patterns to Avoid¶
Truncated y-axis¶
# BAD — y-axis starts at 42000, makes a 5% difference look like 3x
ax.set_ylim(42000, 55000)
# GOOD — always start at 0 for bar charts
ax.set_ylim(0, max(revenue) * 1.1)
Cherry-picking the date range¶
"Revenue grew 300% in the last 3 months" — but those 3 months were compared to a pandemic lockdown. Always show context.
3D effects¶
3D charts distort proportions. A 3D pie chart makes the slice at the front look larger than it is. Never use 3D for data.
Inconsistent scales¶
Comparing two charts on the same dashboard where one y-axis goes 0-1000 and another 0-100 leads readers to make incorrect comparisons. Always align scales when charts are meant to be compared.
The Pre-Publish Checklist¶
Before sharing any chart:
- [ ] Does the title state the insight, not just name the chart?
- [ ] Are both axes labelled with units?
- [ ] Is the y-axis zero-based (for bar charts)?
- [ ] Is the colour scheme intentional (max 3 colours, accessible)?
- [ ] Are there any data-ink elements that can be removed?
- [ ] Is the date range clearly stated?
- [ ] Is the data source noted?
- [ ] Can the main insight be read in 5 seconds?
Practice Exercises¶
Warm-up 1. Take a default matplotlib bar chart and improve it: remove top/right spines, reduce grid opacity, remove tick marks, add a descriptive title. 2. Highlight the highest bar in teal and all others in grey. 3. Add an annotation to the highest value with an arrow and text.
Main
4. Redesign this chart: a pie chart with 10 slices, 3D effect, bright rainbow colours, and a title that says "Revenue Breakdown." Make it clear, honest, and readable.
5. Build a "before and after" figure showing the same data with default styling vs. your improved version side by side.
6. Check your charts for colour-blind accessibility using the colourblind palette: sns.set_palette("colorblind").
Stretch
7. Build a function format_ax(ax, title, xlabel, ylabel, source="") that applies your standard chart formatting to any Axes object.
8. Build a complete annotated chart with: descriptive title, subtitle, axis labels with units, annotations on the highest and lowest values, data source note at the bottom.
Interview Questions¶
[Beginner] What is the data-ink ratio and why does it matter?
[Mid-level] A colleague presents a chart where the y-axis starts at 80% and goes to 95%, showing a line that appears to double. What's wrong, and how do you fix it?
[Senior] How would you approach building a data visualisation style guide for a company? What decisions would you standardise?