@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering
426 lines • 16.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TransformManager = void 0;
const x6_common_1 = require("@antv/x6-common");
const x6_geometry_1 = require("@antv/x6-geometry");
const base_1 = require("./base");
const util_1 = require("../util");
class TransformManager extends base_1.Base {
get container() {
return this.graph.view.container;
}
get viewport() {
return this.graph.view.viewport;
}
get stage() {
return this.graph.view.stage;
}
init() {
this.resize();
}
/**
* Returns the current transformation matrix of the graph.
*/
getMatrix() {
const transform = this.viewport.getAttribute('transform');
if (transform !== this.viewportTransformString) {
// `getCTM`: top-left relative to the SVG element
// `getScreenCTM`: top-left relative to the document
this.viewportMatrix = this.viewport.getCTM();
this.viewportTransformString = transform;
}
// Clone the cached current transformation matrix.
// If no matrix previously stored the identity matrix is returned.
return x6_common_1.Dom.createSVGMatrix(this.viewportMatrix);
}
/**
* Sets new transformation with the given `matrix`
*/
setMatrix(matrix) {
const ctm = x6_common_1.Dom.createSVGMatrix(matrix);
const transform = x6_common_1.Dom.matrixToTransformString(ctm);
this.viewport.setAttribute('transform', transform);
this.viewportMatrix = ctm;
this.viewportTransformString = transform;
}
resize(width, height) {
let w = width === undefined ? this.options.width : width;
let h = height === undefined ? this.options.height : height;
this.options.width = w;
this.options.height = h;
if (typeof w === 'number') {
w = Math.round(w);
}
if (typeof h === 'number') {
h = Math.round(h);
}
this.container.style.width = w == null ? '' : `${w}px`;
this.container.style.height = h == null ? '' : `${h}px`;
const size = this.getComputedSize();
this.graph.trigger('resize', Object.assign({}, size));
return this;
}
getComputedSize() {
let w = this.options.width;
let h = this.options.height;
if (!x6_common_1.NumberExt.isNumber(w)) {
w = this.container.clientWidth;
}
if (!x6_common_1.NumberExt.isNumber(h)) {
h = this.container.clientHeight;
}
return { width: w, height: h };
}
getScale() {
return x6_common_1.Dom.matrixToScale(this.getMatrix());
}
scale(sx, sy = sx, ox = 0, oy = 0) {
sx = this.clampScale(sx); // eslint-disable-line
sy = this.clampScale(sy); // eslint-disable-line
if (ox || oy) {
const ts = this.getTranslation();
const tx = ts.tx - ox * (sx - 1);
const ty = ts.ty - oy * (sy - 1);
if (tx !== ts.tx || ty !== ts.ty) {
this.translate(tx, ty);
}
}
const matrix = this.getMatrix();
matrix.a = sx;
matrix.d = sy;
this.setMatrix(matrix);
this.graph.trigger('scale', { sx, sy, ox, oy });
return this;
}
clampScale(scale) {
const range = this.graph.options.scaling;
return x6_common_1.NumberExt.clamp(scale, range.min || 0.01, range.max || 16);
}
getZoom() {
return this.getScale().sx;
}
zoom(factor, options) {
options = options || {}; // eslint-disable-line
let sx = factor;
let sy = factor;
const scale = this.getScale();
const clientSize = this.getComputedSize();
let cx = clientSize.width / 2;
let cy = clientSize.height / 2;
if (!options.absolute) {
sx += scale.sx;
sy += scale.sy;
}
if (options.scaleGrid) {
sx = Math.round(sx / options.scaleGrid) * options.scaleGrid;
sy = Math.round(sy / options.scaleGrid) * options.scaleGrid;
}
if (options.maxScale) {
sx = Math.min(options.maxScale, sx);
sy = Math.min(options.maxScale, sy);
}
if (options.minScale) {
sx = Math.max(options.minScale, sx);
sy = Math.max(options.minScale, sy);
}
if (options.center) {
cx = options.center.x;
cy = options.center.y;
}
sx = this.clampScale(sx);
sy = this.clampScale(sy);
if (cx || cy) {
const ts = this.getTranslation();
const tx = cx - (cx - ts.tx) * (sx / scale.sx);
const ty = cy - (cy - ts.ty) * (sy / scale.sy);
if (tx !== ts.tx || ty !== ts.ty) {
this.translate(tx, ty);
}
}
this.scale(sx, sy);
return this;
}
getRotation() {
return x6_common_1.Dom.matrixToRotation(this.getMatrix());
}
rotate(angle, cx, cy) {
if (cx == null || cy == null) {
const bbox = util_1.Util.getBBox(this.stage);
cx = bbox.width / 2; // eslint-disable-line
cy = bbox.height / 2; // eslint-disable-line
}
const ctm = this.getMatrix()
.translate(cx, cy)
.rotate(angle)
.translate(-cx, -cy);
this.setMatrix(ctm);
return this;
}
getTranslation() {
return x6_common_1.Dom.matrixToTranslation(this.getMatrix());
}
translate(tx, ty) {
const matrix = this.getMatrix();
matrix.e = tx || 0;
matrix.f = ty || 0;
this.setMatrix(matrix);
const ts = this.getTranslation();
this.options.x = ts.tx;
this.options.y = ts.ty;
this.graph.trigger('translate', Object.assign({}, ts));
return this;
}
setOrigin(ox, oy) {
return this.translate(ox || 0, oy || 0);
}
fitToContent(gridWidth, gridHeight, padding, options) {
if (typeof gridWidth === 'object') {
const opts = gridWidth;
gridWidth = opts.gridWidth || 1; // eslint-disable-line
gridHeight = opts.gridHeight || 1; // eslint-disable-line
padding = opts.padding || 0; // eslint-disable-line
options = opts; // eslint-disable-line
}
else {
gridWidth = gridWidth || 1; // eslint-disable-line
gridHeight = gridHeight || 1; // eslint-disable-line
padding = padding || 0; // eslint-disable-line
if (options == null) {
options = {}; // eslint-disable-line
}
}
const paddings = x6_common_1.NumberExt.normalizeSides(padding);
const border = options.border || 0;
const contentArea = options.contentArea
? x6_geometry_1.Rectangle.create(options.contentArea)
: this.getContentArea(options);
if (border > 0) {
contentArea.inflate(border);
}
const scale = this.getScale();
const translate = this.getTranslation();
const sx = scale.sx;
const sy = scale.sy;
contentArea.x *= sx;
contentArea.y *= sy;
contentArea.width *= sx;
contentArea.height *= sy;
let width = Math.max(Math.ceil((contentArea.width + contentArea.x) / gridWidth), 1) *
gridWidth;
let height = Math.max(Math.ceil((contentArea.height + contentArea.y) / gridHeight), 1) * gridHeight;
let tx = 0;
let ty = 0;
if ((options.allowNewOrigin === 'negative' && contentArea.x < 0) ||
(options.allowNewOrigin === 'positive' && contentArea.x >= 0) ||
options.allowNewOrigin === 'any') {
tx = Math.ceil(-contentArea.x / gridWidth) * gridWidth;
tx += paddings.left;
width += tx;
}
if ((options.allowNewOrigin === 'negative' && contentArea.y < 0) ||
(options.allowNewOrigin === 'positive' && contentArea.y >= 0) ||
options.allowNewOrigin === 'any') {
ty = Math.ceil(-contentArea.y / gridHeight) * gridHeight;
ty += paddings.top;
height += ty;
}
width += paddings.right;
height += paddings.bottom;
// Make sure the resulting width and height are greater than minimum.
width = Math.max(width, options.minWidth || 0);
height = Math.max(height, options.minHeight || 0);
// Make sure the resulting width and height are lesser than maximum.
width = Math.min(width, options.maxWidth || Number.MAX_SAFE_INTEGER);
height = Math.min(height, options.maxHeight || Number.MAX_SAFE_INTEGER);
const size = this.getComputedSize();
const sizeChanged = width !== size.width || height !== size.height;
const originChanged = tx !== translate.tx || ty !== translate.ty;
// Change the dimensions only if there is a size discrepency or an origin change
if (originChanged) {
this.translate(tx, ty);
}
if (sizeChanged) {
this.resize(width, height);
}
return new x6_geometry_1.Rectangle(-tx / sx, -ty / sy, width / sx, height / sy);
}
scaleContentToFit(options = {}) {
this.scaleContentToFitImpl(options);
}
scaleContentToFitImpl(options = {}, translate = true) {
let contentBBox;
let contentLocalOrigin;
if (options.contentArea) {
const contentArea = options.contentArea;
contentBBox = this.graph.localToGraph(contentArea);
contentLocalOrigin = x6_geometry_1.Point.create(contentArea);
}
else {
contentBBox = this.getContentBBox(options);
contentLocalOrigin = this.graph.graphToLocal(contentBBox);
}
if (!contentBBox.width || !contentBBox.height) {
return;
}
const padding = x6_common_1.NumberExt.normalizeSides(options.padding);
const minScale = options.minScale || 0;
const maxScale = options.maxScale || Number.MAX_SAFE_INTEGER;
const minScaleX = options.minScaleX || minScale;
const maxScaleX = options.maxScaleX || maxScale;
const minScaleY = options.minScaleY || minScale;
const maxScaleY = options.maxScaleY || maxScale;
let fittingBox;
if (options.viewportArea) {
fittingBox = options.viewportArea;
}
else {
const computedSize = this.getComputedSize();
const currentTranslate = this.getTranslation();
fittingBox = {
x: currentTranslate.tx,
y: currentTranslate.ty,
width: computedSize.width,
height: computedSize.height,
};
}
fittingBox = x6_geometry_1.Rectangle.create(fittingBox).moveAndExpand({
x: padding.left,
y: padding.top,
width: -padding.left - padding.right,
height: -padding.top - padding.bottom,
});
const currentScale = this.getScale();
let newSX = (fittingBox.width / contentBBox.width) * currentScale.sx;
let newSY = (fittingBox.height / contentBBox.height) * currentScale.sy;
if (options.preserveAspectRatio !== false) {
newSX = newSY = Math.min(newSX, newSY);
}
// snap scale to a grid
const gridSize = options.scaleGrid;
if (gridSize) {
newSX = gridSize * Math.floor(newSX / gridSize);
newSY = gridSize * Math.floor(newSY / gridSize);
}
// scale min/max boundaries
newSX = x6_common_1.NumberExt.clamp(newSX, minScaleX, maxScaleX);
newSY = x6_common_1.NumberExt.clamp(newSY, minScaleY, maxScaleY);
this.scale(newSX, newSY);
if (translate) {
const origin = this.options;
const newOX = fittingBox.x - contentLocalOrigin.x * newSX - origin.x;
const newOY = fittingBox.y - contentLocalOrigin.y * newSY - origin.y;
this.translate(newOX, newOY);
}
}
getContentArea(options = {}) {
// use geometry calc default
if (options.useCellGeometry !== false) {
return this.model.getAllCellsBBox() || new x6_geometry_1.Rectangle();
}
return util_1.Util.getBBox(this.stage);
}
getContentBBox(options = {}) {
return this.graph.localToGraph(this.getContentArea(options));
}
getGraphArea() {
const rect = x6_geometry_1.Rectangle.fromSize(this.getComputedSize());
return this.graph.graphToLocal(rect);
}
zoomToRect(rect, options = {}) {
const area = x6_geometry_1.Rectangle.create(rect);
const graph = this.graph;
options.contentArea = area;
if (options.viewportArea == null) {
options.viewportArea = {
x: graph.options.x,
y: graph.options.y,
width: this.options.width,
height: this.options.height,
};
}
this.scaleContentToFitImpl(options, false);
const center = area.getCenter();
this.centerPoint(center.x, center.y);
return this;
}
zoomToFit(options = {}) {
return this.zoomToRect(this.getContentArea(options), options);
}
centerPoint(x, y) {
const clientSize = this.getComputedSize();
const scale = this.getScale();
const ts = this.getTranslation();
const cx = clientSize.width / 2;
const cy = clientSize.height / 2;
x = typeof x === 'number' ? x : cx; // eslint-disable-line
y = typeof y === 'number' ? y : cy; // eslint-disable-line
x = cx - x * scale.sx; // eslint-disable-line
y = cy - y * scale.sy; // eslint-disable-line
if (ts.tx !== x || ts.ty !== y) {
this.translate(x, y);
}
}
centerContent(options) {
const rect = this.graph.getContentArea(options);
const center = rect.getCenter();
this.centerPoint(center.x, center.y);
}
centerCell(cell) {
return this.positionCell(cell, 'center');
}
positionPoint(point, x, y) {
const clientSize = this.getComputedSize();
// eslint-disable-next-line
x = x6_common_1.NumberExt.normalizePercentage(x, Math.max(0, clientSize.width));
if (x < 0) {
x = clientSize.width + x; // eslint-disable-line
}
// eslint-disable-next-line
y = x6_common_1.NumberExt.normalizePercentage(y, Math.max(0, clientSize.height));
if (y < 0) {
y = clientSize.height + y; // eslint-disable-line
}
const ts = this.getTranslation();
const scale = this.getScale();
const dx = x - point.x * scale.sx;
const dy = y - point.y * scale.sy;
if (ts.tx !== dx || ts.ty !== dy) {
this.translate(dx, dy);
}
}
positionRect(rect, pos) {
const bbox = x6_geometry_1.Rectangle.create(rect);
switch (pos) {
case 'center':
return this.positionPoint(bbox.getCenter(), '50%', '50%');
case 'top':
return this.positionPoint(bbox.getTopCenter(), '50%', 0);
case 'top-right':
return this.positionPoint(bbox.getTopRight(), '100%', 0);
case 'right':
return this.positionPoint(bbox.getRightMiddle(), '100%', '50%');
case 'bottom-right':
return this.positionPoint(bbox.getBottomRight(), '100%', '100%');
case 'bottom':
return this.positionPoint(bbox.getBottomCenter(), '50%', '100%');
case 'bottom-left':
return this.positionPoint(bbox.getBottomLeft(), 0, '100%');
case 'left':
return this.positionPoint(bbox.getLeftMiddle(), 0, '50%');
case 'top-left':
return this.positionPoint(bbox.getTopLeft(), 0, 0);
default:
return this;
}
}
positionCell(cell, pos) {
const bbox = cell.getBBox();
return this.positionRect(bbox, pos);
}
positionContent(pos, options) {
const rect = this.graph.getContentArea(options);
return this.positionRect(rect, pos);
}
}
exports.TransformManager = TransformManager;
//# sourceMappingURL=transform.js.map