@antv/x6
Version:
JavaScript diagramming library that uses SVG and HTML for rendering
197 lines (172 loc) • 5.42 kB
text/typescript
import { attr } from './attr'
import { isSVGGraphicsElement } from './elem'
import {
Scale,
Rotation,
Translation,
createSVGTransform,
parseTransformString,
transformStringToMatrix,
matrixToTransformString,
createSVGMatrix,
} from './matrix'
export interface TransformOptions {
absolute?: boolean
}
export function transform(elem: Element): DOMMatrix
export function transform(
elem: SVGElement,
matrix: DOMMatrix,
options?: TransformOptions,
): void
export function transform(
elem: SVGGraphicsElement,
matrix?: DOMMatrix,
options: TransformOptions = {},
) {
if (matrix == null) {
return transformStringToMatrix(attr(elem, 'transform'))
}
if (options.absolute) {
elem.setAttribute('transform', matrixToTransformString(matrix))
return
}
const transformList = elem.transform
const svgTransform = createSVGTransform(matrix)
transformList.baseVal.appendItem(svgTransform)
}
export function translate(elem: Element): Translation
export function translate(
elem: Element,
tx: number,
ty?: number,
options?: TransformOptions,
): void
export function translate(
elem: Element,
tx?: number,
ty = 0,
options: TransformOptions = {},
) {
let transformAttr = attr(elem, 'transform')
const transform = parseTransformString(transformAttr)
if (tx == null) {
return transform.translation
}
transformAttr = transform.raw
transformAttr = transformAttr.replace(/translate\([^)]*\)/g, '').trim()
const newTx = options.absolute ? tx : transform.translation.tx + tx
const newTy = options.absolute ? ty : transform.translation.ty + ty
const newTranslate = `translate(${newTx},${newTy})`
// Note that `translate()` is always the first transformation. This is
// usually the desired case.
elem.setAttribute('transform', `${newTranslate} ${transformAttr}`.trim())
}
export function rotate(elem: Element): Rotation
export function rotate(
elem: Element,
angle: number,
cx?: number,
cy?: number,
options?: TransformOptions,
): void
export function rotate(
elem: Element,
angle?: number,
cx?: number,
cy?: number,
options: TransformOptions = {},
) {
let transformAttr = attr(elem, 'transform')
const transform = parseTransformString(transformAttr)
if (angle == null) {
return transform.rotation
}
transformAttr = transform.raw
transformAttr = transformAttr.replace(/rotate\([^)]*\)/g, '').trim()
angle %= 360 // eslint-disable-line
const newAngle = options.absolute ? angle : transform.rotation.angle + angle
const newOrigin = cx != null && cy != null ? `,${cx},${cy}` : ''
const newRotate = `rotate(${newAngle}${newOrigin})`
elem.setAttribute('transform', `${transformAttr} ${newRotate}`.trim())
}
export function scale(elem: Element): Scale
export function scale(elem: Element, sx: number, sy?: number): void
export function scale(elem: Element, sx?: number, sy?: number) {
let transformAttr = attr(elem, 'transform')
const transform = parseTransformString(transformAttr)
if (sx == null) {
return transform.scale
}
sy = sy == null ? sx : sy // eslint-disable-line
transformAttr = transform.raw
transformAttr = transformAttr.replace(/scale\([^)]*\)/g, '').trim()
const newScale = `scale(${sx},${sy})`
elem.setAttribute('transform', `${transformAttr} ${newScale}`.trim())
}
/**
* Returns an DOMMatrix that specifies the transformation necessary
* to convert `elem` coordinate system into `target` coordinate system.
*/
export function getTransformToElement(elem: SVGElement, target: SVGElement) {
if (isSVGGraphicsElement(target) && isSVGGraphicsElement(elem)) {
const targetCTM = target.getScreenCTM()
const nodeCTM = elem.getScreenCTM()
if (targetCTM && nodeCTM) {
return targetCTM.inverse().multiply(nodeCTM)
}
}
// Could not get actual transformation matrix
return createSVGMatrix()
}
/**
* Returns an DOMMatrix that specifies the transformation necessary
* to convert `elem` coordinate system into `target` coordinate system.
* Unlike getTransformToElement, elem is child of target,Because of the reduction in DOM API calls,
* there is a significant performance improvement.
*/
export function getTransformToParentElement(
elem: SVGElement,
target: SVGElement,
) {
let matrix = createSVGMatrix()
if (isSVGGraphicsElement(target) && isSVGGraphicsElement(elem)) {
let node = elem
const matrixList = []
while (node && node !== target) {
const transform = node.getAttribute('transform') || null
const nodeMatrix = transformStringToMatrix(transform)
matrixList.push(nodeMatrix)
node = node.parentNode as SVGGraphicsElement
}
matrixList.reverse().forEach((m) => {
matrix = matrix.multiply(m)
})
}
return matrix
}
/**
* Converts a global point with coordinates `x` and `y` into the
* coordinate space of the element.
*/
export function toLocalPoint(
elem: SVGElement | SVGSVGElement,
x: number,
y: number,
) {
const svg =
elem instanceof SVGSVGElement
? elem
: (elem.ownerSVGElement as SVGSVGElement)
const p = svg.createSVGPoint()
p.x = x
p.y = y
try {
const ctm = svg.getScreenCTM()!
const globalPoint = p.matrixTransform(ctm.inverse())
const globalToLocalMatrix = getTransformToElement(elem, svg).inverse()
return globalPoint.matrixTransform(globalToLocalMatrix)
} catch (e) {
return p
}
}