Browse Source

add d3 data formatting & update map view with two links repr

axolotle 3 years ago
parent
commit
9ffc391722
6 changed files with 138 additions and 52 deletions
  1. 2 2
      src/App.vue
  2. 32 36
      src/components/NodeMap.vue
  3. 75 0
      src/helpers/d3Data.js
  4. 23 7
      src/pages/Map.vue
  5. 5 5
      src/router/index.js
  6. 1 2
      src/store/index.js

+ 2 - 2
src/App.vue

@@ -1,10 +1,10 @@
 <template>
   <div id="app">
-    <header>
+    <!-- <header>
       <h1>
         <router-link :to="{ name: 'home' }">En Français</router-link>
       </h1>
-    </header>
+    </header> -->
 
     <main id="main">
       <router-view />

+ 32 - 36
src/components/NodeMap.vue

@@ -1,5 +1,8 @@
 <template>
-  <svg width="100%" height="100%" ref="svg">
+  <svg
+    width="100%" height="100%"
+    ref="svg" :id="id"
+  >
     <path
       v-for="(item, index) in links"
       :key="`link_${index}`"
@@ -7,21 +10,22 @@
       stroke="black"
     />
 
-    <circle
-      v-for="(node, index) in nodes"
-      :key="index"
-      :cx="node.x"
-      :cy="node.y"
-      :class="node.data.class"
-      :r="node.children ? 7 : 5"
-    >
-      <title>{{ node.data.name }}</title>
-    </circle>
+    <template v-for="(node, index) in nodes">
+      <circle
+        :key="'circle' + index"
+        :cx="node.x"
+        :cy="node.y"
+        :class="node.data.class + (node.data.first ? ' first' : '')"
+        :r="node.children ? 7 : 5"
+      >
+        <title>{{ node.data.title }}</title>
+      </circle>
+      <text :key="'text' + index" :x="node.x + 7" :y="node.y-7">{{ node.data.id }}</text>
+    </template>
   </svg>
 </template>
 
 <script>
-import { hierarchy } from 'd3-hierarchy'
 import { line } from 'd3-shape'
 import { selectAll } from 'd3-selection'
 import { drag } from 'd3-drag'
@@ -39,29 +43,20 @@ export default {
   name: 'NodeMap',
 
   props: {
-    data: { type: Object, required: true }
+    nodes: { type: Array, required: true },
+    links: { type: Array, required: true },
+    id: { type: String, default: 'map' }
   },
 
   data () {
     return {
       width: 100,
       height: 100,
-      h: hierarchy({}),
       simulation: forceSimulation(),
       lineGenerator: line().x(node => node.x).y(node => node.y)
     }
   },
 
-  computed: {
-    nodes () {
-      return this.h.descendants()
-    },
-
-    links () {
-      return this.h.links()
-    }
-  },
-
   methods: {
     updateSize () {
       const { width, height } = this.$el.getBoundingClientRect()
@@ -71,16 +66,11 @@ export default {
     init () {
       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()
       // Wait for DOM update so d3 can select
       this.$nextTick(this.initListeners)
@@ -88,7 +78,8 @@ export default {
 
     initListeners () {
       // TODO replace with vue events ?
-      selectAll('circle')
+
+      selectAll(`#${this.id} circle`)
         .data(this.nodes)
         .call(drag()
           .on('start', this.onNodeDragStart)
@@ -100,8 +91,8 @@ export default {
     setupForces () {
       this.simulation
         .nodes(this.nodes)
-        .force('link', forceLink(this.links).distance(100))
-        .force('charge', forceManyBody().strength(-350))
+        .force('link', forceLink(this.links).id(d => d.id).distance(50))
+        .force('charge', forceManyBody().strength(-150))
         .force('x', forceX())
         .force('y', forceY())
         .force('center', forceCenter(this.width * 0.5, this.height * 0.5))
@@ -142,6 +133,11 @@ path {
   stroke: grey;
 }
 
+text {
+  font-size: 0.7rem;
+  user-select: none;
+}
+
 @each $id in map-keys($families){
   .family-#{$id} {
     fill: setColorFromId($id);

+ 75 - 0
src/helpers/d3Data.js

@@ -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
src/pages/Map.vue

@@ -1,14 +1,17 @@
 <template>
   <div id="map">
-    <node-map v-if="data" :data="data" />
-    <div>
-      {{ data }}
-    </div>
+    <node-map class="node-map" v-if="data" v-bind="one" />
+    <node-map
+      class="node-map" v-if="data2" v-bind="two"
+      id="ùmqsldùml"
+    />
   </div>
 </template>
 
 <script>
+import { toSingleManyData, toManyManyData } from '@/helpers/d3Data'
 import NodeMap from '@/components/NodeMap'
+
 export default {
   name: 'Home',
 
@@ -18,14 +21,22 @@ export default {
 
   data () {
     return {
-      text: null,
-      data: null
+      id: 39,
+      data: null,
+      data2: null,
+      one: { nodes: null, links: null },
+      two: { nodes: null, links: null }
     }
   },
 
   created () {
-    this.$store.dispatch('GET_TREE', 17).then((data) => {
+    this.$store.dispatch('GET_TREE', this.id).then((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 {
   height: 100%;
   width: 100%;
+  display: flex;
+  flex-basis: 50%;
+}
+.node-map {
+  width: 50vw;
 }
 </style>

+ 5 - 5
src/router/index.js

@@ -8,13 +8,13 @@ const routes = [
   {
     name: 'home',
     path: '/',
-    component: Home
-  },
-  {
-    name: 'map',
-    path: '/map',
     component: () => import(/* webpackChunkName: "node-map" */ '../pages/Map')
   },
+  // {
+  //   name: 'map',
+  //   path: '/map',
+  //   component: () => import(/* webpackChunkName: "node-map" */ '../pages/Map')
+  // },
   {
     name: 'notfound',
     path: '/404',

+ 1 - 2
src/store/index.js

@@ -27,8 +27,7 @@ export default new Vuex.Store({
 
     'GET_TREE' ({ dispatch }, id) {
       return api.post('', { query: print(TextdepartRecursive), variables: { id } }).then(({ data }) => {
-        const text = data.data.textref
-        return parse(text, text.id)
+        return data.data.textref
       })
     }
   }