dash14 / v-network-graph

An interactive network graph visualization component for Vue 3
https://dash14.github.io/v-network-graph/
MIT License
485 stars 44 forks source link

Re-loading D3 Graph #88

Closed jjackevans closed 1 year ago

jjackevans commented 1 year ago

Hi,

I have a graph using D3 to create a layout. When the edges change, I would like the entire graph to re-render the layout. Currently the layout does not adjust when the underlying data does.

Could you suggest how I might achieve that?

Thanks!

dash14 commented 1 year ago

Hi @jjackevans, I implemented the following example as a tryout d3 force layout.

<script setup lang="ts">
import { reactive, ref, watch } from "vue";
import * as vNG from "v-network-graph";
import { ForceLayout } from "v-network-graph/lib/force-layout";

const NODE_COUNT = 20
const nodeCount = ref(NODE_COUNT);
const nodes = reactive<vNG.Nodes>({});
const edges = reactive<vNG.Edges>({});

// ref: https://dash14.github.io/v-network-graph/examples/layout.html#position-nodes-with-d3-force

// initialize network
buildNetwork(nodeCount.value, nodes, edges);

watch(nodeCount, () => {
  buildNetwork(nodeCount.value, nodes, edges);
});

const configs = reactive(
  vNG.defineConfigs({
    view: {
      layoutHandler: new ForceLayout({
        positionFixedByDrag: false,
        positionFixedByClickWithAltKey: true,
      }),
    },
    node: {
      label: {
        visible: false,
      },
    },
  })
);

function buildNetwork(count: number, nodes: vNG.Nodes, edges: vNG.Edges) {
  const idNums = [...Array(count)].map((_, i) => i);

  // nodes
  const newNodes = Object.fromEntries(idNums.map((id) => [`node${id}`, {}]));
  Object.keys(nodes).forEach((id) => delete nodes[id]);
  Object.assign(nodes, newNodes);

  // edges
  const makeEdgeEntry = (id1: number, id2: number) => {
    return [
      `edge${id1}-${id2}`,
      { source: `node${id1}`, target: `node${id2}` },
    ];
  };
  const newEdges = Object.fromEntries([
    ...idNums
      .map((n) => [n, (Math.floor(n / 5) * 5) % count])
      .map(([n, m]) => (n === m ? [n, (n + 5) % count] : [n, m]))
      .map(([n, m]) => makeEdgeEntry(n, m)),
  ]);
  Object.keys(edges).forEach((id) => delete edges[id]);
  Object.assign(edges, newEdges);
}

// Add edges between randomly selected nodes
function addEdge() {
  let i = Math.floor(Math.random() * NODE_COUNT);
  let j = Math.floor(Math.random() * NODE_COUNT);
  do {
    i = Math.floor(Math.random() * NODE_COUNT);
    j = Math.floor(Math.random() * NODE_COUNT);
  } while (i == j || `edge${i}-${j}` in edges || `edge${j}-${i}` in edges)
  edges[`edge${i}-${j}`] = { source: `node${i}`, target: `node${j}` };
}
</script>

<template>
  <div>
    <button @click="addEdge">Add an edge</button>
  </div>

  <v-network-graph
    :nodes="nodes"
    :edges="edges"
    :configs="configs"
  />
</template>

When clicking Add an edge, the edge is added and the force layout also seems to be reacting and recalculating. Is this what you would like to achieve? If not, please show a minimum sample code and I may be able to reply with a more specific answer.

dash14 commented 1 year ago

Since some time has elapsed, I close this issue for now. If there are additional questions, please reopen this issue.

piliadis commented 1 month ago

Hi there @dash14 ! I have a similar question.

I'm using a force layout and have saved a graph layout in the backend. When the user returns to the graph, I want to display it exactly as they left it, so they can continue their work from where they left off. However, when I try to do this, the simulation creates a new layout.

I assume this happens because I'm adding each node and layout successively.

So, my basic question is: how can I initialize a graph layout with a stored "steady-state" layout, without running the simulation again?

Thank you in advance!