remove NodeMap component

This commit is contained in:
axolotle
2021-06-11 20:41:16 +02:00
parent cfee58bde7
commit 9c4018a615
-255
View File
@@ -1,255 +0,0 @@
<template>
<svg
width="100%" height="100%"
ref="svg" :id="id"
:view-box.camel="viewBox"
>
<g :id="id + '-map'" v-if="ready">
<g>
<path
v-for="(item, index) in links"
:key="`link_${index}`"
:d="lineGenerator([item.source, item.target])"
:class="['link', item.linkType || item.target.data.linkType]"
/>
</g>
<circle
v-for="(node, index) in nodes"
:key="'circle' + index"
:cx="node.x"
:cy="node.y"
class="svg-dot-btn"
:class="['svg-dot-btn-' + node.data.variant, { 'origin': node.parent === null }]"
tabindex="0"
@click="onNodeClick(node.data)"
@keyup.enter="onNodeClick(node.data)"
@mouseenter="activeNode = node"
/>
<g>
<!-- rect for forcing full render -->
<rect
x="-50%" y="-50%" fill="transparent"
width="100%" height="100%" style="pointer-events: none;"
/>
<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="activeNode.data.preTitle + ','" />
<span v-html="activeNode.data.title || '<em>pas de titre ital</em>'" />
</dot-button>
</foreignObject>
</g>
</g>
</svg>
</template>
<script>
import { line } from 'd3-shape'
import { selectAll } from 'd3-selection'
import { select, zoom } from 'd3'
import { drag } from 'd3-drag'
import {
forceSimulation, forceLink, forceManyBody,
forceX, forceY
} 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: {
nodes: { type: Array, required: true },
links: { type: Array, required: true },
id: { type: String, default: 'node-map' }
},
data () {
return {
ready: false,
width: 100,
height: 100,
simulation: forceSimulation(),
lineGenerator: line().x(node => node.x).y(node => node.y),
activeNode: null
}
},
computed: {
viewBox () {
const { width, height } = this
return `-${width / 2} -${height / 2} ${width} ${height}`
}
},
methods: {
toCommaList (arr) {
// FIXME TEMP some texts doesn't have authors
try {
return arr.map(({ name }) => name).join(', ')
} catch {
return 'pas d\'auteur⋅rices'
}
},
updateSize () {
const { width, height } = this.$el.getBoundingClientRect()
Object.assign(this.$data, { width, height })
},
init () {
this.updateSize()
this.nodes.forEach(node => {
this.$set(node, 'x', this.width * 0.5)
this.$set(node, 'y', this.height * 0.5)
})
this.setupForces()
// Wait for DOM update so d3 can select
this.$nextTick(this.initListeners)
this.ready = true
},
initListeners () {
// TODO replace with vue events ?
const svg = select('#' + this.id)
const map = select('#' + this.id + '-map')
svg.call(zoom()
.extent([[0, 0], [this.width, this.height]])
.scaleExtent([0.3, 1])
.on('zoom', ({ transform }) => {
map.attr('transform', transform)
}))
selectAll(`#${this.id} 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('charge', forceManyBody().strength((d) => {
if (d.data.linkType === 'parents') return -2000
if (d.data.linkType === 'siblings') return -500
return -1000
}))
.force('link', forceLink(this.links).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())
},
// D3 DRAG EVENT HANDLERS
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
},
// BASIC HANDLERS
onNodeClick (nodeData) {
// FIXME CURRENT PROBLEM AS API TREATING TEXTPRODs as TEXTREFs (no `parents:`)
const { id, parents } = nodeData
if (parents) {
this.$emit('open-node', parents[0].id, id)
} else {
this.$emit('open-node', id)
}
}
},
mounted () {
this.init()
}
}
</script>
<style lang="scss" scoped>
.link {
stroke: grey;
vector-effect: non-scaling-stroke;
&.siblings {
stroke-dasharray: 4;
}
&.parents {
stroke: red;
opacity: .3;
}
}
.svg-dot-btn {
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;
}
}
foreignObject {
height: 1px;
width: 1px;
overflow: visible;
}
.dot-btn {
vertical-align: top;
transform: translate(-50%, -50%);
}
</style>