Starting with version 4, D3 is highly modular, and the computing parts are well isolated by small librairies, such as d3-force . One approach that I like is to let Vue.js handle DOM and event manipulation and use d3.js for computation. The visualization component is similar to your other components and is easier to understand for someone familiar with Vue.js but not d3.js.
I created a codepen to show the implementation of a power graph:
HTML:
<div id="app"> <svg xmlns="http://www.w3.org/2000/svg" :width="width+'px'" :height="height+'px'" @mousemove="drag($event)" @mouseup="drop()" v-if="bounds.minX"> <line v-for="link in graph.links" :x1="coords[link.source.index].x" :y1="coords[link.source.index].y" :x2="coords[link.target.index].x" :y2="coords[link.target.index].y" stroke="black" stroke-width="2"/> <circle v-for="(node, i) in graph.nodes" :cx="coords[i].x" :cy="coords[i].y" :r="20" :fill="colors[Math.ceil(Math.sqrt(node.index))]" stroke="white" stroke-width="1" @mousedown="currentMove = {x: $event.screenX, y: $event.screenY, node: node}"/> </svg> </div>
Javascript:
new Vue({ el: '#app', data: { graph: { nodes: d3.range(100).map(i => ({ index: i, x: null, y: null })), links: d3.range(99).map(i => ({ source: Math.floor(Math.sqrt(i)), target: i + 1 })) }, width: Math.max(document.documentElement.clientWidth, window.innerWidth || 0), height: Math.max(document.documentElement.clientHeight, window.innerHeight || 0) - 40, padding: 20, colors: ['#2196F3', '#E91E63', '#7E57C2', '#009688', '#00BCD4', '#EF6C00', '#4CAF50', '#FF9800', '#F44336', '#CDDC39', '#9C27B0'], simulation: null, currentMove: null }, computed: { bounds() { return { minX: Math.min(...this.graph.nodes.map(n => nx)), maxX: Math.max(...this.graph.nodes.map(n => nx)), minY: Math.min(...this.graph.nodes.map(n => ny)), maxY: Math.max(...this.graph.nodes.map(n => ny)) } }, coords() { return this.graph.nodes.map(node => { return { x: this.padding + (node.x - this.bounds.minX) * (this.width - 2*this.padding) / (this.bounds.maxX - this.bounds.minX), y: this.padding + (node.y - this.bounds.minY) * (this.height - 2*this.padding) / (this.bounds.maxY - this.bounds.minY) } }) } }, created(){ this.simulation = d3.forceSimulation(this.graph.nodes) .force('charge', d3.forceManyBody().strength(d => -100)) .force('link', d3.forceLink(this.graph.links)) .force('x', d3.forceX()) .force('y', d3.forceY()) }, methods: { drag(e) { if (this.currentMove) { this.currentMove.node.fx = this.currentMove.node.x - (this.currentMove.x - e.screenX) * (this.bounds.maxX - this.bounds.minX) / (this.width - 2 * this.padding) this.currentMove.node.fy = this.currentMove.node.y -(this.currentMove.y - e.screenY) * (this.bounds.maxY - this.bounds.minY) / (this.height - 2 * this.padding) this.currentMove.x = e.screenX this.currentMove.y = e.screenY } }, drop(){ delete this.currentMove.node.fx delete this.currentMove.node.fy this.currentMove = null this.simulation.alpha(1) this.simulation.restart() } } })
The main drawback that I see is that you have a large d3.js database that you want to reuse in your Vue.js application, since you will have to rewrite it. You will also find many examples written in pure d3.js syntax, and you will have to adapt them.