Fix scope viewer: initialize div state from data range to prevent empty CRT
xDiv/yDiv were initialized as null and excluded from the main uPlot effect's dependency array, causing the effect to bail on its null guard and never re-run when the init effect set them to computed values.
This commit is contained in:
parent
ac82068b98
commit
3c9c83742d
@ -104,18 +104,8 @@ export function ScopeWaveformViewer({ waveform, width, onExitScope }: ScopeWavef
|
|||||||
return waveform.variables.length > 1 ? waveform.variables[1].type : 'voltage';
|
return waveform.variables.length > 1 ? waveform.variables[1].type : 'voltage';
|
||||||
}, [waveform, isAC]);
|
}, [waveform, isAC]);
|
||||||
|
|
||||||
// State
|
|
||||||
const [activeTrace, setActiveTrace] = useState(0);
|
|
||||||
const [xDiv, setXDiv] = useState<number | null>(null);
|
|
||||||
const [yDiv, setYDiv] = useState<number | null>(null);
|
|
||||||
const [skinId, setSkinId] = useState<SkinId>(() => {
|
|
||||||
const stored = safeGetItem('spicebook-scope-skin') as SkinId | null;
|
|
||||||
return stored && SKINS[stored] ? stored : '465';
|
|
||||||
});
|
|
||||||
|
|
||||||
const skin = SKINS[skinId];
|
|
||||||
|
|
||||||
// Compute data range using loop-based min/max (safe for large arrays)
|
// Compute data range using loop-based min/max (safe for large arrays)
|
||||||
|
// Must be computed before useState so div initializers can reference them
|
||||||
const { xMin, xMax, yMin, yMax } = useMemo(() => {
|
const { xMin, xMax, yMin, yMax } = useMemo(() => {
|
||||||
const [xLo, xHi] = arrayMinMax(waveform.x_data);
|
const [xLo, xHi] = arrayMinMax(waveform.x_data);
|
||||||
const yData = isAC && waveform.y_magnitude_db
|
const yData = isAC && waveform.y_magnitude_db
|
||||||
@ -125,7 +115,18 @@ export function ScopeWaveformViewer({ waveform, width, onExitScope }: ScopeWavef
|
|||||||
return { xMin: xLo, xMax: xHi, yMin: yLo, yMax: yHi };
|
return { xMin: xLo, xMax: xHi, yMin: yLo, yMax: yHi };
|
||||||
}, [waveform, isAC]);
|
}, [waveform, isAC]);
|
||||||
|
|
||||||
// Initialize div values from data range
|
// State — xDiv/yDiv initialized from data range (never null)
|
||||||
|
const [activeTrace, setActiveTrace] = useState(0);
|
||||||
|
const [xDiv, setXDiv] = useState(() => autoDiv(xMin, xMax, NUM_H_DIVS, xSeqForType(xType)));
|
||||||
|
const [yDiv, setYDiv] = useState(() => autoDiv(yMin, yMax, NUM_V_DIVS, ySeqForType(yType)));
|
||||||
|
const [skinId, setSkinId] = useState<SkinId>(() => {
|
||||||
|
const stored = safeGetItem('spicebook-scope-skin') as SkinId | null;
|
||||||
|
return stored && SKINS[stored] ? stored : '465';
|
||||||
|
});
|
||||||
|
|
||||||
|
const skin = SKINS[skinId];
|
||||||
|
|
||||||
|
// Re-initialize div values when data range changes (new waveform loaded)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setXDiv(autoDiv(xMin, xMax, NUM_H_DIVS, xSeqForType(xType)));
|
setXDiv(autoDiv(xMin, xMax, NUM_H_DIVS, xSeqForType(xType)));
|
||||||
setYDiv(autoDiv(yMin, yMax, NUM_V_DIVS, ySeqForType(yType)));
|
setYDiv(autoDiv(yMin, yMax, NUM_V_DIVS, ySeqForType(yType)));
|
||||||
@ -133,7 +134,7 @@ export function ScopeWaveformViewer({ waveform, width, onExitScope }: ScopeWavef
|
|||||||
|
|
||||||
// Build and mount uPlot (only on data/size changes, NOT on trace/div changes)
|
// Build and mount uPlot (only on data/size changes, NOT on trace/div changes)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!screenRef.current || width <= 0 || xDiv === null || yDiv === null) return;
|
if (!screenRef.current || width <= 0) return;
|
||||||
|
|
||||||
const el = screenRef.current;
|
const el = screenRef.current;
|
||||||
const rect = el.getBoundingClientRect();
|
const rect = el.getBoundingClientRect();
|
||||||
@ -219,7 +220,7 @@ export function ScopeWaveformViewer({ waveform, width, onExitScope }: ScopeWavef
|
|||||||
// Update X/Y scale via setScale (no chart rebuild)
|
// Update X/Y scale via setScale (no chart rebuild)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const u = uplotRef.current;
|
const u = uplotRef.current;
|
||||||
if (!u || xDiv === null || yDiv === null) return;
|
if (!u) return;
|
||||||
|
|
||||||
const xMid = (xMin + xMax) / 2;
|
const xMid = (xMin + xMax) / 2;
|
||||||
const yMid = (yMin + yMax) / 2;
|
const yMid = (yMin + yMax) / 2;
|
||||||
@ -240,7 +241,6 @@ export function ScopeWaveformViewer({ waveform, width, onExitScope }: ScopeWavef
|
|||||||
|
|
||||||
const stepXDiv = useCallback(() => {
|
const stepXDiv = useCallback(() => {
|
||||||
setXDiv(prev => {
|
setXDiv(prev => {
|
||||||
if (prev === null) return prev;
|
|
||||||
const seq = xSeqForType(xType);
|
const seq = xSeqForType(xType);
|
||||||
const next = step125(prev, seq, 1);
|
const next = step125(prev, seq, 1);
|
||||||
if (next === prev) return seq[0];
|
if (next === prev) return seq[0];
|
||||||
@ -250,7 +250,6 @@ export function ScopeWaveformViewer({ waveform, width, onExitScope }: ScopeWavef
|
|||||||
|
|
||||||
const stepYDiv = useCallback(() => {
|
const stepYDiv = useCallback(() => {
|
||||||
setYDiv(prev => {
|
setYDiv(prev => {
|
||||||
if (prev === null) return prev;
|
|
||||||
const seq = ySeqForType(yType);
|
const seq = ySeqForType(yType);
|
||||||
const next = step125(prev, seq, 1);
|
const next = step125(prev, seq, 1);
|
||||||
if (next === prev) return seq[0];
|
if (next === prev) return seq[0];
|
||||||
@ -273,8 +272,8 @@ export function ScopeWaveformViewer({ waveform, width, onExitScope }: ScopeWavef
|
|||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
const currentTraceName = traceNames[activeTrace] || '\u2014';
|
const currentTraceName = traceNames[activeTrace] || '\u2014';
|
||||||
const xDivLabel = xDiv !== null ? formatEng(xDiv, divUnit(xType) + '/div') : '\u2014';
|
const xDivLabel = formatEng(xDiv, divUnit(xType) + '/div');
|
||||||
const yDivLabel = yDiv !== null ? formatEng(yDiv, divUnit(yType) + '/div') : '\u2014';
|
const yDivLabel = formatEng(yDiv, divUnit(yType) + '/div');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="scope-skin-active">
|
<div className="scope-skin-active">
|
||||||
|
|||||||
@ -125,8 +125,10 @@
|
|||||||
|
|
||||||
/* uPlot chart fills the CRT screen */
|
/* uPlot chart fills the CRT screen */
|
||||||
.scope-skin-active .scope-screen .uplot {
|
.scope-skin-active .scope-screen .uplot {
|
||||||
position: absolute;
|
position: absolute !important;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Graticule overlay (10 horizontal × 8 vertical) ──────── */
|
/* ── Graticule overlay (10 horizontal × 8 vertical) ──────── */
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user