LibraryTree.vue 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <template>
  2. <b-overlay :show="nodeTree.nodes.length === 0" class="h-100">
  3. <map-zoom id="library-tree" :min-zoom="0.3" :max-zoom="1">
  4. <path
  5. v-for="link in nodeTree.links"
  6. :key="`${link.source.data.id}_${link.target.data.id}`"
  7. :d="lineGenerator([link.source, link.target])"
  8. :class="['svg-link', (link.linkType || link.target.data.linkType)]"
  9. />
  10. <circle
  11. v-for="node in nodeTree.nodes"
  12. :key="'circle' + node.data.id"
  13. :cx="node.x"
  14. :cy="node.y"
  15. class="svg-dot"
  16. :class="['svg-dot-' + node.data.variant, { 'origin': node.parent === null }]"
  17. tabindex="0"
  18. @click="onNodeClick(node.data)"
  19. @keyup.enter="onNodeClick(node.data)"
  20. @mouseenter="activeNode = node"
  21. />
  22. <g>
  23. <rect class="svg-overlay" />
  24. <foreignObject
  25. v-if="activeNode"
  26. :x="activeNode.x"
  27. :y="activeNode.y"
  28. >
  29. <dot-button
  30. :variant="activeNode.data.variant"
  31. class="active"
  32. @click="onNodeClick(activeNode.data)"
  33. @mouseleave="activeNode = null"
  34. >
  35. <template v-if="activeNode.data.variant !== 'depart'">
  36. {{ $t('variants.' + activeNode.data.variant) }}<br>
  37. </template>
  38. {{ toCommaList(activeNode.data.authors) }},<br>
  39. <span v-if="activeNode.data.preTitle" v-html="trim(activeNode.data.preTitle) + ','" />
  40. <span v-html="trim(activeNode.data.title) || '<em>pas de titre ital</em>'" />
  41. </dot-button>
  42. </foreignObject>
  43. </g>
  44. </map-zoom>
  45. </b-overlay>
  46. </template>
  47. <script>
  48. import { mapGetters } from 'vuex'
  49. import { line } from 'd3-shape'
  50. import { forceSimulation, forceLink, forceManyBody, forceX, forceY } from 'd3-force'
  51. import { trim, toCommaList } from '@/helpers/common'
  52. import { MapZoom } from '@/components/layouts'
  53. export default {
  54. name: 'LibraryTree',
  55. components: {
  56. MapZoom
  57. },
  58. data () {
  59. return {
  60. activeNode: null
  61. }
  62. },
  63. computed: {
  64. ...mapGetters(['nodeTree']),
  65. // ONE TIME GETTER
  66. simulation () {
  67. return forceSimulation()
  68. .force('charge', forceManyBody().strength((d) => {
  69. if (d.data.linkType === 'parents') return -2000
  70. if (d.data.linkType === 'siblings') return -500
  71. return -1000
  72. }))
  73. .force('link', forceLink().id(d => d.id).distance((d) => {
  74. if (d.linkType === 'parents') return 200
  75. if (d.linkType === 'siblings') return 100
  76. return 0
  77. }).strength(0.5))
  78. .force('x', forceX())
  79. .force('y', forceY())
  80. },
  81. // ONE TIME GETTER
  82. lineGenerator () {
  83. return line().x(node => node.x).y(node => node.y)
  84. }
  85. },
  86. watch: {
  87. nodeTree (tree) {
  88. this.activeNode = null
  89. this.simulation.nodes(tree.nodes)
  90. this.simulation.force('link').links(tree.links)
  91. this.simulation.alpha(0.5).restart()
  92. }
  93. },
  94. methods: {
  95. toCommaList,
  96. trim,
  97. onNodeClick (node) {
  98. if (node.parents && node.parents.length) {
  99. this.$emit('open-node', { parentId: node.parents[0].id, childId: node.id })
  100. } else {
  101. this.$emit('open-node', { parentId: node.id })
  102. }
  103. }
  104. },
  105. created () {
  106. this.$store.dispatch('INIT_LIBRARY_TREE')
  107. }
  108. }
  109. </script>
  110. <style lang="scss" scoped>
  111. .svg {
  112. &-link {
  113. stroke: grey;
  114. vector-effect: non-scaling-stroke;
  115. &.siblings {
  116. stroke-dasharray: 4;
  117. }
  118. &.parents {
  119. stroke: red;
  120. opacity: .3;
  121. }
  122. }
  123. &-dot {
  124. r: 9.5px;
  125. @each $color, $value in $theme-colors {
  126. &-#{$color} {
  127. fill: $value;
  128. @if $color == 'depart' {
  129. stroke: $black;
  130. stroke-width: 3px;
  131. vector-effect: non-scaling-stroke;
  132. }
  133. }
  134. }
  135. &.origin {
  136. r: 15px;
  137. }
  138. }
  139. &-overlay {
  140. width: 100%;
  141. height: 100%;
  142. x: -50%;
  143. y: -50%;
  144. fill: transparent;
  145. pointer-events: none;
  146. }
  147. }
  148. foreignObject {
  149. height: 1px;
  150. width: 1px;
  151. overflow: visible;
  152. }
  153. .dot-btn {
  154. transform: translate(-50%, -50%);
  155. }
  156. </style>