Comparing version 0.0.10 to 0.0.11
@@ -6,4 +6,3 @@ "use strict"; | ||
var workflow_1 = require("./graph/workflow"); | ||
// import * as loaded from "/Users/ivanbatic/Documents/CWL/Whole Genome Analysis - BWA + GATK 2.3.9-Lite (with Metrics).json"; | ||
var loaded = require("../cwl-samples/fastqc.json"); | ||
var loaded = require("/Users/ivanbatic/Documents/CWL/Whole Genome Analysis - BWA + GATK 2.3.9-Lite (with Metrics).json"); | ||
var wf = models_1.WorkflowFactory.from(loaded); | ||
@@ -10,0 +9,0 @@ console.log("Model", wf); |
@@ -5,2 +5,3 @@ /// <reference types="snapsvg" /> | ||
import { Shape } from "./shape"; | ||
import { StepModel, WorkflowInputParameterModel, WorkflowOutputParameterModel } from "cwlts/models"; | ||
export declare type NodePosition = { | ||
@@ -11,19 +12,12 @@ x: number; | ||
export declare class GraphNode extends Shape { | ||
private dataModel; | ||
position: NodePosition; | ||
protected inputs: InputPort[]; | ||
protected outputs: OutputPort[]; | ||
protected paper: Snap.Paper; | ||
protected group: any; | ||
protected radius: number; | ||
private circleGroup; | ||
private dataModel; | ||
private name; | ||
constructor(position: Partial<NodePosition>, dataModel: { | ||
id: string; | ||
label?: string; | ||
}, paper: Snap.Paper); | ||
constructor(position: Partial<NodePosition>, dataModel: WorkflowInputParameterModel | WorkflowOutputParameterModel | StepModel, paper: Snap.Paper); | ||
private makeIconFragment(model); | ||
makeTemplate(): string; | ||
draw(): Snap.Element; | ||
scale(coef: number): void; | ||
create<T>(portType: new (...args: any[]) => T, options: any): T; | ||
protected attachEventListeners(el: any): void; | ||
private static makePortTemplate(port, type, transform?); | ||
addPort(port: OutputPort | InputPort): void; | ||
@@ -41,4 +35,4 @@ /** | ||
*/ | ||
private distributePorts(); | ||
protected drawInnerContent(): void; | ||
distributePorts(): void; | ||
static createPortMatrix(totalPortLength: number, portIndex: number, radius: number, type: "input" | "output"): Snap.Matrix; | ||
} |
@@ -13,5 +13,2 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var input_port_1 = require("./input-port"); | ||
var io_port_1 = require("./io-port"); | ||
var output_port_1 = require("./output-port"); | ||
var shape_1 = require("./shape"); | ||
@@ -23,121 +20,81 @@ var models_1 = require("cwlts/models"); | ||
var _this = _super.call(this) || this; | ||
_this.dataModel = dataModel; | ||
_this.position = { x: 0, y: 0 }; | ||
_this.inputs = []; | ||
_this.outputs = []; | ||
_this.radius = 40; | ||
_this.paper = paper; | ||
_this.dataModel = dataModel; | ||
Object.assign(_this.position, position); | ||
return _this; | ||
} | ||
GraphNode.prototype.makeIconFragment = function (model) { | ||
if (model instanceof models_1.StepModel) { | ||
if (model.run.class == "CommandLineTool") { | ||
return "\n <g class=\"icon icon-tool\">\n <path d=\"M 0 10 h 15\"></path>\n <path d=\"M -10 10 L 0 0 L -10 -10\"></path>\n </g>\n "; | ||
} | ||
else if (model.run.class === "Workflow") { | ||
return "\n <g class=\"icon icon-workflow\">\n <circle cx=\"-8\" cy=\"10\" r=\"3\"></circle>\n <circle cx=\"12\" cy=\"0\" r=\"3\"></circle>\n <circle cx=\"-8\" cy=\"-10\" r=\"3\"></circle>\n <line x1=\"-8\" y1=\"10\" x2=\"12\" y2=\"0\"></line>\n <line x1=\"-8\" y1=\"-10\" x2=\"12\" y2=\"0\"></line>\n </g>\n "; | ||
} | ||
} | ||
return ""; | ||
}; | ||
GraphNode.prototype.makeTemplate = function () { | ||
var _this = this; | ||
var nodeTypeClass = "step"; | ||
if (dataModel instanceof models_1.WorkflowInputParameterModel) { | ||
if (this.dataModel instanceof models_1.WorkflowInputParameterModel) { | ||
nodeTypeClass = "input"; | ||
} | ||
else if (dataModel instanceof models_1.WorkflowOutputParameterModel) { | ||
else if (this.dataModel instanceof models_1.WorkflowOutputParameterModel) { | ||
nodeTypeClass = "output"; | ||
} | ||
_this.group = _this.paper.g().addClass("node " + dataModel.id + " " + nodeTypeClass).attr({ | ||
"data-id": dataModel.id | ||
}); | ||
_this.dataModel = dataModel; | ||
Object.assign(_this.position, position); | ||
return _this; | ||
} | ||
var iconTemplate = this.makeIconFragment(this.dataModel); | ||
var inputPortTemplates = (this.dataModel.in || []) | ||
.filter(function (p) { return p.isVisible; }) | ||
.sort(function (a, b) { return -a.id.localeCompare(b.id); }) | ||
.map(function (p, i, arr) { return GraphNode.makePortTemplate(p, "input", GraphNode.createPortMatrix(arr.length, i, _this.radius, "input").toString()); }) | ||
.reduce(function (acc, tpl) { return acc + tpl; }, ""); | ||
var outputPortTemplates = (this.dataModel.out || []) | ||
.filter(function (p) { return p.isVisible; }) | ||
.sort(function (a, b) { return -a.id.localeCompare(b.id); }) | ||
.map(function (p, i, arr) { return GraphNode.makePortTemplate(p, "output", GraphNode.createPortMatrix(arr.length, i, _this.radius, "output").toString()); }) | ||
.reduce(function (acc, tpl) { return acc + tpl; }, ""); | ||
var template = "\n <g class=\"node " + this.dataModel.id + " " + nodeTypeClass + "\"\n transform=\"matrix(1, 0, 0, 1, " + this.position.x + ", " + this.position.y + ")\"\n data-id=\"" + this.dataModel.id + "\">\n \n <g class=\"drag-handle\" transform=\"matrix(1, 0, 0, 1, 0, 0)\">\n <circle cx=\"0\" cy=\"0\" r=\"" + this.radius + "\" class=\"outer\"></circle>\n <circle cx=\"0\" cy=\"0\" r=\"" + this.radius * .8 + "\" class=\"inner\"></circle>\n " + iconTemplate + "\n </g>\n <text x=\"0\" y=\"" + (this.radius + 30) + "\" class=\"label\">" + (this.dataModel.label || this.dataModel.id) + "</text>\n " + inputPortTemplates + "\n " + outputPortTemplates + "\n </g>\n "; | ||
return template; | ||
}; | ||
GraphNode.prototype.draw = function () { | ||
this.group.transform(new Snap.Matrix().translate(this.position.x, this.position.y)); | ||
var outerCircle = this.paper.circle(0, 0, this.radius).addClass("outer"); | ||
var innerCircle = this.paper.circle(0, 0, this.radius * .8).addClass("inner"); | ||
this.name = this.paper.text(0, this.radius + 30, this.dataModel.label || this.dataModel.id).addClass("label"); | ||
this.circleGroup = this.paper.group(outerCircle, innerCircle).transform("").addClass("drag-handle"); | ||
this.group.add(this.circleGroup, this.name); | ||
var iconFragment = ""; | ||
if (this.dataModel instanceof models_1.StepModel) { | ||
var iconGroup = this.paper.group(); | ||
this.circleGroup.add(iconGroup); | ||
if (this.dataModel.run.class == "CommandLineTool") { | ||
iconGroup.add(this.paper.path("M 0 10 h 15"), this.paper.path("M -10 10 L 0 0 L -10 -10")).addClass("icon icon-tool"); | ||
iconFragment = "\n <g class=\"icon icon-tool\">\n <path d=\"M 0 10 h 15\"></path>\n <path d=\"M -10 10 L 0 0 L -10 -10\"></path>\n </g>\n "; | ||
} | ||
else if (this.dataModel.run.class === "Workflow") { | ||
iconGroup.add(this.paper.circle(-8, 10, 3), this.paper.circle(12, 0, 3), this.paper.circle(-8, -10, 3), this.paper.line(-8, 10, 12, 0), this.paper.line(-8, -10, 12, 0)).addClass("icon icon-workflow"); | ||
iconFragment = "\n <g class=\"icon icon-workflow\">\n <circle cx=\"-8\" cy=\"10\" r=\"3\"></circle>\n <circle cx=\"12\" cy=\"0\" r=\"3\"></circle>\n <circle cx=\"-8\" cy=\"-10\" r=\"3\"></circle>\n <line x1=\"-8\" y1=\"10\" x2=\"12\" y2=\"0\"></line>\n <line x1=\"-8\" y1=\"-10\" x2=\"12\" y2=\"0\"></line>\n </g>\n "; | ||
} | ||
} | ||
this.attachEventListeners(this.circleGroup); | ||
this.group.add(Snap.parse("\n <g class=\"drag-handle\" transform=\"matrix(1, 0, 0, 1, 0, 0)\">\n <circle cx=\"0\" cy=\"0\" r=\"" + this.radius + "\" class=\"outer\"></circle>\n <circle cx=\"0\" cy=\"0\" r=\"" + this.radius * .8 + "\" class=\"inner\"></circle>\n " + iconFragment + "\n </g>\n <text x=\"0\" y=\"" + (this.radius + 30) + "\" class=\"label\">" + (this.dataModel.label || this.dataModel.id) + "</text>\n ")); | ||
// this.attachEventListeners(this.circleGroup); | ||
return this.group; | ||
}; | ||
GraphNode.prototype.scale = function (coef) { | ||
this.circleGroup.transform(this.circleGroup.matrix.clone().scale(coef, coef)); | ||
this.radius = this.circleGroup.getBBox().width / 2; | ||
this.name.attr({ | ||
y: this.radius + 30 | ||
}); | ||
GraphNode.makePortTemplate = function (port, type, transform) { | ||
if (transform === void 0) { transform = "matrix(1, 0, 0, 1, 0, 0)"; } | ||
var portClass = type === "input" ? "input-port" : "output-port"; | ||
var label = port.label || port.id; | ||
var template = "\n <g class=\"port " + portClass + " " + port.id + "\" transform=\"" + (transform || 'matrix(1, 0, 0, 1, 0, 0)') + "\"\n data-connection-id=\"" + port.connectionId + "\"\n data-port-id=\"" + port.id + "\"\n >\n <g class=\"io-port " + port.id + "\">\n <circle cx=\"0\" cy=\"0\" r=\"5\" class=\"port-handle\"></circle>\n </g>\n <text x=\"0\" y=\"0\" class=\"label unselectable\">" + label + "</text>\n </g>\n \n "; | ||
return template; | ||
}; | ||
GraphNode.prototype.create = function (portType, options) { | ||
switch (portType) { | ||
case input_port_1.InputPort: | ||
case output_port_1.OutputPort: | ||
return new portType(this.paper, options); | ||
default: | ||
throw new Error("Cannot create IOPort of type: " + portType); | ||
} | ||
}; | ||
GraphNode.prototype.attachEventListeners = function (el) { | ||
var _this = this; | ||
var groupBBox; | ||
var localMatrix; | ||
var globalMatrix; | ||
var inputEdges = new Map(); | ||
var outputEdges = new Map(); | ||
var scaleReverse; | ||
el.hover(function () { | ||
_this.group.toFront(); | ||
}); | ||
el.drag(function (dx, dy) { | ||
var moveX = dx * scaleReverse; | ||
var moveY = dy * scaleReverse; | ||
_this.group.transform(localMatrix.clone().translate(moveX, moveY)); | ||
inputEdges.forEach(function (path, edge) { | ||
edge.attr({ | ||
d: io_port_1.IOPort.makeConnectionPath(path[0][1], path[0][2], path[1][5] + moveX, path[1][6] + moveY) | ||
}); | ||
}); | ||
outputEdges.forEach(function (path, edge) { | ||
edge.attr({ | ||
d: io_port_1.IOPort.makeConnectionPath(path[0][1] + moveX, path[0][2] + moveY, path[1][5], path[1][6]) | ||
}); | ||
}); | ||
}, function (x, y, ev) { | ||
groupBBox = _this.group.getBBox(); | ||
localMatrix = _this.group.transform().localMatrix; | ||
globalMatrix = _this.group.transform().globalMatrix; | ||
scaleReverse = 1 / globalMatrix.get(3); | ||
document.querySelectorAll(".in-" + _this.dataModel.id + " .sub-edge") | ||
.forEach(function (edge) { | ||
inputEdges.set(Snap(edge), Snap.parsePathString(edge.getAttribute("d"))); | ||
}); | ||
document.querySelectorAll(".out-" + _this.dataModel.id + " .sub-edge") | ||
.forEach(function (edge) { | ||
outputEdges.set(Snap(edge), Snap.parsePathString(edge.getAttribute("d"))); | ||
}); | ||
}, function (ev) { | ||
inputEdges.clear(); | ||
outputEdges.clear(); | ||
}); | ||
}; | ||
GraphNode.prototype.addPort = function (port) { | ||
var portClass = "input-port"; | ||
var portStore = this.inputs; | ||
if (port instanceof output_port_1.OutputPort) { | ||
portClass = "output-port"; | ||
portStore = this.outputs; | ||
} | ||
var drawn = port.draw().addClass(portClass); | ||
this.group.add(drawn); | ||
portStore.push(port); | ||
var template = GraphNode.makePortTemplate(port); | ||
this.group.add(Snap.parse(template)); | ||
// Ports should be sorted in reverse to comply with the SBG platform's coordinate positioning | ||
portStore = portStore.sort(function (a, b) { return -a.portModel.id.localeCompare(b.portModel.id); }); | ||
// portStore = portStore.sort((a, b) => -a.portModel.id.localeCompare(b.portModel.id)); | ||
this.distributePorts(); | ||
if (portStore.length > 6 && portStore.length <= 20) { | ||
var _a = portStore.slice(-2).map(function (i) { return i.group.getBBox(); }), a = _a[0], b = _a[1]; | ||
var overlapping = a.y + a.height >= b.y; | ||
if (overlapping) { | ||
this.scale(1.08); | ||
this.distributePorts(); | ||
} | ||
} | ||
// if (portStore.length > 6 && portStore.length <= 20) { | ||
// | ||
// const [a, b] = portStore.slice(-2).map(i => i.group.getBBox()); | ||
// const overlapping = a.y + a.height >= b.y; | ||
// if (overlapping) { | ||
// this.scale(1.08); | ||
// this.distributePorts(); | ||
// } | ||
// } | ||
}; | ||
@@ -162,6 +119,8 @@ /** | ||
GraphNode.prototype.distributePorts = function () { | ||
var outputs = Array.from(this.group.node.querySelectorAll(".output-port")).map(function (p) { return Snap(p); }); | ||
var inputs = Array.from(this.group.node.querySelectorAll(".input-port")).map(function (p) { return Snap(p); }); | ||
var availableAngle = 140; | ||
var rotationAngle; | ||
// Distribute output ports | ||
for (var i = 0; i < this.outputs.length; i++) { | ||
for (var i = 0; i < outputs.length; i++) { | ||
rotationAngle = | ||
@@ -173,7 +132,7 @@ // Starting rotation angle | ||
(i + 1) | ||
* availableAngle / (this.outputs.length + 1)); | ||
GraphNode.movePortToOuterEdge(this.outputs[i].group, rotationAngle, this.radius); | ||
* availableAngle / (outputs.length + 1)); | ||
GraphNode.movePortToOuterEdge(outputs[i], rotationAngle, this.radius); | ||
} | ||
// Distribute input ports | ||
for (var i = 0; i < this.inputs.length; i++) { | ||
for (var i = 0; i < inputs.length; i++) { | ||
rotationAngle = | ||
@@ -183,7 +142,26 @@ // Determines the starting rotation angle | ||
- (i + 1) | ||
* availableAngle / (this.inputs.length + 1); | ||
GraphNode.movePortToOuterEdge(this.inputs[i].group, rotationAngle, this.radius); | ||
* availableAngle / (inputs.length + 1); | ||
GraphNode.movePortToOuterEdge(inputs[i], rotationAngle, this.radius); | ||
} | ||
}; | ||
GraphNode.prototype.drawInnerContent = function () { | ||
GraphNode.createPortMatrix = function (totalPortLength, portIndex, radius, type) { | ||
var availableAngle = 140; | ||
var rotationAngle = | ||
// Starting rotation angle | ||
(-availableAngle / 2) + | ||
( | ||
// Angular offset by element index | ||
(portIndex + 1) | ||
* availableAngle / (totalPortLength + 1)); | ||
if (type === "input") { | ||
rotationAngle = | ||
// Determines the starting rotation angle | ||
180 - (availableAngle / -2) | ||
- (portIndex + 1) | ||
* availableAngle / (totalPortLength + 1); | ||
} | ||
return new Snap.Matrix() | ||
.rotate(rotationAngle, 0, 0) | ||
.translate(radius, 0) | ||
.rotate(-rotationAngle, 0, 0); | ||
}; | ||
@@ -190,0 +168,0 @@ return GraphNode; |
/// <reference types="snapsvg" /> | ||
import { WorkflowStepInputModel } from "cwlts/models"; | ||
import { WorkflowStepOutputModel } from "cwlts/models"; | ||
import { WorkflowStepInputModel, WorkflowStepOutputModel } from "cwlts/models"; | ||
import { Shape } from "./shape"; | ||
@@ -13,13 +12,4 @@ export declare class IOPort extends Shape { | ||
protected handle: Snap.Element; | ||
protected title: Snap.Element; | ||
protected drawingElements: { | ||
circleGroup: any; | ||
innerCircle: any; | ||
outerCircle: any; | ||
title: any; | ||
}; | ||
constructor(paper: Snap.Paper, portModel: any); | ||
protected drawHandle(): Snap.Element; | ||
protected drawTitle(content: any): Snap.Element; | ||
draw(): Snap.Element; | ||
private attachDragBehaviour(el); | ||
@@ -26,0 +16,0 @@ private attachDrop(); |
@@ -20,8 +20,2 @@ "use strict"; | ||
_this.connectionFormat = "M {x1} {y1}, C {bx1} {by1} {bx2} {by2} {x2} {y2}"; | ||
_this.drawingElements = { | ||
circleGroup: null, | ||
innerCircle: null, | ||
outerCircle: null, | ||
title: null | ||
}; | ||
_this.paper = paper; | ||
@@ -36,15 +30,2 @@ _this.portModel = portModel; | ||
}; | ||
IOPort.prototype.drawTitle = function (content) { | ||
return this.paper.text(0, 0, content); | ||
}; | ||
IOPort.prototype.draw = function () { | ||
this.handle = this.drawHandle(); | ||
var _a = this.portModel.connectionId.split("/"), id = _a[2]; | ||
this.title = this.drawTitle(this.portModel.label || id).addClass("label unselectable"); | ||
this.drawingElements.circleGroup = this.handle; | ||
this.group = this.paper.group(this.drawingElements.circleGroup, this.title).addClass("port"); | ||
// this.attachEventListeners(this.drawingElements.circleGroup); | ||
// this.attachDrop(); | ||
return this.group; | ||
}; | ||
IOPort.prototype.attachDragBehaviour = function (el) { | ||
@@ -95,7 +76,7 @@ var _this = this; | ||
if (!forceDirection) { | ||
return "M " + x1 + " " + y1 + ", C " + (x1 + x2) / 2 + " " + y1 + " " + (x1 + x2) / 2 + " " + y2 + " " + x2 + " " + y2; | ||
return "M " + x1 + " " + y1 + " C " + (x1 + x2) / 2 + " " + y1 + " " + (x1 + x2) / 2 + " " + y2 + " " + x2 + " " + y2; | ||
} | ||
var outDir = x1 + Math.abs(x1 - x2) / 2; | ||
var inDir = x2 - Math.abs(x1 - x2) / 2; | ||
return "M " + x1 + " " + y1 + ", C " + outDir + " " + y1 + " " + inDir + " " + y2 + " " + x2 + " " + y2; | ||
return "M " + x1 + " " + y1 + " C " + outDir + " " + y1 + " " + inDir + " " + y2 + " " + x2 + " " + y2; | ||
}; | ||
@@ -102,0 +83,0 @@ IOPort.prototype.makePathStringBetween = function (x1, y1, x2, y2) { |
@@ -1,3 +0,2 @@ | ||
export declare abstract class Shape { | ||
abstract draw(): Snap.Element; | ||
export declare class Shape { | ||
} |
/// <reference types="snapsvg" /> | ||
import { WorkflowModel } from "cwlts/models"; | ||
import "snapsvg-cjs"; | ||
import { EventHub } from "../utils/event-hub"; | ||
import "snapsvg-cjs"; | ||
export declare class Workflow { | ||
@@ -10,7 +10,22 @@ private paper; | ||
readonly eventHub: EventHub; | ||
constructor(paper: Snap.Paper, model?: WorkflowModel); | ||
private domEvents; | ||
private workflow; | ||
private svgRoot; | ||
private model; | ||
constructor(paper: Snap.Paper, model: WorkflowModel); | ||
command(event: string, ...data: any[]): void; | ||
on(event: string, handler: any): void; | ||
off(event: any, handler: any): void; | ||
getScale(): number; | ||
private renderModel(model); | ||
private attachEvents(); | ||
private addEventListeners(root); | ||
private highlightEdge(el); | ||
private adaptToScale(x); | ||
deselectEverything(): void; | ||
translateMouseCoords(x: any, y: any): { | ||
x: any; | ||
y: any; | ||
}; | ||
private attachEdgeHoverBehavior(el); | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var models_1 = require("cwlts/models"); | ||
require("snapsvg-cjs"); | ||
var event_hub_1 = require("../utils/event-hub"); | ||
var app_node_1 = require("./app-node"); | ||
var graph_node_1 = require("./graph-node"); | ||
var edge_1 = require("./edge"); | ||
var dom_events_1 = require("../utils/dom-events"); | ||
var io_port_1 = require("./io-port"); | ||
require("snapsvg-cjs"); | ||
Snap.plugin(function (Snap, Element, Paper, glob) { | ||
var geometry_1 = require("../utils/geometry"); | ||
Snap.plugin(function (Snap, Element) { | ||
var proto = Element.prototype; | ||
@@ -21,4 +26,8 @@ proto.toFront = function () { | ||
this.paper = paper; | ||
var dragRect = this.paper.rect(0, 0, "100%", "100%"); | ||
this.group = this.paper.group().addClass("workflow"); | ||
this.svgRoot = paper.node; | ||
this.model = model; | ||
this.domEvents = new dom_events_1.DomEvents(this.paper.node); | ||
this.paper.node.innerHTML = "\n <rect x=\"0\" y=\"0\" width=\"100%\" height=\"100%\" class=\"pan-handle\" transform=\"matrix(1,0,0,1,0,0)\"></rect>\n <g class=\"workflow\" transform=\"matrix(1,0,0,1,0,0)\"></g>\n "; | ||
this.workflow = this.paper.node.querySelector(".workflow"); | ||
this.group = Snap(this.workflow); | ||
this.paper.node.addEventListener("mousewheel", function (ev) { | ||
@@ -33,23 +42,7 @@ var newScale = _this.getScale() + ev.deltaY / 500; | ||
}, true); | ||
this.paper.node.addEventListener("click", function (ev) { | ||
var node = ev.path.find(function (el) { return el.classList && el.classList.contains("node"); }); | ||
if (!node) { | ||
return; | ||
} | ||
console.log("Click on node", node); | ||
}); | ||
{ | ||
var originalMatrix_1; | ||
dragRect.drag(function (dx, dy) { | ||
_this.group.transform(originalMatrix_1.clone().translate(dx, dy)); | ||
}, function () { | ||
originalMatrix_1 = _this.group.transform().localMatrix; | ||
}, function () { | ||
}); | ||
} | ||
this.eventHub = new event_hub_1.EventHub([ | ||
/** @link connection.create */ | ||
"connection.create", | ||
/** @link app.create */ | ||
"app.create", | ||
/** @link app.create.step */ | ||
"app.create.step", | ||
/** @link app.create.input */ | ||
@@ -70,2 +63,5 @@ "app.create.input", | ||
} | ||
console.time("Event Listeners"); | ||
this.addEventListeners(this.paper.node); | ||
console.timeEnd("Event Listeners"); | ||
} | ||
@@ -80,2 +76,8 @@ Workflow.prototype.command = function (event) { | ||
}; | ||
Workflow.prototype.on = function (event, handler) { | ||
this.eventHub.on(event, handler); | ||
}; | ||
Workflow.prototype.off = function (event, handler) { | ||
this.eventHub.off(event, handler); | ||
}; | ||
Workflow.prototype.getScale = function () { | ||
@@ -87,8 +89,26 @@ return this.scale; | ||
console.time("Graph Rendering"); | ||
model.steps.forEach(function (s) { return _this.command("app.create", s); }); | ||
model.outputs.filter(function (o) { return o.isVisible; }).forEach(function (o) { return _this.command("app.create.output", o); }); | ||
model.inputs.filter(function (e) { return e.isVisible; }).forEach(function (e) { return _this.command("app.create.input", e); }); | ||
model.connections.forEach(function (c) { return _this.command("connection.create", c); }); | ||
var nodes = model.steps.concat(model.inputs, model.outputs).filter(function (n) { return n.isVisible; }); | ||
var graphNodes = nodes.map(function (n) { | ||
var patch = [{ connectionId: n.connectionId, isVisible: true, id: n.id }]; | ||
if (n instanceof models_1.WorkflowInputParameterModel) { | ||
var copy = Object.create(n); | ||
return Object.assign(copy, { out: patch }); | ||
} | ||
else if (n instanceof models_1.WorkflowOutputParameterModel) { | ||
var copy = Object.create(n); | ||
return Object.assign(copy, { in: patch }); | ||
} | ||
return n; | ||
}).map(function (node) { return new graph_node_1.GraphNode({ | ||
x: node.customProps["sbg:x"] || Math.random() * 500, | ||
y: node.customProps["sbg:y"] || Math.random() * 500 | ||
}, node, _this.paper); }); | ||
var nodesTpl = graphNodes.reduce(function (tpl, node) { return tpl + node.makeTemplate(); }, ""); | ||
this.workflow.innerHTML += nodesTpl; | ||
var edgesTpl = model.connections.map(function (c) { return edge_1.Edge.makeTemplate(c, _this.paper); }).reduce(function (acc, tpl) { return acc + tpl; }, ""); | ||
this.workflow.innerHTML += edgesTpl; | ||
console.timeEnd("Graph Rendering"); | ||
console.time("Ordering"); | ||
document.querySelectorAll(".node").forEach(function (e) { return Snap(e).toFront(); }); | ||
console.timeEnd("Graph Rendering"); | ||
console.timeEnd("Ordering"); | ||
}; | ||
@@ -101,5 +121,7 @@ Workflow.prototype.attachEvents = function () { | ||
this.eventHub.on("app.create.input", function (input) { | ||
_this.command("app.create", Object.assign(input, { | ||
_this.command("app.create.step", Object.assign(input, { | ||
out: [{ | ||
connectionId: input.connectionId, isVisible: true | ||
id: input.id, | ||
connectionId: input.connectionId, | ||
isVisible: true | ||
}] | ||
@@ -112,4 +134,5 @@ })); | ||
this.eventHub.on("app.create.output", function (output) { | ||
_this.command("app.create", Object.assign(output, { | ||
_this.command("app.create.step", Object.assign(output, { | ||
in: [{ | ||
id: output.id, | ||
connectionId: output.connectionId, | ||
@@ -121,5 +144,5 @@ isVisible: true | ||
/** | ||
* @name app.create | ||
* @name app.create.step | ||
*/ | ||
this.eventHub.on("app.create", function (step) { | ||
this.eventHub.on("app.create.step", function (step) { | ||
var n = new app_node_1.AppNode({ | ||
@@ -129,3 +152,3 @@ x: step.customProps["sbg:x"] || Math.random() * 1000, | ||
}, step, _this.paper); | ||
_this.group.add(n.draw()); | ||
_this.workflow.innerHTML += n.makeTemplate(); | ||
}); | ||
@@ -136,50 +159,3 @@ /** | ||
this.eventHub.on("connection.create", function (connection) { | ||
if (!connection.isVisible || connection.source.type === "Step" || connection.destination.type === "Step") { | ||
return; | ||
} | ||
var _a = connection.source.id.split("/"), sourceSide = _a[0], sourceStepId = _a[1], sourcePort = _a[2]; | ||
var _b = connection.destination.id.split("/"), destSide = _b[0], destStepId = _b[1], destPort = _b[2]; | ||
var sourceVertex = Snap("." + sourceStepId + " .output-port ." + sourcePort); | ||
var destVertex = Snap("." + destStepId + " .input-port ." + destPort); | ||
var sourceNode = Snap(".node." + sourceStepId); | ||
var destNode = Snap(".node." + destStepId); | ||
if (connection.source.type === connection.destination.type) { | ||
console.error("Cant draw connection between nodes of the same type.", connection); | ||
return; | ||
} | ||
if (!sourceVertex.node) { | ||
console.error("Source vertex not found for edge " + connection.source.id, connection); | ||
return; | ||
} | ||
if (!destVertex.node) { | ||
console.error("Destination vertex not found for edge " + connection.destination.id, connection); | ||
return; | ||
} | ||
var sourceRect = sourceVertex.node.getBoundingClientRect(); | ||
var destRect = destVertex.node.getBoundingClientRect(); | ||
var paperRect = _this.paper.node.getBoundingClientRect(); | ||
var portRadiusOffset = sourceRect.width / 2; | ||
var pathStr = io_port_1.IOPort.makeConnectionPath(sourceRect.left + portRadiusOffset - paperRect.left, sourceRect.top + portRadiusOffset - paperRect.top, destRect.left + portRadiusOffset - paperRect.left, destRect.top + portRadiusOffset - paperRect.top); | ||
var outerPath = _this.paper.path(pathStr).addClass("outer sub-edge"); | ||
var innerPath = _this.paper.path(pathStr).addClass("inner sub-edge"); | ||
var pathGroup = _this.paper.group(outerPath, innerPath).addClass("edge " + sourceSide + "-" + sourceStepId + " " + destSide + "-" + destStepId); | ||
_this.group.add(pathGroup); | ||
var hoverClass = "edge-hover"; | ||
var connectionLabel; | ||
pathGroup.hover(function (ev, x, y) { | ||
sourceNode.addClass(hoverClass); | ||
destNode.addClass(hoverClass); | ||
var sourceLabel = sourceStepId === sourcePort ? sourcePort : sourceStepId + "/" + sourcePort; | ||
var destLabel = destStepId === destPort ? destPort : destStepId + "/" + destPort; | ||
connectionLabel = _this.paper | ||
.text(x, y - 16, sourceLabel + " \u2194 " + destLabel) | ||
.addClass("label label-edge"); | ||
pathGroup.add(connectionLabel); | ||
pathGroup.toFront(); | ||
}, function () { | ||
sourceNode.removeClass(hoverClass); | ||
destNode.removeClass(hoverClass); | ||
connectionLabel.remove(); | ||
pathGroup.toBack(); | ||
}); | ||
_this.workflow.innerHTML += edge_1.Edge.makeTemplate(connection, _this.paper); | ||
}); | ||
@@ -265,5 +241,2 @@ /** | ||
var wfBounds = _this.group.node.getBoundingClientRect(); | ||
// if (clientBounds.width > wfBounds.width && clientBounds.height > wfBounds.height) { | ||
// return; | ||
// } | ||
var padding = 200; | ||
@@ -281,2 +254,248 @@ var verticalScale = (wfBounds.height + padding) / paperHeight; | ||
}; | ||
Workflow.prototype.addEventListeners = function (root) { | ||
var _this = this; | ||
/** | ||
* Whenever a click happens on a blank space, remove selections | ||
*/ | ||
this.domEvents.on("click", "*", function (ev, el, root) { | ||
_this.deselectEverything(); | ||
}); | ||
/** | ||
* Whenever a click happens on a node, select that node and | ||
* highlight all connecting edges and adjacent vertices | ||
* while shadowing others. | ||
*/ | ||
this.domEvents.on("click", ".node", function (ev, el, root) { | ||
_this.deselectEverything(); | ||
_this.workflow.classList.add("has-selection"); | ||
var nodeID = el.getAttribute("data-id"); | ||
Array.from(root.querySelectorAll(".edge." + nodeID)).forEach(function (edge) { | ||
edge.classList.add("highlighted"); | ||
var sourceNodeID = edge.getAttribute("data-source-node"); | ||
var destinationNodeID = edge.getAttribute("data-destination-node"); | ||
Array.from(root.querySelectorAll(".node." + sourceNodeID + ", .node." + destinationNodeID)).forEach(function (el) { | ||
el.classList.add("highlighted"); | ||
}); | ||
}); | ||
el.classList.add("selected"); | ||
}); | ||
/** | ||
* Move nodes and edges on drag | ||
*/ | ||
{ | ||
var startX_1; | ||
var startY_1; | ||
var inputEdges_1; | ||
var outputEdges_1; | ||
this.domEvents.drag(".node .drag-handle", function (dx, dy, ev, handle) { | ||
var el = handle.parentNode; | ||
var sdx = _this.adaptToScale(dx); | ||
var sdy = _this.adaptToScale(dy); | ||
el.transform.baseVal.getItem(0).setTranslate(startX_1 + sdx, startY_1 + sdy); | ||
inputEdges_1.forEach(function (p, el) { | ||
el.setAttribute("d", io_port_1.IOPort.makeConnectionPath(p[0], p[1], p[6] + sdx, p[7] + sdy)); | ||
}); | ||
outputEdges_1.forEach(function (p, el) { | ||
el.setAttribute("d", io_port_1.IOPort.makeConnectionPath(p[0] + sdx, p[1] + sdy, p[6], p[7])); | ||
}); | ||
}, function (ev, handle, root) { | ||
var el = handle.parentNode; | ||
var matrix = el.transform.baseVal.getItem(0).matrix; | ||
startX_1 = matrix.e; | ||
startY_1 = matrix.f; | ||
inputEdges_1 = new Map(); | ||
outputEdges_1 = new Map(); | ||
Array.from(root.querySelectorAll(".edge[data-destination-node='" + el.getAttribute("data-id") + "'] .sub-edge")) | ||
.forEach(function (el) { | ||
inputEdges_1.set(el, el.getAttribute("d").split(" ").map(function (e) { return Number(e); }).filter(function (e) { return !isNaN(e); })); | ||
}); | ||
Array.from(root.querySelectorAll(".edge[data-source-node='" + el.getAttribute("data-id") + "'] .sub-edge")) | ||
.forEach(function (el) { | ||
outputEdges_1.set(el, el.getAttribute("d").split(" ").map(function (e) { return Number(e); }).filter(function (e) { return !isNaN(e); })); | ||
}); | ||
}, function () { | ||
inputEdges_1 = undefined; | ||
outputEdges_1 = undefined; | ||
}); | ||
} | ||
/** | ||
* Attach canvas panning | ||
*/ | ||
{ | ||
var pane_1; | ||
var x_1; | ||
var y_1; | ||
var matrix_1; | ||
this.domEvents.drag(".pan-handle", function (dx, dy, ev, el, root) { | ||
matrix_1.e = x_1 + dx; | ||
matrix_1.f = y_1 + dy; | ||
pane_1.transform.baseVal.getItem(0).setMatrix(matrix_1); | ||
}, function (ev, el, root) { | ||
pane_1 = root.querySelector(".workflow"); | ||
matrix_1 = pane_1.transform.baseVal.getItem(0).matrix.scale(1); | ||
x_1 = matrix_1.e; | ||
y_1 = matrix_1.f; | ||
}, function () { | ||
pane_1 = undefined; | ||
matrix_1 = undefined; | ||
}); | ||
} | ||
/** | ||
* Edge Selection | ||
*/ | ||
this.domEvents.on("click", ".edge", function (ev, target, root) { | ||
_this.highlightEdge(target); | ||
target.classList.add("selected"); | ||
}); | ||
/** | ||
* Manually wire up hovering on edges because mouseenters don't propagate | ||
*/ | ||
{ | ||
Array.from(this.workflow.querySelectorAll(".edge")).forEach(function (el) { | ||
_this.attachEdgeHoverBehavior(el); | ||
}); | ||
} | ||
/** | ||
* On mouse over node, bring it to the front | ||
*/ | ||
this.domEvents.on("mouseover", ".node", function (ev, target, root) { | ||
if (_this.workflow.querySelector(".edge.dragged")) { | ||
return; | ||
} | ||
target.parentElement.append(target); | ||
}); | ||
/** | ||
* Drag a path from the port | ||
*/ | ||
{ | ||
var edge_2; | ||
var subEdges_1; | ||
var connectionPorts_1; | ||
var highlightedNode_1; | ||
this.domEvents.drag(".port", function (dx, dy, ev, target) { | ||
var ctm = target.getScreenCTM(); | ||
var coords = _this.translateMouseCoords(ev.clientX, ev.clientY); | ||
var origin = _this.translateMouseCoords(ctm.e, ctm.f); | ||
subEdges_1.forEach(function (el) { | ||
el.setAttribute("d", io_port_1.IOPort.makeConnectionPath(origin.x, origin.y, coords.x, coords.y)); | ||
}); | ||
var sorted = connectionPorts_1.map(function (el) { | ||
var ctm = el.wfCTM; | ||
el.distance = geometry_1.Geometry.distance(coords.x, coords.y, ctm.e, ctm.f); | ||
return el; | ||
}).sort(function (el1, el2) { | ||
return el1.distance - el2.distance; | ||
}); | ||
if (highlightedNode_1) { | ||
highlightedNode_1.classList.remove("highlighted"); | ||
} | ||
if (sorted.length && sorted[0].distance < 150) { | ||
highlightedNode_1 = sorted[0]; | ||
highlightedNode_1.classList.add("highlighted"); | ||
} | ||
}, function (ev, target, root) { | ||
edge_2 = edge_1.Edge.spawn(); | ||
edge_2.classList.add("eventless", "dragged"); | ||
_this.workflow.appendChild(edge_2); | ||
subEdges_1 = Array.from(edge_2.querySelectorAll(".sub-edge")); | ||
var targetConnectionId = target.getAttribute("data-connection-id"); | ||
connectionPorts_1 = (_this.model.gatherValidConnectionPoints(targetConnectionId) || []) | ||
.filter(function (point) { return point.isVisible; }) | ||
.map(function (p) { | ||
var el = _this.workflow.querySelector("[data-connection-id=\"" + p.connectionId + "\"]"); | ||
el.wfCTM = geometry_1.Geometry.getTransformToElement(el, _this.workflow); | ||
el.classList.add("connection-candidate"); | ||
return el; | ||
}); | ||
connectionPorts_1.forEach(function (el) { | ||
var parentNode; | ||
while ((parentNode = el.parentNode) && !parentNode.classList.contains("node")) | ||
; | ||
parentNode.classList.add("highlighted", "connection-candidate"); | ||
}); | ||
_this.workflow.classList.add("has-selection"); | ||
}, function (ev, target) { | ||
edge_2.remove(); | ||
edge_2 = undefined; | ||
if (highlightedNode_1) { | ||
var newEdge = edge_1.Edge.spawnBetweenConnectionIDs(_this.workflow, target.getAttribute("data-connection-id"), highlightedNode_1.getAttribute("data-connection-id")); | ||
_this.attachEdgeHoverBehavior(newEdge); | ||
highlightedNode_1.classList.remove("highlighted"); | ||
highlightedNode_1 = undefined; | ||
} | ||
Array.from(_this.workflow.querySelectorAll(".connection-candidate")).forEach(function (el) { | ||
el.classList.remove("connection-candidate", "highlighted"); | ||
}); | ||
_this.workflow.classList.remove("has-selection"); | ||
subEdges_1 = undefined; | ||
connectionPorts_1 = undefined; | ||
}); | ||
} | ||
}; | ||
Workflow.prototype.highlightEdge = function (el) { | ||
var sourceNode = el.getAttribute("data-source-node"); | ||
var destNode = el.getAttribute("data-destination-node"); | ||
var sourcePort = el.getAttribute("data-source-port"); | ||
var destPort = el.getAttribute("data-destination-port"); | ||
Array.from(this.workflow.querySelectorAll(".node." + sourceNode + " .output-port." + sourcePort + ", " | ||
+ (".node." + destNode + " .input-port." + destPort))).forEach(function (el) { | ||
el.classList.add("highlighted"); | ||
}); | ||
}; | ||
Workflow.prototype.adaptToScale = function (x) { | ||
return x * (1 / this.scale); | ||
}; | ||
Workflow.prototype.deselectEverything = function () { | ||
Array.from(this.workflow.querySelectorAll(".highlighted")).forEach(function (el) { | ||
el.classList.remove("highlighted"); | ||
}); | ||
this.workflow.classList.remove("has-selection"); | ||
var selected = this.workflow.querySelector(".selected"); | ||
if (selected) { | ||
selected.classList.remove("selected"); | ||
} | ||
}; | ||
Workflow.prototype.translateMouseCoords = function (x, y) { | ||
var svg = this.paper.node; | ||
var wf = svg.querySelector(".workflow"); | ||
var ctm = wf.getScreenCTM(); | ||
var point = svg.createSVGPoint(); | ||
point.x = x; | ||
point.y = y; | ||
var t = point.matrixTransform(ctm.inverse()); | ||
return { | ||
x: t.x, | ||
y: t.y | ||
}; | ||
}; | ||
Workflow.prototype.attachEdgeHoverBehavior = function (el) { | ||
var _this = this; | ||
var tipEl; | ||
this.domEvents.hover(el, function (ev, target) { | ||
var coords = _this.translateMouseCoords(ev.clientX, ev.clientY); | ||
tipEl.setAttribute("x", coords.x); | ||
tipEl.setAttribute("y", coords.y - 16); | ||
}, function (ev, target) { | ||
var sourceNode = target.getAttribute("data-source-node"); | ||
var destNode = target.getAttribute("data-destination-node"); | ||
var sourcePort = target.getAttribute("data-source-port"); | ||
var destPort = target.getAttribute("data-destination-port"); | ||
var sourceLabel = sourceNode === sourcePort ? sourceNode : sourceNode + " (" + sourcePort + ")"; | ||
var destLabel = destNode === destPort ? destNode : destNode + " (" + destPort + ")"; | ||
var coords = _this.translateMouseCoords(ev.clientX, ev.clientY); | ||
var ns = "http://www.w3.org/2000/svg"; | ||
tipEl = document.createElementNS(ns, "text"); | ||
tipEl.classList.add("label"); | ||
tipEl.classList.add("label-edge"); | ||
tipEl.setAttribute("x", coords.x); | ||
tipEl.setAttribute("y", coords.x - 16); | ||
tipEl.innerHTML = sourceLabel + " → " + destLabel; | ||
_this.workflow.appendChild(tipEl); | ||
}, function () { | ||
if (tipEl) { | ||
tipEl.remove(); | ||
tipEl = undefined; | ||
} | ||
}); | ||
}; | ||
return Workflow; | ||
@@ -283,0 +502,0 @@ }()); |
@@ -10,3 +10,3 @@ { | ||
"description": "A library for generating an interactive SVG visualization of CWL workflows", | ||
"version": "0.0.10", | ||
"version": "0.0.11", | ||
"dependencies": { | ||
@@ -20,3 +20,3 @@ "core-js": "^2.4.1", | ||
"css-loader": "^0.26.1", | ||
"cwlts": "^1.11.16", | ||
"cwlts": "^1.11.25", | ||
"json-loader": "0.5.4", | ||
@@ -23,0 +23,0 @@ "node-sass": "^4.5.0", |
@@ -38,3 +38,3 @@ "use strict"; | ||
if (!this.handlers[event]) { | ||
throw new Error("Cannot " + verb + " a non-supported event \u201C" + event + "\u201D. \n Supported events are: " + Object.keys(this.handlers).join(", ") + "\u201D"); | ||
console.warn("Trying to " + verb + " a non-supported event \u201C" + event + "\u201D. \n Supported events are: " + Object.keys(this.handlers).join(", ") + "\u201D"); | ||
} | ||
@@ -41,0 +41,0 @@ }; |
export declare class Geometry { | ||
static isColliding(a: Snap.Element, b: Snap.Element): void; | ||
static distance(x1: any, y1: any, x2: any, y2: any): number; | ||
static getTransformToElement(from: any, to: any): any; | ||
} |
@@ -10,2 +10,8 @@ "use strict"; | ||
}; | ||
Geometry.distance = function (x1, y1, x2, y2) { | ||
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)); | ||
}; | ||
Geometry.getTransformToElement = function (from, to) { | ||
return to.getScreenCTM().inverse().multiply(from.getScreenCTM()); | ||
}; | ||
return Geometry; | ||
@@ -12,0 +18,0 @@ }()); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
110908
48
1340
0