Climate warming stripes (also known as "warming stripes") display temperature anomaly data as a sequence of vertical colored bars, one per year, using a blue-to-red diverging colormap. Created by climate scientist Ed Hawkins, this minimalist visualization strips away axes, labels, and gridlines to communicate long-term warming trends through pure color encoding. The progression from cool blues to warm reds makes temperature change immediately visible at a glance.

// anyplot.ai
// heatmap-stripes-climate: Climate Warming Stripes
// Library: muix 7.29.1 | JavaScript 22.23.2
// Quality: 87/100 | Created: 2026-08-25
//# anyplot-orientation: landscape
// anyplot.ai
// heatmap-stripes-climate: Climate Warming Stripes
// Library: MUI X Charts | React | Node 22
// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.
// Quality: pending | Created: 2026-08-25
import { BarChart } from "@mui/x-charts/BarChart";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
const tokens = window.ANYPLOT_TOKENS;
// --- Data (in-memory, deterministic) ----------------------------------------
// Synthetic global-mean temperature anomaly (°C, vs. a 1961-1990-style
// baseline) for 1900-2024 — a tiny LCG stands in for a seeded RNG since the
// browser has no Math.random() seed.
function lcg(seed) {
let state = seed;
return () => {
state = (state * 1103515245 + 12345) & 0x7fffffff;
return state / 0x7fffffff;
};
}
const random = lcg(42);
const startYear = 1900;
const endYear = 2024;
const years = [];
const anomalies = [];
for (let year = startYear; year <= endYear; year += 1) {
const progress = (year - startYear) / (endYear - startYear);
const trend = -0.25 + 1.35 * progress ** 2.2; // slow start, accelerating post-1975 warming
const noise = (random() - 0.5) * 0.28;
years.push(year);
anomalies.push(Math.round((trend + noise) * 100) / 100);
}
const maxAbsAnomaly = Math.max(...anomalies.map(Math.abs));
// --- Diverging color scale (Imprint imprint_div: cold blue -> midpoint -> warm red) ---
// Bar height stays constant (see `heights` below) — only color encodes the
// anomaly, the defining trait of the warming-stripes form.
function hexToRgb(hex) {
const clean = hex.replace("#", "");
return [parseInt(clean.slice(0, 2), 16), parseInt(clean.slice(2, 4), 16), parseInt(clean.slice(4, 6), 16)];
}
function mixColor(from, to, fraction) {
const [r1, g1, b1] = hexToRgb(from);
const [r2, g2, b2] = hexToRgb(to);
const r = Math.round(r1 + (r2 - r1) * fraction);
const g = Math.round(g1 + (g2 - g1) * fraction);
const b = Math.round(b1 + (b2 - b1) * fraction);
return `rgb(${r}, ${g}, ${b})`;
}
const COLD = tokens.div[2]; // Imprint imprint_div — blue, cold anomalies
const MID = tokens.div[1]; // Imprint imprint_div — theme-adaptive midpoint (zero anomaly)
const HOT = tokens.div[0]; // Imprint imprint_div — matte red, warm anomalies
// The diverging midpoint token is the theme-adaptive page background, so a
// fraction that reaches exactly 0 mixes to a color pixel-identical to the
// backdrop and the bar disappears. Floor the magnitude so every bar keeps a
// faint but real, non-zero-contrast tint — the fixed red/blue endpoints are
// untouched since |fraction| is already >= this floor near them.
const MIN_FRACTION = 0.15;
function anomalyToColor(value) {
const rawFraction = value / maxAbsAnomaly; // symmetric around zero, per spec
const sign = rawFraction < 0 ? -1 : 1;
const fraction = sign * Math.max(MIN_FRACTION, Math.abs(rawFraction));
return fraction < 0 ? mixColor(MID, COLD, -fraction) : mixColor(MID, HOT, fraction);
}
const stripeColors = anomalies.map(anomalyToColor);
// Uniform bar height — the stripe form encodes the anomaly purely through
// color, never through height, so every bar spans the full drawing area.
const heights = years.map(() => 1);
// --- Chart (default-exported component — the harness mounts it) -------------
export default function Chart() {
const stripeWidth = Math.round(window.ANYPLOT_SIZE.width * 0.94);
const stripeHeight = Math.round(stripeWidth / 3.1); // ~3:1, per spec's "wide and short"
return (
<Box
sx={{
width: window.ANYPLOT_SIZE.width,
height: window.ANYPLOT_SIZE.height,
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
>
<Typography sx={{ fontSize: 22, fontWeight: 500, mb: 3 }}>
heatmap-stripes-climate · javascript · muix · anyplot.ai
</Typography>
<BarChart
width={stripeWidth}
height={stripeHeight}
series={[
{
data: heights,
color: tokens.palette[0],
valueFormatter: (_value, context) =>
`${years[context.dataIndex]}: ${anomalies[context.dataIndex].toFixed(2)}°C`,
},
]}
xAxis={[
{
scaleType: "band",
data: years,
categoryGapRatio: 0,
colorMap: { type: "ordinal", values: years, colors: stripeColors },
},
]}
yAxis={[{ scaleType: "linear", min: 0, max: 1, domainLimit: "strict" }]}
bottomAxis={null}
leftAxis={null}
margin={{ top: 0, right: 0, bottom: 0, left: 0 }}
borderRadius={0}
skipAnimation
slotProps={{ legend: { hidden: true } }}
/>
</Box>
);
}
Part of Climate Warming Stripes on anyplot.ai.