Skip to content

Commit

Permalink
feat(dia.Paper): add methods to find cell/element/link views in paper (
Browse files Browse the repository at this point in the history
  • Loading branch information
kumilingus authored Oct 29, 2024
1 parent b62479c commit ca06b4e
Show file tree
Hide file tree
Showing 13 changed files with 904 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion packages/joint-core/demo/chess/src/chess.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
22 changes: 21 additions & 1 deletion packages/joint-core/src/dia/CellView.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions packages/joint-core/src/dia/ElementView.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
113 changes: 95 additions & 18 deletions packages/joint-core/src/dia/Graph.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
36 changes: 34 additions & 2 deletions packages/joint-core/src/dia/LinkView.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {

Expand Down Expand Up @@ -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() {

Expand Down Expand Up @@ -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;
Expand Down
84 changes: 84 additions & 0 deletions packages/joint-core/src/dia/Paper.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
18 changes: 13 additions & 5 deletions packages/joint-core/src/g/rect.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit ca06b4e

Please sign in to comment.