From 72b070ba3c809fa7ea6e89bf8c3be9a8a01acafc Mon Sep 17 00:00:00 2001 From: Roman Bruckner Date: Tue, 19 Dec 2023 19:15:37 +0100 Subject: [PATCH] refactor(dia.LinkView)!: remove support for legacy link view (#2446) --- packages/joint-core/css/layout.css | 74 - .../joint-core/demo/archive/erd/index.html | 2 + .../joint-core/demo/archive/erd/src/erd.js | 1 + .../demo/archive/joint.dia.LegacyLinkView.css | 60 + .../demo/archive/joint.dia.LegacyLinkView.js | 2648 +++++++++++++++++ .../joint-core/demo/archive/logic/index.html | 2 + .../demo/archive/logic/src/logic.js | 1 + .../joint-core/demo/archive/org/index.html | 3 + .../joint-core/demo/archive/org/src/org.js | 1 + .../demo/archive/petri-nets/index.html | 2 + .../demo/archive/petri-nets/src/pn.js | 1 + .../demo/{links => archive/pipes}/pipes.html | 7 +- .../{links => archive/pipes}/src/pipes.js | 4 +- .../joint-core/demo/archive/umlcd/index.html | 2 + .../demo/archive/umlcd/src/umlcd.js | 1 + .../joint-core/demo/archive/umlsc/index.html | 2 + .../demo/archive/umlsc/src/umlsc.js | 1 + .../demo/custom-shapes/src/custom-shapes.mjs | 3 +- .../demo/ports/port-layouts-defaults.js | 6 +- packages/joint-core/demo/ports/ports2.js | 2 +- packages/joint-core/demo/rough/src/rough.js | 2 +- .../dia/Paper/interactive/arrowheadMove.html | 77 - .../demo/dia/Paper/interactive/enableAll.html | 44 +- .../dia/Paper/interactive/useLinkTools.html | 74 - .../demo/dia/Paper/interactive/vertexAdd.html | 74 - .../dia/Paper/interactive/vertexMove.html | 74 - .../dia/Paper/interactive/vertexRemove.html | 74 - .../docs/src/joint/api/dia/Element/intro.html | 8 +- .../api/dia/Graph/prototype/addCell.html | 6 +- .../docs/src/joint/api/dia/Link/intro.html | 54 +- .../docs/src/joint/api/dia/Paper/events.html | 10 +- .../Paper/prototype/options/defaultLink.html | 2 +- .../Paper/prototype/options/interactive.html | 49 +- .../src/joint/api/layout/DirectedGraph.html | 2 +- .../src/joint/api/linkTools/Vertices.html | 10 + packages/joint-core/src/dia/CellView.mjs | 2 +- packages/joint-core/src/dia/Link.mjs | 48 - packages/joint-core/src/dia/LinkView.mjs | 401 +-- packages/joint-core/src/dia/Paper.mjs | 13 +- .../joint-core/src/linkTools/Arrowhead.mjs | 8 +- .../joint-core/src/linkTools/Vertices.mjs | 6 +- packages/joint-core/test/jointjs/basic.js | 22 +- .../joint-core/test/jointjs/connectors.js | 18 +- .../joint-core/test/jointjs/dia/attributes.js | 6 +- packages/joint-core/test/jointjs/embedding.js | 4 +- packages/joint-core/test/jointjs/graph.js | 48 +- packages/joint-core/test/jointjs/linkView.js | 59 +- packages/joint-core/test/jointjs/links.js | 405 +-- packages/joint-core/test/jointjs/paper.js | 154 +- packages/joint-core/test/jointjs/routers.js | 62 +- packages/joint-core/test/utils.js | 30 + packages/joint-core/types/joint.d.ts | 14 +- 52 files changed, 3109 insertions(+), 1574 deletions(-) create mode 100644 packages/joint-core/demo/archive/joint.dia.LegacyLinkView.css create mode 100644 packages/joint-core/demo/archive/joint.dia.LegacyLinkView.js rename packages/joint-core/demo/{links => archive/pipes}/pipes.html (65%) rename packages/joint-core/demo/{links => archive/pipes}/src/pipes.js (98%) delete mode 100644 packages/joint-core/docs/demo/dia/Paper/interactive/arrowheadMove.html delete mode 100644 packages/joint-core/docs/demo/dia/Paper/interactive/useLinkTools.html delete mode 100644 packages/joint-core/docs/demo/dia/Paper/interactive/vertexAdd.html delete mode 100644 packages/joint-core/docs/demo/dia/Paper/interactive/vertexMove.html delete mode 100644 packages/joint-core/docs/demo/dia/Paper/interactive/vertexRemove.html diff --git a/packages/joint-core/css/layout.css b/packages/joint-core/css/layout.css index 1f1f906c0..663ea69a9 100644 --- a/packages/joint-core/css/layout.css +++ b/packages/joint-core/css/layout.css @@ -64,21 +64,6 @@ the whole group of elements. Each plugin can provide its own stylesheet. /* The default behavior when scaling an element is not to scale the stroke in order to prevent the ugly effect of stroke with different proportions. */ vector-effect: non-scaling-stroke; } -/* - -connection-wrap is a element of the joint.dia.Link that follows the .connection of that link. -In other words, the `d` attribute of the .connection-wrap contains the same data as the `d` attribute of the -.connection . The advantage of using .connection-wrap is to be able to catch pointer events -in the neighborhood of the .connection . This is especially handy if the .connection is -very thin. - -*/ - -.marker-source, -.marker-target { - /* This makes the arrowheads point to the border of objects even though the transform: scale() is applied on them. */ - vector-effect: non-scaling-stroke; -} /* Paper */ .joint-paper { @@ -86,63 +71,4 @@ very thin. } /* Paper */ -/* - -Vertex markers are `` elements that appear at connection vertex positions. - -*/ - -.joint-link .connection-wrap, -.joint-link .connection { - fill: none; -} -/* element wrapping .marker-vertex-group. */ -.marker-vertices { - opacity: 0; - cursor: move; -} -.marker-arrowheads { - opacity: 0; - cursor: move; - cursor: -webkit-grab; - cursor: -moz-grab; -/* display: none; */ /* setting `display: none` on .marker-arrowheads effectivelly switches of links reconnecting */ -} -.link-tools { - opacity: 0; - cursor: pointer; -} -.link-tools .tool-options { - display: none; /* by default, we don't display link options tool */ -} -.joint-link:hover .marker-vertices, -.joint-link:hover .marker-arrowheads, -.joint-link:hover .link-tools { - opacity: 1; -} - -/* element used to remove a vertex */ -.marker-vertex-remove { - cursor: pointer; - opacity: .1; -} - -.marker-vertex-group:hover .marker-vertex-remove { - opacity: 1; -} - -.marker-vertex-remove-area { - opacity: .1; - cursor: pointer; -} -.marker-vertex-group:hover .marker-vertex-remove-area { - opacity: 1; -} - -/* -Example of custom changes (in pure CSS only!): - -Do not show marker vertices at all: .marker-vertices { display: none; } -Do not allow adding new vertices: .connection-wrap { pointer-events: none; } -*/ diff --git a/packages/joint-core/demo/archive/erd/index.html b/packages/joint-core/demo/archive/erd/index.html index 9913c681c..784284b19 100644 --- a/packages/joint-core/demo/archive/erd/index.html +++ b/packages/joint-core/demo/archive/erd/index.html @@ -8,12 +8,14 @@ ER Diagrams | JointJS +
+ diff --git a/packages/joint-core/demo/archive/erd/src/erd.js b/packages/joint-core/demo/archive/erd/src/erd.js index 6883f747d..15539271c 100644 --- a/packages/joint-core/demo/archive/erd/src/erd.js +++ b/packages/joint-core/demo/archive/erd/src/erd.js @@ -162,6 +162,7 @@ var paper = new joint.dia.Paper({ cellViewNamespace: shapes, linkPinning: false, highlighting: false, + linkView: joint.dia.LegacyLinkView, defaultConnectionPoint: function(line, view) { var element = view.model; return element.getConnectionPoint(line.start) || element.getBBox().center(); diff --git a/packages/joint-core/demo/archive/joint.dia.LegacyLinkView.css b/packages/joint-core/demo/archive/joint.dia.LegacyLinkView.css new file mode 100644 index 000000000..3e75ff4ad --- /dev/null +++ b/packages/joint-core/demo/archive/joint.dia.LegacyLinkView.css @@ -0,0 +1,60 @@ +/* + +Vertex markers are `` elements that appear at connection vertex positions. + +*/ + +.joint-link .connection-wrap, +.joint-link .connection { + fill: none; +} + +/* element wrapping .marker-vertex-group. */ +.marker-vertices { + opacity: 0; + cursor: move; +} +.marker-arrowheads { + opacity: 0; + cursor: move; + cursor: -webkit-grab; + cursor: -moz-grab; +/* display: none; */ /* setting `display: none` on .marker-arrowheads effectively switches of links reconnecting */ +} +.link-tools { + opacity: 0; + cursor: pointer; +} +.link-tools .tool-options { + display: none; /* by default, we don't display link options tool */ +} +.joint-link:hover .marker-vertices, +.joint-link:hover .marker-arrowheads, +.joint-link:hover .link-tools { + opacity: 1; +} + +/* element used to remove a vertex */ +.marker-vertex-remove { + cursor: pointer; + opacity: .1; +} + +.marker-vertex-group:hover .marker-vertex-remove { + opacity: 1; +} + +.marker-vertex-remove-area { + opacity: .1; + cursor: pointer; +} +.marker-vertex-group:hover .marker-vertex-remove-area { + opacity: 1; +} + +/* +Example of custom changes (in pure CSS only!): + +Do not show marker vertices at all: .marker-vertices { display: none; } +Do not allow adding new vertices: .connection-wrap { pointer-events: none; } +*/ diff --git a/packages/joint-core/demo/archive/joint.dia.LegacyLinkView.js b/packages/joint-core/demo/archive/joint.dia.LegacyLinkView.js new file mode 100644 index 000000000..bf70efe5b --- /dev/null +++ b/packages/joint-core/demo/archive/joint.dia.LegacyLinkView.js @@ -0,0 +1,2648 @@ +(function(joint) { + + const CellView = joint.dia.CellView; + const Link = joint.dia.Link; + const V = joint.V; + const { addClassNamePrefix, removeClassNamePrefix, merge, template, assign, toArray, isObject, isFunction, clone, isPercentage, result, isEqual, camelCase } = joint.util; + const { Point, Line, Path, normalizeAngle, Rect, Polyline } = joint.g; + const routers = joint.routers; + const connectors = joint.connectors; + const $ = joint.mvc.$; + + const Flags = { + TOOLS: CellView.Flags.TOOLS, + RENDER: 'RENDER', + UPDATE: 'UPDATE', + LEGACY_TOOLS: 'LEGACY_TOOLS', + LABELS: 'LABELS', + VERTICES: 'VERTICES', + SOURCE: 'SOURCE', + TARGET: 'TARGET', + CONNECTOR: 'CONNECTOR' + }; + + // Link base view and controller. + // ---------------------------------------- + + const LinkView = joint.dia.LinkView.extend({ + + className: function() { + + var classNames = CellView.prototype.className.apply(this).split(' '); + + classNames.push('link'); + + return classNames.join(' '); + }, + + // The default markup for links. + markup: [ + '', + '', + '', + '', + '', + '', + '', + '' + ].join(''), + + toolMarkup: [ + '', + '', + '', + '', + 'Remove link.', + '', + '', + '', + '', + 'Link options.', + '', + '' + ].join(''), + + doubleToolMarkup: undefined, + + // The default markup for showing/removing vertices. These elements are the children of the .marker-vertices element (see `this.markup`). + // Only .marker-vertex and .marker-vertex-remove element have special meaning. The former is used for + // dragging vertices (changing their position). The latter is used for removing vertices. + vertexMarkup: [ + '', + '', + '', + '', + 'Remove vertex.', + '', + '' + ].join(''), + + arrowheadMarkup: [ + '', + '', + '' + ].join(''), + + options: { + + shortLinkLength: 105, + doubleLinkTools: false, + longLinkLength: 155, + linkToolsOffset: 40, + doubleLinkToolsOffset: 65, + sampleInterval: 50 + }, + + _labelCache: null, + _labelSelectors: null, + _markerCache: null, + _V: null, + _dragData: null, // deprecated + + metrics: null, + decimalsRounding: 2, + + initialize: function() { + + CellView.prototype.initialize.apply(this, arguments); + + // `_.labelCache` is a mapping of indexes of labels in the `this.get('labels')` array to + // `` nodes wrapped by Vectorizer. This allows for quick access to the + // nodes in `updateLabelPosition()` in order to update the label positions. + this._labelCache = {}; + + // a cache of label selectors + this._labelSelectors = {}; + + // keeps markers bboxes and positions again for quicker access + this._markerCache = {}; + + // cache of default markup nodes + this._V = {}; + + // connection path metrics + this.cleanNodesCache(); + }, + + presentationAttributes: { + markup: [Flags.RENDER], + attrs: [Flags.UPDATE], + router: [Flags.UPDATE], + connector: [Flags.CONNECTOR], + toolMarkup: [Flags.LEGACY_TOOLS], + labels: [Flags.LABELS], + labelMarkup: [Flags.LABELS], + vertices: [Flags.VERTICES, Flags.UPDATE], + vertexMarkup: [Flags.VERTICES], + source: [Flags.SOURCE, Flags.UPDATE], + target: [Flags.TARGET, Flags.UPDATE] + }, + + initFlag: [Flags.RENDER, Flags.SOURCE, Flags.TARGET, Flags.TOOLS], + + UPDATE_PRIORITY: 1, + + confirmUpdate: function(flags, opt) { + + opt || (opt = {}); + + if (this.hasFlag(flags, Flags.SOURCE)) { + if (!this.updateEndProperties('source')) return flags; + flags = this.removeFlag(flags, Flags.SOURCE); + } + + if (this.hasFlag(flags, Flags.TARGET)) { + if (!this.updateEndProperties('target')) return flags; + flags = this.removeFlag(flags, Flags.TARGET); + } + + const { paper, sourceView, targetView } = this; + if (paper && ((sourceView && !paper.isViewMounted(sourceView)) || (targetView && !paper.isViewMounted(targetView)))) { + // Wait for the sourceView and targetView to be rendered + return flags; + } + + if (this.hasFlag(flags, Flags.RENDER)) { + this.render(); + this.updateHighlighters(true); + this.updateTools(opt); + flags = this.removeFlag(flags, [Flags.RENDER, Flags.UPDATE, Flags.VERTICES, Flags.LABELS, Flags.TOOLS, Flags.LEGACY_TOOLS, Flags.CONNECTOR]); + return flags; + } + + let updateHighlighters = false; + + if (this.hasFlag(flags, Flags.VERTICES)) { + this.renderVertexMarkers(); + flags = this.removeFlag(flags, Flags.VERTICES); + } + + const { model } = this; + const { attributes } = model; + let updateLabels = this.hasFlag(flags, Flags.LABELS); + let updateLegacyTools = this.hasFlag(flags, Flags.LEGACY_TOOLS); + + if (updateLabels) { + this.onLabelsChange(model, attributes.labels, opt); + flags = this.removeFlag(flags, Flags.LABELS); + updateHighlighters = true; + } + + if (updateLegacyTools) { + this.renderTools(); + flags = this.removeFlag(flags, Flags.LEGACY_TOOLS); + } + + const updateAll = this.hasFlag(flags, Flags.UPDATE); + const updateConnector = this.hasFlag(flags, Flags.CONNECTOR); + if (updateAll || updateConnector) { + if (!updateAll) { + // Keep the current route and update the geometry + this.updatePath(); + this.updateDOM(); + } else if (opt.translateBy && model.isRelationshipEmbeddedIn(opt.translateBy)) { + // The link is being translated by an ancestor that will + // shift source point, target point and all vertices + // by an equal distance. + this.translate(opt.tx, opt.ty); + } else { + this.update(); + } + this.updateTools(opt); + flags = this.removeFlag(flags, [Flags.UPDATE, Flags.TOOLS, Flags.CONNECTOR]); + updateLabels = false; + updateLegacyTools = false; + updateHighlighters = true; + } + + if (updateLabels) { + this.updateLabelPositions(); + } + + if (updateLegacyTools) { + this.updateToolsPosition(); + } + + if (updateHighlighters) { + this.updateHighlighters(); + } + + if (this.hasFlag(flags, Flags.TOOLS)) { + this.updateTools(opt); + flags = this.removeFlag(flags, Flags.TOOLS); + } + + return flags; + }, + + requestConnectionUpdate: function(opt) { + this.requestUpdate(this.getFlag(Flags.UPDATE), opt); + }, + + isLabelsRenderRequired: function(opt = {}) { + + const previousLabels = this.model.previous('labels'); + if (!previousLabels) return true; + + // Here is an optimization for cases when we know, that change does + // not require re-rendering of all labels. + if (('propertyPathArray' in opt) && ('propertyValue' in opt)) { + // The label is setting by `prop()` method + var pathArray = opt.propertyPathArray || []; + var pathLength = pathArray.length; + if (pathLength > 1) { + // We are changing a single label here e.g. 'labels/0/position' + var labelExists = !!previousLabels[pathArray[1]]; + if (labelExists) { + if (pathLength === 2) { + // We are changing the entire label. Need to check if the + // markup is also being changed. + return ('markup' in Object(opt.propertyValue)); + } else if (pathArray[2] !== 'markup') { + // We are changing a label property but not the markup + return false; + } + } + } + } + + return true; + }, + + onLabelsChange: function(_link, _labels, opt) { + + // Note: this optimization works in async=false mode only + if (this.isLabelsRenderRequired(opt)) { + this.renderLabels(); + } else { + this.updateLabels(); + } + }, + + // Rendering. + // ---------- + + render: function() { + + this.vel.empty(); + this.unmountLabels(); + this._V = {}; + this.renderMarkup(); + // rendering labels has to be run after the link is appended to DOM tree. (otherwise bbox + // returns zero values) + this.renderLabels(); + this.update(); + + return this; + }, + + renderMarkup: function() { + + var link = this.model; + var markup = link.get('markup') || link.markup || this.markup; + if (!markup) throw new Error('dia.LinkView: markup required'); + if (Array.isArray(markup)) return this.renderJSONMarkup(markup); + if (typeof markup === 'string') return this.renderStringMarkup(markup); + throw new Error('dia.LinkView: invalid markup'); + }, + + renderJSONMarkup: function(markup) { + + var doc = this.parseDOMJSON(markup, this.el); + // Selectors + this.selectors = doc.selectors; + // Fragment + this.vel.append(doc.fragment); + }, + + renderStringMarkup: function(markup) { + + // A special markup can be given in the `properties.markup` property. This might be handy + // if e.g. arrowhead markers should be `` elements or any other element than ``s. + // `.connection`, `.connection-wrap`, `.marker-source` and `.marker-target` selectors + // of elements with special meaning though. Therefore, those classes should be preserved in any + // special markup passed in `properties.markup`. + var children = V(markup); + // custom markup may contain only one children + if (!Array.isArray(children)) children = [children]; + // Cache all children elements for quicker access. + var cache = this._V; // vectorized markup; + for (var i = 0, n = children.length; i < n; i++) { + var child = children[i]; + var className = child.attr('class'); + if (className) { + // Strip the joint class name prefix, if there is one. + className = removeClassNamePrefix(className); + cache[camelCase(className)] = child; + } + } + // partial rendering + this.renderTools(); + this.renderVertexMarkers(); + this.renderArrowheadMarkers(); + this.vel.append(children); + }, + + _getLabelMarkup: function(labelMarkup) { + + if (!labelMarkup) return undefined; + + if (Array.isArray(labelMarkup)) return this.parseDOMJSON(labelMarkup, null); + if (typeof labelMarkup === 'string') return this._getLabelStringMarkup(labelMarkup); + throw new Error('dia.linkView: invalid label markup'); + }, + + _getLabelStringMarkup: function(labelMarkup) { + + var children = V(labelMarkup); + var fragment = document.createDocumentFragment(); + + if (!Array.isArray(children)) { + fragment.appendChild(children.node); + + } else { + for (var i = 0, n = children.length; i < n; i++) { + var currentChild = children[i].node; + fragment.appendChild(currentChild); + } + } + + return { fragment: fragment, selectors: {}}; // no selectors + }, + + // Label markup fragment may come wrapped in , or not. + // If it doesn't, add the container here. + _normalizeLabelMarkup: function(markup) { + + if (!markup) return undefined; + + var fragment = markup.fragment; + if (!(markup.fragment instanceof DocumentFragment) || !markup.fragment.hasChildNodes()) throw new Error('dia.LinkView: invalid label markup.'); + + var vNode; + var childNodes = fragment.childNodes; + + if ((childNodes.length > 1) || childNodes[0].nodeName.toUpperCase() !== 'G') { + // default markup fragment is not wrapped in + // add a container + vNode = V('g').append(fragment); + } else { + vNode = V(childNodes[0]); + } + + vNode.addClass('label'); + + return { node: vNode.node, selectors: markup.selectors }; + }, + + renderLabels: function() { + + var cache = this._V; + var vLabels = cache.labels; + var labelCache = this._labelCache = {}; + var labelSelectors = this._labelSelectors = {}; + var model = this.model; + var labels = model.attributes.labels || []; + var labelsCount = labels.length; + + if (labelsCount === 0) { + if (vLabels) vLabels.remove(); + return this; + } + + if (vLabels) { + vLabels.empty(); + } else { + // there is no label container in the markup but some labels are defined + // add a container + vLabels = cache.labels = V('g').addClass('labels'); + if (this.options.labelsLayer) { + vLabels.addClass(addClassNamePrefix(result(this, 'className'))); + vLabels.attr('model-id', model.id); + } + } + + for (var i = 0; i < labelsCount; i++) { + + var label = labels[i]; + var labelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(label.markup)); + var labelNode; + var selectors; + if (labelMarkup) { + + labelNode = labelMarkup.node; + selectors = labelMarkup.selectors; + + } else { + + var builtinDefaultLabel = model._builtins.defaultLabel; + var builtinDefaultLabelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(builtinDefaultLabel.markup)); + var defaultLabel = model._getDefaultLabel(); + var defaultLabelMarkup = this._normalizeLabelMarkup(this._getLabelMarkup(defaultLabel.markup)); + var defaultMarkup = defaultLabelMarkup || builtinDefaultLabelMarkup; + + labelNode = defaultMarkup.node; + selectors = defaultMarkup.selectors; + } + + labelNode.setAttribute('label-idx', i); // assign label-idx + vLabels.append(labelNode); + labelCache[i] = labelNode; // cache node for `updateLabels()` so it can just update label node positions + + var rootSelector = this.selector; + if (selectors[rootSelector]) throw new Error('dia.LinkView: ambiguous label root selector.'); + selectors[rootSelector] = labelNode; + + labelSelectors[i] = selectors; // cache label selectors for `updateLabels()` + } + if (!vLabels.parent()) { + this.mountLabels(); + } + + this.updateLabels(); + + return this; + }, + + mountLabels: function() { + const { el, paper, model, _V, options } = this; + const { labels: vLabels } = _V; + if (!vLabels || !model.hasLabels()) return; + const { node } = vLabels; + if (options.labelsLayer) { + paper.getLayerView(options.labelsLayer).insertSortedNode(node, model.get('z')); + } else { + if (node.parentNode !== el) { + el.appendChild(node); + } + } + }, + + unmountLabels: function() { + const { options, _V } = this; + if (!_V) return; + const { labels: vLabels } = _V; + if (vLabels && options.labelsLayer) { + vLabels.remove(); + } + }, + + findLabelNodes: function(labelIndex, selector) { + const labelRoot = this._labelCache[labelIndex]; + if (!labelRoot) return []; + const labelSelectors = this._labelSelectors[labelIndex]; + return this.findBySelector(selector, labelRoot, labelSelectors); + }, + + findLabelNode: function(labelIndex, selector) { + const [node = null] = this.findLabelNodes(labelIndex, selector); + return node; + }, + + // merge default label attrs into label attrs (or use built-in default label attrs if neither is provided) + // keep `undefined` or `null` because `{}` means something else + _mergeLabelAttrs: function(hasCustomMarkup, labelAttrs, defaultLabelAttrs, builtinDefaultLabelAttrs) { + + if (labelAttrs === null) return null; + if (labelAttrs === undefined) { + + if (defaultLabelAttrs === null) return null; + if (defaultLabelAttrs === undefined) { + + if (hasCustomMarkup) return undefined; + return builtinDefaultLabelAttrs; + } + + if (hasCustomMarkup) return defaultLabelAttrs; + return merge({}, builtinDefaultLabelAttrs, defaultLabelAttrs); + } + + if (hasCustomMarkup) return merge({}, defaultLabelAttrs, labelAttrs); + return merge({}, builtinDefaultLabelAttrs, defaultLabelAttrs, labelAttrs); + }, + + // merge default label size into label size (no built-in default) + // keep `undefined` or `null` because `{}` means something else + _mergeLabelSize: function(labelSize, defaultLabelSize) { + + if (labelSize === null) return null; + if (labelSize === undefined) { + + if (defaultLabelSize === null) return null; + if (defaultLabelSize === undefined) return undefined; + + return defaultLabelSize; + } + + return merge({}, defaultLabelSize, labelSize); + }, + + updateLabels: function() { + + if (!this._V.labels) return this; + + var model = this.model; + var labels = model.get('labels') || []; + var canLabelMove = this.can('labelMove'); + + var builtinDefaultLabel = model._builtins.defaultLabel; + var builtinDefaultLabelAttrs = builtinDefaultLabel.attrs; + + var defaultLabel = model._getDefaultLabel(); + var defaultLabelMarkup = defaultLabel.markup; + var defaultLabelAttrs = defaultLabel.attrs; + var defaultLabelSize = defaultLabel.size; + + for (var i = 0, n = labels.length; i < n; i++) { + + var labelNode = this._labelCache[i]; + labelNode.setAttribute('cursor', (canLabelMove ? 'move' : 'default')); + + var selectors = this._labelSelectors[i]; + + var label = labels[i]; + var labelMarkup = label.markup; + var labelAttrs = label.attrs; + var labelSize = label.size; + + var attrs = this._mergeLabelAttrs( + (labelMarkup || defaultLabelMarkup), + labelAttrs, + defaultLabelAttrs, + builtinDefaultLabelAttrs + ); + + var size = this._mergeLabelSize( + labelSize, + defaultLabelSize + ); + + this.updateDOMSubtreeAttributes(labelNode, attrs, { + rootBBox: new Rect(size), + selectors: selectors + }); + } + + return this; + }, + + renderTools: function() { + + if (!this._V.linkTools) return this; + + // Tools are a group of clickable elements that manipulate the whole link. + // A good example of this is the remove tool that removes the whole link. + // Tools appear after hovering the link close to the `source` element/point of the link + // but are offset a bit so that they don't cover the `marker-arrowhead`. + + var $tools = $(this._V.linkTools.node).empty(); + var toolTemplate = template(this.model.get('toolMarkup') || this.model.toolMarkup || this.toolMarkup); + var tool = V(toolTemplate()); + + $tools.append(tool.node); + + // Cache the tool node so that the `updateToolsPosition()` can update the tool position quickly. + this._toolCache = tool; + + // If `doubleLinkTools` is enabled, we render copy of the tools on the other side of the + // link as well but only if the link is longer than `longLinkLength`. + if (this.options.doubleLinkTools) { + + var tool2; + if (this.model.get('doubleToolMarkup') || this.model.doubleToolMarkup) { + toolTemplate = template(this.model.get('doubleToolMarkup') || this.model.doubleToolMarkup || this.doubleToolMarkup); + tool2 = V(toolTemplate()); + } else { + tool2 = tool.clone(); + } + + $tools.append(tool2.node); + this._tool2Cache = tool2; + } + + return this; + }, + + renderVertexMarkers: function() { + + if (!this._V.markerVertices) return this; + + var $markerVertices = $(this._V.markerVertices.node).empty(); + + // A special markup can be given in the `properties.vertexMarkup` property. This might be handy + // if default styling (elements) are not desired. This makes it possible to use any + // SVG elements for .marker-vertex and .marker-vertex-remove tools. + var markupTemplate = template(this.model.get('vertexMarkup') || this.model.vertexMarkup || this.vertexMarkup); + + this.model.vertices().forEach(function(vertex, idx) { + $markerVertices.append(V(markupTemplate(assign({ idx: idx }, vertex))).node); + }); + + return this; + }, + + renderArrowheadMarkers: function() { + + // Custom markups might not have arrowhead markers. Therefore, jump of this function immediately if that's the case. + if (!this._V.markerArrowheads) return this; + + var $markerArrowheads = $(this._V.markerArrowheads.node); + + $markerArrowheads.empty(); + + // A special markup can be given in the `properties.vertexMarkup` property. This might be handy + // if default styling (elements) are not desired. This makes it possible to use any + // SVG elements for .marker-vertex and .marker-vertex-remove tools. + var markupTemplate = template(this.model.get('arrowheadMarkup') || this.model.arrowheadMarkup || this.arrowheadMarkup); + + this._V.sourceArrowhead = V(markupTemplate({ end: 'source' })); + this._V.targetArrowhead = V(markupTemplate({ end: 'target' })); + + $markerArrowheads.append(this._V.sourceArrowhead.node, this._V.targetArrowhead.node); + + return this; + }, + + // remove vertices that lie on (or nearly on) straight lines within the link + // return the number of removed points + removeRedundantLinearVertices: function(opt) { + + const SIMPLIFY_THRESHOLD = 0.001; + + const link = this.model; + const vertices = link.vertices(); + const routePoints = [this.sourceAnchor, ...vertices, this.targetAnchor]; + const numRoutePoints = routePoints.length; + + // put routePoints into a polyline and try to simplify + const polyline = new Polyline(routePoints); + polyline.simplify({ threshold: SIMPLIFY_THRESHOLD }); + const polylinePoints = polyline.points.map((point) => (point.toJSON())); // JSON of points after simplification + const numPolylinePoints = polylinePoints.length; // number of points after simplification + + // shortcut if simplification did not remove any redundant vertices: + if (numRoutePoints === numPolylinePoints) return 0; + + // else: set simplified polyline points as link vertices + // remove first and last polyline points again (= source/target anchors) + link.vertices(polylinePoints.slice(1, numPolylinePoints - 1), opt); + return (numRoutePoints - numPolylinePoints); + }, + + updateDefaultConnectionPath: function() { + + var cache = this._V; + + if (cache.connection) { + cache.connection.attr('d', this.getSerializedConnection()); + } + + if (cache.connectionWrap) { + cache.connectionWrap.attr('d', this.getSerializedConnection()); + } + + if (cache.markerSource && cache.markerTarget) { + this._translateAndAutoOrientArrows(cache.markerSource, cache.markerTarget); + } + }, + + getEndView: function(type) { + switch (type) { + case 'source': + return this.sourceView || null; + case 'target': + return this.targetView || null; + default: + throw new Error('dia.LinkView: type parameter required.'); + } + }, + + getEndAnchor: function(type) { + switch (type) { + case 'source': + return new Point(this.sourceAnchor); + case 'target': + return new Point(this.targetAnchor); + default: + throw new Error('dia.LinkView: type parameter required.'); + } + }, + + getEndConnectionPoint: function(type) { + switch (type) { + case 'source': + return new Point(this.sourcePoint); + case 'target': + return new Point(this.targetPoint); + default: + throw new Error('dia.LinkView: type parameter required.'); + } + }, + + getEndMagnet: function(type) { + switch (type) { + case 'source': + var sourceView = this.sourceView; + if (!sourceView) break; + return this.sourceMagnet || sourceView.el; + case 'target': + var targetView = this.targetView; + if (!targetView) break; + return this.targetMagnet || targetView.el; + default: + throw new Error('dia.LinkView: type parameter required.'); + } + return null; + }, + + + // Updating. + // --------- + + update: function() { + this.updateRoute(); + this.updatePath(); + this.updateDOM(); + return this; + }, + + translate: function(tx = 0, ty = 0) { + const { route, path } = this; + if (!route || !path) return; + // translate the route + const polyline = new Polyline(route); + polyline.translate(tx, ty); + this.route = polyline.points; + // translate source and target connection and marker points. + this._translateConnectionPoints(tx, ty); + // translate the geometry path + path.translate(tx, ty); + this.updateDOM(); + }, + + updateDOM() { + const { el, model, selectors } = this; + this.cleanNodesCache(); + // update SVG attributes defined by 'attrs/'. + this.updateDOMSubtreeAttributes(el, model.attr(), { selectors }); + // legacy link path update + this.updateDefaultConnectionPath(); + // update the label position etc. + this.updateLabelPositions(); + this.updateToolsPosition(); + this.updateArrowheadMarkers(); + // *Deprecated* + // Local perpendicular flag (as opposed to one defined on paper). + // Could be enabled inside a connector/router. It's valid only + // during the update execution. + this.options.perpendicular = null; + }, + + updateRoute: function() { + const { model } = this; + const vertices = model.vertices(); + // 1. Find Anchors + const anchors = this.findAnchors(vertices); + const sourceAnchor = this.sourceAnchor = anchors.source; + const targetAnchor = this.targetAnchor = anchors.target; + // 2. Find Route + const route = this.findRoute(vertices); + this.route = route; + // 3. Find Connection Points + var connectionPoints = this.findConnectionPoints(route, sourceAnchor, targetAnchor); + this.sourcePoint = connectionPoints.source; + this.targetPoint = connectionPoints.target; + }, + + updatePath: function() { + const { route, sourcePoint, targetPoint } = this; + // 3b. Find Marker Connection Point - Backwards Compatibility + const markerPoints = this.findMarkerPoints(route, sourcePoint, targetPoint); + // 4. Find Connection + const path = this.findPath(route, markerPoints.source || sourcePoint, markerPoints.target || targetPoint); + this.path = path; + }, + + findMarkerPoints: function(route, sourcePoint, targetPoint) { + + var firstWaypoint = route[0]; + var lastWaypoint = route[route.length - 1]; + + // Move the source point by the width of the marker taking into account + // its scale around x-axis. Note that scale is the only transform that + // makes sense to be set in `.marker-source` attributes object + // as all other transforms (translate/rotate) will be replaced + // by the `translateAndAutoOrient()` function. + var cache = this._markerCache; + // cache source and target points + var sourceMarkerPoint, targetMarkerPoint; + + if (this._V.markerSource) { + + cache.sourceBBox = cache.sourceBBox || this._V.markerSource.getBBox(); + sourceMarkerPoint = Point(sourcePoint).move( + firstWaypoint || targetPoint, + cache.sourceBBox.width * this._V.markerSource.scale().sx * -1 + ).round(); + } + + if (this._V.markerTarget) { + + cache.targetBBox = cache.targetBBox || this._V.markerTarget.getBBox(); + targetMarkerPoint = Point(targetPoint).move( + lastWaypoint || sourcePoint, + cache.targetBBox.width * this._V.markerTarget.scale().sx * -1 + ).round(); + } + + // if there was no markup for the marker, use the connection point. + cache.sourcePoint = sourceMarkerPoint || sourcePoint.clone(); + cache.targetPoint = targetMarkerPoint || targetPoint.clone(); + + return { + source: sourceMarkerPoint, + target: targetMarkerPoint + }; + }, + + findAnchorsOrdered: function(firstEndType, firstRef, secondEndType, secondRef) { + + var firstAnchor, secondAnchor; + var firstAnchorRef, secondAnchorRef; + var model = this.model; + var firstDef = model.get(firstEndType); + var secondDef = model.get(secondEndType); + var firstView = this.getEndView(firstEndType); + var secondView = this.getEndView(secondEndType); + var firstMagnet = this.getEndMagnet(firstEndType); + var secondMagnet = this.getEndMagnet(secondEndType); + + // Anchor first + if (firstView) { + if (firstRef) { + firstAnchorRef = new Point(firstRef); + } else if (secondView) { + firstAnchorRef = secondMagnet; + } else { + firstAnchorRef = new Point(secondDef); + } + firstAnchor = this.getAnchor(firstDef.anchor, firstView, firstMagnet, firstAnchorRef, firstEndType); + } else { + firstAnchor = new Point(firstDef); + } + + // Anchor second + if (secondView) { + secondAnchorRef = new Point(secondRef || firstAnchor); + secondAnchor = this.getAnchor(secondDef.anchor, secondView, secondMagnet, secondAnchorRef, secondEndType); + } else { + secondAnchor = new Point(secondDef); + } + + var res = {}; + res[firstEndType] = firstAnchor; + res[secondEndType] = secondAnchor; + return res; + }, + + findAnchors: function(vertices) { + + var model = this.model; + var firstVertex = vertices[0]; + var lastVertex = vertices[vertices.length - 1]; + + if (model.target().priority && !model.source().priority) { + // Reversed order + return this.findAnchorsOrdered('target', lastVertex, 'source', firstVertex); + } + + // Usual order + return this.findAnchorsOrdered('source', firstVertex, 'target', lastVertex); + }, + + findConnectionPoints: function(route, sourceAnchor, targetAnchor) { + + var firstWaypoint = route[0]; + var lastWaypoint = route[route.length - 1]; + var model = this.model; + var sourceDef = model.get('source'); + var targetDef = model.get('target'); + var sourceView = this.sourceView; + var targetView = this.targetView; + var paperOptions = this.paper.options; + var sourceMagnet, targetMagnet; + + // Connection Point Source + var sourcePoint; + if (sourceView && !sourceView.isNodeConnection(this.sourceMagnet)) { + sourceMagnet = (this.sourceMagnet || sourceView.el); + var sourceConnectionPointDef = sourceDef.connectionPoint || paperOptions.defaultConnectionPoint; + var sourcePointRef = firstWaypoint || targetAnchor; + var sourceLine = new Line(sourcePointRef, sourceAnchor); + sourcePoint = this.getConnectionPoint( + sourceConnectionPointDef, + sourceView, + sourceMagnet, + sourceLine, + 'source' + ); + } else { + sourcePoint = sourceAnchor; + } + // Connection Point Target + var targetPoint; + if (targetView && !targetView.isNodeConnection(this.targetMagnet)) { + targetMagnet = (this.targetMagnet || targetView.el); + var targetConnectionPointDef = targetDef.connectionPoint || paperOptions.defaultConnectionPoint; + var targetPointRef = lastWaypoint || sourceAnchor; + var targetLine = new Line(targetPointRef, targetAnchor); + targetPoint = this.getConnectionPoint( + targetConnectionPointDef, + targetView, + targetMagnet, + targetLine, + 'target' + ); + } else { + targetPoint = targetAnchor; + } + + return { + source: sourcePoint, + target: targetPoint + }; + }, + + getAnchor: function(anchorDef, cellView, magnet, ref, endType) { + + var isConnection = cellView.isNodeConnection(magnet); + var paperOptions = this.paper.options; + if (!anchorDef) { + if (isConnection) { + anchorDef = paperOptions.defaultLinkAnchor; + } else { + if (this.options.perpendicular) { + // Backwards compatibility + // See `manhattan` router for more details + anchorDef = { name: 'perpendicular' }; + } else { + anchorDef = paperOptions.defaultAnchor; + } + } + } + + if (!anchorDef) throw new Error('Anchor required.'); + var anchorFn; + if (typeof anchorDef === 'function') { + anchorFn = anchorDef; + } else { + var anchorName = anchorDef.name; + var anchorNamespace = isConnection ? 'linkAnchorNamespace' : 'anchorNamespace'; + anchorFn = paperOptions[anchorNamespace][anchorName]; + if (typeof anchorFn !== 'function') throw new Error('Unknown anchor: ' + anchorName); + } + var anchor = anchorFn.call( + this, + cellView, + magnet, + ref, + anchorDef.args || {}, + endType, + this + ); + if (!anchor) return new Point(); + return anchor.round(this.decimalsRounding); + }, + + + getConnectionPoint: function(connectionPointDef, view, magnet, line, endType) { + + var connectionPoint; + var anchor = line.end; + var paperOptions = this.paper.options; + + if (!connectionPointDef) return anchor; + var connectionPointFn; + if (typeof connectionPointDef === 'function') { + connectionPointFn = connectionPointDef; + } else { + var connectionPointName = connectionPointDef.name; + connectionPointFn = paperOptions.connectionPointNamespace[connectionPointName]; + if (typeof connectionPointFn !== 'function') throw new Error('Unknown connection point: ' + connectionPointName); + } + connectionPoint = connectionPointFn.call(this, line, view, magnet, connectionPointDef.args || {}, endType, this); + if (!connectionPoint) return anchor; + return connectionPoint.round(this.decimalsRounding); + }, + + _translateConnectionPoints: function(tx, ty) { + + var cache = this._markerCache; + + cache.sourcePoint.offset(tx, ty); + cache.targetPoint.offset(tx, ty); + this.sourcePoint.offset(tx, ty); + this.targetPoint.offset(tx, ty); + this.sourceAnchor.offset(tx, ty); + this.targetAnchor.offset(tx, ty); + }, + + // combine default label position with built-in default label position + _getDefaultLabelPositionProperty: function() { + + var model = this.model; + + var builtinDefaultLabel = model._builtins.defaultLabel; + var builtinDefaultLabelPosition = builtinDefaultLabel.position; + + var defaultLabel = model._getDefaultLabel(); + var defaultLabelPosition = this._normalizeLabelPosition(defaultLabel.position); + + return merge({}, builtinDefaultLabelPosition, defaultLabelPosition); + }, + + // if label position is a number, normalize it to a position object + // this makes sure that label positions can be merged properly + _normalizeLabelPosition: function(labelPosition) { + + if (typeof labelPosition === 'number') return { distance: labelPosition, offset: null, angle: 0, args: null }; + return labelPosition; + }, + + // expects normalized position properties + // e.g. `this._normalizeLabelPosition(labelPosition)` and `this._getDefaultLabelPositionProperty()` + _mergeLabelPositionProperty: function(normalizedLabelPosition, normalizedDefaultLabelPosition) { + + if (normalizedLabelPosition === null) return null; + if (normalizedLabelPosition === undefined) { + + if (normalizedDefaultLabelPosition === null) return null; + return normalizedDefaultLabelPosition; + } + + return merge({}, normalizedDefaultLabelPosition, normalizedLabelPosition); + }, + + updateLabelPositions: function() { + + if (!this._V.labels) return this; + + var path = this.path; + if (!path) return this; + + // This method assumes all the label nodes are stored in the `this._labelCache` hash table + // by their indices in the `this.get('labels')` array. This is done in the `renderLabels()` method. + + var model = this.model; + var labels = model.get('labels') || []; + if (!labels.length) return this; + + var defaultLabelPosition = this._getDefaultLabelPositionProperty(); + + for (var idx = 0, n = labels.length; idx < n; idx++) { + var labelNode = this._labelCache[idx]; + if (!labelNode) continue; + var label = labels[idx]; + var labelPosition = this._normalizeLabelPosition(label.position); + var position = this._mergeLabelPositionProperty(labelPosition, defaultLabelPosition); + var transformationMatrix = this._getLabelTransformationMatrix(position); + labelNode.setAttribute('transform', V.matrixToTransformString(transformationMatrix)); + this._cleanLabelMatrices(idx); + } + + return this; + }, + + _cleanLabelMatrices: function(index) { + // Clean magnetMatrix for all nodes of the label. + // Cached BoundingRect does not need to updated when the position changes + // TODO: this doesn't work for labels with XML String markups. + const { metrics, _labelSelectors } = this; + const selectors = _labelSelectors[index]; + if (!selectors) return; + for (let selector in selectors) { + const { id } = selectors[selector]; + if (id && (id in metrics)) delete metrics[id].magnetMatrix; + } + }, + + updateToolsPosition: function() { + + if (!this._V.linkTools) return this; + + // Move the tools a bit to the target position but don't cover the `sourceArrowhead` marker. + // Note that the offset is hardcoded here. The offset should be always + // more than the `this.$('.marker-arrowhead[end="source"]')[0].bbox().width` but looking + // this up all the time would be slow. + + var scale = ''; + var offset = this.options.linkToolsOffset; + var connectionLength = this.getConnectionLength(); + + // Firefox returns connectionLength=NaN in odd cases (for bezier curves). + // In that case we won't update tools position at all. + if (!Number.isNaN(connectionLength)) { + + // If the link is too short, make the tools half the size and the offset twice as low. + if (connectionLength < this.options.shortLinkLength) { + scale = 'scale(.5)'; + offset /= 2; + } + + var toolPosition = this.getPointAtLength(offset); + + this._toolCache.attr('transform', 'translate(' + toolPosition.x + ', ' + toolPosition.y + ') ' + scale); + + if (this.options.doubleLinkTools && connectionLength >= this.options.longLinkLength) { + + var doubleLinkToolsOffset = this.options.doubleLinkToolsOffset || offset; + + toolPosition = this.getPointAtLength(connectionLength - doubleLinkToolsOffset); + this._tool2Cache.attr('transform', 'translate(' + toolPosition.x + ', ' + toolPosition.y + ') ' + scale); + this._tool2Cache.attr('display', 'inline'); + + } else if (this.options.doubleLinkTools) { + + this._tool2Cache.attr('display', 'none'); + } + } + + return this; + }, + + updateArrowheadMarkers: function() { + + if (!this._V.markerArrowheads) return this; + + var sx = this.getConnectionLength() < this.options.shortLinkLength ? .5 : 1; + this._V.sourceArrowhead.scale(sx); + this._V.targetArrowhead.scale(sx); + + this._translateAndAutoOrientArrows(this._V.sourceArrowhead, this._V.targetArrowhead); + + return this; + }, + + updateEndProperties: function(endType) { + + const { model, paper } = this; + const endViewProperty = `${endType}View`; + const endDef = model.get(endType); + const endId = endDef && endDef.id; + + if (!endId) { + // the link end is a point ~ rect 0x0 + this[endViewProperty] = null; + this.updateEndMagnet(endType); + return true; + } + + const endModel = paper.getModelById(endId); + if (!endModel) throw new Error('LinkView: invalid ' + endType + ' cell.'); + + const endView = endModel.findView(paper); + if (!endView) { + // A view for a model should always exist + return false; + } + + this[endViewProperty] = endView; + this.updateEndMagnet(endType); + return true; + }, + + updateEndMagnet: function(endType) { + + const endMagnetProperty = `${endType}Magnet`; + const endView = this.getEndView(endType); + if (endView) { + let connectedMagnet = endView.getMagnetFromLinkEnd(this.model.get(endType)); + if (connectedMagnet === endView.el) connectedMagnet = null; + this[endMagnetProperty] = connectedMagnet; + } else { + this[endMagnetProperty] = null; + } + }, + + _translateAndAutoOrientArrows: function(sourceArrow, targetArrow) { + + // Make the markers "point" to their sticky points being auto-oriented towards + // `targetPosition`/`sourcePosition`. And do so only if there is a markup for them. + var route = toArray(this.route); + if (sourceArrow) { + sourceArrow.translateAndAutoOrient( + this.sourcePoint, + route[0] || this.targetPoint, + this.paper.cells + ); + } + + if (targetArrow) { + targetArrow.translateAndAutoOrient( + this.targetPoint, + route[route.length - 1] || this.sourcePoint, + this.paper.cells + ); + } + }, + + _getLabelPositionProperty: function(idx) { + + return (this.model.label(idx).position || {}); + }, + + _getLabelPositionAngle: function(idx) { + + var labelPosition = this._getLabelPositionProperty(idx); + return (labelPosition.angle || 0); + }, + + _getLabelPositionArgs: function(idx) { + + var labelPosition = this._getLabelPositionProperty(idx); + return labelPosition.args; + }, + + _getDefaultLabelPositionArgs: function() { + + var defaultLabel = this.model._getDefaultLabel(); + var defaultLabelPosition = defaultLabel.position || {}; + return defaultLabelPosition.args; + }, + + // merge default label position args into label position args + // keep `undefined` or `null` because `{}` means something else + _mergeLabelPositionArgs: function(labelPositionArgs, defaultLabelPositionArgs) { + + if (labelPositionArgs === null) return null; + if (labelPositionArgs === undefined) { + + if (defaultLabelPositionArgs === null) return null; + return defaultLabelPositionArgs; + } + + return merge({}, defaultLabelPositionArgs, labelPositionArgs); + }, + + // Add default label at given position at end of `labels` array. + // Four signatures: + // - obj, obj = point, opt + // - obj, num, obj = point, angle, opt + // - num, num, obj = x, y, opt + // - num, num, num, obj = x, y, angle, opt + // Assigns relative coordinates by default: + // `opt.absoluteDistance` forces absolute coordinates. + // `opt.reverseDistance` forces reverse absolute coordinates (if absoluteDistance = true). + // `opt.absoluteOffset` forces absolute coordinates for offset. + // Additional args: + // `opt.keepGradient` auto-adjusts the angle of the label to match path gradient at position. + // `opt.ensureLegibility` rotates labels so they are never upside-down. + addLabel: function(p1, p2, p3, p4) { + + // normalize data from the four possible signatures + var localX; + var localY; + var localAngle = 0; + var localOpt; + if (typeof p1 !== 'number') { + // {x, y} object provided as first parameter + localX = p1.x; + localY = p1.y; + if (typeof p2 === 'number') { + // angle and opt provided as second and third parameters + localAngle = p2; + localOpt = p3; + } else { + // opt provided as second parameter + localOpt = p2; + } + } else { + // x and y provided as first and second parameters + localX = p1; + localY = p2; + if (typeof p3 === 'number') { + // angle and opt provided as third and fourth parameters + localAngle = p3; + localOpt = p4; + } else { + // opt provided as third parameter + localOpt = p3; + } + } + + // merge label position arguments + var defaultLabelPositionArgs = this._getDefaultLabelPositionArgs(); + var labelPositionArgs = localOpt; + var positionArgs = this._mergeLabelPositionArgs(labelPositionArgs, defaultLabelPositionArgs); + + // append label to labels array + var label = { position: this.getLabelPosition(localX, localY, localAngle, positionArgs) }; + var idx = -1; + this.model.insertLabel(idx, label, localOpt); + return idx; + }, + + // Add a new vertex at calculated index to the `vertices` array. + addVertex: function(x, y, opt) { + + // accept input in form `{ x, y }, opt` or `x, y, opt` + var isPointProvided = (typeof x !== 'number'); + var localX = isPointProvided ? x.x : x; + var localY = isPointProvided ? x.y : y; + var localOpt = isPointProvided ? y : opt; + + var vertex = { x: localX, y: localY }; + var idx = this.getVertexIndex(localX, localY); + this.model.insertVertex(idx, vertex, localOpt); + return idx; + }, + + // Send a token (an SVG element, usually a circle) along the connection path. + // Example: `link.findView(paper).sendToken(V('circle', { r: 7, fill: 'green' }).node)` + // `opt.duration` is optional and is a time in milliseconds that the token travels from the source to the target of the link. Default is `1000`. + // `opt.directon` is optional and it determines whether the token goes from source to target or other way round (`reverse`) + // `opt.connection` is an optional selector to the connection path. + // `callback` is optional and is a function to be called once the token reaches the target. + sendToken: function(token, opt, callback) { + + function onAnimationEnd(vToken, callback) { + return function() { + vToken.remove(); + if (typeof callback === 'function') { + callback(); + } + }; + } + + var duration, isReversed, selector; + if (isObject(opt)) { + duration = opt.duration; + isReversed = (opt.direction === 'reverse'); + selector = opt.connection; + } else { + // Backwards compatibility + duration = opt; + isReversed = false; + selector = null; + } + + duration = duration || 1000; + + var animationAttributes = { + dur: duration + 'ms', + repeatCount: 1, + calcMode: 'linear', + fill: 'freeze' + }; + + if (isReversed) { + animationAttributes.keyPoints = '1;0'; + animationAttributes.keyTimes = '0;1'; + } + + var vToken = V(token); + var connection; + if (typeof selector === 'string') { + // Use custom connection path. + connection = this.findNode(selector); + } else { + // Select connection path automatically. + var cache = this._V; + connection = (cache.connection) ? cache.connection.node : this.el.querySelector('path'); + } + + if (!(connection instanceof SVGPathElement)) { + throw new Error('dia.LinkView: token animation requires a valid connection path.'); + } + + vToken + .appendTo(this.paper.cells) + .animateAlongPath(animationAttributes, connection); + + setTimeout(onAnimationEnd(vToken, callback), duration); + }, + + findRoute: function(vertices) { + + vertices || (vertices = []); + + var namespace = this.paper.options.routerNamespace || routers; + var router = this.model.router(); + var defaultRouter = this.paper.options.defaultRouter; + + if (!router) { + if (defaultRouter) router = defaultRouter; + else return vertices.map(Point); // no router specified + } + + var routerFn = isFunction(router) ? router : namespace[router.name]; + if (!isFunction(routerFn)) { + throw new Error('dia.LinkView: unknown router: "' + router.name + '".'); + } + + var args = router.args || {}; + + var route = routerFn.call( + this, // context + vertices, // vertices + args, // options + this // linkView + ); + + if (!route) return vertices.map(Point); + return route; + }, + + // Return the `d` attribute value of the `` element representing the link + // between `source` and `target`. + findPath: function(route, sourcePoint, targetPoint) { + + var namespace = this.paper.options.connectorNamespace || connectors; + var connector = this.model.connector(); + var defaultConnector = this.paper.options.defaultConnector; + + if (!connector) { + connector = defaultConnector || {}; + } + + var connectorFn = isFunction(connector) ? connector : namespace[connector.name]; + if (!isFunction(connectorFn)) { + throw new Error('dia.LinkView: unknown connector: "' + connector.name + '".'); + } + + var args = clone(connector.args || {}); + args.raw = true; // Request raw g.Path as the result. + + var path = connectorFn.call( + this, // context + sourcePoint, // start point + targetPoint, // end point + route, // vertices + args, // options + this // linkView + ); + + if (typeof path === 'string') { + // Backwards compatibility for connectors not supporting `raw` option. + path = new Path(V.normalizePathData(path)); + } + + return path; + }, + + // Public API. + // ----------- + + getConnection: function() { + + var path = this.path; + if (!path) return null; + + return path.clone(); + }, + + getSerializedConnection: function() { + + var path = this.path; + if (!path) return null; + + var metrics = this.metrics; + if (metrics.hasOwnProperty('data')) return metrics.data; + var data = path.serialize(); + metrics.data = data; + return data; + }, + + getConnectionSubdivisions: function() { + + var path = this.path; + if (!path) return null; + + var metrics = this.metrics; + if (metrics.hasOwnProperty('segmentSubdivisions')) return metrics.segmentSubdivisions; + var subdivisions = path.getSegmentSubdivisions(); + metrics.segmentSubdivisions = subdivisions; + return subdivisions; + }, + + getConnectionLength: function() { + + var path = this.path; + if (!path) return 0; + + var metrics = this.metrics; + if (metrics.hasOwnProperty('length')) return metrics.length; + var length = path.length({ segmentSubdivisions: this.getConnectionSubdivisions() }); + metrics.length = length; + return length; + }, + + getPointAtLength: function(length) { + + var path = this.path; + if (!path) return null; + + return path.pointAtLength(length, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getPointAtRatio: function(ratio) { + + var path = this.path; + if (!path) return null; + if (isPercentage(ratio)) ratio = parseFloat(ratio) / 100; + return path.pointAt(ratio, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getTangentAtLength: function(length) { + + var path = this.path; + if (!path) return null; + + return path.tangentAtLength(length, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getTangentAtRatio: function(ratio) { + + var path = this.path; + if (!path) return null; + + return path.tangentAt(ratio, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getClosestPoint: function(point) { + + var path = this.path; + if (!path) return null; + + return path.closestPoint(point, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getClosestPointLength: function(point) { + + var path = this.path; + if (!path) return null; + + return path.closestPointLength(point, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + getClosestPointRatio: function(point) { + + var path = this.path; + if (!path) return null; + + return path.closestPointNormalizedLength(point, { segmentSubdivisions: this.getConnectionSubdivisions() }); + }, + + // Get label position object based on two provided coordinates, x and y. + // (Used behind the scenes when user moves labels around.) + // Two signatures: + // - num, num, obj = x, y, options + // - num, num, num, obj = x, y, angle, options + // Accepts distance/offset options = `absoluteDistance: boolean`, `reverseDistance: boolean`, `absoluteOffset: boolean` + // - `absoluteOffset` is necessary in order to move beyond connection endpoints + // Additional options = `keepGradient: boolean`, `ensureLegibility: boolean` + getLabelPosition: function(x, y, p3, p4) { + + var position = {}; + + // normalize data from the two possible signatures + var localAngle = 0; + var localOpt; + if (typeof p3 === 'number') { + // angle and opt provided as third and fourth argument + localAngle = p3; + localOpt = p4; + } else { + // opt provided as third argument + localOpt = p3; + } + + // save localOpt as `args` of the position object that is passed along + if (localOpt) position.args = localOpt; + + // identify distance/offset settings + var isDistanceRelative = !(localOpt && localOpt.absoluteDistance); // relative by default + var isDistanceAbsoluteReverse = (localOpt && localOpt.absoluteDistance && localOpt.reverseDistance); // non-reverse by default + var isOffsetAbsolute = localOpt && localOpt.absoluteOffset; // offset is non-absolute by default + + // find closest point t + var path = this.path; + var pathOpt = { segmentSubdivisions: this.getConnectionSubdivisions() }; + var labelPoint = new Point(x, y); + var t = path.closestPointT(labelPoint, pathOpt); + + // DISTANCE: + var labelDistance = path.lengthAtT(t, pathOpt); + if (isDistanceRelative) labelDistance = (labelDistance / this.getConnectionLength()) || 0; // fix to prevent NaN for 0 length + if (isDistanceAbsoluteReverse) labelDistance = (-1 * (this.getConnectionLength() - labelDistance)) || 1; // fix for end point (-0 => 1) + position.distance = labelDistance; + + // OFFSET: + // use absolute offset if: + // - opt.absoluteOffset is true, + // - opt.absoluteOffset is not true but there is no tangent + var tangent; + if (!isOffsetAbsolute) tangent = path.tangentAtT(t); + var labelOffset; + if (tangent) { + labelOffset = tangent.pointOffset(labelPoint); + } else { + var closestPoint = path.pointAtT(t); + var labelOffsetDiff = labelPoint.difference(closestPoint); + labelOffset = { x: labelOffsetDiff.x, y: labelOffsetDiff.y }; + } + position.offset = labelOffset; + + // ANGLE: + position.angle = localAngle; + + return position; + }, + + _getLabelTransformationMatrix: function(labelPosition) { + + var labelDistance; + var labelAngle = 0; + var args = {}; + if (typeof labelPosition === 'number') { + labelDistance = labelPosition; + } else if (typeof labelPosition.distance === 'number') { + args = labelPosition.args || {}; + labelDistance = labelPosition.distance; + labelAngle = labelPosition.angle || 0; + } else { + throw new Error('dia.LinkView: invalid label position distance.'); + } + + var isDistanceRelative = ((labelDistance > 0) && (labelDistance <= 1)); + + var labelOffset = 0; + var labelOffsetCoordinates = { x: 0, y: 0 }; + if (labelPosition.offset) { + var positionOffset = labelPosition.offset; + if (typeof positionOffset === 'number') labelOffset = positionOffset; + if (positionOffset.x) labelOffsetCoordinates.x = positionOffset.x; + if (positionOffset.y) labelOffsetCoordinates.y = positionOffset.y; + } + + var isOffsetAbsolute = ((labelOffsetCoordinates.x !== 0) || (labelOffsetCoordinates.y !== 0) || labelOffset === 0); + + var isKeepGradient = args.keepGradient; + var isEnsureLegibility = args.ensureLegibility; + + var path = this.path; + var pathOpt = { segmentSubdivisions: this.getConnectionSubdivisions() }; + + var distance = isDistanceRelative ? (labelDistance * this.getConnectionLength()) : labelDistance; + var tangent = path.tangentAtLength(distance, pathOpt); + + var translation; + var angle = labelAngle; + if (tangent) { + if (isOffsetAbsolute) { + translation = tangent.start.clone(); + translation.offset(labelOffsetCoordinates); + } else { + var normal = tangent.clone(); + normal.rotate(tangent.start, -90); + normal.setLength(labelOffset); + translation = normal.end; + } + + if (isKeepGradient) { + angle = (tangent.angle() + labelAngle); + if (isEnsureLegibility) { + angle = normalizeAngle(((angle + 90) % 180) - 90); + } + } + + } else { + // fallback - the connection has zero length + translation = path.start.clone(); + if (isOffsetAbsolute) translation.offset(labelOffsetCoordinates); + } + + return V.createSVGMatrix() + .translate(translation.x, translation.y) + .rotate(angle); + }, + + getLabelCoordinates: function(labelPosition) { + + var transformationMatrix = this._getLabelTransformationMatrix(labelPosition); + return new Point(transformationMatrix.e, transformationMatrix.f); + }, + + getVertexIndex: function(x, y) { + + var model = this.model; + var vertices = model.vertices(); + + var vertexLength = this.getClosestPointLength(new Point(x, y)); + + var idx = 0; + for (var n = vertices.length; idx < n; idx++) { + var currentVertex = vertices[idx]; + var currentVertexLength = this.getClosestPointLength(currentVertex); + if (vertexLength < currentVertexLength) break; + } + + return idx; + }, + + // Interaction. The controller part. + // --------------------------------- + + notifyPointerdown(evt, x, y) { + CellView.prototype.pointerdown.call(this, evt, x, y); + this.notify('link:pointerdown', evt, x, y); + }, + + notifyPointermove(evt, x, y) { + CellView.prototype.pointermove.call(this, evt, x, y); + this.notify('link:pointermove', evt, x, y); + }, + + notifyPointerup(evt, x, y) { + this.notify('link:pointerup', evt, x, y); + CellView.prototype.pointerup.call(this, evt, x, y); + }, + + pointerdblclick: function(evt, x, y) { + + CellView.prototype.pointerdblclick.apply(this, arguments); + this.notify('link:pointerdblclick', evt, x, y); + }, + + pointerclick: function(evt, x, y) { + + CellView.prototype.pointerclick.apply(this, arguments); + this.notify('link:pointerclick', evt, x, y); + }, + + contextmenu: function(evt, x, y) { + + CellView.prototype.contextmenu.apply(this, arguments); + this.notify('link:contextmenu', evt, x, y); + }, + + pointerdown: function(evt, x, y) { + + this.notifyPointerdown(evt, x, y); + + // Backwards compatibility for the default markup + var className = evt.target.getAttribute('class'); + switch (className) { + + case 'marker-vertex': + this.dragVertexStart(evt, x, y); + return; + + case 'marker-vertex-remove': + case 'marker-vertex-remove-area': + this.dragVertexRemoveStart(evt, x, y); + return; + + case 'marker-arrowhead': + this.dragArrowheadStart(evt, x, y); + return; + + case 'connection': + case 'connection-wrap': + this.dragConnectionStart(evt, x, y); + return; + + case 'marker-source': + case 'marker-target': + return; + } + + this.dragStart(evt, x, y); + }, + + pointermove: function(evt, x, y) { + + // Backwards compatibility + var dragData = this._dragData; + if (dragData) this.eventData(evt, dragData); + + var data = this.eventData(evt); + switch (data.action) { + + case 'vertex-move': + this.dragVertex(evt, x, y); + break; + + case 'label-move': + this.dragLabel(evt, x, y); + break; + + case 'arrowhead-move': + this.dragArrowhead(evt, x, y); + break; + + case 'move': + this.drag(evt, x, y); + break; + } + + // Backwards compatibility + if (dragData) assign(dragData, this.eventData(evt)); + + this.notifyPointermove(evt, x, y); + }, + + pointerup: function(evt, x, y) { + + // Backwards compatibility + var dragData = this._dragData; + if (dragData) { + this.eventData(evt, dragData); + this._dragData = null; + } + + var data = this.eventData(evt); + switch (data.action) { + + case 'vertex-move': + this.dragVertexEnd(evt, x, y); + break; + + case 'label-move': + this.dragLabelEnd(evt, x, y); + break; + + case 'arrowhead-move': + this.dragArrowheadEnd(evt, x, y); + break; + + case 'move': + this.dragEnd(evt, x, y); + } + + this.notifyPointerup(evt, x, y); + this.checkMouseleave(evt); + }, + + mouseover: function(evt) { + + CellView.prototype.mouseover.apply(this, arguments); + this.notify('link:mouseover', evt); + }, + + mouseout: function(evt) { + + CellView.prototype.mouseout.apply(this, arguments); + this.notify('link:mouseout', evt); + }, + + mouseenter: function(evt) { + + CellView.prototype.mouseenter.apply(this, arguments); + this.notify('link:mouseenter', evt); + }, + + mouseleave: function(evt) { + + CellView.prototype.mouseleave.apply(this, arguments); + this.notify('link:mouseleave', evt); + }, + + mousewheel: function(evt, x, y, delta) { + + CellView.prototype.mousewheel.apply(this, arguments); + this.notify('link:mousewheel', evt, x, y, delta); + }, + + onevent: function(evt, eventName, x, y) { + + // Backwards compatibility + var linkTool = V(evt.target).findParentByClass('link-tool', this.el); + if (linkTool) { + // No further action to be executed + evt.stopPropagation(); + + // Allow `interactive.useLinkTools=false` + if (this.can('useLinkTools')) { + if (eventName === 'remove') { + // Built-in remove event + this.model.remove({ ui: true }); + // Do not trigger link pointerdown + return; + + } else { + // link:options and other custom events inside the link tools + this.notify(eventName, evt, x, y); + } + } + + this.notifyPointerdown(evt, x, y); + this.paper.delegateDragEvents(this, evt.data); + + } else { + CellView.prototype.onevent.apply(this, arguments); + } + }, + + onlabel: function(evt, x, y) { + + this.notifyPointerdown(evt, x, y); + + this.dragLabelStart(evt, x, y); + + var stopPropagation = this.eventData(evt).stopPropagation; + if (stopPropagation) evt.stopPropagation(); + }, + + // Drag Start Handlers + + dragConnectionStart: function(evt, x, y) { + + if (!this.can('vertexAdd')) return; + + // Store the index at which the new vertex has just been placed. + // We'll be update the very same vertex position in `pointermove()`. + var vertexIdx = this.addVertex({ x: x, y: y }, { ui: true }); + this.eventData(evt, { + action: 'vertex-move', + vertexIdx: vertexIdx + }); + }, + + dragLabelStart: function(evt, x, y) { + + if (this.can('labelMove')) { + + if (this.isDefaultInteractionPrevented(evt)) return; + + var labelNode = evt.currentTarget; + var labelIdx = parseInt(labelNode.getAttribute('label-idx'), 10); + + var defaultLabelPosition = this._getDefaultLabelPositionProperty(); + var initialLabelPosition = this._normalizeLabelPosition(this._getLabelPositionProperty(labelIdx)); + var position = this._mergeLabelPositionProperty(initialLabelPosition, defaultLabelPosition); + + var coords = this.getLabelCoordinates(position); + var dx = coords.x - x; // how much needs to be added to cursor x to get to label x + var dy = coords.y - y; // how much needs to be added to cursor y to get to label y + + var positionAngle = this._getLabelPositionAngle(labelIdx); + var labelPositionArgs = this._getLabelPositionArgs(labelIdx); + var defaultLabelPositionArgs = this._getDefaultLabelPositionArgs(); + var positionArgs = this._mergeLabelPositionArgs(labelPositionArgs, defaultLabelPositionArgs); + + this.eventData(evt, { + action: 'label-move', + labelIdx: labelIdx, + dx: dx, + dy: dy, + positionAngle: positionAngle, + positionArgs: positionArgs, + stopPropagation: true + }); + + } else { + + // Backwards compatibility: + // If labels can't be dragged no default action is triggered. + this.eventData(evt, { stopPropagation: true }); + } + + this.paper.delegateDragEvents(this, evt.data); + }, + + dragVertexStart: function(evt, x, y) { + + if (!this.can('vertexMove')) return; + + var vertexNode = evt.target; + var vertexIdx = parseInt(vertexNode.getAttribute('idx'), 10); + this.eventData(evt, { + action: 'vertex-move', + vertexIdx: vertexIdx + }); + }, + + dragVertexRemoveStart: function(evt, x, y) { + + if (!this.can('vertexRemove')) return; + + var removeNode = evt.target; + var vertexIdx = parseInt(removeNode.getAttribute('idx'), 10); + this.model.removeVertex(vertexIdx); + }, + + dragArrowheadStart: function(evt, x, y) { + + if (!this.can('arrowheadMove')) return; + + var arrowheadNode = evt.target; + var arrowheadType = arrowheadNode.getAttribute('end'); + var data = this.startArrowheadMove(arrowheadType, { ignoreBackwardsCompatibility: true }); + + this.eventData(evt, data); + }, + + dragStart: function(evt, x, y) { + + if (this.isDefaultInteractionPrevented(evt)) return; + + if (!this.can('linkMove')) return; + + this.eventData(evt, { + action: 'move', + dx: x, + dy: y + }); + }, + + // Drag Handlers + dragLabel: function(evt, x, y) { + + var data = this.eventData(evt); + var label = { position: this.getLabelPosition((x + data.dx), (y + data.dy), data.positionAngle, data.positionArgs) }; + if (this.paper.options.snapLabels) delete label.position.offset; + // The `touchmove' events are not fired + // when the original event target is removed from the DOM. + // The labels are currently re-rendered completely when only + // the position changes. This is why we need to make sure that + // the label is updated synchronously. + // TODO: replace `touchmove` with `pointermove` (breaking change). + const setOptions = { ui: true }; + if (this.paper.isAsync() && evt.type === 'touchmove') { + setOptions.async = false; + } + this.model.label(data.labelIdx, label, setOptions); + }, + + dragVertex: function(evt, x, y) { + + var data = this.eventData(evt); + this.model.vertex(data.vertexIdx, { x: x, y: y }, { ui: true }); + }, + + dragArrowhead: function(evt, x, y) { + if (this.paper.options.snapLinks) { + const isSnapped = this._snapArrowhead(evt, x, y); + if (!isSnapped && this.paper.options.snapLinksSelf) { + this._snapArrowheadSelf(evt, x, y); + } + } else { + if (this.paper.options.snapLinksSelf) { + this._snapArrowheadSelf(evt, x, y); + } else { + this._connectArrowhead(this.getEventTarget(evt), x, y, this.eventData(evt)); + } + } + }, + + drag: function(evt, x, y) { + + var data = this.eventData(evt); + this.model.translate(x - data.dx, y - data.dy, { ui: true }); + this.eventData(evt, { + dx: x, + dy: y + }); + }, + + // Drag End Handlers + + dragLabelEnd: function() { + // noop + }, + + dragVertexEnd: function() { + // noop + }, + + dragArrowheadEnd: function(evt, x, y) { + + var data = this.eventData(evt); + var paper = this.paper; + + if (paper.options.snapLinks) { + this._snapArrowheadEnd(data); + } else { + this._connectArrowheadEnd(data, x, y); + } + + if (!paper.linkAllowed(this)) { + // If the changed link is not allowed, revert to its previous state. + this._disallow(data); + } else { + this._finishEmbedding(data); + this._notifyConnectEvent(data, evt); + } + + this._afterArrowheadMove(data); + }, + + dragEnd: function() { + // noop + }, + + _disallow: function(data) { + + switch (data.whenNotAllowed) { + + case 'remove': + this.model.remove({ ui: true }); + break; + + case 'revert': + default: + this.model.set(data.arrowhead, data.initialEnd, { ui: true }); + break; + } + }, + + _finishEmbedding: function(data) { + + // Reparent the link if embedding is enabled + if (this.paper.options.embeddingMode && this.model.reparent()) { + // Make sure we don't reverse to the original 'z' index (see afterArrowheadMove()). + data.z = null; + } + }, + + _notifyConnectEvent: function(data, evt) { + + var arrowhead = data.arrowhead; + var initialEnd = data.initialEnd; + var currentEnd = this.model.prop(arrowhead); + var endChanged = currentEnd && !Link.endsEqual(initialEnd, currentEnd); + if (endChanged) { + var paper = this.paper; + if (initialEnd.id) { + this.notify('link:disconnect', evt, paper.findViewByModel(initialEnd.id), data.initialMagnet, arrowhead); + } + if (currentEnd.id) { + this.notify('link:connect', evt, paper.findViewByModel(currentEnd.id), data.magnetUnderPointer, arrowhead); + } + } + }, + + _snapToPoints: function(snapPoint, points, radius) { + let closestPointX = null; + let closestDistanceX = Infinity; + + let closestPointY = null; + let closestDistanceY = Infinity; + + let x = snapPoint.x; + let y = snapPoint.y; + + for (let i = 0; i < points.length; i++) { + const distX = Math.abs(points[i].x - snapPoint.x); + if (distX < closestDistanceX) { + closestDistanceX = distX; + closestPointX = points[i]; + } + + const distY = Math.abs(points[i].y - snapPoint.y); + if (distY < closestDistanceY) { + closestDistanceY = distY; + closestPointY = points[i]; + } + } + + if (closestDistanceX < radius) { + x = closestPointX.x; + } + if (closestDistanceY < radius) { + y = closestPointY.y; + } + + return { x, y }; + }, + + _snapArrowheadSelf: function(evt, x, y) { + + const { paper, model } = this; + const { snapLinksSelf } = paper.options; + const data = this.eventData(evt); + const radius = snapLinksSelf.radius || 20; + + const anchor = this.getEndAnchor(data.arrowhead === 'source' ? 'target' : 'source'); + const vertices = model.vertices(); + const points = [anchor, ...vertices]; + + const snapPoint = this._snapToPoints({ x: x, y: y }, points, radius); + + const point = paper.localToClientPoint(snapPoint); + this._connectArrowhead(document.elementFromPoint(point.x, point.y), snapPoint.x, snapPoint.y, this.eventData(evt)); + }, + + _snapArrowhead: function(evt, x, y) { + + const { paper } = this; + const { snapLinks, connectionStrategy } = paper.options; + const data = this.eventData(evt); + let isSnapped = false; + // checking view in close area of the pointer + + var r = snapLinks.radius || 50; + var viewsInArea = paper.findViewsInArea({ x: x - r, y: y - r, width: 2 * r, height: 2 * r }); + + var prevClosestView = data.closestView || null; + var prevClosestMagnet = data.closestMagnet || null; + var prevMagnetProxy = data.magnetProxy || null; + + data.closestView = data.closestMagnet = data.magnetProxy = null; + + var minDistance = Number.MAX_VALUE; + var pointer = new Point(x, y); + + viewsInArea.forEach(function(view) { + const candidates = []; + // skip connecting to the element in case '.': { magnet: false } attribute present + if (view.el.getAttribute('magnet') !== 'false') { + candidates.push({ + bbox: view.model.getBBox(), + magnet: view.el + }); + } + + view.$('[magnet]').toArray().forEach(magnet => { + candidates.push({ + bbox: view.getNodeBBox(magnet), + magnet + }); + }); + + candidates.forEach(candidate => { + const { magnet, bbox } = candidate; + // find distance from the center of the model to pointer coordinates + const distance = bbox.center().squaredDistance(pointer); + // the connection is looked up in a circle area by `distance < r` + if (distance < minDistance) { + const isAlreadyValidated = prevClosestMagnet === magnet; + if (isAlreadyValidated || paper.options.validateConnection.apply( + paper, data.validateConnectionArgs(view, (view.el === magnet) ? null : magnet) + )) { + minDistance = distance; + data.closestView = view; + data.closestMagnet = magnet; + } + } + }); + + }, this); + + var end; + var magnetProxy = null; + var closestView = data.closestView; + var closestMagnet = data.closestMagnet; + if (closestMagnet) { + magnetProxy = data.magnetProxy = closestView.findProxyNode(closestMagnet, 'highlighter'); + } + var endType = data.arrowhead; + var newClosestMagnet = (prevClosestMagnet !== closestMagnet); + if (prevClosestView && newClosestMagnet) { + prevClosestView.unhighlight(prevMagnetProxy, { + connecting: true, + snapping: true + }); + } + + if (closestView) { + const { prevEnd, prevX, prevY } = data; + data.prevX = x; + data.prevY = y; + isSnapped = true; + + if (!newClosestMagnet) { + if (typeof connectionStrategy !== 'function' || (prevX === x && prevY === y)) { + // the magnet has not changed and the link's end does not depend on the x and y + return isSnapped; + } + } + + end = closestView.getLinkEnd(closestMagnet, x, y, this.model, endType); + if (!newClosestMagnet && isEqual(prevEnd, end)) { + // the source/target json has not changed + return isSnapped; + } + + data.prevEnd = end; + + if (newClosestMagnet) { + closestView.highlight(magnetProxy, { + connecting: true, + snapping: true + }); + } + + } else { + + end = { x: x, y: y }; + } + + this.model.set(endType, end || { x: x, y: y }, { ui: true }); + + if (prevClosestView) { + this.notify('link:snap:disconnect', evt, prevClosestView, prevClosestMagnet, endType); + } + if (closestView) { + this.notify('link:snap:connect', evt, closestView, closestMagnet, endType); + } + + return isSnapped; + }, + + _snapArrowheadEnd: function(data) { + + // Finish off link snapping. + // Everything except view unhighlighting was already done on pointermove. + var closestView = data.closestView; + var closestMagnet = data.closestMagnet; + if (closestView && closestMagnet) { + + closestView.unhighlight(data.magnetProxy, { connecting: true, snapping: true }); + data.magnetUnderPointer = closestView.findMagnet(closestMagnet); + } + + data.closestView = data.closestMagnet = null; + }, + + _connectArrowhead: function(target, x, y, data) { + + // checking views right under the pointer + const { paper, model } = this; + + if (data.eventTarget !== target) { + // Unhighlight the previous view under pointer if there was one. + if (data.magnetProxy) { + data.viewUnderPointer.unhighlight(data.magnetProxy, { + connecting: true + }); + } + + const viewUnderPointer = data.viewUnderPointer = paper.findView(target); + if (viewUnderPointer) { + // If we found a view that is under the pointer, we need to find the closest + // magnet based on the real target element of the event. + const magnetUnderPointer = data.magnetUnderPointer = viewUnderPointer.findMagnet(target); + const magnetProxy = data.magnetProxy = viewUnderPointer.findProxyNode(magnetUnderPointer, 'highlighter'); + + if (magnetUnderPointer && this.paper.options.validateConnection.apply( + paper, + data.validateConnectionArgs(viewUnderPointer, magnetUnderPointer) + )) { + // If there was no magnet found, do not highlight anything and assume there + // is no view under pointer we're interested in reconnecting to. + // This can only happen if the overall element has the attribute `'.': { magnet: false }`. + if (magnetProxy) { + viewUnderPointer.highlight(magnetProxy, { + connecting: true + }); + } + } else { + // This type of connection is not valid. Disregard this magnet. + data.magnetUnderPointer = null; + data.magnetProxy = null; + } + } else { + // Make sure we'll unset previous magnet. + data.magnetUnderPointer = null; + data.magnetProxy = null; + } + } + + data.eventTarget = target; + + model.set(data.arrowhead, { x: x, y: y }, { ui: true }); + }, + + _connectArrowheadEnd: function(data = {}, x, y) { + + const { model } = this; + const { viewUnderPointer, magnetUnderPointer, magnetProxy, arrowhead } = data; + + if (!magnetUnderPointer || !magnetProxy || !viewUnderPointer) return; + + viewUnderPointer.unhighlight(magnetProxy, { connecting: true }); + + // The link end is taken from the magnet under the pointer, not the proxy. + const end = viewUnderPointer.getLinkEnd(magnetUnderPointer, x, y, model, arrowhead); + model.set(arrowhead, end, { ui: true }); + }, + + _beforeArrowheadMove: function(data) { + + data.z = this.model.get('z'); + this.model.toFront(); + + // Let the pointer propagate through the link view elements so that + // the `evt.target` is another element under the pointer, not the link itself. + var style = this.el.style; + data.pointerEvents = style.pointerEvents; + style.pointerEvents = 'none'; + + if (this.paper.options.markAvailable) { + this._markAvailableMagnets(data); + } + }, + + _afterArrowheadMove: function(data) { + + if (data.z !== null) { + this.model.set('z', data.z, { ui: true }); + data.z = null; + } + + // Put `pointer-events` back to its original value. See `_beforeArrowheadMove()` for explanation. + this.el.style.pointerEvents = data.pointerEvents; + + if (this.paper.options.markAvailable) { + this._unmarkAvailableMagnets(data); + } + }, + + _createValidateConnectionArgs: function(arrowhead) { + // It makes sure the arguments for validateConnection have the following form: + // (source view, source magnet, target view, target magnet and link view) + var args = []; + + args[4] = arrowhead; + args[5] = this; + + var oppositeArrowhead; + var i = 0; + var j = 0; + + if (arrowhead === 'source') { + i = 2; + oppositeArrowhead = 'target'; + } else { + j = 2; + oppositeArrowhead = 'source'; + } + + var end = this.model.get(oppositeArrowhead); + + if (end.id) { + var view = args[i] = this.paper.findViewByModel(end.id); + var magnet = view.getMagnetFromLinkEnd(end); + if (magnet === view.el) magnet = undefined; + args[i + 1] = magnet; + } + + function validateConnectionArgs(cellView, magnet) { + args[j] = cellView; + args[j + 1] = cellView.el === magnet ? undefined : magnet; + return args; + } + + return validateConnectionArgs; + }, + + _markAvailableMagnets: function(data) { + + function isMagnetAvailable(view, magnet) { + var paper = view.paper; + var validate = paper.options.validateConnection; + return validate.apply(paper, this.validateConnectionArgs(view, magnet)); + } + + var paper = this.paper; + var elements = paper.model.getCells(); + data.marked = {}; + + for (var i = 0, n = elements.length; i < n; i++) { + var view = elements[i].findView(paper); + + if (!view) { + continue; + } + + var magnets = Array.prototype.slice.call(view.el.querySelectorAll('[magnet]')); + if (view.el.getAttribute('magnet') !== 'false') { + // Element wrapping group is also a magnet + magnets.push(view.el); + } + + var availableMagnets = magnets.filter(isMagnetAvailable.bind(data, view)); + + if (availableMagnets.length > 0) { + // highlight all available magnets + for (var j = 0, m = availableMagnets.length; j < m; j++) { + view.highlight(availableMagnets[j], { magnetAvailability: true }); + } + // highlight the entire view + view.highlight(null, { elementAvailability: true }); + + data.marked[view.model.id] = availableMagnets; + } + } + }, + + _unmarkAvailableMagnets: function(data) { + + var markedKeys = Object.keys(data.marked); + var id; + var markedMagnets; + + for (var i = 0, n = markedKeys.length; i < n; i++) { + id = markedKeys[i]; + markedMagnets = data.marked[id]; + + var view = this.paper.findViewByModel(id); + if (view) { + for (var j = 0, m = markedMagnets.length; j < m; j++) { + view.unhighlight(markedMagnets[j], { magnetAvailability: true }); + } + view.unhighlight(null, { elementAvailability: true }); + } + } + + data.marked = null; + }, + + startArrowheadMove: function(end, opt) { + + opt || (opt = {}); + + // Allow to delegate events from an another view to this linkView in order to trigger arrowhead + // move without need to click on the actual arrowhead dom element. + var data = { + action: 'arrowhead-move', + arrowhead: end, + whenNotAllowed: opt.whenNotAllowed || 'revert', + initialMagnet: this[end + 'Magnet'] || (this[end + 'View'] ? this[end + 'View'].el : null), + initialEnd: clone(this.model.get(end)), + validateConnectionArgs: this._createValidateConnectionArgs(end) + }; + + this._beforeArrowheadMove(data); + + if (opt.ignoreBackwardsCompatibility !== true) { + this._dragData = data; + } + + return data; + }, + + // Lifecycle methods + + onMount: function() { + CellView.prototype.onMount.apply(this, arguments); + this.mountLabels(); + }, + + onDetach: function() { + CellView.prototype.onDetach.apply(this, arguments); + this.unmountLabels(); + }, + + onRemove: function() { + CellView.prototype.onRemove.apply(this, arguments); + this.unmountLabels(); + } + + }, { + + Flags: Flags, + }); + + Object.defineProperty(LinkView.prototype, 'sourceBBox', { + + enumerable: true, + + get: function() { + var sourceView = this.sourceView; + if (!sourceView) { + var sourceDef = this.model.source(); + return new Rect(sourceDef.x, sourceDef.y); + } + var sourceMagnet = this.sourceMagnet; + if (sourceView.isNodeConnection(sourceMagnet)) { + return new Rect(this.sourceAnchor); + } + return sourceView.getNodeBBox(sourceMagnet || sourceView.el); + } + + }); + + Object.defineProperty(LinkView.prototype, 'targetBBox', { + + enumerable: true, + + get: function() { + var targetView = this.targetView; + if (!targetView) { + var targetDef = this.model.target(); + return new Rect(targetDef.x, targetDef.y); + } + var targetMagnet = this.targetMagnet; + if (targetView.isNodeConnection(targetMagnet)) { + return new Rect(this.targetAnchor); + } + return targetView.getNodeBBox(targetMagnet || targetView.el); + } + }); + + joint.dia.LegacyLinkView = LinkView; + +})(joint); diff --git a/packages/joint-core/demo/archive/logic/index.html b/packages/joint-core/demo/archive/logic/index.html index be28ece5a..32a72ccb6 100644 --- a/packages/joint-core/demo/archive/logic/index.html +++ b/packages/joint-core/demo/archive/logic/index.html @@ -8,12 +8,14 @@ Logic Circuits | JointJS +
+ diff --git a/packages/joint-core/demo/archive/logic/src/logic.js b/packages/joint-core/demo/archive/logic/src/logic.js index a306617f2..9c4f7bb26 100644 --- a/packages/joint-core/demo/archive/logic/src/logic.js +++ b/packages/joint-core/demo/archive/logic/src/logic.js @@ -188,6 +188,7 @@ var paper = new joint.dia.Paper({ snapLinks: true, linkPinning: false, cellViewNamespace: shapes, + linkView: joint.dia.LegacyLinkView, defaultLink: new shapes.logic.Wire, validateConnection: function(vs, ms, vt, mt, e, vl) { diff --git a/packages/joint-core/demo/archive/org/index.html b/packages/joint-core/demo/archive/org/index.html index 321394c77..7d0fc9f7a 100644 --- a/packages/joint-core/demo/archive/org/index.html +++ b/packages/joint-core/demo/archive/org/index.html @@ -8,11 +8,14 @@ Organizational Charts | JointJS + +
+ diff --git a/packages/joint-core/demo/archive/org/src/org.js b/packages/joint-core/demo/archive/org/src/org.js index b3561f3af..956ea4bc5 100644 --- a/packages/joint-core/demo/archive/org/src/org.js +++ b/packages/joint-core/demo/archive/org/src/org.js @@ -48,6 +48,7 @@ var paper = new joint.dia.Paper({ gridSize: 1, model: graph, cellViewNamespace: shapes, + linkView: joint.dia.LegacyLinkView, defaultAnchor: { name: 'perpendicular' }, restrictTranslate: true }); diff --git a/packages/joint-core/demo/archive/petri-nets/index.html b/packages/joint-core/demo/archive/petri-nets/index.html index b61bb905e..d9811b814 100644 --- a/packages/joint-core/demo/archive/petri-nets/index.html +++ b/packages/joint-core/demo/archive/petri-nets/index.html @@ -8,6 +8,7 @@ Petri Nets | JointJS + @@ -15,6 +16,7 @@ + diff --git a/packages/joint-core/demo/archive/petri-nets/src/pn.js b/packages/joint-core/demo/archive/petri-nets/src/pn.js index f34b3727d..3802ac508 100644 --- a/packages/joint-core/demo/archive/petri-nets/src/pn.js +++ b/packages/joint-core/demo/archive/petri-nets/src/pn.js @@ -126,6 +126,7 @@ var paper = new joint.dia.Paper({ height: 350, gridSize: 10, cellViewNamespace: shapes, + linkView: joint.dia.LegacyLinkView, defaultAnchor: { name: 'perpendicular' }, defaultConnectionPoint: { name: 'boundary' }, model: graph diff --git a/packages/joint-core/demo/links/pipes.html b/packages/joint-core/demo/archive/pipes/pipes.html similarity index 65% rename from packages/joint-core/demo/links/pipes.html rename to packages/joint-core/demo/archive/pipes/pipes.html index 26ebca51d..bf0fe46cb 100644 --- a/packages/joint-core/demo/links/pipes.html +++ b/packages/joint-core/demo/archive/pipes/pipes.html @@ -4,7 +4,8 @@ Pipes - link patterns - + + @@ -14,8 +15,8 @@
- - + + diff --git a/packages/joint-core/demo/links/src/pipes.js b/packages/joint-core/demo/archive/pipes/src/pipes.js similarity index 98% rename from packages/joint-core/demo/links/src/pipes.js rename to packages/joint-core/demo/archive/pipes/src/pipes.js index b9e80605d..66829b862 100644 --- a/packages/joint-core/demo/links/src/pipes.js +++ b/packages/joint-core/demo/archive/pipes/src/pipes.js @@ -14,7 +14,7 @@ joint.connectors.normal = function(sourcePoint, targetPoint, vertices) { return d.join(' '); }; -var PatternLinkView = joint.dia.LinkView.extend({ +var PatternLinkView = joint.dia.LegacyLinkView.extend({ patternMarkup: [ '', @@ -32,7 +32,7 @@ var PatternLinkView = joint.dia.LinkView.extend({ V(this.paper.svg).defs().append(this.pattern); } - joint.dia.LinkView.prototype.render.apply(this, arguments); + joint.dia.LegacyLinkView.prototype.render.apply(this, arguments); this._V.connection.attr({ 'stroke': 'url(#pattern-' + this.id + ')' }); diff --git a/packages/joint-core/demo/archive/umlcd/index.html b/packages/joint-core/demo/archive/umlcd/index.html index 11dd1b062..07a34cd18 100644 --- a/packages/joint-core/demo/archive/umlcd/index.html +++ b/packages/joint-core/demo/archive/umlcd/index.html @@ -8,12 +8,14 @@ UML Class Diagrams | JointJS +
+ diff --git a/packages/joint-core/demo/archive/umlcd/src/umlcd.js b/packages/joint-core/demo/archive/umlcd/src/umlcd.js index 54183b8ab..208738d10 100644 --- a/packages/joint-core/demo/archive/umlcd/src/umlcd.js +++ b/packages/joint-core/demo/archive/umlcd/src/umlcd.js @@ -159,6 +159,7 @@ new joint.dia.Paper({ gridSize: 1, model: graph, cellViewNamespace: shapes, + linkView: joint.dia.LegacyLinkView, }); var uml = joint.shapes.uml; diff --git a/packages/joint-core/demo/archive/umlsc/index.html b/packages/joint-core/demo/archive/umlsc/index.html index ca238722a..4706ab31e 100644 --- a/packages/joint-core/demo/archive/umlsc/index.html +++ b/packages/joint-core/demo/archive/umlsc/index.html @@ -8,12 +8,14 @@ UML StateChart Diagrams | JointJS +
+ diff --git a/packages/joint-core/demo/archive/umlsc/src/umlsc.js b/packages/joint-core/demo/archive/umlsc/src/umlsc.js index c1e836541..e26634a86 100644 --- a/packages/joint-core/demo/archive/umlsc/src/umlsc.js +++ b/packages/joint-core/demo/archive/umlsc/src/umlsc.js @@ -133,6 +133,7 @@ new joint.dia.Paper({ gridSize: 1, model: graph, cellViewNamespace: shapes, + linkView: joint.dia.LegacyLinkView, }); var uml = joint.shapes.uml; diff --git a/packages/joint-core/demo/custom-shapes/src/custom-shapes.mjs b/packages/joint-core/demo/custom-shapes/src/custom-shapes.mjs index 4d9122abd..c4d7a44e6 100644 --- a/packages/joint-core/demo/custom-shapes/src/custom-shapes.mjs +++ b/packages/joint-core/demo/custom-shapes/src/custom-shapes.mjs @@ -2,10 +2,11 @@ import * as joint from '../../../joint.mjs'; import * as g from '../../../src/g/index.mjs'; import V from '../../../src/V/index.mjs'; -var graph = new joint.dia.Graph; +var graph = new joint.dia.Graph({}, { cellNamespace: joint.shapes }); var paper = new joint.dia.Paper({ el: document.getElementById('paper'), + cellViewNamespace: joint.shapes, width: 650, height: 400, gridSize: 10, diff --git a/packages/joint-core/demo/ports/port-layouts-defaults.js b/packages/joint-core/demo/ports/port-layouts-defaults.js index 327af833d..ad77bb7b6 100644 --- a/packages/joint-core/demo/ports/port-layouts-defaults.js +++ b/packages/joint-core/demo/ports/port-layouts-defaults.js @@ -39,6 +39,6 @@ var g2 = new joint.shapes.standard.Circle({ }); graph1.addCell(g2); -new joint.dia.Link({ source: { id: 'target' }, target: { id: g1.id, port: g1.getPorts()[0].id }}).addTo(graph1); -new joint.dia.Link({ source: { id: 'target' }, target: { id: g1.id, port: g1.getPorts()[1].id }}).addTo(graph1); -new joint.dia.Link({ source: { id: 'target' }, target: { id: g1.id, port: g1.getPorts()[2].id }}).addTo(graph1); +new joint.shapes.standard.Link({ source: { id: 'target' }, target: { id: g1.id, port: g1.getPorts()[0].id }}).addTo(graph1); +new joint.shapes.standard.Link({ source: { id: 'target' }, target: { id: g1.id, port: g1.getPorts()[1].id }}).addTo(graph1); +new joint.shapes.standard.Link({ source: { id: 'target' }, target: { id: g1.id, port: g1.getPorts()[2].id }}).addTo(graph1); diff --git a/packages/joint-core/demo/ports/ports2.js b/packages/joint-core/demo/ports/ports2.js index e5ec72eab..480386b38 100644 --- a/packages/joint-core/demo/ports/ports2.js +++ b/packages/joint-core/demo/ports/ports2.js @@ -55,7 +55,7 @@ graph.addCell(m2); // Manually create a link connecting ports. -var l1 = new joint.dia.Link({ +var l1 = new joint.shapes.standard.Link({ source: { id: m1.id, port: 'out' }, target: { id: m2.id, port: 'in1' } }); diff --git a/packages/joint-core/demo/rough/src/rough.js b/packages/joint-core/demo/rough/src/rough.js index b5ab48e21..c348e59db 100644 --- a/packages/joint-core/demo/rough/src/rough.js +++ b/packages/joint-core/demo/rough/src/rough.js @@ -13,7 +13,7 @@ model: graph, clickThreshold: 5, async: true, -w connectionStrategy: joint.connectionStrategies.pinAbsolute, + connectionStrategy: joint.connectionStrategies.pinAbsolute, defaultConnectionPoint: { name: 'boundary', args: { selector: 'border' }}, defaultLink: function() { return new RoughLink(); diff --git a/packages/joint-core/docs/demo/dia/Paper/interactive/arrowheadMove.html b/packages/joint-core/docs/demo/dia/Paper/interactive/arrowheadMove.html deleted file mode 100644 index 67ae54598..000000000 --- a/packages/joint-core/docs/demo/dia/Paper/interactive/arrowheadMove.html +++ /dev/null @@ -1,77 +0,0 @@ - - - - - - - dia.Paper - interactive - arrowheadMove disabled - - - - - - - - - - - - - - diff --git a/packages/joint-core/docs/demo/dia/Paper/interactive/enableAll.html b/packages/joint-core/docs/demo/dia/Paper/interactive/enableAll.html index 09ba79bc2..0ee428c36 100644 --- a/packages/joint-core/docs/demo/dia/Paper/interactive/enableAll.html +++ b/packages/joint-core/docs/demo/dia/Paper/interactive/enableAll.html @@ -36,15 +36,7 @@ interactive: true }); - var link = new joint.dia.Link({ - attrs: { - '.marker-source': { - d: 'M 10 0 L 0 5 L 10 10 z' - }, - '.marker-target': { - d: 'M 10 0 L 0 5 L 10 10 z' - } - }, + var link = new joint.shapes.standard.Link({ labels: [ { position: { distance: .5, offset: { x: 30, y: 20 } }, @@ -60,33 +52,33 @@ vertices: [{ x: 86, y: 71 }, { x: 87, y: 36 }] }); - var Circle = joint.dia.Element.define('i.Circle', { - markup: '>', + var circle = new joint.shapes.standard.Ellipse({ + size: { width: 40, height: 40 }, attrs: { - circle: { - magnet: 'passive', - r: 20 + root: { + magnetSelector: 'body', + highlighterSelector: 'body' }, - text: { + label: { fill: 'white', fontSize: 8, - xAlignment: 'middle', - yAlignment: 'middle' + textAnchor: 'middle', + textVerticalAnchor: 'middle' } } }); - graph.addCell(new Circle() - .position(226, 34) - .attr('circle/fill', '#333333') + graph.addCell(circle.clone() + .position(206, 14) + .attr('body/fill', '#333333') ); - graph.addCell(new Circle() - .position(337, 50) - .attr('circle/fill', '#a6a6a6') - .attr('circle/r', 30) - .attr('circle/magnet', true) - .attr('text/text', 'MAGNET') + graph.addCell(circle.clone() + .position(307, 20) + .resize(60, 60) + .attr('body/fill', '#a6a6a6') + .attr('body/magnet', true) + .attr('label/text', 'MAGNET') ); graph.addCell(link); diff --git a/packages/joint-core/docs/demo/dia/Paper/interactive/useLinkTools.html b/packages/joint-core/docs/demo/dia/Paper/interactive/useLinkTools.html deleted file mode 100644 index 149f07199..000000000 --- a/packages/joint-core/docs/demo/dia/Paper/interactive/useLinkTools.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - dia.Paper - interactive - useLinkTools disabled - - - - - - - - - - - - diff --git a/packages/joint-core/docs/demo/dia/Paper/interactive/vertexAdd.html b/packages/joint-core/docs/demo/dia/Paper/interactive/vertexAdd.html deleted file mode 100644 index f92059477..000000000 --- a/packages/joint-core/docs/demo/dia/Paper/interactive/vertexAdd.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - dia.Paper - interactive - vertexAdd disabled - - - - - - - - - - - - diff --git a/packages/joint-core/docs/demo/dia/Paper/interactive/vertexMove.html b/packages/joint-core/docs/demo/dia/Paper/interactive/vertexMove.html deleted file mode 100644 index 485118529..000000000 --- a/packages/joint-core/docs/demo/dia/Paper/interactive/vertexMove.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - dia.Paper - interactive - vertexMove disabled - - - - - - - - - - - - diff --git a/packages/joint-core/docs/demo/dia/Paper/interactive/vertexRemove.html b/packages/joint-core/docs/demo/dia/Paper/interactive/vertexRemove.html deleted file mode 100644 index 4b8874bed..000000000 --- a/packages/joint-core/docs/demo/dia/Paper/interactive/vertexRemove.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - dia.Paper - interactive - vertexRemove disabled - - - - - - - - - - - - diff --git a/packages/joint-core/docs/src/joint/api/dia/Element/intro.html b/packages/joint-core/docs/src/joint/api/dia/Element/intro.html index 43ee9f78b..00708de28 100644 --- a/packages/joint-core/docs/src/joint/api/dia/Element/intro.html +++ b/packages/joint-core/docs/src/joint/api/dia/Element/intro.html @@ -22,7 +22,7 @@

Presentation

selector: 'label' }] -

As we can see, the joint.shapes.standard.Rect shape consists of two subelements: one SVGRectElement named 'body' and one SVGTextElement named 'label'. The attrs object refers to the subelements' names (selectors) to provide SVG attributes to these constituent SVGElements.

+

As we can see, the joint.shapes.standard.Rectangle shape consists of two subelements: one SVGRectElement named 'body' and one SVGTextElement named 'label'. The attrs object refers to the subelements' names (selectors) to provide SVG attributes to these constituent SVGElements.

Styling

@@ -64,9 +64,9 @@

Styling

Attributes

- Attributes defined in markup are evaluated once at CellView creation, - while attributes defined in attrs are evaluated on every model change. As JointJS special attributes usually depend on the - current state of the model, we should define them in attrs, along with any other SVG attributes that will be modified + Attributes defined in markup are evaluated once at CellView creation, + while attributes defined in attrs are evaluated on every model change. As JointJS special attributes usually depend on the + current state of the model, we should define them in attrs, along with any other SVG attributes that will be modified in the runtime of your application.

diff --git a/packages/joint-core/docs/src/joint/api/dia/Graph/prototype/addCell.html b/packages/joint-core/docs/src/joint/api/dia/Graph/prototype/addCell.html index 85b24ae98..ecb7ee2f4 100644 --- a/packages/joint-core/docs/src/joint/api/dia/Graph/prototype/addCell.html +++ b/packages/joint-core/docs/src/joint/api/dia/Graph/prototype/addCell.html @@ -2,7 +2,7 @@

Add a new cell to the graph. If cell is an array, all the cells in the array will be added to the graph. - Any additional option or custom property provided in the options object will be accessible in the callback function of + Any additional option or custom property provided in the options object will be accessible in the callback function of the graph add event.

@@ -18,12 +18,12 @@ If opt.sort is set to false, the cell will be added at the end of the collection.

-
const rect = new joint.shapes.standard.Rectangle({ 
+
const rect = new joint.shapes.standard.Rectangle({
     position: { x: 100, y: 100 },
     size: { width: 90, height: 30 },
     attrs: { label: { text: 'my rectangle' } }
 });
 const rect2 = rect.clone();
-const link = new joint.dia.Link({ source: { id: rect.id }, target: { id: rect2.id } });
+const link = new joint.shapes.standard.Link({ source: { id: rect.id }, target: { id: rect2.id } });
 const graph = new joint.dia.Graph({}, { cellNamespace: joint.shapes });
 graph.addCell(rect).addCell(rect2).addCell(link);
diff --git a/packages/joint-core/docs/src/joint/api/dia/Link/intro.html b/packages/joint-core/docs/src/joint/api/dia/Link/intro.html index 741f6fe84..9f2626758 100644 --- a/packages/joint-core/docs/src/joint/api/dia/Link/intro.html +++ b/packages/joint-core/docs/src/joint/api/dia/Link/intro.html @@ -6,12 +6,13 @@

Geometry

Links have two crucial properties: source and target. They define the starting point and the end point of the link. They can be defined with a Cell id (optionally, with additional subelement/magnet/port reference) or with a Point:

-
var link1 = new joint.dia.Link({
+
// `shapes.standard.Link` inherits from `dia.Link` (`dia.Link` is an abstract class that has no SVG  markup defined)
+var link1 = new joint.shapes.standard.Link({
     source: { id: sourceId },
     target: { id: targetId, port: portId }
 });
 
-var link2 = new joint.dia.Link({
+var link2 = new joint.shapes.standard.Link({
     source: { id: sourceId },
     target: { x: 100, y: 100 }
 });
@@ -22,7 +23,8 @@

Geometry

Presentation

-

Each joint.dia.Link defines its own SVG markup which is then used by joint.dia.LinkView to render the link to the paper. For instance, the joint.shapes.standard.Link (which inherits from joint.dia.Link) defines its markup using the JSON array notation as follows:

+

Each joint.dia.Link defines its own SVG markup which is then used by joint.dia.LinkView to render the link to the paper. + For instance, the joint.shapes.standard.Link (which inherits from joint.dia.Link) defines its markup using the JSON array notation as follows:

markup: [{
     tagName: 'path',
@@ -44,44 +46,6 @@ 

Presentation

As we can see, the joint.shapes.standard.Link shape consists of two subelements: one SVGPathElement named 'wrapper' and one SVGPathElement named 'line'. The attrs object refers to the subelements' names (selectors) to provide SVG attributes to these constituent SVGElements.

-

Direct use of joint.dia.Link is deprecated. Use links from the standard namespace.

- -

The joint.dia.Link markup looks like the following:

- -
<path class="connection"/>
-<path class="marker-source"/>
-<path class="marker-target"/>
-<path class="connection-wrap"/>
-<g class="labels" />
-<g class="marker-vertices"/>
-<g class="marker-arrowheads"/>
-<g class="link-tools" />
- -

As you can see, the link consists of a couple of SVG path elements and a couple of SVG group elements:

- -
    -
  • .connection is the actual line of the link.
  • -
  • .connection-wrap is an SVG path element that covers the .connection element and is usually thicker so that the link is able to handle pointer events (mousedown, mousemove, mouseup) that didn't target the thin .connection path exactly. This makes it easy to grab the link even though the mouse cursor didn't point exactly at the (usually thin) .connection path element.
  • -
  • .marker-source and .marker-target are the arrowheads of the link.
  • -
- -

The subelements of the link can be optionally styled through link attributes. For example:

- -
link.attr({
-    '.connection': { stroke: 'blue' },
-    '.marker-source': { fill: 'red', d: 'M 10 0 L 0 5 L 10 10 z' },
-    '.marker-target': { fill: 'yellow', d: 'M 10 0 L 0 5 L 10 10 z' }
-});
- -

The markup attribute is not the only presentation attribute that may be specified for a joint.dia.Link. However, the following four Link properties are deprecated and included only for backwards compatibility. Use link tools instead.

- -
    -
  • vertexMarkup - provide default vertex markup for all vertices created on an instance of this Link type (on hover).
  • -
  • toolMarkup - provide custom tool markup for all instances of this Link type (on hover).
  • -
  • doubleToolMarkup - provide custom markup for second set of tools for all instances of this Link type (on hover, if linkView.doubleLinkTools is true).
  • -
  • arrowheadMarkup - provide custom arrowhead markup for all instances of this Link type (on hover).
  • -
-

Styling

The keys of the attrs object are selectors that match subelements defined in the link's markup (see above). The values of this object are special JointJS attributes or native SVG attributes that should be set on the selected subelements. (A list of native SVG attributes and their descriptions can be found online, e.g. on MDN.)

@@ -212,18 +176,18 @@

Custom Properties

interactive: function(cellView) { if (cellView.model.get('customLinkInteractions')) { // only links with `customLinkInteractions: true` - return { vertexAdd: false }; + return true; } - return true; // otherwise + return { labelMove: false }; // otherwise } }); -var link1 = new joint.dia.Link({ +var link1 = new joint.shapes.standard.Link({ //... customLinkInteractions: true // right-click adds a label }); -var link2 = new joint.dia.Link({ +var link2 = new joint.shapes.standard.Link({ //... customLinkInteractions: false // or omit completely });
diff --git a/packages/joint-core/docs/src/joint/api/dia/Paper/events.html b/packages/joint-core/docs/src/joint/api/dia/Paper/events.html index f245e5092..3ab031104 100644 --- a/packages/joint-core/docs/src/joint/api/dia/Paper/events.html +++ b/packages/joint-core/docs/src/joint/api/dia/Paper/events.html @@ -505,14 +505,14 @@
// Create a new link by dragging
 paper.on({
   'blank:pointerdown': function(evt, x, y) {
-    var link = new joint.dia.Link();
-    link.set('source', { x: x, y: y });
-    link.set('target', { x: x, y: y });
+    const link = new joint.shapes.standard.Link();
+    link.set('source', { x, y });
+    link.set('target', { x, y });
     link.addTo(this.model);
-    evt.data = { link: link, x: x, y: y };
+    evt.data = { link, x, y };
   },
   'blank:pointermove': function(evt, x, y) {
-    evt.data.link.set('target', { x: x, y: y });
+    evt.data.link.set('target', { x, y });
   },
   'blank:pointerup': function(evt) {
     var target = evt.data.link.get('target');
diff --git a/packages/joint-core/docs/src/joint/api/dia/Paper/prototype/options/defaultLink.html b/packages/joint-core/docs/src/joint/api/dia/Paper/prototype/options/defaultLink.html
index e87344655..305bb4313 100644
--- a/packages/joint-core/docs/src/joint/api/dia/Paper/prototype/options/defaultLink.html
+++ b/packages/joint-core/docs/src/joint/api/dia/Paper/prototype/options/defaultLink.html
@@ -1 +1 @@
-defaultLink - link that should be created when the user clicks and drags an active magnet (when creating a link from a port via the UI). Defaults to new joint.dia.Link. It can also be a function with signature function(cellView, magnet) {} that must return an object of type joint.dia.Link.
+defaultLink - link that should be created when the user clicks and drags an active magnet (when creating a link from a port via the UI). Defaults to new joint.shapes.standard.Link(). It can also be a function with signature function(cellView, magnet) {} that must return an object of type joint.dia.Link.
diff --git a/packages/joint-core/docs/src/joint/api/dia/Paper/prototype/options/interactive.html b/packages/joint-core/docs/src/joint/api/dia/Paper/prototype/options/interactive.html
index 5b1ff3305..289330839 100644
--- a/packages/joint-core/docs/src/joint/api/dia/Paper/prototype/options/interactive.html
+++ b/packages/joint-core/docs/src/joint/api/dia/Paper/prototype/options/interactive.html
@@ -18,10 +18,10 @@
 
 

Using an object, specific interactions may be disabled by assigning false to their corresponding property name. It is not necessary to pass true values; all omitted properties are assigned true by default. (Note that the passed object is not merged with the default; unless labelMove is explicitly excluded, it becomes enabled.) A full list of recognized interaction keys is provided below.

-
// disable arrowheadMove
+
// disable labelMove
 var paper = new joint.dia.Paper({
     // ...
-    interactive: { arrowheadMove: false }
+    interactive: { labelMove: false }
 });
 
 // disable all element interactions
@@ -40,11 +40,6 @@
             return {
                 linkMove: false,
                 labelMove: false,
-                arrowheadMove: false,
-                vertexMove: false,
-                vertexAdd: false,
-                vertexRemove: false,
-                useLinkTools: false,
             };
         }
 
@@ -78,46 +73,6 @@
             
         
     
-    
-        arrowheadMove
-        
-            

Deprecated. Use a link tool instead.

-

(Is the user allowed to move the arrowheads?)

- - - - - vertexMove - -

Deprecated. Use a link tool instead.

-

(Is the user allowed to move the vertices?)

- - - - - vertexAdd - -

Deprecated. Use a link tool instead.

-

(Is the user allowed to add vertices by clicking along the link path?)

- - - - - vertexRemove - -

Deprecated. Use a link tool instead.

-

(Is the user allowed to remove vertices?)

- - - - - useLinkTools - -

Deprecated.

-

(Is the user allowed to use the default link buttons?)

- - -

Elements:

diff --git a/packages/joint-core/docs/src/joint/api/layout/DirectedGraph.html b/packages/joint-core/docs/src/joint/api/layout/DirectedGraph.html index e6b4fe677..5b83fca07 100644 --- a/packages/joint-core/docs/src/joint/api/layout/DirectedGraph.html +++ b/packages/joint-core/docs/src/joint/api/layout/DirectedGraph.html @@ -182,7 +182,7 @@

API

}); }, importEdge: function(edge) { - return new joint.dia.Link({ + return new joint.shapes.standard.Link({ source: { id: edge.v }, target: { id: edge.w } }); diff --git a/packages/joint-core/docs/src/joint/api/linkTools/Vertices.html b/packages/joint-core/docs/src/joint/api/linkTools/Vertices.html index f343b8e4b..67d7da001 100644 --- a/packages/joint-core/docs/src/joint/api/linkTools/Vertices.html +++ b/packages/joint-core/docs/src/joint/api/linkTools/Vertices.html @@ -16,6 +16,16 @@ boolean Can the user add new vertices (by clicking a segment of the link)? Default is true. + + vertexMoving + boolean + Can the user move vertices (by dragging them)? Default is true. + + + vertexRemoving + boolean + Can the user remove vertices (by double clicking them)? Default is true. + stopPropagation boolean diff --git a/packages/joint-core/src/dia/CellView.mjs b/packages/joint-core/src/dia/CellView.mjs index 02d440fa1..f82741c69 100644 --- a/packages/joint-core/src/dia/CellView.mjs +++ b/packages/joint-core/src/dia/CellView.mjs @@ -189,7 +189,7 @@ export const CellView = View.extend({ }, // Return `true` if cell link is allowed to perform a certain UI `feature`. - // Example: `can('vertexMove')`, `can('labelMove')`. + // Example: `can('labelMove')`. can: function(feature) { var interactive = isFunction(this.options.interactive) diff --git a/packages/joint-core/src/dia/Link.mjs b/packages/joint-core/src/dia/Link.mjs index 74d61721f..7968256e3 100644 --- a/packages/joint-core/src/dia/Link.mjs +++ b/packages/joint-core/src/dia/Link.mjs @@ -7,54 +7,6 @@ import { Point, Polyline } from '../g/index.mjs'; export const Link = Cell.extend({ - // The default markup for links. - markup: [ - '', - '', - '', - '', - '', - '', - '', - '' - ].join(''), - - toolMarkup: [ - '', - '', - '', - '', - 'Remove link.', - '', - '', - '', - '', - 'Link options.', - '', - '' - ].join(''), - - doubleToolMarkup: undefined, - - // The default markup for showing/removing vertices. These elements are the children of the .marker-vertices element (see `this.markup`). - // Only .marker-vertex and .marker-vertex-remove element have special meaning. The former is used for - // dragging vertices (changing their position). The latter is used for removing vertices. - vertexMarkup: [ - '', - '', - '', - '', - 'Remove vertex.', - '', - '' - ].join(''), - - arrowheadMarkup: [ - '', - '', - '' - ].join(''), - // may be overwritten by user to change default label (its markup, size, attrs, position) defaultLabel: undefined, diff --git a/packages/joint-core/src/dia/LinkView.mjs b/packages/joint-core/src/dia/LinkView.mjs index ecdbc5437..549e07f5f 100644 --- a/packages/joint-core/src/dia/LinkView.mjs +++ b/packages/joint-core/src/dia/LinkView.mjs @@ -1,20 +1,17 @@ import { CellView } from './CellView.mjs'; import { Link } from './Link.mjs'; import V from '../V/index.mjs'; -import { addClassNamePrefix, removeClassNamePrefix, merge, template, assign, toArray, isObject, isFunction, clone, isPercentage, result, isEqual, camelCase } from '../util/index.mjs'; +import { addClassNamePrefix, merge, assign, isObject, isFunction, clone, isPercentage, result, isEqual } from '../util/index.mjs'; import { Point, Line, Path, normalizeAngle, Rect, Polyline } from '../g/index.mjs'; import * as routers from '../routers/index.mjs'; import * as connectors from '../connectors/index.mjs'; -import $ from '../mvc/Dom/index.mjs'; const Flags = { TOOLS: CellView.Flags.TOOLS, RENDER: 'RENDER', UPDATE: 'UPDATE', - LEGACY_TOOLS: 'LEGACY_TOOLS', LABELS: 'LABELS', - VERTICES: 'VERTICES', SOURCE: 'SOURCE', TARGET: 'TARGET', CONNECTOR: 'CONNECTOR' @@ -46,7 +43,6 @@ export const LinkView = CellView.extend({ _labelCache: null, _labelSelectors: null, - _markerCache: null, _V: null, _dragData: null, // deprecated @@ -65,9 +61,6 @@ export const LinkView = CellView.extend({ // a cache of label selectors this._labelSelectors = {}; - // keeps markers bboxes and positions again for quicker access - this._markerCache = {}; - // cache of default markup nodes this._V = {}; @@ -80,11 +73,9 @@ export const LinkView = CellView.extend({ attrs: [Flags.UPDATE], router: [Flags.UPDATE], connector: [Flags.CONNECTOR], - toolMarkup: [Flags.LEGACY_TOOLS], labels: [Flags.LABELS], labelMarkup: [Flags.LABELS], - vertices: [Flags.VERTICES, Flags.UPDATE], - vertexMarkup: [Flags.VERTICES], + vertices: [Flags.UPDATE], source: [Flags.SOURCE, Flags.UPDATE], target: [Flags.TARGET, Flags.UPDATE] }, @@ -117,21 +108,15 @@ export const LinkView = CellView.extend({ this.render(); this.updateHighlighters(true); this.updateTools(opt); - flags = this.removeFlag(flags, [Flags.RENDER, Flags.UPDATE, Flags.VERTICES, Flags.LABELS, Flags.TOOLS, Flags.LEGACY_TOOLS, Flags.CONNECTOR]); + flags = this.removeFlag(flags, [Flags.RENDER, Flags.UPDATE, Flags.LABELS, Flags.TOOLS, Flags.CONNECTOR]); return flags; } let updateHighlighters = false; - if (this.hasFlag(flags, Flags.VERTICES)) { - this.renderVertexMarkers(); - flags = this.removeFlag(flags, Flags.VERTICES); - } - const { model } = this; const { attributes } = model; let updateLabels = this.hasFlag(flags, Flags.LABELS); - let updateLegacyTools = this.hasFlag(flags, Flags.LEGACY_TOOLS); if (updateLabels) { this.onLabelsChange(model, attributes.labels, opt); @@ -139,11 +124,6 @@ export const LinkView = CellView.extend({ updateHighlighters = true; } - if (updateLegacyTools) { - this.renderTools(); - flags = this.removeFlag(flags, Flags.LEGACY_TOOLS); - } - const updateAll = this.hasFlag(flags, Flags.UPDATE); const updateConnector = this.hasFlag(flags, Flags.CONNECTOR); if (updateAll || updateConnector) { @@ -162,7 +142,6 @@ export const LinkView = CellView.extend({ this.updateTools(opt); flags = this.removeFlag(flags, [Flags.UPDATE, Flags.TOOLS, Flags.CONNECTOR]); updateLabels = false; - updateLegacyTools = false; updateHighlighters = true; } @@ -170,10 +149,6 @@ export const LinkView = CellView.extend({ this.updateLabelPositions(); } - if (updateLegacyTools) { - this.updateToolsPosition(); - } - if (updateHighlighters) { this.updateHighlighters(); } @@ -276,21 +251,7 @@ export const LinkView = CellView.extend({ var children = V(markup); // custom markup may contain only one children if (!Array.isArray(children)) children = [children]; - // Cache all children elements for quicker access. - var cache = this._V; // vectorized markup; - for (var i = 0, n = children.length; i < n; i++) { - var child = children[i]; - var className = child.attr('class'); - if (className) { - // Strip the joint class name prefix, if there is one. - className = removeClassNamePrefix(className); - cache[camelCase(className)] = child; - } - } - // partial rendering - this.renderTools(); - this.renderVertexMarkers(); - this.renderArrowheadMarkers(); + this.vel.append(children); }, @@ -537,83 +498,6 @@ export const LinkView = CellView.extend({ return this; }, - renderTools: function() { - - if (!this._V.linkTools) return this; - - // Tools are a group of clickable elements that manipulate the whole link. - // A good example of this is the remove tool that removes the whole link. - // Tools appear after hovering the link close to the `source` element/point of the link - // but are offset a bit so that they don't cover the `marker-arrowhead`. - - var $tools = $(this._V.linkTools.node).empty(); - var toolTemplate = template(this.model.get('toolMarkup') || this.model.toolMarkup); - var tool = V(toolTemplate()); - - $tools.append(tool.node); - - // Cache the tool node so that the `updateToolsPosition()` can update the tool position quickly. - this._toolCache = tool; - - // If `doubleLinkTools` is enabled, we render copy of the tools on the other side of the - // link as well but only if the link is longer than `longLinkLength`. - if (this.options.doubleLinkTools) { - - var tool2; - if (this.model.get('doubleToolMarkup') || this.model.doubleToolMarkup) { - toolTemplate = template(this.model.get('doubleToolMarkup') || this.model.doubleToolMarkup); - tool2 = V(toolTemplate()); - } else { - tool2 = tool.clone(); - } - - $tools.append(tool2.node); - this._tool2Cache = tool2; - } - - return this; - }, - - renderVertexMarkers: function() { - - if (!this._V.markerVertices) return this; - - var $markerVertices = $(this._V.markerVertices.node).empty(); - - // A special markup can be given in the `properties.vertexMarkup` property. This might be handy - // if default styling (elements) are not desired. This makes it possible to use any - // SVG elements for .marker-vertex and .marker-vertex-remove tools. - var markupTemplate = template(this.model.get('vertexMarkup') || this.model.vertexMarkup); - - this.model.vertices().forEach(function(vertex, idx) { - $markerVertices.append(V(markupTemplate(assign({ idx: idx }, vertex))).node); - }); - - return this; - }, - - renderArrowheadMarkers: function() { - - // Custom markups might not have arrowhead markers. Therefore, jump of this function immediately if that's the case. - if (!this._V.markerArrowheads) return this; - - var $markerArrowheads = $(this._V.markerArrowheads.node); - - $markerArrowheads.empty(); - - // A special markup can be given in the `properties.vertexMarkup` property. This might be handy - // if default styling (elements) are not desired. This makes it possible to use any - // SVG elements for .marker-vertex and .marker-vertex-remove tools. - var markupTemplate = template(this.model.get('arrowheadMarkup') || this.model.arrowheadMarkup); - - this._V.sourceArrowhead = V(markupTemplate({ end: 'source' })); - this._V.targetArrowhead = V(markupTemplate({ end: 'target' })); - - $markerArrowheads.append(this._V.sourceArrowhead.node, this._V.targetArrowhead.node); - - return this; - }, - // remove vertices that lie on (or nearly on) straight lines within the link // return the number of removed points removeRedundantLinearVertices: function(opt) { @@ -640,23 +524,6 @@ export const LinkView = CellView.extend({ return (numRoutePoints - numPolylinePoints); }, - updateDefaultConnectionPath: function() { - - var cache = this._V; - - if (cache.connection) { - cache.connection.attr('d', this.getSerializedConnection()); - } - - if (cache.connectionWrap) { - cache.connectionWrap.attr('d', this.getSerializedConnection()); - } - - if (cache.markerSource && cache.markerTarget) { - this._translateAndAutoOrientArrows(cache.markerSource, cache.markerTarget); - } - }, - getEndView: function(type) { switch (type) { case 'source': @@ -724,8 +591,11 @@ export const LinkView = CellView.extend({ const polyline = new Polyline(route); polyline.translate(tx, ty); this.route = polyline.points; - // translate source and target connection and marker points. - this._translateConnectionPoints(tx, ty); + // translate source and target connection and anchor points. + this.sourcePoint.offset(tx, ty); + this.targetPoint.offset(tx, ty); + this.sourceAnchor.offset(tx, ty); + this.targetAnchor.offset(tx, ty); // translate the geometry path path.translate(tx, ty); this.updateDOM(); @@ -736,12 +606,8 @@ export const LinkView = CellView.extend({ this.cleanNodesCache(); // update SVG attributes defined by 'attrs/'. this.updateDOMSubtreeAttributes(el, model.attr(), { selectors }); - // legacy link path update - this.updateDefaultConnectionPath(); // update the label position etc. this.updateLabelPositions(); - this.updateToolsPosition(); - this.updateArrowheadMarkers(); // *Deprecated* // Local perpendicular flag (as opposed to one defined on paper). // Could be enabled inside a connector/router. It's valid only @@ -767,55 +633,11 @@ export const LinkView = CellView.extend({ updatePath: function() { const { route, sourcePoint, targetPoint } = this; - // 3b. Find Marker Connection Point - Backwards Compatibility - const markerPoints = this.findMarkerPoints(route, sourcePoint, targetPoint); // 4. Find Connection - const path = this.findPath(route, markerPoints.source || sourcePoint, markerPoints.target || targetPoint); + const path = this.findPath(route, sourcePoint.clone(), targetPoint.clone()); this.path = path; }, - findMarkerPoints: function(route, sourcePoint, targetPoint) { - - var firstWaypoint = route[0]; - var lastWaypoint = route[route.length - 1]; - - // Move the source point by the width of the marker taking into account - // its scale around x-axis. Note that scale is the only transform that - // makes sense to be set in `.marker-source` attributes object - // as all other transforms (translate/rotate) will be replaced - // by the `translateAndAutoOrient()` function. - var cache = this._markerCache; - // cache source and target points - var sourceMarkerPoint, targetMarkerPoint; - - if (this._V.markerSource) { - - cache.sourceBBox = cache.sourceBBox || this._V.markerSource.getBBox(); - sourceMarkerPoint = Point(sourcePoint).move( - firstWaypoint || targetPoint, - cache.sourceBBox.width * this._V.markerSource.scale().sx * -1 - ).round(); - } - - if (this._V.markerTarget) { - - cache.targetBBox = cache.targetBBox || this._V.markerTarget.getBBox(); - targetMarkerPoint = Point(targetPoint).move( - lastWaypoint || sourcePoint, - cache.targetBBox.width * this._V.markerTarget.scale().sx * -1 - ).round(); - } - - // if there was no markup for the marker, use the connection point. - cache.sourcePoint = sourceMarkerPoint || sourcePoint.clone(); - cache.targetPoint = targetMarkerPoint || targetPoint.clone(); - - return { - source: sourceMarkerPoint, - target: targetMarkerPoint - }; - }, - findAnchorsOrdered: function(firstEndType, firstRef, secondEndType, secondRef) { var firstAnchor, secondAnchor; @@ -986,18 +808,6 @@ export const LinkView = CellView.extend({ return connectionPoint.round(this.decimalsRounding); }, - _translateConnectionPoints: function(tx, ty) { - - var cache = this._markerCache; - - cache.sourcePoint.offset(tx, ty); - cache.targetPoint.offset(tx, ty); - this.sourcePoint.offset(tx, ty); - this.targetPoint.offset(tx, ty); - this.sourceAnchor.offset(tx, ty); - this.targetAnchor.offset(tx, ty); - }, - // combine default label position with built-in default label position _getDefaultLabelPositionProperty: function() { @@ -1077,63 +887,6 @@ export const LinkView = CellView.extend({ } }, - updateToolsPosition: function() { - - if (!this._V.linkTools) return this; - - // Move the tools a bit to the target position but don't cover the `sourceArrowhead` marker. - // Note that the offset is hardcoded here. The offset should be always - // more than the `this.$('.marker-arrowhead[end="source"]')[0].bbox().width` but looking - // this up all the time would be slow. - - var scale = ''; - var offset = this.options.linkToolsOffset; - var connectionLength = this.getConnectionLength(); - - // Firefox returns connectionLength=NaN in odd cases (for bezier curves). - // In that case we won't update tools position at all. - if (!Number.isNaN(connectionLength)) { - - // If the link is too short, make the tools half the size and the offset twice as low. - if (connectionLength < this.options.shortLinkLength) { - scale = 'scale(.5)'; - offset /= 2; - } - - var toolPosition = this.getPointAtLength(offset); - - this._toolCache.attr('transform', 'translate(' + toolPosition.x + ', ' + toolPosition.y + ') ' + scale); - - if (this.options.doubleLinkTools && connectionLength >= this.options.longLinkLength) { - - var doubleLinkToolsOffset = this.options.doubleLinkToolsOffset || offset; - - toolPosition = this.getPointAtLength(connectionLength - doubleLinkToolsOffset); - this._tool2Cache.attr('transform', 'translate(' + toolPosition.x + ', ' + toolPosition.y + ') ' + scale); - this._tool2Cache.attr('display', 'inline'); - - } else if (this.options.doubleLinkTools) { - - this._tool2Cache.attr('display', 'none'); - } - } - - return this; - }, - - updateArrowheadMarkers: function() { - - if (!this._V.markerArrowheads) return this; - - var sx = this.getConnectionLength() < this.options.shortLinkLength ? .5 : 1; - this._V.sourceArrowhead.scale(sx); - this._V.targetArrowhead.scale(sx); - - this._translateAndAutoOrientArrows(this._V.sourceArrowhead, this._V.targetArrowhead); - - return this; - }, - updateEndProperties: function(endType) { const { model, paper } = this; @@ -1175,28 +928,6 @@ export const LinkView = CellView.extend({ } }, - _translateAndAutoOrientArrows: function(sourceArrow, targetArrow) { - - // Make the markers "point" to their sticky points being auto-oriented towards - // `targetPosition`/`sourcePosition`. And do so only if there is a markup for them. - var route = toArray(this.route); - if (sourceArrow) { - sourceArrow.translateAndAutoOrient( - this.sourcePoint, - route[0] || this.targetPoint, - this.paper.cells - ); - } - - if (targetArrow) { - targetArrow.translateAndAutoOrient( - this.targetPoint, - route[route.length - 1] || this.sourcePoint, - this.paper.cells - ); - } - }, - _getLabelPositionProperty: function(idx) { return (this.model.label(idx).position || {}); @@ -1311,7 +1042,7 @@ export const LinkView = CellView.extend({ // Send a token (an SVG element, usually a circle) along the connection path. // Example: `link.findView(paper).sendToken(V('circle', { r: 7, fill: 'green' }).node)` // `opt.duration` is optional and is a time in milliseconds that the token travels from the source to the target of the link. Default is `1000`. - // `opt.directon` is optional and it determines whether the token goes from source to target or other way round (`reverse`) + // `opt.direction` is optional and it determines whether the token goes from source to target or other way round (`reverse`) // `opt.connection` is an optional selector to the connection path. // `callback` is optional and is a function to be called once the token reaches the target. sendToken: function(token, opt, callback) { @@ -1740,34 +1471,6 @@ export const LinkView = CellView.extend({ pointerdown: function(evt, x, y) { this.notifyPointerdown(evt, x, y); - - // Backwards compatibility for the default markup - var className = evt.target.getAttribute('class'); - switch (className) { - - case 'marker-vertex': - this.dragVertexStart(evt, x, y); - return; - - case 'marker-vertex-remove': - case 'marker-vertex-remove-area': - this.dragVertexRemoveStart(evt, x, y); - return; - - case 'marker-arrowhead': - this.dragArrowheadStart(evt, x, y); - return; - - case 'connection': - case 'connection-wrap': - this.dragConnectionStart(evt, x, y); - return; - - case 'marker-source': - case 'marker-target': - return; - } - this.dragStart(evt, x, y); }, @@ -1780,10 +1483,6 @@ export const LinkView = CellView.extend({ var data = this.eventData(evt); switch (data.action) { - case 'vertex-move': - this.dragVertex(evt, x, y); - break; - case 'label-move': this.dragLabel(evt, x, y); break; @@ -1815,10 +1514,6 @@ export const LinkView = CellView.extend({ var data = this.eventData(evt); switch (data.action) { - case 'vertex-move': - this.dragVertexEnd(evt, x, y); - break; - case 'label-move': this.dragLabelEnd(evt, x, y); break; @@ -1865,36 +1560,6 @@ export const LinkView = CellView.extend({ this.notify('link:mousewheel', evt, x, y, delta); }, - onevent: function(evt, eventName, x, y) { - - // Backwards compatibility - var linkTool = V(evt.target).findParentByClass('link-tool', this.el); - if (linkTool) { - // No further action to be executed - evt.stopPropagation(); - - // Allow `interactive.useLinkTools=false` - if (this.can('useLinkTools')) { - if (eventName === 'remove') { - // Built-in remove event - this.model.remove({ ui: true }); - // Do not trigger link pointerdown - return; - - } else { - // link:options and other custom events inside the link tools - this.notify(eventName, evt, x, y); - } - } - - this.notifyPointerdown(evt, x, y); - this.paper.delegateDragEvents(this, evt.data); - - } else { - CellView.prototype.onevent.apply(this, arguments); - } - }, - onlabel: function(evt, x, y) { this.notifyPointerdown(evt, x, y); @@ -1907,19 +1572,6 @@ export const LinkView = CellView.extend({ // Drag Start Handlers - dragConnectionStart: function(evt, x, y) { - - if (!this.can('vertexAdd')) return; - - // Store the index at which the new vertex has just been placed. - // We'll be update the very same vertex position in `pointermove()`. - var vertexIdx = this.addVertex({ x: x, y: y }, { ui: true }); - this.eventData(evt, { - action: 'vertex-move', - vertexIdx: vertexIdx - }); - }, - dragLabelStart: function(evt, x, y) { if (this.can('labelMove')) { @@ -1962,27 +1614,6 @@ export const LinkView = CellView.extend({ this.paper.delegateDragEvents(this, evt.data); }, - dragVertexStart: function(evt, x, y) { - - if (!this.can('vertexMove')) return; - - var vertexNode = evt.target; - var vertexIdx = parseInt(vertexNode.getAttribute('idx'), 10); - this.eventData(evt, { - action: 'vertex-move', - vertexIdx: vertexIdx - }); - }, - - dragVertexRemoveStart: function(evt, x, y) { - - if (!this.can('vertexRemove')) return; - - var removeNode = evt.target; - var vertexIdx = parseInt(removeNode.getAttribute('idx'), 10); - this.model.removeVertex(vertexIdx); - }, - dragArrowheadStart: function(evt, x, y) { if (!this.can('arrowheadMove')) return; @@ -2026,12 +1657,6 @@ export const LinkView = CellView.extend({ this.model.label(data.labelIdx, label, setOptions); }, - dragVertex: function(evt, x, y) { - - var data = this.eventData(evt); - this.model.vertex(data.vertexIdx, { x: x, y: y }, { ui: true }); - }, - dragArrowhead: function(evt, x, y) { if (this.paper.options.snapLinks) { const isSnapped = this._snapArrowhead(evt, x, y); @@ -2063,10 +1688,6 @@ export const LinkView = CellView.extend({ // noop }, - dragVertexEnd: function() { - // noop - }, - dragArrowheadEnd: function(evt, x, y) { var data = this.eventData(evt); diff --git a/packages/joint-core/src/dia/Paper.mjs b/packages/joint-core/src/dia/Paper.mjs index bbad4c34c..bbf9ad33c 100644 --- a/packages/joint-core/src/dia/Paper.mjs +++ b/packages/joint-core/src/dia/Paper.mjs @@ -37,7 +37,6 @@ import { View, views } from '../mvc/index.mjs'; import { CellView } from './CellView.mjs'; import { ElementView } from './ElementView.mjs'; import { LinkView } from './LinkView.mjs'; -import { Link } from './Link.mjs'; import { Cell } from './Cell.mjs'; import { Graph } from './Graph.mjs'; import { LayersNames, PaperLayer } from './PaperLayer.mjs'; @@ -168,8 +167,16 @@ export const Paper = View.extend({ // Defines what link model is added to the graph after an user clicks on an active magnet. // Value could be the mvc.model or a function returning the mvc.model - // defaultLink: function(elementView, magnet) { return condition ? new customLink1() : new customLink2() } - defaultLink: new Link, + // defaultLink: (elementView, magnet) => { + // return condition ? new customLink1() : new customLink2() + // } + defaultLink: function() { + // Do not create hard dependency on the joint.shapes.standard namespace (by importing the standard.Link model directly) + const { cellNamespace } = this.model.get('cells'); + const ctor = getByPath(cellNamespace, ['standard', 'Link']); + if (!ctor) throw new Error('dia.Paper: no default link model found. Use `options.defaultLink` to specify a default link model.'); + return new ctor(); + }, // A connector that is used by links with no connector defined on the model. // e.g. { name: 'rounded', args: { radius: 5 }} or a function diff --git a/packages/joint-core/src/linkTools/Arrowhead.mjs b/packages/joint-core/src/linkTools/Arrowhead.mjs index 9b4a9c6a9..7ce59e12b 100644 --- a/packages/joint-core/src/linkTools/Arrowhead.mjs +++ b/packages/joint-core/src/linkTools/Arrowhead.mjs @@ -49,11 +49,9 @@ const Arrowhead = ToolView.extend({ evt.preventDefault(); var relatedView = this.relatedView; relatedView.model.startBatch('arrowhead-move', { ui: true, tool: this.cid }); - if (relatedView.can('arrowheadMove')) { - relatedView.startArrowheadMove(this.arrowheadType); - this.delegateDocumentEvents(); - relatedView.paper.undelegateEvents(); - } + relatedView.startArrowheadMove(this.arrowheadType); + this.delegateDocumentEvents(); + relatedView.paper.undelegateEvents(); this.focus(); this.el.style.pointerEvents = 'none'; }, diff --git a/packages/joint-core/src/linkTools/Vertices.mjs b/packages/joint-core/src/linkTools/Vertices.mjs index 03ce84147..8a98a6345 100644 --- a/packages/joint-core/src/linkTools/Vertices.mjs +++ b/packages/joint-core/src/linkTools/Vertices.mjs @@ -150,13 +150,13 @@ export const Vertices = ToolView.extend({ if (connection) connection.setAttribute('d', this.relatedView.getSerializedConnection()); }, startHandleListening: function(handle) { - var relatedView = this.relatedView; - if (relatedView.can('vertexMove')) { + const { vertexRemoving = true, vertexMoving = true } = this.options; + if (vertexMoving) { this.listenTo(handle, 'will-change', this.onHandleWillChange); this.listenTo(handle, 'changing', this.onHandleChanging); this.listenTo(handle, 'changed', this.onHandleChanged); } - if (relatedView.can('vertexRemove')) { + if (vertexRemoving) { this.listenTo(handle, 'remove', this.onHandleRemove); } }, diff --git a/packages/joint-core/test/jointjs/basic.js b/packages/joint-core/test/jointjs/basic.js index adbc63702..95e7827a3 100644 --- a/packages/joint-core/test/jointjs/basic.js +++ b/packages/joint-core/test/jointjs/basic.js @@ -31,7 +31,7 @@ QUnit.module('basic', function(hooks) { function ml(id, a, b) { var source = a.x ? a : { id: a.id }; var target = b.x ? b : { id: b.id }; - return new joint.dia.Link({ id: id, source: source, target: target, name: id }).addTo(graph); + return new joint.shapes.standard.Link({ id: id, source: source, target: target, name: id }).addTo(graph); } var a = me('a'); @@ -604,11 +604,11 @@ QUnit.module('basic', function(hooks) { assert.equal(elView.$('.big').attr('stroke'), undefined, 'The stroke was correctly unset from the element by removeAttr()'); - var link = new joint.dia.Link({ + var link = new joint.shapes.standard.Link({ source: { x: 100, y: 100 }, target: { x: 200, y: 200 }, attrs: { - '.connection': { width: 100, height: 50, fill: 'gray', 'stroke-width': 2 } + line: { fill: 'gray', strokeWidth: 2 } } }); @@ -616,11 +616,11 @@ QUnit.module('basic', function(hooks) { var linkView = this.paper.findViewByModel(link); - assert.equal(linkView.$('.connection').attr('stroke-width'), '2', 'A stroke is set on the link'); + assert.equal(linkView.findNode('line').getAttribute('stroke-width'), '2', 'A stroke is set on the link'); - link.removeAttr('.connection/stroke-width'); + link.removeAttr('line/strokeWidth'); - assert.equal(linkView.$('.connection').attr('stroke-width'), undefined, 'The stroke was correctly unset from the link by removeAttr()'); + assert.equal(linkView.findNode('line').getAttribute('stroke-width'), undefined, 'The stroke was correctly unset from the link by removeAttr()'); }); @@ -1209,7 +1209,7 @@ QUnit.module('basic', function(hooks) { // Deep clone. - var l = new joint.dia.Link({ source: { id: r1.id }, target: { id: r2.id }}); + var l = new joint.shapes.standard.Link({ source: { id: r1.id }, target: { id: r2.id }}); this.graph.addCell(l); var clones = r1.clone({ deep: true }); @@ -2107,9 +2107,9 @@ QUnit.module('basic', function(hooks) { QUnit.test('cell.isEmbedded()', function(assert) { var rect = new joint.shapes.standard.Rectangle; - var link = new joint.dia.Link; + var link = new joint.shapes.standard.Link; var embeddedRect = new joint.shapes.standard.Rectangle; - var embeddedLink = new joint.dia.Link; + var embeddedLink = new joint.shapes.standard.Link; rect.embed(embeddedRect); rect.embed(embeddedLink); @@ -2129,7 +2129,7 @@ QUnit.module('basic', function(hooks) { }; hooks.beforeEach(function() { - this.link = new joint.dia.Link({ + this.link = new joint.shapes.standard.Link({ source: { x: 100, y: 100 }, target: { x: 200, y: 200 }, vertices: [] @@ -2192,7 +2192,7 @@ QUnit.module('basic', function(hooks) { QUnit.module('Link.scale()', function(hooks) { hooks.beforeEach(function() { - this.link = new joint.dia.Link({ + this.link = new joint.shapes.standard.Link({ source: { x: 100, y: 100 }, target: { x: 200, y: 200 }, vertices: [{ x: 100, y: 200 }] diff --git a/packages/joint-core/test/jointjs/connectors.js b/packages/joint-core/test/jointjs/connectors.js index e211221a6..539e8b1ce 100644 --- a/packages/joint-core/test/jointjs/connectors.js +++ b/packages/joint-core/test/jointjs/connectors.js @@ -28,7 +28,7 @@ QUnit.module('connectors', function(hooks) { this.graph.addCell([r1, r2]); - var l0 = new joint.dia.Link({ + var l0 = new joint.shapes.standard.Link({ source: { id: r1.id }, target: { id: r2.id }, vertices: [{ x: 150, y: 200 }], @@ -42,17 +42,17 @@ QUnit.module('connectors', function(hooks) { l0.set('connector', { name: 'normal' }); this.graph.addCell(l0); assert.equal(this.graph.getLinks().length, 1, 'A link with the normal connector was successfully added to the graph'); - assert.checkDataPath(this.paper.findViewByModel(l0).getConnection().round(2).serialize(), 'M 102 110 L 150 200 L 320 104', 'A link with the normal connector was correctly rendered'); + assert.checkDataPath(this.paper.findViewByModel(l0).getConnection().round().serialize(), 'M 102 110 L 150 200 L 320 104', 'A link with the normal connector was correctly rendered'); var l1 = l0.clone().set('connector', { name: 'rounded' }); this.graph.addCell(l1); assert.equal(this.graph.getLinks().length, 2, 'A link with the rounded connector was successfully added to the graph'); - assert.checkDataPath(this.paper.findViewByModel(l1).getConnection().round(2).serialize(), 'M 102 110 L 145 191 C 148.33 197 153 198.33 159 195 L 320 104', 'A link with the rounded connector was correctly rendered'); + assert.checkDataPath(this.paper.findViewByModel(l1).getConnection().round().serialize(), 'M 102 110 L 145 191 C 148 197 153 198 159 195 L 320 104', 'A link with the rounded connector was correctly rendered'); var l2 = l0.clone().set('connector', { name: 'smooth' }); this.graph.addCell(l2); assert.equal(this.graph.getLinks().length, 3, 'A link with the smooth connector was successfully added to the graph'); - assert.checkDataPath(this.paper.findViewByModel(l2).getConnection().round(2).serialize(), 'M 102 110 C 107.83 155.5 113.67 201 150 200 C 186.33 199 253.17 151.5 320 104', 'A link with the smooth connector was correctly rendered'); + assert.checkDataPath(this.paper.findViewByModel(l2).getConnection().round().serialize(), 'M 102 110 C 108 156 114 201 150 200 C 186 199 253 151 320 104', 'A link with the smooth connector was correctly rendered'); var l3 = l0.clone().set('connector', { name: 'straight', args: { cornerType: 'non-existing' }}); assert.throws(function() { @@ -62,30 +62,30 @@ QUnit.module('connectors', function(hooks) { l3.set('connector', { name: 'straight' }); this.graph.addCell(l3); assert.equal(this.graph.getLinks().length, 4, 'A link with the (default) straight connector was successfully added to the graph'); - assert.checkDataPath(this.paper.findViewByModel(l3).getConnection().round(2).serialize(), 'M 102 110 L 150 200 L 320 104', 'A link with the (default) straight connector was correctly rendered'); + assert.checkDataPath(this.paper.findViewByModel(l3).getConnection().round().serialize(), 'M 102 110 L 150 200 L 320 104', 'A link with the (default) straight connector was correctly rendered'); assert.checkDataPath(this.paper.findViewByModel(l3).getConnection().round(2).serialize(), this.paper.findViewByModel(l0).getConnection().round(2).serialize(), 'A link with the (default) straight connector was rendered same as if it had the normal connector'); var l4 = l0.clone().set('connector', { name: 'straight', args: { cornerType: 'point' }}); this.graph.addCell(l4); assert.equal(this.graph.getLinks().length, 5, 'A link with the (point) straight connector was successfully added to the graph'); - assert.checkDataPath(this.paper.findViewByModel(l4).getConnection().round(2).serialize(), 'M 102 110 L 150 200 L 320 104', 'A link with the (point) straight connector was correctly rendered'); + assert.checkDataPath(this.paper.findViewByModel(l4).getConnection().round().serialize(), 'M 102 110 L 150 200 L 320 104', 'A link with the (point) straight connector was correctly rendered'); assert.checkDataPath(this.paper.findViewByModel(l4).getConnection().round(2).serialize(), this.paper.findViewByModel(l0).getConnection().round(2).serialize(), 'A link with the (point) straight connector was rendered same as if it had the normal connector'); var l5 = l0.clone().set('connector', { name: 'straight', args: { cornerType: 'cubic', precision: 0 }}); this.graph.addCell(l5); assert.equal(this.graph.getLinks().length, 6, 'A link with the (cubic) straight connector was successfully added to the graph'); - assert.checkDataPath(this.paper.findViewByModel(l5).getConnection().round(2).serialize(), 'M 102 110 L 145 191 C 148.33 197 153 198.33 159 195 L 320 104', 'A link with the (cubic) straight connector was correctly rendered'); + assert.checkDataPath(this.paper.findViewByModel(l5).getConnection().round().serialize(), 'M 102 110 L 145 191 C 148 197 153 198 159 195 L 320 104', 'A link with the (cubic) straight connector was correctly rendered'); assert.checkDataPath(this.paper.findViewByModel(l5).getConnection().round(2).serialize(), this.paper.findViewByModel(l1).getConnection().round(2).serialize(), 'A link with the (cubic) straight connector was rendered same as if it had the rounded connector'); var l6 = l0.clone().set('connector', { name: 'straight', args: { cornerType: 'line' }}); this.graph.addCell(l6); assert.equal(this.graph.getLinks().length, 7, 'A link with the (line) straight connector was successfully added to the graph'); - assert.checkDataPath(this.paper.findViewByModel(l6).getConnection().round(2).serialize(), 'M 102 110 L 145.3 191.2 L 158.7 195.1 L 320 104', 'A link with the (line) straight connector was correctly rendered'); + assert.checkDataPath(this.paper.findViewByModel(l6).getConnection().round().serialize(), 'M 102 110 L 145 191 L 159 195 L 320 104', 'A link with the (line) straight connector was correctly rendered'); var l7 = l0.clone().set('connector', { name: 'straight', args: { cornerType: 'gap' }}); this.graph.addCell(l7); assert.equal(this.graph.getLinks().length, 8, 'A link with the (gap) straight connector was successfully added to the graph'); - assert.checkDataPath(this.paper.findViewByModel(l7).getConnection().round(2).serialize(), 'M 102 110 L 145.3 191.2 M 158.7 195.1 L 320 104', 'A link with the (gap) straight connector was correctly rendered'); + assert.checkDataPath(this.paper.findViewByModel(l7).getConnection().round().serialize(), 'M 102 110 L 145 191 M 159 195 L 320 104', 'A link with the (gap) straight connector was correctly rendered'); var customCalled = 0; var l99 = l0.clone().set('connector', function() { diff --git a/packages/joint-core/test/jointjs/dia/attributes.js b/packages/joint-core/test/jointjs/dia/attributes.js index 02fdc78b4..e1624ef32 100644 --- a/packages/joint-core/test/jointjs/dia/attributes.js +++ b/packages/joint-core/test/jointjs/dia/attributes.js @@ -210,7 +210,7 @@ QUnit.module('Attributes', function() { paper.on('cell:unhighlight', unhighlightSpy); paper.options.validateConnection = validateSpy; - var link = new joint.dia.Link({ width: 100, height: 100 }); + var link = new joint.shapes.standard.Link(); link.addTo(graph); var linkView = link.findView(paper); assert.equal(linkView.sourceMagnet, null); @@ -252,7 +252,7 @@ QUnit.module('Attributes', function() { paper.on('cell:unhighlight', unhighlightSpy); paper.options.validateConnection = validateSpy; - var link = new joint.dia.Link({ width: 100, height: 100 }); + var link = new joint.shapes.standard.Link(); link.addTo(graph); var linkView = link.findView(paper); assert.equal(linkView.sourceMagnet, null); @@ -321,7 +321,7 @@ QUnit.module('Attributes', function() { paper.on('cell:unhighlight', unhighlightSpy); paper.options.validateConnection = validateSpy; - var link = new joint.dia.Link({ width: 100, height: 100 }); + var link = new joint.shapes.standard.Link(); link.addTo(graph); var linkView = link.findView(paper); assert.equal(linkView.sourceMagnet, null); diff --git a/packages/joint-core/test/jointjs/embedding.js b/packages/joint-core/test/jointjs/embedding.js index 3e9cb611b..fdaede1fc 100644 --- a/packages/joint-core/test/jointjs/embedding.js +++ b/packages/joint-core/test/jointjs/embedding.js @@ -162,8 +162,8 @@ QUnit.module('embedding', function(hooks) { var r1 = new joint.shapes.standard.Rectangle({ position: { x: 100, y: 100 }, size: { width: 100, height: 100 }}); var r2 = new joint.shapes.standard.Rectangle({ position: { x: 500, y: 500 }, size: { width: 100, height: 100 }}); var r3 = new joint.shapes.standard.Rectangle({ position: { x: 600, y: 600 }, size: { width: 100, height: 100 }}); - var l23 = new joint.dia.Link({ source: { id: r2.id }, target: { id: r3.id }}); - var l22 = new joint.dia.Link({ source: { id: r2.id }, target: { id: r2.id }}); + var l23 = new joint.shapes.standard.Link({ source: { id: r2.id }, target: { id: r3.id }}); + var l22 = new joint.shapes.standard.Link({ source: { id: r2.id }, target: { id: r2.id }}); this.graph.addCells([r1, r2, r3, l23, l22]); var v2 = r2.findView(this.paper); diff --git a/packages/joint-core/test/jointjs/graph.js b/packages/joint-core/test/jointjs/graph.js index 3c7f49197..e5faac4f8 100644 --- a/packages/joint-core/test/jointjs/graph.js +++ b/packages/joint-core/test/jointjs/graph.js @@ -21,15 +21,15 @@ QUnit.module('graph', function(hooks) { var g = new joint.shapes.standard.Rectangle({ id: 'g' }).addTo(graph); // -> g new joint.shapes.standard.Rectangle({ id: 'h' }).addTo(graph); - new joint.dia.Link({ id: 'l1', source: { id: a.id }, target: { id: b.id }}).addTo(graph); // a -> b - new joint.dia.Link({ id: 'l2', source: { id: a.id }, target: { id: c.id }}).addTo(graph); // a -> c - new joint.dia.Link({ id: 'l3', source: { id: a.id }, target: { id: d.id }}).addTo(graph); // a -> d - new joint.dia.Link({ id: 'l4', source: { id: d.id }, target: { id: e.id }}).addTo(graph); // d -> e - new joint.dia.Link({ id: 'l5', source: { id: e.id }, target: { id: b.id }}).addTo(graph); // e -> b - new joint.dia.Link({ id: 'l6', source: { id: e.id }, target: { id: a.id }}).addTo(graph); // e -> a - new joint.dia.Link({ id: 'l7', source: { id: f.id }, target: { x: 50, y: 50 }}).addTo(graph); // f -> - new joint.dia.Link({ id: 'l8', source: { x: 100, y: 100 }, target: { id: g.id }}).addTo(graph); // -> g - new joint.dia.Link({ id: 'l9', source: { x: 200, y: 200 }, target: { x: 300, y: 300 }}).addTo(graph); // -> + new joint.shapes.standard.Link({ id: 'l1', source: { id: a.id }, target: { id: b.id }}).addTo(graph); // a -> b + new joint.shapes.standard.Link({ id: 'l2', source: { id: a.id }, target: { id: c.id }}).addTo(graph); // a -> c + new joint.shapes.standard.Link({ id: 'l3', source: { id: a.id }, target: { id: d.id }}).addTo(graph); // a -> d + new joint.shapes.standard.Link({ id: 'l4', source: { id: d.id }, target: { id: e.id }}).addTo(graph); // d -> e + new joint.shapes.standard.Link({ id: 'l5', source: { id: e.id }, target: { id: b.id }}).addTo(graph); // e -> b + new joint.shapes.standard.Link({ id: 'l6', source: { id: e.id }, target: { id: a.id }}).addTo(graph); // e -> a + new joint.shapes.standard.Link({ id: 'l7', source: { id: f.id }, target: { x: 50, y: 50 }}).addTo(graph); // f -> + new joint.shapes.standard.Link({ id: 'l8', source: { x: 100, y: 100 }, target: { id: g.id }}).addTo(graph); // -> g + new joint.shapes.standard.Link({ id: 'l9', source: { x: 200, y: 200 }, target: { x: 300, y: 300 }}).addTo(graph); // -> // Add hierarchy. var aa = new joint.shapes.standard.Rectangle({ id: 'aa' }).addTo(graph); // top -> aa; child of a, parent of aaa @@ -37,11 +37,11 @@ QUnit.module('graph', function(hooks) { var aaa = new joint.shapes.standard.Rectangle({ id: 'aaa' }).addTo(graph); // top, aa -> aaa -> top; aaa -> aaa (loop); child of a(aa) aa.embed(aaa); var top = new joint.shapes.standard.Rectangle({ id: 'top' }).addTo(graph); // aaa -> top -> aaa - new joint.dia.Link({ id: 'l10', source: { id: top.id }, target: { id: aa.id }}).addTo(graph); // top -> aa - new joint.dia.Link({ id: 'l11', source: { id: top.id }, target: { id: aaa.id }}).addTo(graph); // top -> aaa - new joint.dia.Link({ id: 'l12', source: { id: aaa.id }, target: { id: top.id }}).addTo(graph); // aaa -> top - new joint.dia.Link({ id: 'l13', source: { id: aaa.id }, target: { id: aaa.id }}).addTo(graph); // aaa -> aaa - new joint.dia.Link({ id: 'l14', source: { id: aa.id }, target: { id: aaa.id }}).addTo(graph); // aa -> aaa + new joint.shapes.standard.Link({ id: 'l10', source: { id: top.id }, target: { id: aa.id }}).addTo(graph); // top -> aa + new joint.shapes.standard.Link({ id: 'l11', source: { id: top.id }, target: { id: aaa.id }}).addTo(graph); // top -> aaa + new joint.shapes.standard.Link({ id: 'l12', source: { id: aaa.id }, target: { id: top.id }}).addTo(graph); // aaa -> top + new joint.shapes.standard.Link({ id: 'l13', source: { id: aaa.id }, target: { id: aaa.id }}).addTo(graph); // aaa -> aaa + new joint.shapes.standard.Link({ id: 'l14', source: { id: aa.id }, target: { id: aaa.id }}).addTo(graph); // aa -> aaa }; this.setupTestTreeGraph = function(graph) { @@ -55,7 +55,7 @@ QUnit.module('graph', function(hooks) { function ml(id, a, b) { var source = a.x ? a : { id: a.id }; var target = b.x ? b : { id: b.id }; - return new joint.dia.Link({ id: id, source: source, target: target, name: id }).addTo(graph); + return new joint.shapes.standard.Link({ id: id, source: source, target: target, name: id }).addTo(graph); } var a = me('a'); var b = me('b'); var c = me('c'); var d = me('d'); @@ -98,7 +98,7 @@ QUnit.module('graph', function(hooks) { function ml(id, a, b) { var source = a.x ? a : { id: a.id }; var target = b.x ? b : { id: b.id }; - return new joint.dia.Link({ id: id, source: source, target: target, name: id }).addTo(graph); + return new joint.shapes.standard.Link({ id: id, source: source, target: target, name: id }).addTo(graph); } var a = me('a'); @@ -451,7 +451,7 @@ QUnit.module('graph', function(hooks) { var graph = this.graph; var r1 = new joint.shapes.standard.Rectangle({ id: 'r1' }); var r2 = new joint.shapes.standard.Rectangle({ id: 'r2' }); - var l1 = new joint.dia.Link({ id: 'l1' }); + var l1 = new joint.shapes.standard.Link({ id: 'l1' }); graph.addCells([r1, r2, l1]); @@ -647,7 +647,7 @@ QUnit.module('graph', function(hooks) { new joint.shapes.standard.Rectangle({ id: 'el1' }).addTo(graph); new joint.shapes.standard.Rectangle({ id: 'el2' }).addTo(graph); - var l1 = new joint.dia.Link({ id: 'l1', source: { id: 'el1' }, target: { id: 'el2' }}).addTo(graph); + var l1 = new joint.shapes.standard.Link({ id: 'l1', source: { id: 'el1' }, target: { id: 'el2' }}).addTo(graph); var sinks = graph.getSinks(); assert.equal(sinks.length, 1, 'only one sink is in the graph'); @@ -811,7 +811,7 @@ QUnit.module('graph', function(hooks) { assert.equal(this.graph.getCellsBBox([]), null, 'graph.getBBox([]) with empty array returns null'); - var l = new joint.dia.Link(); + var l = new joint.shapes.standard.Link(); l.source({ x: 10, y: 20 }); l.target({ x: 110, y: 120 }); this.graph.addCell(l); @@ -1176,13 +1176,13 @@ QUnit.module('graph', function(hooks) { size: { width: 40, height: 60 } }); - var link1 = new joint.dia.Link({ + var link1 = new joint.shapes.standard.Link({ id: 'link1', source: { x: 200, y: 200 }, target: { x: 300, y: 300 } }); - var link2 = new joint.dia.Link({ + var link2 = new joint.shapes.standard.Link({ id: 'link2', source: { id: 'rect1' }, target: { id: 'rect2' }, @@ -1199,7 +1199,7 @@ QUnit.module('graph', function(hooks) { rect1.embed(embeddedElement1); - var embeddedLink1 = new joint.dia.Link({ + var embeddedLink1 = new joint.shapes.standard.Link({ id: 'embeddedLink1', source: { x: 20, y: 20 }, target: { x: 30, y: 30 } @@ -1286,7 +1286,7 @@ QUnit.module('graph', function(hooks) { var graph = this.graph; var el = new joint.shapes.standard.Rectangle({ size: { width: 100, height: 50 }}); - var l = new joint.dia.Link(); + var l = new joint.shapes.standard.Link(); this.ea = el.clone().set('id', 'a').position(100, 100).addTo(graph); this.eb = el.clone().set('id', 'b').position(300, 100).addTo(graph); @@ -1325,7 +1325,7 @@ QUnit.module('graph', function(hooks) { var graph = this.graph; var el = new joint.shapes.standard.Rectangle({ size: { width: 100, height: 50 }}); - var l = new joint.dia.Link(); + var l = new joint.shapes.standard.Link(); this.ea = el.clone().set('id', 'a').position(100, 100).addTo(graph); this.eb = el.clone().set('id', 'b').position(300, 100).addTo(graph); diff --git a/packages/joint-core/test/jointjs/linkView.js b/packages/joint-core/test/jointjs/linkView.js index dd572a7b9..df42a56b2 100644 --- a/packages/joint-core/test/jointjs/linkView.js +++ b/packages/joint-core/test/jointjs/linkView.js @@ -17,14 +17,14 @@ QUnit.module('linkView', function(hooks) { height: 300 }); - link = new joint.dia.Link({ + link = new joint.shapes.standard.Link({ source: { x: 100, y: 100 }, target: { x: 200, y: 100 } }); link.addTo(paper.model); linkView = link.findView(paper); - link2 = new joint.dia.Link({ + link2 = new joint.shapes.standard.Link({ source: { x: 100, y: 100 }, target: { x: 100, y: 100 } }); @@ -750,12 +750,12 @@ QUnit.module('linkView', function(hooks) { var sourceMagnetAnchorSpy = joint.anchors.test1 = sinon.spy(function() { return sourceAnchor; }); - linkView.model.prop('source/magnet', '.connection'); + linkView.model.prop('source/magnet', 'line'); assert.ok(sourceAnchorSpy.notCalled); assert.ok(sourceMagnetAnchorSpy.calledWithExactly( linkView2, // eslint-disable-next-line no-undef - linkView2.el.querySelector('.connection'), + linkView2.findNode('line'), sinon.match(function(value) { return value instanceof SVGElement; }), // requires resolving @@ -770,12 +770,12 @@ QUnit.module('linkView', function(hooks) { var targetMagnetAnchorSpy = joint.anchors.test2 = sinon.spy(function() { return targetAnchor; }); - linkView.model.prop('target/magnet', '.connection'); + linkView.model.prop('target/magnet', 'line'); assert.ok(targetAnchorSpy.notCalled); assert.ok(targetMagnetAnchorSpy.calledWithExactly( linkView2, - linkView2.el.querySelector('.connection'), + linkView2.findNode('line'), sinon.match.instanceOf(g.Point), sinon.match({ testArg2: true }), 'target', @@ -1096,28 +1096,12 @@ QUnit.module('linkView', function(hooks) { QUnit.test('sanity', function(assert) { - var data; var strategySpy = paper.options.connectionStrategy = sinon.spy(function(end) { end.test = true; }); // Source - data = {}; - linkView.pointerdown({ - target: linkView.el.querySelector('.marker-arrowhead[end=source]'), - type: 'mousedown', - data: data - }, 0, 0); - linkView.pointermove({ - target: rv1.el, - type: 'mousemove', - data: data - }, 50, 50); - linkView.pointerup({ - target: rv1.el, - type: 'mouseup', - data: data - }, 50, 50); + simulate.dragLinkView(linkView, 'source', { targetEl: rv1.el, x: 50, y: 50 }); assert.ok(strategySpy.calledOnce); assert.ok(strategySpy.calledWithExactly( @@ -1133,22 +1117,7 @@ QUnit.module('linkView', function(hooks) { assert.equal(linkView.model.attributes.source.test, true); // Target - data = {}; - linkView.pointerdown({ - target: linkView.el.querySelector('.marker-arrowhead[end=target]'), - type: 'mousedown', - data: data - }, 0, 0); - linkView.pointermove({ - target: rv1.el, - type: 'mousemove', - data: data - }, 40, 40); - linkView.pointerup({ - target: rv1.el, - type: 'mouseup', - data: data - }, 40, 40); + simulate.dragLinkView(linkView, 'target', { targetEl: rv1.el, x: 40, y: 40 }); assert.ok(strategySpy.calledTwice); assert.ok(strategySpy.calledWithExactly( @@ -1207,11 +1176,7 @@ QUnit.module('linkView', function(hooks) { r1.translate(10,0); // Source data = {}; - linkView.pointerdown({ - target: linkView.el.querySelector('.marker-arrowhead[end=source]'), - type: 'mousedown', - data: data - }, 0, 0); + simulate.dragLinkView(linkView, 'source', { data }); // the first move linkView.pointermove({ target: rv1.el, @@ -1292,11 +1257,7 @@ QUnit.module('linkView', function(hooks) { paper.options.snapLinksSelf = { radius: 40 }; // Source data = {}; - linkView.pointerdown({ - target: linkView.el.querySelector('.marker-arrowhead[end=source]'), - type: 'mousedown', - data: data - }, 0, 0); + simulate.dragLinkView(linkView, 'source', { data }); // the move linkView.pointermove({ target: paper, diff --git a/packages/joint-core/test/jointjs/links.js b/packages/joint-core/test/jointjs/links.js index e21be678f..64a6517bb 100644 --- a/packages/joint-core/test/jointjs/links.js +++ b/packages/joint-core/test/jointjs/links.js @@ -30,7 +30,7 @@ QUnit.module('links', function(hooks) { QUnit.test('should return FALSE', function(assert) { - var link = new joint.dia.Link; + var link = new joint.shapes.standard.Link; assert.notOk(link.isElement()); }); @@ -45,7 +45,7 @@ QUnit.module('links', function(hooks) { QUnit.test('should return TRUE', function(assert) { - var link = new joint.dia.Link; + var link = new joint.shapes.standard.Link; assert.ok(link.isLink()); }); @@ -58,24 +58,27 @@ QUnit.module('links', function(hooks) { this.graph.addCell([r1, r2]); - var l0 = new joint.dia.Link({ + var l0 = new joint.shapes.standard.Link({ source: { id: r1.id }, target: { id: r2.id }, - attrs: { '.connection': { stroke: 'black' }} + attrs: { line: { stroke: 'black' }} }); this.graph.addCell(l0); - assert.strictEqual(l0.constructor, joint.dia.Link, 'link.constructor === joint.dia.Link'); + assert.strictEqual(l0.constructor, joint.shapes.standard.Link, 'link.constructor === joint.shapes.standard.Link'); var v0 = this.paper.findViewByModel(l0); - assert.checkDataPath(v0.$('.connection').attr('d'), 'M 140 70 L 320 70', 'link path data starts at the source right-middle point and ends in the target left-middle point'); + assert.checkDataPath(v0.findNode('line').getAttribute('d'), 'M 140 70 L 320 70', 'link path data starts at the source right-middle point and ends in the target left-middle point'); var l1 = new joint.dia.Link({ + type: 'test-link', source: { id: r1.id }, target: { id: r2.id }, - markup: '' + markup: joint.util.svg/*xml*/` + + ` }); assert.ok(_.isUndefined(l1.get('source').x) && _.isUndefined(l1.get('source').y), @@ -89,9 +92,13 @@ QUnit.module('links', function(hooks) { assert.ok(v1, 'link with custom markup (1 child) is rendered.'); var l2 = new joint.dia.Link({ + type: 'test-link', source: { id: r1.id }, target: { id: r2.id }, - markup: '' + markup: joint.util.svg/*xml*/` + + + ` }); this.graph.addCell(l2); var v2 = this.paper.findViewByModel(l2); @@ -99,7 +106,7 @@ QUnit.module('links', function(hooks) { assert.ok(v2, 'link with custom markup (2 children) is rendered.'); // It should be possible to create empty links and set source/target later. - var lEmpty = new joint.dia.Link; + var lEmpty = new joint.shapes.standard.Link; assert.ok(true, 'creating a link with no source/target does not throw an exception'); var rEmpty = new joint.shapes.standard.Rectangle; var r2Empty = new joint.shapes.standard.Rectangle; @@ -192,7 +199,7 @@ QUnit.module('links', function(hooks) { QUnit.test('interaction', function(assert) { - assert.expect(6); + assert.expect(4); var event; var r1 = new joint.shapes.standard.Rectangle({ position: { x: 20, y: 30 }, size: { width: 120, height: 80 }}); @@ -204,10 +211,15 @@ QUnit.module('links', function(hooks) { var vr1 = this.paper.findViewByModel(r1); var vr3 = this.paper.findViewByModel(r3); - var l0 = new joint.dia.Link({ + var l0 = new joint.shapes.standard.Link({ source: { id: r1.id }, target: { id: r2.id }, - attrs: { '.connection': { stroke: 'black' }}, + attrs: { + line: { + stroke: 'black', + } + }, + vertices: [{ x: 300, y: 100 }], labels: [ { position: .5, attrs: { text: { text: 'test label' }}} ] @@ -216,30 +228,11 @@ QUnit.module('links', function(hooks) { this.graph.addCell(l0); var v0 = this.paper.findViewByModel(l0); - this.paper.options.validateConnection = function(vs, ms, vt, mt, v) { assert.ok(vs === vr1 && vt === vr3, 'connection validation executed'); return vt instanceof joint.dia.ElementView; }; - // adding vertices - event = { target: v0.el.querySelector('.connection') }; - v0.pointerdown(event, 200, 70); - v0.pointerup(event); - assert.deepEqual(l0.get('vertices'), [{ x: 200, y: 70 }], 'vertex added after click the connection.'); - - var firstVertexRemoveArea = v0.el.querySelector('.marker-vertex-remove-area'); - - event = { target: v0.el.querySelector('.connection') }; - v0.pointerdown(event, 300, 70); - v0.pointermove(event, 300, 100); - v0.pointerup(event); - assert.deepEqual(l0.get('vertices'), [{ x: 200, y: 70 }, { x: 300, y: 100 }], 'vertex added and translated after click the connection wrapper and mousemove.'); - - event = { target: firstVertexRemoveArea }; - v0.pointerdown(event); - v0.pointerup(event); - // arrowheadmove var highlighted = false; @@ -258,8 +251,9 @@ QUnit.module('links', function(hooks) { } }); - event = { target: v0.el.querySelector('.marker-arrowhead[end="target"]') }; - v0.pointerdown(event); + var data = {}; + event = { data }; + simulate.dragLinkView(v0, 'target', { data }); event.target = vr3.el; event.type = 'mousemove'; v0.pointermove(event, 630, 40); @@ -270,29 +264,29 @@ QUnit.module('links', function(hooks) { assert.notOk(highlighted, 'after moving the pointer to coordinates 400, 400 the rectangle is not highlighted anymore'); v0.pointerup(event); - assert.checkDataPath(v0.el.querySelector('.connection').getAttribute('d'), 'M 140 78 L 300 100 L 400 400', 'link path data starts at the source right-middle point, going through the vertex and ends at the coordinates 400, 400'); + assert.checkDataPath(v0.getConnection().round().serialize(), 'M 140 78 L 300 100 L 400 400', 'link path data starts at the source right-middle point, going through the vertex and ends at the coordinates 400, 400'); }); QUnit.test('defaultLink', function(assert) { assert.expect(10); - this.paper.options.defaultLink = new joint.dia.Link(); + this.paper.options.defaultLink = new joint.shapes.standard.DoubleLink(); var link = this.paper.getDefaultLink(); - assert.ok(link instanceof joint.dia.Link, 'sanity: defaultLink is cloned'); + assert.ok(link instanceof joint.shapes.standard.DoubleLink, 'sanity: defaultLink is cloned'); this.paper.options.defaultLink = function(v, m) { - return new joint.dia.Link(); + return new joint.shapes.standard.DoubleLink(); }; link = this.paper.getDefaultLink(); - assert.ok(link instanceof joint.dia.Link, 'sanity: defaultLink is a function'); + assert.ok(link instanceof joint.shapes.standard.DoubleLink, 'sanity: defaultLink is a function'); - var MyLink = joint.dia.Link.extend({ + var MyLink = joint.shapes.standard.Link.extend({ isMyLink: true }); @@ -305,7 +299,7 @@ QUnit.module('links', function(hooks) { this.graph.addCell(model); var view = this.paper.findViewByModel(model); - var rect = view.$('rect')[0]; + var rect = view.findNode('body'); this.graph.on('add', function(cell) { @@ -345,7 +339,7 @@ QUnit.module('links', function(hooks) { QUnit.test('source', function(assert) { - var link = new joint.dia.Link({ + var link = new joint.shapes.standard.Link({ source: { x: 40, y: 40 }, target: { x: 100, y: 100 } }); @@ -377,7 +371,7 @@ QUnit.module('links', function(hooks) { QUnit.test('target', function(assert) { - var link = new joint.dia.Link({ + var link = new joint.shapes.standard.Link({ source: { x: 40, y: 40 }, target: { x: 100, y: 100 } }); @@ -421,11 +415,11 @@ QUnit.module('links', function(hooks) { myrect2.translate(300); this.graph.addCell(myrect2); - var link = new joint.dia.Link({ + var link = new joint.shapes.standard.Link({ source: { id: myrect.id }, target: { id: myrect2.id }, - attrs: { '.connection': { stroke: 'black' }} + attrs: { link: { stroke: 'black' }} }); this.graph.addCell(link); @@ -438,20 +432,20 @@ QUnit.module('links', function(hooks) { assert.notOk(link.get('source').id, 'source of the link became a point'); assert.ok(link.get('target').id, 'target of the link is still not a point'); - assert.checkDataPath(linkView.$('.connection').attr('d'), 'M 140 70 L 320 70', 'link path data stayed the same after disconnection'); - assert.checkDataPath(linkView.$('.connection-wrap').attr('d'), 'M 140 70 L 320 70', 'link connection-wrap path data is the same as the .connection path data'); + assert.checkDataPath(linkView.findNode('line').getAttribute('d'), 'M 140 70 L 320 70', 'link path data stayed the same after disconnection'); + assert.checkDataPath(linkView.findNode('wrapper').getAttribute('d'), 'M 140 70 L 320 70', 'link connection-wrap path data is the same as the .connection path data'); myrect.translate(-10); - assert.checkDataPath(linkView.$('.connection').attr('d'), 'M 140 70 L 320 70', 'link path data stayed the same after the disconnected source moved'); + assert.checkDataPath(linkView.findNode('line').getAttribute('d'), 'M 140 70 L 320 70', 'link path data stayed the same after the disconnected source moved'); link.set('source', { id: myrect.id }); - assert.checkDataPath(linkView.$('.connection').attr('d'), 'M 130 70 L 320 70', 'link path data updated after the disconnected source became re-connected again'); + assert.checkDataPath(linkView.findNode('line').getAttribute('d'), 'M 130 70 L 320 70', 'link path data updated after the disconnected source became re-connected again'); myrect.translate(10); - assert.checkDataPath(linkView.$('.connection').attr('d'), 'M 140 70 L 320 70', 'link path data updated after the just connected source moved'); + assert.checkDataPath(linkView.findNode('line').getAttribute('d'), 'M 140 70 L 320 70', 'link path data updated after the just connected source moved'); // disconnect: link.set('target', linkView.targetPoint.toJSON()); @@ -478,7 +472,7 @@ QUnit.module('links', function(hooks) { this.graph.addCell(myrect); this.graph.addCell(myrect2); - var link = new joint.dia.Link({ + var link = new joint.shapes.standard.Link({ source: { id: myrect.id }, target: { id: myrect2.id } }); @@ -502,13 +496,13 @@ QUnit.module('links', function(hooks) { var myrect = new joint.shapes.standard.Rectangle; this.graph.addCell(myrect); - var link = new joint.dia.Link({ source: { id: myrect.id }, target: { id: myrect.id }}); + var link = new joint.shapes.standard.Link({ source: { id: myrect.id }, target: { id: myrect.id }}); this.graph.addCell(link); assert.equal(link.hasLoop(), true, 'link has a loop'); var myrect2 = new joint.shapes.standard.Rectangle; this.graph.addCell(myrect2); - var link2 = new joint.dia.Link({ source: { id: myrect2.id }, target: { x: 20, y: 20 }}); + var link2 = new joint.shapes.standard.Link({ source: { id: myrect2.id }, target: { x: 20, y: 20 }}); this.graph.addCell(link2); assert.equal(link2.hasLoop(), false, 'link pinned to the paper does not have a loop'); assert.equal(link2.hasLoop({ deep: true }), false, 'link pinned to the paper does not have a loop with deep = true'); @@ -517,7 +511,7 @@ QUnit.module('links', function(hooks) { var myrect3a = new joint.shapes.standard.Rectangle; myrect3.embed(myrect3a); this.graph.addCells([myrect3, myrect3a]); - var link3 = new joint.dia.Link({ source: { id: myrect3.id }, target: { id: myrect3a.id }}); + var link3 = new joint.shapes.standard.Link({ source: { id: myrect3.id }, target: { id: myrect3a.id }}); this.graph.addCell(link3); assert.equal(link3.hasLoop(), false, 'link targetting an embedded element does not have a loop with deep = false'); assert.equal(link3.hasLoop({ deep: true }), true, 'link targetting an embedded element does have a loop with deep = true'); @@ -525,200 +519,76 @@ QUnit.module('links', function(hooks) { QUnit.test('markers', function(assert) { - var myrect = new joint.shapes.standard.Rectangle({ + const myrect1 = new joint.shapes.standard.Rectangle({ position: { x: 20, y: 30 }, size: { width: 120, height: 80 }, attrs: { text: { text: 'my rectangle' }} }); - var myrect2 = myrect.clone(); - myrect2.translate(300); - - this.graph.addCell(myrect); - this.graph.addCell(myrect2); - - var link = new joint.dia.Link({ - - source: { id: myrect.id }, - target: { id: myrect2.id }, - attrs: { - '.connection': { - stroke: 'black' - }, - '.marker-source': { - d: 'M 10 0 L 0 5 L 10 10 z' - }, - '.marker-target': { - d: 'M 10 0 L 0 5 L 10 10 z' - } - } - }); - - this.graph.addCell(link); - - var linkView = this.paper.findViewByModel(link); - - var markerSourceBbox = V(linkView.$('.marker-source')[0]).bbox(); - - assert.deepEqual( - { x: markerSourceBbox.x, y: markerSourceBbox.y, width: markerSourceBbox.width, height: markerSourceBbox.height }, - { x: 140, y: 65, width: 10, height: 10 }, - '.marker-source should point to the left edge of the rectangle' - ); - - var markerTargetBbox = V(linkView.$('.marker-target')[0]).bbox(); - assert.deepEqual( - { x: markerTargetBbox.x, y: markerTargetBbox.y, width: markerTargetBbox.width, height: markerTargetBbox.height, rotation: V(linkView.$('.marker-target')[0]).rotate().angle }, - { x: 310, y: 65, width: 10, height: 10, rotation: -180 }, - '.marker-target should point to the right edge of the rectangle 2 and should be rotated by -180 degrees' - ); - }); - - QUnit.test('vertices', function(assert) { - - var myrect = new joint.shapes.standard.Rectangle({ - position: { x: 20, y: 30 }, - size: { width: 120, height: 80 }, - attrs: { text: { text: 'my rectangle' }} - }); - var myrect2 = myrect.clone(); + const myrect2 = myrect1.clone(); myrect2.translate(300); - this.graph.addCell(myrect); - this.graph.addCell(myrect2); - - var link = new joint.dia.Link({ - - source: { id: myrect.id }, + const link = new joint.shapes.standard.Link({ + source: { id: myrect1.id }, target: { id: myrect2.id }, - vertices: [{ x: 80, y: 150 }, { x: 380, y: 150 }], attrs: { - '.connection': { - stroke: 'black' - }, - '.marker-source': { - d: 'M 10 0 L 0 5 L 10 10 z' - }, - '.marker-target': { - d: 'M 10 0 L 0 5 L 10 10 z' + line: { + sourceMarker: { + d: 'M 10 0 L 0 5 L 10 10 z', + testSourceMarker: true + }, + targetMarker: { + d: 'M 10 0 L 0 5 L 10 10 z', + testTargetMarker: true + }, + vertexMarker: { + d: 'M 10 0 L 0 5 L 10 10 z', + testMidMarker: true + } } } }); + this.graph.addCell(myrect1); + this.graph.addCell(myrect2); this.graph.addCell(link); - var linkView = this.paper.findViewByModel(link); + const linkView = this.paper.findViewByModel(link); - var markerSourceBbox = V(linkView.$('.marker-source')[0]).bbox(); + assert.ok(linkView.findNode('line').getAttribute('marker-start')); + assert.ok(linkView.findNode('line').getAttribute('marker-end')); + assert.ok(linkView.findNode('line').getAttribute('marker-mid')); - assert.deepEqual( - { - x: markerSourceBbox.x, - y: markerSourceBbox.y, - width: markerSourceBbox.width, - height: markerSourceBbox.height, - rotation: g.normalizeAngle(V(linkView.$('.marker-source')[0]).rotate().angle) - }, - { - x: 75, - y: 110, - width: 10, - height: 10, - rotation: g.normalizeAngle(-270) - }, - '.marker-source should point to the bottom edge of the rectangle and should be rotated by -270 degrees' - ); - - var markerTargetBbox = V(linkView.$('.marker-target')[0]).bbox(); - - assert.deepEqual( - { - x: markerTargetBbox.x, - y: markerTargetBbox.y, - width: markerTargetBbox.width, - height: markerTargetBbox.height, - rotation: g.normalizeAngle(V(linkView.$('.marker-target')[0]).rotate().angle) - }, - { - x: 375, - y: 110, - width: 10, - height: 10, - rotation: g.normalizeAngle(-270) - }, - '.marker-target should point to the bottom edge of the rectangle 2 and should be rotated by -270 degrees' - ); - - assert.equal($('.marker-vertex').length, 2, 'there is exactly 2 vertex markers on the page'); - - var firstVertextPosition = g.rect(V($('.marker-vertex')[0]).bbox()).center(); - assert.deepEqual( - { x: firstVertextPosition.x, y: firstVertextPosition.y }, - link.get('vertices')[0], - 'first vertex is in the same position as defined in the vertices array' - ); - - var secondVertextPosition = g.rect(V($('.marker-vertex')[1]).bbox()).center(); - assert.deepEqual( - { x: secondVertextPosition.x, y: secondVertextPosition.y }, - link.get('vertices')[1], - 'second vertex is in the same position as defined in the vertices array' - ); + assert.ok(this.paper.defs.contains(this.paper.el.querySelector('[test-source-marker]'))); + assert.ok(this.paper.defs.contains(this.paper.el.querySelector('[test-target-marker]'))); + assert.ok(this.paper.defs.contains(this.paper.el.querySelector('[test-mid-marker]'))); }); QUnit.test('perpendicular links', function(assert) { this.paper.options.defaultAnchor = { name: 'perpendicular' }; - var myrect = new joint.shapes.standard.Rectangle({ + const myrect1 = new joint.shapes.standard.Rectangle({ position: { x: 20, y: 30 }, size: { width: 120, height: 80 }, attrs: { text: { text: 'my rectangle' }} }); - var myrect2 = myrect.clone(); + const myrect2 = myrect1.clone(); myrect2.translate(300); - this.graph.addCell(myrect); - this.graph.addCell(myrect2); - - var link = new joint.dia.Link({ - - source: { id: myrect.id }, + const link = new joint.shapes.standard.Link({ + source: { id: myrect1.id }, target: { id: myrect2.id }, vertices: [{ x: 138, y: 150 }, { x: 180, y: 108 }], - attrs: { - '.connection': { - stroke: 'black' - }, - '.marker-source': { - d: 'M 0 0 L 0 0 z' - }, - '.marker-target': { - d: 'M 0 0 L 0 0 z' - } - } }); + this.graph.addCell(myrect1); + this.graph.addCell(myrect2); this.graph.addCell(link); - var linkView = this.paper.findViewByModel(link); - - var markerSourceBbox = V(linkView.$('.marker-source')[0]).bbox(); - - assert.deepEqual( - { x: markerSourceBbox.x, y: markerSourceBbox.y }, - { x: 138, y: 110 }, - '.marker-source should point vertically to the edge of the source rectangle making the part of the link before the first vertex perpendicular to the source rectangle' - ); - - var markerTargetBbox = V(linkView.$('.marker-target')[0]).bbox(); - - assert.deepEqual( - { x: markerTargetBbox.x, y: markerTargetBbox.y }, - { x: myrect2.get('position').x, y: 108 }, - '.marker-target should point horizontally to the edge of the target rectangle making the part of the link after the last vertex perpendicular to the target rectangle' - ); - + const linkView = this.paper.findViewByModel(link); + assert.equal(linkView.sourcePoint.toString(), '138@110'); + assert.equal(linkView.targetPoint.toString(), '320@108'); }); QUnit.module('Labels', function(assert) { @@ -732,7 +602,7 @@ QUnit.module('links', function(hooks) { this.graph.addCell(myrect); this.graph.addCell(myrect2); - var link = new joint.dia.Link({ + var link = new joint.shapes.standard.Link({ source: { id: myrect.id }, target: { id: myrect2.id }, @@ -1262,7 +1132,7 @@ QUnit.module('links', function(hooks) { QUnit.test('change:labels', function(assert) { - var l = new joint.dia.Link({ + var l = new joint.shapes.standard.Link({ source: { x: 0, y: 0 }, target: { x: 100, y: 100 } }).addTo(this.graph); @@ -1347,7 +1217,7 @@ QUnit.module('links', function(hooks) { QUnit.test('change:labels + change:source', function(assert) { var graph = this.graph; var paper = this.paper; - var l = new joint.dia.Link({ + var l = new joint.shapes.standard.Link({ source: { x: 0, y: 0 }, target: { x: 100, y: 100 } }).addTo(graph); @@ -1401,7 +1271,7 @@ QUnit.module('links', function(hooks) { QUnit.test('snap links', function(assert) { var event; - var link = new joint.dia.Link({ + var link = new joint.shapes.standard.Link({ source: { x: 0, y: 0 }, target: { x: 0, y: 0 } }); @@ -1413,17 +1283,11 @@ QUnit.module('links', function(hooks) { this.graph.addCells([myrect, link]); var v = this.paper.findViewByModel(link); - var t = v.el.querySelector('.marker-arrowhead[end=target]'); // link target was out of the radius and therefore was not snapped to the element this.paper.options.snapLinks = { radius: 5 }; - - event = { target: t }; - v.pointerdown(event, 0, 0); - event.target = this.paper.el; - v.pointermove(event, 90, 90); - v.pointerup(event, 90, 90); + simulate.dragLinkView(v, 'target', { targetEl: this.paper.el, x: 90, y: 90 }); assert.deepEqual(link.get('target'), { x: 90, y: 90 @@ -1432,12 +1296,7 @@ QUnit.module('links', function(hooks) { // link target was snapped to the element this.paper.options.snapLinks = { radius: 50 }; - - event = { target: t }; - v.pointerdown(event, 0, 0); - event.target = this.paper.el; - v.pointermove(event, 90, 90); - v.pointerup(event, 90, 90); + simulate.dragLinkView(v, 'target', { targetEl: this.paper.el, x: 90, y: 90 }); assert.ok(link.get('target').id === myrect.id, 'link target was snapped to the element'); @@ -1446,28 +1305,18 @@ QUnit.module('links', function(hooks) { // getBoundingClientRect returns negative values for top and left when paper not visible this.paper.options.snapLinks = { radius: Number.MAX_VALUE }; - myrect.attr('.', { magnet: false }); - myrect.attr('text', { magnet: true, port: 'port' }); + myrect.attr('root', { magnet: false }); + myrect.attr('label', { magnet: true, port: 'port' }); this.paper.options.validateConnection = function() { return true; }; - - event = { target: t }; - v.pointerdown(event, 0, 0); - event.target = this.paper.el; - v.pointermove(event, 90, 90); - v.pointerup(event, 90, 90); + simulate.dragLinkView(v, 'target', { targetEl: this.paper.el, x: 90, y: 90 }); assert.ok(link.get('target').port === 'port', 'link target was snapped to the port'); // the validation is taken into account when snapping to port this.paper.options.validateConnection = function() { return false; }; - - event = { target: t }; - v.pointerdown(event, 0, 0); - event.target = this.paper.el; - v.pointermove(event, 90, 90); - v.pointerup(event, 90, 90); + simulate.dragLinkView(v, 'target', { targetEl: this.paper.el, x: 90, y: 90 }); assert.deepEqual(link.get('target'), { x: 90, y: 90 @@ -1477,7 +1326,7 @@ QUnit.module('links', function(hooks) { QUnit.test('mark available', function(assert) { var event; - var link = new joint.dia.Link({ + var link = new joint.shapes.standard.Link({ source: { x: 0, y: 0 }, target: { x: 0, y: 0 } }); @@ -1498,12 +1347,12 @@ QUnit.module('links', function(hooks) { this.graph.addCells([myrect1, myrect2, link]); var v = this.paper.findViewByModel(link); - var t = v.el.querySelector('.marker-arrowhead[end=target]'); this.paper.options.markAvailable = true; - event = { target: t }; - v.pointerdown(event, 0, 0); + var data = {}; + event = { data }; + simulate.dragLinkView(v, 'target', { data }); var availableMagnets = this.paper.el.querySelectorAll('.available-magnet'); var availableCells = this.paper.el.querySelectorAll('.available-cell'); @@ -1531,13 +1380,13 @@ QUnit.module('links', function(hooks) { assert.ok(vertices.length > 0, 'Default router was used for the model with no router defined.'); }; - var linkDefaultRouter = new joint.dia.Link({ + var linkDefaultRouter = new joint.shapes.standard.Link({ source: { x: 0, y: 0 }, target: { x: 0, y: 0 }, vertices: [{ x: 50, y: 50 }] }); - var linkOwnRouter = new joint.dia.Link({ + var linkOwnRouter = new joint.shapes.standard.Link({ source: { x: 0, y: 0 }, target: { x: 0, y: 0 }, router: { name: 'orthogonal' }, @@ -1556,13 +1405,13 @@ QUnit.module('links', function(hooks) { return 'M 0 0'; }; - var linkDefaultConnector = new joint.dia.Link({ + var linkDefaultConnector = new joint.shapes.standard.Link({ source: { x: 0, y: 0 }, target: { x: 0, y: 0 }, vertices: [{ x: 50, y: 50 }] }); - var linkOwnConnector = new joint.dia.Link({ + var linkOwnConnector = new joint.shapes.standard.Link({ source: { x: 0, y: 0 }, target: { x: 0, y: 0 }, connector: { name: 'normal' }, @@ -1574,7 +1423,7 @@ QUnit.module('links', function(hooks) { QUnit.test('getSourceCell', function(assert) { - var link = new joint.dia.Link({ + var link = new joint.shapes.standard.Link({ source: { x: 40, y: 40 }, target: { x: 100, y: 100 } }); @@ -1602,7 +1451,7 @@ QUnit.module('links', function(hooks) { assert.ok(source && source instanceof joint.dia.Element && source.id === element.id, 'with source element'); - var linkNotInGraph = new joint.dia.Link({ + var linkNotInGraph = new joint.shapes.standard.Link({ source: { id: element.get('id') }, target: { id: element.get('id') } }); @@ -1642,7 +1491,7 @@ QUnit.module('links', function(hooks) { QUnit.test('getTargetCell', function(assert) { - var link = new joint.dia.Link({ + var link = new joint.shapes.standard.Link({ source: { x: 40, y: 40 }, target: { x: 100, y: 100 } }); @@ -1670,7 +1519,7 @@ QUnit.module('links', function(hooks) { assert.ok(target && target instanceof joint.dia.Element && target.id === element.id, 'with target element'); - var linkNotInGraph = new joint.dia.Link({ + var linkNotInGraph = new joint.shapes.standard.Link({ source: { id: element.get('id') }, target: { id: element.get('id') } }); @@ -1714,7 +1563,7 @@ QUnit.module('links', function(hooks) { var a = new joint.shapes.standard.Rectangle({ id: 'a' }); var b = new joint.shapes.standard.Rectangle({ id: 'b' }); var c = new joint.shapes.standard.Rectangle({ id: 'c' }); - var l = new joint.dia.Link({ id: 'l' }); + var l = new joint.shapes.standard.Link({ id: 'l' }); this.graph.addCells([a, b, c, l]); @@ -1741,7 +1590,7 @@ QUnit.module('links', function(hooks) { var a = new joint.shapes.standard.Rectangle({ id: 'a' }); var b = new joint.shapes.standard.Rectangle({ id: 'b' }); var c = new joint.shapes.standard.Rectangle({ id: 'c' }); - var l = new joint.dia.Link({ id: 'l' }); + var l = new joint.shapes.standard.Link({ id: 'l' }); this.graph.addCells([a, b, c, l]); @@ -1765,8 +1614,8 @@ QUnit.module('links', function(hooks) { var a = new joint.shapes.standard.Rectangle({ id: 'a' }); var b = new joint.shapes.standard.Rectangle({ id: 'b' }); var c = new joint.shapes.standard.Rectangle({ id: 'c' }); - var l = new joint.dia.Link({ id: 'l' }); - var l2 = new joint.dia.Link({ id: 'l2' }); + var l = new joint.shapes.standard.Link({ id: 'l' }); + var l2 = new joint.shapes.standard.Link({ id: 'l2' }); this.graph.addCells([a, b, c, l, l2]); var lv = l.findView(this.paper); @@ -2050,14 +1899,14 @@ QUnit.module('links', function(hooks) { QUnit.module('label', function() { QUnit.test('getter', function(assert) { - var link = new joint.dia.Link({ labels: [{ position: { distance: 10, offset: 10 }}, { position: { distance: 20, offset: 20 }}] }); + var link = new joint.shapes.standard.Link({ labels: [{ position: { distance: 10, offset: 10 }}, { position: { distance: 20, offset: 20 }}] }); assert.deepEqual(link.label(0), { position: { distance: 10, offset: 10 }}); assert.deepEqual(link.label(1), { position: { distance: 20, offset: 20 }}); assert.deepEqual(link.label(2), undefined); }); QUnit.test('setter', function(assert) { - var link = new joint.dia.Link({ labels: [{ position: { distance: 10, offset: 10 }}, { position: { distance: 20, offset: 20 }}] }); + var link = new joint.shapes.standard.Link({ labels: [{ position: { distance: 10, offset: 10 }}, { position: { distance: 20, offset: 20 }}] }); link.label(0, { position: { distance: 100, offset: 100 }}); link.label(1, { position: { distance: 200 }}); link.label(2, { position: { distance: 30, offset: 30 }}); @@ -2068,7 +1917,7 @@ QUnit.module('links', function(hooks) { QUnit.module('labels', function() { QUnit.test('getter', function(assert) { - var link = new joint.dia.Link(); + var link = new joint.shapes.standard.Link(); assert.deepEqual(link.labels(), []); link.set('labels', [{ position: { distance: 10, offset: 10 }}]); assert.notEqual(link.labels(), link.get('labels'), 'Copy'); @@ -2076,7 +1925,7 @@ QUnit.module('links', function(hooks) { }); QUnit.test('setter', function(assert) { - var link = new joint.dia.Link({ labels: [{ position: { distance: 10, offset: 10 }}, { position: { distance: 20, offset: 20 }}] }); + var link = new joint.shapes.standard.Link({ labels: [{ position: { distance: 10, offset: 10 }}, { position: { distance: 20, offset: 20 }}] }); link.labels([{ position: { distance: 30, offset: 30 }}]); assert.deepEqual(link.get('labels'), [{ position: { distance: 30, offset: 30 }}]); }); @@ -2085,7 +1934,7 @@ QUnit.module('links', function(hooks) { QUnit.module('insertLabel', function() { QUnit.test('sanity', function(assert) { - var link = new joint.dia.Link(); + var link = new joint.shapes.standard.Link(); var error; try { @@ -2105,7 +1954,7 @@ QUnit.module('links', function(hooks) { QUnit.module('appendLabel', function() { QUnit.test('sanity', function(assert) { - var link = new joint.dia.Link(); + var link = new joint.shapes.standard.Link(); var error; try { @@ -2123,7 +1972,7 @@ QUnit.module('links', function(hooks) { QUnit.module('removeLabel', function() { QUnit.test('sanity', function(assert) { - var link = new joint.dia.Link({ labels: [{ position: { distance: 10, offset: 10 }}, { position: { distance: 20, offset: 20 }}, { position: { distance: 30, offset: 30 }}, { position: { distance: 40, offset: 40 }}] }); + var link = new joint.shapes.standard.Link({ labels: [{ position: { distance: 10, offset: 10 }}, { position: { distance: 20, offset: 20 }}, { position: { distance: 30, offset: 30 }}, { position: { distance: 40, offset: 40 }}] }); link.removeLabel(100); assert.deepEqual(link.labels(), [{ position: { distance: 10, offset: 10 }}, { position: { distance: 20, offset: 20 }}, { position: { distance: 30, offset: 30 }}, { position: { distance: 40, offset: 40 }}]); link.removeLabel(-1); @@ -2139,13 +1988,13 @@ QUnit.module('links', function(hooks) { QUnit.module('vertex', function() { QUnit.test('getter', function(assert) { - var link = new joint.dia.Link(); + var link = new joint.shapes.standard.Link(); link.vertices([{ x: 0, y: 0 }, { x: 1, y: 1 }]); assert.deepEqual(link.vertex(0), { x: 0, y: 0 }); assert.deepEqual(link.vertex(1), { x: 1, y: 1 }); assert.deepEqual(link.vertex(2), undefined); - var link2 = new joint.dia.Link(); + var link2 = new joint.shapes.standard.Link(); link2.vertices([new g.Point(0, 0), new g.Point(1, 1)]); assert.deepEqual(link2.vertex(0), { x: 0, y: 0 }); assert.deepEqual(link2.vertex(1), { x: 1, y: 1 }); @@ -2153,7 +2002,7 @@ QUnit.module('links', function(hooks) { }); QUnit.test('setter', function(assert) { - var link = new joint.dia.Link(); + var link = new joint.shapes.standard.Link(); link.vertices([{ x: 0, y: 0 }, { x: 1, y: 1 }]); link.vertex(0, { x: 10, y: 10 }); link.vertex(1, { x: 20 }); @@ -2161,7 +2010,7 @@ QUnit.module('links', function(hooks) { assert.deepEqual(link.get('vertices'), [{ x: 10, y: 10 }, { x: 20, y: 1 }, { x: 3, y: 3 }]); assert.deepEqual(link.vertices(), [{ x: 10, y: 10 }, { x: 20, y: 1 }, { x: 3, y: 3 }]); - var link2 = new joint.dia.Link(); + var link2 = new joint.shapes.standard.Link(); link2.vertices([new g.Point(0, 0), new g.Point(1, 1)]); link2.vertex(0, { x: 10, y: 10 }); link2.vertex(1, { x: 20 }); @@ -2174,18 +2023,18 @@ QUnit.module('links', function(hooks) { QUnit.module('vertices', function() { QUnit.test('getter', function(assert) { - var link = new joint.dia.Link(); + var link = new joint.shapes.standard.Link(); assert.deepEqual(link.get('vertices'), undefined); assert.deepEqual(link.vertices(), []); - var link2 = new joint.dia.Link(); + var link2 = new joint.shapes.standard.Link(); link2.vertices([{ x: 0, y: 0 }]); assert.deepEqual(link2.get('vertices'), [{ x: 0, y: 0 }]); assert.deepEqual(link2.vertices(), [{ x: 0, y: 0 }]); }); QUnit.test('setter', function(assert) { - var link = new joint.dia.Link(); + var link = new joint.shapes.standard.Link(); link.vertices([{ x: 0, y: 0 }, { x: 1, y: 1 }]); assert.deepEqual(link.get('vertices'), [{ x: 0, y: 0 }, { x: 1, y: 1 }]); assert.deepEqual(link.vertices(), [{ x: 0, y: 0 }, { x: 1, y: 1 }]); @@ -2193,7 +2042,7 @@ QUnit.module('links', function(hooks) { assert.deepEqual(link.get('vertices'), [{ x: 3, y: 3 }]); assert.deepEqual(link.vertices(), [{ x: 3, y: 3 }]); - var link2 = new joint.dia.Link(); + var link2 = new joint.shapes.standard.Link(); link2.vertices([new g.Point(0, 0), new g.Point(1, 1)]); assert.deepEqual(link2.get('vertices'), [{ x: 0, y: 0 }, { x: 1, y: 1 }]); assert.deepEqual(link2.vertices(), [{ x: 0, y: 0 }, { x: 1, y: 1 }]); @@ -2208,7 +2057,7 @@ QUnit.module('links', function(hooks) { QUnit.test('sanity', function(assert) { var error; - var link = new joint.dia.Link(); + var link = new joint.shapes.standard.Link(); error = null; try { link.insertVertex(0); @@ -2223,7 +2072,7 @@ QUnit.module('links', function(hooks) { assert.deepEqual(link.get('vertices'), [{ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 2 }]); assert.deepEqual(link.vertices(), [{ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 2 }]); - var link2 = new joint.dia.Link(); + var link2 = new joint.shapes.standard.Link(); error = null; try { link2.insertVertex(0); @@ -2243,7 +2092,7 @@ QUnit.module('links', function(hooks) { QUnit.module('removeVertex', function() { QUnit.test('sanity', function(assert) { - var link = new joint.dia.Link(); + var link = new joint.shapes.standard.Link(); link.vertices([{ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 2 }, { x: 3, y: 3 }]); link.removeVertex(100); assert.deepEqual(link.get('vertices'), [{ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 2 }, { x: 3, y: 3 }]); @@ -2255,7 +2104,7 @@ QUnit.module('links', function(hooks) { assert.deepEqual(link.get('vertices'), [{ x: 1, y: 1 }, { x: 2, y: 2 }]); assert.deepEqual(link.vertices(), [{ x: 1, y: 1 }, { x: 2, y: 2 }]); - var link2 = new joint.dia.Link(); + var link2 = new joint.shapes.standard.Link(); link2.vertices([{ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 2 }, { x: 3, y: 3 }]); link2.removeVertex(100); assert.deepEqual(link2.get('vertices'), [{ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 2 }, { x: 3, y: 3 }]); diff --git a/packages/joint-core/test/jointjs/paper.js b/packages/joint-core/test/jointjs/paper.js index 8b4f24187..290227934 100644 --- a/packages/joint-core/test/jointjs/paper.js +++ b/packages/joint-core/test/jointjs/paper.js @@ -185,7 +185,7 @@ QUnit.module('paper', function(hooks) { this.paper.options.sorting = joint.dia.Paper.sorting.EXACT; - var json = JSON.parse('{"cells":[{"type":"standard.Ellipse","size":{"width":100,"height":60},"position":{"x":110,"y":480},"id":"bbb9e641-9756-4f42-997a-f4818b89f374","embeds":"","z":0},{"type":"link","source":{"id":"bbb9e641-9756-4f42-997a-f4818b89f374"},"target":{"id":"cbd1109e-4d34-4023-91b0-f31bce1318e6"},"id":"b4289c08-07ea-49d2-8dde-e67eb2f2a06a","z":1},{"type":"standard.Rectangle","position":{"x":420,"y":410},"size":{"width":100,"height":60},"id":"cbd1109e-4d34-4023-91b0-f31bce1318e6","embeds":"","z":2}]}'); + var json = JSON.parse('{"cells":[{"type":"standard.Ellipse","size":{"width":100,"height":60},"position":{"x":110,"y":480},"id":"bbb9e641-9756-4f42-997a-f4818b89f374","embeds":"","z":0},{"type":"standard.Link","source":{"id":"bbb9e641-9756-4f42-997a-f4818b89f374"},"target":{"id":"cbd1109e-4d34-4023-91b0-f31bce1318e6"},"id":"b4289c08-07ea-49d2-8dde-e67eb2f2a06a","z":1},{"type":"standard.Rectangle","position":{"x":420,"y":410},"size":{"width":100,"height":60},"id":"cbd1109e-4d34-4023-91b0-f31bce1318e6","embeds":"","z":2}]}'); this.graph.fromJSON(json); @@ -317,7 +317,7 @@ QUnit.module('paper', function(hooks) { var customElementView = joint.dia.ElementView.extend({ custom: true }); var customLinkView = joint.dia.LinkView.extend({ custom: true }); var element = new joint.shapes.standard.Rectangle(); - var link = new joint.dia.Link(); + var link = new joint.shapes.standard.Link(); // Custom View via class @@ -385,7 +385,7 @@ QUnit.module('paper', function(hooks) { var customElementView = joint.dia.ElementView.extend({ custom: true }); var customLinkView = joint.dia.LinkView.extend({ custom: true }); var element = new joint.shapes.standard.Rectangle({ type: 'elements.Element' }); - var link = new joint.dia.Link({ type: 'links.Link' }); + var link = new joint.shapes.standard.Link({ type: 'links.Link' }); this.paper.options.cellViewNamespace = { elements: { ElementView: customElementView }, @@ -426,8 +426,8 @@ QUnit.module('paper', function(hooks) { position: { x: 400, y: 400 }, size: { width: 100, height: 100 } }); - var link = new joint.dia.Link({ id: 'link', source: { id: source.id }, target: { id: target.id }}); - var soloLink = new joint.dia.Link({ id: 'link2', source: { id: source.id }, target: { x: 300, y: 300 }}); + var link = new joint.shapes.standard.Link({ id: 'link', source: { id: source.id }, target: { id: target.id }}); + var soloLink = new joint.shapes.standard.Link({ id: 'link2', source: { id: source.id }, target: { x: 300, y: 300 }}); graphCells = [source, target, solo, link, soloLink]; this.graph.addCells(graphCells); @@ -443,12 +443,7 @@ QUnit.module('paper', function(hooks) { QUnit.test('disconnect from element', function(assert) { - var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]'); - - var data = {}; - connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 0, 0); - connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 0, 0); + simulate.dragLinkView(connectedLinkView, 'target', { targetEl: this.paper.el, x: 0, y: 0 }); assert.notOk(connectSpy.called); assert.ok(disconnectSpy.calledOnce); @@ -456,13 +451,9 @@ QUnit.module('paper', function(hooks) { QUnit.test('disconnect from element, connect to new one', function(assert) { - var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]'); var soloView = graphCells[2].findView(this.paper); - var data = {}; - connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - connectedLinkView.pointermove({ target: soloView.el, type: 'mousemove', data: data }, 450, 450); - connectedLinkView.pointerup({ target: soloView.el, type: 'mouseup', data: data }, 450, 450); + simulate.dragLinkView(connectedLinkView, 'target', { targetEl: soloView.el, x: 450, y: 450 }); assert.ok(connectSpy.calledOnce, 'connect to solo'); assert.ok(disconnectSpy.calledOnce, 'disconnect from source'); @@ -470,13 +461,9 @@ QUnit.module('paper', function(hooks) { QUnit.test('disconnect from element, connect to same one - nothing changed', function(assert) { - var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]'); var targetView = graphCells[1].findView(this.paper); - var data = {}; - connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - connectedLinkView.pointermove({ target: targetView.el, type: 'mousemove', data: data }, 450, 450); - connectedLinkView.pointerup({ target: targetView.el, type: 'mouseup', data: data }, 450, 150); + simulate.dragLinkView(connectedLinkView, 'target', { targetEl: targetView.el, x: 450, y: 150 }); assert.notOk(connectSpy.called, 'connect should not be called'); assert.notOk(disconnectSpy.called, 'disconnect should not be called'); @@ -488,12 +475,11 @@ QUnit.module('paper', function(hooks) { this.paper.options.snapLinks = true; - var arrowhead = soloLinkView.el.querySelector('.marker-arrowhead[end=target]'); var targetView = graphCells[1].findView(this.paper); var soloView = graphCells[2].findView(this.paper); var data = {}; - soloLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); + simulate.dragLinkView(soloLinkView, 'target', { data }); soloLinkView.pointermove({ target: soloView.el, type: 'mousemove', data: data }, 450, 450); soloLinkView.pointermove({ target: targetView.el, type: 'mousemove', data: data }, 450, 150); soloLinkView.pointerup({ target: targetView.el, type: 'mouseup', data: data }, 450, 450); @@ -508,12 +494,11 @@ QUnit.module('paper', function(hooks) { this.paper.options.validateConnection = validateConnectionSpy; this.paper.options.snapLinks = true; - var arrowhead = soloLinkView.el.querySelector('.marker-arrowhead[end=target]'); var targetView = graphCells[1].findView(this.paper); var soloView = graphCells[2].findView(this.paper); var data = {}; - soloLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); + simulate.dragLinkView(soloLinkView, 'target', { data }); assert.equal(validateConnectionSpy.callCount, 0); soloLinkView.pointermove({ target: soloView.el, type: 'mousemove', data: data }, 450, 450); assert.equal(validateConnectionSpy.callCount, 1); @@ -532,12 +517,7 @@ QUnit.module('paper', function(hooks) { this.paper.options.linkPinning = true; - var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]'); - - var data = {}; - connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50); - connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50); + simulate.dragLinkView(connectedLinkView, 'target', { targetEl: this.paper.el, x: 50, y: 50 }); assert.ok(disconnectSpy.called); assert.notOk(connectSpy.called); @@ -547,12 +527,7 @@ QUnit.module('paper', function(hooks) { this.paper.options.linkPinning = true; - var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]'); - - var data = {}; - connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50); - connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50); + simulate.dragLinkView(connectedLinkView, 'target', { targetEl: this.paper.el, x: 50, y: 50 }); assert.ok(disconnectSpy.called); assert.notOk(connectSpy.called); @@ -562,12 +537,7 @@ QUnit.module('paper', function(hooks) { this.paper.options.linkPinning = false; - var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]'); - - var data = {}; - connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50); - connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50); + simulate.dragLinkView(connectedLinkView, 'target', { targetEl: this.paper.el, x: 50, y: 50 }); assert.notOk(disconnectSpy.called, 'message'); assert.notOk(connectSpy.called, 'message'); @@ -582,13 +552,7 @@ QUnit.module('paper', function(hooks) { var allowLinkSpy = sinon.spy(); this.paper.options.allowLink = allowLinkSpy; - - var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]'); - - var data = {}; - connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50); - connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50); + simulate.dragLinkView(connectedLinkView, 'target', { targetEl: this.paper.el, x: 50, y: 50 }); assert.ok(allowLinkSpy.calledOnce); assert.ok(allowLinkSpy.calledWith(connectedLinkView, connectedLinkView.paper)); @@ -599,12 +563,7 @@ QUnit.module('paper', function(hooks) { this.paper.options.allowLink = function() { return false; }; - var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]'); - - var data = {}; - connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50); - connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50); + simulate.dragLinkView(connectedLinkView, 'target', { targetEl: this.paper.el, x: 50, y: 50 }); assert.notOk(disconnectSpy.called); }); @@ -613,12 +572,7 @@ QUnit.module('paper', function(hooks) { this.paper.options.allowLink = function() { return true; }; - var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]'); - - var data = {}; - connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50); - connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50); + simulate.dragLinkView(connectedLinkView, 'target', { targetEl: this.paper.el, x: 50, y: 50 }); assert.ok(disconnectSpy.called); }); @@ -627,14 +581,9 @@ QUnit.module('paper', function(hooks) { this.paper.options.allowLink = null; - var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]'); + simulate.dragLinkView(connectedLinkView, 'target', { targetEl: this.paper.el, x: 50, y: 50 }); - var data = {}; - connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50); - connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50); - - assert.ok(disconnectSpy.called, 'message'); + assert.ok(disconnectSpy.called); }); }); @@ -648,7 +597,7 @@ QUnit.module('paper', function(hooks) { var link; hooks.beforeEach(function() { - link = new joint.dia.Link(); + link = new joint.shapes.standard.Link(); element = new joint.shapes.standard.Rectangle({ position: { x: 500, y: 250 }, size: { width: 100, height: 100 }, @@ -684,7 +633,6 @@ QUnit.module('paper', function(hooks) { var paper = this.paper; var linkView = link.findView(paper); var elementView = element.findView(paper); - var arrowhead = linkView.el.querySelector('.marker-arrowhead[end=' + end + ']'); var ports = element.getPortsPositions('in'); var position = element.position(); var in1PortEl = elementView.el.querySelector('[port="in1"]'); @@ -692,7 +640,7 @@ QUnit.module('paper', function(hooks) { var x, y, evt; var data = {}; - linkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); + simulate.dragLinkView(linkView, end, { data }); // Connect to IN1 x = position.x + ports.in1.x; y = position.y + ports.in1.y; @@ -770,17 +718,13 @@ QUnit.module('paper', function(hooks) { QUnit.test('connect to port', function(assert) { - var link = new joint.dia.Link({ id: 'link' }); + var link = new joint.shapes.standard.Link({ id: 'link' }); this.graph.addCells([this.modelWithPorts, link]); var linkView = link.findView(this.paper); - var arrowhead = linkView.el.querySelector('.marker-arrowhead[end=source]'); var port = this.paper.findViewByModel(this.modelWithPorts).el.querySelector('[port="in1"]'); - var data = {}; - linkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - linkView.pointermove({ target: port, type: 'mousemove', data: data }, 0, 0); - linkView.pointerup({ target: port, type: 'mouseup', data: data }, 0, 0); + simulate.dragLinkView(linkView, 'source', { targetEl: port }); assert.ok(connectSpy.calledOnce); assert.notOk(disconnectSpy.called); @@ -788,17 +732,13 @@ QUnit.module('paper', function(hooks) { QUnit.test('reconnect port', function(assert) { - var link = new joint.dia.Link({ id: 'link', source: { id: this.modelWithPorts, port: 'in1' }}); + var link = new joint.shapes.standard.Link({ id: 'link', source: { id: this.modelWithPorts, port: 'in1' }}); this.graph.addCells([this.modelWithPorts, link]); var linkView = link.findView(this.paper); - var arrowhead = linkView.el.querySelector('.marker-arrowhead[end=source]'); var portElement = this.paper.findViewByModel(this.modelWithPorts).el.querySelector('[port="in2"]'); - var data = {}; - linkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - linkView.pointermove({ target: portElement, type: 'mousemove', data: data }, 0, 0); - linkView.pointerup({ target: portElement, type: 'mouseup', data: data }, 0, 0); + simulate.dragLinkView(linkView, 'source', { targetEl: portElement }); assert.ok(connectSpy.calledOnce); assert.ok(disconnectSpy.calledOnce); @@ -976,7 +916,7 @@ QUnit.module('paper', function(hooks) { position: { x: 400, y: 100 }, size: { width: 100, height: 100 } }); - var link = new joint.dia.Link({ id: 'link', source: { id: source.id }, target: { id: target.id }}); + var link = new joint.shapes.standard.Link({ id: 'link', source: { id: source.id }, target: { id: target.id }}); var newLink; // to be created. this.graph.addCells([source, target, link]); @@ -985,21 +925,13 @@ QUnit.module('paper', function(hooks) { var sourceView = source.findView(this.paper); var targetView = target.findView(this.paper); - var arrowhead = linkView.el.querySelector('.marker-arrowhead[end=target]'); - this.paper.options.linkPinning = false; - data = {}; - linkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - linkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50); - linkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50); + simulate.dragLinkView(linkView, 'target', { targetEl: this.paper.el, x: 50, y: 50 }); assert.deepEqual(link.get('target'), { id: target.id }, 'pinning disabled: when the arrowhead is dragged&dropped to the blank paper area, the arrowhead is return to its original position.'); this.paper.options.linkPinning = true; - data = {}; - linkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - linkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50); - linkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50); + simulate.dragLinkView(linkView, 'target', { targetEl: this.paper.el, x: 50, y: 50 }); assert.deepEqual(link.get('target'), { x: 50, @@ -1007,10 +939,7 @@ QUnit.module('paper', function(hooks) { }, 'pinning enabled: when the arrowhead is dragged&dropped to the blank paper area, the arrowhead is set to a point.'); this.paper.options.linkPinning = false; - data = {}; - linkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0); - linkView.pointermove({ target: targetView.el, type: 'mousemove', data: data }, 450, 150); - linkView.pointerup({ target: targetView.el, type: 'mouseup', data: data }, 450, 150); + simulate.dragLinkView(linkView, 'target', { targetEl: targetView.el, x: 450, y: 150 }); assert.deepEqual(link.get('target'), { id: 'target' }, 'pinning disabled: it\'s still possible to connect link to elements.'); @@ -1021,19 +950,14 @@ QUnit.module('paper', function(hooks) { sourceView.pointermove({ type: 'mousemove', data: data }, 150, 400); newLink = _.reject(this.graph.getLinks(), { id: 'link' })[0]; - if (newLink) { - assert.deepEqual(newLink.get('target'), { - x: 150, - y: 400 - }, 'pinning enabled: when there was a link created from a magnet a dropped into the blank paper area, the link target is set to a point.'); - newLink.remove(); - } + assert.deepEqual(newLink.get('target'), { + x: 150, + y: 400 + }, 'pinning enabled: when there was a link created from a magnet a dropped into the blank paper area, the link target is set to a point.'); + newLink.remove(); this.paper.options.linkPinning = false; - data = {}; - sourceView.pointerdown({ target: sourceView.el, type: 'mousedown', data: data }, 150, 150); - sourceView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 150, 400); - sourceView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 150, 400); + simulate.dragLinkView(linkView, 'target', { targetEl: this.paper.el, x: 150, y: 400 }); newLink = _.reject(this.graph.getLinks(), { id: 'link' })[0]; assert.notOk(newLink, 'pinning disabled: when there was a link created from a magnet a dropped into the blank paper area, the link was removed after the drop.'); @@ -1354,7 +1278,7 @@ QUnit.module('paper', function(hooks) { this.paper.options.multiLinks = true; this.paper.options.linkPinning = true; - var link = new joint.dia.Link({ + var link = new joint.shapes.standard.Link({ source: { x: 300, y: 300 }, target: { x: 320, y: 320 } }); @@ -1365,7 +1289,7 @@ QUnit.module('paper', function(hooks) { assert.ok(this.paper.linkAllowed(linkView), 'can use link view'); - var pinnedLink = new joint.dia.Link({ + var pinnedLink = new joint.shapes.standard.Link({ source: { id: rect1.id }, target: { x: 200, y: 200 } }); @@ -1380,12 +1304,12 @@ QUnit.module('paper', function(hooks) { this.paper.options.linkPinning = true; assert.ok(this.paper.linkAllowed(pinnedLinkView), 'pinned link allowed when link pinning is enabled'); - var multiLink1 = new joint.dia.Link({ + var multiLink1 = new joint.shapes.standard.Link({ source: { id: rect1.id }, target: { id: rect2.id } }); - var multiLink2 = new joint.dia.Link({ + var multiLink2 = new joint.shapes.standard.Link({ source: { id: rect1.id }, target: { id: rect2.id } }); @@ -1782,7 +1706,7 @@ QUnit.module('paper', function(hooks) { this.graph.addCell(r1); this.graph.addCell(r2); - new joint.dia.Link() + new joint.shapes.standard.Link() .set({ source: { id: r1.id }, target: { id: r2.id } diff --git a/packages/joint-core/test/jointjs/routers.js b/packages/joint-core/test/jointjs/routers.js index d7c37cbbe..88d626de7 100644 --- a/packages/joint-core/test/jointjs/routers.js +++ b/packages/joint-core/test/jointjs/routers.js @@ -27,7 +27,7 @@ QUnit.module('routers', function(hooks) { this.graph.addCell([r1, r2]); - var l0 = new joint.dia.Link({ + var l0 = new joint.shapes.standard.Link({ source: { id: r1.id }, target: { id: r2.id }, router: { name: 'non-existing' } @@ -62,7 +62,7 @@ QUnit.module('routers', function(hooks) { var r1 = new joint.shapes.standard.Rectangle({ position: { x: 200, y: 60 }, size: { width: 50, height: 30 }}); var r2 = new joint.shapes.standard.Rectangle({ position: { x: 125, y: 60 }, size: { width: 50, height: 30 }}); - var link = new joint.dia.Link({ + var link = new joint.shapes.standard.Link({ source: { id: r1.id }, target: { id: r2.id }, router: { name: 'normal' }, @@ -72,7 +72,7 @@ QUnit.module('routers', function(hooks) { this.graph.addCells([r1, r2, link]); var linkView = this.paper.findViewByModel(link); - var pathData = linkView.$('.connection').attr('d'); + var pathData = linkView.findNode('line').getAttribute('d'); assert.checkDataPath(pathData, 'M 216 90 L 150 200 L 150 90', 'link was correctly routed'); }); @@ -84,7 +84,7 @@ QUnit.module('routers', function(hooks) { var r1 = new joint.shapes.standard.Rectangle({ position: { x: 200, y: 60 }, size: { width: 50, height: 30 }}); var r2 = new joint.shapes.standard.Rectangle({ position: { x: 125, y: 60 }, size: { width: 50, height: 30 }}); - var l1 = new joint.dia.Link({ + var l1 = new joint.shapes.standard.Link({ source: { id: r1.id }, target: { id: r2.id }, router: { name: 'orthogonal' }, @@ -94,7 +94,7 @@ QUnit.module('routers', function(hooks) { this.graph.addCells([r1, r2, l1]); var l1View = this.paper.findViewByModel(l1); - var l1PathData = l1View.$('.connection').attr('d'); + var l1PathData = l1View.findNode('line').getAttribute('d'); assert.checkDataPath(l1PathData, 'M 225 90 L 225 200 L 150 200 L 150 90', 'link with one vertex was correctly routed'); @@ -103,7 +103,7 @@ QUnit.module('routers', function(hooks) { var r3 = new joint.shapes.standard.Rectangle({ position: { x: 40, y: 40 }, size: { width: 50, height: 30 }}); var r4 = new joint.shapes.standard.Rectangle({ position: { x: 220, y: 120 }, size: { width: 50, height: 30 }}); - var l2 = new joint.dia.Link({ + var l2 = new joint.shapes.standard.Link({ source: { id: r3.id }, target: { id: r4.id }, router: { name: 'orthogonal' }, @@ -112,7 +112,7 @@ QUnit.module('routers', function(hooks) { this.graph.addCells([r3, r4, l2]); var l2View = this.paper.findViewByModel(l2); - var l2PathData = l2View.$('.connection').attr('d'); + var l2PathData = l2View.findNode('line').getAttribute('d'); assert.checkDataPath(l2PathData, 'M 90 55 L 245 55 L 245 120', 'link with no vertex was correctly routed'); @@ -121,7 +121,7 @@ QUnit.module('routers', function(hooks) { var r5 = new joint.shapes.standard.Rectangle({ position: { x: 200, y: 60 }, size: { width: 50, height: 30 }}); var r6 = new joint.shapes.standard.Rectangle({ position: { x: 350, y: 40 }, size: { width: 50, height: 30 }}); - var l3 = new joint.dia.Link({ + var l3 = new joint.shapes.standard.Link({ source: { id: r5.id }, target: { id: r6.id }, router: { name: 'orthogonal' }, @@ -131,7 +131,7 @@ QUnit.module('routers', function(hooks) { this.graph.addCells([r5, r6, l3]); var l3View = this.paper.findViewByModel(l3); - var l3PathData = l3View.$('.connection').attr('d'); + var l3PathData = l3View.findNode('line').getAttribute('d'); assert.checkDataPath(l3PathData, 'M 225 90 L 225 200 L 150 200 L 150 55 L 350 55', 'no spike (a return path segment) was created'); }); @@ -143,7 +143,7 @@ QUnit.module('routers', function(hooks) { var r3 = r2.clone().translate(300); - var l0 = new joint.dia.Link({ + var l0 = new joint.shapes.standard.Link({ source: { id: r1.id }, target: { id: r3.id }, router: { @@ -158,20 +158,20 @@ QUnit.module('routers', function(hooks) { var v0 = this.paper.findViewByModel(l0); - var d = v0.$('.connection').attr('d'); + var d = v0.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 140 70 L 300 70 L 300 10 L 600 10 L 600 70 L 620 70', 'Route avoids an obstacle.'); r1.translate(0, 50); - d = v0.$('.connection').attr('d'); + d = v0.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 140 120 L 600 120 L 600 70 L 620 70', 'Source has been moved. Route recalculated starting from target.'); r3.translate(0, -50); - d = v0.$('.connection').attr('d'); + d = v0.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 140 120 L 600 120 L 600 20 L 620 20', 'Target has been moved. Route recalculated starting from source.'); @@ -187,7 +187,7 @@ QUnit.module('routers', function(hooks) { } }); - d = v0.$('.connection').attr('d'); + d = v0.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 140 120 L 280 120 L 280 0 L 580 0 L 580 20 L 620 20', 'The option paddingBox was passed. The source and target element and obstacles are avoided taken this padding in account.'); @@ -214,7 +214,7 @@ QUnit.module('routers', function(hooks) { } }); - d = v0.$('.connection').attr('d'); + d = v0.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 140 120 L 680 120 L 680 60', 'The default fallback router made an orthogonal link.'); @@ -230,7 +230,7 @@ QUnit.module('routers', function(hooks) { } }); - d = v0.$('.connection').attr('d'); + d = v0.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 80 80 L 80 20 L 20 20 L 20 0 L 600 0 L 600 20 L 620 20', 'A vertex was added. Route correctly recalculated.'); @@ -239,7 +239,7 @@ QUnit.module('routers', function(hooks) { vertices: [{ x: 21, y: 21 }] }); - d = v0.$('.connection').attr('d'); + d = v0.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 80 80 L 80 21 L 21 21 L 21 20 L 620 20', 'A vertex was moved (not snapped to the grid now). Route correctly recalculated.'); @@ -274,7 +274,7 @@ QUnit.module('routers', function(hooks) { r1.translate(0, -50); r3.translate(0, 50); - d = v0.$('.connection').attr('d'); + d = v0.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 140 70 L 620 70', 'Set excludeTypes parameter to "standard.Rectangle" makes routing ignore those shapes.'); @@ -293,7 +293,7 @@ QUnit.module('routers', function(hooks) { } }); - d = v0.$('.connection').attr('d'); + d = v0.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 140 70 L 800 70 L 800 80 L 760 80 L 760 70 L 740 70', 'Set excludeEnds parameter to "target" makes routing ignore target element.'); @@ -311,7 +311,7 @@ QUnit.module('routers', function(hooks) { } }); - d = v0.$('.connection').attr('d'); + d = v0.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 20 70 L 0 70 L 0 10 L 760 10 L 760 70 L 740 70', 'Set startDirections & endDirections parameters makes routing starts and ends from/to the given direction.'); @@ -329,7 +329,7 @@ QUnit.module('routers', function(hooks) { } }); - d = v0.$('.connection').attr('d'); + d = v0.findNode('line').getAttribute('d'); assert.ok(spyIsPointObstacle.called); assert.ok(spyIsPointObstacle.alwaysCalledWithExactly(sinon.match.instanceOf(g.Point))); @@ -344,7 +344,7 @@ QUnit.module('routers', function(hooks) { var r3 = r2.clone().translate(300, 300); - var l0 = new joint.dia.Link({ + var l0 = new joint.shapes.standard.Link({ source: { id: r1.id }, target: { id: r3.id }, router: { @@ -360,7 +360,7 @@ QUnit.module('routers', function(hooks) { var v0 = this.paper.findViewByModel(l0); - var d = v0.$('.connection').attr('d'); + var d = v0.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 140 70 L 160 70 L 400 310 L 440 310 L 680 550 L 680 630', 'Route avoids an obstacle.'); @@ -372,7 +372,7 @@ QUnit.module('routers', function(hooks) { } }); - d = v0.$('.connection').attr('d'); + d = v0.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 80 70 L 81 70 L 680 670 L 680 670', 'The default fallback router made a metro link.'); @@ -383,7 +383,7 @@ QUnit.module('routers', function(hooks) { var r1 = new joint.shapes.standard.Rectangle({ position: { x: 20, y: 30 }, size: { width: 120, height: 80 }}); var r2 = r1.clone().translate(300, 300); - var l = new joint.dia.Link({ source: { id: r1.id }, target: { id: r2.id }}); + var l = new joint.shapes.standard.Link({ source: { id: r1.id }, target: { id: r2.id }}); this.graph.addCell([r1, r2, l]); @@ -391,27 +391,27 @@ QUnit.module('routers', function(hooks) { // Left side l.set('router', { name: 'oneSide', args: { padding: 20, side: 'left' }}); - var d = v.$('.connection').attr('d'); + var d = v.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 20 70 L 0 70 L 0 370 L 320 370', 'Route goes only on the left side.'); // Padding option l.set('router', { name: 'oneSide', args: { padding: 40, side: 'left' }}); - d = v.$('.connection').attr('d'); + d = v.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 20 70 L -20 70 L -20 370 L 320 370', 'Route respects the padding.'); // Right side l.set('router', { name: 'oneSide', args: { padding: 40, side: 'right' }}); - d = v.$('.connection').attr('d'); + d = v.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 140 70 L 480 70 L 480 370 L 440 370', 'Route goes only on the right side.'); // Top side l.set('router', { name: 'oneSide', args: { padding: 40, side: 'top' }}); - d = v.$('.connection').attr('d'); + d = v.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 80 30 L 80 -10 L 380 -10 L 380 330', 'Route goes only on the top.'); // Bottom side l.set('router', { name: 'oneSide', args: { padding: 40, side: 'bottom' }}); - d = v.$('.connection').attr('d'); + d = v.findNode('line').getAttribute('d'); assert.checkDataPath(d, 'M 80 110 L 80 450 L 380 450 L 380 410', 'Route goes only on the bottom'); // Wrong side specified @@ -424,7 +424,7 @@ QUnit.module('routers', function(hooks) { var r1 = new joint.shapes.standard.Rectangle({ position: { x: 20, y: 30 }, size: { width: 120, height: 80 }}); var r2 = r1.clone().translate(300, 300); - var l = new joint.dia.Link({ source: { id: r1.id }, target: { id: r2.id }}); + var l = new joint.shapes.standard.Link({ source: { id: r1.id }, target: { id: r2.id }}); this.graph.addCell([r1, r2, l]); diff --git a/packages/joint-core/test/utils.js b/packages/joint-core/test/utils.js index e001c21ab..650b26007 100644 --- a/packages/joint-core/test/utils.js +++ b/packages/joint-core/test/utils.js @@ -300,7 +300,37 @@ window.simulate = { touchend: function(touchInit) { return this.touchevent({ ...touchInit, type: 'touchend' }); + }, + + dragLinkView: function(linkView, endType, options = {}) { + const { + targetEl, + x = 0 , + y = 0, + data: userData + } = options; + const data = userData || {}; + const mousedownEvent = { + // Dummy event target determines the arrowhead to move. + target: V('circle', { r: 10, end: endType }).node, + type: 'mousedown', + data: data + }; + linkView.pointerdown(mousedownEvent, 0, 0); + linkView.dragArrowheadStart(mousedownEvent); + if (!targetEl) return; + linkView.pointermove({ + target: targetEl, + type: 'mousemove', + data: data + }, x, y); + linkView.pointerup({ + target: targetEl, + type: 'mouseup', + data: data + }, x, y); } + }; window.fixtures = { diff --git a/packages/joint-core/types/joint.d.ts b/packages/joint-core/types/joint.d.ts index b5e94b08c..33fbc3f05 100644 --- a/packages/joint-core/types/joint.d.ts +++ b/packages/joint-core/types/joint.d.ts @@ -1098,32 +1098,20 @@ export namespace dia { protected onlabel(evt: dia.Event, x: number, y: number): void; - protected dragConnectionStart(evt: dia.Event, x: number, y: number): void; - protected dragLabelStart(evt: dia.Event, x: number, y: number): void; - protected dragVertexStart(evt: dia.Event, x: number, y: number): void; - protected dragArrowheadStart(evt: dia.Event, x: number, y: number): void; protected dragStart(evt: dia.Event, x: number, y: number): void; - protected dragConnection(evt: dia.Event, x: number, y: number): void; - protected dragLabel(evt: dia.Event, x: number, y: number): void; - protected dragVertex(evt: dia.Event, x: number, y: number): void; - protected dragArrowhead(evt: dia.Event, x: number, y: number): void; protected drag(evt: dia.Event, x: number, y: number): void; - protected dragConnectionEnd(evt: dia.Event, x: number, y: number): void; - protected dragLabelEnd(evt: dia.Event, x: number, y: number): void; - protected dragVertexEnd(evt: dia.Event, x: number, y: number): void; - protected dragArrowheadEnd(evt: dia.Event, x: number, y: number): void; protected dragEnd(evt: dia.Event, x: number, y: number): void; @@ -4186,6 +4174,8 @@ export namespace linkTools { snapRadius?: number; redundancyRemoval?: boolean; vertexAdding?: boolean; + vertexRemoving?: boolean; + vertexMoving?: boolean; stopPropagation?: boolean; scale?: number; }