How to implement D3 for Vue.js

后端 未结 1 1509
小鲜肉
小鲜肉 2021-01-05 07:03

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

相关标签:
1条回答
  • 2021-01-05 07:44

    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.

    0 讨论(0)
提交回复
热议问题