transform.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.TransformManager = void 0;
  4. const x6_common_1 = require("@antv/x6-common");
  5. const x6_geometry_1 = require("@antv/x6-geometry");
  6. const base_1 = require("./base");
  7. const util_1 = require("../util");
  8. class TransformManager extends base_1.Base {
  9. get container() {
  10. return this.graph.view.container;
  11. }
  12. get viewport() {
  13. return this.graph.view.viewport;
  14. }
  15. get stage() {
  16. return this.graph.view.stage;
  17. }
  18. init() {
  19. this.resize();
  20. }
  21. /**
  22. * Returns the current transformation matrix of the graph.
  23. */
  24. getMatrix() {
  25. const transform = this.viewport.getAttribute('transform');
  26. if (transform !== this.viewportTransformString) {
  27. // `getCTM`: top-left relative to the SVG element
  28. // `getScreenCTM`: top-left relative to the document
  29. this.viewportMatrix = this.viewport.getCTM();
  30. this.viewportTransformString = transform;
  31. }
  32. // Clone the cached current transformation matrix.
  33. // If no matrix previously stored the identity matrix is returned.
  34. return x6_common_1.Dom.createSVGMatrix(this.viewportMatrix);
  35. }
  36. /**
  37. * Sets new transformation with the given `matrix`
  38. */
  39. setMatrix(matrix) {
  40. const ctm = x6_common_1.Dom.createSVGMatrix(matrix);
  41. const transform = x6_common_1.Dom.matrixToTransformString(ctm);
  42. this.viewport.setAttribute('transform', transform);
  43. this.viewportMatrix = ctm;
  44. this.viewportTransformString = transform;
  45. }
  46. resize(width, height) {
  47. let w = width === undefined ? this.options.width : width;
  48. let h = height === undefined ? this.options.height : height;
  49. this.options.width = w;
  50. this.options.height = h;
  51. if (typeof w === 'number') {
  52. w = Math.round(w);
  53. }
  54. if (typeof h === 'number') {
  55. h = Math.round(h);
  56. }
  57. this.container.style.width = w == null ? '' : `${w}px`;
  58. this.container.style.height = h == null ? '' : `${h}px`;
  59. const size = this.getComputedSize();
  60. this.graph.trigger('resize', Object.assign({}, size));
  61. return this;
  62. }
  63. getComputedSize() {
  64. let w = this.options.width;
  65. let h = this.options.height;
  66. if (!x6_common_1.NumberExt.isNumber(w)) {
  67. w = this.container.clientWidth;
  68. }
  69. if (!x6_common_1.NumberExt.isNumber(h)) {
  70. h = this.container.clientHeight;
  71. }
  72. return { width: w, height: h };
  73. }
  74. getScale() {
  75. return x6_common_1.Dom.matrixToScale(this.getMatrix());
  76. }
  77. scale(sx, sy = sx, ox = 0, oy = 0) {
  78. sx = this.clampScale(sx); // eslint-disable-line
  79. sy = this.clampScale(sy); // eslint-disable-line
  80. if (ox || oy) {
  81. const ts = this.getTranslation();
  82. const tx = ts.tx - ox * (sx - 1);
  83. const ty = ts.ty - oy * (sy - 1);
  84. if (tx !== ts.tx || ty !== ts.ty) {
  85. this.translate(tx, ty);
  86. }
  87. }
  88. const matrix = this.getMatrix();
  89. matrix.a = sx;
  90. matrix.d = sy;
  91. this.setMatrix(matrix);
  92. this.graph.trigger('scale', { sx, sy, ox, oy });
  93. return this;
  94. }
  95. clampScale(scale) {
  96. const range = this.graph.options.scaling;
  97. return x6_common_1.NumberExt.clamp(scale, range.min || 0.01, range.max || 16);
  98. }
  99. getZoom() {
  100. return this.getScale().sx;
  101. }
  102. zoom(factor, options) {
  103. options = options || {}; // eslint-disable-line
  104. let sx = factor;
  105. let sy = factor;
  106. const scale = this.getScale();
  107. const clientSize = this.getComputedSize();
  108. let cx = clientSize.width / 2;
  109. let cy = clientSize.height / 2;
  110. if (!options.absolute) {
  111. sx += scale.sx;
  112. sy += scale.sy;
  113. }
  114. if (options.scaleGrid) {
  115. sx = Math.round(sx / options.scaleGrid) * options.scaleGrid;
  116. sy = Math.round(sy / options.scaleGrid) * options.scaleGrid;
  117. }
  118. if (options.maxScale) {
  119. sx = Math.min(options.maxScale, sx);
  120. sy = Math.min(options.maxScale, sy);
  121. }
  122. if (options.minScale) {
  123. sx = Math.max(options.minScale, sx);
  124. sy = Math.max(options.minScale, sy);
  125. }
  126. if (options.center) {
  127. cx = options.center.x;
  128. cy = options.center.y;
  129. }
  130. sx = this.clampScale(sx);
  131. sy = this.clampScale(sy);
  132. if (cx || cy) {
  133. const ts = this.getTranslation();
  134. const tx = cx - (cx - ts.tx) * (sx / scale.sx);
  135. const ty = cy - (cy - ts.ty) * (sy / scale.sy);
  136. if (tx !== ts.tx || ty !== ts.ty) {
  137. this.translate(tx, ty);
  138. }
  139. }
  140. this.scale(sx, sy);
  141. return this;
  142. }
  143. getRotation() {
  144. return x6_common_1.Dom.matrixToRotation(this.getMatrix());
  145. }
  146. rotate(angle, cx, cy) {
  147. if (cx == null || cy == null) {
  148. const bbox = util_1.Util.getBBox(this.stage);
  149. cx = bbox.width / 2; // eslint-disable-line
  150. cy = bbox.height / 2; // eslint-disable-line
  151. }
  152. const ctm = this.getMatrix()
  153. .translate(cx, cy)
  154. .rotate(angle)
  155. .translate(-cx, -cy);
  156. this.setMatrix(ctm);
  157. return this;
  158. }
  159. getTranslation() {
  160. return x6_common_1.Dom.matrixToTranslation(this.getMatrix());
  161. }
  162. translate(tx, ty) {
  163. const matrix = this.getMatrix();
  164. matrix.e = tx || 0;
  165. matrix.f = ty || 0;
  166. this.setMatrix(matrix);
  167. const ts = this.getTranslation();
  168. this.options.x = ts.tx;
  169. this.options.y = ts.ty;
  170. this.graph.trigger('translate', Object.assign({}, ts));
  171. return this;
  172. }
  173. setOrigin(ox, oy) {
  174. return this.translate(ox || 0, oy || 0);
  175. }
  176. fitToContent(gridWidth, gridHeight, padding, options) {
  177. if (typeof gridWidth === 'object') {
  178. const opts = gridWidth;
  179. gridWidth = opts.gridWidth || 1; // eslint-disable-line
  180. gridHeight = opts.gridHeight || 1; // eslint-disable-line
  181. padding = opts.padding || 0; // eslint-disable-line
  182. options = opts; // eslint-disable-line
  183. }
  184. else {
  185. gridWidth = gridWidth || 1; // eslint-disable-line
  186. gridHeight = gridHeight || 1; // eslint-disable-line
  187. padding = padding || 0; // eslint-disable-line
  188. if (options == null) {
  189. options = {}; // eslint-disable-line
  190. }
  191. }
  192. const paddings = x6_common_1.NumberExt.normalizeSides(padding);
  193. const border = options.border || 0;
  194. const contentArea = options.contentArea
  195. ? x6_geometry_1.Rectangle.create(options.contentArea)
  196. : this.getContentArea(options);
  197. if (border > 0) {
  198. contentArea.inflate(border);
  199. }
  200. const scale = this.getScale();
  201. const translate = this.getTranslation();
  202. const sx = scale.sx;
  203. const sy = scale.sy;
  204. contentArea.x *= sx;
  205. contentArea.y *= sy;
  206. contentArea.width *= sx;
  207. contentArea.height *= sy;
  208. let width = Math.max(Math.ceil((contentArea.width + contentArea.x) / gridWidth), 1) *
  209. gridWidth;
  210. let height = Math.max(Math.ceil((contentArea.height + contentArea.y) / gridHeight), 1) * gridHeight;
  211. let tx = 0;
  212. let ty = 0;
  213. if ((options.allowNewOrigin === 'negative' && contentArea.x < 0) ||
  214. (options.allowNewOrigin === 'positive' && contentArea.x >= 0) ||
  215. options.allowNewOrigin === 'any') {
  216. tx = Math.ceil(-contentArea.x / gridWidth) * gridWidth;
  217. tx += paddings.left;
  218. width += tx;
  219. }
  220. if ((options.allowNewOrigin === 'negative' && contentArea.y < 0) ||
  221. (options.allowNewOrigin === 'positive' && contentArea.y >= 0) ||
  222. options.allowNewOrigin === 'any') {
  223. ty = Math.ceil(-contentArea.y / gridHeight) * gridHeight;
  224. ty += paddings.top;
  225. height += ty;
  226. }
  227. width += paddings.right;
  228. height += paddings.bottom;
  229. // Make sure the resulting width and height are greater than minimum.
  230. width = Math.max(width, options.minWidth || 0);
  231. height = Math.max(height, options.minHeight || 0);
  232. // Make sure the resulting width and height are lesser than maximum.
  233. width = Math.min(width, options.maxWidth || Number.MAX_SAFE_INTEGER);
  234. height = Math.min(height, options.maxHeight || Number.MAX_SAFE_INTEGER);
  235. const size = this.getComputedSize();
  236. const sizeChanged = width !== size.width || height !== size.height;
  237. const originChanged = tx !== translate.tx || ty !== translate.ty;
  238. // Change the dimensions only if there is a size discrepency or an origin change
  239. if (originChanged) {
  240. this.translate(tx, ty);
  241. }
  242. if (sizeChanged) {
  243. this.resize(width, height);
  244. }
  245. return new x6_geometry_1.Rectangle(-tx / sx, -ty / sy, width / sx, height / sy);
  246. }
  247. scaleContentToFit(options = {}) {
  248. this.scaleContentToFitImpl(options);
  249. }
  250. scaleContentToFitImpl(options = {}, translate = true) {
  251. let contentBBox;
  252. let contentLocalOrigin;
  253. if (options.contentArea) {
  254. const contentArea = options.contentArea;
  255. contentBBox = this.graph.localToGraph(contentArea);
  256. contentLocalOrigin = x6_geometry_1.Point.create(contentArea);
  257. }
  258. else {
  259. contentBBox = this.getContentBBox(options);
  260. contentLocalOrigin = this.graph.graphToLocal(contentBBox);
  261. }
  262. if (!contentBBox.width || !contentBBox.height) {
  263. return;
  264. }
  265. const padding = x6_common_1.NumberExt.normalizeSides(options.padding);
  266. const minScale = options.minScale || 0;
  267. const maxScale = options.maxScale || Number.MAX_SAFE_INTEGER;
  268. const minScaleX = options.minScaleX || minScale;
  269. const maxScaleX = options.maxScaleX || maxScale;
  270. const minScaleY = options.minScaleY || minScale;
  271. const maxScaleY = options.maxScaleY || maxScale;
  272. let fittingBox;
  273. if (options.viewportArea) {
  274. fittingBox = options.viewportArea;
  275. }
  276. else {
  277. const computedSize = this.getComputedSize();
  278. const currentTranslate = this.getTranslation();
  279. fittingBox = {
  280. x: currentTranslate.tx,
  281. y: currentTranslate.ty,
  282. width: computedSize.width,
  283. height: computedSize.height,
  284. };
  285. }
  286. fittingBox = x6_geometry_1.Rectangle.create(fittingBox).moveAndExpand({
  287. x: padding.left,
  288. y: padding.top,
  289. width: -padding.left - padding.right,
  290. height: -padding.top - padding.bottom,
  291. });
  292. const currentScale = this.getScale();
  293. let newSX = (fittingBox.width / contentBBox.width) * currentScale.sx;
  294. let newSY = (fittingBox.height / contentBBox.height) * currentScale.sy;
  295. if (options.preserveAspectRatio !== false) {
  296. newSX = newSY = Math.min(newSX, newSY);
  297. }
  298. // snap scale to a grid
  299. const gridSize = options.scaleGrid;
  300. if (gridSize) {
  301. newSX = gridSize * Math.floor(newSX / gridSize);
  302. newSY = gridSize * Math.floor(newSY / gridSize);
  303. }
  304. // scale min/max boundaries
  305. newSX = x6_common_1.NumberExt.clamp(newSX, minScaleX, maxScaleX);
  306. newSY = x6_common_1.NumberExt.clamp(newSY, minScaleY, maxScaleY);
  307. this.scale(newSX, newSY);
  308. if (translate) {
  309. const origin = this.options;
  310. const newOX = fittingBox.x - contentLocalOrigin.x * newSX - origin.x;
  311. const newOY = fittingBox.y - contentLocalOrigin.y * newSY - origin.y;
  312. this.translate(newOX, newOY);
  313. }
  314. }
  315. getContentArea(options = {}) {
  316. // use geometry calc default
  317. if (options.useCellGeometry !== false) {
  318. return this.model.getAllCellsBBox() || new x6_geometry_1.Rectangle();
  319. }
  320. return util_1.Util.getBBox(this.stage);
  321. }
  322. getContentBBox(options = {}) {
  323. return this.graph.localToGraph(this.getContentArea(options));
  324. }
  325. getGraphArea() {
  326. const rect = x6_geometry_1.Rectangle.fromSize(this.getComputedSize());
  327. return this.graph.graphToLocal(rect);
  328. }
  329. zoomToRect(rect, options = {}) {
  330. const area = x6_geometry_1.Rectangle.create(rect);
  331. const graph = this.graph;
  332. options.contentArea = area;
  333. if (options.viewportArea == null) {
  334. options.viewportArea = {
  335. x: graph.options.x,
  336. y: graph.options.y,
  337. width: this.options.width,
  338. height: this.options.height,
  339. };
  340. }
  341. this.scaleContentToFitImpl(options, false);
  342. const center = area.getCenter();
  343. this.centerPoint(center.x, center.y);
  344. return this;
  345. }
  346. zoomToFit(options = {}) {
  347. return this.zoomToRect(this.getContentArea(options), options);
  348. }
  349. centerPoint(x, y) {
  350. const clientSize = this.getComputedSize();
  351. const scale = this.getScale();
  352. const ts = this.getTranslation();
  353. const cx = clientSize.width / 2;
  354. const cy = clientSize.height / 2;
  355. x = typeof x === 'number' ? x : cx; // eslint-disable-line
  356. y = typeof y === 'number' ? y : cy; // eslint-disable-line
  357. x = cx - x * scale.sx; // eslint-disable-line
  358. y = cy - y * scale.sy; // eslint-disable-line
  359. if (ts.tx !== x || ts.ty !== y) {
  360. this.translate(x, y);
  361. }
  362. }
  363. centerContent(options) {
  364. const rect = this.graph.getContentArea(options);
  365. const center = rect.getCenter();
  366. this.centerPoint(center.x, center.y);
  367. }
  368. centerCell(cell) {
  369. return this.positionCell(cell, 'center');
  370. }
  371. positionPoint(point, x, y) {
  372. const clientSize = this.getComputedSize();
  373. // eslint-disable-next-line
  374. x = x6_common_1.NumberExt.normalizePercentage(x, Math.max(0, clientSize.width));
  375. if (x < 0) {
  376. x = clientSize.width + x; // eslint-disable-line
  377. }
  378. // eslint-disable-next-line
  379. y = x6_common_1.NumberExt.normalizePercentage(y, Math.max(0, clientSize.height));
  380. if (y < 0) {
  381. y = clientSize.height + y; // eslint-disable-line
  382. }
  383. const ts = this.getTranslation();
  384. const scale = this.getScale();
  385. const dx = x - point.x * scale.sx;
  386. const dy = y - point.y * scale.sy;
  387. if (ts.tx !== dx || ts.ty !== dy) {
  388. this.translate(dx, dy);
  389. }
  390. }
  391. positionRect(rect, pos) {
  392. const bbox = x6_geometry_1.Rectangle.create(rect);
  393. switch (pos) {
  394. case 'center':
  395. return this.positionPoint(bbox.getCenter(), '50%', '50%');
  396. case 'top':
  397. return this.positionPoint(bbox.getTopCenter(), '50%', 0);
  398. case 'top-right':
  399. return this.positionPoint(bbox.getTopRight(), '100%', 0);
  400. case 'right':
  401. return this.positionPoint(bbox.getRightMiddle(), '100%', '50%');
  402. case 'bottom-right':
  403. return this.positionPoint(bbox.getBottomRight(), '100%', '100%');
  404. case 'bottom':
  405. return this.positionPoint(bbox.getBottomCenter(), '50%', '100%');
  406. case 'bottom-left':
  407. return this.positionPoint(bbox.getBottomLeft(), 0, '100%');
  408. case 'left':
  409. return this.positionPoint(bbox.getLeftMiddle(), 0, '50%');
  410. case 'top-left':
  411. return this.positionPoint(bbox.getTopLeft(), 0, 0);
  412. default:
  413. return this;
  414. }
  415. }
  416. positionCell(cell, pos) {
  417. const bbox = cell.getBBox();
  418. return this.positionRect(bbox, pos);
  419. }
  420. positionContent(pos, options) {
  421. const rect = this.graph.getContentArea(options);
  422. return this.positionRect(rect, pos);
  423. }
  424. }
  425. exports.TransformManager = TransformManager;
  426. //# sourceMappingURL=transform.js.map