Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions packages/joint-layout-directed-graph/DirectedGraph.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ export namespace DirectedGraph {
marginX?: number;
marginY?: number;
resizeClusters?: boolean;
clusterPadding?: dia.Padding;
clusterPadding?: dia.Padding | 'default';
debugTiming?: boolean;
}

interface ImportOptions {
setPosition?: (element: dia.Element, position: dia.BBox) => void;
setVertices?: boolean | ((link: dia.Link, vertices: dia.Point[]) => void);
setLabels?: boolean | ((link: dia.Link, position: dia.Point, points: dia.Point[]) => void);
// deprecated
/** @deprecated use `setVertices` instead */
setLinkVertices?: boolean;
}

Expand All @@ -58,12 +58,12 @@ export namespace DirectedGraph {

export function fromGraphLib(glGraph: any, opt?: FromGraphLibOptions): dia.Graph;

// @deprecated pass the `graph` option instead
/** @deprecated pass the `graph` option instead */
export function fromGraphLib(this: dia.Graph, glGraph: any, opt?: { [key: string]: any }): dia.Graph;

// @deprecated use `FromGraphLibOptions` instead
/** @deprecated use `FromGraphLibOptions` instead */
type fromGraphLibOptions = FromGraphLibOptions;

// @deprecated use `ToGraphLibOptions` instead
/** @deprecated use `ToGraphLibOptions` instead */
type toGraphLibOptions = ToGraphLibOptions;
}
17 changes: 13 additions & 4 deletions packages/joint-layout-directed-graph/DirectedGraph.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,26 @@ export const DirectedGraph = {

importElement: function(nodeId, glGraph, graph, opt) {

var element = graph.getCell(nodeId);
var glNode = glGraph.node(nodeId);
const element = graph.getCell(nodeId);
const glNode = glGraph.node(nodeId);

if (opt.setPosition) {
if (util.isFunction(opt.setPosition)) {
opt.setPosition(element, glNode);
} else {
element.set('position', {
x: glNode.x - glNode.width / 2,
y: glNode.y - glNode.height / 2
});
}

// check if we want to use Dagre's default cluster padding
if (opt.resizeClusters && (opt.clusterPadding === 'default') && (glNode.rank === undefined)) {
// when `glNode.rank === undefined`, it means that the current element is a cluster
element.set('size', {
width: glNode.width,
height: glNode.height
});
} // else: possibly apply numeric `opt.clusterPadding` (see `layout()` function)
},

importLink: function(edgeObj, glGraph, graph, opt) {
Expand Down Expand Up @@ -172,7 +181,7 @@ export const DirectedGraph = {
graph,
});

if (opt.resizeClusters) {
if (opt.resizeClusters && (typeof opt.clusterPadding === 'number')) {
// Resize and reposition cluster elements
// Filter out top-level clusters (nodes without a parent and with children) and map them to cells
const topLevelClusters = glGraph.nodes()
Expand Down
186 changes: 180 additions & 6 deletions packages/joint-layout-directed-graph/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,6 @@ QUnit.module('DirectedGraph', function(hooks) {
assert.deepEqual({ x, y }, { x: 5, y: 210 });
});


QUnit.test('should return a rectangle representing the graph bounding box', function(assert) {

var bbox;
Expand Down Expand Up @@ -315,10 +314,48 @@ QUnit.module('DirectedGraph', function(hooks) {
marginY: -1000
});
assert.deepEqual(bbox.toJSON(), graph.getBBox().toJSON());
});

QUnit.test('resizeClusters: false, clusterPadding: number - should not resize clusters', function(assert) {

const deepestSize = {
width: 500,
height: 500
};

const elements = [
new joint.shapes.standard.Rectangle({ size: { width: 60, height: 60 }}),
new joint.shapes.standard.Rectangle({ size: { width: 120, height: 120 }}),
new joint.shapes.standard.Rectangle({ size: { width: 100, height: 300 }}),
new joint.shapes.standard.Rectangle({ size: deepestSize })
];

elements[0].embed(elements[1]);
elements[1].embed(elements[2]);
elements[2].embed(elements[3]);

graph.resetCells(elements);

const padding = 20;

DirectedGraph.layout(graph, {
resizeClusters: false,
clusterPadding: padding
});

// Sizes remain unchanged
const expectedSizes = [
{ width: 60, height: 60 },
{ width: 120, height: 120 },
{ width: 100, height: 300 },
deepestSize
];
for (let i = 0; i < elements.length; i++) {
assert.deepEqual(elements[i].size(), expectedSizes[i]);
}
});

QUnit.test('should resize clusters', function(assert) {
QUnit.test('resizeClusters: true, clusterPadding: number - should resize clusters according to our algorithm', function(assert) {

const deepestSize = {
width: 500,
Expand All @@ -345,17 +382,17 @@ QUnit.module('DirectedGraph', function(hooks) {
clusterPadding: padding
});

// Parents are resized to fit all children
// - note that we are checking from deepest child up
const nextExpectedSize = deepestSize;

// Parents should be resized to fit all children
for (let i = elements.length - 1; i >= 0; i--) {
assert.deepEqual(elements[i].size(), nextExpectedSize);
nextExpectedSize.width += padding * 2;
nextExpectedSize.height += padding * 2;
}
});

QUnit.test('should not resize clusters if `glGraph` does not hold reference to their children', function(assert) {
QUnit.test('resizeClusters: true, clusterPadding: number - should not resize clusters if `glGraph` does not hold reference to their children', function(assert) {

const containerSize = {
width: 500,
Expand All @@ -374,11 +411,148 @@ QUnit.module('DirectedGraph', function(hooks) {
graph.resetCells([container1, container2, rect1, rect2]);

// Do not pass the children to the layout function
// opt.clusterPadding = `10` by default
DirectedGraph.layout([container1, container2], {
resizeClusters: true
});

// Size remains unchanged
// Sizes remain unchanged
assert.deepEqual(container1.size(), containerSize);
assert.deepEqual(container2.size(), containerSize);
});

QUnit.test('resizeClusters: false, clusterPadding: \'default\' - should not resize clusters', function(assert) {

const deepestSize = {
width: 500,
height: 500
};

const elements = [
new joint.shapes.standard.Rectangle({ size: { width: 60, height: 60 }}),
new joint.shapes.standard.Rectangle({ size: { width: 120, height: 120 }}),
new joint.shapes.standard.Rectangle({ size: { width: 100, height: 300 }}),
new joint.shapes.standard.Rectangle({ size: deepestSize })
];

elements[0].embed(elements[1]);
elements[1].embed(elements[2]);
elements[2].embed(elements[3]);

graph.resetCells(elements);

DirectedGraph.layout(graph, {
resizeClusters: false,
clusterPadding: 'default'
});

// Sizes remain unchanged
const expectedSizes = [
{ width: 60, height: 60 },
{ width: 120, height: 120 },
{ width: 100, height: 300 },
deepestSize
];
for (let i = 0; i < elements.length; i++) {
assert.deepEqual(elements[i].size(), expectedSizes[i]);
}
});

QUnit.test('resizeClusters: true, clusterPadding: \'default\' - should resize nested clusters according to default dagre algorithm', function(assert) {

const deepestSize = {
width: 500,
height: 500
};

const elements = [
new joint.shapes.standard.Rectangle({ size: { width: 60, height: 60 }}),
new joint.shapes.standard.Rectangle({ size: { width: 120, height: 120 }}),
new joint.shapes.standard.Rectangle({ size: { width: 100, height: 300 }}),
new joint.shapes.standard.Rectangle({ size: deepestSize })
];

elements[0].embed(elements[1]);
elements[1].embed(elements[2]);
elements[2].embed(elements[3]);

graph.resetCells(elements);

// opt.resizeClusters = `true` by default
DirectedGraph.layout(graph, {
clusterPadding: 'default'
});

const expectedSizes = [
{ width: 650, height: 650 },
{ width: 610, height: 600 },
{ width: 570, height: 550 },
deepestSize
];
for (let i = 0; i < elements.length; i++) {
assert.deepEqual(elements[i].size(), expectedSizes[i]);
}
});

QUnit.test('resizeClusters: true, clusterPadding: \'default\' - should resize clusters with connected non-embedded children according to default dagre algorithm', function(assert) {

const elements = [
new joint.shapes.standard.Rectangle({ position: { x: 0, y: 0 }, size: { width: 30, height: 30 }}),
new joint.shapes.standard.Rectangle({ position: { x: 100, y: 100 }, size: { width: 30, height: 30 }}),
new joint.shapes.standard.Rectangle({ position: { x: 100, y: 200 }, size: { width: 30, height: 30 }}),
new joint.shapes.standard.Rectangle({ position: { x: 200, y: 200 }, size: { width: 30, height: 30 }}),
];

const links = [
new joint.shapes.standard.Link({ source: elements[1], target: elements[2] }),
new joint.shapes.standard.Link({ source: elements[1], target: elements[3] })
];

elements[0].embed(elements[1]);

graph.resetCells([...elements, ...links]);

// opt.resizeClusters = `true` by default
DirectedGraph.layout(graph, {
clusterPadding: 'default'
});

const expectedBBoxes = [
{ x: 0, y: 0, width: 160, height: 80 },
{ x: 70, y: 25, width: 30, height: 30 },
{ x: 30, y: 205, width: 30, height: 30 },
{ x: 110, y: 205, width: 30, height: 30 }
];
for (let i = 0; i < elements.length; i++) {
assert.deepEqual(elements[i].getBBox(), new joint.g.Rect(expectedBBoxes[i]));
}
});

QUnit.test('resizeClusters: true, clusterPadding: \'default\' - should not resize clusters if `glGraph` does not hold reference to their children', function(assert) {

const containerSize = {
width: 500,
height: 500
};

const container1 = new joint.shapes.standard.Rectangle({ size: containerSize });
const container2 = new joint.shapes.standard.Rectangle({ size: containerSize });

const rect1 = new joint.shapes.standard.Rectangle({ size: { width: 60, height: 60 }});
const rect2 = new joint.shapes.standard.Rectangle({ size: { width: 120, height: 120 }});

container1.embed(rect1);
container2.embed(rect2);

graph.resetCells([container1, container2, rect1, rect2]);

// Do not pass the children to the layout function
// opt.resizeClusters = `true` by default
DirectedGraph.layout([container1, container2], {
clusterPadding: 'default'
});

// Sizes remain unchanged
assert.deepEqual(container1.size(), containerSize);
assert.deepEqual(container2.size(), containerSize);
});
Expand Down
Loading