UNPKG

jquery.flowchart

Version:

JQuery UI plugin that allows you to draw a flow chart.

939 lines (767 loc) 40.3 kB
$(function() { // the widget definition, where "custom" is the namespace, // "colorize" the widget name $.widget( "flowchart.flowchart", { // default options options: { canUserEditLinks: true, canUserMoveOperators: true, data: {}, distanceFromArrow: 3, defaultOperatorClass: 'flowchart-default-operator', defaultLinkColor: '#3366ff', defaultSelectedLinkColor: 'black', linkWidth: 10, grid: 20, multipleLinksOnOutput: false, multipleLinksOnInput: false, linkVerticalDecal: 0, onOperatorSelect: function(operatorId) { return true; }, onOperatorUnselect: function() { return true; }, onLinkSelect: function(linkId) { return true; }, onLinkUnselect: function() { return true; }, onOperatorCreate: function(operatorId, operatorData, fullElement) { return true; }, onLinkCreate: function(linkId, linkData) { return true; }, onOperatorDelete: function(operatorId) { return true; }, onLinkDelete: function(linkId, forced) { return true; }, onOperatorMoved: function(operatorId, position) { }, onAfterChange: function(changeType) { } }, data: null, objs: null, maskNum: 0, linkNum: 0, operatorNum: 0, lastOutputConnectorClicked: null, selectedOperatorId: null, selectedLinkId: null, positionRatio: 1, globalId: null, // the constructor _create: function() { if (typeof document.__flowchartNumber == 'undefined') { document.__flowchartNumber = 0; } else { document.__flowchartNumber++; } this.globalId = document.__flowchartNumber; this._unitVariables(); this.element.addClass('flowchart-container'); this.objs.layers.links = $('<svg class="flowchart-links-layer"></svg>'); this.objs.layers.links.appendTo(this.element); this.objs.layers.operators = $('<div class="flowchart-operators-layer unselectable"></div>'); this.objs.layers.operators.appendTo(this.element); this.objs.layers.temporaryLink = $('<svg class="flowchart-temporary-link-layer"></svg>'); this.objs.layers.temporaryLink.appendTo(this.element); var shape = document.createElementNS("http://www.w3.org/2000/svg", "line"); shape.setAttribute("x1", "0"); shape.setAttribute("y1", "0"); shape.setAttribute("x2", "0"); shape.setAttribute("y2", "0"); shape.setAttribute("stroke-dasharray", "6,6"); shape.setAttribute("stroke-width", "4"); shape.setAttribute("stroke", "black"); shape.setAttribute("fill", "none"); this.objs.layers.temporaryLink[0].appendChild(shape); this.objs.temporaryLink = shape; this._initEvents(); if (typeof this.options.data != 'undefined') { this.setData(this.options.data); } }, _unitVariables: function() { this.data = { operators: {}, links: {}, }; this.objs = { layers: { operators: null, temporaryLink: null, links: null }, linksContext: null, temporaryLink: null }; }, _initEvents: function() { var self = this; this.element.mousemove(function(e) { var $this = $(this); var offset = $this.offset(); self._mousemove((e.pageX - offset.left) / self.positionRatio, (e.pageY - offset.top) / self.positionRatio, e); }); this.element.click(function(e) { var $this = $(this); var offset = $this.offset(); self._click((e.pageX - offset.left) / self.positionRatio, (e.pageY - offset.top) / self.positionRatio, e); }); this.objs.layers.operators.on('mousedown touchstart', '.flowchart-operator', function(e) { e.stopImmediatePropagation(); }); this.objs.layers.operators.on('click', '.flowchart-operator', function(e) { if ($(e.target).closest('.flowchart-operator-connector').length == 0) { self.selectOperator($(this).data('operator_id')); } }); this.objs.layers.operators.on('click', '.flowchart-operator-connector', function() { var $this = $(this); if (self.options.canUserEditLinks) { self._connectorClicked($this.closest('.flowchart-operator').data('operator_id'), $this.data('connector'), $this.data('sub_connector'), $this.closest('.flowchart-operator-connector-set').data('connector_type')); } }); this.objs.layers.links.on('mousedown touchstart', '.flowchart-link', function(e) { e.stopImmediatePropagation(); }); this.objs.layers.links.on('mouseover', '.flowchart-link', function() { self._connecterMouseOver($(this).data('link_id')); }); this.objs.layers.links.on('mouseout', '.flowchart-link', function() { self._connecterMouseOut($(this).data('link_id')); }); this.objs.layers.links.on('click', '.flowchart-link', function() { self.selectLink($(this).data('link_id')); }); }, setData: function(data) { this._clearOperatorsLayer(); this.data.operatorTypes = {}; if (typeof data.operatorTypes != 'undefined') { this.data.operatorTypes = data.operatorTypes; } this.data.operators = {}; for (var operatorId in data.operators) { this.createOperator(operatorId, data.operators[operatorId]); } for (var linkId in data.links) { this.createLink(linkId, data.links[linkId]); } this.redrawLinksLayer(); }, addLink: function(linkData) { while(typeof this.data.links[this.linkNum] != 'undefined') { this.linkNum++; } this.createLink(this.linkNum, linkData); return this.linkNum; }, createLink: function(linkId, linkDataOriginal) { var linkData = $.extend(true, {}, linkDataOriginal); if (!this.options.onLinkCreate(linkId, linkData)) { return; } var subConnectors = this._getSubConnectors(linkData); var fromSubConnector = subConnectors[0]; var toSubConnector = subConnectors[1]; var multipleLinksOnOutput = this.options.multipleLinksOnOutput; var multipleLinksOnInput = this.options.multipleLinksOnInput; if (!multipleLinksOnOutput || !multipleLinksOnInput) { for (var linkId2 in this.data.links) { var currentLink = this.data.links[linkId2]; var currentSubConnectors = this._getSubConnectors(currentLink); var currentFromSubConnector = currentSubConnectors[0]; var currentToSubConnector = currentSubConnectors[1]; if (!multipleLinksOnOutput && currentLink.fromOperator == linkData.fromOperator && currentLink.fromConnector == linkData.fromConnector && currentFromSubConnector == fromSubConnector) { this.deleteLink(linkId2); continue; } if (!multipleLinksOnInput && currentLink.toOperator == linkData.toOperator && currentLink.toConnector == linkData.toConnector && currentToSubConnector == toSubConnector) { this.deleteLink(linkId2); continue; } } } this._autoCreateSubConnector(linkData.fromOperator, linkData.fromConnector, 'outputs', fromSubConnector); this._autoCreateSubConnector(linkData.toOperator, linkData.toConnector, 'inputs', toSubConnector); this.data.links[linkId] = linkData; this._drawLink(linkId); this.options.onAfterChange('link_create'); }, _autoCreateSubConnector: function(operator, connector, connectorType, subConnector) { var connectorInfos = this.data.operators[operator].properties[connectorType][connector]; if (connectorInfos.multiple) { var fromFullElement = this.data.operators[operator].internal.els; var nbFromConnectors = this.data.operators[operator].internal.els.connectors[connector].length; for (var i = nbFromConnectors; i < subConnector + 2; i++) { this._createSubConnector(connector, connectorInfos, fromFullElement); } } }, redrawLinksLayer: function() { this._clearLinksLayer(); for (var linkId in this.data.links) { this._drawLink(linkId); } }, _clearLinksLayer: function() { this.objs.layers.links.empty(); this.objs.layers.operators.find('.flowchart-operator-connector-small-arrow').css('border-left-color', 'transparent'); }, _clearOperatorsLayer: function() { this.objs.layers.operators.empty(); }, getConnectorPosition: function(operatorId, connectorId, subConnector) { var operatorData = this.data.operators[operatorId]; var $connector = operatorData.internal.els.connectorArrows[connectorId][subConnector]; var connectorOffset = $connector.offset(); var elementOffset = this.element.offset(); var x = (connectorOffset.left - elementOffset.left) / this.positionRatio; var width = parseInt($connector.css('border-top-width')); var y = (connectorOffset.top - elementOffset.top - 1) / this.positionRatio + parseInt($connector.css('border-left-width')); return {x: x, width: width, y: y}; }, getLinkMainColor: function(linkId) { var color = this.options.defaultLinkColor; var linkData = this.data.links[linkId]; if (typeof linkData.color != 'undefined') { color = linkData.color; } return color; }, setLinkMainColor: function(linkId, color) { this.data.links[linkId].color = color; this.options.onAfterChange('link_change_main_color'); }, _drawLink: function(linkId) { var linkData = this.data.links[linkId]; if (typeof linkData.internal == 'undefined') { linkData.internal = {}; } linkData.internal.els = {}; var fromOperatorId = linkData.fromOperator; var fromConnectorId = linkData.fromConnector; var toOperatorId = linkData.toOperator; var toConnectorId = linkData.toConnector; var subConnectors = this._getSubConnectors(linkData); var fromSubConnector = subConnectors[0]; var toSubConnector = subConnectors[1]; var color = this.getLinkMainColor(linkId); var fromOperator = this.data.operators[fromOperatorId]; var toOperator = this.data.operators[toOperatorId]; var fromSmallConnector = fromOperator.internal.els.connectorSmallArrows[fromConnectorId][fromSubConnector]; var toSmallConnector = toOperator.internal.els.connectorSmallArrows[toConnectorId][toSubConnector]; linkData.internal.els.fromSmallConnector = fromSmallConnector; linkData.internal.els.toSmallConnector = toSmallConnector; var overallGroup = document.createElementNS("http://www.w3.org/2000/svg", "g"); this.objs.layers.links[0].appendChild(overallGroup); linkData.internal.els.overallGroup = overallGroup; var mask = document.createElementNS("http://www.w3.org/2000/svg", "mask"); var maskId = "fc_mask_" + this.globalId + "_" + this.maskNum; this.maskNum++; mask.setAttribute("id", maskId); overallGroup.appendChild(mask); var shape = document.createElementNS("http://www.w3.org/2000/svg", "rect"); shape.setAttribute("x", "0"); shape.setAttribute("y", "0"); shape.setAttribute("width", "100%"); shape.setAttribute("height", "100%"); shape.setAttribute("stroke", "none"); shape.setAttribute("fill", "white"); mask.appendChild(shape); var shape = document.createElementNS("http://www.w3.org/2000/svg", "polygon"); shape.setAttribute("stroke", "none"); shape.setAttribute("fill", "black"); mask.appendChild(shape); linkData.internal.els.mask = shape; var group = document.createElementNS("http://www.w3.org/2000/svg", "g"); group.setAttribute('class', 'flowchart-link'); group.setAttribute('data-link_id', linkId); overallGroup.appendChild(group); var shape = document.createElementNS("http://www.w3.org/2000/svg", "path"); shape.setAttribute("stroke-width", this.options.linkWidth); shape.setAttribute("fill", "none"); group.appendChild(shape); linkData.internal.els.path = shape; var shape = document.createElementNS("http://www.w3.org/2000/svg", "rect"); shape.setAttribute("stroke", "none"); shape.setAttribute("mask", "url(#"+maskId+")"); group.appendChild(shape); linkData.internal.els.rect = shape; this._refreshLinkPositions(linkId); this.uncolorizeLink(linkId); }, _getSubConnectors: function(linkData) { var fromSubConnector = 0; if (typeof linkData.fromSubConnector != 'undefined') { fromSubConnector = linkData.fromSubConnector; } var toSubConnector = 0; if (typeof linkData.toSubConnector != 'undefined') { toSubConnector = linkData.toSubConnector; } return [fromSubConnector, toSubConnector]; }, _refreshLinkPositions: function(linkId) { var linkData = this.data.links[linkId]; var subConnectors = this._getSubConnectors(linkData); var fromSubConnector = subConnectors[0]; var toSubConnector = subConnectors[1]; var fromPosition = this.getConnectorPosition(linkData.fromOperator, linkData.fromConnector, fromSubConnector); var toPosition = this.getConnectorPosition(linkData.toOperator, linkData.toConnector, toSubConnector); var fromX = fromPosition.x; var offsetFromX = fromPosition.width; var fromY = fromPosition.y; var toX = toPosition.x; var toY = toPosition.y; fromY += this.options.linkVerticalDecal; toY += this.options.linkVerticalDecal; var distanceFromArrow = this.options.distanceFromArrow; linkData.internal.els.mask.setAttribute("points", fromX+','+(fromY - offsetFromX - distanceFromArrow)+' '+(fromX + offsetFromX + distanceFromArrow)+','+fromY+' '+fromX+','+(fromY + offsetFromX + distanceFromArrow)); var bezierFromX = (fromX+offsetFromX + distanceFromArrow); var bezierToX = toX+1; var bezierIntensity = Math.min(100,Math.max(Math.abs(bezierFromX-bezierToX)/2,Math.abs(fromY-toY))); linkData.internal.els.path.setAttribute("d", 'M'+bezierFromX+','+(fromY)+' C'+(fromX + offsetFromX + distanceFromArrow + bezierIntensity)+','+fromY+' '+(toX - bezierIntensity)+','+toY+' '+bezierToX+','+toY); linkData.internal.els.rect.setAttribute("x", fromX); linkData.internal.els.rect.setAttribute("y", fromY - this.options.linkWidth / 2); linkData.internal.els.rect.setAttribute("width", offsetFromX + distanceFromArrow+1); linkData.internal.els.rect.setAttribute("height", this.options.linkWidth); }, getOperatorCompleteData: function(operatorData) { if (typeof operatorData.internal == 'undefined') { operatorData.internal = {}; } this._refreshInternalProperties(operatorData); infos = $.extend(true, {}, operatorData.internal.properties); for (var connectorId in infos.inputs) { if (infos.inputs[connectorId] == null) { delete infos.inputs[connectorId]; } } for (var connectorId in infos.outputs) { if (infos.outputs[connectorId] == null) { delete infos.outputs[connectorId]; } } if (typeof infos.class == 'undefined') { infos.class = this.options.defaultOperatorClass; } return infos; }, _getOperatorFullElement: function(operatorData) { var infos = this.getOperatorCompleteData(operatorData); var $operator = $('<div class="flowchart-operator"></div>'); $operator.addClass(infos.class); var $operator_title = $('<div class="flowchart-operator-title"></div>'); $operator_title.text(infos.title); $operator_title.appendTo($operator); var $operator_inputs_outputs = $('<div class="flowchart-operator-inputs-outputs"></div>'); $operator_inputs_outputs.appendTo($operator); var $operator_inputs = $('<div class="flowchart-operator-inputs"></div>'); $operator_inputs.appendTo($operator_inputs_outputs); var $operator_outputs = $('<div class="flowchart-operator-outputs"></div>'); $operator_outputs.appendTo($operator_inputs_outputs); var self = this; var connectorArrows = {}; var connectorSmallArrows = {}; var connectorSets = {}; var connectors = {}; var fullElement = {operator: $operator, title: $operator_title, connectorSets: connectorSets, connectors: connectors, connectorArrows: connectorArrows, connectorSmallArrows: connectorSmallArrows}; function addConnector(connectorKey, connectorInfos, $operator_container, connectorType) { var $operator_connector_set = $('<div class="flowchart-operator-connector-set"></div>'); $operator_connector_set.data('connector_type', connectorType); $operator_connector_set.appendTo($operator_container); connectorArrows[connectorKey] = []; connectorSmallArrows[connectorKey] = []; connectors[connectorKey] = []; connectorSets[connectorKey] = $operator_connector_set; self._createSubConnector(connectorKey, connectorInfos, fullElement); } for (var key in infos.inputs) { addConnector(key, infos.inputs[key], $operator_inputs, 'inputs'); } for (var key in infos.outputs) { addConnector(key, infos.outputs[key], $operator_outputs, 'outputs'); } return fullElement; }, _createSubConnector: function(connectorKey, connectorInfos, fullElement) { var $operator_connector_set = fullElement.connectorSets[connectorKey]; var subConnector = fullElement.connectors[connectorKey].length; var $operator_connector = $('<div class="flowchart-operator-connector"></div>'); $operator_connector.appendTo($operator_connector_set); $operator_connector.data('connector', connectorKey); $operator_connector.data('sub_connector', subConnector); var $operator_connector_label = $('<div class="flowchart-operator-connector-label"></div>'); $operator_connector_label.text(connectorInfos.label.replace('(:i)', subConnector + 1)); $operator_connector_label.appendTo($operator_connector); var $operator_connector_arrow = $('<div class="flowchart-operator-connector-arrow"></div>'); $operator_connector_arrow.appendTo($operator_connector); var $operator_connector_small_arrow = $('<div class="flowchart-operator-connector-small-arrow"></div>'); $operator_connector_small_arrow.appendTo($operator_connector); fullElement.connectors[connectorKey].push($operator_connector); fullElement.connectorArrows[connectorKey].push($operator_connector_arrow); fullElement.connectorSmallArrows[connectorKey].push($operator_connector_small_arrow); }, getOperatorElement: function(operatorData) { var fullElement = this._getOperatorFullElement(operatorData); return fullElement.operator; }, addOperator: function(operatorData) { while(typeof this.data.operators[this.operatorNum] != 'undefined') { this.operatorNum++; } this.createOperator(this.operatorNum, operatorData); return this.operatorNum; }, createOperator: function(operatorId, operatorData) { operatorData.internal = {}; this._refreshInternalProperties(operatorData); var fullElement = this._getOperatorFullElement(operatorData); if (!this.options.onOperatorCreate(operatorId, operatorData, fullElement)) { return false; } var grid = this.options.grid; operatorData.top = Math.round(operatorData.top / grid) * grid; operatorData.left = Math.round(operatorData.left / grid) * grid; fullElement.operator.appendTo(this.objs.layers.operators); fullElement.operator.css({top: operatorData.top, left: operatorData.left}); fullElement.operator.data('operator_id', operatorId); this.data.operators[operatorId] = operatorData; this.data.operators[operatorId].internal.els = fullElement; if (operatorId == this.selectedOperatorId) { this._addSelectedClass(operatorId); } var operatorData = this.data.operators[operatorId] ; var self = this; function operatorChangedPosition(operator_id, pos) { operatorData.top = pos.top; operatorData.left = pos.left; for (var linkId in self.data.links) { var linkData = self.data.links[linkId]; if (linkData.fromOperator == operator_id || linkData.toOperator == operator_id) { self._refreshLinkPositions(linkId); } } } // Small fix has been added in order to manage eventual zoom // http://stackoverflow.com/questions/2930092/jquery-draggable-with-zoom-problem if (this.options.canUserMoveOperators) { var pointerX; var pointerY; fullElement.operator.draggable({ handle: '.flowchart-operator-title', start: function(e, ui) { if (self.lastOutputConnectorClicked != null) { e.preventDefault(); return; } var elementOffset = self.element.offset(); pointerX = (e.pageX - elementOffset.left) / self.positionRatio - parseInt($(e.target).css('left')); pointerY = (e.pageY - elementOffset.top) / self.positionRatio - parseInt($(e.target).css('top')); }, drag: function(e, ui){ var grid = self.options.grid; var elementOffset = self.element.offset(); ui.position.left = Math.round(((e.pageX - elementOffset.left) / self.positionRatio - pointerX) / grid) * grid; ui.position.top = Math.round(((e.pageY - elementOffset.top) / self.positionRatio - pointerY) / grid) * grid; ui.offset.left = Math.round(ui.position.left + elementOffset.left); ui.offset.top = Math.round(ui.position.top + elementOffset.top); fullElement.operator.css({left: ui.position.left, top: ui.position.top}); operatorChangedPosition($(this).data('operator_id'), ui.position); }, stop: function(e, ui){ self._unsetTemporaryLink(); var operatorId = $(this).data('operator_id'); operatorChangedPosition(operatorId, ui.position); fullElement.operator.css({ height: 'auto' }); self.options.onOperatorMoved(operatorId, ui.position); self.options.onAfterChange('operator_moved'); }, }); } this.options.onAfterChange('operator_create'); }, _connectorClicked: function(operator, connector, subConnector, connectorCategory) { if (connectorCategory == 'outputs') { var d = new Date(); var currentTime = d.getTime(); this.lastOutputConnectorClicked = {operator: operator, connector: connector, subConnector: subConnector}; this.objs.layers.temporaryLink.show(); var position = this.getConnectorPosition(operator, connector, subConnector); var x = position.x + position.width; var y = position.y; this.objs.temporaryLink.setAttribute('x1', x); this.objs.temporaryLink.setAttribute('y1', y); this._mousemove(x, y); } if (connectorCategory == 'inputs' && this.lastOutputConnectorClicked != null) { var linkData = { fromOperator: this.lastOutputConnectorClicked.operator, fromConnector: this.lastOutputConnectorClicked.connector, fromSubConnector: this.lastOutputConnectorClicked.subConnector, toOperator: operator, toConnector: connector, toSubConnector: subConnector }; this.addLink(linkData); this._unsetTemporaryLink(); } }, _unsetTemporaryLink: function () { this.lastOutputConnectorClicked = null; this.objs.layers.temporaryLink.hide(); }, _mousemove: function(x, y, e) { if (this.lastOutputConnectorClicked != null) { this.objs.temporaryLink.setAttribute('x2', x); this.objs.temporaryLink.setAttribute('y2', y); } }, _click: function(x, y, e) { var $target = $(e.target); if ($target.closest('.flowchart-operator-connector').length == 0) { this._unsetTemporaryLink(); } if ($target.closest('.flowchart-operator').length == 0) { this.unselectOperator(); } if ($target.closest('.flowchart-link').length == 0) { this.unselectLink(); } }, _removeSelectedClassOperators: function() { this.objs.layers.operators.find('.flowchart-operator').removeClass('selected'); }, unselectOperator: function() { if (this.selectedOperatorId != null) { if (!this.options.onOperatorUnselect()) { return; } this._removeSelectedClassOperators(); this.selectedOperatorId = null; } }, _addSelectedClass: function(operatorId) { this.data.operators[operatorId].internal.els.operator.addClass('selected'); }, selectOperator: function(operatorId) { if (!this.options.onOperatorSelect(operatorId)) { return; } this.unselectLink(); this._removeSelectedClassOperators(); this._addSelectedClass(operatorId); this.selectedOperatorId = operatorId; }, getSelectedOperatorId: function() { return this.selectedOperatorId; }, getSelectedLinkId: function() { return this.selectedLinkId; }, // Found here : http://stackoverflow.com/questions/5560248/programmatically-lighten-or-darken-a-hex-color-or-rgb-and-blend-colors _shadeColor: function(color, percent) { var f=parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF; return "#"+(0x1000000+(Math.round((t-R)*p)+R)*0x10000+(Math.round((t-G)*p)+G)*0x100+(Math.round((t-B)*p)+B)).toString(16).slice(1); }, colorizeLink: function(linkId, color) { var linkData = this.data.links[linkId]; linkData.internal.els.path.setAttribute('stroke', color); linkData.internal.els.rect.setAttribute('fill', color); linkData.internal.els.fromSmallConnector.css('border-left-color', color); linkData.internal.els.toSmallConnector.css('border-left-color', color); }, uncolorizeLink: function(linkId) { this.colorizeLink(linkId, this.getLinkMainColor(linkId)); }, _connecterMouseOver: function(linkId) { if (this.selectedLinkId != linkId) { this.colorizeLink(linkId, this._shadeColor(this.getLinkMainColor(linkId), -0.4)); } }, _connecterMouseOut: function(linkId) { if (this.selectedLinkId != linkId) { this.uncolorizeLink(linkId); } }, unselectLink: function() { if (this.selectedLinkId != null) { if (!this.options.onLinkUnselect()) { return; } this.uncolorizeLink(this.selectedLinkId, this.options.defaultSelectedLinkColor); this.selectedLinkId = null; } }, selectLink: function(linkId) { this.unselectLink(); if (!this.options.onLinkSelect(linkId)) { return; } this.unselectOperator(); this.selectedLinkId = linkId; this.colorizeLink(linkId, this.options.defaultSelectedLinkColor); }, deleteOperator: function(operatorId) { this._deleteOperator(operatorId, false); }, _deleteOperator: function(operatorId, replace) { if (!this.options.onOperatorDelete(operatorId, replace)) { return false; } if (!replace) { for (var linkId in this.data.links) { var currentLink = this.data.links[linkId]; if (currentLink.fromOperator == operatorId || currentLink.toOperator == operatorId) { this._deleteLink(linkId, true); } } } if (!replace && operatorId == this.selectedOperatorId) { this.unselectOperator(); } this.data.operators[operatorId].internal.els.operator.remove(); delete this.data.operators[operatorId]; this.options.onAfterChange('operator_delete'); }, deleteLink: function(linkId) { this._deleteLink(linkId, false); }, _deleteLink: function(linkId, forced) { if (this.selectedLinkId == linkId) { this.unselectLink(); } if (!this.options.onLinkDelete(linkId, forced)) { if (!forced) { return; } } this.colorizeLink(linkId, 'transparent'); var linkData = this.data.links[linkId]; var fromOperator = linkData.fromOperator; var fromConnector = linkData.fromConnector; var toOperator = linkData.toOperator; var toConnector = linkData.toConnector; linkData.internal.els.overallGroup.remove(); delete this.data.links[linkId]; this._cleanMultipleConnectors(fromOperator, fromConnector, 'from'); this._cleanMultipleConnectors(toOperator, toConnector, 'to'); this.options.onAfterChange('link_delete'); }, _cleanMultipleConnectors: function(operator, connector, linkFromTo) { if (!this.data.operators[operator].properties[linkFromTo == 'from' ? 'outputs': 'inputs'][connector].multiple) { return; } var maxI = -1; var fromToOperator = linkFromTo + 'Operator'; var fromToConnector = linkFromTo + 'Connector'; var fromToSubConnector = linkFromTo + 'SubConnector'; var els = this.data.operators[operator].internal.els; var subConnectors = els.connectors[connector]; var nbSubConnectors = subConnectors.length; for (var linkId in this.data.links) { var linkData = this.data.links[linkId]; if (linkData[fromToOperator] == operator && linkData[fromToConnector] == connector) { if (maxI < linkData[fromToSubConnector]) { maxI = linkData[fromToSubConnector]; } } } var nbToDelete = Math.min(nbSubConnectors - maxI - 2, nbSubConnectors - 1); for (var i = 0; i < nbToDelete; i++) { subConnectors[subConnectors.length - 1].remove(); subConnectors.pop(); els.connectorArrows[connector].pop(); els.connectorSmallArrows[connector].pop(); } }, deleteSelected: function() { if (this.selectedLinkId != null) { this.deleteLink(this.selectedLinkId); } if (this.selectedOperatorId != null) { this.deleteOperator(this.selectedOperatorId); } }, setPositionRatio: function(positionRatio) { this.positionRatio = positionRatio; }, getPositionRatio: function() { return this.positionRatio; }, getData: function() { var keys = ['operators', 'links']; var data = {}; data.operators = $.extend(true, {}, this.data.operators); data.links = $.extend(true, {}, this.data.links); for (var keyI in keys) { var key = keys[keyI]; for (var objId in data[key]) { delete data[key][objId].internal; } } data.operatorTypes = this.data.operatorTypes; return data; }, setOperatorTitle: function(operatorId, title) { this.data.operators[operatorId].internal.els.title.text(title); if (typeof this.data.operators[operatorId].properties == 'undefined') { this.data.operators[operatorId].properties = {}; } this.data.operators[operatorId].properties.title = title; this._refreshInternalProperties(this.data.operators[operatorId]); this.options.onAfterChange('operator_title_change'); }, getOperatorTitle: function(operatorId) { return this.data.operators[operatorId].internal.properties.title; }, setOperatorData: function(operatorId, operatorData) { var infos = this.getOperatorCompleteData(operatorData); for (var linkId in this.data.links) { var linkData = this.data.links[linkId]; if ((linkData.fromOperator == operatorId && typeof infos.outputs[linkData.fromConnector] == 'undefined') || (linkData.toOperator == operatorId && typeof infos.inputs[linkData.toConnector] == 'undefined')) { this._deleteLink(linkId, true); continue; } } this._deleteOperator(operatorId, true); this.createOperator(operatorId, operatorData); this.redrawLinksLayer(); this.options.onAfterChange('operator_data_change'); }, getOperatorData: function(operatorId) { var data = $.extend(true, {}, this.data.operators[operatorId]); delete data.internal; return data; }, getOperatorFullProperties: function(operatorData) { if (typeof operatorData.type != 'undefined') { var typeProperties = this.data.operatorTypes[operatorData.type]; var operatorProperties = {}; if (typeof operatorData.properties != 'undefined') { operatorProperties = operatorData.properties; } return $.extend({}, typeProperties, operatorProperties); } else { return operatorData.properties; } }, _refreshInternalProperties: function(operatorData) { operatorData.internal.properties = this.getOperatorFullProperties(operatorData); } }); });