Skip to content
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

Expose on:tick from ForceSimulation to allow for dynamic simulation tuning #198

Closed
regexident opened this issue Jun 19, 2024 · 1 comment · Fixed by #205 or #209
Closed

Expose on:tick from ForceSimulation to allow for dynamic simulation tuning #198

regexident opened this issue Jun 19, 2024 · 1 comment · Fixed by #205 or #209

Comments

@regexident
Copy link
Contributor

A common technique used to keep force-directed graphs with collision forces butter-smooth is collision easing:
Slowly ramping up the collide force's strength from an initial 0 to the desired end value by updating it on each tick().

This way the layout has a chance to very quickly settle on a "pretty good yet possibly overlapping" layout before turning up the collide force for nudging the nodes to their final positions, while resolving any remaining overlaps.

By adding tick event like …

type TickEvent = CustomEvent<{
    alpha: number;
    // Any other values worth providing here?
}>;

… and a corresponding on:tick to ForceSimulation one would be able to implement collision easing (or any other dynamic simulation fine-tuning for that matter) along these (pseudo-code) lines, by coupling the weighted strength to the simulation's current alpha value (or any other timing value available to the component itself):

<script lang="ts">

import * as d3 from 'd3';

import { writable, type Writable } from 'svelte/store';

const collideStrengthTarget: number = 1.0;

let forces: Writable<Record<string, d3.Force<any, any>>> = writable({
    link: d3.forceLink(links).distance(0).strength(1.0),
    charge: d3.forceManyBody().strength(-50.0),
    collide: d3.forceCollide().strength(0.0),
    x: d3.forceX(),
    y: d3.forceY()
});

function handleTick(event: TickEvent) {
    const weight = d3.easeQuadInOut(1.0 - event.alpha);
    // I'm assuming here that ForceSimulation's force objects have reference semantics:
    forces.update((forces) => forces.collide.strength(weight * collideStrengthTarget));
}
</script>

<ForceSimulation
    {forces}
    ...
    on:tick={handleTick}
>
    <!-- ... -->
</ForceSimulation>
@techniq
Copy link
Owner

techniq commented Jun 19, 2024

This makes sense to me, but I would love to see an example of this in action (or even a PR 😁)

Updating forces should reactively update (see Force Group example which does something similar). I also have a good bit of additional reference examples in issue #139, but I haven't seen this kind of tick/easing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
2 participants