d3.jsvuejs2vue-componentdagre-d3

Error in mounted hook: "TypeError: edge is undefined" when using dagre-d3 with vue2


I'm trying to set up very simple example with Vue2 using dagre-d3 for rendering directed graphs. Unfortunately, even with extremely simple example, it wont work. Examples found elsewhere online are using older version of d3. Currently, Vue2 app is mostly default template with a router with a typescript as language. Diagram component is in javascript (due to missing types in my code for d3 and dagre-d3).

When running the component mentioned below, following error happens and nothing is shown in the <svg> block.

Error in mounted hook: "TypeError: edge is undefined"

And it happens on this line

render(container, g);

Only thing i can think off is that i might be missing some some dependencies or that all components must be typescript.

Help?

Diagram.vue:

<template>
  <div class="myback">
    <h1>This is a diagram component</h1>
    <svg>
      <g></g>
    </svg>
  </div>
</template>

<script>
import * as d3 from "d3";
import dagreD3 from "dagre-d3";

// let edges = {}
// let nodes = {}
// let g = new dagreD3.graphlib.Graph().setGraph({})

export default {
  /*
    data () {
        return {
            edges: {},
            nodes: {}
        }
    },
    */

  mounted() {
    /* create graph itself */
    const g = new dagreD3.graphlib.Graph().setGraph({});
    g.setGraph({
      nodesep: 70,
      ranksep: 50,
      rankdir: "LR",
      marginx: 20,
      marginy: 20,
    });
    console.log(g);

    const render = new dagreD3.render(); // eslint-disable-line new-cap
    console.log(render);

    const svg = d3.select("svg");
    const container = svg.select("g");

    console.log(svg);
    console.log(container);

    /* define zoom behavior */
    function zoomed(e) {
      container.attr("transform", e.transform);
    }

    const zoom = d3.zoom().scaleExtent([1, 10]).on("zoom", zoomed);

    g.setNode("kspacey", { label: "Kevin Spacey", width: 144, height: 100 });
    g.setNode("blabla", { label: "blabla", width: 144, height: 100 });

    g.setEdge("kspacey", "blabla");

    svg.call(zoom);

    render(container, g);
  },
  /*
    methods: {
        draw () {
        }
    }
    */
};
</script>

<style scoped>
section {
  margin-bottom: 3em;
}

section p {
  text-align: justify;
}

svg {
  border: 1px solid #ccc;
  overflow: hidden;
  margin: 0 auto;
  background: white;
  width: 800px;
  height: 600px;
}

text {
  font-weight: 300;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serf;
  font-size: 14px;
}

path.link {
  fill: none;
  stroke: #666;
  stroke-width: 1.5px;
}

.node rect {
  stroke: #333;
  fill: #fff;
  stroke-width: 1.5px;
}

.myback {
  background: gray;
}
</style>

Codesanbox link is here


Solution

  • There were two problems with this component:

    1. Default edge label needs to be set

    I was missing a call on the graph instantiation. It should have been

    const g = new dagreD3.graphlib.Graph()
        .setGraph({})
        .setDefaultEdgeLabel(function () { return {} })
    
    1. d3.select() was selecting and using wrong node for rendering. Due to usage of fontawesome, in the router-link, i was using following
    <router-link to="/about">
        <font-awesome-icon :icon="['fas', 'info-circle']" />
    </router-link>
    

    Because the font-awesome-icon is rendered to <svg> element, rendering was happening in that node, but i only noticed this after fix with the default edge.

    If someone has the same issue, this example was the one that helped me identify explained issues