Visualization Best Practices
Honest, readable, accessible charts that communicate decisions without distorting data.
How to Use This List
- Apply before exporting any chart to stakeholders or slides.
- Use in PR review for notebooks, Streamlit apps, and report generators.
- Pair with Plotting Large Data when points exceed tens of thousands.
A - Chart Choice & Honesty
- Match chart to question. Trends = line; parts-of-whole only when few categories and sums to 100%.
- Bar charts start at zero. Truncated y-axis exaggerates deltas unless log scale is explicit and labeled.
- Avoid dual y-axes unless necessary. Different scales confuse - prefer indexed series or separate panels.
- Sort categories intentionally. By value, time, or domain order - never default alphabetical for months.
- Label units in axis titles. Dollars, percent, count - not bare numbers.
B - Readability & Accessibility
- Use colorblind-safe palettes.
seaborn colorblind,viridis, or Okabe-Ito - not rainbow jet. - Do not rely on color alone. Add markers, hatching, or direct labels for categories.
- Limit series count. More than ~6 lines in one chart needs faceting or highlighting one series.
- Readable font sizes. 10-12 pt minimum in final PNG at intended viewing size.
- Title states takeaway. "West revenue grew 12% in Q2" beats "Revenue chart".
C - Data Preparation
- Aggregate before plot. Million-point scatters become hexbins, SQL rollups, or datashader images.
- Document filters. App widgets and SQL WHERE clauses belong in subtitle or caption.
- Handle missing data explicitly. Gaps in lines or annotated breaks - no silent interpolation.
- Use consistent timezones. UTC in data; local only in display with label.
- Version data snapshot. Footer with
data_dtand git SHA for reproducibility.
D - Export & Production
- Close matplotlib figures in loops. Prevent memory leaks in batch jobs.
- Choose format for medium. PDF/SVG print; PNG 150 dpi screen, 300 dpi slides.
- Cache dashboard queries. Streamlit
cache_data, Dash stores - no Parquet reload per click. - Test headless rendering.
matplotlib.use("Agg")in CI snapshot tests. - No manual chart edits after export. Change the script, not PowerPoint rectangles.
FAQs
When can y-axis not start at zero?
- Line charts of rates or indices where small moves matter and scale is labeled.
- Never for bar magnitude comparisons without clear justification.
pie or bar?
- Bar almost always - easier to compare lengths than angles.
- Pie only for 2-3 parts of a whole and static slides.
How do I show uncertainty?
- Error bars, ribbons, or box/violin plots - state what interval means (CI vs IQR).
What about 3D charts?
- Avoid - distorts perception. Use faceted 2D panels instead.
How do I test chart output?
- Snapshot PNG bytes in CI with fixed seed data.
- Assert file exists and size > threshold as minimal gate.
Interactive or static?
- Static for papers and email; interactive for exploration dashboards.
- Do not email 5 MB Plotly HTML unless requested.
How do I annotate outliers?
ax.annotatewith short label - explain why point matters.- Do not delete outliers from chart without disclosure.
Should I use seaborn defaults?
- Good starting point - still verify scales, labels, and estimator choice.
How do I handle class imbalance in charts?
- Show counts alongside rates; use separate views for prevalence vs performance.
What is chartjunk?
- Heavy grids, 3D effects, and decoration that do not encode data.
- Prefer
seaborn ticksor light grid alpha 0.3.
Related
- Visualization Basics - chart tour
- matplotlib - export and layout
- Plotting Large Data - aggregation
- Data Analysis Best Practices - trustworthy numbers upstream
Stack versions: This page was written for Python 3.14.0 (stable 3.14, maintenance 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, and uv 0.6+.