Support `tick` on `deactivate` for ForceLayout
adrianlungu opened this issue · comments
Hello!
Was looking into using the tick
function for D3 Force in order to render a static graph at a specific tick similar to the demo here:
https://observablehq.com/@d3/simulation-tick?collection=@d3/d3-force
While taking a look at the library, I saw it has a deactivate
function which essentially stops the forceSimulation
, was thinking we could add a default null
variable that can be set to the tick
for a .tick(x)
call which would allow the deactivation to be done to a specific tick.
I'm thinking of making a quick MR for this, please let me know what you think!
Hi @adrianlungu,
Thank you for your suggestion!
I have made the necessary modifications to manually specify the d3-force ticks and perform the initial rendering.
v0.9.2
However, I am not confident that this will accomplish what you need. If there are any shortcomings, MR is most welcome!!
The following is an example of how a static graph layout is calculated with d3-force and then switched to the default layout thereafter.
<script setup lang="ts">
import { reactive } from "vue";
import * as vNG from "v-network-graph";
import {
ForceLayout,
ForceNodeDatum,
ForceEdgeDatum,
} from "v-network-graph/lib/force-layout";
import data from "./data"
const defaultConfigs = vNG.defineConfigs({
view: {
autoPanAndZoomOnLoad: "fit-content",
layoutHandler: new ForceLayout({
createSimulation: (d3, nodes, edges) => {
const forceLink = d3
.forceLink<ForceNodeDatum, ForceEdgeDatum>(edges)
.id((d: ForceNodeDatum) => d.id);
return d3
.forceSimulation(nodes)
.force("edge", forceLink.distance(150).strength(1))
.force("charge", d3.forceManyBody().strength(-1000))
.force("x", d3.forceX())
.force("y", d3.forceY())
.stop() // tick manually
.tick(100);
},
}),
},
node: {
label: {
visible: false,
},
},
});
const configs = reactive(defaultConfigs);
const eventHandlers: vNG.EventHandlers = {
"view:load": () => {
// Switch to default layout handler after initial layout calculation.
configs.view.layoutHandler = new vNG.SimpleLayout();
},
};
</script>
<template>
<v-network-graph
:nodes="data.nodes"
:edges="data.edges"
:configs="configs"
:event-handlers="eventHandlers"
/>
</template>
<style lang="scss" scoped>
.v-network-graph {
width: 600px;
height: 400px;
border: 1px solid #aaa;
}
</style>
// data.ts
import { Nodes, Edges } from "v-network-graph"
const nodes: Nodes = {
node0: {},
node1: {},
node2: {},
node3: {},
node4: {},
node5: {},
node6: {},
node7: {},
node8: {},
node9: {},
node10: {},
node11: {},
node12: {},
node13: {},
node14: {},
node15: {},
node16: {},
node17: {},
node18: {},
node19: {},
}
const edges: Edges = {
"edge0-5": { source: "node0", target: "node5" },
"edge1-0": { source: "node1", target: "node0" },
"edge2-0": { source: "node2", target: "node0" },
"edge3-0": { source: "node3", target: "node0" },
"edge4-0": { source: "node4", target: "node0" },
"edge5-10": { source: "node5", target: "node10" },
"edge6-5": { source: "node6", target: "node5" },
"edge7-5": { source: "node7", target: "node5" },
"edge8-5": { source: "node8", target: "node5" },
"edge9-5": { source: "node9", target: "node5" },
"edge10-15": { source: "node10", target: "node15" },
"edge11-10": { source: "node11", target: "node10" },
"edge12-10": { source: "node12", target: "node10" },
"edge13-10": { source: "node13", target: "node10" },
"edge14-10": { source: "node14", target: "node10" },
"edge15-0": { source: "node15", target: "node0" },
"edge16-15": { source: "node16", target: "node15" },
"edge17-15": { source: "node17", target: "node15" },
"edge18-15": { source: "node18", target: "node15" },
"edge19-15": { source: "node19", target: "node15" },
}
export default {
nodes,
edges,
}
Awesome! I'll give it a shot very soon! Thanks @dash14
You're right, I was not able to get it to work as expected.
Trying to rebuild the demo from d3-force and changing the tick
via some kind of input yielded various interesting results.
I was able to get something up and running by doing the following changes
This allows me to call
'view:load': () => {
nextTick().then(() => {
forceLayout.ticked(175)
})
},
and even change the tick by calling ticked
afterwards with other values, although it does not behave exactly like the d3-force
demo for some reason.
I also couldn't get it to load properly in the view:load
event on my end, not sure why though.
What do you think ? Should I make a MR although I'm not confident that my changes are the best implementation for this.
Thanks for trying it so quickly!
I have checked and experimented with the d3-force demo you provided to see if it can be replicated in v-network-graph.
First, the initialization of node positions had different behavior in d3-force and v-network-graph, which has been corrected.
I am extremely grateful to have found this difference!
v0.9.3
And, in the d3-force demo, when the value of ticks is changed, the node coordinates are reset and the simulation object is recreated. In v-network-graph this seems to be equivalent to clearing the layouts
and recreating the ForceLayout
.
Certainly, the simulation is also recreated by calling deactivate() -> activate(). However, since this is not the original role of those functions, I thought it would be simpler to recreate the simulation object outside the library along with clearing the layouts
.
I wrote code that replicated the d3-force demo using this approach. It is not a perfect replication of the look and feel, though.
https://codesandbox.io/p/sandbox/v-network-graph-d3-force-tick-example-t9l4vv?file=%2Fsrc%2FApp.vue%3A
I hope this has gotten you closer to what you want to do. Please feel free to point out any shortcomings!
Awesome, thanks for the update @dash14
I'm still getting a few weird artifacts on my end, not sure why though, I'll try to build a vue component to extract the logic I'm using and have a demo up and running.
It also seems that once I reset the nodes using layouts.value = { nodes: {} }
the actual nodes disappear from the graph on my end. 🤔
The rest seems to be working as expected!
I am still unable to reproduce the problem on my end.
It has been a long time, but if you are still suffering from the same problem, I would appreciate it if you could provide me with the minimal code where the problem occurs, so that I can analyze the problem.
Best regards
Hey @dash14 ,
Been caught with other stuff lately, I'll try to setup a demo asap
I tried to reproduce the issue that I'm having locally in another environment but it seems that I can't get it to work exactly as it does in the project I'm using it when isolating just the v-network-graph
, so there may be more to it than this.
Using nextTick
does solve the issue I'm having so, if I manage to identify the root cause, I'll update this ticket or open a new one, but for now I think we can close this one as the updates you've done already improved the behavior alot.
Thanks again @dash14 !