add NodeMap component that display an interactive text relation tree + test route
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<svg width="100%" height="100%" ref="svg">
|
||||
<path
|
||||
v-for="(item, index) in links"
|
||||
:key="`link_${index}`"
|
||||
:d="lineGenerator([item.source, item.target])"
|
||||
stroke="black"
|
||||
/>
|
||||
|
||||
<circle
|
||||
v-for="(node, index) in nodes"
|
||||
:key="index"
|
||||
:cx="node.x"
|
||||
:cy="node.y"
|
||||
:fill="getNodeFill(node)"
|
||||
:r="5"
|
||||
>
|
||||
<title>{{ node.data.name }}</title>
|
||||
</circle>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { hierarchy } from 'd3-hierarchy'
|
||||
import { line } from 'd3-shape'
|
||||
import { selectAll } from 'd3-selection'
|
||||
import { drag } from 'd3-drag'
|
||||
import {
|
||||
forceSimulation, forceLink, forceManyBody,
|
||||
forceX, forceY, forceCenter
|
||||
} from 'd3-force'
|
||||
|
||||
|
||||
export default {
|
||||
/*
|
||||
This component uses vue as the svg renderer and the rest is handle with d3 functions
|
||||
(events, data computing, etc.)
|
||||
*/
|
||||
name: 'NodeMap',
|
||||
|
||||
props: {
|
||||
data: { type: Object, required: true }
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
width: 100,
|
||||
height: 100,
|
||||
h: hierarchy({}),
|
||||
simulation: forceSimulation(),
|
||||
lineGenerator: line().x(node => node.x).y(node => node.y)
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
nodes () {
|
||||
return this.h.descendants()
|
||||
},
|
||||
|
||||
links () {
|
||||
return this.h.links()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
updateSize () {
|
||||
const { width, height } = this.$el.getBoundingClientRect()
|
||||
Object.assign(this.$data, { width, height })
|
||||
},
|
||||
|
||||
init () {
|
||||
this.updateSize()
|
||||
|
||||
const h = hierarchy(this.data)
|
||||
|
||||
h.descendants().forEach(node =>
|
||||
Object.assign(node, {
|
||||
x: this.width * 0.5,
|
||||
y: this.height * 0.5
|
||||
})
|
||||
)
|
||||
|
||||
this.h = h
|
||||
this.setupForces()
|
||||
// Wait for DOM update so d3 can select
|
||||
this.$nextTick(this.initListeners)
|
||||
},
|
||||
|
||||
initListeners () {
|
||||
// TODO replace with vue events ?
|
||||
selectAll('circle')
|
||||
.data(this.nodes)
|
||||
.call(drag()
|
||||
.on('start', this.onNodeDragStart)
|
||||
.on('drag', this.onNodeDrag)
|
||||
.on('end', this.onNodeDragEnd)
|
||||
)
|
||||
},
|
||||
|
||||
setupForces () {
|
||||
this.simulation
|
||||
.nodes(this.nodes)
|
||||
.force('link', forceLink(this.links).distance(100))
|
||||
.force('charge', forceManyBody().strength(-350))
|
||||
.force('x', forceX())
|
||||
.force('y', forceY())
|
||||
.force('center', forceCenter(this.width * 0.5, this.height * 0.5))
|
||||
},
|
||||
|
||||
getNodeFill (node) {
|
||||
return node.data.type === 'textref' ? 'red' : 'black'
|
||||
},
|
||||
|
||||
// DRAG EVENT HANDLER
|
||||
|
||||
onNodeDragStart (e, node) {
|
||||
if (!e.active) {
|
||||
this.simulation.alphaTarget(0.8).restart()
|
||||
}
|
||||
node.fx = node.x
|
||||
node.fy = node.y
|
||||
},
|
||||
|
||||
onNodeDrag (e, node) {
|
||||
node.fx = e.x
|
||||
node.fy = e.y
|
||||
},
|
||||
|
||||
onNodeDragEnd (e, node) {
|
||||
if (!e.active) {
|
||||
this.simulation.alphaTarget(0)
|
||||
}
|
||||
node.fx = null
|
||||
node.fy = null
|
||||
}
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div id="map">
|
||||
<node-map v-if="data" :data="data" />
|
||||
<div>
|
||||
{{ data }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import NodeMap from '@/components/NodeMap'
|
||||
export default {
|
||||
name: 'Home',
|
||||
|
||||
components: {
|
||||
NodeMap
|
||||
},
|
||||
|
||||
data () {
|
||||
return {
|
||||
text: null,
|
||||
data: null
|
||||
}
|
||||
},
|
||||
|
||||
created () {
|
||||
this.$store.dispatch('GET_TREE', 17).then((data) => {
|
||||
this.data = data
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#map {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -10,6 +10,11 @@ const routes = [
|
||||
path: '/',
|
||||
component: Home
|
||||
},
|
||||
{
|
||||
name: 'map',
|
||||
path: '/map',
|
||||
component: () => import(/* webpackChunkName: "node-map" */ '../pages/Map')
|
||||
},
|
||||
{
|
||||
name: 'notfound',
|
||||
path: '/404',
|
||||
|
||||
Reference in New Issue
Block a user