06fdafa925
Keyboard control for the served chart, deliberately the discrete counterpart to the
glitchy hand-rolled pointer drag that was just removed: each key is one clean
setScale("x", …), deterministic, with none of uPlot's pointer-pan jank.
- arrows pan (15% of the visible span), +/- zoom, 0/Home reset, x toggles the spine.
- zoom anchors on the CURSOR: the x-value under the mouse keeps its relative position,
so the point under the pointer stays put while zooming (centre when the mouse is off
the chart). The mouse position is tracked per chart via posToVal on mousemove.
- keys drive every chart instance together, so panels stay x-aligned.
- uPlot's default mouse (drag = box-zoom, double-click = reset) is retained — you have
both.
The load-bearing math is the pure keyXRange(key, lo, hi, anchor, ext) helper (the rest
is browser-only DOM wiring in mount); it is node-guarded by chart_viewer_keys.mjs, which
pins the zoom-toward-cursor invariant (anchor keeps its relative position) with an
off-centre anchor so a centre-zoom regression fails loudly. All five chart guards green.
refs #103
55 lines
2.9 KiB
JavaScript
55 lines
2.9 KiB
JavaScript
// Headless guard for the trace-chart viewer's keyboard-control math (keyXRange).
|
|
//
|
|
// keyXRange is PURE: given a key + the current x-range [lo,hi] + the zoom anchor (the
|
|
// x-value to keep fixed, i.e. the cursor position) + the data extent [first,last], it
|
|
// returns the next {min,max} — or null for a non-chart key. The load-bearing property is
|
|
// "zoom toward the cursor": the anchor keeps its RELATIVE position in the new range, so
|
|
// the point under the mouse stays put. A regression that zoomed around the centre instead
|
|
// would still shrink the range but slide the cursor's point — caught here by an anchor
|
|
// off-centre (anchor=2 in a [0,10] range). Arrows pan by 15% of the span; 0/Home reset to
|
|
// the extent. Drives the pure export; no DOM/uPlot.
|
|
import { createRequire } from "node:module";
|
|
import { fileURLToPath } from "node:url";
|
|
import { dirname, join } from "node:path";
|
|
|
|
const require = createRequire(import.meta.url);
|
|
const here = dirname(fileURLToPath(import.meta.url));
|
|
|
|
const fail = (msg) => { console.error("FAIL: " + msg); process.exit(1); };
|
|
const close = (a, b) => Math.abs(a - b) < 1e-9;
|
|
const eqRange = (r, min, max, msg) => {
|
|
if (!r || !close(r.min, min) || !close(r.max, max)) fail(`${msg} (got ${JSON.stringify(r)}, want {min:${min},max:${max}})`);
|
|
};
|
|
|
|
// No document / no AURA_TRACES -> the IIFE's browser mount is skipped; we get the export.
|
|
global.window = {};
|
|
const { keyXRange } = require(join(here, "..", "assets", "chart-viewer.js"));
|
|
|
|
// lo=0, hi=10 (span 10), anchor=2 (off-centre, left), full extent [0, 99].
|
|
const lo = 0, hi = 10, anchor = 2, ext = [0, 99];
|
|
|
|
// pan: 15% of span = 1.5, span preserved.
|
|
eqRange(keyXRange("ArrowLeft", lo, hi, anchor, ext), -1.5, 8.5, "ArrowLeft pans left by 15% of the span");
|
|
eqRange(keyXRange("ArrowRight", lo, hi, anchor, ext), 1.5, 11.5, "ArrowRight pans right by 15% of the span");
|
|
|
|
// zoom in (factor 0.8) toward anchor=2 -> newLo=0.4, newHi=8.4 (span 8).
|
|
const zin = keyXRange("+", lo, hi, anchor, ext);
|
|
eqRange(zin, 0.4, 8.4, "+ zooms in toward the anchor");
|
|
if (!close((anchor - zin.min) / (zin.max - zin.min), (anchor - lo) / (hi - lo)))
|
|
fail("+ must keep the anchor at the same relative position (zoom toward cursor, not centre)");
|
|
eqRange(keyXRange("=", lo, hi, anchor, ext), 0.4, 8.4, "= aliases + (zoom in)");
|
|
|
|
// zoom out (factor 1.25) toward anchor=2 -> newLo=-0.5, newHi=12.
|
|
eqRange(keyXRange("-", lo, hi, anchor, ext), -0.5, 12, "- zooms out toward the anchor");
|
|
eqRange(keyXRange("_", lo, hi, anchor, ext), -0.5, 12, "_ aliases - (zoom out)");
|
|
|
|
// reset to the full data extent.
|
|
eqRange(keyXRange("0", lo, hi, anchor, ext), 0, 99, "0 resets to the data extent");
|
|
eqRange(keyXRange("Home", lo, hi, anchor, ext), 0, 99, "Home resets to the data extent");
|
|
|
|
// a non-chart key is ignored.
|
|
if (keyXRange("q", lo, hi, anchor, ext) !== null) fail("a non-chart key must return null");
|
|
|
|
console.log("OK — keyboard pan / zoom-toward-cursor / reset math; non-chart keys ignored.");
|
|
process.exit(0);
|