Heatmap
Overview
A heatmap draws a grid of values, one z per cell, as colored cells. Use it when the data is already a matrix: a 2D field sampled on a grid, a spectrogram, a correlation matrix, activity per weekday and hour. Color shows the overall shape; hover or cell labels give the exact values.
Holochart draws the whole grid as one textured quad: the values go into a single float texture, and the fragment shader colors each pixel through a colorscale lookup. Zooming, panning and most style changes only update uniforms, so grids of millions of cells stay interactive.
Pick a different chart when:
- you have samples, not values per cell: use a 2D histogram, which bins them first;
- you want level lines or bands rather than cells: use a contour plot, or a 2D density contour for samples;
- the values are colors (RGB pixels or a picture): use an image;
- you start from a matrix and want sensible defaults (rows down from the top, square cells):
hx.imshowbuilds the heatmap for you.
Minimal example
import { createChart } from '@mk7s/holochart';
createChart(document.getElementById('chart')!, {
data: [
{
type: 'heatmap',
z: [
[1, 20, 30],
[20, 1, 60],
[30, 60, 1],
],
},
],
});z is a list of rows: z[row][column], with row 0 at the bottom of the y axis. Without x and y, columns and rows sit at 0, 1, 2, …. A colorbar on the right maps values to colors. The live example draws a 60 × 40 field on numeric cell centers:
heatmap/basicData format
zas a 2D array (z[row][column], rows along y). Rows can be typed arrays (Float32Array, …) and can differ in length: the grid is as wide as the longest row. Anything that is not a finite number or a numeric string (null,NaN,'') is a gap and draws nothing.transpose: truereadszas columns instead (z[column][row]).zas a 1D array withxandycolumns (tidy data): one value per point, in any order. The points are placed on the grid of the sorted distinctxandyvalues; cells without a point are gaps. Only the firstmin(x.length, y.length, z.length)points are used.- Coordinates. With one
xvalue per column,xholds the cell centers and the edges lie halfway between them (the outer edges extrapolated). With one more value than columns,xholds the cell edges, so cells can have different widths. Withoutx, cells aredxwide and the first one is centered onx0(defaults 0 and 1).y,y0anddywork the same for rows.xtype: 'scaled'ignores a givenxand usesx0/dx;'array'is the default whenxis given. - Axis types. Numbers, dates and categories all work, per axis:
- Date axes:
xcan be date strings, orx0a date anddxa step in milliseconds. - Log axes: centers are given as plain values, and the edges between them are geometric means (as in Plotly), so evenly spaced centers on the log scale give evenly tall cells.
- Category axes: the grid spans the axis' categories, one cell each, and each column is matched to its category by name. Traces that share an axis, or a
categoryarraythat reorders it, keep their cells under the right labels.
- Date axes:
- A trace without a numeric cell (2D
z), or withoutxandycolumns (1Dz), is hidden. - Grids of more than 4096 × 4096 cells are not drawn (a warning is logged).
import { createChart } from '@mk7s/holochart';
// Column data: one row per measurement, in any order.
createChart(document.getElementById('chart')!, {
data: [
{
type: 'heatmap',
x: ['Mon', 'Mon', 'Tue', 'Tue', 'Wed'],
y: [9, 10, 9, 10, 9],
z: [12, 30, 18, 25, 22],
},
],
});import { createChart } from '@mk7s/holochart';
// Daily cells from x0 / dx: one column per day, one row per sensor.
createChart(document.getElementById('chart')!, {
data: [
{
type: 'heatmap',
x0: '2026-03-01',
dx: 86_400_000,
y: ['north', 'south'],
z: [
[3, 5, 4, 7],
[6, 2, 5, 8],
],
},
],
layout: { xaxis: { type: 'date' } },
});Gaps
connectgaps: truefills gaps from their neighbors (Plotly's iterative neighbor average), for a continuous field. It defaults totruefor 1Dzwithzsmooth, elsefalse.hoverongaps(defaulttrue) shows a hover label on gaps, with an emptyz.
Variations
Annotated heatmap
texttemplate writes a label in every cell, with %{z}, %{x}, %{y} and %{text} and optional d3 formats (%{z:.2f}). With the default textfont.size: 'auto', labels take the largest size at which they fit the cells, and without textfont.color they are black or white, whichever contrasts with the cell. Here a correlation matrix on category axes uses a diverging scale centered on 0 (zmid) and 1 px gaps (xgap, ygap):
heatmap/annotatedUneven cells and filled gaps
Give x and y one more value than columns and rows, and they are the cell edges: cells can have any width and height. The grid is still one texture; the shader finds the cell under each pixel with a binary search of an edge texture. connectgaps fills the missing values:
heatmap/unevenSmoothing
zsmooth: 'best' interpolates bilinearly between cell centers in data space, which stays exact for uneven cells. 'fast' interpolates by cell index; like Plotly, it is turned off on log axes and uneven grids. Both run in the fragment shader. The two panels share one coloraxis:
heatmap/smoothDate and log axes
Daily columns from x0 (a date) and dx (one day in ms), against frequency bands whose centers grow geometrically on a log y axis. On a log axis the cell edges are geometric means of the centers, so the bands are evenly tall:
heatmap/datesMonthly columns with period alignment
Values that stand for a whole period are often stamped with its first day. xperiod gives the period length (ms, or 'M<n>' months on date axes) and xperiodalignment where to draw each column in it ('start', 'middle' or 'end'); xperiod0 sets where periods start. Here each month's column is centered in its month, under its ticklabelmode: 'period' label. Hover shows the dates as given. yperiod does the same for rows.
heatmap/periodRange breaks
On an axis with rangebreaks, columns whose date falls in a break are dropped, as in Plotly, and the others share the remaining axis: every trading day keeps a full-width column and Friday meets Monday. Here weekends and two holidays are hidden from daily data:
heatmap/rangebreaksColumn data on a category axis
A 1D z with x and y columns, listed in any order, is placed on the grid of their distinct values. The weekdays sit on a category axis ordered by categoryarray:
heatmap/columnsStyling
- Colorscale.
colorscale(a name, a list of colors, or[position, color]stops), with thez-lettered range attributes:zauto,zmin,zmax,zmid(a symmetric automatic domain for diverging data) andreversescale. See colors and colorscales. - Default look. In the default
holocharttemplate, heatmaps use the neon plasma sequential scale. Plotly's own default isRdBu, which you get withlayout.template: 'plotly-classic'(themes and templates). - Colorbar.
showscale(defaulttrue) andcolorbar(title,tickformat,len, …). To share one scale and one colorbar across traces, setcoloraxis: 'coloraxis'on each and stylelayout.coloraxis. - Cells.
xgap/ygap(px of background between cells, only withoutzsmooth),zsmooth(false,'fast','best') and traceopacity. - Labels.
texttemplateandtextfont(family,sizeor'auto',color,weight,style); the font defaults tolayout.font. Grids of more than 65,536 cells (256 × 256) get no labels. - Legend.
showlegenddefaults tofalse: the colorbar describes the trace. - Order. Heatmaps draw below contours, bars and scatter traces in the same subplot;
zorderchanges that.
import { createChart } from '@mk7s/holochart';
createChart(document.getElementById('chart')!, {
data: [
{
type: 'heatmap',
z: [
[-0.8, 0.1, 0.6],
[0.3, -0.2, 0.9],
],
colorscale: 'RdBu',
zmid: 0,
xgap: 2,
ygap: 2,
texttemplate: '%{z:+.1f}',
colorbar: { title: { text: 'Δ' } },
},
],
});Interactivity
Hover. The cell under the pointer shows its center and value:
x: 2,y: 0.5,z: 37.textorhovertext(2D likez, or one per point with column data) adds a line.xhoverformat,yhoverformatandzhoverformatformat the values, andhovertemplatetakes%{x},%{y},%{z},%{text}and%{customdata}(2D likez):tsimport { createChart } from '@mk7s/holochart'; createChart(document.getElementById('chart')!, { data: [ { type: 'heatmap', x: ['Mon', 'Tue', 'Wed'], y: ['9:00', '10:00'], z: [ [12, 18, 22], [30, 25, 27], ], hovertemplate: '%{x} %{y}<br>%{z} visits<extra></extra>', }, ], });Markers and lines drawn over the grid win the
closesthover, as in Plotly.Events.
hoverandclickpoints carryx,y,zandpointNumber: the cell as[row, column]ofzfor a 2Dz(as in Plotly; when a category axis reorders the grid, still the position in yourz, and per-celltext,hovertextandcustomdatafollow it), or the index of the point placed in the cell for column data:tschart.on('click', (e) => console.log(e.points[0]?.pointNumber, e.points[0]?.z));Zoom and pan. Drag to zoom, double-click to reset. The autorange spans the grid edge to edge, without padding. Cell labels move with the cells and are resized when the cells' size in pixels changes.
Selection. Box and lasso selection don't select cells, as in Plotly.
3D-native options
Holochart extensions (full bundle), see Extrusion & 2.5D:
depth: stands every cell up as a column as tall as its value — the height in px of a column atzmax(or a percentage of the mean cell width,'300%'). Heights grow linearly from 0 (fromzminwhen the color range has negative values); values outside the color range are clamped.0(default) draws a flat heatmap.- Columns take the cell's color,
xgap/ygapspace them, and the flat heatmap stays under them as their floor (cells at the bottom of the range keep their color). bevelandmaterial: rounded edges (up to 10,000 cells) and the shading, as for bars.layout.view3d: the grid in perspective, like a 3D histogram; hover reports the column under the pointer, top or side.
heatmap/columns-cityAll columns are one merged mesh (one draw call). A 200 × 200 grid — 40,000 columns — builds in about 0.2 s, again on each zoom step (pans only move it); grids of more than 100,000 cells stay flat, with a console warning.
heatmap/columns-terrainPerformance notes
- The grid is one textured quad per trace: one float texture holds every value (with a validity channel for gaps), and a small lookup texture holds the colorscale. Uneven cells need no extra geometry: the shader binary-searches an edge texture.
- Zoom and pan only set uniforms. Restyling
colorscaleswaps the lookup texture, andzmin,zmax,zmid,reversescale,zsmooth, gaps and opacity only set uniforms: none of them re-uploads the values. Only newzor coordinates do. - Up to 4096 × 4096 cells (16.7 million) per trace. Pass rows as
Float32Arrayto keep the input compact. Theheatmap/largeexample in the sandbox draws a 4096 × 4096 grid and measures the first draw and pan and zoom frame rates. connectgapsruns on the CPU in the calc step and scales with the number of gaps.- Cell labels are text, typeset asynchronously; keep
texttemplatefor coarse grids. See the performance guide.
Accessibility notes
- Screen readers: the chart is a
<canvas>; the hidden description (see the accessibility guide) gives the grid size, the value range and the cell with the highest value, and its data table lists the cells that have a value (x, y, z). - Keyboard: Tab moves into the plot area; the arrow keys then move a cell cursor along the row (← / →) and the column (↑ / ↓), showing each cell's hover label. See the keys by chart family. The script-tag build leaves these stops out for now.
- Color: use a sequential scale that is monotonic in lightness (the default, Viridis, Cividis) so that "more" reads as "brighter", and a diverging scale with
zmidonly when the data has a meaningful center. Keep the colorbar visible, and for small grids put the values in the cells withtexttemplate.
Attribute reference
See the heatmap attribute reference for every attribute, its type, and its default. Shared color axes are under coloraxis in the layout reference.
Related charts
- 2D histogram: bins samples into a grid first, drawn with the same GPU primitive
- Contour: level lines and bands of values on a grid
- 2D density contour: contour levels of binned samples
- Image: pixels given as colors (RGB, HSL) or a picture
- Express
imshow: a matrix as a heatmap in one call, with facets and animation over extra dimensions
express/imshowPlotly migration notes
- Attribute names and defaults match Plotly's
heatmap:z,x,y,x0/dx,y0/dy,xtype/ytype,transpose,text,hovertext,connectgaps,hoverongaps,xgap/ygap,zsmooth, the colorscale attributes,texttemplate,textfont, the hover formats andhovertemplate. Cell edges (including geometric means on log axes), column data and category placement follow Plotly's rules, so Plotly figures carry over. - The default colorscale comes from the template: the neon plasma scale in the default look,
RdBuwithtemplate: 'plotly-classic'. - Smoothing is bilinear on the GPU in both modes.
'fast'interpolates between cell centers by index (Plotly stretches an image of the cells, so edges look slightly different) and, as in Plotly, falls back to flat cells on log axes and uneven grids.'best'interpolates in data space. Next to a gap, only the neighbors with a value take part (Plotly extrapolates the missing ones). xperiod/yperiodalign the givenx/y(and column data) as in Plotly, and on date and linear axes (Plotly: date axes only), like Holochart's bar and scatter. Hover shows the values as given; with column data it shows the aligned values, as in Plotly.- Range breaks: columns and rows centered in a break are dropped, as in Plotly. The cell edges are halfway between the centers on the compressed axis, so the cells beside a break keep their width (Plotly halves in real time, which narrows or even collapses them).
x0/dxstep in real time and hide the columns that land in a break (Plotly draws nothing when an axis has breaks andxoryis not an array); given cell edges are clamped to the breaks. - Not supported yet:
xcalendar/ycalendar. Grids larger than 4096 × 4096 cells are not drawn, and grids larger than 65,536 cells get notexttemplatelabels.