v-network-graph icon indicating copy to clipboard operation
v-network-graph copied to clipboard

Re-loading D3 Graph

Open jjackevans opened this issue 3 years ago • 1 comments

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!

jjackevans avatar Sep 18 '22 10:09 jjackevans

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 avatar Sep 20 '22 15:09 dash14

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

dash14 avatar Oct 22 '22 15:10 dash14

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!

piliadis avatar Jun 22 '24 12:06 piliadis