|
| 1 | +# MF4 Signal Visualization — Stage 2 Design |
| 2 | + |
| 3 | +**Status:** Draft for review · **Owner:** (Özge) · **Created:** 2026-06-30 |
| 4 | + |
| 5 | +Stage 1 (done, on `main`) made MF4/MDF4 files open: the adapter decodes channels |
| 6 | +into normalized `t=<master> <name>=<value> …` text lines, parsed off-thread in a |
| 7 | +worker. You can already chart *one* signal at a time via the Trends panel. |
| 8 | + |
| 9 | +Stage 2 is the **flexible visualization**: browse all signals and overlay several |
| 10 | +on a shared time axis, with LOGAN's click-to-line tie-back to the raw records. |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## 1. Goals |
| 15 | + |
| 16 | +- See **many signals at once**, overlaid on a shared **time (`t`) axis**. |
| 17 | +- **Browse/pick** signals from a searchable list (a file can have hundreds). |
| 18 | +- Keep LOGAN's signature: **click a point → jump to that line** in the viewer. |
| 19 | +- Handle different scales gracefully (e.g. `rpm` 0–8000 vs `throttle` 0–1). |
| 20 | +- Reuse the existing Trends discovery + series engine; minimal new surface. |
| 21 | + |
| 22 | +### Non-goals (this stage) |
| 23 | +- Channel-conversion (`##CC`) application, units/engineering scaling. |
| 24 | +- Math channels / derived signals, FFT, XY (signal-vs-signal) plots. |
| 25 | +- Multi-file / baseline overlay. |
| 26 | +- Export of the plot (PNG/CSV) — fast follow, not core. |
| 27 | + |
| 28 | +--- |
| 29 | + |
| 30 | +## 2. Current state (what we reuse) |
| 31 | + |
| 32 | +| Piece | Where | Reuse | |
| 33 | +|-------|-------|-------| |
| 34 | +| Field discovery | `trendDiscoverFields()` → `TrendFieldSpec[]` (`name,type,occurrences,distinct`) | populate the signal list | |
| 35 | +| Single series | `trendSeries({field})` → `TrendSeriesResult` (`points[]` = `{viewerLine, epochMs, num, raw}`) | per-signal samples + click-to-line | |
| 36 | +| Canvas chart | `drawTrendChart()` (single series) | generalize to multi-series | |
| 37 | +| Click→line | `jumpToTrendLine()` / `goToLine()` | unchanged | |
| 38 | +| Bottom panel tabs | `data-bottom-tab="…"` system | host the new view | |
| 39 | + |
| 40 | +**Key nuance — the X axis is `t`, not wall-clock.** MF4's master is a *relative* |
| 41 | +float (seconds from start), so `TrendPoint.epochMs` is `null` for these files. |
| 42 | +The overlay must plot against the **`t` value** (or record index when `t` is |
| 43 | +absent), not the timestamp buckets Trends uses for normal logs. This is the main |
| 44 | +reason we need a small new data path rather than just looping `trendSeries`. |
| 45 | + |
| 46 | +--- |
| 47 | + |
| 48 | +## 3. UX |
| 49 | + |
| 50 | +A new **"Signals"** tab in the bottom panel (shown for files that expose numeric |
| 51 | +fields; surfaced prominently for MF4). Two regions: |
| 52 | + |
| 53 | +``` |
| 54 | +┌──────────────────────────────────────────────────────────────────────┐ |
| 55 | +│ Signals [search ▢ "rpm" ] [▣ shared Y] [⊟ fit] [⛶ zoom] │ |
| 56 | +├───────────────┬──────────────────────────────────────────────────────┤ |
| 57 | +│ ▣ ● rpm │ ╱╲ ╱╲ │ |
| 58 | +│ ▣ ● throttle │ ╱╲ ╱ ╲ ╱ ╲ ← overlay plot, x = t │ |
| 59 | +│ ▢ ● speed │ ╱╲╱ ╲╱ ╲╱ ╲ │ |
| 60 | +│ ▢ ● coolantT │ ╱ ╲ │ |
| 61 | +│ … (filterable)│ t=0 t=12.4s cursor: t=6.2 rpm=4100 │ |
| 62 | +└───────────────┴──────────────────────────────────────────────────────┘ |
| 63 | +``` |
| 64 | + |
| 65 | +- **Left — signal browser:** searchable, scrollable checkbox list. Each row: a |
| 66 | + color swatch (auto-assigned, editable later), name, and a sparkline/count hint. |
| 67 | + Check to add to the plot, uncheck to remove. "Numeric only" by default. |
| 68 | +- **Right — overlay plot:** all checked signals on one canvas, shared `t` axis. |
| 69 | + - **Shared Y** (normalized 0–1 per signal) by default so differently-scaled |
| 70 | + signals are comparable; toggle to **real Y** with a left axis (and optionally |
| 71 | + a second right axis for one selected signal). |
| 72 | + - **Cursor readout:** hovering shows `t` and each visible signal's value at the |
| 73 | + nearest sample. |
| 74 | + - **Zoom/pan:** drag-select to zoom X; double-click to reset; wheel to zoom. |
| 75 | + - **Click a point → jump** to that record's line in the viewer (via `viewerLine`). |
| 76 | + |
| 77 | +--- |
| 78 | + |
| 79 | +## 4. Data model & backend |
| 80 | + |
| 81 | +Add one main-process method + IPC: **`signalSeries`** — given selected field |
| 82 | +names, read the normalized file once and return **aligned** samples: |
| 83 | + |
| 84 | +```ts |
| 85 | +// request |
| 86 | +{ fields: string[], xField?: string /* default 't' */, maxPoints?: number, |
| 87 | + startLine?: number, endLine?: number } |
| 88 | + |
| 89 | +// response |
| 90 | +{ |
| 91 | + x: { field: string; values: number[] }, // the t (or index) axis |
| 92 | + series: Array<{ |
| 93 | + field: string; |
| 94 | + values: (number | null)[]; // aligned 1:1 with x (null = absent) |
| 95 | + viewerLines: number[]; // for click→line |
| 96 | + min: number; max: number; // for autoscale / normalize |
| 97 | + }>, |
| 98 | + totalRecords: number; truncated: boolean; |
| 99 | +} |
| 100 | +``` |
| 101 | + |
| 102 | +Why a dedicated call instead of N × `trendSeries`: |
| 103 | +- **Alignment by `t`:** every record line carries `t=` + the signals; reading once |
| 104 | + yields a single shared X with each signal aligned, no cross-join guesswork. |
| 105 | +- **One pass, downsampled:** decimate to `maxPoints` (e.g. ~2–4k per signal, |
| 106 | + min/max-preserving) so the canvas stays fast on million-record files. |
| 107 | +- The line is `t=… name=value …`; parsing is the same regex Trends already uses. |
| 108 | + |
| 109 | +`trendDiscoverFields` is reused as-is for the browser list (it already returns |
| 110 | +type so we can default to numeric). |
| 111 | + |
| 112 | +**Surfacing MF4-ness to the renderer:** extend the `OPEN_FILE` result with the |
| 113 | +resolved adapter id / capabilities (already tracked in `sourceRegistry` via |
| 114 | +`getSourceCapabilities`) so the renderer can auto-open/feature the Signals tab for |
| 115 | +MF4. Generic numeric logs can opt in manually. |
| 116 | + |
| 117 | +--- |
| 118 | + |
| 119 | +## 5. Phased plan |
| 120 | + |
| 121 | +**P1 — Minimal overlay (the core ask).** |
| 122 | +- "Signals" tab + searchable checkbox list (`trendDiscoverFields`). |
| 123 | +- `signalSeries` backend (aligned, downsampled). |
| 124 | +- Multi-series canvas (generalized `drawTrendChart`), shared-Y normalize, legend, |
| 125 | + click→line. Auto colors. |
| 126 | + |
| 127 | +**P2 — Comfort.** |
| 128 | +- Zoom/pan + double-click reset, cursor readout/crosshair. |
| 129 | +- Real-Y mode with left axis + optional second (right) axis for one signal. |
| 130 | +- Per-signal color edit + show/hide without unchecking. |
| 131 | + |
| 132 | +**P3 — Nice-to-have.** |
| 133 | +- Stacked **lanes** (separate plots sharing the X cursor) for unrelated scales. |
| 134 | +- Export PNG/CSV; persist the chosen signal set per file in `.logan/` sidecar. |
| 135 | + |
| 136 | +Ship P1 first, get it in front of real MF4 files, iterate. |
| 137 | + |
| 138 | +--- |
| 139 | + |
| 140 | +## 6. Open questions (need decisions) |
| 141 | + |
| 142 | +1. **Placement** — bottom-panel **"Signals" tab** (consistent with Trends), or a |
| 143 | + larger dedicated view? Default: tab. |
| 144 | +2. **Default Y mode** — start in **shared/normalized** (compare shapes) or real |
| 145 | + units? Default: normalized, with a one-click toggle. |
| 146 | +3. **Second axis** — is a single right-hand axis enough for P2, or do you want |
| 147 | + full per-signal axis assignment (→ pushes toward P3 lanes)? |
| 148 | +4. **Auto-open for MF4** — when an MF4 file opens, should the Signals tab pop |
| 149 | + automatically, or stay opt-in like other panels? |
| 150 | +5. **Scale limits** — biggest real file (record count) we must stay smooth on? |
| 151 | + Drives the downsample budget. |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +## 7. Risks |
| 156 | + |
| 157 | +- **Performance** on huge recordings → mitigated by one-pass min/max decimation |
| 158 | + and a hard `maxPoints` cap; never feed raw points to canvas. |
| 159 | +- **No timestamp** → X is relative `t`; if a file lacks a master, fall back to |
| 160 | + record index and label it clearly. |
| 161 | +- **Sparse signals** (different channel groups sampled at different rates) → P1 |
| 162 | + reads the first channel group only (Stage-1 limitation); multi-group alignment |
| 163 | + is a later item, called out so results aren't silently partial. |
0 commit comments