From ca06b4e5dd8221644441f41698cfd2bd1c36cd6b Mon Sep 17 00:00:00 2001 From: Roman Bruckner Date: Tue, 29 Oct 2024 17:32:48 +0100 Subject: [PATCH] feat(dia.Paper): add methods to find cell/element/link views in paper (#2781) --- .../demo/archive/joint.dia.LegacyLinkView.js | 2 +- packages/joint-core/demo/chess/src/chess.js | 2 +- packages/joint-core/src/dia/CellView.mjs | 22 +- packages/joint-core/src/dia/ElementView.mjs | 4 +- packages/joint-core/src/dia/Graph.mjs | 113 +++- packages/joint-core/src/dia/LinkView.mjs | 36 +- packages/joint-core/src/dia/Paper.mjs | 84 +++ packages/joint-core/src/g/rect.mjs | 18 +- packages/joint-core/test/geometry/rect.js | 30 +- packages/joint-core/test/jointjs/graph.js | 38 +- packages/joint-core/test/jointjs/paper.js | 496 +++++++++++++++++- packages/joint-core/types/geometry.d.ts | 7 +- packages/joint-core/types/joint.d.ts | 140 ++++- 13 files changed, 904 insertions(+), 88 deletions(-) diff --git a/packages/joint-core/demo/archive/joint.dia.LegacyLinkView.js b/packages/joint-core/demo/archive/joint.dia.LegacyLinkView.js index 15fbdad45..a6db15986 100644 --- a/packages/joint-core/demo/archive/joint.dia.LegacyLinkView.js +++ b/packages/joint-core/demo/archive/joint.dia.LegacyLinkView.js @@ -2242,7 +2242,7 @@ // 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 viewsInArea = paper.findElementViewsInArea({ x: x - r, y: y - r, width: 2 * r, height: 2 * r }); var prevClosestView = data.closestView || null; var prevClosestMagnet = data.closestMagnet || null; diff --git a/packages/joint-core/demo/chess/src/chess.js b/packages/joint-core/demo/chess/src/chess.js index 4b41190bc..42cb5ba33 100644 --- a/packages/joint-core/demo/chess/src/chess.js +++ b/packages/joint-core/demo/chess/src/chess.js @@ -290,7 +290,7 @@ const Board = joint.dia.Paper.extend({ at: function(square) { - return this.model.findModelsFromPoint(this._mid(this._n2p(square))); + return this.model.findElementsAtPoint(this._mid(this._n2p(square))); }, addPiece: function(piece, square) { diff --git a/packages/joint-core/src/dia/CellView.mjs b/packages/joint-core/src/dia/CellView.mjs index b698b5c0d..8f53bbd97 100644 --- a/packages/joint-core/src/dia/CellView.mjs +++ b/packages/joint-core/src/dia/CellView.mjs @@ -16,7 +16,7 @@ import { merge, uniq } from '../util/index.mjs'; -import { Point, Rect } from '../g/index.mjs'; +import { Point, Rect, intersection } from '../g/index.mjs'; import V from '../V/index.mjs'; import $ from '../mvc/Dom/index.mjs'; import { HighlighterView } from './HighlighterView.mjs'; @@ -1326,7 +1326,27 @@ export const CellView = View.extend({ setInteractivity: function(value) { this.options.interactive = value; + }, + + isIntersecting: function(geometryShape, geometryData) { + return intersection.exists(geometryShape, this.getNodeBBox(this.el), geometryData); + }, + + isEnclosedIn: function(geometryRect) { + return geometryRect.containsRect(this.getNodeBBox(this.el)); + }, + + isInArea: function(geometryRect, options = {}) { + if (options.strict) { + return this.isEnclosedIn(geometryRect); + } + return this.isIntersecting(geometryRect); + }, + + isAtPoint: function(point, options) { + return this.getNodeBBox(this.el).containsPoint(point, options); } + }, { Flags, diff --git a/packages/joint-core/src/dia/ElementView.mjs b/packages/joint-core/src/dia/ElementView.mjs index bec0d5de0..e86390054 100644 --- a/packages/joint-core/src/dia/ElementView.mjs +++ b/packages/joint-core/src/dia/ElementView.mjs @@ -404,9 +404,9 @@ export const ElementView = CellView.extend({ if (isFunction(findParentBy)) { candidates = toArray(findParentBy.call(graph, this, evt, x, y)); } else if (findParentBy === 'pointer') { - candidates = toArray(graph.findModelsFromPoint({ x, y })); + candidates = graph.findElementsAtPoint({ x, y }); } else { - candidates = graph.findModelsUnderElement(model, { searchBy: findParentBy }); + candidates = graph.findElementsUnderElement(model, { searchBy: findParentBy }); } candidates = candidates.filter((el) => { diff --git a/packages/joint-core/src/dia/Graph.mjs b/packages/joint-core/src/dia/Graph.mjs index bc32ff251..41615e594 100644 --- a/packages/joint-core/src/dia/Graph.mjs +++ b/packages/joint-core/src/dia/Graph.mjs @@ -992,28 +992,105 @@ export const Graph = Model.extend({ util.invoke(this.getConnectedLinks(model), 'remove', opt); }, - // Find all elements at given point - findModelsFromPoint: function(p) { - return this.getElements().filter(el => el.getBBox({ rotate: true }).containsPoint(p)); + // Find all cells at given point + + findElementsAtPoint: function(point, opt) { + return this._filterAtPoint(this.getElements(), point, opt); + }, + + findLinksAtPoint: function(point, opt) { + return this._filterAtPoint(this.getLinks(), point, opt); + }, + + findCellsAtPoint: function(point, opt) { + return this._filterAtPoint(this.getCells(), point, opt); + }, + + _filterAtPoint: function(cells, point, opt = {}) { + return cells.filter(el => el.getBBox({ rotate: true }).containsPoint(point, opt)); + }, + + // Find all cells in given area + + findElementsInArea: function(area, opt = {}) { + return this._filterInArea(this.getElements(), area, opt); }, - // Find all elements in given area - findModelsInArea: function(rect, opt = {}) { - const r = new g.Rect(rect); + findLinksInArea: function(area, opt = {}) { + return this._filterInArea(this.getLinks(), area, opt); + }, + + findCellsInArea: function(area, opt = {}) { + return this._filterInArea(this.getCells(), area, opt); + }, + + _filterInArea: function(cells, area, opt = {}) { + const r = new g.Rect(area); const { strict = false } = opt; const method = strict ? 'containsRect' : 'intersect'; - return this.getElements().filter(el => r[method](el.getBBox({ rotate: true }))); - }, - - // Find all elements under the given element. - findModelsUnderElement: function(element, opt = {}) { - const { searchBy = 'bbox' } = opt; - const bbox = element.getBBox().rotateAroundCenter(element.angle()); - const elements = (searchBy === 'bbox') - ? this.findModelsInArea(bbox) - : this.findModelsFromPoint(util.getRectPoint(bbox, searchBy)); - // don't account element itself or any of its descendants - return elements.filter(el => element.id !== el.id && !el.isEmbeddedIn(element)); + return cells.filter(el => r[method](el.getBBox({ rotate: true }))); + }, + + // Find all cells under the given element. + + findElementsUnderElement: function(element, opt) { + return this._filterCellsUnderElement(this.getElements(), element, opt); + }, + + findLinksUnderElement: function(element, opt) { + return this._filterCellsUnderElement(this.getLinks(), element, opt); + }, + + findCellsUnderElement: function(element, opt) { + return this._filterCellsUnderElement(this.getCells(), element, opt); + }, + + _isValidElementUnderElement: function(el1, el2) { + return el1.id !== el2.id && !el1.isEmbeddedIn(el2); + }, + + _isValidLinkUnderElement: function(link, el) { + return ( + link.source().id !== el.id && + link.target().id !== el.id && + !link.isEmbeddedIn(el) + ); + }, + + _validateCellsUnderElement: function(cells, element) { + return cells.filter(cell => { + return cell.isLink() + ? this._isValidLinkUnderElement(cell, element) + : this._isValidElementUnderElement(cell, element); + }); + }, + + _getFindUnderElementGeometry: function(element, searchBy = 'bbox') { + const bbox = element.getBBox({ rotate: true }); + return (searchBy !== 'bbox') ? util.getRectPoint(bbox, searchBy) : bbox; + }, + + _filterCellsUnderElement: function(cells, element, opt = {}) { + const geometry = this._getFindUnderElementGeometry(element, opt.searchBy); + const filteredCells = (geometry.type === g.types.Point) + ? this._filterAtPoint(cells, geometry) + : this._filterInArea(cells, geometry, opt); + return this._validateCellsUnderElement(filteredCells, element); + }, + + // @deprecated use `findElementsInArea` instead + findModelsInArea: function(area, opt) { + return this.findElementsInArea(area, opt); + }, + + // @deprecated use `findElementsAtPoint` instead + findModelsFromPoint: function(point) { + return this.findElementsAtPoint(point); + }, + + // @deprecated use `findModelsUnderElement` instead + findModelsUnderElement: function(element, opt) { + return this.findElementsUnderElement(element, opt); }, // Return bounding box of all elements. diff --git a/packages/joint-core/src/dia/LinkView.mjs b/packages/joint-core/src/dia/LinkView.mjs index 7e3a923ef..c1976bf80 100644 --- a/packages/joint-core/src/dia/LinkView.mjs +++ b/packages/joint-core/src/dia/LinkView.mjs @@ -2,7 +2,7 @@ import { CellView } from './CellView.mjs'; import { Link } from './Link.mjs'; import V from '../V/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 { Point, Line, Path, normalizeAngle, Rect, Polyline, intersection } from '../g/index.mjs'; import * as routers from '../routers/index.mjs'; import * as connectors from '../connectors/index.mjs'; import { env } from '../env/index.mjs'; @@ -73,6 +73,7 @@ export const LinkView = CellView.extend({ initFlag: [Flags.RENDER, Flags.SOURCE, Flags.TARGET, Flags.TOOLS], UPDATE_PRIORITY: 1, + EPSILON: 1e-6, confirmUpdate: function(flags, opt) { @@ -823,6 +824,34 @@ export const LinkView = CellView.extend({ return connectionPoint.round(this.decimalsRounding); }, + isIntersecting: function(geometryShape, geometryData) { + const connection = this.getConnection(); + if (!connection) return false; + return intersection.exists( + geometryShape, + connection, + geometryData, + { segmentSubdivisions: this.getConnectionSubdivisions() }, + ); + }, + + isEnclosedIn: function(geometryRect) { + const connection = this.getConnection(); + if (!connection) return false; + const bbox = connection.bbox(); + if (!bbox) return false; + return geometryRect.containsRect(bbox); + }, + + isAtPoint: function(point /*, options */) { + // Note: `strict` option is not applicable for links. + // There is currently no method to determine if a path contains a point. + const area = new Rect(point); + // Intersection with a zero-size area is not possible. + area.inflate(this.EPSILON); + return this.isIntersecting(area); + }, + // combine default label position with built-in default label position _getDefaultLabelPositionProperty: function() { @@ -1830,7 +1859,10 @@ export const LinkView = CellView.extend({ // 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 viewsInArea = paper.findElementViewsInArea( + { x: x - r, y: y - r, width: 2 * r, height: 2 * r }, + snapLinks.findInAreaOptions + ); var prevClosestView = data.closestView || null; var prevClosestMagnet = data.closestMagnet || null; diff --git a/packages/joint-core/src/dia/Paper.mjs b/packages/joint-core/src/dia/Paper.mjs index eb4ce76c7..d03bbaf7a 100644 --- a/packages/joint-core/src/dia/Paper.mjs +++ b/packages/joint-core/src/dia/Paper.mjs @@ -382,6 +382,11 @@ export const Paper = View.extend({ ], MIN_SCALE: 1e-6, + // Default find buffer for the findViewsInArea and findViewsAtPoint methods. + // The find buffer is used to extend the area of the search + // to mitigate the differences between the model and view geometry. + DEFAULT_FIND_BUFFER: 200, + init: function() { const { options } = this; @@ -1858,6 +1863,85 @@ export const Paper = View.extend({ }, this); }, + findElementViewsInArea(plainArea, opt) { + return this._filterViewsInArea( + plainArea, + (extArea, findOpt) => this.model.findElementsInArea(extArea, findOpt), + opt + ); + }, + + findLinkViewsInArea: function(plainArea, opt) { + return this._filterViewsInArea( + plainArea, + (extArea, findOpt) => this.model.findLinksInArea(extArea, findOpt), + opt + ); + }, + + findCellViewsInArea: function(plainArea, opt) { + return this._filterViewsInArea( + plainArea, + (extArea, findOpt) => this.model.findCellsInArea(extArea, findOpt), + opt + ); + }, + + findElementViewsAtPoint: function(plainPoint, opt) { + return this._filterViewsAtPoint( + plainPoint, + (extArea) => this.model.findElementsInArea(extArea), + opt + ); + }, + + findLinkViewsAtPoint: function(plainPoint, opt) { + return this._filterViewsAtPoint( + plainPoint, + (extArea) => this.model.findLinksInArea(extArea), + opt, + ); + }, + + findCellViewsAtPoint: function(plainPoint, opt) { + return this._filterViewsAtPoint( + plainPoint, + // Note: we do not want to pass `opt` to `findCellsInArea` + // because the `strict` option works differently for querying at a point + (extArea) => this.model.findCellsInArea(extArea), + opt + ); + }, + + _findInExtendedArea: function(area, findCellsFn, opt = {}) { + const { + buffer = this.DEFAULT_FIND_BUFFER, + } = opt; + const extendedArea = (new Rect(area)).inflate(buffer); + const cellsInExtendedArea = findCellsFn(extendedArea, opt); + return cellsInExtendedArea.map(element => this.findViewByModel(element)); + }, + + _filterViewsInArea: function(plainArea, findCells, opt = {}) { + const area = new Rect(plainArea); + const viewsInExtendedArea = this._findInExtendedArea(area, findCells, opt); + const viewsInArea = viewsInExtendedArea.filter(view => { + if (!view) return false; + return view.isInArea(area, opt); + }); + return viewsInArea; + }, + + _filterViewsAtPoint: function(plainPoint, findCells, opt = {}) { + const area = new Rect(plainPoint); // zero-size area + const viewsInExtendedArea = this._findInExtendedArea(area, findCells, opt); + const viewsAtPoint = viewsInExtendedArea.filter(view => { + if (!view) return false; + return view.isAtPoint(plainPoint, opt); + }); + return viewsAtPoint; + }, + removeTools: function() { this.dispatchToolsEvent('remove'); return this; diff --git a/packages/joint-core/src/g/rect.mjs b/packages/joint-core/src/g/rect.mjs index 8690d7247..fd7a96b19 100644 --- a/packages/joint-core/src/g/rect.mjs +++ b/packages/joint-core/src/g/rect.mjs @@ -138,12 +138,20 @@ Rect.prototype = { }, // @return {bool} true if point p is inside me. - containsPoint: function(p) { - - if (!(p instanceof Point)) { - p = new Point(p); + // @param {bool} strict If true, the point has to be strictly inside (not on the border). + containsPoint: function(p, opt) { + let x, y; + if (!p || (typeof p === 'string')) { + // Backwards compatibility: if the point is not provided, + // the point is considered to be the origin [0, 0]. + ({ x, y } = new Point(p)); + } else { + // Do not create a new Point object if the point is already a Point-like object. + ({ x = 0, y = 0 } = p); } - return p.x >= this.x && p.x <= this.x + this.width && p.y >= this.y && p.y <= this.y + this.height; + return opt && opt.strict + ? (x > this.x && x < this.x + this.width && y > this.y && y < this.y + this.height) + : x >= this.x && x <= this.x + this.width && y >= this.y && y <= this.y + this.height; }, // @return {bool} true if rectangle `r` is inside me. diff --git a/packages/joint-core/test/geometry/rect.js b/packages/joint-core/test/geometry/rect.js index 80bc7dde7..75752ce41 100644 --- a/packages/joint-core/test/geometry/rect.js +++ b/packages/joint-core/test/geometry/rect.js @@ -161,7 +161,7 @@ QUnit.module('rect', function() { }); QUnit.module('containsPoint(point)', function() { - + QUnit.test('returns TRUE when a point is inside the rect', function(assert) { assert.ok((new g.Rect(50, 50, 50, 50).containsPoint(new g.Point(75, 75))), 'inside for Point'); assert.ok((new g.Rect(50, 50, 50, 50).containsPoint({ x: 75, y: 75 })), 'inside for object'); @@ -176,7 +176,7 @@ QUnit.module('rect', function() { assert.ok(new g.Rect(50, 50, 50, 50).containsPoint(new g.Point(100, 100))); }); - QUnit.test('returns TRUE when a point is outside the rect', function(assert) { + QUnit.test('returns FALSE when a point is outside the rect', function(assert) { assert.notOk((new g.Rect(50, 50, 50, 50).containsPoint(new g.Point(49, 75))), 'outside for Point'); assert.notOk((new g.Rect(50, 50, 50, 50).containsPoint({ x: 101, y: 75 })), 'outside for object'); assert.notOk((new g.Rect(50, 50, 50, 50).containsPoint('75@101')), 'outside for string coords separated by @'); @@ -184,6 +184,32 @@ QUnit.module('rect', function() { }); }); + + QUnit.module('containsPoint(point, strict=true)', function() { + + QUnit.test('returns TRUE when a point is inside the rect', function(assert) { + assert.ok((new g.Rect(50, 50, 50, 50).containsPoint(new g.Point(75, 75), { strict: true })), 'inside for Point'); + assert.ok((new g.Rect(50, 50, 50, 50).containsPoint({ x: 75, y: 75 }, { strict: true })), 'inside for object'); + assert.ok((new g.Rect(50, 50, 50, 50).containsPoint('75@75', { strict: true })), 'inside for string coords separated by @'); + assert.ok((new g.Rect(50, 50, 50, 50).containsPoint('75 75', { strict: true })), 'inside for string coords separated by space'); + }); + + QUnit.test('returns FALSE when a point is a corner of the rect', function(assert) { + assert.notOk(new g.Rect(50, 50, 50, 50).containsPoint(new g.Point(50, 50), { strict: true })); + assert.notOk(new g.Rect(50, 50, 50, 50).containsPoint(new g.Point(50, 100), { strict: true })); + assert.notOk(new g.Rect(50, 50, 50, 50).containsPoint(new g.Point(100, 50), { strict: true })); + assert.notOk(new g.Rect(50, 50, 50, 50).containsPoint(new g.Point(100, 100), { strict: true })); + }); + + QUnit.test('returns FALSE when a point is outside the rect', function(assert) { + assert.notOk((new g.Rect(50, 50, 50, 50).containsPoint(new g.Point(49, 75), { strict: true })), 'outside for Point'); + assert.notOk((new g.Rect(50, 50, 50, 50).containsPoint({ x: 101, y: 75 }, { strict: true })), 'outside for object'); + assert.notOk((new g.Rect(50, 50, 50, 50).containsPoint('75@101', { strict: true })), 'outside for string coords separated by @'); + assert.notOk((new g.Rect(50, 50, 50, 50).containsPoint('75 49', { strict: true })), 'outside for string coords separated by space'); + }); + }); + + QUnit.module('containsRect(rect)', function() { QUnit.test('returns TRUE when rect is completely inside the other rect', function(assert) { diff --git a/packages/joint-core/test/jointjs/graph.js b/packages/joint-core/test/jointjs/graph.js index fc79caf72..5abc40451 100644 --- a/packages/joint-core/test/jointjs/graph.js +++ b/packages/joint-core/test/jointjs/graph.js @@ -840,7 +840,7 @@ QUnit.module('graph', function(hooks) { }); }); - QUnit.module('graph.findModelsUnderElement()', function() { + QUnit.module('graph.findElementsUnderElement()', function() { QUnit.test('sanity', function(assert) { @@ -854,20 +854,20 @@ QUnit.module('graph', function(hooks) { this.graph.addCells([rect, under, away]); - assert.deepEqual(this.graph.findModelsUnderElement(away), [], 'There are no models under the element.'); - assert.deepEqual(this.graph.findModelsUnderElement(rect), [under], 'There is a model under the element.'); + assert.deepEqual(this.graph.findElementsUnderElement(away), [], 'There are no models under the element.'); + assert.deepEqual(this.graph.findElementsUnderElement(rect), [under], 'There is a model under the element.'); under.translate(50, 50); - assert.deepEqual(this.graph.findModelsUnderElement(rect, { searchBy: 'origin' }), [], 'There is no model under the element if searchBy origin option used.'); - assert.deepEqual(this.graph.findModelsUnderElement(rect, { searchBy: 'corner' }), [under], 'There is a model under the element if searchBy corner options used.'); + assert.deepEqual(this.graph.findElementsUnderElement(rect, { searchBy: 'origin' }), [], 'There is no model under the element if searchBy origin option used.'); + assert.deepEqual(this.graph.findElementsUnderElement(rect, { searchBy: 'corner' }), [under], 'There is a model under the element if searchBy corner options used.'); var embedded = rect.clone().addTo(this.graph); rect.embed(embedded); - assert.deepEqual(this.graph.findModelsUnderElement(rect), [under], 'There is 1 model under the element found and 1 embedded element is omitted.'); - assert.deepEqual(this.graph.findModelsUnderElement(under), [rect, embedded], 'There are 2 models under the element. Parent and its embed.'); - assert.deepEqual(this.graph.findModelsUnderElement(embedded), [rect, under], 'There are 2 models under the element. The element\'s parent and one other element.'); + assert.deepEqual(this.graph.findElementsUnderElement(rect), [under], 'There is 1 model under the element found and 1 embedded element is omitted.'); + assert.deepEqual(this.graph.findElementsUnderElement(under), [rect, embedded], 'There are 2 models under the element. Parent and its embed.'); + assert.deepEqual(this.graph.findElementsUnderElement(embedded), [rect, under], 'There are 2 models under the element. The element\'s parent and one other element.'); }); QUnit.test('rotated elements', function(assert) { @@ -876,14 +876,14 @@ QUnit.module('graph', function(hooks) { var rect1 = new joint.shapes.standard.Rectangle({ size: { width: 10, height: 100 }}); var rect2 = rect1.clone().translate(30, 30); graph.addCells([rect1, rect2]); - assert.deepEqual(graph.findModelsUnderElement(rect1), []); + assert.deepEqual(graph.findElementsUnderElement(rect1), []); rect1.set('angle', 90); - assert.deepEqual(graph.findModelsUnderElement(rect1), [rect2]); + assert.deepEqual(graph.findElementsUnderElement(rect1), [rect2]); rect2.set('angle', 90); // there is no intersection when both elements are rotated - assert.deepEqual(graph.findModelsUnderElement(rect1), []); + assert.deepEqual(graph.findElementsUnderElement(rect1), []); rect1.set('angle', 0); - assert.deepEqual(graph.findModelsUnderElement(rect1), [rect2]); + assert.deepEqual(graph.findElementsUnderElement(rect1), [rect2]); }); }); @@ -1118,7 +1118,7 @@ QUnit.module('graph', function(hooks) { }); - QUnit.module('findModelsInArea(rect[, opt])', function(hooks) { + QUnit.module('findElementsInArea(rect[, opt])', function(hooks) { var cells; @@ -1146,19 +1146,19 @@ QUnit.module('graph', function(hooks) { var modelsInArea; - modelsInArea = this.graph.findModelsInArea(new g.rect(0, 0, 10, 10)); + modelsInArea = this.graph.findElementsInArea(new g.rect(0, 0, 10, 10)); assert.equal(modelsInArea.length, 0, 'area with no elements in it'); - modelsInArea = this.graph.findModelsInArea(new g.rect(0, 0, 25, 25)); + modelsInArea = this.graph.findElementsInArea(new g.rect(0, 0, 25, 25)); assert.equal(modelsInArea.length, 1, 'area with 1 element in it'); - modelsInArea = this.graph.findModelsInArea(new g.rect(0, 0, 300, 300)); + modelsInArea = this.graph.findElementsInArea(new g.rect(0, 0, 300, 300)); assert.equal(modelsInArea.length, 3, 'area with 3 elements in it'); - modelsInArea = this.graph.findModelsInArea(new g.rect(0, 0, 100, 100), { strict: true }); + modelsInArea = this.graph.findElementsInArea(new g.rect(0, 0, 100, 100), { strict: true }); assert.equal(modelsInArea.length, 1, '[opt.strict = TRUE] should require elements to be completely within rect'); }); @@ -1167,7 +1167,7 @@ QUnit.module('graph', function(hooks) { var modelsInArea; - modelsInArea = this.graph.findModelsInArea({ + modelsInArea = this.graph.findElementsInArea({ x: 0, y: 0, width: 10, @@ -1475,7 +1475,7 @@ QUnit.module('graph', function(hooks) { type: 'test.Element' } }); - + const el = new El({ foo: {} }); diff --git a/packages/joint-core/test/jointjs/paper.js b/packages/joint-core/test/jointjs/paper.js index f3ac0eed3..dda023b05 100644 --- a/packages/joint-core/test/jointjs/paper.js +++ b/packages/joint-core/test/jointjs/paper.js @@ -152,7 +152,7 @@ QUnit.module('paper', function(hooks) { this.paper.on('render:done', function() { assert.equal(this.graph.getCells().length, 3); - assert.equal(this.paper.findViewsInArea(g.rect(-10, -10, 500, 500)).length, 3); + assert.equal(this.paper.findElementViewsInArea(g.rect(-10, -10, 500, 500)).length, 3); done(); }, this); @@ -1212,42 +1212,486 @@ QUnit.module('paper', function(hooks) { }, 'two rectangles + one circle (scaled by factor of 2), content bbox should be correct'); }); - QUnit.test('findViewsInArea(rect[, opt])', function(assert) { + function getViewsIds(views) { + return views.map((view) => view.model.id).sort(); + } + + QUnit.module('findElementViewsInArea()', function() { + + QUnit.test('option: strict=boolean', function(assert) { + + const cells = [ + new joint.shapes.standard.Rectangle({ + position: { x: 20, y: 20 }, + size: { width: 20, height: 20 } + }), + new joint.shapes.standard.Rectangle({ + position: { x: 80, y: 80 }, + size: { width: 40, height: 60 } + }), + new joint.shapes.standard.Rectangle({ + position: { x: 120, y: 180 }, + size: { width: 40, height: 40 } + }) + ]; + + this.graph.addCells(cells); + + let viewsInArea; + + viewsInArea = this.paper.findElementViewsInArea(new g.Rect(0, 0, 10, 10)); + assert.equal(viewsInArea.length, 0, 'area with no elements in it'); + + viewsInArea = this.paper.findElementViewsInArea(new g.Rect(0, 0, 25, 25)); + assert.equal(viewsInArea.length, 1, 'area with 1 element in it'); + + viewsInArea = this.paper.findElementViewsInArea({ x: 0, y: 0, width: 25, height: 25 }); + assert.equal(viewsInArea.length, 1, 'area with 1 element in it'); + + + viewsInArea = this.paper.findElementViewsInArea(new g.Rect(0, 0, 300, 300)); + assert.equal(viewsInArea.length, 3, 'area with 3 elements in it'); + + viewsInArea = this.paper.findElementViewsInArea(new g.Rect(0, 0, 100, 100), { strict: true }); + assert.equal(viewsInArea.length, 1, '[opt.strict = TRUE] should require elements to be completely within rect'); + }); + + QUnit.test('option: buffer=number', function(assert) { + + const cells = [ + new joint.shapes.standard.Rectangle({ + position: { x: 20, y: 20 }, + size: { width: 20, height: 20 }, + ports: { + groups: { + in: { + position: 'left', + } + }, + items: [{ + id: 'in', + group: 'in' + }] + } + }) + ]; + + this.graph.addCells(cells); + + let viewsInArea; + + viewsInArea = this.paper.findElementViewsInArea(new g.Rect(0, 0, 25, 25), { buffer: 0 }); + assert.equal(viewsInArea.length, 1); + + // If there is no buffer, the view should not be found when we query only the + // area containing the port overflow + viewsInArea = this.paper.findElementViewsInArea(new g.Rect(0, 0, 20, 100), { buffer: 0 }); + assert.equal(viewsInArea.length, 0); + + // There is a port on the left, that should be found with enough buffer + viewsInArea = this.paper.findElementViewsInArea(new g.Rect(0, 0, 20, 100), { buffer: 10 }); + assert.equal(viewsInArea.length, 1); + + // No port on the top + viewsInArea = this.paper.findElementViewsInArea(new g.Rect(0, 0, 100, 20), { buffer: 10 }); + assert.equal(viewsInArea.length, 0); + + viewsInArea = this.paper.findElementViewsInArea(new g.Rect(0, 0, 100, 21), { buffer: 10 }); + assert.equal(viewsInArea.length, 1); + }); + }); + + QUnit.module('findLinkViewsInArea()', function() { + + QUnit.test('option: strict=boolean', function(assert) { + + const cells = [ + new joint.shapes.standard.Rectangle({ + id: 'r1', + position: { x: 20, y: 20 }, + size: { width: 20, height: 20 } + }), + new joint.shapes.standard.Rectangle({ + id: 'r2', + position: { x: 80, y: 80 }, + size: { width: 40, height: 60 } + }), + new joint.shapes.standard.Link({ + id: 'l1', + source: { id: 'r1' }, + target: { id: 'r2' } + }), + ]; + + this.graph.addCells(cells); + + let viewsInArea; + + viewsInArea = this.paper.findLinkViewsInArea(new g.Rect(0, 0, 10, 10)); + assert.equal(viewsInArea.length, 0); + + viewsInArea = this.paper.findLinkViewsInArea(this.graph.getCell('r1').getBBox().inflate(-1)); + assert.equal(viewsInArea.length, 0); + + viewsInArea = this.paper.findLinkViewsInArea(this.graph.getCell('r1').getBBox()); + assert.equal(viewsInArea.length, 1); + + viewsInArea = this.paper.findLinkViewsInArea(new g.Rect(50, 20, 20, 60)); + assert.equal(viewsInArea.length, 1); + + viewsInArea = this.paper.findLinkViewsInArea(new g.Rect(50, 20, 20, 60), { strict: true }); + assert.equal(viewsInArea.length, 0); + + viewsInArea = this.paper.findLinkViewsInArea(new g.Rect(0, 0, 200, 200), { strict: true }); + assert.equal(viewsInArea.length, 1); + }); + + QUnit.test('option: buffer=number', function(assert) { + + const cells = [ + new joint.shapes.standard.Rectangle({ + id: 'r1', + position: { x: 20, y: 20 }, + size: { width: 20, height: 20 } + }), + new joint.shapes.standard.Rectangle({ + id: 'r2', + position: { x: 80, y: 80 }, + size: { width: 40, height: 60 } + }), + new joint.shapes.standard.Link({ + id: 'l1', + source: { id: 'r1' }, + target: { id: 'r2' }, + router: { + name: 'oneSide', + args: { + side: 'left' + } + } + }), + ]; + + this.graph.addCells(cells); + + let viewsInArea; + + viewsInArea = this.paper.findLinkViewsInArea(new g.Rect(0, 0, 25, 100), { buffer: 0 }); + assert.equal(viewsInArea.length, 0); + + viewsInArea = this.paper.findLinkViewsInArea(new g.Rect(0, 0, 25, 100), { buffer: 10 }); + assert.equal(viewsInArea.length, 1); + }); + }); + + QUnit.module('findCellViewsInArea()', function(assert) { + + QUnit.test('option: strict=boolean', function(assert) { + + const cells = [ + new joint.shapes.standard.Rectangle({ + id: 'r1', + position: { x: 20, y: 20 }, + size: { width: 20, height: 20 } + }), + new joint.shapes.standard.Rectangle({ + id: 'r2', + position: { x: 80, y: 80 }, + size: { width: 40, height: 60 } + }), + new joint.shapes.standard.Link({ + id: 'l1', + source: { id: 'r1' }, + target: { id: 'r2' } + }), + ]; + + this.graph.addCells(cells); + + let viewsInArea; + + viewsInArea = this.paper.findCellViewsInArea(new g.Rect(0, 0, 10, 10)); + assert.equal(viewsInArea.length, 0); + + viewsInArea = this.paper.findCellViewsInArea(this.graph.getCell('r1').getBBox().inflate(-1)); + assert.deepEqual(getViewsIds(viewsInArea), ['r1']); + + viewsInArea = this.paper.findCellViewsInArea(this.graph.getCell('r1').getBBox()); + assert.deepEqual(getViewsIds(viewsInArea), ['l1', 'r1']); + + viewsInArea = this.paper.findCellViewsInArea(new g.Rect(50, 20, 20, 60)); + assert.deepEqual(getViewsIds(viewsInArea), ['l1']); + + viewsInArea = this.paper.findCellViewsInArea(this.graph.getBBox().inflate(-5)); + assert.deepEqual(getViewsIds(viewsInArea), ['l1', 'r1', 'r2']); + + viewsInArea = this.paper.findCellViewsInArea(this.graph.getBBox().inflate(-5), { strict: true }); + assert.deepEqual(getViewsIds(viewsInArea), ['l1']); + }); + + QUnit.test('option: buffer=number', function(assert) { + + const cells = [ + new joint.shapes.standard.Rectangle({ + id: 'r1', + position: { x: 20, y: 20 }, + size: { width: 20, height: 20 }, + ports: { + groups: { + in: { + position: 'left', + } + }, + items: [{ + id: 'in', + group: 'in' + }] + } + }), + new joint.shapes.standard.Link({ + id: 'l1', + source: { id: 'r1' }, + target: { x: 0, y: 0 } + }) + ]; + + this.graph.addCells(cells); + + let viewsInArea; + + viewsInArea = this.paper.findCellViewsInArea(new g.Rect(0, 0, 20, 100), { buffer: 0 }); + assert.deepEqual(getViewsIds(viewsInArea), ['l1']); + + viewsInArea = this.paper.findCellViewsInArea(new g.Rect(0, 0, 20, 100), { buffer: 10 }); + assert.deepEqual(getViewsIds(viewsInArea), ['l1', 'r1']); + }); + }); + + QUnit.module('findElementViewsAtPoint()', function() { + + QUnit.test('option: strict=boolean', function(assert) { + + const cells = [ + new joint.shapes.standard.Rectangle({ + id: 'r1', + position: { x: 20, y: 20 }, + size: { width: 20, height: 20 } + }), + new joint.shapes.standard.Rectangle({ + id: 'r2', + position: { x: 35, y: 35 }, + size: { width: 40, height: 40 } + }), + ]; + + this.graph.addCells(cells); + + let viewsAtPoint; + + viewsAtPoint = this.paper.findElementViewsAtPoint({ x: 0, y: 0 }); + assert.equal(viewsAtPoint.length, 0); + + viewsAtPoint = this.paper.findElementViewsAtPoint({ x: 25, y: 25 }); + assert.deepEqual(viewsAtPoint.map(view => view.model.id).sort(), ['r1']); + + viewsAtPoint = this.paper.findElementViewsAtPoint({ x: 35, y: 35 }); + assert.deepEqual(viewsAtPoint.map(view => view.model.id).sort(), ['r1', 'r2']); + + viewsAtPoint = this.paper.findElementViewsAtPoint({ x: 35, y: 35 }, { strict: true }); + assert.deepEqual(viewsAtPoint.map(view => view.model.id).sort(), ['r1']); + }); + + QUnit.test('option: buffer=number', function(assert) { + + const cells = [ + new joint.shapes.standard.Rectangle({ + id: 'r1', + position: { x: 20, y: 20 }, + size: { width: 20, height: 20 }, + ports: { + groups: { + in: { + position: 'left', + } + }, + items: [{ + id: 'in', + group: 'in' + }] + } + }) + ]; + + this.graph.addCells(cells); + + let viewsAtPoint; + + viewsAtPoint = this.paper.findElementViewsAtPoint({ x: 18, y: 30 }, { buffer: 0 }); + assert.deepEqual(viewsAtPoint.map(view => view.model.id).sort(), []); + + viewsAtPoint = this.paper.findElementViewsAtPoint({ x: 18, y: 30 }, { buffer: 10 }); + assert.deepEqual(viewsAtPoint.map(view => view.model.id).sort(), ['r1']); + }); + }); + + QUnit.module('findLinkViewsAtPoint()', function() { + + QUnit.test('option: strict=boolean', function(assert) { + + const cells = [ + new joint.shapes.standard.Rectangle({ + id: 'r1', + position: { x: 20, y: 20 }, + size: { width: 20, height: 20 } + }), + new joint.shapes.standard.Rectangle({ + id: 'r2', + position: { x: 80, y: 20 }, + size: { width: 20, height: 20 } + }), + new joint.shapes.standard.Link({ + id: 'l1', + source: { id: 'r1' }, + target: { id: 'r2' } + }), + ]; + + this.graph.addCells(cells); + + let viewsAtPoint; + + viewsAtPoint = this.paper.findLinkViewsAtPoint({ x: 0, y: 0 }); + assert.equal(viewsAtPoint.length, 0); + + viewsAtPoint = this.paper.findLinkViewsAtPoint({ x: 35, y: 30 }); + assert.deepEqual(viewsAtPoint.map(view => view.model.id).sort(), []); + + viewsAtPoint = this.paper.findLinkViewsAtPoint({ x: 40, y: 30 }); + assert.deepEqual(viewsAtPoint.map(view => view.model.id).sort(), ['l1']); + + viewsAtPoint = this.paper.findLinkViewsAtPoint({ x: 40, y: 30 + 1e-10 }); + assert.deepEqual(viewsAtPoint.map(view => view.model.id).sort(), ['l1']); + + viewsAtPoint = this.paper.findLinkViewsAtPoint({ x: 40, y: 30 + 1e-10 }, { strict: true }); + assert.deepEqual(viewsAtPoint.map(view => view.model.id).sort(), ['l1']); + }); + + QUnit.test('option: buffer=number', function(assert) { + + const cells = [ + new joint.shapes.standard.Rectangle({ + id: 'r1', + position: { x: 20, y: 20 }, + size: { width: 20, height: 20 } + }), + new joint.shapes.standard.Rectangle({ + id: 'r2', + position: { x: 80, y: 80 }, + size: { width: 40, height: 60 } + }), + new joint.shapes.standard.Link({ + id: 'l1', + source: { id: 'r1' }, + target: { id: 'r2' }, + router: { + name: 'oneSide', + args: { + side: 'left' + } + } + }), + ]; + + this.graph.addCells(cells); + + let viewsAtPoint; - var cells = [ - new joint.shapes.standard.Rectangle({ - position: { x: 20, y: 20 }, - size: { width: 20, height: 20 } - }), - new joint.shapes.standard.Rectangle({ - position: { x: 80, y: 80 }, - size: { width: 40, height: 60 } - }), - new joint.shapes.standard.Rectangle({ - position: { x: 120, y: 180 }, - size: { width: 40, height: 40 } - }) - ]; + viewsAtPoint = this.paper.findLinkViewsAtPoint({ x: 10, y: 30 }, { buffer: 0 }); + assert.equal(viewsAtPoint.length, 0); - this.graph.addCells(cells); + viewsAtPoint = this.paper.findLinkViewsAtPoint({ x: 10, y: 30 }, { buffer: 100 }); + assert.deepEqual(viewsAtPoint.map(view => view.model.id).sort(), ['l1']); + }); + }); + + QUnit.module('findCellViewsAtPoint()', function(assert) { + + QUnit.test('option: strict=boolean', function(assert) { + + const cells = [ + new joint.shapes.standard.Rectangle({ + id: 'r1', + position: { x: 20, y: 20 }, + size: { width: 20, height: 20 } + }), + new joint.shapes.standard.Rectangle({ + id: 'r2', + position: { x: 80, y: 20 }, + size: { width: 20, height: 20 } + }), + new joint.shapes.standard.Link({ + id: 'l1', + source: { + id: 'r1', + connectionPoint: { name: 'anchor' }, + anchor: { name: 'modelCenter' } + }, + target: { id: 'r2' }, + vertices: [{ x: 10, y: 30 }] + }), + ]; + + this.graph.addCells(cells); + + let viewsAtPoint; + + viewsAtPoint = this.paper.findCellViewsAtPoint({ x: 0, y: 0 }); + assert.equal(viewsAtPoint.length, 0); - var viewsInArea; + viewsAtPoint = this.paper.findCellViewsAtPoint({ x: 15, y: 30 }); + assert.deepEqual(getViewsIds(viewsAtPoint).sort(), ['l1']); - viewsInArea = this.paper.findViewsInArea(new g.rect(0, 0, 10, 10)); + viewsAtPoint = this.paper.findCellViewsAtPoint({ x: 25, y: 30 }); + assert.deepEqual(getViewsIds(viewsAtPoint), ['l1', 'r1']); - assert.equal(viewsInArea.length, 0, 'area with no elements in it'); + viewsAtPoint = this.paper.findCellViewsAtPoint({ x: 20, y: 30 }); + assert.deepEqual(getViewsIds(viewsAtPoint), ['l1', 'r1']); - viewsInArea = this.paper.findViewsInArea(new g.rect(0, 0, 25, 25)); + viewsAtPoint = this.paper.findCellViewsAtPoint({ x: 20, y: 30 }, { strict: true }); + assert.deepEqual(getViewsIds(viewsAtPoint).sort(), ['l1']); + }); + + QUnit.test('option: buffer=number', function(assert) { - assert.equal(viewsInArea.length, 1, 'area with 1 element in it'); + const cells = [ + new joint.shapes.standard.Rectangle({ + id: 'r1', + position: { x: 20, y: 20 }, + size: { width: 20, height: 20 }, + ports: { + groups: { + in: { + position: 'left', + } + }, + items: [{ + id: 'in', + group: 'in' + }] + } + }) + ]; - viewsInArea = this.paper.findViewsInArea(new g.rect(0, 0, 300, 300)); + this.graph.addCells(cells); - assert.equal(viewsInArea.length, 3, 'area with 3 elements in it'); + let viewsAtPoint; - viewsInArea = this.paper.findViewsInArea(new g.rect(0, 0, 100, 100), { strict: true }); + viewsAtPoint = this.paper.findCellViewsAtPoint({ x: 18, y: 30 }, { buffer: 0 }); + assert.deepEqual(getViewsIds(viewsAtPoint), []); - assert.equal(viewsInArea.length, 1, '[opt.strict = TRUE] should require elements to be completely within rect'); + viewsAtPoint = this.paper.findCellViewsAtPoint({ x: 18, y: 30 }, { buffer: 10 }); + assert.deepEqual(getViewsIds(viewsAtPoint), ['r1']); + }); }); QUnit.test('linkAllowed(linkViewOrModel)', function(assert) { diff --git a/packages/joint-core/types/geometry.d.ts b/packages/joint-core/types/geometry.d.ts index edba8d4fe..e3be3ec91 100644 --- a/packages/joint-core/types/geometry.d.ts +++ b/packages/joint-core/types/geometry.d.ts @@ -37,6 +37,11 @@ export namespace g { precision?: number; } + export interface StrictOpt { + + strict?: boolean; + } + export interface SubdivisionsOpt extends PrecisionOpt { subdivisions?: Curve[]; @@ -644,7 +649,7 @@ export namespace g { clone(): Rect; - containsPoint(p: PlainPoint | string): boolean; + containsPoint(p: PlainPoint | string, opt?: StrictOpt): boolean; containsRect(r: PlainRect): boolean; diff --git a/packages/joint-core/types/joint.d.ts b/packages/joint-core/types/joint.d.ts index 722b18e8e..e7027c073 100644 --- a/packages/joint-core/types/joint.d.ts +++ b/packages/joint-core/types/joint.d.ts @@ -163,6 +163,20 @@ export namespace dia { breadthFirst?: boolean; } + interface FindAtPointOptions extends Options { + strict?: boolean; + } + + interface FindInAreaOptions extends Options { + strict?: boolean; + } + + type SearchByKey = 'bbox' | PositionName; + + interface FindUnderElementOptions extends FindInAreaOptions, FindAtPointOptions { + searchBy?: SearchByKey; + } + class Cells extends mvc.Collection { graph: Graph; cellNamespace: any; @@ -245,11 +259,42 @@ export namespace dia { clear(opt?: { [key: string]: any }): this; + findElementsAtPoint(p: Point, opt?: Graph.FindAtPointOptions): Element[]; + + findElementsInArea(rect: BBox, opt?: Graph.FindInAreaOptions): Element[]; + + findElementsUnderElement(element: Element, opt?: Graph.FindUnderElementOptions): Element[]; + + findLinksAtPoint(p: Point, opt?: Graph.FindAtPointOptions): Link[]; + + findLinksInArea(rect: BBox, opt?: Graph.FindInAreaOptions): Link[]; + + findLinksUnderElement(element: Element, opt?: Graph.FindUnderElementOptions): Link[]; + + findCellsAtPoint(p: Point, opt?: Graph.FindAtPointOptions): Cell[]; + + findCellsInArea(rect: BBox, opt?: Graph.FindInAreaOptions): Cell[]; + + findCellsUnderElement(element: Element, opt?: Graph.FindUnderElementOptions): Cell[]; + + protected _getFindUnderElementGeometry(element: Element, searchBy: Graph.SearchByKey): g.Point | g.Rect; + + protected _validateCellsUnderElement(cells: T, element: Element): T; + + protected _isValidElementUnderElement(el1: Element, el2: Element): boolean; + + protected _isValidLinkUnderElement(link: Link, element: Element): boolean; + + protected _filterCellsUnderElement(cells: Cell[], element: Element, opt: Graph.FindUnderElementOptions): Cell[]; + + /** @deprecated use `findElementsAtPoint` instead */ findModelsFromPoint(p: Point): Element[]; - findModelsInArea(rect: BBox, opt?: { strict?: boolean }): Element[]; + /** @deprecated use `findElementsInArea` instead */ + findModelsInArea(rect: BBox, opt?: Graph.FindInAreaOptions): Element[]; - findModelsUnderElement(element: Element, opt?: { searchBy?: 'bbox' | PositionName }): Element[]; + /** @deprecated use `findElementsUnderElement` instead */ + findModelsUnderElement(element: Element, opt?: Graph.FindUnderElementOptions): Element[]; getBBox(): g.Rect | null; @@ -896,6 +941,14 @@ export namespace dia { isDefaultInteractionPrevented(evt: dia.Event): boolean; + isIntersecting(geometryShape: g.Shape, geometryData?: g.SegmentSubdivisionsOpt | null): boolean; + + protected isEnclosedIn(area: g.Rect): boolean; + + protected isInArea(area: g.Rect, options: g.StrictOpt): boolean; + + protected isAtPoint(point: g.Point, options: g.StrictOpt): boolean; + protected findBySelector(selector: string, root?: SVGElement): SVGElement[]; protected removeHighlighters(): void; @@ -1293,6 +1346,11 @@ export namespace dia { afterRender?: AfterRenderCallback; } + interface SnapLinksOptions { + radius?: number; + findInAreaOptions?: FindInAreaOptions; + } + type PointConstraintCallback = (x: number, y: number, opt: any) => Point; type RestrictTranslateCallback = (elementView: ElementView, x0: number, y0: number) => BBox | boolean | PointConstraintCallback; type FindParentByType = 'bbox' | 'pointer' | PositionName; @@ -1311,7 +1369,7 @@ export namespace dia { highlighting?: boolean | Record; interactive?: ((cellView: CellView, event: string) => boolean | CellView.InteractivityOptions) | boolean | CellView.InteractivityOptions; snapLabels?: boolean; - snapLinks?: boolean | { radius: number }; + snapLinks?: boolean | SnapLinksOptions; snapLinksSelf?: boolean | { distance: number }; markAvailable?: boolean; // validations @@ -1485,6 +1543,20 @@ export namespace dia { // custom [eventName: string]: mvc.EventHandler; } + + interface BufferOptions { + /** + * A buffer around the area to extend the search to + * to mitigate the differences between the model and view geometry. + */ + buffer?: number; + } + + interface FindAtPointOptions extends Graph.FindAtPointOptions, BufferOptions { + } + + interface FindInAreaOptions extends Graph.FindInAreaOptions, BufferOptions { + } } class Paper extends mvc.View { @@ -1578,19 +1650,52 @@ export namespace dia { findViewByModel(model: Cell | Cell.ID): T; - findViewsFromPoint(point: string | Point): ElementView[]; + /** + * Finds all the element views at the specified point + * @param point a point in local paper coordinates + * @param opt options for the search + */ + findElementViewsAtPoint(point: Point, opt?: Paper.FindAtPointOptions): ElementView[]; - findViewsInArea(rect: BBox, opt?: { strict?: boolean }): ElementView[]; + /** + * Finds all the link views at the specified point + * @param point a point in local paper coordinates + * @param opt options for the search + */ + findLinkViewsAtPoint(point: Point, opt?: Paper.FindAtPointOptions): LinkView[]; - fitToContent(opt?: Paper.FitToContentOptions): g.Rect; - fitToContent(gridWidth?: number, gridHeight?: number, padding?: number, opt?: any): g.Rect; + /** + * Finds all the cell views at the specified point + * @param point a point in local paper coordinates + * @param opt options for the search + */ + findCellViewsAtPoint(point: Point, opt?: Paper.FindAtPointOptions): CellView[]; - getFitToContentArea(opt?: Paper.FitToContentOptions): g.Rect; + /** + * Finds all the element views in the specified area + * @param area a rectangle in local paper coordinates + * @param opt options for the search + */ + findElementViewsInArea(area: BBox, opt?: Paper.FindInAreaOptions): ElementView[]; /** - * @deprecated use transformToFitContent + * Finds all the link views in the specified area + * @param area a rectangle in local paper coordinates + * @param opt options for the search */ - scaleContentToFit(opt?: Paper.ScaleContentOptions): void; + findLinkViewsInArea(area: BBox, opt?: Paper.FindInAreaOptions): LinkView[]; + + /** + * Finds all the cell views in the specified area + * @param area a rectangle in local paper coordinates + * @param opt options for the search + */ + findCellViewsInArea(area: BBox, opt?: Paper.FindInAreaOptions): CellView[]; + + fitToContent(opt?: Paper.FitToContentOptions): g.Rect; + fitToContent(gridWidth?: number, gridHeight?: number, padding?: number, opt?: any): g.Rect; + + getFitToContentArea(opt?: Paper.FitToContentOptions): g.Rect; transformToFitContent(opt?: Paper.TransformToFitContentOptions): void; @@ -1820,6 +1925,21 @@ export namespace dia { protected customEventTrigger(event: dia.Event, view: CellView, rootNode?: SVGElement): dia.Event | null; protected addStylesheet(stylesheet: string): void; + + /** + * @deprecated use `findElementViewsAtPoint() + */ + findViewsFromPoint(point: string | Point): ElementView[]; + + /** + * @deprecated use `findElementViewsInArea() + */ + findViewsInArea(rect: BBox, opt?: { strict?: boolean }): ElementView[]; + + /** + * @deprecated use transformToFitContent + */ + scaleContentToFit(opt?: Paper.ScaleContentOptions): void; } namespace PaperLayer {