Relax during stretching and slow down

This commit is contained in:
Jack Kingsman
2026-01-19 17:32:53 -08:00
parent ff6b25c0e5
commit 00c4cfcdf0
4 changed files with 75 additions and 56 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -13,7 +13,7 @@
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
<script type="module" crossorigin src="/assets/index-Pr4PQ17F.js"></script>
<script type="module" crossorigin src="/assets/index-CQVFPi-8.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Bg6UtK9Z.css">
</head>
<body>
+39 -20
View File
@@ -734,51 +734,73 @@ function useVisualizerData({
}, [dimensions]);
// Expand to high repulsion, hold, then contract back
// Also weakens link force during expansion so nodes can actually separate
const expandContract = useCallback(() => {
const sim = simulationRef.current;
if (!sim) return;
const startStrength = chargeStrength;
const peakStrength = -5000;
const expandDuration = 1000; // 1 second
const holdDuration = 2000; // 2 seconds
const contractDuration = 1000; // 1 second
const startChargeStrength = chargeStrength;
const peakChargeStrength = -5000;
const startLinkStrength = 0.3;
const minLinkStrength = 0.02; // Nearly disable links during expansion
const expandDuration = 1000;
const holdDuration = 2000;
const contractDuration = 1000;
const startTime = performance.now();
const animate = (now: number) => {
const elapsed = now - startTime;
let currentStrength: number;
let currentChargeStrength: number;
let currentLinkStrength: number;
if (elapsed < expandDuration) {
// Expanding phase: lerp from start to peak
// Expanding: ramp up repulsion, weaken links
const t = elapsed / expandDuration;
currentStrength = startStrength + (peakStrength - startStrength) * t;
currentChargeStrength =
startChargeStrength + (peakChargeStrength - startChargeStrength) * t;
currentLinkStrength = startLinkStrength + (minLinkStrength - startLinkStrength) * t;
} else if (elapsed < expandDuration + holdDuration) {
// Hold phase: stay at peak
currentStrength = peakStrength;
// Hold: stay at peak repulsion, links weak
currentChargeStrength = peakChargeStrength;
currentLinkStrength = minLinkStrength;
} else if (elapsed < expandDuration + holdDuration + contractDuration) {
// Contracting phase: lerp from peak back to start
// Contracting: restore both forces
const t = (elapsed - expandDuration - holdDuration) / contractDuration;
currentStrength = peakStrength + (startStrength - peakStrength) * t;
currentChargeStrength = peakChargeStrength + (startChargeStrength - peakChargeStrength) * t;
currentLinkStrength = minLinkStrength + (startLinkStrength - minLinkStrength) * t;
} else {
// Done - restore original
// Done - restore originals
sim.force(
'charge',
forceManyBody<GraphNode>()
.strength((d) => (d.id === 'self' ? startStrength * 6 : startStrength))
.strength((d) => (d.id === 'self' ? startChargeStrength * 6 : startChargeStrength))
.distanceMax(500)
);
sim.force(
'link',
forceLink<GraphNode, GraphLink>(Array.from(linksRef.current.values()))
.id((d) => d.id)
.distance(80)
.strength(startLinkStrength)
);
sim.alpha(0.3).restart();
return;
}
// Apply current strength
// Apply current strengths
sim.force(
'charge',
forceManyBody<GraphNode>()
.strength((d) => (d.id === 'self' ? currentStrength * 6 : currentStrength))
.strength((d) => (d.id === 'self' ? currentChargeStrength * 6 : currentChargeStrength))
.distanceMax(500)
);
sim.force(
'link',
forceLink<GraphNode, GraphLink>(Array.from(linksRef.current.values()))
.id((d) => d.id)
.distance(80)
.strength(currentLinkStrength)
);
sim.alpha(0.5).restart();
requestAnimationFrame(animate);
@@ -961,7 +983,7 @@ export function PacketVisualizer({
const [filterOldRepeaters, setFilterOldRepeaters] = useState(false);
const [observationWindowSec, setObservationWindowSec] = useState(DEFAULT_OBSERVATION_WINDOW_SEC);
const [letEmDrift, setLetEmDrift] = useState(true);
const [particleSpeedMultiplier, setParticleSpeedMultiplier] = useState(3);
const [particleSpeedMultiplier, setParticleSpeedMultiplier] = useState(2);
const [hideUI, setHideUI] = useState(false);
// Pan/zoom
@@ -1189,9 +1211,6 @@ export function PacketVisualizer({
<>
<div>Nodes: {data.stats.nodes}</div>
<div>Links: {data.stats.links}</div>
<div className="text-muted-foreground">
Processed: {data.stats.processed} | Animated: {data.stats.animated}
</div>
<div className="border-t border-border pt-2 mt-1 flex flex-col gap-2">
<label className="flex items-center gap-2 cursor-pointer">
<Checkbox