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
This commit is contained in:
@@ -59,10 +59,13 @@ const GRAPH_HEAD: &str = r#"<header>
|
||||
"#;
|
||||
|
||||
/// The chart page's `<title>` text and `<header>` line — describes what the chart
|
||||
/// view actually renders (timestamp-aligned trace series), not the graph viewer.
|
||||
/// view actually renders (timestamp-aligned trace series), not the graph viewer. The
|
||||
/// `#xmode-toggle` button is wired by `chart-viewer.js` (`mount`) to flip the x-axis
|
||||
/// between the ordinal (gap-collapsed) default and continuous (real-time) spacing.
|
||||
const CHART_HEAD: &str = r#"<header>
|
||||
<b>aura chart</b>
|
||||
<span class="sub">timestamp-aligned trace series</span>
|
||||
<button id="xmode-toggle" type="button">x: collapsed (gaps off)</button>
|
||||
</header>
|
||||
<div id="stage"></div>
|
||||
<pre id="err"></pre>
|
||||
@@ -76,6 +79,16 @@ const UPLOT_JS: &str = include_str!("../assets/uPlot.iife.min.js");
|
||||
const UPLOT_CSS: &str = include_str!("../assets/uPlot.min.css");
|
||||
const CHART_VIEWER_JS: &str = include_str!("../assets/chart-viewer.js");
|
||||
|
||||
/// Chart-page-only styling (the `#xmode-toggle` button) — injected by
|
||||
/// `render_chart_html` after the vendored uPlot CSS, NOT into the shared `SHELL_HEAD`
|
||||
/// (the graph page has no toggle and must not inherit its rule).
|
||||
const CHART_CSS: &str = r#"
|
||||
#xmode-toggle { margin-left: auto; background: #1e1e2e; color: #cdd6f4;
|
||||
border: 1px solid #313244; border-radius: 6px; padding: 4px 10px;
|
||||
font-family: ui-monospace, monospace; font-size: 12px; cursor: pointer; }
|
||||
#xmode-toggle:hover { border-color: #89b4fa; color: #f5e0dc; }
|
||||
"#;
|
||||
|
||||
/// Assemble the self-contained interactive graph viewer page for `root`.
|
||||
///
|
||||
/// Order is load-bearing: the Graphviz-WASM and pan-zoom globals (`Viz`,
|
||||
@@ -146,12 +159,13 @@ pub fn render_chart_html(data: &ChartData, mode: ChartMode) -> String {
|
||||
.to_string();
|
||||
|
||||
let mut html = String::with_capacity(
|
||||
SHELL_HEAD.len() + CHART_HEAD.len() + UPLOT_JS.len() + UPLOT_CSS.len() + CHART_VIEWER_JS.len() + traces.len() + 256,
|
||||
SHELL_HEAD.len() + CHART_HEAD.len() + UPLOT_JS.len() + UPLOT_CSS.len() + CHART_CSS.len() + CHART_VIEWER_JS.len() + traces.len() + 256,
|
||||
);
|
||||
html.push_str(&SHELL_HEAD.replace("{title}", "aura chart"));
|
||||
html.push_str(CHART_HEAD);
|
||||
html.push_str("<style>");
|
||||
html.push_str(UPLOT_CSS);
|
||||
html.push_str(CHART_CSS);
|
||||
html.push_str("</style>\n<script>");
|
||||
html.push_str(UPLOT_JS);
|
||||
html.push_str("</script>\n<script>window.AURA_TRACES = ");
|
||||
@@ -194,6 +208,8 @@ mod tests {
|
||||
html.contains("i64: \"#f9e2af\"") && html.contains("timestamp: \"#cba6f7\""),
|
||||
"C4 four-colour palette missing"
|
||||
);
|
||||
// the chart-only toggle control must not leak onto the graph page
|
||||
assert!(!html.contains("xmode-toggle"), "chart toggle leaked onto the graph page");
|
||||
}
|
||||
|
||||
fn sample_chart_data() -> ChartData {
|
||||
@@ -229,6 +245,8 @@ mod tests {
|
||||
let html = render_chart_html(&sample_chart_data(), ChartMode::Overlay);
|
||||
assert!(html.contains("<title>aura chart</title>"), "chart page title not chart-specific");
|
||||
assert!(html.contains("<b>aura chart</b>"), "chart header label not chart-specific");
|
||||
// the x-axis toggle control is part of the chart chrome
|
||||
assert!(html.contains("id=\"xmode-toggle\""), "x-axis toggle control missing from the chart page");
|
||||
// none of the graph viewer's chrome leaked in
|
||||
assert!(!html.contains("aura graph"), "graph label leaked onto the chart page");
|
||||
assert!(!html.contains("hover · [+] expand · body drill"), "graph breadcrumb leaked onto the chart page");
|
||||
|
||||
Reference in New Issue
Block a user