15ee6d5f4f
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
62 lines
2.7 KiB
JavaScript
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 };
|
|
})();
|