Data Visualization Principles Cheat Sheet
Guides chart type selection, design best practices like data-ink ratio and consistent color encoding, and common mistakes that mislead viewers.
A Clean Chart in Matplotlib
Remove chartjunk and emphasize the data itself.
import matplotlib.pyplot as pltfig, ax = plt.subplots(figsize=(8, 5))ax.bar(categories, values, color="#4C72B0")ax.spines["top"].set_visible(False) # remove chartjunkax.spines["right"].set_visible(False)ax.set_title("Revenue by Region", fontsize=14, weight="bold")ax.set_ylabel("Revenue ($M)")ax.grid(axis="y", alpha=0.3) # subtle gridlines onlyplt.tight_layout()plt.savefig("chart.png", dpi=150)
Choosing a Chart Type
Match the chart to the question you're answering.
- Comparison (categories)- Use bar charts; sort bars by value unless there's a natural category order
- Trend over time- Use line charts; keep the time axis continuous and evenly spaced
- Part-to-whole- Use stacked bar or 100% stacked bar; avoid pie charts with more than 5 slices
- Distribution- Use histograms or box plots to show spread, skew, and outliers
- Relationship- Use scatter plots for two continuous variables; add a trend line for correlation
- Ranking- Use horizontal bar charts sorted descending when category labels are long
Design Best Practices
Habits that make charts easier to read correctly.
- Data-ink ratio- Maximize the proportion of ink used to show data vs. decoration (Tufte's principle)
- Consistent color encoding- Use the same color for the same category across all charts in a report
- Direct labeling- Label lines/bars directly instead of relying solely on a legend when possible
- Start bar charts at zero- Truncated y-axes on bar charts exaggerate differences and mislead viewers
- Pre-attentive attributes- Use color, size, or position (not just labels) to highlight the key takeaway
Small Multiples Instead of Overplotting
Split a cluttered multi-series chart into a grid of simple, comparable panels sharing one scale.
import matplotlib.pyplot as pltregions = df["region"].unique()fig, axes = plt.subplots(2, 3, figsize=(12, 6), sharey=True)for ax, region in zip(axes.flat, regions): subset = df[df["region"] == region] ax.plot(subset["month"], subset["revenue"], color="#4C72B0") ax.set_title(region, fontsize=10) ax.spines[["top", "right"]].set_visible(False)fig.suptitle("Monthly Revenue by Region", fontsize=14, weight="bold")plt.tight_layout()# Shared y-axis lets viewers compare magnitude across panels honestly
Colorblind-Safe Categorical Palette
Use a palette validated for deuteranopia/protanopia instead of default red-green encodings.
# Okabe-Ito palette: safe for the most common forms of color vision deficiencyOKABE_ITO = [ "#E69F00", # orange "#56B4E9", # sky blue "#009E73", # bluish green "#F0E442", # yellow "#0072B2", # blue "#D55E00", # vermillion "#CC79A7", # reddish purple "#000000", # black]import matplotlib.pyplot as pltfig, ax = plt.subplots()for i, cat in enumerate(categories): ax.bar(cat, values[i], color=OKABE_ITO[i % len(OKABE_ITO)])# Never rely on red vs. green alone to encode meaning (e.g. "bad" vs "good")
Diverging Color Scale Anchored at a Meaningful Midpoint
Center a heatmap's color scale at zero (or another baseline) so positive and negative values are visually symmetric.
import matplotlib.pyplot as pltfrom matplotlib.colors import TwoSlopeNormimport numpy as npdata = df.pivot(index="metric", columns="month", values="pct_change")norm = TwoSlopeNorm(vmin=data.values.min(), vcenter=0, vmax=data.values.max())fig, ax = plt.subplots(figsize=(9, 4))im = ax.imshow(data, cmap="RdBu_r", norm=norm, aspect="auto")fig.colorbar(im, ax=ax, label="% change")ax.set_xticks(range(len(data.columns)))ax.set_xticklabels(data.columns, rotation=45, ha="right")ax.set_yticks(range(len(data.index)))ax.set_yticklabels(data.index)# Without vcenter=0, a symmetric red-blue colormap would misleadingly imply# the midpoint of the DATA range is "neutral" rather than zero
Annotating the Key Takeaway Directly on the Chart
Add a callout so the reader doesn't have to infer the insight themselves.
fig, ax = plt.subplots(figsize=(8, 5))ax.plot(dates, revenue, color="#4C72B0", linewidth=2)launch_date = pd.Timestamp("2026-03-01")ax.axvline(launch_date, color="#D55E00", linestyle="--", alpha=0.7)ax.annotate( "Pricing change launched\n(+18% revenue in 30 days)", xy=(launch_date, revenue.loc[launch_date]), xytext=(20, 30), textcoords="offset points", fontsize=9, color="#D55E00", arrowprops=dict(arrowstyle="->", color="#D55E00"),)ax.spines[["top", "right"]].set_visible(False)plt.tight_layout()
Deceptive Chart Patterns to Avoid
Techniques (intentional or accidental) that mislead a viewer beyond the basic truncated-axis pitfall.
- Dual-axis correlation trick- Overlaying two unrelated series on independently scaled axes to visually imply a correlation that isn't statistically real
- Cherry-picked time window- Choosing a start/end date that flatters a trend while hiding the fuller, less favorable context
- Area/volume scaling errors- Scaling a circle or icon's linear radius by a value instead of its area, which exaggerates the perceived ratio (radius^2)
- 3D perspective distortion- 3D pie/bar charts distort slice and bar proportions due to perspective - always prefer flat 2D encodings
- Non-uniform time buckets- Mixing daily, weekly, and monthly buckets on the same axis without labeling, which hides real volatility
- Inverted or non-monotonic axis- Reversing an axis direction (e.g. a y-axis that decreases upward) without a clear label reverses the visual story
- Rebasing without disclosure- Silently changing an index's base year/value mid-series so growth rates before and after aren't comparable
Before choosing a chart type, write down the one-sentence takeaway you want the viewer to walk away with - the chart type should make that sentence obvious at a glance.