45cc1c44c9
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
56 lines
2.9 KiB
JavaScript
56 lines
2.9 KiB
JavaScript
// Headless guard for the trace-chart viewer (chart-viewer.js). Property protected:
|
|
// buildCharts maps an AURA_TRACES payload to uPlot configs WITHOUT a DOM — overlay
|
|
// is one chart with every series on its own y-scale over the shared xs; panels is
|
|
// one chart per series, each on the SAME shared xs (timestamp-aligned). It loads the
|
|
// real viewer module (a fix/regression there is observed here) and drives the
|
|
// exported pure `buildCharts`; uPlot itself (which needs `document`) is never
|
|
// instantiated — mount() is browser-only and not called here.
|
|
|
|
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 eq = (a, b, msg) => { if (a !== b) fail(`${msg} (got ${a}, want ${b})`); };
|
|
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);
|
|
};
|
|
|
|
// No `document` global -> chart-viewer.js's browser mount is skipped; require gives
|
|
// us only the pure export (mirrors viewer_dot_ids.mjs stubbing window before require).
|
|
global.window = { AURA_TRACES: traces };
|
|
const { buildCharts } = require(join(here, "..", "assets", "chart-viewer.js"));
|
|
|
|
const n = traces.series.length;
|
|
const idx = Array.from({ length: traces.xs.length }, (_, i) => i);
|
|
|
|
// overlay: ONE chart, all series superimposed, each on its own scale. Default x spine is
|
|
// ORDINAL (indices) so non-trading gaps collapse.
|
|
const overlay = buildCharts(traces, "overlay");
|
|
eq(overlay.length, 1, "overlay is one chart");
|
|
eq(overlay[0].data.length, 1 + n, "overlay data = xs + N series");
|
|
eq(overlay[0].opts.series.length, 1 + n, "overlay uPlot series = x + N");
|
|
sameArr(overlay[0].data[0], idx, "default overlay x-axis is the ordinal index spine");
|
|
const scaleIds = overlay[0].opts.series.slice(1).map((s) => s.scale);
|
|
eq(new Set(scaleIds).size, n, "each overlay series has a distinct y-scale");
|
|
|
|
// continuous mode restores the real-timestamp x spine.
|
|
const overlayC = buildCharts(traces, "overlay", "continuous");
|
|
sameArr(overlayC[0].data[0], traces.xs, "continuous overlay x-axis is the shared xs");
|
|
|
|
// panels: ONE chart per series, all positionally aligned on the SAME ordinal spine.
|
|
const panels = buildCharts(traces, "panels");
|
|
eq(panels.length, n, "panels = one chart per series");
|
|
for (const p of panels) {
|
|
eq(p.data.length, 2, "each panel = xs + its one series");
|
|
sameArr(p.data[0], idx, "panel shares the same ordinal index spine (position-aligned)");
|
|
}
|
|
|
|
console.log(`OK — overlay 1 chart / ${n} series, panels ${n} charts, shared xs.`);
|
|
process.exit(0);
|