123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- <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"
- :class="node.data.class"
- :r="node.children ? 7 : 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))
- },
- // 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>
- path {
- stroke: grey;
- }
- @each $id in map-keys($families){
- .family-#{$id} {
- fill: setColorFromId($id);
- @if $id == 9 {
- stroke: black;
- }
- }
- }
- .first {
- stroke-width: 2px;
- }
- </style>
|