Functional v2
This commit is contained in:
+55
-14
@@ -187,7 +187,9 @@ function checkTrigger(sigs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
function fireTrigger(t) {
|
function fireTrigger(t) {
|
||||||
trig.armed=false; trig.collecting=true; trig.trigTime=t; trig.snapshot=null;
|
// Clear the previous snapshot here (not in trigArm) so the last waveform
|
||||||
|
// and trigger marker stay visible while the trigger is rearmed and waiting.
|
||||||
|
trig.snapshot=null; trig.armed=false; trig.collecting=true; trig.trigTime=t;
|
||||||
updateTrigStatusBadge('waiting'); setAllCardsCollecting(true);
|
updateTrigStatusBadge('waiting'); setAllCardsCollecting(true);
|
||||||
}
|
}
|
||||||
function finaliseTriggerCapture() {
|
function finaliseTriggerCapture() {
|
||||||
@@ -214,7 +216,10 @@ function finaliseTriggerCapture() {
|
|||||||
setTimeout(() => { if (trig.enabled && trig.mode==='normal' && !trig.stopped) trigArm(); }, 200);
|
setTimeout(() => { if (trig.enabled && trig.mode==='normal' && !trig.stopped) trigArm(); }, 200);
|
||||||
}
|
}
|
||||||
function trigArm() {
|
function trigArm() {
|
||||||
trig.snapshot=null; trig.armed=true; trig.collecting=false; trig.prevVal=null;
|
// Do NOT clear trig.snapshot here — keep the last waveform and trigger marker
|
||||||
|
// visible while waiting for the next event. fireTrigger() clears it when a new
|
||||||
|
// trigger actually fires.
|
||||||
|
trig.armed=true; trig.collecting=false; trig.prevVal=null;
|
||||||
showRearmBtn(false); updateTrigStatusBadge('armed');
|
showRearmBtn(false); updateTrigStatusBadge('armed');
|
||||||
cursors.tA = null; cursors.tB = null; updateCursorReadout();
|
cursors.tA = null; cursors.tB = null; updateCursorReadout();
|
||||||
updateCursorBtnVisibility();
|
updateCursorBtnVisibility();
|
||||||
@@ -380,6 +385,30 @@ function fmtTrigTick(u, vals) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw a vertical marker line at x=0 (the trigger event) in trigger mode.
|
||||||
|
function drawTriggerMarker(u) {
|
||||||
|
if (!trig.enabled || !trig.snapshot) return;
|
||||||
|
const { ctx, bbox } = u;
|
||||||
|
if (!bbox) return;
|
||||||
|
const x = u.valToPos(0, 'x', true);
|
||||||
|
if (x < bbox.left || x > bbox.left + bbox.width) return;
|
||||||
|
const px = Math.round(x);
|
||||||
|
ctx.save();
|
||||||
|
ctx.strokeStyle = 'rgba(203,166,247,0.9)';
|
||||||
|
ctx.lineWidth = 1.5;
|
||||||
|
ctx.setLineDash([]);
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(px, bbox.top);
|
||||||
|
ctx.lineTo(px, bbox.top + bbox.height);
|
||||||
|
ctx.stroke();
|
||||||
|
// Small "T" label
|
||||||
|
ctx.fillStyle = 'rgba(203,166,247,0.9)';
|
||||||
|
ctx.font = 'bold 10px monospace';
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
ctx.fillText('T', px + 3, bbox.top + 2);
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
// Draw custom cursor A/B lines onto the canvas (called from draw hook)
|
// Draw custom cursor A/B lines onto the canvas (called from draw hook)
|
||||||
function drawCursorLines(u) {
|
function drawCursorLines(u) {
|
||||||
const { ctx, bbox } = u;
|
const { ctx, bbox } = u;
|
||||||
@@ -436,13 +465,13 @@ function makeUPlotOpts(p, inTrigMode) {
|
|||||||
series: seriesArr,
|
series: seriesArr,
|
||||||
axes: [
|
axes: [
|
||||||
{ stroke:'#7f849c', grid:{ stroke:'#313244', width:1 }, ticks:{ stroke:'#313244', width:1 },
|
{ stroke:'#7f849c', grid:{ stroke:'#313244', width:1 }, ticks:{ stroke:'#313244', width:1 },
|
||||||
values: xVals, size: 40 },
|
values: xVals, size: 36, space: 90 },
|
||||||
{ stroke:'#7f849c', grid:{ stroke:'#313244', width:1 }, ticks:{ stroke:'#313244', width:1 }, size: 50 },
|
{ stroke:'#7f849c', grid:{ stroke:'#313244', width:1 }, ticks:{ stroke:'#313244', width:1 }, size: 50 },
|
||||||
],
|
],
|
||||||
legend: { show: false },
|
legend: { show: false },
|
||||||
padding: [4, 8, 0, 0],
|
padding: [4, 4, 0, 0],
|
||||||
hooks: {
|
hooks: {
|
||||||
draw: [ u => drawCursorLines(u) ],
|
draw: [ u => { drawCursorLines(u); drawTriggerMarker(u); } ],
|
||||||
// Two-hook zoom detection: setSelect flags that the NEXT setScale is user-initiated.
|
// Two-hook zoom detection: setSelect flags that the NEXT setScale is user-initiated.
|
||||||
// uPlot fires setSelect → then immediately setScale (when drag.setScale:true).
|
// uPlot fires setSelect → then immediately setScale (when drag.setScale:true).
|
||||||
// All programmatic setScale calls happen without a preceding setSelect, so the
|
// All programmatic setScale calls happen without a preceding setSelect, so the
|
||||||
@@ -989,7 +1018,16 @@ document.getElementById('trig-pre').addEventListener('input', e => {
|
|||||||
trig.prePercent = parseInt(e.target.value, 10);
|
trig.prePercent = parseInt(e.target.value, 10);
|
||||||
document.getElementById('trig-pre-val').textContent = trig.prePercent + '%';
|
document.getElementById('trig-pre-val').textContent = trig.prePercent + '%';
|
||||||
});
|
});
|
||||||
document.getElementById('trig-mode').addEventListener('change', e => { trig.mode = e.target.value; });
|
document.getElementById('trig-mode').addEventListener('change', e => {
|
||||||
|
trig.mode = e.target.value;
|
||||||
|
// If a snapshot already exists the trigger has already fired — update button
|
||||||
|
// visibility immediately so it matches the newly selected mode.
|
||||||
|
if (trig.snapshot) {
|
||||||
|
showRearmBtn(trig.mode === 'single');
|
||||||
|
showStopBtn(trig.mode === 'normal');
|
||||||
|
updateStopBtn();
|
||||||
|
}
|
||||||
|
});
|
||||||
document.getElementById('btn-trig-rearm').addEventListener('click', () => { if (trig.enabled) trigArm(); });
|
document.getElementById('btn-trig-rearm').addEventListener('click', () => { if (trig.enabled) trigArm(); });
|
||||||
document.getElementById('btn-trig-stop').addEventListener('click', () => {
|
document.getElementById('btn-trig-stop').addEventListener('click', () => {
|
||||||
if (!trig.enabled || trig.mode !== 'normal') return;
|
if (!trig.enabled || trig.mode !== 'normal') return;
|
||||||
@@ -1076,7 +1114,7 @@ function layoutPlotCount(cls) {
|
|||||||
|
|
||||||
// Build a small SVG grid thumbnail for a given cols×rows layout.
|
// Build a small SVG grid thumbnail for a given cols×rows layout.
|
||||||
function layoutSVG(cols, rows) {
|
function layoutSVG(cols, rows) {
|
||||||
const W = 40, H = 28, GAP = 2, PAD = 2;
|
const W = 28, H = 20, GAP = 1.5, PAD = 1.5;
|
||||||
const cw = (W - PAD * 2 - GAP * (cols - 1)) / cols;
|
const cw = (W - PAD * 2 - GAP * (cols - 1)) / cols;
|
||||||
const ch = (H - PAD * 2 - GAP * (rows - 1)) / rows;
|
const ch = (H - PAD * 2 - GAP * (rows - 1)) / rows;
|
||||||
let rects = '';
|
let rects = '';
|
||||||
@@ -1119,12 +1157,17 @@ function applyLayout(cls) {
|
|||||||
deletePlot(removeId);
|
deletePlot(removeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add missing plots.
|
// Add missing plots (DOM only — uPlot created below after layout settles).
|
||||||
while (plots.length < needed) addPlot();
|
while (plots.length < needed) addPlot();
|
||||||
|
|
||||||
setTimeout(() => plots.forEach(p => {
|
// Recreate all uPlot instances once the CSS grid has sized the cells.
|
||||||
if (p.uplot) p.uplot.setSize({ width: p.div.clientWidth, height: p.div.clientHeight });
|
// This also updates axis visibility (which axes show labels depends on grid position).
|
||||||
}), 120);
|
requestAnimationFrame(() => {
|
||||||
|
plots.forEach(p => createUPlot(p));
|
||||||
|
setTimeout(() => plots.forEach(p => {
|
||||||
|
if (p.uplot) p.uplot.setSize({ width: p.div.clientWidth, height: p.div.clientHeight });
|
||||||
|
}), 60);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildLayoutMenu() {
|
function buildLayoutMenu() {
|
||||||
@@ -1218,9 +1261,7 @@ function addPlot() {
|
|||||||
const plotBody = card.querySelector('#pbody-'+id);
|
const plotBody = card.querySelector('#pbody-'+id);
|
||||||
const p = { id, traces:[], div: plotBody, needsRedraw:false, xRange:null, uplot:null, ro:null };
|
const p = { id, traces:[], div: plotBody, needsRedraw:false, xRange:null, uplot:null, ro:null };
|
||||||
plots.push(p);
|
plots.push(p);
|
||||||
|
// uPlot creation is handled by applyLayout (batch, after DOM settles).
|
||||||
// Create the uPlot instance (empty) — deferred so DOM has settled
|
|
||||||
requestAnimationFrame(() => { createUPlot(p); });
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -191,8 +191,8 @@ input[type=range].trig-range::-webkit-slider-thumb {
|
|||||||
#main { flex:1; display:flex; flex-direction:column; overflow:hidden; min-width:0; }
|
#main { flex:1; display:flex; flex-direction:column; overflow:hidden; min-width:0; }
|
||||||
/* Layout toggle button in topbar */
|
/* Layout toggle button in topbar */
|
||||||
#btn-layout {
|
#btn-layout {
|
||||||
display:flex; align-items:center; gap:6px;
|
display:flex; align-items:center; gap:4px;
|
||||||
font-size:12px; padding:4px 10px;
|
font-size:11px; padding:3px 8px;
|
||||||
}
|
}
|
||||||
#btn-layout svg { flex-shrink:0; }
|
#btn-layout svg { flex-shrink:0; }
|
||||||
#btn-layout span { flex-shrink:0; }
|
#btn-layout span { flex-shrink:0; }
|
||||||
@@ -206,10 +206,10 @@ input[type=range].trig-range::-webkit-slider-thumb {
|
|||||||
}
|
}
|
||||||
#layout-menu.open { display:grid; }
|
#layout-menu.open { display:grid; }
|
||||||
.layout-menu-item {
|
.layout-menu-item {
|
||||||
display:flex; flex-direction:column; align-items:center; gap:5px;
|
display:flex; flex-direction:column; align-items:center; gap:3px;
|
||||||
padding:8px 14px; cursor:pointer;
|
padding:5px 10px; cursor:pointer;
|
||||||
background:var(--surface0); border:1px solid var(--surface1); border-radius:6px;
|
background:var(--surface0); border:1px solid var(--surface1); border-radius:6px;
|
||||||
color:var(--subtext1); font-size:11px; font-family:monospace;
|
color:var(--subtext1); font-size:10px; font-family:monospace;
|
||||||
transition:background var(--transition),border-color var(--transition),color var(--transition);
|
transition:background var(--transition),border-color var(--transition),color var(--transition);
|
||||||
}
|
}
|
||||||
.layout-menu-item:hover { background:var(--surface1); border-color:var(--accent); color:var(--accent); }
|
.layout-menu-item:hover { background:var(--surface1); border-color:var(--accent); color:var(--accent); }
|
||||||
@@ -217,7 +217,8 @@ input[type=range].trig-range::-webkit-slider-thumb {
|
|||||||
|
|
||||||
/* ── Plot grid ────────────────────────────────────────────────── */
|
/* ── Plot grid ────────────────────────────────────────────────── */
|
||||||
#plot-grid {
|
#plot-grid {
|
||||||
flex:1; min-height:0; display:grid; gap:8px; padding:8px; overflow:hidden;
|
flex:1; min-height:0; display:grid; gap:0; padding:0; overflow:hidden;
|
||||||
|
border-top:1px solid var(--surface0); border-left:1px solid var(--surface0);
|
||||||
}
|
}
|
||||||
#plot-grid.l1x1 { grid-template-columns:1fr; grid-template-rows:1fr; }
|
#plot-grid.l1x1 { grid-template-columns:1fr; grid-template-rows:1fr; }
|
||||||
#plot-grid.l2x1 { grid-template-columns:1fr 1fr; grid-template-rows:1fr; }
|
#plot-grid.l2x1 { grid-template-columns:1fr 1fr; grid-template-rows:1fr; }
|
||||||
@@ -232,16 +233,25 @@ input[type=range].trig-range::-webkit-slider-thumb {
|
|||||||
|
|
||||||
/* ── Plot card ────────────────────────────────────────────────── */
|
/* ── Plot card ────────────────────────────────────────────────── */
|
||||||
.plot-card {
|
.plot-card {
|
||||||
background:var(--surface0); border:1px solid var(--surface1);
|
background:var(--bg);
|
||||||
border-radius:var(--radius); display:flex; flex-direction:column;
|
border-right:1px solid var(--surface0); border-bottom:1px solid var(--surface0);
|
||||||
|
border-radius:0; display:flex; flex-direction:column;
|
||||||
min-height:0; position:relative; overflow:hidden;
|
min-height:0; position:relative; overflow:hidden;
|
||||||
transition:border-color var(--transition);
|
|
||||||
}
|
}
|
||||||
.plot-card.drag-over { border-color:var(--accent); box-shadow:0 0 0 2px rgba(137,180,250,0.25); }
|
.plot-card.drag-over { background:rgba(137,180,250,0.04); box-shadow:inset 0 0 0 2px var(--accent); }
|
||||||
|
/* Header: hidden by default, slides in on hover */
|
||||||
.plot-card-header {
|
.plot-card-header {
|
||||||
|
position:absolute; top:0; left:0; right:0; z-index:5;
|
||||||
|
background:rgba(17,17,27,0.88); backdrop-filter:blur(6px);
|
||||||
|
border-bottom:1px solid var(--surface1);
|
||||||
display:flex; align-items:center; gap:5px;
|
display:flex; align-items:center; gap:5px;
|
||||||
padding:4px 8px; border-bottom:1px solid var(--surface1);
|
padding:3px 8px; min-height:26px; overflow:hidden;
|
||||||
flex-shrink:0; min-height:30px; max-height:30px; overflow:hidden;
|
opacity:0; transform:translateY(-100%);
|
||||||
|
transition:opacity 0.12s ease, transform 0.12s ease;
|
||||||
|
pointer-events:none;
|
||||||
|
}
|
||||||
|
.plot-card:hover .plot-card-header {
|
||||||
|
opacity:1; transform:translateY(0); pointer-events:auto;
|
||||||
}
|
}
|
||||||
.plot-title {
|
.plot-title {
|
||||||
font-size:11px; color:var(--subtext1); font-weight:600;
|
font-size:11px; color:var(--subtext1); font-weight:600;
|
||||||
@@ -263,29 +273,11 @@ input[type=range].trig-range::-webkit-slider-thumb {
|
|||||||
transition:color var(--transition);
|
transition:color var(--transition);
|
||||||
}
|
}
|
||||||
.sig-badge-x:hover { color:var(--red); }
|
.sig-badge-x:hover { color:var(--red); }
|
||||||
.plot-icons { display:flex; gap:2px; flex-shrink:0; }
|
|
||||||
.plot-icon-btn {
|
|
||||||
background:none; border:none; cursor:pointer; color:var(--subtext0);
|
|
||||||
font-size:13px; line-height:1; padding:2px 4px; border-radius:4px;
|
|
||||||
transition:color var(--transition),background var(--transition);
|
|
||||||
}
|
|
||||||
.plot-icon-btn:hover { color:var(--accent); background:var(--surface1); }
|
|
||||||
.plot-icon-btn.danger:hover { color:var(--red); }
|
|
||||||
.plot-body { flex:1; position:relative; min-height:0; overflow:hidden; }
|
.plot-body { flex:1; position:relative; min-height:0; overflow:hidden; }
|
||||||
.drop-hint {
|
.drop-hint {
|
||||||
position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
|
position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
|
||||||
color:var(--overlay0); font-size:13px; pointer-events:none;
|
color:var(--overlay0); font-size:13px; pointer-events:none;
|
||||||
}
|
}
|
||||||
.pause-overlay {
|
|
||||||
position:absolute; inset:0; display:none;
|
|
||||||
align-items:center; justify-content:center; pointer-events:none; z-index:10;
|
|
||||||
}
|
|
||||||
.plot-card.paused .pause-overlay { display:flex; }
|
|
||||||
.pause-overlay-text {
|
|
||||||
background:rgba(49,50,68,0.82); color:var(--yellow);
|
|
||||||
font-size:11px; font-weight:600; padding:4px 12px; border-radius:20px;
|
|
||||||
border:1px solid var(--yellow);
|
|
||||||
}
|
|
||||||
.trig-collect-overlay {
|
.trig-collect-overlay {
|
||||||
position:absolute; inset:0; display:none;
|
position:absolute; inset:0; display:none;
|
||||||
align-items:center; justify-content:center; pointer-events:none; z-index:10;
|
align-items:center; justify-content:center; pointer-events:none; z-index:10;
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ $TestApp = {
|
|||||||
// ── 1 kHz sine burst – channel 2 (phase-shifted by π/2) ──────────────
|
// ── 1 kHz sine burst – channel 2 (phase-shifted by π/2) ──────────────
|
||||||
+SineGAM4 = {
|
+SineGAM4 = {
|
||||||
Class = SineArrayGAM
|
Class = SineArrayGAM
|
||||||
Frequency = 1000.0
|
Frequency = 10000.0
|
||||||
Amplitude = 0.5
|
Amplitude = 0.5
|
||||||
Phase = 1.5708
|
Phase = 1.5708
|
||||||
Offset = 0.0
|
Offset = 0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user