Why are my embedded JointJS elements overlapping?
Asked Answered
I

1

6

I am working on a JointJS graph, using DirectedGraph to take care of the layout and I am trying to achieve something similar to the image below. I need the nodes (A, B, C, D, E, F, G, H, I, J) to be "outlined" or enclosed in a separate node (Foo, Bar, Hmm). When I add all of the elements to the graph, everything is on top of each other. However, if I do not add the vertices between the regions, all the elements are laid out correctly, but without the vertices connecting the regions.

Based on the code below, what am I doing wrong? Do the nodes (A, B, C, D, E, F, G, H, I, J) cause an error because they are not connected to the rest of the graph?

Any feedback you could offer is much appreciated.

enter image description here

var graph = new joint.dia.Graph;
var paper = new joint.dia.Paper({
    el: $('#paper');
    width: 2000,
    height: 2000,
    gridSize: 1,
    model: graph
});

var regions = [ makeRegion('Foo'), makeRegion('Bar'), makeRegion('Hmm')];
var nodes = [
    makeNode('A'),
    makeNode('B'),
    makeNode('C'),
    makeNode('D'),
    makeNode('F'),
    makeNode('G'),
    makeNode('H'),
    makeNode('I'),
    makeNode('J'),
];
regions[0].embed(nodes[0]).embed(nodes[1]).embed(nodes[2]);
regions[1].embed(nodes[3]).embed(nodes[4]).embed(nodes[5]);
regions[2].embed(nodes[6]).embed(nodes[7]).embed(nodes[8]);
var vertices = [
    connect(regions[0], regions[1]),
    connect(regions[1], regions[2]),
    connect(regions[2], regions[0])
    ];
paper.addCells(regions.concat(nodes).concat(vertices));
joint.layout.DirectedGraph.layout(paper, {
    rankDir: 'LR',
    marginX: 30,
    marginY: 30,
    clusterPadding: {
        top: 30,
        left: 10,
        right: 10,
        bottom: 10
    }
});

function makeNode(name) {
    return new joint.shapes.basic.Rect({
        size: {
            width: 35,
            height: 35
        },
        attrs: {
            text: {
                text: name
            }
        }
    });
}

function connect(a, b) {
    return new joint.shapes.fsa.Arrow({
        source: { id: a.id },
        target: { id: b.id }
    });
}

function makeRegion(name) {
    return new joint.shapes.basic.Rect({
        size: {
            width: 300,
            height: 200
        },
        attrs: {
            text: {
                text: name
            }
        }
    });
}

EDIT:

While I never found a good solution to this problem, I did find that with some extra work, this can be achieved. It assumes that your graph is not too complex with few edges between regions.

If I added the edges separately, after all the nodes had been added to the graph, it would work ok. The directed layout, however, did not work exactly as intended. I used joint.layout.DirectedGraph.layout() initially so that it would align the internal nodes, and then I reposition the out regions. At the end I added the edges, and that produces results similar to the image above.

Here is a rough outline of how I achieved this:

var graph = new joint.dia.Graph;
var paper = new joint.dia.Paper({
    el: $('#paper');
    width: 2000,
    height: 2000,
    gridSize: 1,
    model: graph
});

var regions = [ makeRegion('Foo'), makeRegion('Bar'), makeRegion('Hmm')];
var nodes = [
    makeNode('A'),
    makeNode('B'),
    makeNode('C'),
    makeNode('D'),
    makeNode('F'),
    makeNode('G'),
    makeNode('H'),
    makeNode('I'),
    makeNode('J'),
];
regions[0].embed(nodes[0]).embed(nodes[1]).embed(nodes[2]);
regions[1].embed(nodes[3]).embed(nodes[4]).embed(nodes[5]);
regions[2].embed(nodes[6]).embed(nodes[7]).embed(nodes[8]);

paper.addCells(regions.concat(nodes));
joint.layout.DirectedGraph.layout(paper, {
    rankDir: 'LR',
    marginX: 30,
    marginY: 30,
    clusterPadding: {
        top: 30,
        left: 10,
        right: 10,
        bottom: 10
    }
});

var vertices = [
    connect(regions[0], regions[1]),
    connect(regions[1], regions[2]),
    connect(regions[2], regions[0])
    ];
repositionRegions();
// Add the edges after all the nodes are in their final position
graph.addCells(vertices);

function repositionRegions() {
    // Move regions to where you want them
}

function makeNode(name) {
    return new joint.shapes.basic.Rect({
        size: {
            width: 35,
            height: 35
        },
        attrs: {
            text: {
                text: name
            }
        }
    });
}

function connect(a, b) {
    return new joint.shapes.fsa.Arrow({
        source: { id: a.id },
        target: { id: b.id }
    });
}

function makeRegion(name) {
    return new joint.shapes.basic.Rect({
        size: {
            width: 300,
            height: 200
        },
        attrs: {
            text: {
                text: name
            }
        }
    });
}
Intendment answered 30/3, 2016 at 16:19 Comment(0)
B
1

This is a known issue of Dagre-D3: Automatic layout does not work on hierarchical diagrams with links with parent.

As a workaround, you can either omit the links between the regions (suggested in the edition of your question) or you can do a bit of extra work that will work nicely for any number of nodes:

  1. Layout each group of children nodes separately:

    var fooChildren = [nodes[0], nodes[1], nodes[2]];
    var barChildren = [nodes[3], nodes[4], nodes[5]];
    var hmmChildren = [nodes[6], nodes[7], nodes[8]];
    var children = [fooChildren, barChildren, hmmChildren];
    
    for(var i = 0; i < children.length; i++)
        joint.layout.DirectedGraph.layout(children[i]);
    
  2. For each region, create an auxiliary clone region.

    var clones = [];
    for(var i = 0; i < regions.length; i++) {
        var clone = regions[i].clone();
        graph.addCell(clone);
        clones.push(clone);
    }
    
  3. Set the corresponding nodes to their cloned region.

    for(var i = 0; i < children.length; i ++)
        for(var k = 0; k < children[i].length; k++)
            clones[i].embed(children[i][k]);
    
  4. Fit each cloned region to the size of its children and resize the original region with the size of the cloned region.

    for(var i = 0; i < clones.length; i++) {
        clones[i].fitEmbeds(padding: { top: 30, left: 10, right: 10, bottom: 10 });
        regions[i].resize(clones[i].getBBox().width, clones[i].getBBox().height);
    }
    
  5. Layout the graph.

    joint.layout.DirectedGraph.layout(graph, {
        rankDir: 'LR',
        marginX: 30,
        marginY: 30,
        clusterPadding: {
            top: 30,
            left: 10,
            right: 10,
            bottom: 10
        }
    });
    
  6. Translate the cloned regions to the position of its original region (the children will be translated accordingly).

    for(var i = 0; i < clones.length; i++) {
        var dx = regions[i].getBBox().x - clones[i].getBBox().x;
        var dy = regions[i].getBBox().y - clones[i].getBBox().y;
        clones[i].translate(dx, dy);
    }
    
  7. Remove the cloned regions and set the children to the original regions.

    for(var i = 0; i < regions.length; i++) {
        clones[i].remove();
    
        for(var j = 0; j < children[i].length; j++)
            regions[i].embed(children[i][j]);
    }
    

Hope this can help to provide an alternative solution.

Boito answered 27/6, 2018 at 13:10 Comment(1)
Can you provide a working JSFiddle link with this solution? I've tried it and I'm getting errors like Uncaught TypeError: Cannot read property 'isElement' of undefinedVolga

© 2022 - 2024 — McMap. All rights reserved.