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)
本文暂时没有评论,来添加一个吧(●'◡'●)