Files
Aura/crates/aura-cli/assets/chart-viewer.js
T
Brummel 15ee6d5f4f feat(trace-charts): serve persisted traces as a web chart (iter 2)
Iteration 2 (serve half) of the visual face — spec 0056 / plan 0056:
read a persisted run's traces and serve them as a self-contained uPlot
HTML chart. Completes the goal "tap data from a graph, serve it as a
chart in the browser".

- chart-viewer.js (first-party): a pure buildCharts(traces, mode) that
  maps the injected window.AURA_TRACES to uPlot configs (no DOM) — overlay
  = one plot, every series on its own y-scale over the shared xs; panels =
  one plot per series, all on the same timestamp x. A browser-only mount()
  instantiates the vendored global uPlot. Guarded by a node .mjs DOM test
  (+ a sparse/gaps variant) shelling out exactly like viewer_dot.rs.
- render_chart_html + ChartMode/ChartData/Series (aura-cli/render.rs):
  mirrors render_html — self-contained page, uPlot (1.6.32, vendored
  d534f10) + chart-viewer.js inlined via include_str!, data injected as
  window.AURA_TRACES.
- emit_chart + build_chart_data (aura-cli/main.rs): the spec-§6 3-step
  union-spine alignment — xs = sorted-dedup union of every tap's ts; a
  synthetic empty-payload spine + ALL taps as symmetric join_on_ts sides
  (no tap privileged, no point dropped); flatten each (tap,column) to a
  Series of Option<f64>. New arms `aura chart <name> [--panels]`; a
  missing run is stderr + exit 2 (TraceStoreError::NotFound), never a
  panic. USAGE advertises the verb in lockstep.

Verified by the orchestrator: `cargo test --workspace` green (incl. the
node guards, render_chart_html, and the chart e2e); `cargo clippy
--workspace --all-targets -D warnings` exit 0; and a real end-to-end —
`aura run --trace demo && aura chart demo` emits a 57KB self-contained
page (doctype + inlined uPlot + AURA_TRACES carrying the equity/exposure
series), `--panels` reflects the panels mode, `aura chart nope` exits 2.
Implementer deviation folded in (compiler-prescribed): serde added to
aura-cli/Cargo.toml for the Series derive (Cargo.lock updated).

refs #101
2026-06-19 00:52:45 +02:00

62 lines
2.7 KiB
JavaScript

// The `aura chart` viewer: turns the injected window.AURA_TRACES (xs + per-series
// Option<f64> points, already timestamp-union-aligned by the CLI) into uPlot charts.
//
// buildCharts is PURE (no DOM, no uPlot) and is the unit the headless guard drives.
// mount() is browser-only — it instantiates the vendored global `uPlot` per config.
(function () {
var PALETTE = ["#89b4fa", "#f38ba8", "#a6e3a1", "#f9e2af", "#cba6f7", "#94e2d5"];
// {xs, series:[{name, y_scale_id, points}]} + mode -> array of {opts, data} configs.
// overlay -> one chart, all series, each on its own auto-ranged y-scale (shared xs).
// panels -> one chart per series, each on the SAME shared xs (timestamp-aligned).
function buildCharts(traces, mode) {
var xs = traces.xs;
var series = traces.series;
if (mode === "panels") {
return series.map(function (s, i) {
return {
opts: {
title: s.name,
scales: { x: { time: false }, [s.y_scale_id]: {} },
series: [{}, { label: s.name, scale: s.y_scale_id, stroke: PALETTE[i % PALETTE.length] }],
axes: [{}, { scale: s.y_scale_id }],
},
data: [xs, s.points],
};
});
}
// overlay (default)
var scales = { x: { time: false } };
var uSeries = [{}];
var axes = [{}];
series.forEach(function (s, i) {
scales[s.y_scale_id] = {};
uSeries.push({ label: s.name, scale: s.y_scale_id, stroke: PALETTE[i % PALETTE.length] });
// the first two series carry a drawn axis (left, then right); the rest keep
// their own auto-scale, read via the legend.
if (i < 2) axes.push({ scale: s.y_scale_id, side: i === 0 ? 3 : 1 });
});
return [{ opts: { scales: scales, series: uSeries, axes: axes }, data: [xs].concat(series.map(function (s) { return s.points; })) }];
}
// Browser-only: instantiate the vendored global uPlot per config into #stage.
function mount() {
var traces = window.AURA_TRACES;
var configs = buildCharts(traces, traces.mode);
var stage = document.getElementById("stage");
var w = stage.clientWidth || 900;
var h = configs.length > 1 ? Math.max(160, Math.floor(440 / configs.length)) : 460;
configs.forEach(function (cfg) {
var opts = Object.assign({ width: w, height: h }, cfg.opts);
// eslint-disable-next-line no-undef
new uPlot(opts, cfg.data, stage);
});
}
if (typeof window !== "undefined" && typeof document !== "undefined" && window.AURA_TRACES) {
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", mount);
else mount();
}
if (typeof module !== "undefined" && module.exports) module.exports = { buildCharts: buildCharts };
})();