There are various implementations of D3 with React. One of the more interesting ones uses the react-faux-dom project. Advantages to this approach are that React knows about DOM elements created by D3 and the ability to create isomorphic charts.
Refer to the following:
- http://oli.me.uk/2015/09/09/d3-within-react-the-right-way/
- https://github.com/Olical/react-faux-dom
- http://www.reactd3.org/
What would it take to implement D3 in Vue.js with the same benefits?
Is there a need to create something similar to react-faux-dom or does Vue already have something that can be used for this?
How does this approach make sense (or not) considering Vue’s architecture?
Since version 4, D3 is highly modularized and computational parts are well isolated in small librairies, such as d3-force. One approach I like is to let Vue.js handle DOM manipulation and events, and use d3.js for computations. Your visualization component is similar to your other components, and are easier to understand for someone familiar with Vue.js but not d3.js.
I created a codepen to show a force graph implementation :
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 => n.x)),
maxX: Math.max(...this.graph.nodes.map(n => n.x)),
minY: Math.min(...this.graph.nodes.map(n => n.y)),
maxY: Math.max(...this.graph.nodes.map(n => n.y))
}
},
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 I see is if you have a large d3.js codebase you want to reuse in your Vue.js application as you will have to rewrite it. You will also find a lot of examples written in pure d3.js syntax and you will have to adapt them.