编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

D3.js结合vue实现力导向布局

wxchong 2024-06-13 03:28:31 开源技术 8 ℃ 0 评论

D3是一个数据可视化的JavaScript函数库,允许用户绑定任意数据到DOM,然后根据数据来操作文档,创建可交互式的图表。Vue是一个响应式的前端框架,能够将数据渲染到DOM并抽象出复杂的逻辑。看起来两个库都能完成数据到DOM的渲染,结合起来可能会出现使用逻辑上的冲突,故尝试D3做数据处理,Vue做数据响应式处理来解决此冲突。

现在完成如下示例:



首先不使用Vue的模板操作,直接使用常规D3.js的Dom操作,包括数据处理、数据显示。

<script>
import * as d3 from 'd3'
export default {
  name: 'demo',
  template: '<div></div>',
  data () {
    return {
      width: 600,
      height: 400,
      nodes: [],
      links: []
    }
  },
  mounted () {
    const nodes = [
      {id: 1, name: '福州'},
      {id: 2, name: '宁德'},
      {id: 3, name: '莆田'},
      {id: 4, name: '泉州'},
      {id: 5, name: '厦门'},
      {id: 6, name: '漳州'},
    ];
    const links = [
      {source: 1, target: 2},
      {source: 1, target: 3},
      {source: 1, target: 4},
      {source: 3, target: 4},
      {source: 4, target: 5},
      {source: 4, target: 6},
      {source: 5, target: 6},
    ];
    const svg = d3.select(this.$el)
      .append('svg')
      .attr('width', this.width)
      .attr('height', this.height)
    const link = svg.selectAll('line')
      .data(links)
      .enter()
      .append('line')
      .attr('stroke', 'red')
    const node = svg.selectAll('circle')
      .data(nodes)
      .enter()
      .append('circle')
      .attr('fill', 'blue')
      .attr('r', '20')
    const text = svg.selectAll('text')
      .data(nodes)
      .enter()
      .append('text')
      .attr('dx', 0)
      .attr('dy', 5)
      .attr('fill', 'white')
      .attr('text-anchor', 'middle')
      .text(d => d.name)
    
    const simulation = d3.forceSimulation(nodes)
      .force('charge', d3.forceManyBody().strength(-100))
      .force("link", d3.forceLink(links).id(d => d.id).distance(50))
      .force("center", d3.forceCenter(this.width / 2, this.height / 2))
      .on('tick', () => {
        node.attr('cx', d => d.x).attr('cy', d => d.y)
        text.attr('x', d => d.x).attr('y', d => d.y)
        link.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
          .attr('x2', d => d.target.x).attr('y2', d => d.target.y)
      })
  }
}
</script>

从代码中可以看出,在不使用模板操作情况下,视图显示操作和逻辑处理其实是混在一起的,现在优化下代码:

<template>
  <div class="container" style="cursor: pointer">
    <svg ref="svg" :width="width" :height="height">
      <g>
        <line
          v-for="(link, i) in links"
          :key="i"
          :x1="link.x1"
          :x2="link.x2"
          :y1="link.y1"
          :y2="link.y2"
          style="stroke: red"
        ></line>
      </g>
      <g class="node"
        style="fill: blue; cursor: pointer;"
        :key="node.id"
        v-for="node in nodes">
        <circle
          :cx="node.x"
          :cy="node.y"
          r="20"
        >
        </circle>
        <text 
          :x="node.x"
          :y="node.y"
          dx="0" dy="5"
          fill="white"
          text-anchor="middle">
          {{node.name}}
        </text>
      </g>
    </svg>
  </div>
</template>

<script>
import * as d3 from 'd3'
export default {
  name: 'demo',
  data () {
    return {
      width: 600,
      height: 400,
      nodes: [
        {id: 1, name: '福州'},
        {id: 2, name: '宁德'},
        {id: 3, name: '莆田'},
        {id: 4, name: '泉州'},
        {id: 5, name: '厦门'},
        {id: 6, name: '漳州'},
      ],
      links: [
        {source: 1, target: 2},
        {source: 1, target: 3},
        {source: 1, target: 4},
        {source: 3, target: 4},
        {source: 4, target: 5},
        {source: 4, target: 6},
        {source: 5, target: 6},
      ],
      simulation: ''
    }
  },
  mounted () {
    this.simulation = d3.forceSimulation(this.nodes)
      .force('charge', d3.forceManyBody().strength(-100))
      .force("link", d3.forceLink(this.links).id(d => d.id).distance(50))
      .force("center", d3.forceCenter(this.width / 2, this.height / 2))
      .on('tick', () => {
        this.nodes = this.nodes.map(v => v)
        this.links = this.links.map(v => ({
          ...v, x1: v.source.x, y1: v.source.y,
          x2: v.target.x, y2: v.target.y
        }))
      })
  }
}
</script>

把对视图操作部分移植到Vue的模板中,很好的利用的Vue的数据响应特性,同时用户交互很容易就能运用到Vue的事件流:

<g class="node"
        style="fill: blue; cursor: pointer;"
        :key="node.id"
        @click="show(node)"
        v-for="node in nodes">
        ....
</g>
....
methods: {
    show(node) {
      alert(node.name)
    }
}
....

同时D3内部也提供了方便灵活并且抽象的拖拽交互模块 d3-drag

const node = d3.select(this.$refs.svg).selectAll('.node').data(this.nodes)
const drag = d3.drag().on('start', d => {
    if (!d3.event.active) {
      this.simulation.alphaTarget(0.4).restart();
    }
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }).on('drag', d => {
    d.fx = d3.event.x;
    d.fy = d3.event.y;
  }).on('end', d => {
    if (!d3.event.active) {
      this.simulation.alphaTarget(0);
    }
    d.fx = null
    d.fy = null
  })
  node.call(drag)

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表