Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement D3 for Vue.js

Tags:

vue.js

d3.js

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?

like image 317
Mark Tucker Avatar asked Dec 09 '16 18:12

Mark Tucker


People also ask

Does d3 work with Vue?

A customizable component for adding D3 charts that binds to your components data. Easily bind a chart to the data stored in your Vue.

How do I import d3 into JavaScript?

Install D3 by running npm install d3 --save . Import D3 to App. js by adding import * as d3 from d3 . You need to use import * (“import everything”) since D3 has no default exported module.


1 Answers

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.

like image 194
Nicolas Bonnel Avatar answered Oct 08 '22 19:10

Nicolas Bonnel