Skip to content

Commit

Permalink
Added support for ports in PipelineEditor Fixes #178
Browse files Browse the repository at this point in the history
WIP #178 Moved delete button to top left

WIP #178 Added basic port rendering

WIP #178 Added labels for ports and fixed input pos's

WIP #178 Added support for connecting op ports

WIP Made PipelineEditor embedded

WIP #178 Added conn deletion

WIP #178 Fixed backwards connecting

WIP #178. Remove port opts that would create a cycle

WIP #178 Added single node delete

WIP #178 Updated connections to ports and removed create btn

WIP #178 Fixed port color on change
  • Loading branch information
brollb committed Jun 2, 2016
1 parent 90964af commit 7d37d1e
Show file tree
Hide file tree
Showing 8 changed files with 510 additions and 42 deletions.
8 changes: 8 additions & 0 deletions src/common/viz/PipelineControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,13 @@ define([
return desc;
};

PipelineControl.prototype.formatIO = function(id) {
var node = this._client.getNode(id);
return {
id: id,
name: node.getAttribute('name')
};
};

return PipelineControl;
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ define([
'use strict';

var OperationDecorator,
NAME_MARGIN = 25,
DECORATOR_ID = 'OperationDecorator';

// Operation nodes need to be able to...
Expand All @@ -23,33 +24,135 @@ define([
// - unhighlight ports
// - report the location of specific ports
OperationDecorator = function (options) {
options.color = options.color || '#78909c';
DecoratorBase.call(this, options);

this.id = this._node.id;
this.$ports = this.$el.append('g')
.attr('id', 'ports');
};

_.extend(OperationDecorator.prototype, DecoratorBase.prototype);

OperationDecorator.prototype.DECORATOR_ID = DECORATOR_ID;
OperationDecorator.prototype.expand = function() {
DecoratorBase.prototype.expand.call(this);
// Add the ports for data inputs/outputs
// TODO
//var inputs = this._node.inputs;
//var outputs = this._node.outputs;
OperationDecorator.prototype.PORT_COLOR = {
OPEN: '#90caf9',
OCCUPIED: '#e57373'
};

OperationDecorator.prototype._highlightPort = function(/*name*/) {
// Highlight port with the given name
OperationDecorator.prototype.condense = function() {
var path,
width,
rx,
ry;

width = Math.max(this.nameWidth + 2 * NAME_MARGIN, this.dense.width);
rx = width/2;
ry = this.dense.height/2;

path = [
`M${-rx},${-ry}`,
`l ${width} 0`,
`l 0 ${this.dense.height}`,
`l -${width} 0`,
`l 0 -${this.dense.height}`
].join(' ');


this.$body
.attr('d', path);

// Clear the attributes
this.$attributes.remove();
this.$attributes = this.$el.append('g')
.attr('fill', '#222222');

this.height = this.dense.height;
this.width = width;

this.$name.attr('y', '0');

this.$el
.attr('transform', `translate(${this.width/2}, ${this.height/2})`);
this.expanded = false;
this.onResize();
};

OperationDecorator.prototype.showPorts = function(ids, areInputs) {
var allPorts = areInputs ? this._node.inputs : this._node.outputs,
ports = ids ? allPorts.filter(port => ids.indexOf(port.id) > -1) : allPorts,
x = -this.width/2,
dx = this.width/(ports.length+1),
y = (this.height/2);

if (areInputs) {
y *= -1;
}

ports.forEach(port => {
x += dx;
this.renderPort(port, x, y, areInputs);
});
};

OperationDecorator.prototype.renderPort = function(port, x, y, isInput) {
var color = this.PORT_COLOR.OPEN,
portIcon = this.$ports.append('g');

// If the port is incoming and occupied, render it differently
if (isInput && port.connection) {
color = this.PORT_COLOR.OCCUPIED;
}

portIcon.append('circle')
.attr('cx', x)
.attr('cy', y)
.attr('r', 10)
.attr('fill', color);

portIcon.append('text')
.attr('x', x)
.attr('y', y)
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
.attr('fill', 'black')
.text(port.name[0]);

portIcon.on('click', this.onPortClick.bind(this, this.id, port.id, !isInput));

// Add tooltip with whole name
// TODO
};

OperationDecorator.prototype.getPortLocation = function(/*name*/) {
OperationDecorator.prototype.hidePorts = function() {
this.logger.info(`hiding ports for ${this.name} (${this.id})`);
this.$ports.remove();
this.$ports = this.$el.append('g')
.attr('id', 'ports');
};

OperationDecorator.prototype.getPortLocation = function(id, isInput) {
// Report location of given port
// TODO
var ports = isInput ? this._node.inputs : this._node.outputs,
i = ports.length-1,
y;

while (i >= 0 && ports[i].id !== id) {
i--;
}
if (i !== -1) {
i += 1;
y = (this.height/2);
return {
x: i * this.width/(ports.length+1),
y: isInput ? y * -1 : y
};
}
return null;
};

OperationDecorator.prototype.unhighlightPort = function(/*name*/) {
// Highlight port with the given name
// TODO
OperationDecorator.prototype.onPortClick = function() {
// Overridden in the widget
};

OperationDecorator.prototype.getDisplayName = function() {
Expand Down
135 changes: 109 additions & 26 deletions src/visualizers/panels/PipelineEditor/PipelineEditorControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ define([

'use strict';

var PipelineEditorControl;
var PipelineEditorControl,
CONN = {
SRC: 'src',
DST: 'dst'
};

PipelineEditorControl = function (options) {
EasyDAGControl.call(this, options);
Expand All @@ -32,6 +36,9 @@ define([
PipelineControl.prototype
);

PipelineEditorControl.prototype._getValidInitialNodes =
PipelineControl.prototype.getValidInitialNodes;

PipelineEditorControl.prototype.TERRITORY_RULE = {children: 3};
PipelineEditorControl.prototype.selectedObjectChanged = function (nodeId) {
var desc = this._getObjectDescriptor(nodeId);
Expand Down Expand Up @@ -90,16 +97,6 @@ define([
this._client.updateTerritory(this._territoryId, this._territories);
};

PipelineEditorControl.prototype.formatIO = function(id) {
var node = this._client.getNode(id);
// This might not be necessary...
//return [
//node.getAttribute('name'),
//node.getBaseId()
//];
return node.getAttribute('name');
};

PipelineEditorControl.prototype.getSiblingContaining = function(containedId) {
var n = this._client.getNode(containedId);
while (n && n.getParentId() !== this._currentNodeId) {
Expand All @@ -108,6 +105,13 @@ define([
return n && n.getId();
};

PipelineEditorControl.prototype._initWidgetEventHandlers = function () {
EasyDAGControl.prototype._initWidgetEventHandlers.call(this);
this._widget.getExistingPortMatches = this.getExistingPortMatches.bind(this);
this._widget.createConnection = this.createConnection.bind(this);
this._widget.removeConnection = this.removeConnection.bind(this);
};

PipelineEditorControl.prototype.isContainedInActive = function (gmeId) {
// Check if the given id is contained in the active node
return gmeId.indexOf(this._currentNodeId) === 0;
Expand Down Expand Up @@ -189,18 +193,97 @@ define([
});
};

PipelineEditorControl.prototype._getValidInitialNodes = function () {
// Get all nodes that have no inputs
return this._getAllValidChildren(this._currentNodeId)
.map(id => this._client.getNode(id))
.filter(node => !node.isAbstract() && !node.isConnection())
// Checking the name (below) is simply convenience so we can
// still create operation prototypes from Operation (which we
// wouldn't be able to do if it was abstract - which it probably
// should be)
.filter(node => node.getAttribute('name') !== 'Operation' &&
this.getOperationInputs(node).length === 0)
.map(node => this._getObjectDescriptor(node.getId()));
PipelineEditorControl.prototype.removeConnection = function (id) {
var conn = this._client.getNode(id),
names,
msg;

names = ['src', 'dst'] // srcPort, srcOp, dstPort, dstOp
.map(type => conn.getPointer(type).to)
.map(portId => [portId, this.getSiblingContaining(portId)])
.reduce((l1, l2) => l1.concat(l2))
.map(id => this._client.getNode(id));

msg = `Disconnecting ${names[0]} of ${names[1]} from ${names[2]} of ${names[3]}`;

this._client.startTransaction(msg);
this._client.delMoreNodes([id]);
this._client.completeTransaction();
};

PipelineEditorControl.prototype.getExistingPortMatches = function (portId, isOutput) {
// Get the children nodeIds
var srcOpId = this.getSiblingContaining(portId),
childrenIds,
skipIds, // Either ancestors or predecessors -> no cycles allowed!
skipType = isOutput ? 'Predecessors' : 'Successors',
method = 'get' + skipType,
matches;

childrenIds = this._client.getNode(this._currentNodeId).getChildrenIds();

// Remove either ancestors or descendents
skipIds = this[method](childrenIds.map(id => this._client.getNode(id)), srcOpId);
childrenIds = _.difference(childrenIds, skipIds);

matches = this._getPortMatchFor(portId, childrenIds, isOutput);

// Get the port matches in the children
return matches.map(tuple => {
return {
nodeId: tuple[0],
portIds: tuple[1]
};
});
};

PipelineEditorControl.prototype._getPortMatchFor = function (portId, opIds, isOutput) {
//opIds = opIds || this._getAllValidChildren(node.getParentId());
var opNodes = opIds.map(id => this._client.getNode(id)),
portType = this._client.getNode(portId).getMetaTypeId(),
getNodes = node => {
var searchType = isOutput ? 'Inputs' : 'Outputs',
searchFn = 'getOperation' + searchType,
dstPorts = this[searchFn](node);

return [
node.getId(),
dstPorts.filter(id => {
var typeId = this._client.getNode(id).getMetaTypeId();
return isOutput ?
this._client.isTypeOf(portType, typeId) :
this._client.isTypeOf(typeId, portType);
})
];
};

return opNodes
.map(getNodes) // Get all valid src/dst ports
.filter(tuple => tuple[1].length);
};

PipelineEditorControl.prototype.createConnection = function (srcId, dstId) {
var connId,
names,
msg;

names = [srcId, dstId] // srcPort, srcOp, dstPort, dstOp
.map(id => [id, this.getSiblingContaining(srcId)])
.reduce((l1, l2) => l1.concat(l2))
.map(id => this._client.getNode(id));

msg = `Connecting ${names[0]} of ${names[1]} to ${names[2]} of ${names[4]}`;

this._client.startTransaction(msg);

connId = this._client.createChild({
parentId: this._currentNodeId,
baseId: this.getConnectionId()
});
this._client.makePointer(connId, CONN.SRC, srcId);
this._client.makePointer(connId, CONN.DST, dstId);

this._client.completeTransaction();
};

PipelineEditorControl.prototype._getPortPairs = function (outputs, inputs) {
Expand Down Expand Up @@ -264,13 +347,12 @@ define([
commitMsg,
root;

// FIXME: This should use the core...
// For now, I am going to try to load the core and use it here...
// This next portion uses the core bc it requires async loading and batching
// into a single commit
var core = new Core(project, {
globConf: WebGMEGlobal.gmeConfig,
logger: this._logger.fork('core')
});
//this._client.startTransaction();
// Load the first node/commit...
core.loadRoot(rootGuid)
.then(_root => {
Expand Down Expand Up @@ -340,6 +422,7 @@ define([
} else if (pairs.length > 1) {
// Else, prompt!
// TODO
this._logger.error('multiple port combinations... This is currently unsupported');
}
};

Expand Down
2 changes: 2 additions & 0 deletions src/visualizers/panels/PipelineEditor/PipelineEditorPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ define(['js/PanelBase/PanelBaseWithHeader',
PanelBaseWithHeader.apply(this, [options, layoutManager]);

this._client = params.client;
this._embedded = params.embedded;

//initialize UI
this._initialize();
Expand All @@ -52,6 +53,7 @@ define(['js/PanelBase/PanelBaseWithHeader',
this.control = new PipelineEditorControl({
logger: this.logger,
client: this._client,
embedded: this._embedded,
widget: this.widget
});

Expand Down
Loading

0 comments on commit 7d37d1e

Please sign in to comment.