5

I converted this Dagre-D3 demo to a React component. The code is below.

import React from 'react'
import d3 from 'd3'
import dagreD3 from 'dagre-d3'

export default class D3Chart extends React.Component {
    constructor () {
        super();
    }

    componentDidMount() {
        // Create the input graph
        var g = new dagreD3.graphlib.Graph()
          .setGraph({})
          .setDefaultEdgeLabel(function() { return {}; });

        // Here we"re setting nodeclass, which is used by our custom drawNodes function
        // below.
        g.setNode(0,  { label: "TOP",       class: "type-TOP" });
        g.setNode(1,  { label: "S",         class: "type-S" });
        g.setNode(2,  { label: "NP",        class: "type-NP" });
        g.setNode(3,  { label: "DT",        class: "type-DT" });
        g.setNode(4,  { label: "This",      class: "type-TK" });
        g.setNode(5,  { label: "VP",        class: "type-VP" });
        g.setNode(6,  { label: "VBZ",       class: "type-VBZ" });
        g.setNode(7,  { label: "is",        class: "type-TK" });
        g.setNode(8,  { label: "NP",        class: "type-NP" });
        g.setNode(9,  { label: "DT",        class: "type-DT" });
        g.setNode(10, { label: "an",        class: "type-TK" });
        g.setNode(11, { label: "NN",        class: "type-NN" });
        g.setNode(12, { label: "example",   class: "type-TK" });
        g.setNode(13, { label: ".",         class: "type-." });
        g.setNode(14, { label: "sentence",  class: "type-TK" });

        g.nodes().forEach(function(v) {
            var node = g.node(v);
            // Round the corners of the nodes
            node.rx = node.ry = 5;
        });

        // Set up edges, no special attributes.
        g.setEdge(3, 4);
        g.setEdge(2, 3);
        g.setEdge(1, 2);
        g.setEdge(6, 7);
        g.setEdge(5, 6);
        g.setEdge(9, 10);
        g.setEdge(8, 9);
        g.setEdge(11,12);
        g.setEdge(8, 11);
        g.setEdge(5, 8);
        g.setEdge(1, 5);
        g.setEdge(13,14);
        g.setEdge(1, 13);
        g.setEdge(0, 1)

        // Create the renderer
        var render = new dagreD3.render();

        // Set up an SVG group so that we can translate the final graph.
        var svg = d3.select(React.findDOMNode(this.refs.nodeTree));
        var svgGroup = d3.select(React.findDOMNode(this.refs.nodeTreeGroup));

        // Run the renderer. This is what draws the final graph.
        render(d3.select(React.findDOMNode(this.refs.nodeTreeGroup)), g);

        // Center the graph
        var xCenterOffset = (svg.attr("width") - g.graph().width) / 2;
        svgGroup.attr("transform", "translate(" + xCenterOffset + ", 20)");
        svg.attr("height", g.graph().height + 40);
    }

    render() {
        return (<svg id="nodeTree" ref="nodeTree" width="960" height="600"><g ref="nodeTreeGroup"/></svg>
        )
    };
}

The problem is that the rendering of the nodes are mis-aligned and their sizes too.

This is how it looks like. How it should like is here.

UPDATE:

This is how the first node looks like:

enter image description here

What now:

<g class="node type-TOP" transform="translate(100,0)" style="opacity: 1;"><rect rx="5" ry="5" x="-10" y="-10" width="20" height="20"></rect><g class="label" transform="translate(0,0)"><g transform="translate(0,0)"><text><tspan xml:space="preserve" dy="1em" x="1">TOP</tspan></text></g></g></g>

What should be:

<g class="node type-TOP" transform="translate(211.25,18)" style="opacity: 1;"><rect rx="5" ry="5" x="-24.5" y="-18" width="49" height="36"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-14.5,-8)"><text><tspan xml:space="preserve" dy="1em" x="1">TOP</tspan></text></g></g></g>

The width and height are not calculated correctly. The width should be 49 but it is only 20.

wonderful world
  • 10,969
  • 20
  • 97
  • 194

2 Answers2

1

Try to set the height and width inside the componentDidMount just after you caught the svg with findDOMNode.

Or try to put the height and width this way.

style={{height:'900', width:'300'}}

Let me know if it works

François Richard
  • 6,817
  • 10
  • 43
  • 78
  • Unfortunately that did not work. I think the issue may be in dagre framework where it calculates the width and height of the node. Currently it calculates to be 20x20. This value should be based on the content inside the node. – wonderful world Aug 30 '15 at 10:22
  • 1
    so this would be react fault ? Let me know if you find the solution I'm really curious about it – François Richard Aug 30 '15 at 10:40
  • 1
    After spending few hours on it and digging into the dagre code, what seems to be happening is that dagre is not able to calculate the width and height of the nodes (which is the width and height of the text inside it, for example TOP) because the the DOM has not been manipulated yet. There is a getBBox() call for the text inside the node and it returns 0 for width and height because the text TOP has not been added to the DOM yet. – wonderful world Aug 30 '15 at 11:47
  • Seems similar problem as http://stackoverflow.com/questions/26234252/best-way-to-retrieve-and-cache-bounding-boxes-of-svg-nodes-with-react-js – wonderful world Aug 30 '15 at 11:53
0

I've figured out a workaround for this. I am having the exact same issue as you, and through pure trial and error I discovered that if you append the graph to the body of the page, it works fine. The text and nodes line up and the node sizes change appropriately to the text. This isn't ideal, but is a workaround..

(I don't know react syntax, so bear with me)

Instead of having a svg node pre created like this (don't do this):

var svg = d3.select(React.findDOMNode(this.refs.nodeTree));

Build one on the fly and append it to the body element of your whole HTML doc (do this):

var svg = d3.select(React.findDOMNode('body'))
            .append('svg')
            .attr('width', 500)
            .attr('height', 500);

I'm not sure why this is, possibly the reason you explained in the comments of François Richard's answer.

Elliott
  • 325
  • 2
  • 13