Skip to content

Commit afd7814

Browse files
committed
Enhance force simulation tuning features in FORCE_SIMULATION_TUNER and NodeCanvas
- Introduced layout scale and iteration presets in FORCE_SIMULATION_TUNER, allowing users to quickly adjust node spacing and solver passes for auto-layout. - Updated NodeCanvas to integrate new layout settings, including state management for layout scale and iteration presets, improving user control over graph layout. - Enhanced ForceSimulationModal with UI elements for selecting layout presets and adjusting scale multipliers, streamlining the tuning process. - Improved CSS styles for better visual organization of layout options in the simulation modal.
1 parent 6942513 commit afd7814

6 files changed

Lines changed: 443 additions & 93 deletions

File tree

FORCE_SIMULATION_TUNER.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ Interactive, draggable modal for tuning force-directed graph layout parameters i
1818
- **Reset**: Randomize node positions and restart from alpha=1.0
1919
- **Apply to Graph**: Apply the current positions to your actual Redstring graph
2020

21+
### Layout Scale & Iteration Presets (Auto-Layout Synced)
22+
- **Layout Scale Slider (0.6–2.4×)**: Scales node spacing, repulsion reach, and link targets. Stored globally so Auto-Layout/Test Graph share the exact spacing you tested.
23+
- **Scale Presets (Compact / Balanced / Spacious)**: Quick chips that reset the slider to tuned baselines and update the shared layout-scale preset.
24+
- **Iteration Presets (Fast / Balanced / Deep)**: Choose how many solver passes auto-layout should run. Selecting a preset also updates the cooling rate slider to match.
25+
- These values are saved in the Zustand store (`autoLayoutSettings`) and flow into `applyAutoLayoutToActiveGraph`, the Auto Graph generator, and the Force tuner itself.
26+
2127
### Tunable Parameters
2228

2329
#### Repulsion Strength (100-5000)

src/NodeCanvas.jsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ function NodeCanvas() {
177177
setTypeListMode: () => {},
178178
toggleEnableAutoRouting: () => {},
179179
setRoutingStyle: () => {},
180+
setCleanLaneSpacing: () => {},
181+
setLayoutScalePreset: () => {},
182+
setLayoutScaleMultiplier: () => {},
183+
setLayoutIterationPreset: () => {},
180184
deleteNodePrototype: () => {},
181185
deleteGraph: () => {}
182186
};
@@ -1168,6 +1172,9 @@ function NodeCanvas() {
11681172
const routingStyle = useGraphStore(state => state.autoLayoutSettings?.routingStyle || 'straight');
11691173
const manhattanBends = useGraphStore(state => state.autoLayoutSettings?.manhattanBends || 'auto');
11701174
const cleanLaneSpacing = useGraphStore(state => state.autoLayoutSettings?.cleanLaneSpacing || 24);
1175+
const layoutScalePreset = useGraphStore(state => state.autoLayoutSettings?.layoutScale || 'balanced');
1176+
const layoutScaleMultiplier = useGraphStore(state => state.autoLayoutSettings?.layoutScaleMultiplier ?? 1);
1177+
const layoutIterationPreset = useGraphStore(state => state.autoLayoutSettings?.layoutIterations || 'balanced');
11711178
const edgesMap = useGraphStore(state => state.edges);
11721179
const savedNodeIds = useGraphStore(state => state.savedNodeIds);
11731180
const savedGraphIds = useGraphStore(state => state.savedGraphIds);
@@ -2041,8 +2048,10 @@ function NodeCanvas() {
20412048
}));
20422049

20432050
const layoutOptions = {
2044-
...FORCE_LAYOUT_DEFAULTS,
2045-
preSimulate: true
2051+
layoutScale: layoutScalePreset,
2052+
layoutScaleMultiplier,
2053+
iterationPreset: layoutIterationPreset,
2054+
useExistingPositions: false
20462055
};
20472056

20482057
try {
@@ -2074,7 +2083,7 @@ function NodeCanvas() {
20742083
console.error('[AutoLayout] Failed to apply layout:', error);
20752084
alert(`Auto-layout failed: ${error.message}`);
20762085
}
2077-
}, [activeGraphId, baseDimsById, nodes, edges, storeActions, moveOutOfBoundsNodesInBounds, resetConnectionLabelCache]);
2086+
}, [activeGraphId, baseDimsById, nodes, edges, storeActions, moveOutOfBoundsNodesInBounds, resetConnectionLabelCache, layoutScalePreset, layoutScaleMultiplier, layoutIterationPreset]);
20782087

20792088
// Auto-correct out-of-bounds nodes on graph load
20802089
useEffect(() => {
@@ -12127,13 +12136,23 @@ function NodeCanvas() {
1212712136

1212812137
// Get fresh state - will be updated after graph creation if needed
1212912138
let storeState = useGraphStore.getState();
12139+
const mergedLayoutOptions = {
12140+
...options.layoutOptions,
12141+
layoutScale: layoutScalePreset,
12142+
layoutScaleMultiplier,
12143+
iterationPreset: layoutIterationPreset
12144+
};
12145+
const patchedOptions = {
12146+
...options,
12147+
layoutOptions: mergedLayoutOptions
12148+
};
1213012149

1213112150
const results = generateGraph(
1213212151
parsedData,
1213312152
targetGraphId,
1213412153
storeState,
1213512154
storeActions,
12136-
options,
12155+
patchedOptions,
1213712156
() => useGraphStore.getState() // Function to get fresh state
1213812157
);
1213912158

@@ -12162,6 +12181,12 @@ function NodeCanvas() {
1216212181
onClose={() => setForceSimModalVisible(false)}
1216312182
graphId={activeGraphId}
1216412183
storeActions={storeActions}
12184+
layoutScalePreset={layoutScalePreset}
12185+
layoutScaleMultiplier={layoutScaleMultiplier}
12186+
onLayoutScalePresetChange={storeActions.setLayoutScalePreset}
12187+
onLayoutScaleMultiplierChange={storeActions.setLayoutScaleMultiplier}
12188+
layoutIterationPreset={layoutIterationPreset}
12189+
onLayoutIterationPresetChange={storeActions.setLayoutIterationPreset}
1216512190
getNodes={() => hydratedNodes.map(n => {
1216612191
const dims = baseDimsById.get(n.id) || getNodeDimensions(n, false, null);
1216712192
return {

src/components/ForceSimulationModal.css

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,3 +221,60 @@
221221
font-weight: bold;
222222
}
223223

224+
.force-sim-preset-row {
225+
display: flex;
226+
flex-direction: column;
227+
gap: 8px;
228+
padding: 12px 12px 0;
229+
}
230+
231+
.force-sim-preset-label {
232+
font-size: 12px;
233+
text-transform: uppercase;
234+
letter-spacing: 0.5px;
235+
color: #ffb3b3;
236+
opacity: 0.8;
237+
}
238+
239+
.force-sim-chip-row,
240+
.force-sim-chip-group {
241+
display: flex;
242+
gap: 6px;
243+
flex-wrap: wrap;
244+
}
245+
246+
.force-sim-chip-group {
247+
grid-column: 1 / span 3;
248+
margin-top: 6px;
249+
}
250+
251+
.force-sim-chip {
252+
flex: 1;
253+
min-width: 90px;
254+
background: #2a0000;
255+
border: 1px solid #7A0000;
256+
border-radius: 999px;
257+
color: #bdb5b5;
258+
font-size: 12px;
259+
font-family: 'EmOne', sans-serif;
260+
padding: 6px 12px;
261+
cursor: pointer;
262+
display: flex;
263+
flex-direction: column;
264+
align-items: center;
265+
gap: 2px;
266+
transition: background 0.2s, border 0.2s, color 0.2s;
267+
}
268+
269+
.force-sim-chip small {
270+
font-size: 11px;
271+
color: #bdb5b5;
272+
opacity: 0.8;
273+
}
274+
275+
.force-sim-chip.active {
276+
background: #7A0000;
277+
color: #ffffff;
278+
border-color: #ff7070;
279+
}
280+

0 commit comments

Comments
 (0)