Files
Aura/crates/aura-cli/tests/chart_viewer_xmode.mjs
T
Brummel 45cc1c44c9 feat(trace-charts): ordinal x-axis default + zoom/pan/cursor-sync (viewer Tier-1)
The served trace chart's x-axis no longer plots against raw timestamps by
default — non-trading gaps (nights, weekends) left horizontal blanks that
dominated the view. The x spine is now ordinal by default: every recorded slot
sits at an even position, so gaps collapse and the signal fills the width. A
header toggle flips to continuous (real-time-proportional) spacing and back;
default ordinal honours the requested "x-axis should not be continuous", with
continuous as the one-click option.

Comfort, all on the read/render side (the engine and the Rust serve contract
are untouched — window.AURA_TRACES still carries the real xs, the ordinal
indices are derived client-side):
- ordinal mode keeps the axis honest: ticks and the legend value-readout map a
  collapsed index back to the real xs[i], never the index itself.
- wheel-zoom (x, cursor-anchored), drag-pan, double-click-reset on the plot
  (uPlot's default drag-box-zoom is disabled so drag pans).
- panels share a cursor.sync key, so the crosshair lines up across stacked
  panels.

Confined to chart-viewer.js (buildCharts gains an xMode param + a wheelZoomPlugin
factory; mount re-renders on the toggle) and render.rs (CHART_HEAD toggle button
+ a chart-only style). The two twin node guards (build/gaps) re-point to the
ordinal default in lockstep; a new x-mode guard pins ordinal-default / continuous
/ panel-sync / plugin wiring, and a label-honesty guard (gappy-traces fixture,
xs far from the index spine) pins that ordinal ticks report the real timestamp.

Verified: cargo test --workspace green (incl. the four chart guards + render
inline tests), clippy --all-targets -D warnings clean, e2e (aura run --trace +
aura chart) emits a self-contained page defaulting to ordinal with the toggle.

Spec docs/specs/0057, plan docs/plans/0057 (boss-signed; grounding-check PASS).
Derived-fork decision log in the issue comments.

closes #103
2026-06-19 19:00:33 +02:00

49 lines
2.6 KiB
JavaScript

// Headless guard for the trace-chart viewer's x-mode + interaction wiring.
// Properties: (1) the DEFAULT x spine is ORDINAL — data[0] is [0..n-1], not the raw
// timestamps, so non-trading gaps collapse; the x-axis carries an index->label values
// formatter. (2) "continuous" mode restores data[0] === xs verbatim. (3) panels carry a
// shared cursor.sync.key so the crosshair lines up. (4) every config attaches an
// interaction plugin with an init hook. Drives the pure buildCharts; no DOM/uPlot.
import { createRequire } from "node:module";
import { readFileSync } from "node:fs";
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 traces = JSON.parse(readFileSync(join(here, "fixtures", "sample-traces.json"), "utf8"));
const fail = (msg) => { console.error("FAIL: " + msg); process.exit(1); };
const sameArr = (a, b, msg) => {
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length || a.some((v, i) => v !== b[i])) fail(msg);
};
global.window = { AURA_TRACES: traces };
const { buildCharts } = require(join(here, "..", "assets", "chart-viewer.js"));
const n = traces.xs.length;
const idx = Array.from({ length: n }, (_, i) => i);
// (1) default is ordinal: data[0] is the index spine, with an x-axis values formatter.
const ov = buildCharts(traces, "overlay");
sameArr(ov[0].data[0], idx, "default overlay x is the ordinal index spine [0..n-1]");
if (typeof ov[0].opts.axes[0].values !== "function") fail("ordinal overlay x-axis lacks a values formatter");
// (4) interaction wired: a plugin with an init hook.
if (!Array.isArray(ov[0].opts.plugins) || ov[0].opts.plugins.length < 1) fail("no interaction plugin attached");
if (typeof ov[0].opts.plugins[0].hooks.init !== "function") fail("interaction plugin has no init hook");
// (2) continuous mode: data[0] is xs verbatim.
const ovc = buildCharts(traces, "overlay", "continuous");
sameArr(ovc[0].data[0], traces.xs, "continuous overlay x is xs verbatim");
// (3) panels: cursor.sync.key present and equal across panels; default still ordinal.
const panels = buildCharts(traces, "panels");
const keys = panels.map((p) => p.opts.cursor && p.opts.cursor.sync && p.opts.cursor.sync.key);
if (keys.some((k) => !k)) fail("a panel lacks cursor.sync.key");
if (new Set(keys).size !== 1) fail("panels do not share one cursor.sync key");
sameArr(panels[0].data[0], idx, "default panel x is the ordinal index spine");
console.log("OK — ordinal default + continuous toggle + panel cursor-sync + interaction plugin.");
process.exit(0);