Skip to content

Commit f68e1d9

Browse files
committed
Heatmap: live cutoff label + relocate animate toggle
- "Low cutoff: X.XX" label now follows the live preview during scrub (was stuck at the URL-committed value, since pointer-only hover never commits). Added a small React-state mirror of the preview ref — re-renders Controls per move but doesn't touch the 3D path (shader still reads the ref). - "Animate cutoff changes" toggle moved from the global Settings drawer into the Heatmap section right under the cutoff scrubber, where it belongs alongside the other heatmap controls.
1 parent 0330eeb commit f68e1d9

3 files changed

Lines changed: 30 additions & 18 deletions

File tree

pkgs/core/src/components/Controls.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,16 @@ interface ControlsProps {
9494
heatmapGamma?: number
9595
onHeatmapGammaChange?: (v: number) => void
9696
heatmapLowCutoff?: number
97+
/** Live cutoff for the "Low cutoff: X.XX" label — equals preview while
98+
* scrubbing, equals `heatmapLowCutoff` otherwise. The slider/histogram
99+
* positioning still uses `heatmapLowCutoff` (committed). */
100+
displayedHeatmapLowCutoff?: number
97101
onHeatmapLowCutoffChange?: (v: number) => void
98102
/** Transient low-cutoff preview while hovering the heatmap histogram. */
99103
onHeatmapLowCutoffPreview?: (v: number | null) => void
104+
/** Smooth-lerp the heatmap shader's cutoff between values, vs snapping. */
105+
heatmapCutoffAnim?: boolean
106+
onHeatmapCutoffAnimChange?: (v: boolean) => void
100107
/** Symmetric absolute-max for the heatmap histogram x-axis. Defaults to
101108
* `maxDensity` (unsigned data); diff mode passes `max(|min|, |max|)`. */
102109
dataAbsMax?: number
@@ -189,8 +196,11 @@ export function Controls({
189196
heatmapGamma = 2.5,
190197
onHeatmapGammaChange,
191198
heatmapLowCutoff = 0,
199+
displayedHeatmapLowCutoff,
192200
onHeatmapLowCutoffChange,
193201
onHeatmapLowCutoffPreview,
202+
heatmapCutoffAnim,
203+
onHeatmapCutoffAnimChange,
194204
dataAbsMax,
195205
heatmapStepCount = 256,
196206
onHeatmapStepCountChange,
@@ -436,7 +446,7 @@ export function Controls({
436446
<span title={isDiff
437447
? "Densities with |val| / dataAbsMax below this fraction contribute zero alpha"
438448
: "Densities below this fraction contribute zero alpha (hard threshold)"
439-
}>Low cutoff: {heatmapLowCutoff.toFixed(2)}</span>
449+
}>Low cutoff: {(displayedHeatmapLowCutoff ?? heatmapLowCutoff).toFixed(2)}</span>
440450
<button
441451
className={styles.resetBtn}
442452
onClick={() => onHeatmapLowCutoffChange(0)}
@@ -467,6 +477,16 @@ export function Controls({
467477
/>
468478
)}
469479
</div>
480+
{onHeatmapCutoffAnimChange && (
481+
<label className={styles.toggle} title="Smoothly lerp the cutoff between values vs snapping (more GPU work, less choppy)">
482+
<input
483+
type="checkbox"
484+
checked={!!heatmapCutoffAnim}
485+
onChange={e => onHeatmapCutoffAnimChange(e.target.checked)}
486+
/>
487+
Animate cutoff changes
488+
</label>
489+
)}
470490
<div className={styles.controlLabel}>
471491
<div className={styles.sliderHeader}>
472492
<span title="Ray-march sample count — higher is smoother but slower">Steps: {heatmapStepCount}</span>

pkgs/core/src/components/Settings.tsx

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -59,23 +59,6 @@ export function Settings({ settings, onUpdate, showCacheToggle = true, lineWidth
5959
/>
6060
MB
6161
</label>
62-
<label style={{
63-
display: 'flex',
64-
alignItems: 'center',
65-
gap: 8,
66-
fontSize: 13,
67-
color: '#ccc',
68-
cursor: 'pointer',
69-
marginTop: 6,
70-
}}>
71-
<input
72-
type="checkbox"
73-
checked={settings.heatmapCutoffAnim}
74-
onChange={e => onUpdate({ heatmapCutoffAnim: e.target.checked })}
75-
style={{ accentColor: '#4a9eff' }}
76-
/>
77-
Animate heatmap cutoff changes
78-
</label>
7962
{onLineWidthChange && (
8063
<label style={{
8164
display: 'flex',

pkgs/static/src/App.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,6 +1565,11 @@ export default function App() {
15651565
// and pushes it straight to the shader uniform — drag updates land in the
15661566
// 3D viz next frame (~16 ms) instead of after a full React reconciliation.
15671567
const heatmapLowCutoffPreviewRef = useRef<number | null>(null)
1568+
// Mirror of the preview ref as React state, used ONLY for the "Low cutoff:
1569+
// X.XX" label so it tracks the live scrub. The shader still reads the ref
1570+
// (no App re-render needed for the 3D path); this state's re-render only
1571+
// touches Controls.
1572+
const [displayedHeatmapLowCutoff, setDisplayedHeatmapLowCutoff] = useState<number | null>(null)
15681573
// Populated by `<InvalidateBridge>` inside the Canvas with R3F's `invalidate`.
15691574
// Wakes the render loop after a ref write so the shader paints next tick.
15701575
const invalidateRef = useRef<(() => void) | null>(null)
@@ -1586,6 +1591,7 @@ export default function App() {
15861591
}, [])
15871592
const setPreviewHeatmapLowCutoff = useCallback((v: number | null) => {
15881593
heatmapLowCutoffPreviewRef.current = v
1594+
setDisplayedHeatmapLowCutoff(v)
15891595
// Null = clear (hover-out, drag-release): flush immediately so the committed
15901596
// value snaps back without waiting on debounce.
15911597
if (v === null) { flushScrub(); return }
@@ -1927,8 +1933,11 @@ export default function App() {
19271933
heatmapGamma={heatmapGamma}
19281934
onHeatmapGammaChange={setHeatmapGamma}
19291935
heatmapLowCutoff={heatmapLowCutoff}
1936+
displayedHeatmapLowCutoff={displayedHeatmapLowCutoff ?? heatmapLowCutoff}
19301937
onHeatmapLowCutoffChange={setHeatmapLowCutoff}
19311938
onHeatmapLowCutoffPreview={setPreviewHeatmapLowCutoff}
1939+
heatmapCutoffAnim={settings.heatmapCutoffAnim}
1940+
onHeatmapCutoffAnimChange={v => updateSettings({ heatmapCutoffAnim: v })}
19321941
dataAbsMax={dataAbsMax}
19331942
heatmapStepCount={heatmapStepCount}
19341943
onHeatmapStepCountChange={setHeatmapStepCount}

0 commit comments

Comments
 (0)