update LibraryTree with MapZoom
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
<strong class="d-block">
|
<strong class="d-block">
|
||||||
{{ toCommaList(node.authors) }},
|
{{ toCommaList(node.authors) }},
|
||||||
</strong>
|
</strong>
|
||||||
<span v-if="node.preTitle" v-html="node.preTitle + ','" />
|
<span v-if="node.preTitle" v-html="trim(node.preTitle) + ','" />
|
||||||
<em v-html="(trim(node.title) || 'pas de titre ital') + ','" />
|
<em v-html="(trim(node.title) || 'pas de titre ital') + ','" />
|
||||||
<div v-if="node.edition" class="edition">
|
<div v-if="node.edition" class="edition">
|
||||||
{{ node.edition.map(ed => ed.name).join(' ') }}
|
{{ node.edition.map(ed => ed.name).join(' ') }}
|
||||||
|
|||||||
@@ -1,35 +1,112 @@
|
|||||||
<template>
|
<template>
|
||||||
<b-overlay :show="nodeTree === undefined" class="h-100">
|
<b-overlay :show="nodeTree.nodes.length === 0" class="h-100">
|
||||||
<node-map
|
<map-zoom id="library-tree" :min-zoom="0.3" :max-zoom="1">
|
||||||
v-if="nodeTree !== undefined"
|
<path
|
||||||
v-bind="nodeTree"
|
v-for="link in nodeTree.links"
|
||||||
v-on="$listeners"
|
: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>
|
</b-overlay>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex'
|
import { mapGetters } from 'vuex'
|
||||||
|
import { line } from 'd3-shape'
|
||||||
|
import { forceSimulation, forceLink, forceManyBody, forceX, forceY } from 'd3-force'
|
||||||
|
|
||||||
import NodeMap from '@/components/NodeMap'
|
import { trim, toCommaList } from '@/helpers/common'
|
||||||
|
import { MapZoom } from '@/components/layouts'
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TreeMap',
|
name: 'LibraryTree',
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
NodeMap
|
MapZoom
|
||||||
},
|
},
|
||||||
|
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
loading: true,
|
activeNode: null
|
||||||
mapData: undefined
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters(['nodeTree'])
|
...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
|
||||||
},
|
},
|
||||||
|
|
||||||
created () {
|
created () {
|
||||||
@@ -39,4 +116,58 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<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>
|
</style>
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
activeNodes: (state) => {
|
activeNodes: state => {
|
||||||
if (!state.nodebook) return []
|
if (!state.nodebook) return []
|
||||||
return state.nodebook.reduce((acc, ids) => [...acc, ...ids], [])
|
return state.nodebook.reduce((acc, ids) => [...acc, ...ids], [])
|
||||||
},
|
},
|
||||||
@@ -218,8 +218,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
nodeTree: (state, rootState) => {
|
nodeTree: (state, rootState) => {
|
||||||
if (state.nodeDepartId === undefined) return
|
return state.trees[state.nodeDepartId] || { nodes: [], links: [] }
|
||||||
return state.trees[state.nodeDepartId]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,8 @@ export function buildTree (treeData, nodesData) {
|
|||||||
if (!uniqueIds.includes(node.id)) {
|
if (!uniqueIds.includes(node.id)) {
|
||||||
uniqueIds.push(node.id)
|
uniqueIds.push(node.id)
|
||||||
node.data = nodesData.find(n => n.id === node.id)
|
node.data = nodesData.find(n => n.id === node.id)
|
||||||
|
// Add `x` and `y` keys so Vue can update on these values updates
|
||||||
|
Object.assign(node, { x: undefined, y: undefined })
|
||||||
nodes.push(node)
|
nodes.push(node)
|
||||||
if (node.children) {
|
if (node.children) {
|
||||||
node.children.forEach(child => {
|
node.children.forEach(child => {
|
||||||
|
|||||||
Reference in New Issue
Block a user