123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- <template>
- <b-overlay :show="nodeTree.nodes.length === 0" class="h-100">
- <map-zoom id="library-tree" :min-zoom="0.3" :max-zoom="1">
- <path
- v-for="link in nodeTree.links"
- :key="`${link.source.data.id}_${link.target.data.id}`"
- :d="lineGenerator([link.source, link.target])"
- :class="['svg-link', (link.linkType || link.target.data.linkType)]"
- />
- <circle
- v-for="node in nodeTree.nodes"
- :key="'circle' + node.data.id"
- :cx="node.x"
- :cy="node.y"
- class="svg-dot"
- :class="['svg-dot-' + node.data.variant, { 'origin': node.parent === null }]"
- tabindex="0"
- @click="onNodeClick(node.data)"
- @keyup.enter="onNodeClick(node.data)"
- @mouseenter="activeNode = node"
- />
- <g>
- <rect class="svg-overlay" />
- <foreignObject
- v-if="activeNode"
- :x="activeNode.x"
- :y="activeNode.y"
- >
- <dot-button
- :variant="activeNode.data.variant"
- class="active"
- @click="onNodeClick(activeNode.data)"
- @mouseleave="activeNode = null"
- >
- <template v-if="activeNode.data.variant !== 'depart'">
- {{ $t('variants.' + activeNode.data.variant) }}<br>
- </template>
- {{ toCommaList(activeNode.data.authors) }},<br>
- <span v-if="activeNode.data.preTitle" v-html="trim(activeNode.data.preTitle) + ','" />
- <span v-html="trim(activeNode.data.title) || '<em>pas de titre ital</em>'" />
- </dot-button>
- </foreignObject>
- </g>
- </map-zoom>
- </b-overlay>
- </template>
- <script>
- import { mapGetters } from 'vuex'
- import { line } from 'd3-shape'
- import { forceSimulation, forceLink, forceManyBody, forceX, forceY } from 'd3-force'
- import { trim, toCommaList } from '@/helpers/common'
- import { MapZoom } from '@/components/layouts'
- export default {
- name: 'LibraryTree',
- components: {
- MapZoom
- },
- data () {
- return {
- activeNode: null
- }
- },
- computed: {
- ...mapGetters(['nodeTree']),
- // ONE TIME GETTER
- simulation () {
- return forceSimulation()
- .force('charge', forceManyBody().strength((d) => {
- if (d.data.linkType === 'parents') return -2000
- if (d.data.linkType === 'siblings') return -500
- return -1000
- }))
- .force('link', forceLink().id(d => d.id).distance((d) => {
- if (d.linkType === 'parents') return 200
- if (d.linkType === 'siblings') return 100
- return 0
- }).strength(0.5))
- .force('x', forceX())
- .force('y', forceY())
- },
- // ONE TIME GETTER
- lineGenerator () {
- return line().x(node => node.x).y(node => node.y)
- }
- },
- watch: {
- nodeTree (tree) {
- this.activeNode = null
- this.simulation.nodes(tree.nodes)
- this.simulation.force('link').links(tree.links)
- this.simulation.alpha(0.5).restart()
- }
- },
- methods: {
- toCommaList,
- trim,
- onNodeClick (node) {
- if (node.parents && node.parents.length) {
- this.$emit('open-node', { parentId: node.parents[0].id, childId: node.id })
- } else {
- this.$emit('open-node', { parentId: node.id })
- }
- }
- },
- created () {
- this.$store.dispatch('INIT_LIBRARY_TREE')
- }
- }
- </script>
- <style lang="scss" scoped>
- .svg {
- &-link {
- stroke: grey;
- vector-effect: non-scaling-stroke;
- &.siblings {
- stroke-dasharray: 4;
- }
- &.parents {
- stroke: red;
- opacity: .3;
- }
- }
- &-dot {
- r: 9.5px;
- @each $color, $value in $theme-colors {
- &-#{$color} {
- fill: $value;
- @if $color == 'depart' {
- stroke: $black;
- stroke-width: 3px;
- vector-effect: non-scaling-stroke;
- }
- }
- }
- &.origin {
- r: 15px;
- }
- }
- &-overlay {
- width: 100%;
- height: 100%;
- x: -50%;
- y: -50%;
- fill: transparent;
- pointer-events: none;
- }
- }
- foreignObject {
- height: 1px;
- width: 1px;
- overflow: visible;
- }
- .dot-btn {
- transform: translate(-50%, -50%);
- }
- </style>
|