1import type { ColormapFunc } from './colormaps.ts';
3/** Compact numeric label: 3 significant digits, trailing zeros trimmed. */
4export const fmtValue = (v: number): string =>
5 Number.isFinite(v) ? v.toPrecision(3).replace(/\.?0+$/, '') : '—';
7/** Vertical colorbar drawn on a small canvas, with min/max labels. */
8export class Colorbar {
9 #canvas: HTMLCanvasElement;
10 #minLabel: HTMLElement;
11 #maxLabel: HTMLElement;
13 constructor(container: HTMLElement) {
14 container.classList.add('colorbar');
15 this.#maxLabel = document.createElement('div');
16 this.#maxLabel.className = 'colorbar-label';
17 this.#canvas = document.createElement('canvas');
18 this.#canvas.width = 12;
19 this.#canvas.height = 160;
20 this.#minLabel = document.createElement('div');
21 this.#minLabel.className = 'colorbar-label';
22 container.append(this.#maxLabel, this.#canvas, this.#minLabel);
23 }
25 update(cmap: ColormapFunc, vmin: number, vmax: number): void {
26 const ctx = this.#canvas.getContext('2d');
27 if (!ctx) return;
28 const h = this.#canvas.height;
29 for (let y = 0; y < h; y++) {
30 const t = 1 - y / (h - 1);
31 const [r, g, b] = cmap(t);
32 ctx.fillStyle = `rgb(${r},${g},${b})`;
33 ctx.fillRect(0, y, this.#canvas.width, 1);
34 }
35 this.#maxLabel.textContent = fmtValue(vmax);
36 this.#minLabel.textContent = fmtValue(vmin);
37 }
38}