Closed bureauvanlieshout closed 1 year ago
Hi @bureauvanlieshout,
Thanks for using v-network-graph!
Currently there is no feature that will fulfill your request, but I will give you what I can think of as a workaround.
The following is an example that periodically checks the contents and adjusts the display to include all objects.
It is based on the "Position nodes with d3-force" in the docs, but the view.scalingObjects
config is set to true
for easy recognition of zoom changes.
<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, watch } from "vue";
import * as vNG from "v-network-graph";
import { ForceEdgeDatum, ForceLayout, ForceNodeDatum } from "v-network-graph/lib/force-layout";
const nodeCount = ref(20);
const nodes = reactive({});
const edges = reactive({});
const layouts = ref<vNG.Layouts>({ nodes: {} });
// initialize network
buildNetwork(nodeCount.value, nodes, edges);
watch(nodeCount, () => {
buildNetwork(nodeCount.value, nodes, edges);
});
const configs = reactive(
vNG.defineConfigs({
view: {
scalingObjects: true,
layoutHandler: new ForceLayout({
positionFixedByDrag: false,
positionFixedByClickWithAltKey: true,
createSimulation: (d3, nodes, edges) => {
const forceLink = d3.forceLink<ForceNodeDatum, ForceEdgeDatum>(edges).id((d: Node) => d.id)
return d3
.forceSimulation(nodes)
.force("edge", forceLink.distance(30))
.force("charge", d3.forceManyBody())
.force("collide", d3.forceCollide(30).strength(0.2))
.force("center", d3.forceCenter().strength(0.05))
.alphaMin(0.001)
}
}),
},
node: {
label: {
visible: true,
text: "label",
},
},
})
);
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}`, { label: `${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);
}
const graph = ref<vNG.VNetworkGraphInstance>();
const svg = ref<SVGSVGElement>();
const viewport = ref<SVGGElement>();
function adjustToDisplayTheWhole() {
if (!graph.value) return;
if (!svg.value || !viewport.value) return;
const outerRect = svg.value.getBoundingClientRect();
const innerRect = viewport.value.getBoundingClientRect();
if (
innerRect.x < outerRect.x ||
outerRect.right < innerRect.right ||
innerRect.y < outerRect.y ||
outerRect.bottom < innerRect.bottom
) {
if (
outerRect.width < innerRect.width ||
outerRect.height < innerRect.height
) {
// zoom and centering
graph.value.fitToContents();
} else {
// only panning.
// If you want to also zoom in to fit the size of the topology,
// use `fitToContents()` here too.
graph.value.panToCenter();
}
}
}
let timerId: ReturnType<typeof setInterval>;
onMounted(() => {
if (graph.value) {
svg.value = graph.value.$el.querySelector("svg");
viewport.value = graph.value.$el.querySelector(".svg-pan-zoom_viewport");
}
timerId = setInterval(adjustToDisplayTheWhole, 1000);
});
onUnmounted(() => {
clearInterval(timerId);
});
</script>
<template>
<div>
<label>Node count:</label>
<input v-model="nodeCount" type="number" :min="3" :max="200" />
</div>
<v-network-graph
class="graph"
ref="graph"
:nodes="nodes"
:edges="edges"
:layouts="layouts"
:configs="configs"
/>
</template>
<style>
.graph {
height: 500px;
border: 1px solid #000;
}
</style>
If you want to exclude adjustment when zoomed in by user operation, you might not make it a periodic process, but rather execute the adjustment process a few seconds after the number of nodes has changed.
I hope this information helps you. BTW, since this workaround requires touching the internal DOM, I would consider implementing and providing it as a feature in the future. Thanks for the suggestion!
I close this issue for now. If you have any other question/comment, please reopen this issue.
Hi dash14, I am using your fantastic v-network-graph with d3-force for positioning of nodes. I notice that sometimes nodes are pushed out of view. We use a fixed height (500px) for the graph and the width is flexible. Nodes disappear off the edge on the top and the bottom. I also see this happening in the docs example for Positioning nodes with d3 force. Would be great if nodes and their labels (our nodes have different sizes and labels at the bottom) would remain in view. Played around with many settings, but a ‘hard’ boundary or some form of auto-zoom to keep all items in view would be great. Thanks for the great work! Bart