Matrices, contours and colour scales

Represent a field, define its value-to-colour mapping, and share a scale across panels.

4 min readPlotting

Make a heatmap

import matplotlib.pyplot as plt
import fluxplot as fp

fig, ax = plt.subplots(layout="constrained")
image = fp.heatmap(ax, [[1, .2], [.2, 1]], series="correlation",
                   cmap="viridis", vmin=0, vmax=1, cells=True)
fp.colorbar(image, name="correlation", label="Correlation")
fp.save(fig, "plots/correlation.svg")

For a contour field, supply your coordinate arrays x, y and measured or calculated field z. The helpers use Matplotlib’s contour options:

# x, y and z come from your analysis.
fig, ax = plt.subplots()
bands = fp.contourf(ax, x, y, z, series="energy", levels=[0, 1, 3, 5])
fp.colorbar(bands, name="energy", label="Energy")

Saved colour-scale metadata

How Flux retains values, normalization and colour keys

Every colour-mapped mark — a heatmap, a hexmatrix, a filled contour, a fp.scatter(..., c=values), even a raw ax.imshow or ax.pcolormesh you never tagged — records its complete value → colour law in the manifest’s colorScales[]:

{"id": "rates",           // the same key as the recipe's colour control
 "kind": "continuous",     // continuous | binned (a BoundaryNorm)
 "colormap": {"name": "viridis", "source": "matplotlib", "N": 256,
              "lut": ["#440154ff", …], "under": "#440154ff", "over": "#fde725ff",
              "bad": "#00000000", "discrete": false},
 "norm": {"kind": "log", "vmin": 1.0, "vmax": 53.0, "clip": false, "base": 10,
          "extend": "neither"},
 "mappables": ["rates.hexes"], "colorbars": ["colorbar.color"],
 "label": "Synapses per hexbin",
 "recolor": "live",        // live | raster | regenerate (an imshow is a raster)
 "editable": {"cmap": true, "limits": true,
              "normKinds": ["linear", "log", "power", "symlog"], "center": false}}

lut is the full lookup table matplotlib indexes, so a consumer reproduces its colours exactly: lut[trunc(x · N)] for the normalised x, under / over beyond the limits, bad for a missing value (fluxplot.colorscale.apply is the reference implementation, tested hex for hex against matplotlib for every norm kind; tests/fixtures/colorscale_vectors.json carries the vectors Flux checks too). In the SVG every coloured element carries the value it was coloured by — data-value on each cell, hexagon, band (with data-level-low / -high), contour line and point, data-missing="1" for a gap — and the group carries data-color-scale and data-paint (fill, stroke or fill stroke), so Flux can recolour or re-range a plot live, without Python. series[].field and series[].color.scale point at the scale; fp.heatmap(..., value_raster=True) additionally writes the matrix as <plot>.<key>.values.json beside an image layer.

Colour keys are exact vectors: the 256 solid quads become one <rect> filled by a hard-stepped <linearGradient> (no rasterization, no warning), and the colorbar guide records anchors (vmin / vcenter / vmax ↔︎ SVG position), axisLength, orientation, tickLocator, tickFormatter, extend and the extend triangles (colorbar.color.extend-min / -max).

fp.tag_seaborn(ax, plot="heatmap") (and the bivariate histplot / kdeplot) turn a seaborn mesh or contour set into the same kind of field, cells named and colour key linked.

Shared scales

Declare the scale once and let every panel join it:

# C1 and C2 are correlation matrices from your analysis.
fig, (ax1, ax2) = plt.subplots(1, 2, layout="constrained")
fp.color_scale("corr", cmap="RdBu_r", center=0)
fp.heatmap(ax1, C1, series="ctrl", scale="corr")
fp.heatmap(ax2, C2, series="drug", scale="corr")
# One key, borrowing space from both panels:
fp.colorbar(scale="corr", ax=[ax1, ax2], label="r")

fp.color_scale also accepts norm= ("linear", "log", "sqrt" or "symlog"), vmin, vmax and robust. Besides heatmaps, contour, contourf, scatter(c=…) and hexmatrix can join a scale with scale=.

The members share the map and the norm, and their limits default to the union of every member’s finite values (symmetric about center when one is given; robust=True uses the 2nd–98th percentiles), resolved at fp.save before layout so the figure never lays out against stale limits. The manifest carries one colorScales entry (id: "corr") listing every member and the key, the recipe carries one control (__fluxplot__.corr), and an edit to it — a new map, a pinned vmax — repaints every panel at once.

Rasterization and vector cells

Heavy layers are rasterized when you save (see fp.save). fp.save(..., force_vectors=True) keeps them vector, overriding (and afterwards restoring) any rasterization flags your code set, including axes z-order rasterization. It cannot vectorize a source image created with imshow; use a modest cells=True heatmap when you need individual vector cells.

For surface maps, lighting survives SVG rendering and colorbars use a separate scalar mappable. Categories are drawn with per-part painter ordering, not a full cross-category depth buffer: convex projections are supported, while arbitrary folded meshes can show occlusion differences between parts. The surface metadata records this limitation.

Colour and confidence

A colour-mapped mark can also carry an opacity channel (alpha_by= on fp.hexmatrix, fp.heatmap and fp.scatter), so sparsely sampled regions read lighter than well-sampled ones. The colour guide describes the channel, its range and how Flux recomputes it live.

Search the docs

Search guides, workspaces, and reference topics.

↑ ↓ to move · Enter to open · Esc to close