/ concept-collection / dulcimer
Sign in
concept-collection / dulcimer
dulcimer / src / render / colorbar.ts
47 lines · 1.7 KBBlameHistoryRaw
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.
8 * Adapted from turing-surface's src/render/colorbar.ts. */
9export class Colorbar {
10 #canvas: HTMLCanvasElement;
11 #minLabel: HTMLElement;
12 #maxLabel: HTMLElement;
14 constructor(container: HTMLElement) {
15 container.classList.add('colorbar');
16 this.#maxLabel = document.createElement('div');
17 this.#maxLabel.className = 'colorbar-label';
18 this.#canvas = document.createElement('canvas');
19 this.#canvas.width = 12;
20 this.#canvas.height = 160;
21 this.#minLabel = document.createElement('div');
22 this.#minLabel.className = 'colorbar-label';
23 container.append(this.#maxLabel, this.#canvas, this.#minLabel);
24 }
26 /** Repaint the gradient. Only when the colormap changes: it is 160 filled
27 * rows, and the frame loop has better things to do. */
28 setColormap(cmap: ColormapFunc): void {
29 const ctx = this.#canvas.getContext('2d');
30 if (!ctx) return;
31 const h = this.#canvas.height;
32 for (let y = 0; y < h; y++) {
33 const t = 1 - y / (h - 1);
34 const [r, g, b] = cmap(t);
35 ctx.fillStyle = `rgb(${r},${g},${b})`;
36 ctx.fillRect(0, y, this.#canvas.width, 1);
37 }
38 }
40 /** The end labels, which do change as the scale follows the field. */
41 setRange(vmin: number, vmax: number): void {
42 const lo = fmtValue(vmin);
43 const hi = fmtValue(vmax);
44 if (this.#minLabel.textContent !== lo) this.#minLabel.textContent = lo;
45 if (this.#maxLabel.textContent !== hi) this.#maxLabel.textContent = hi;
46 }
moveopenescclose