dash14 / v-network-graph

An interactive network graph visualization component for Vue 3

Home Page:https://dash14.github.io/v-network-graph/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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,
}

screenshot

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 !