diff --git a/src/dia/CellView.mjs b/src/dia/CellView.mjs index ce4b390e7..7f6deaefe 100644 --- a/src/dia/CellView.mjs +++ b/src/dia/CellView.mjs @@ -255,11 +255,20 @@ export const CellView = View.extend({ getNodeBBox: function(magnet) { - var rect = this.getNodeBoundingRect(magnet); - var magnetMatrix = this.getNodeMatrix(magnet); - var translateMatrix = this.getRootTranslateMatrix(); - var rotateMatrix = this.getRootRotateMatrix(); - return V.transformRect(rect, translateMatrix.multiply(rotateMatrix).multiply(magnetMatrix)); + const rect = this.getNodeBoundingRect(magnet); + const transformMatrix = this.getRootTranslateMatrix().multiply(this.getNodeRotateMatrix(magnet)); + const magnetMatrix = this.getNodeMatrix(magnet); + return V.transformRect(rect, transformMatrix.multiply(magnetMatrix)); + }, + + getNodeRotateMatrix(node) { + if (!this.rotatableNode || this.rotatableNode.contains(node)) { + // Rotate transformation is applied to all nodes when no rotatableGroup + // is present or to nodes inside the rotatableGroup only. + return this.getRootRotateMatrix(); + } + // Nodes outside the rotatable group + return V.createSVGMatrix(); }, getNodeUnrotatedBBox: function(magnet) { @@ -730,9 +739,15 @@ export const CellView = View.extend({ getNodeMatrix: function(magnet) { - var metrics = this.nodeCache(magnet); + const metrics = this.nodeCache(magnet); if (metrics.magnetMatrix === undefined) { - var target = this.rotatableNode || this.el; + const { rotatableNode, el } = this; + let target; + if (rotatableNode && rotatableNode.contains(magnet)) { + target = rotatableNode; + } else { + target = el; + } metrics.magnetMatrix = V(magnet).getTransformToElement(target); } return V.createSVGMatrix(metrics.magnetMatrix); diff --git a/src/highlighters/mask.mjs b/src/highlighters/mask.mjs index 1fe8803ec..53d4b892a 100644 --- a/src/highlighters/mask.mjs +++ b/src/highlighters/mask.mjs @@ -200,11 +200,12 @@ export const mask = HighlighterView.extend({ vel.remove(); } const highlighterBBox = cellView.getNodeBoundingRect(node).inflate(padding + maskClip); + const highlightMatrix = cellView.getNodeRotateMatrix(node).multiply(cellView.getNodeMatrix(node)); const maskEl = this.getMask(cellView, V(node)); this.addMask(cellView.paper, maskEl); vel.attr(highlighterBBox.toJSON()); vel.attr({ - 'transform': V.matrixToTransformString(cellView.getNodeMatrix(node)), + 'transform': V.matrixToTransformString(highlightMatrix), 'mask': `url(#${maskEl.id})`, 'fill': color }); diff --git a/src/highlighters/stroke.mjs b/src/highlighters/stroke.mjs index 60eca2865..0c8d56377 100644 --- a/src/highlighters/stroke.mjs +++ b/src/highlighters/stroke.mjs @@ -52,7 +52,7 @@ export const stroke = HighlighterView.extend({ highlightNode(cellView, node) { const { vel, options } = this; const { padding, layer } = options; - let highlightMatrix = cellView.getNodeMatrix(node); + let highlightMatrix = cellView.getNodeRotateMatrix(node).multiply(cellView.getNodeMatrix(node)); // Add padding to the highlight element. if (padding) { if (!layer && node === cellView.el) { diff --git a/test/jointjs/elementView.js b/test/jointjs/elementView.js index 4af98fe9f..60c815f9b 100644 --- a/test/jointjs/elementView.js +++ b/test/jointjs/elementView.js @@ -155,6 +155,81 @@ QUnit.module('elementView', function(hooks) { elementView.model.resize(200, 300, { passed: true }); }); + + QUnit.test('getNodeBBox(), getNodeMatrix()', function(assert) { + + elementView.model.set({ + markup: [ + { + tagName: 'g', + selector: 'rotatable', + children: [ + { + tagName: 'rect', + selector: 'rectInside', + }, + { + tagName: 'circle', + selector: 'circle', + attributes: { + transform: 'translate(11,13)', + r: 5 + } + } + ], + }, { + tagName: 'rect', + selector: 'rectOutside', + }, + ], + attrs: { + rectInside: { + x: 21, + y: 13, + width: 20, + height: 10 + }, + rectOutside: { + x: 21, + y: 13, + width: 20, + height: 10 + } + } + }); + + elementView.model.resize(100, 100).translate(100, 100).rotate(90); + + var rectInside = elementView.findBySelector('rectInside')[0]; + assert.checkBboxApproximately(1, elementView.getNodeBBox(rectInside), { + x: 177, + y: 121, + width: 10, + height: 20 + }); + + assert.equal(V.matrixToTransformString(elementView.getNodeMatrix(rectInside)), 'matrix(1,0,0,1,0,0)'); + + var rectOutside = elementView.findBySelector('rectOutside')[0]; + assert.checkBboxApproximately(1, elementView.getNodeBBox(rectOutside), { + x: 121, + y: 113, + width: 20, + height: 10 + }); + + assert.equal(V.matrixToTransformString(elementView.getNodeMatrix(rectOutside)), 'matrix(1,0,0,1,0,0)'); + + var circle = elementView.findBySelector('circle')[0]; + assert.checkBboxApproximately(1, elementView.getNodeBBox(circle), { + x: 182, + y: 106, + width: 10, + height: 10 + }); + + assert.equal(V.matrixToTransformString(elementView.getNodeMatrix(circle)), 'matrix(1,0,0,1,11,13)'); + }); }); QUnit.module('no rotatable group and no scalable group', function(hooks) { diff --git a/types/joint.d.ts b/types/joint.d.ts index 03c4db760..a6715afee 100644 --- a/types/joint.d.ts +++ b/types/joint.d.ts @@ -774,6 +774,8 @@ export namespace dia { getNodeMatrix(node: SVGElement): SVGMatrix; + getNodeRotateMatrix(node: SVGElement): SVGMatrix; + getNodeBoundingRect(node: SVGElement): g.Rect; getBBox(opt?: { useModelGeometry?: boolean }): g.Rect;