Heatmap
A grid of colored cells where color intensity encodes magnitude. Heatmaps turn dense data matrices into instantly scannable patterns — the darker the cell, the higher the value — and have been used to spot structure in everything from social statistics to gene expression.
// 01 — The chart
What it looks like
A heatmap showing website traffic patterns across days and hours. Darker cells indicate higher traffic — the pattern clearly shows weekday business hours as the peak period.
// 02 — Definition
What is a heatmap?
A heatmap is a data visualization that represents values as colors in a two-dimensional grid. Each cell in the grid corresponds to a combination of two categorical or ordinal variables (rows and columns), and the cell’s color encodes the magnitude of a third, quantitative variable on a sequential or diverging color scale. The result is a matrix where the structure of the data — clusters, gradients, outliers, blocks — jumps out at a glance even when there are thousands of cells.
The power of a heatmap lies in pattern recognition at scale. The human visual system is unusually good at picking out regions of consistent color and noticing where they break, but unusually bad at reading exact values from color. Heatmaps lean into that asymmetry: they sacrifice per-cell precision (color is less accurate than position or length) in exchange for the ability to see the whole matrix at once. A dark patch jumps out immediately; the same extreme value buried in row 47, column 12 of a spreadsheet does not.
Two design choices dominate every heatmap. The first is the palette: sequential (light to dark in a single hue) for unsigned magnitudes, or diverging (two hues meeting at a neutral midpoint) for signed values around a meaningful zero such as a correlation coefficient or year-over-year change. The second is the row and column order: random order looks like noise, but sorting by value, by a meaningful key, or by hierarchical clustering can transform the same data into clearly readable blocks and stripes.
Heatmaps live in two related families. As a Magnitude chart they are an alternative to bar charts and stacked bars when the data has two categorical dimensions. As a Correlation chart they are the standard way to display correlation matrices, distance matrices, and confusion matrices. The rest of this guide is about how to live in the heatmap’s sweet spot — and how to recognize when a sortable table, a small multiple of bar charts, or a choropleth map would do the job better.
Origin: The idea of using shading to encode values in a matrix dates back to French statistician Toussaint Loua’s 1873 Atlas statistique de la population de Paris, which used shaded cells to compare social statistics across Parisian arrondissements. The modern term “heat map” was trademarked by software designer Cormac Kinney in 1991 to describe a real-time financial market display. Heatmaps then exploded in genomics in the 1990s with hierarchical clustering of gene-expression matrices, and today they are everywhere from GitHub contribution graphs to UX click maps.
// 03 — When to use
When a heatmap is the right call
Reach for a heatmap when the question is about pattern across two dimensions and the answer is encoded in a third numeric value per cell. Below are the situations where a heatmap consistently wins against the alternatives.
- You have a dense matrix (dozens to thousands of cells) and want readers to spot patterns and clusters
- Showing a correlation matrix between many variables in a single chart
- Displaying activity patterns across two dimensions (day × hour, product × region, week × day-of-week)
- Visualizing genomics, machine-learning confusion matrices, or other naturally matrix-shaped data
- The audience needs to see the gestalt rather than every exact value (they can hover or read a table for precision)
- Rows and columns can be sorted or clustered to reveal block structure
- You want a layout that scales from a small dashboard tile to a wall-sized poster without redesign
// 04 — When not to use
When a heatmap is the wrong call
Heatmaps can technically display almost any 2D matrix, but “technically possible” is not the same as “good idea.” Below are the cases where a heatmap actively hides information you need to communicate.
- Readers need precise per-cell values — color is the least precise visual encoding
- Your matrix is small (under ~5×5) — a labeled table or small-multiple bar chart is clearer
- Your data is one-dimensional — use a bar chart or histogram instead
- Your data is geographic — use a choropleth, hexbin map, or proportional symbol map
- Rows and columns have no meaningful order and can’t be clustered — the matrix will look like noise
- You can’t guarantee a colorblind-safe palette and a textual fallback
- The values span many orders of magnitude — use a log color scale, or split the data into panels
- You’re showing parts of a whole — use a stacked bar, marimekko, or 100% stacked variant
// 05 — Data requirements
What your data needs to look like
Before building the chart, your dataset needs to fit a specific shape. Use this checklist to confirm yours does.
Shape
One row per (row, column) pair, with a numeric value column. Or, equivalently, a 2D matrix with row labels on the index and column labels on the header.
Minimum rows
At least 5×5 cells (~25 rows in long format). Below that, a labeled table communicates more clearly.
Maximum rows
Up to ~10,000 cells before rendering and Moiré patterns become a problem. Past that, downsample or cluster.
The first dimension of the matrix — days of the week, gene IDs, products, customer segments, hours of the day. Each unique value becomes one row of the grid. Order is significant: re-sorting rows often reveals patterns that random order hides.
The second dimension of the matrix — hours, time periods, experiment conditions, regions. Each unique value becomes one column. Like rows, the order of columns can be sorted, clustered, or kept in a natural sequence (months, ordinal scales).
The quantity each cell encodes. Must share a single unit across cells (count, percent, dollars, correlation coefficient). For diverging palettes the value should have a meaningful zero point (e.g. signed change, correlation in [-1, +1]).
Optional cell text used to print the numeric value (or a symbol such as * for significance) directly inside the cell. Useful for small grids; turn off for grids larger than ~50 cells where text becomes unreadable.
| day | hour | sessions |
|---|---|---|
| Mon | 10am | 880 |
| Mon | 12pm | 940 |
| Tue | 12pm | 1180 |
| Tue | 2pm | 980 |
| Sat | 8pm | 540 |
| Sun | 6am | 30 |
Tip: heatmaps want a complete matrix — every (row, column) pair filled in. If your raw data is sparse, decide explicitly how to fill the gaps (zero, NA, the row mean, the global mean) before plotting. Tools like pandas .pivot() + .fillna() or SQL CROSS JOIN are how you get there.
// 06 — Anatomy
Parts of a heatmap
Every heatmap is built from the same handful of parts. Knowing the names makes it easier to talk about what to keep, what to drop, and which knob to turn when the chart isn’t doing its job.
// 07 — Step-by-step
Step-by-step: how to build a good heatmap
A nine-step recipe that works regardless of the tool. Walk through it the first few times and the moves become automatic; skip steps and the chart usually shows it.
- 1
Pick the question the matrix has to answer
A heatmap answers “where are the high and low values across these two dimensions, and what pattern do they form?” Write that question down. If you actually need precise per-cell values, a sortable table is better; if your data is one-dimensional, use a bar chart. - 2
Reshape the data into a 2D matrix
Pivot your long-format data into a wide matrix with one row per row-category, one column per column-category, and one numeric value per cell. Decide how to fill in missing cells (NA, zero, the row mean) before plotting — different fills tell different stories. - 3
Choose sequential vs diverging
If values run from low to high with no meaningful zero, use a sequential palette (light→dark in one hue: Viridis, Blues). If values are signed around a meaningful zero (correlation, percent change, deviation from mean), use a diverging palette (two hues meeting at a neutral midpoint: RdBu, BrBG). - 4
Pick a perceptually uniform palette
Use Viridis, Cividis, Magma, Inferno, or any ColorBrewer scheme. Avoid jet, rainbow, hsv, and spectral — they are not perceptually uniform and will distort your data. Cividis is the safest default for colorblind audiences while still looking attractive. - 5
Set the color domain explicitly
Don’t let the library auto-scale silently. Decide whether the domain is [0, max], [min, max], or fixed across multiple panels. For a diverging scale, anchor the midpoint to zero and make the domain symmetric (e.g. [-1, +1]) so reds and blues are comparable. - 6
Sort or cluster the rows and columns
Random row order makes a heatmap look like noise. Sort rows by value, by a meaningful key (date, magnitude), or by hierarchical clustering. Re-order columns the same way. Block patterns, diagonal stripes, and clusters only emerge after sorting. - 7
Decide whether to label cells
If the grid is smaller than ~50 cells, print the numeric value inside each cell with text contrast that flips between dark and light against the cell color. Past 50 cells, hide the labels and lean on the legend; past 200 cells, hide the gridlines too. - 8
Add a clear legend with units
Every heatmap needs a labeled color bar. State the units (“correlation r”, “sessions per hour”, “%”), show the numeric range, and use 5–7 visible tick marks. Position the legend close to the plot, not floating in a corner. - 9
Write a takeaway title and ship
“Traffic by day and hour” is a label. “Weekday lunchtime drives our peak traffic — weekend evenings barely register” is a takeaway. Lead with the takeaway, put the descriptive label as a subtitle, and verify the chart still works at the size your readers will see it.
// 08 — Real-world examples
Where you’ll see heatmaps used
Heatmaps show up most often in four places: software product analytics, scientific research, financial markets, and operational dashboards. Each context has its own conventions, but they all reward the same fundamentals.
Software: GitHub contribution calendar
GitHub’s contribution graph is one of the most widely recognized heatmaps in tech. It shows your coding activity across 365 days as a grid of green squares (rows = days of the week, columns = weeks of the year). Darker greens mean more commits. At a glance you can see work patterns, vacations, and productivity streaks — information no line chart of daily commits would surface as fast.
Product AnalyticsGenomics: Gene-expression heatmaps
Bioinformatics researchers use heatmaps to visualize expression of thousands of genes across dozens of samples simultaneously. Red cells mean high expression, blue means low, and hierarchical clustering on both axes groups similar genes and similar samples together. The resulting block patterns are often the headline finding of a paper.
ScienceUX research: Click and scroll heatmaps
Tools like Hotjar, Crazy Egg, and Microsoft Clarity overlay heatmaps on web pages to show where users click, move their mouse, or stop scrolling. Hot spots reveal which elements get attention; cold spots reveal what the design failed to surface. Product teams use them to prioritize layout changes.
UX DesignOperations: Day × hour traffic dashboards
Operations teams at retail, transit, and SaaS companies pin a day-of-week × hour-of-day heatmap of demand to the wall of their war room. The matrix instantly tells the staffing manager where peaks form and where troughs sit, in a way a list of 168 hourly numbers never would.
Operations// 09 — Variations
Types of heatmaps
The basic heatmap has several specialized variants, each tuned for a particular data shape. The headline rule is the same: pick the variant whose strengths match your question.
Calendar heatmap
Maps daily values to a calendar grid (weeks × days). GitHub’s contribution graph is the canonical example.
Correlation matrix
Symmetric heatmap of pairwise correlations between many variables. Diverging palette anchored at zero.
Clustered heatmap (clustermap)
A heatmap with hierarchical-clustering dendrograms on the row and column margins. Standard in genomics.
Density heatmap (KDE / hexbin)
A continuous heatmap built from kernel density or hexagonal binning of point data. Useful for over-plotted scatters.
// 10 — Comparisons
Heatmap vs other chart types
Heatmaps get confused with several other chart types because they all use color to encode magnitude. The differences matter — picking the wrong one changes what your reader is allowed to conclude.
Heatmap vs choropleth (geographic)
Both encode magnitude with color, but they live in different coordinate systems. A choropleth colors geographic regions on a map; a heatmap colors cells in an abstract row×column grid. Use whichever coordinate system actually carries the meaning of your data.
Heatmap (row × column)
Encodes a value with color across a grid of two non-spatial dimensions (day×hour, gene×sample, product×region as labels, not as map polygons).
- Rows and columns are categorical or ordinal
- Order can be sorted or clustered
- No geographic interpretation
Choropleth map
Encodes a value with color across geographic polygons (countries, states, ZIPs, hex bins on a map). Spatial position is part of the meaning.
- Polygons follow real geography
- Must normalize by area (rate, not count)
- Often misleading without a cartogram alternative
Heatmap vs scatter plot (correlation)
When the question is “how do these two variables relate?” a scatter plot answers it precisely for a few hundred points. When the question is “how do these many variables correlate with each other?” a correlation heatmap is the right tool.
Heatmap (correlation matrix)
Each cell is the correlation between two variables. Color encodes r in [-1, +1], usually with a diverging palette anchored at zero. Reveals clusters of correlated variables at a glance.
- Compares many pairs at once
- Loses individual point detail
- Diverging palette anchored at 0
Scatter plot
Each point is one observation; position encodes two continuous variables. Reveals shape (linear, non-linear), clusters, and outliers at the row level. Best for one or a small number of variable pairs.
- Precise per-point
- Hard to scan many variable pairs
- Use scatter matrix for several pairs
Heatmap vs sortable data table
A table gives you precise per-cell values and unlimited interaction (sort, filter, copy). A heatmap gives you the gestalt — the pattern of highs and lows across the whole matrix. Pair them: heatmap above, table beneath.
Heatmap
Color encodes value, sacrificing precision for pattern recognition. Best when the audience needs to see structure, clusters, or outliers in a large matrix at once.
- Pattern-first, not value-first
- Scales to thousands of cells
- Color is the encoding
Sortable table
Numbers in cells, no color encoding. Best when readers will read or copy individual values, sort by columns, or filter to a subset.
- Value-first, not pattern-first
- Hard to scan past ~30 rows
- Always pair with a heatmap if matrix is large
Heatmap vs calendar heatmap
A calendar heatmap is a heatmap with the rows and columns fixed to days-of-week and weeks-of-year. Use it when the time dimension is the story; use a generic heatmap when rows and columns are anything else.
Generic heatmap
Rows and columns are arbitrary categorical or ordinal dimensions. Order can be sorted or clustered to reveal blocks.
- Flexible row/column meaning
- Sorting reveals structure
- Use for matrices, correlations, day×hour grids
Calendar heatmap
Rows are days of the week (or months), columns are weeks of the year. Layout mirrors a wall calendar so seasonal patterns and individual outlier days are obvious.
- Fixed calendar layout
- Best for daily activity over months/years
- GitHub contribution graph is the canonical example
// 11 — Common mistakes
Mistakes to watch out for
Almost every broken heatmap in the wild fails the same handful of ways. If you only memorize a few rules, make them these.
Using a rainbow palette
Rainbow palettes (jet, hsv, spectral) look colorful, but they’re not perceptually uniform: equal data steps look like unequal color steps to the eye, especially around the green band. They also fail badly for the ~8% of men with red–green color-vision deficiency. Switch to Viridis, Cividis, or any ColorBrewer scheme — they were engineered specifically to avoid this.
Missing or unlabeled legend
Without a labeled color bar, readers have no way to map colors back to numbers. The legend should sit close to the plot, show the units (“sessions”, “r”, “%”), and have 5–7 visible tick marks. A heatmap without a legend is just decorative coloring.
Random row and column order
The same data with random rows looks like noise; with sorted rows it tells a story. Sort by row sum, by a meaningful key (date, magnitude), or by hierarchical clustering before plotting. This is often the single biggest improvement you can make to a heatmap.
Diverging palette on unsigned data
Diverging palettes (red–white–blue, orange–white–purple) imply a meaningful midpoint. Using one for unsigned magnitudes invents a midpoint that doesn’t exist and biases the reader toward seeing the median as a special boundary. Use sequential palettes for unsigned data and reserve diverging for signed values around zero.
Auto-scaled domains across panels
When you small-multiple several heatmaps and let each auto-scale its color domain, the same color means a different value in each panel. Fix vmin and vmax explicitly so colors are comparable across panels and across time.
Heatmap on a tiny matrix
Below about 5×5 cells, a heatmap is just a confusingly colored table. Use a labeled table, a small-multiple bar chart, or a dot plot instead. Heatmaps earn their keep when the matrix is too dense to read cell-by-cell.
Encoding everything in color, nothing in text
If color is the only encoding, anyone who can’t see it (printed in grayscale, screen-reader users, colorblind viewers, low-contrast monitors) loses the entire chart. Always pair the heatmap with the underlying data table, and label the most important cells directly when the grid is small enough.
// 12 — Accessibility
Accessibility checklist
Run through this list before publishing. The chart should still communicate its message to readers using assistive technology, color-blind users, keyboard navigation, and reduced-motion settings.
- ✓
Use a colorblind-safe, perceptually uniform palette
WCAG 1.4.1Cividis, Viridis, and ColorBrewer’s sequential and diverging schemes are perceptually uniform and have been tested against the major forms of color-vision deficiency. Avoid red–green diverging palettes (Spectral, RdYlGn) for any audience that has not been screened. - ✓
Match the palette type to the data type
WCAG 1.4.1Sequential palettes for unsigned magnitudes (counts, durations). Diverging palettes for signed values around a meaningful midpoint (correlations in [-1, +1], year-over-year change). A diverging palette on unsigned data invents a midpoint that doesn’t exist and misleads readers. - ✓
Provide a textual data table fallback
WCAG 1.1.1Place the underlying matrix in a screen-reader-friendly HTML table beneath or beside the chart, or expose it via a hidden table. Many readers cannot perceive color encoding at all and will navigate the data via the table or copy it to their own tools. - ✓
Write an aria-label that summarizes the pattern
WCAG 1.1.1Set the SVG’s role="img" and an aria-label that names the takeaway, not the chart type. “Heatmap of website traffic” is weak; “Traffic peaks Tuesday–Thursday between 10am and 2pm; weekend mornings are nearly empty” is strong. - ✓
Meet contrast for legend text and cell labels
WCAG 1.4.3Axis text, legend ticks, and cell annotations must reach 4.5:1 contrast for body text or 3:1 for large text. When cells are labeled, switch label color between dark and light depending on the cell’s luminance so labels stay readable across the whole palette. - ✓
Encode meaning programmatically
WCAG 1.3.1Don’t encode information only in color. Add gridlines, group separators, or printed values for the most important cells so the matrix structure is conveyed in the DOM (and to assistive technology) and not only as pixels. - ✓
Make the chart resizable and zoomable
WCAG 1.4.4Render the heatmap in a responsive SVG with a viewBox so it stays legible at 200% browser zoom and on small screens. Avoid baking cell pixel sizes into the markup — the cell aspect ratio should adapt to the container. - ✓
Keep cell tooltips keyboard-reachable
WCAG 2.1.1If cells expose tooltips with the precise value, every cell should be reachable with the Tab key, the tooltip should appear on focus (not only hover), and the cell should have a visible focus ring. Tooltip text must be selectable so screen readers can read it. - ✓
Respect prefers-reduced-motion
WCAG 2.3.3If cells fade or recolor on load or on filter, gate the animation behind a prefers-reduced-motion: no-preference media query so motion-sensitive readers see the final state immediately.
// 13 — Best practices
Design and craft tips
The mistakes section above tells you what to avoid. The list below is the positive version: the small set of habits that separate a good heatmap from a passable one.
Use Viridis, Cividis, or ColorBrewer
Use rainbow / jet / spectral palettes
Sort or cluster rows and columns
Use a heatmap for small data
Anchor diverging scales at zero
Hide the legend
Provide the data as a table too
Cram thousands of unlabeled cells
// 15 — Tool instructions
How to build it in your tool of choice
Heatmaps live in every modern data tool, but the path to a clean, sorted, colorblind-safe heatmap differs from one to the next. The recipes below get you to a publication-ready chart in each of the most common platforms.
Microsoft Excel
Spreadsheet — ~4 min- 01Lay out the matrix with row labels in column A, column labels in row 1, and numeric values in the inner cells.
- 02Highlight only the inner numeric cells — do not include the row or column header text.
- 03Open Home → Conditional Formatting → Color Scales and pick a 3-color scale (sequential) or a Red–White–Blue scale (diverging) anchored at zero.
- 04Open Manage Rules → Edit Rule and set Minimum to Number → 0 (or your true min) and Maximum to a fixed number so the scale stays comparable across panels.
- 05Reduce row height and column width to make cells roughly square; turn off cell borders for a cleaner grid.
- 06Cluster or sort the rows by adding a helper column with the row sum, then sorting the table by that helper before re-applying the color scale.
Tip: Excel’s default 3-color scale is green–yellow–red — swap to a colorblind-safe Red–White–Blue or White–Blue scale before sharing.
Google Sheets
Spreadsheet — ~3 min- 01Lay out the matrix in the same shape as Excel: row labels, column labels, numeric inner cells.
- 02Select the inner numeric range, then choose Format → Conditional formatting → Color scale.
- 03Pick a preset (the White→Blue preset is the closest to Viridis available natively) or set custom Min/Mid/Max colors.
- 04For a diverging palette, set Midpoint to Number → 0 and choose a neutral midpoint color so positive and negative values are visually comparable.
- 05Resize rows and columns to be roughly square so the grid reads as a chart, not a table.
- 06Add a manual legend in cells beneath the matrix using the same color scale on a small reference range.
Sheets has no built-in clustering. If row/column order matters, sort the helper column first, then re-apply the conditional format.
Python (seaborn)
Code — ~5 min- 01Install the stack with pip install pandas seaborn matplotlib if it isn’t already in your environment.
- 02Pivot your long-format data into a 2D DataFrame with df.pivot(index='row', columns='col', values='value').
- 03Choose a palette: cmap='viridis' for sequential magnitudes, cmap='RdBu_r' centered on zero for diverging signed data.
- 04Call sns.heatmap(matrix, cmap=..., annot=True, fmt='.0f', linewidths=0.5, cbar_kws={'label': 'units'}) for small matrices, or annot=False for large ones.
- 05For diverging palettes, pass center=0 and vmin=-vmax, vmax=+vmax so the midpoint is anchored on zero with a symmetric domain.
- 06Sort rows and columns by sum, by hierarchical clustering (sns.clustermap), or by a meaningful key before plotting.
- 07Add plt.title() with a takeaway sentence, rotate ylabels with plt.yticks(rotation=0), and call plt.tight_layout() before plt.savefig().
Use sns.clustermap() instead of sns.heatmap() to get hierarchical clustering on rows and columns for free — the dendrograms appear automatically on the margins.
R (ggplot2)
Code — ~5 min- 01Install ggplot2 and viridisLite with install.packages(c('ggplot2', 'viridisLite')) and load them.
- 02Reshape data into long format with three columns: row, column, value (use tidyr::pivot_longer if needed).
- 03Build the plot with ggplot(df, aes(x = col, y = row, fill = value)) + geom_tile().
- 04Add scale_fill_viridis_c(option = 'D', name = 'units') for sequential, or scale_fill_gradient2(low = '#2c7bb6', mid = '#ffffff', high = '#d7191c', midpoint = 0) for diverging.
- 05Reorder factors with reorder(row, value) (or with hclust + cutree for true clustering) so blocks emerge.
- 06Add coord_fixed() so cells are square, and theme_minimal() + theme(panel.grid = element_blank()) to drop default gridlines.
- 07Polish with labs() for the takeaway title and axis labels, then ggsave() to a PNG or SVG.
Use the heatmaply package for an interactive ggplot-flavored heatmap with built-in row/column clustering and zoom — great for exploration before exporting a static publication chart.
JavaScript (D3.js)
Code — ~10 min- 01Install D3 with npm i d3, or include the CDN script tag in your HTML.
- 02Create an SVG container with a viewBox, set role='img', and add an aria-label that summarizes the dominant pattern.
- 03Build d3.scaleBand() for the row and column axes, padded slightly so cell borders are visible.
- 04Build the color scale with d3.scaleSequential(d3.interpolateViridis) for sequential or d3.scaleDiverging(d3.interpolateRdBu) anchored at zero.
- 05Bind data with svg.selectAll('rect').data(data).join('rect') and set x, y, width, height from the band scales and fill from the color scale.
- 06Render the axes with d3.axisBottom() and d3.axisLeft(), and append a color-bar legend using d3-color-legend or by stamping a gradient <rect> with tick text.
- 07For interactivity, add focus rings, keyboard navigation across cells, and tooltips that appear on focus (not only hover) so the chart works for keyboard and screen-reader users.
If you don’t need full control, Observable Plot’s Plot.cell() mark gets you to a publication-quality D3 heatmap in about 10 lines.
Tableau
BI — ~4 min- 01Drag the row dimension to Rows and the column dimension to Columns. Drag the value measure to the Color shelf inside the Marks card.
- 02Change the Marks type to Square so the cells fill the grid (rather than the default circles).
- 03Click the Color shelf and choose Edit Colors. Pick the Viridis palette (or Blue–Teal Sequential) for sequential data, or Red–Blue Diverging anchored at zero for signed data.
- 04Tick “Use Full Color Range” and, for diverging palettes, set the Center value to 0 to keep red and blue comparable.
- 05Drag the value measure onto Label so each cell prints its value, then format the label so dark-cell text is white and light-cell text is dark.
- 06Sort rows and columns by total or by a chosen field (right-click → Sort) so blocks of similar cells emerge instead of random colors.
Tableau’s default “Automatic” color scale is Orange–Blue diverging — it’s pretty but it implies a midpoint even when your data has none. Switch to a sequential palette when in doubt.
Power BI
BI — ~4 min- 01Use the built-in Matrix visual: drag the row dimension to Rows, the column dimension to Columns, and the value to Values.
- 02In the Format pane, expand Cell elements, set Apply to values = the measure, and toggle Background color on.
- 03Click the fx button next to Background color, choose Format style = Gradient, and set Minimum, Center (for diverging), and Maximum colors using a colorblind-safe palette.
- 04For diverging palettes set Center to a fixed Number = 0 so positive and negative values are visually comparable across slicers.
- 05Reduce the column width and row height to make cells more square, and toggle off totals if they would dwarf the per-cell color scale.
- 06For richer heatmaps, install the Custom Visual “Heatmap” or “Correlation plot” from AppSource — they support clustering and dedicated legends out of the box.
Power BI’s default conditional formatting palette is red–yellow–green — the worst possible choice for colorblind users. Always swap to Cividis or a Red–White–Blue diverging scheme.
// 16 — Code examples
Working code in the most common stacks
Three runnable snippets that produce the same chart — a sequential Viridis heatmap of weekly website traffic by day and hour. Copy, paste, and replace the data with yours.
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
# Hours of the day x days of the week, with synthetic traffic counts.
days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
hours = ["6am", "8am", "10am", "12pm", "2pm", "4pm", "6pm", "8pm", "10pm"]
# rows = days, cols = hours, values = sessions
values = np.array([
[ 40, 220, 880, 940, 820, 360, 120, 90, 40],
[ 60, 360, 920, 1180, 980, 540, 200, 110, 50],
[ 50, 280, 860, 1020, 940, 420, 230, 120, 60],
[ 30, 300, 880, 1080, 920, 580, 260, 130, 70],
[ 50, 320, 720, 920, 720, 420, 320, 200, 90],
[ 30, 80, 200, 340, 220, 180, 360, 540, 280],
[ 30, 60, 180, 220, 200, 160, 280, 480, 200],
])
matrix = pd.DataFrame(values, index=days, columns=hours)
fig, ax = plt.subplots(figsize=(9, 4.5))
sns.heatmap(
matrix,
cmap="viridis", # perceptually uniform sequential palette
linewidths=0.5,
linecolor="#fff",
annot=False,
cbar_kws={"label": "Sessions"},
ax=ax,
)
ax.set_title("Weekday lunchtime drives our peak traffic", loc="left", fontsize=14)
ax.set_xlabel("Hour of day")
ax.set_ylabel("Day of week")
plt.yticks(rotation=0)
plt.tight_layout()
plt.savefig("heatmap.png", dpi=200)
plt.show()
// 17 — FAQs
Frequently asked questions
What is a heatmap?+
A heatmap is a data visualization that represents values as colors in a two-dimensional grid. Each cell of the grid corresponds to a combination of two categorical or ordinal variables (rows and columns), and the cell's color encodes the magnitude of a third, quantitative variable on a sequential or diverging color scale.
When should you use a heatmap?+
Use a heatmap when you have a dense matrix of values (dozens to thousands of cells) and want readers to spot patterns, clusters, or outliers without reading every number. It is the right tool for correlation matrices, day×hour activity grids, gene-expression matrices, and any situation where the gestalt of the matrix matters more than precise per-cell values.
When should you avoid a heatmap?+
Avoid a heatmap when readers need precise values — color is the least precise visual encoding. Skip it when the matrix is small (a 3×3 grid is just a confusing table), when you only have one dimension (use a bar chart), or when you can’t guarantee a colorblind-safe palette and a textual fallback.
What is the difference between a heatmap and a choropleth map?+
Both encode magnitude with color, but a choropleth colors geographic regions on a map (countries, states, ZIP codes) while a heatmap colors cells in an abstract row×column grid. A choropleth lives in geographic space; a heatmap lives in tabular space. Both share the same accessibility and palette rules.
What is the difference between a heatmap and a scatter plot?+
A scatter plot encodes two continuous variables with point position to reveal correlation; a heatmap encodes a third value (often a count or a correlation coefficient) with color across a grid of two categorical or binned variables. Scatter plots are precise per-point; heatmaps are precise about pattern.
Sequential or diverging color scale — which should a heatmap use?+
Use a sequential palette (light→dark in a single hue, e.g. Viridis or Blues) when values run from a meaningful low to a meaningful high. Use a diverging palette (two hues meeting at a neutral midpoint, e.g. RdBu) when values are signed around a meaningful zero, such as correlation coefficients (-1 to +1) or percent change.
Is a rainbow palette OK for a heatmap?+
No. Rainbow palettes (jet, hsv, spectral) are not perceptually uniform — the eye sees green steps as smaller than yellow steps, distorting your data. They also fail badly for colorblind readers. Use Viridis, Magma, Inferno, Cividis, or any ColorBrewer sequential or diverging scheme.
How do I make a heatmap accessible?+
Pair a colorblind-safe palette (Viridis, Cividis, ColorBrewer) with a textual data table, label cell values directly when there are fewer than ~50 cells, ensure the legend has 4.5:1 contrast for axis text, and provide an aria-label that summarizes the dominant pattern instead of just naming the chart.
Should I cluster the rows and columns?+
Often, yes. Re-ordering rows and columns by hierarchical clustering, by row sum, or by a meaningful sort key turns a noisy matrix into block patterns the eye can read. The same data with random row order looks chaotic; with sorted rows it tells a story.
What is a calendar heatmap?+
A calendar heatmap is a heatmap whose rows and columns are days of the week and weeks of the year. GitHub’s contribution graph is the canonical example. The grid layout makes seasonal patterns and individual outlier days obvious in a way a line chart of daily totals cannot.
How big can a heatmap be?+
A well-designed heatmap can hold thousands of cells if the cells stay at least 4–6 pixels wide and the palette is perceptually uniform. Past about 10,000 cells you start to fight Moiré patterns and rendering performance — consider downsampling, hierarchical clustering, or a focus-plus-context view instead.
What category of chart is a heatmap?+
A heatmap belongs to the Magnitude family of charts (and overlaps with the Correlation family when used for correlation matrices). Charts in those families are designed to answer the same kind of question, so they often work as alternatives when one doesn’t quite fit your data.
What is the best library for building heatmaps in code?+
For static publication-quality heatmaps, seaborn’s sns.heatmap (Python) and ggplot2’s geom_tile (R) are the standard choices. For interactive web heatmaps, D3.js gives the most control while Observable Plot (Plot.cell), Plotly, ECharts, and Recharts are higher-level alternatives.
// 18 — References
References and further reading
Primary sources, reference texts, and the official documentation for the libraries and tools referenced throughout this guide.
- Wikipedia — Heat mapReferenceEncyclopedia entry covering the history, variants, and visual encoding of heatmaps. A solid neutral starting point with citations to primary sources.https://en.wikipedia.org/wiki/Heat_map
- Loua’s 1873 statistical atlas, where shaded matrices to encode social data first appeared. Hosted by the Bibliothèque nationale de France.https://gallica.bnf.fr/ark:/12148/bpt6k844290
- Cartographer Cynthia Brewer’s online tool for choosing colorblind-safe sequential, diverging, and qualitative palettes. The de facto standard for choropleth and heatmap palettes.https://colorbrewer2.org/
- Stefan van der Walt & Nathaniel Smith — A Better Default Colormap for Matplotlib (Viridis, SciPy 2015)Primary sourceThe talk and write-up that introduced the Viridis family of perceptually uniform colormaps and explained why jet is so harmful for data visualization.https://bids.github.io/colormap/
- Hands-on tutorial with real published examples. Especially useful for sequential vs diverging palette choice and for handling missing cells.https://academy.datawrapper.de/article/197-how-to-create-a-heatmap-in-datawrapper
- Tufte’s foundational text on data graphics. The chapters on data-ink ratio and small multiples explain why a sorted heatmap of small multiples often beats a single dense matrix.https://www.edwardtufte.com/book/the-visual-display-of-quantitative-information/
- Financial Times — Visual VocabularyReferenceOpen-source poster categorizing chart types by intent. Heatmaps appear in both the Magnitude and Correlation families.https://github.com/Financial-Times/chart-doctor/tree/main/visual-vocabulary
- WAI — Complex Images: Charts and GraphsAccessibilityWeb Accessibility Initiative guidance on making charts accessible: text alternatives, long descriptions, and data tables. Use this when building the accessibility checklist for your heatmap.https://www.w3.org/WAI/tutorials/images/complex/
- Official API reference for seaborn’s heatmap helper used in this guide’s Python sample. Includes guidance on cmap, center, vmin/vmax and annotations.https://seaborn.pydata.org/generated/seaborn.heatmap.html
- Tidyverse documentation for the ggplot2 tile geometry. The standard way to draw a heatmap in R.https://ggplot2.tidyverse.org/reference/geom_tile.html
- Maintained Observable notebook from the D3 community that mirrors the JavaScript code sample in this guide.https://observablehq.com/@d3/heatmap
- Mike Bostock — Calendar ViewTutorialThe classic D3 calendar heatmap notebook. Useful template for adapting heatmaps to time-series data with year/month/week structure.https://observablehq.com/@d3/calendar-view