<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>
