Skip to content

Commit

Permalink
Merge pull request #20 from epam/feature/graph-layout
Browse files Browse the repository at this point in the history
Feature/graph layout
  • Loading branch information
sidoruka committed May 12, 2017
2 parents de9992f + 36a8b92 commit 81ba15a
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 56 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "pipeline-builder",
"version": "0.3.4-dev",
"version": "0.3.5-dev",
"description": "Pipeline Builder",
"main": "dist/pipeline.js",
"jsnext:main": "dist/pipeline.module.js",
Expand Down
51 changes: 24 additions & 27 deletions src/visual/VisualGroup.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,47 @@
import joint from 'jointjs';
import _ from 'lodash';
import VisualStep from './VisualStep';

const cDefaultWidth = 100;
const cMinHeight = 100;
const cEmbedsPadding = 50;

/** Class that provides graphical representation for the Group.
* @private
*/
export default class VisualGroup extends joint.shapes.devs.Model {
constructor(step, opts = {}) {
super({
position: {
x: (opts.x - cDefaultWidth / 2) || 0,
y: (opts.y - cMinHeight / 2) || 0,
},
export default class VisualGroup extends VisualStep {
/**
* Creates new Group visual representation. Accepts all options for
* joint.shapes.dev.Model and Step and embeds padding values.
*/
constructor(opts = {}) {
super(_.defaultsDeep(opts, {
attrs: {
'.label': {
text: step.type,
text: opts.step.type,
},
},
type: 'VisualGroup',
});

this.step = step;
this.update();
embedsPadding: {
left: cEmbedsPadding,
right: cEmbedsPadding,
top: cEmbedsPadding,
bottom: cEmbedsPadding,
},
}));
}

/**
* Updates visual step according to the model.
*/
update() {
const step = this.step;

this.attr('.label', {
text: step.type,
});
super.update();

if (this.graph) {
this.fitEmbeds({
deep: true,
padding: {
left: 5,
right: 5,
top: 50,
bottom: 50,
},
padding: this.attributes.embedsPadding,
});
}
}

_getLabel() {
return this.step.type;
}
}
18 changes: 6 additions & 12 deletions src/visual/VisualLink.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import _ from 'lodash';
import joint from 'jointjs';

/**
Expand All @@ -6,19 +7,12 @@ import joint from 'jointjs';
*/
export default class VisualLink extends joint.shapes.devs.Link {

constructor(sourceId, sourcePort, targetId, targetPort, conn) {
super({
source: {
id: sourceId,
port: sourcePort,
},
target: {
id: targetId,
port: targetPort,
},
});
constructor(opts) {
super(_.defaultsDeep(opts, {
conn: null,
}));
/** Connection from model. */
this.conn = conn;
this.conn = this.attributes.conn;
}

_check(name, port) {
Expand Down
33 changes: 24 additions & 9 deletions src/visual/VisualStep.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import joint from 'jointjs';
import _ from 'lodash';
import joint from 'jointjs';

const cDefaultWidth = 100;
const cMinHeight = 100;
const cPixelPerSymbol = 15;
const cPixelPerSymbol = 10;
const cLabelMargin = 20;
const cHeightPerPort = 50;

function findMaxLen(strList) {
Expand All @@ -14,21 +15,30 @@ function findMaxLen(strList) {
* @private
*/
export default class VisualStep extends joint.shapes.devs.Model {
constructor(step, opts = {}) {
super({
/**
* Creates new Step visual representation. Accepts all options for
* joint.shapes.dev.Model and various custom parameters.
* @param {object=} opts - Action description.
* @param {Object.<string, Step>} opts.step - Step instance.
* @param {Object.<string, int>} [opts.x=0] - Center x coordinate.
* @param {Object.<string, int>} [opts.y=0] - Center y coordinate.
*/
constructor(opts = { step: null, x: 0, y: 0 }) {
super(_.defaultsDeep(opts, {
position: {
x: (opts.x - cDefaultWidth / 2) || 0,
y: (opts.y - cMinHeight / 2) || 0,
},
attrs: {
'.label': {
text: step.name,
text: opts.step.name,
},
},
type: 'VisualStep',
});
step: opts.step,
}));

this.step = step;
this.step = opts.step;
this.update();
}

Expand All @@ -40,7 +50,8 @@ export default class VisualStep extends joint.shapes.devs.Model {
const inNames = Object.keys(step.i);
const outNames = Object.keys(step.o);
const height = Math.max(cMinHeight, cHeightPerPort * Math.max(inNames.length, outNames.length));
const width = Math.max(cDefaultWidth, step.name.length * cPixelPerSymbol);
const label = this._getLabel();
const width = Math.max(cDefaultWidth, label.length * cPixelPerSymbol + 2 * cLabelMargin);
this.set({
inPorts: inNames,
outPorts: outNames,
Expand All @@ -50,7 +61,7 @@ export default class VisualStep extends joint.shapes.devs.Model {
},
});
this.attr('.label', {
text: step.name,
text: label,
});

const ports = this.getPorts();
Expand Down Expand Up @@ -84,4 +95,8 @@ export default class VisualStep extends joint.shapes.devs.Model {
}
return bbox;
}

_getLabel() {
return this.step.name;
}
}
82 changes: 75 additions & 7 deletions src/visual/Visualizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,57 @@ V.prototype.transform = function fun(matrix, opt) {
return this;
};

function getDescendants(cell, currDepth) {
const embeds = cell.getEmbeddedCells();
let best = { cell, currDepth };
_.forEach(embeds, (child) => {
const chBest = getDescendants(child, currDepth + 1);
if (chBest.currDepth > best.currDepth) {
best = chBest;
}
});
return best;
}

function getHighestDescendant(cell) {
return getDescendants(cell, 0).cell;
}

function createSubstituteCells(graph) {
const newCellsMap = graph.cloneCells(graph.getCells());

const newCells = [];
let cellIdx = 0;
_.forEach(newCellsMap, (newCell, key) => {
const cellProto = graph.getCell(key);
if (newCell.isLink()) {
const protoSrc = cellProto.getSourceElement();
const protoDst = cellProto.getTargetElement();
if (protoDst.isEmbeddedIn(protoSrc, { deep: true }) ||
protoSrc.isEmbeddedIn(protoDst, { deep: true })) {
return;
}
const source = newCellsMap[getHighestDescendant(protoSrc).id];
const target = newCellsMap[getHighestDescendant(protoDst).id];
newCell.get('source').id = source.id;
newCell.get('target').id = target.id;
} else {
const bbox = cellProto.getBBox();
newCell.set('size', {
width: bbox.width,
height: bbox.height,
});
}
newCells[cellIdx] = newCell;
cellIdx += 1;
newCell.proto = cellProto;
});

const gr = new joint.dia.Graph();
gr.addCells(newCells);
return gr;
}

/**
* Class that allows to work with graphical pipeline representation.
*
Expand Down Expand Up @@ -144,6 +195,7 @@ export default class Visualizer {
this._step = step;
this._update();
this.layout();
this._update();// call update once more to invoke fitEmbeds
this.zoom.fitToPage({ padding: 10, maxScale: 1 });
this._timer = setInterval(() => this._update(), 30);

Expand All @@ -154,17 +206,22 @@ export default class Visualizer {
* Layout the contents automatically.
*/
layout() {
const newCells = createSubstituteCells(this._graph);
const settings = {
marginX: 100,
marginY: 10,
rankSep: 230,
rankSep: 100,
nodeSep: 80,
rankDir: 'LR',
setLinkVertices: false,
resizeClusters: false,
resizeClusters: true,
setPosition: (element, glNode) => {
element.proto.set('position', {
x: glNode.x - glNode.width / 2,
y: glNode.y - glNode.height / 2 });
}, // setVertices is ignored
};

joint.layout.DirectedGraph.layout(this._graph, settings);
joint.layout.DirectedGraph.layout(newCells, settings);
}

_loopPorts(ports, source) {
Expand All @@ -180,8 +237,17 @@ export default class Visualizer {
_.find(links, link => link.conn === conn) === undefined &&
!srcIsGroup &&
!dstIsGroup) {
const link = new VisualLink(
source.id, port.name, children[targetName].id, conn.to.name, conn);
const link = new VisualLink({
source: {
id: source.id,
port: port.name,
},
target: {
id: children[targetName].id,
port: conn.to.name,
},
conn,
});
link.addTo(this._graph);
}
});
Expand Down Expand Up @@ -244,14 +310,16 @@ export default class Visualizer {
x: this.paper.el.offsetWidth / 2,
y: this.paper.el.offsetHeight / 2,
});
opts.step = child;
if (!visChild) {
visChild = _.isUndefined(child.type) ? new VisualStep(child, opts) : new VisualGroup(child, opts);
visChild = _.isUndefined(child.type) ? new VisualStep(opts) : new VisualGroup(opts);

children[name] = visChild;

this._graph.addCell(visChild);
if (parent) {
parent.embed(visChild);
parent.update();
}
} else {
// it is essential to update links before the step!
Expand Down

0 comments on commit 81ba15a

Please sign in to comment.