add d3 data formatting & update map view with two links repr
This commit is contained in:
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<header>
|
<!-- <header>
|
||||||
<h1>
|
<h1>
|
||||||
<router-link :to="{ name: 'home' }">En Français</router-link>
|
<router-link :to="{ name: 'home' }">En Français</router-link>
|
||||||
</h1>
|
</h1>
|
||||||
</header>
|
</header> -->
|
||||||
|
|
||||||
<main id="main">
|
<main id="main">
|
||||||
<router-view />
|
<router-view />
|
||||||
|
|||||||
+32
-36
@@ -1,5 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg width="100%" height="100%" ref="svg">
|
<svg
|
||||||
|
width="100%" height="100%"
|
||||||
|
ref="svg" :id="id"
|
||||||
|
>
|
||||||
<path
|
<path
|
||||||
v-for="(item, index) in links"
|
v-for="(item, index) in links"
|
||||||
:key="`link_${index}`"
|
:key="`link_${index}`"
|
||||||
@@ -7,21 +10,22 @@
|
|||||||
stroke="black"
|
stroke="black"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<circle
|
<template v-for="(node, index) in nodes">
|
||||||
v-for="(node, index) in nodes"
|
<circle
|
||||||
:key="index"
|
:key="'circle' + index"
|
||||||
:cx="node.x"
|
:cx="node.x"
|
||||||
:cy="node.y"
|
:cy="node.y"
|
||||||
:class="node.data.class"
|
:class="node.data.class + (node.data.first ? ' first' : '')"
|
||||||
:r="node.children ? 7 : 5"
|
:r="node.children ? 7 : 5"
|
||||||
>
|
>
|
||||||
<title>{{ node.data.name }}</title>
|
<title>{{ node.data.title }}</title>
|
||||||
</circle>
|
</circle>
|
||||||
|
<text :key="'text' + index" :x="node.x + 7" :y="node.y-7">{{ node.data.id }}</text>
|
||||||
|
</template>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { hierarchy } from 'd3-hierarchy'
|
|
||||||
import { line } from 'd3-shape'
|
import { line } from 'd3-shape'
|
||||||
import { selectAll } from 'd3-selection'
|
import { selectAll } from 'd3-selection'
|
||||||
import { drag } from 'd3-drag'
|
import { drag } from 'd3-drag'
|
||||||
@@ -39,29 +43,20 @@ export default {
|
|||||||
name: 'NodeMap',
|
name: 'NodeMap',
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
data: { type: Object, required: true }
|
nodes: { type: Array, required: true },
|
||||||
|
links: { type: Array, required: true },
|
||||||
|
id: { type: String, default: 'map' }
|
||||||
},
|
},
|
||||||
|
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
h: hierarchy({}),
|
|
||||||
simulation: forceSimulation(),
|
simulation: forceSimulation(),
|
||||||
lineGenerator: line().x(node => node.x).y(node => node.y)
|
lineGenerator: line().x(node => node.x).y(node => node.y)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
nodes () {
|
|
||||||
return this.h.descendants()
|
|
||||||
},
|
|
||||||
|
|
||||||
links () {
|
|
||||||
return this.h.links()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
updateSize () {
|
updateSize () {
|
||||||
const { width, height } = this.$el.getBoundingClientRect()
|
const { width, height } = this.$el.getBoundingClientRect()
|
||||||
@@ -71,16 +66,11 @@ export default {
|
|||||||
init () {
|
init () {
|
||||||
this.updateSize()
|
this.updateSize()
|
||||||
|
|
||||||
const h = hierarchy(this.data)
|
this.nodes.forEach(node => {
|
||||||
|
this.$set(node, 'x', this.width * 0.5)
|
||||||
|
this.$set(node, 'y', this.height * 0.5)
|
||||||
|
})
|
||||||
|
|
||||||
h.descendants().forEach(node =>
|
|
||||||
Object.assign(node, {
|
|
||||||
x: this.width * 0.5,
|
|
||||||
y: this.height * 0.5
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
this.h = h
|
|
||||||
this.setupForces()
|
this.setupForces()
|
||||||
// Wait for DOM update so d3 can select
|
// Wait for DOM update so d3 can select
|
||||||
this.$nextTick(this.initListeners)
|
this.$nextTick(this.initListeners)
|
||||||
@@ -88,7 +78,8 @@ export default {
|
|||||||
|
|
||||||
initListeners () {
|
initListeners () {
|
||||||
// TODO replace with vue events ?
|
// TODO replace with vue events ?
|
||||||
selectAll('circle')
|
|
||||||
|
selectAll(`#${this.id} circle`)
|
||||||
.data(this.nodes)
|
.data(this.nodes)
|
||||||
.call(drag()
|
.call(drag()
|
||||||
.on('start', this.onNodeDragStart)
|
.on('start', this.onNodeDragStart)
|
||||||
@@ -100,8 +91,8 @@ export default {
|
|||||||
setupForces () {
|
setupForces () {
|
||||||
this.simulation
|
this.simulation
|
||||||
.nodes(this.nodes)
|
.nodes(this.nodes)
|
||||||
.force('link', forceLink(this.links).distance(100))
|
.force('link', forceLink(this.links).id(d => d.id).distance(50))
|
||||||
.force('charge', forceManyBody().strength(-350))
|
.force('charge', forceManyBody().strength(-150))
|
||||||
.force('x', forceX())
|
.force('x', forceX())
|
||||||
.force('y', forceY())
|
.force('y', forceY())
|
||||||
.force('center', forceCenter(this.width * 0.5, this.height * 0.5))
|
.force('center', forceCenter(this.width * 0.5, this.height * 0.5))
|
||||||
@@ -142,6 +133,11 @@ path {
|
|||||||
stroke: grey;
|
stroke: grey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
@each $id in map-keys($families){
|
@each $id in map-keys($families){
|
||||||
.family-#{$id} {
|
.family-#{$id} {
|
||||||
fill: setColorFromId($id);
|
fill: setColorFromId($id);
|
||||||
|
|||||||
Vendored
+75
@@ -0,0 +1,75 @@
|
|||||||
|
import { hierarchy } from 'd3-hierarchy'
|
||||||
|
|
||||||
|
function flatten (arr, accumulator = []) {
|
||||||
|
return arr.reduce((acc, child) => {
|
||||||
|
acc.push(child)
|
||||||
|
return child.children ? flatten(child.children, acc) : acc
|
||||||
|
}, accumulator)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLinked (text) {
|
||||||
|
const types = ['text_en_rebond', 'text_produits', 'text_de_depart']
|
||||||
|
return types.reduce((acc, type) => {
|
||||||
|
// Handle `null` and `undefined`
|
||||||
|
return text[type] ? [...acc, ...text[type]] : acc
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toSingleManyData (rawData) {
|
||||||
|
rawData.first = true
|
||||||
|
const h = hierarchy(rawData, d => getLinked(d))
|
||||||
|
h.each(node => {
|
||||||
|
if (node.parent && node.children) {
|
||||||
|
const id = node.parent.data.id
|
||||||
|
node.children = node.children.filter(child => child.data.id !== id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const nodes = h.descendants()
|
||||||
|
const links = h.links()
|
||||||
|
nodes.forEach(node => {
|
||||||
|
Object.assign(node.data, {
|
||||||
|
type: node.data.__typename.toLowerCase(),
|
||||||
|
class: 'family-' + node.data.familles[0].id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return { nodes, links }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function toManyManyData (rawData) {
|
||||||
|
rawData.first = true
|
||||||
|
const h = hierarchy(rawData, d => getLinked(d))
|
||||||
|
h.each(node => {
|
||||||
|
if (node.parent && node.children) {
|
||||||
|
const id = node.parent.data.id
|
||||||
|
node.children = node.children.filter(child => child.data.id !== id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const ids = []
|
||||||
|
const nodes = []
|
||||||
|
const links = []
|
||||||
|
flatten(h.descendants()).filter(node => {
|
||||||
|
if (!ids.includes(node.data.id)) {
|
||||||
|
ids.push(node.data.id)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}).forEach(({ data, children, depth }) => {
|
||||||
|
nodes.push({ id: data.id, data })
|
||||||
|
if (children) {
|
||||||
|
children.forEach(child => {
|
||||||
|
links.push({ source: data.id, target: child.data.id })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
nodes.forEach(node => {
|
||||||
|
Object.assign(node.data, {
|
||||||
|
type: node.data.__typename.toLowerCase(),
|
||||||
|
class: 'family-' + node.data.familles[0].id
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return { nodes, links }
|
||||||
|
}
|
||||||
+23
-7
@@ -1,14 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="map">
|
<div id="map">
|
||||||
<node-map v-if="data" :data="data" />
|
<node-map class="node-map" v-if="data" v-bind="one" />
|
||||||
<div>
|
<node-map
|
||||||
{{ data }}
|
class="node-map" v-if="data2" v-bind="two"
|
||||||
</div>
|
id="ùmqsldùml"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { toSingleManyData, toManyManyData } from '@/helpers/d3Data'
|
||||||
import NodeMap from '@/components/NodeMap'
|
import NodeMap from '@/components/NodeMap'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
|
|
||||||
@@ -18,14 +21,22 @@ export default {
|
|||||||
|
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
text: null,
|
id: 39,
|
||||||
data: null
|
data: null,
|
||||||
|
data2: null,
|
||||||
|
one: { nodes: null, links: null },
|
||||||
|
two: { nodes: null, links: null }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
created () {
|
created () {
|
||||||
this.$store.dispatch('GET_TREE', 17).then((data) => {
|
this.$store.dispatch('GET_TREE', this.id).then((data) => {
|
||||||
this.data = data
|
this.data = data
|
||||||
|
this.one = toSingleManyData(data)
|
||||||
|
})
|
||||||
|
this.$store.dispatch('GET_TREE', this.id).then((data) => {
|
||||||
|
this.data2 = data
|
||||||
|
this.two = toManyManyData(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,5 +46,10 @@ export default {
|
|||||||
#map {
|
#map {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-basis: 50%;
|
||||||
|
}
|
||||||
|
.node-map {
|
||||||
|
width: 50vw;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+5
-5
@@ -8,13 +8,13 @@ const routes = [
|
|||||||
{
|
{
|
||||||
name: 'home',
|
name: 'home',
|
||||||
path: '/',
|
path: '/',
|
||||||
component: Home
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'map',
|
|
||||||
path: '/map',
|
|
||||||
component: () => import(/* webpackChunkName: "node-map" */ '../pages/Map')
|
component: () => import(/* webpackChunkName: "node-map" */ '../pages/Map')
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// name: 'map',
|
||||||
|
// path: '/map',
|
||||||
|
// component: () => import(/* webpackChunkName: "node-map" */ '../pages/Map')
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
name: 'notfound',
|
name: 'notfound',
|
||||||
path: '/404',
|
path: '/404',
|
||||||
|
|||||||
+1
-2
@@ -27,8 +27,7 @@ export default new Vuex.Store({
|
|||||||
|
|
||||||
'GET_TREE' ({ dispatch }, id) {
|
'GET_TREE' ({ dispatch }, id) {
|
||||||
return api.post('', { query: print(TextdepartRecursive), variables: { id } }).then(({ data }) => {
|
return api.post('', { query: print(TextdepartRecursive), variables: { id } }).then(({ data }) => {
|
||||||
const text = data.data.textref
|
return data.data.textref
|
||||||
return parse(text, text.id)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user