Improved cursors functionality

This commit is contained in:
Martino Ferrari
2026-05-27 16:46:06 +02:00
parent 915c6fc126
commit d5b1986761
2 changed files with 76 additions and 1 deletions
+62 -1
View File
@@ -225,6 +225,7 @@ function setActiveSig(plotId, key) {
b.classList.toggle('sig-badge-active', key != null && b.dataset.key === key)); b.classList.toggle('sig-badge-active', key != null && b.dataset.key === key));
const p = plots.find(q => q.id === plotId); const p = plots.find(q => q.id === plotId);
if (p && p.uplot) p.uplot.redraw(false); if (p && p.uplot) p.uplot.redraw(false);
updatePlotCursorReadouts();
} }
// Mark plots containing key dirty and refresh badge vscale text. // Mark plots containing key dirty and refresh badge vscale text.
@@ -1287,8 +1288,10 @@ function drawCursorLines(u, p) {
ctx.fillText(label, px + 14, bbox.top + 2); ctx.fillText(label, px + 14, bbox.top + 2);
// Per-trace: diamond at crossing point + value label // Per-trace: diamond at crossing point + value label
if (p) { if (p) {
const DSZ = 5; // diamond half-size in px const activeKey = plotActiveSignal[p.id];
p.traces.forEach((key, idx) => { p.traces.forEach((key, idx) => {
const isActive = key === activeKey || (!activeKey && p.traces.length === 1);
const DSZ = isActive ? 9 : 5; // diamond half-size in px
const vNorm = interpAtTime(u, idx + 1, val); const vNorm = interpAtTime(u, idx + 1, val);
if (vNorm === null) return; if (vNorm === null) return;
const cy = u.valToPos(vNorm, 'y', true); const cy = u.valToPos(vNorm, 'y', true);
@@ -2019,10 +2022,56 @@ document.getElementById('btn-cursor').addEventListener('click', () => {
cursorsDirty = true; cursorsDirty = true;
}); });
// Format a signal value for the per-plot cursor readout.
function fmtVal(v) {
if (v === null || v === undefined) return '—';
return Math.abs(v) >= 10000 ? v.toExponential(2) : parseFloat(v.toPrecision(4)).toString();
}
// Interpolate the real-unit value of the active/sole signal in plot p at time t.
function getValueAtCursor(p, t) {
if (!p.uplot || t === null) return null;
const key = plotActiveSignal[p.id] || (p.traces.length === 1 ? p.traces[0] : null);
if (!key) return null;
const idx = p.traces.indexOf(key);
if (idx < 0) return null;
const vNorm = interpAtTime(p.uplot, idx + 1, t);
if (vNorm === null) return null;
// Un-normalize: y_norm = (y_raw - offset) / divValue + screenPos
const vs = sigVScale[p.id + ':' + key];
if (!vs) return vNorm;
const dv = vs._resolvedDiv != null ? vs._resolvedDiv : (vs.divValue || 1);
const ofs = vs._resolvedOffset != null ? vs._resolvedOffset : (vs.offset || 0);
const sp = vs.screenPos || 0;
return (vNorm - sp) * dv + ofs;
}
// Update per-plot cursor value readouts (A, B, ΔV) for all plots.
function updatePlotCursorReadouts() {
plots.forEach(p => {
const el = document.getElementById('pcur-' + p.id);
if (!el) return;
// Show only when cursors are on and the plot has an active or sole signal.
const key = plotActiveSignal[p.id] || (p.traces.length === 1 ? p.traces[0] : null);
if (cursors.mode !== 'on' || !key || !p.uplot) {
el.style.display = 'none';
return;
}
const vA = getValueAtCursor(p, cursors.tA);
const vB = getValueAtCursor(p, cursors.tB);
const dv = (vA !== null && vB !== null) ? vB - vA : null;
document.getElementById('pcur-a-' + p.id).textContent = 'A: ' + fmtVal(vA);
document.getElementById('pcur-b-' + p.id).textContent = 'B: ' + fmtVal(vB);
document.getElementById('pcur-dv-' + p.id).textContent = 'ΔV: ' + fmtVal(dv);
el.style.display = 'flex';
});
}
function updateCursorReadout() { function updateCursorReadout() {
const ro = document.getElementById('cursor-readout'); const ro = document.getElementById('cursor-readout');
const active = cursors.mode === 'on'; const active = cursors.mode === 'on';
ro.classList.toggle('visible', active); ro.classList.toggle('visible', active);
updatePlotCursorReadouts();
if (!active) return; if (!active) return;
// Use the current visible x-range to pick the display unit. // Use the current visible x-range to pick the display unit.
@@ -2605,6 +2654,13 @@ function addPlot() {
<div class="plot-card-header"> <div class="plot-card-header">
<span class="plot-title" id="ptitle-${id}" title="Click to configure">Plot ${id}</span> <span class="plot-title" id="ptitle-${id}" title="Click to configure">Plot ${id}</span>
<div class="sig-badges" id="badges-${id}"></div> <div class="sig-badges" id="badges-${id}"></div>
<div class="plot-cursor-ro" id="pcur-${id}" style="display:none">
<span class="pcur-a" id="pcur-a-${id}">A: —</span>
<span class="pcur-sep">│</span>
<span class="pcur-b" id="pcur-b-${id}">B: —</span>
<span class="pcur-sep">│</span>
<span class="pcur-dv" id="pcur-dv-${id}">ΔV: —</span>
</div>
</div> </div>
<div class="plot-cfg-bar" id="pcfg-${id}" style="display:none"> <div class="plot-cfg-bar" id="pcfg-${id}" style="display:none">
<div class="vstb-header"> <div class="vstb-header">
@@ -2657,6 +2713,7 @@ function addTraceTo(plotId, signalKey) {
// Recreate uPlot with new series list // Recreate uPlot with new series list
createUPlot(p); createUPlot(p);
p.needsRedraw = true; p.needsRedraw = true;
updatePlotCursorReadouts();
} }
function removeTraceFrom(plotId, signalKey) { function removeTraceFrom(plotId, signalKey) {
@@ -2672,6 +2729,7 @@ function removeTraceFrom(plotId, signalKey) {
createUPlot(p); createUPlot(p);
p.needsRedraw = true; p.needsRedraw = true;
if (!p.traces.length) document.querySelector('#hint-' + plotId).style.display = ''; if (!p.traces.length) document.querySelector('#hint-' + plotId).style.display = '';
updatePlotCursorReadouts();
} }
function addBadge(plotId, key) { function addBadge(plotId, key) {
@@ -2843,6 +2901,9 @@ function renderDirtyPlots() {
zoomGuard = false; zoomGuard = false;
}); });
// Keep per-plot cursor value readouts in sync with live data.
if (cursors.mode === 'on') updatePlotCursorReadouts();
requestAnimationFrame(renderDirtyPlots); requestAnimationFrame(renderDirtyPlots);
} }
+14
View File
@@ -364,6 +364,20 @@ input[type=range].trig-range::-webkit-slider-thumb {
.vstb-close:hover { color:var(--red); } .vstb-close:hover { color:var(--red); }
.plot-vscale-bar { display:none; } .plot-vscale-bar { display:none; }
/* ── Per-plot cursor value readout (in plot card header) ────────── */
.plot-cursor-ro {
margin-left:auto; flex-shrink:0;
align-items:center; gap:5px;
font-size:10px; font-family:monospace;
background:var(--surface0); border:1px solid var(--surface1);
border-radius:4px; padding:1px 7px; white-space:nowrap;
overflow:hidden;
}
.pcur-a { color:var(--sky); }
.pcur-b { color:var(--yellow); }
.pcur-dv { color:var(--subtext1); }
.pcur-sep { color:var(--surface2); }
/* ── Badge vscale info & active state ───────────────────────────── */ /* ── Badge vscale info & active state ───────────────────────────── */
.vscale-info { .vscale-info {
font-size:9px; color:var(--overlay0); font-family:monospace; margin-left:2px; white-space:nowrap; font-size:9px; color:var(--overlay0); font-family:monospace; margin-left:2px; white-space:nowrap;