Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 31
fix(graph): stop the compat engine's flow particles at flowSpeed=0#178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Uh oh!
There was an error while loading. Please reload this page.
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Jump to file
Failed to load files.
Loading
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -613,6 +613,25 @@ | ||
| const MAX_AUTO_FIT_ZOOM = 4; | ||
| const SETTINGS_ALPHA_TARGET = 0.12; | ||
| const ALPHA_TARGET_HOLD_MS = 180; | ||
| /* Inline utility: bound a value to [min, max]. The dashboard pipeline does not expose | ||
| a shared math helper, so this lives here alongside the spacetime tuners that need it. */ | ||
| function clamp(value, min, max) { | ||
| const n = Number(value); | ||
| if (!Number.isFinite(n)) return min; | ||
| return Math.max(min, Math.min(max, n)); | ||
| } | ||
| /* Mirror of graphBlackHoleMassMultiplier in ledger.js — kept inline so the d3-force | ||
| d3-install path in this file does not need to cross reference the ledger module. The | ||
| formula is identical: baseline 160 below which the multiplier is value/160, above which | ||
| it climbs linearly at 0.02/unit (so 500 -> 8.80, 1000 -> 21.80). */ | ||
| const GRAPH_BLACK_HOLE_MASS_BASELINE = 160; | ||
| function blackHoleMassMultiplier(controlValue) { | ||
| const value = Number(controlValue); | ||
| if (!Number.isFinite(value)) return 1; | ||
| return value <= GRAPH_BLACK_HOLE_MASS_BASELINE | ||
| ? Math.max(0, value / GRAPH_BLACK_HOLE_MASS_BASELINE) | ||
| : 1 + (value - GRAPH_BLACK_HOLE_MASS_BASELINE) * 0.02; | ||
| } | ||
| /* Physics is allowed to respond live, but one bad force update must never turn a | ||
| settled graph into a high-speed slingshot. Keep the bounds in world units so they | ||
| @@ -7968,15 +7987,32 @@ | ||
| charge = d3.forceManyBody(); | ||
| fg.d3Force('charge', charge); | ||
| } | ||
| if (charge && charge.strength) charge.strength(-(mode === 'communities' ? Math.max(10, s.repel * 0.68) : s.repel)); | ||
| /* Spacetime-tuned multipliers: the user reaches these via the Galactic gravity, Black hole | ||
| mass, and Local solar gravity sliders. In non-galaxy mode the d3-force simulator is the | ||
| only consumer, so the multipliers must reach the d3 forces directly. Each map is a | ||
| bounded monotonic curve so the user can move the slider from end to end and see the | ||
| intended effect on every node on the next tick. */ | ||
| const gravityMultiplier = clamp(Number(state.settings.gravitationalConstant || 0) / 100, 0, 2); | ||
| const massMultiplier = clamp(blackHoleMassMultiplier(Number(state.settings.blackHoleMass ?? 160)), 0.25, 4); | ||
| const localMultiplier = clamp(Number(state.settings.localGravitationalConstant || 0) / 100, 0, 2); | ||
| const baseRepel = mode === 'communities' ? Math.max(10, s.repel * 0.68) : s.repel; | ||
| if (charge && charge.strength) charge.strength(-baseRepel * gravityMultiplier); | ||
| if (link && link.distance) link.distance(s.link); | ||
| if (link && link.strength) link.strength(edge => { | ||
| const source = typeof edge.source === 'object' ? edge.source : layoutById.get(linkEndpoint(edge, 'source')); | ||
| const target = typeof edge.target === 'object' ? edge.target : layoutById.get(linkEndpoint(edge, 'target')); | ||
| return 1 / Math.max(1, Math.min( | ||
| const base = 1 / Math.max(1, Math.min( | ||
| source && source.degree || 1, target && target.degree || 1 | ||
| )); | ||
| return base * localMultiplier; | ||
| }); | ||
| /* velocityDecay is the d3 equivalent of the space-damping slider: high damping makes the | ||
| layout settle fast, low damping keeps nodes oscillating. Bounded 0.05..0.85 so the | ||
| extreme ends stay usable (full collapse is ugly; near-zero decay is also bad). */ | ||
| if (fg.velocityDecay) { | ||
| const damping = clamp(Number(state.settings.damping ?? 1), 1, 15); | ||
| fg.velocityDecay(0.05 + (damping - 1) * (0.80 / 14)); | ||
| } | ||
| if (typeof d3 === 'undefined') { | ||
| installVelocityGuard(); | ||
| return; | ||
| @@ -8007,15 +8043,16 @@ | ||
| }); | ||
| /* A gentle origin-based centering keeps the layout coherent without fighting a | ||
| drag; the community grid is still visible through the charge/repel and link | ||
| structure installed above. */ | ||
| const centering = Math.max(0.04, (Number(s.gravity) || 0) / 100); | ||
| structure installed above. Black-hole mass multiplies the centering strength so | ||
| the slider visibly pulls nodes toward the origin. */ | ||
| const centering = Math.max(0.04, (Number(s.gravity) || 0) / 100) * massMultiplier; | ||
| fg.d3Force('x', d3.forceX(0).strength(centering)); | ||
| fg.d3Force('y', d3.forceY(0).strength(centering)); | ||
| } else if (mode === 'radial' && d3.forceRadial) { | ||
| const outerRadius = Math.max(180, Math.min(360, Math.sqrt(Math.max(1, layoutNodes.length)) * 18 + (Number(s.link) || 16) * 4)); | ||
| const degreeScale = Math.max(1, maxOf(layoutNodes.map(node => node.degree || 0), 1)); | ||
| fg.d3Force('x', d3.forceX(0).strength(Math.max(0.05, (Number(s.gravity) || 0) / 500))); | ||
| fg.d3Force('y', d3.forceY(0).strength(Math.max(0.05, (Number(s.gravity) || 0) / 500))); | ||
| fg.d3Force('x', d3.forceX(0).strength(Math.max(0.05, (Number(s.gravity) || 0) / 500) * massMultiplier)); | ||
| fg.d3Force('y', d3.forceY(0).strength(Math.max(0.05, (Number(s.gravity) || 0) / 500) * massMultiplier)); | ||
| fg.d3Force('radial', d3.forceRadial(node => { | ||
| const hubness = Math.max(0, Math.min(1, (node.degree || 0) / degreeScale)); | ||
| return 34 + (outerRadius - 34) * (1 - hubness); | ||
| @@ -9320,10 +9357,17 @@ | ||
| fg.linkDirectionalArrowLength(dense ? 0 : 0.625).linkDirectionalArrowRelPos(1); | ||
| applyLinkLabels(); | ||
| if (fg.linkDirectionalParticles) { | ||
| const flowSpeed = Number(state.settings.flowSpeed); | ||
| /* flowSpeed=0 means "stop" — particles must not render at all. The every-node engine | ||
| already enforces this via a `moving = speed > 0` check; the compat engine must do | ||
| the same. Otherwise the slider visibly does nothing at the low end (particles keep | ||
| crawling at the residual 0.002 floor). */ | ||
| const flowActive = Number.isFinite(flowSpeed) ? flowSpeed > 0 : true; | ||
Coding-Dev-Tools marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| const flowing = !fullGraph | ||
| && state.settings.flow !== false | ||
| && motion | ||
| && !reducedMotion | ||
| && flowActive | ||
| && data.links.length <= PARTICLE_LINK_LIMIT; | ||
| const particles = !flowing | ||
| ? 0 | ||
| @@ -9332,7 +9376,7 @@ | ||
| .linkDirectionalParticleWidth(1) | ||
| .linkDirectionalParticleCanvasObject(paintFlowArrow) | ||
| .linkDirectionalParticleColor(l => alpha(layerColor(l.layer), 0.95)) | ||
| .linkDirectionalParticleSpeed(l => 0.002 + ((state.settings.flowSpeed || 45) / 100) * 0.008); | ||
| .linkDirectionalParticleSpeed(l => flowActive ? (0.002 + (flowSpeed / 100) * 0.008) : 0); | ||
| } | ||
| if (!galaxyMode && reheat && motion && !staticFullLayout && !state.settings.frozen) { | ||
| prepareReheat(); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.