UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering

343 lines (318 loc) 7.54 kB
import { attr } from './attr' import { createSvgElement } from './elem' export const KAPPA = 0.551784 function getNumbericAttribute( elem: SVGElement, attr: string, defaultValue = NaN, ) { const v = elem.getAttribute(attr) if (v == null) { return defaultValue } const n = parseFloat(v) return Number.isNaN(n) ? defaultValue : n } export function sample(elem: SVGPathElement, interval = 1) { const length = elem.getTotalLength() const samples = [] let distance = 0 let sample while (distance < length) { sample = elem.getPointAtLength(distance) samples.push({ distance, x: sample.x, y: sample.y }) distance += interval } return samples } export function lineToPathData(line: SVGLineElement) { return [ 'M', getNumbericAttribute(line, 'x1'), getNumbericAttribute(line, 'y1'), 'L', getNumbericAttribute(line, 'x2'), getNumbericAttribute(line, 'y2'), ].join(' ') } export function polygonToPathData(polygon: SVGPolygonElement) { const points = getPointsFromSvgElement(polygon) if (points.length === 0) { return null } return `${svgPointsToPath(points)} Z` } export function polylineToPathData(polyline: SVGPolylineElement) { const points = getPointsFromSvgElement(polyline) if (points.length === 0) { return null } return svgPointsToPath(points) } function svgPointsToPath(points: DOMPoint[]) { const arr = points.map((p) => `${p.x} ${p.y}`) return `M ${arr.join(' L')}` } export function getPointsFromSvgElement( elem: SVGPolygonElement | SVGPolylineElement, ) { const points = [] const nodePoints = elem.points if (nodePoints) { for (let i = 0, ii = nodePoints.numberOfItems; i < ii; i += 1) { points.push(nodePoints.getItem(i)) } } return points } export function circleToPathData(circle: SVGCircleElement) { const cx = getNumbericAttribute(circle, 'cx', 0) const cy = getNumbericAttribute(circle, 'cy', 0) const r = getNumbericAttribute(circle, 'r') const cd = r * KAPPA // Control distance. return [ 'M', cx, cy - r, // Move to the first point. 'C', cx + cd, cy - r, cx + r, cy - cd, cx + r, cy, // I. Quadrant. 'C', cx + r, cy + cd, cx + cd, cy + r, cx, cy + r, // II. Quadrant. 'C', cx - cd, cy + r, cx - r, cy + cd, cx - r, cy, // III. Quadrant. 'C', cx - r, cy - cd, cx - cd, cy - r, cx, cy - r, // IV. Quadrant. 'Z', ].join(' ') } export function ellipseToPathData(ellipse: SVGEllipseElement) { const cx = getNumbericAttribute(ellipse, 'cx', 0) const cy = getNumbericAttribute(ellipse, 'cy', 0) const rx = getNumbericAttribute(ellipse, 'rx') const ry = getNumbericAttribute(ellipse, 'ry') || rx const cdx = rx * KAPPA // Control distance x. const cdy = ry * KAPPA // Control distance y. const d = [ 'M', cx, cy - ry, // Move to the first point. 'C', cx + cdx, cy - ry, cx + rx, cy - cdy, cx + rx, cy, // I. Quadrant. 'C', cx + rx, cy + cdy, cx + cdx, cy + ry, cx, cy + ry, // II. Quadrant. 'C', cx - cdx, cy + ry, cx - rx, cy + cdy, cx - rx, cy, // III. Quadrant. 'C', cx - rx, cy - cdy, cx - cdx, cy - ry, cx, cy - ry, // IV. Quadrant. 'Z', ].join(' ') return d } export function rectangleToPathData(rect: SVGRectElement) { return rectToPathData({ x: getNumbericAttribute(rect, 'x', 0), y: getNumbericAttribute(rect, 'y', 0), width: getNumbericAttribute(rect, 'width', 0), height: getNumbericAttribute(rect, 'height', 0), rx: getNumbericAttribute(rect, 'rx', 0), ry: getNumbericAttribute(rect, 'ry', 0), }) } export function rectToPathData(r: { x: number y: number width: number height: number rx?: number ry?: number 'top-rx'?: number 'bottom-rx'?: number 'top-ry'?: number 'bottom-ry'?: number }) { let d const x = r.x const y = r.y const width = r.width const height = r.height const topRx = Math.min(r.rx || r['top-rx'] || 0, width / 2) const bottomRx = Math.min(r.rx || r['bottom-rx'] || 0, width / 2) const topRy = Math.min(r.ry || r['top-ry'] || 0, height / 2) const bottomRy = Math.min(r.ry || r['bottom-ry'] || 0, height / 2) if (topRx || bottomRx || topRy || bottomRy) { d = [ 'M', x, y + topRy, 'v', height - topRy - bottomRy, 'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, bottomRy, 'h', width - 2 * bottomRx, 'a', bottomRx, bottomRy, 0, 0, 0, bottomRx, -bottomRy, 'v', -(height - bottomRy - topRy), 'a', topRx, topRy, 0, 0, 0, -topRx, -topRy, 'h', -(width - 2 * topRx), 'a', topRx, topRy, 0, 0, 0, -topRx, topRy, 'Z', ] } else { d = ['M', x, y, 'H', x + width, 'V', y + height, 'H', x, 'V', y, 'Z'] } return d.join(' ') } export function toPath( elem: | SVGLineElement | SVGPolygonElement | SVGPolylineElement | SVGEllipseElement | SVGCircleElement | SVGRectElement, ) { const path = createSvgElement('path') as SVGPathElement attr(path, attr(elem)) const d = toPathData(elem) if (d) { path.setAttribute('d', d) } return path } export function toPathData( elem: | SVGLineElement | SVGPolygonElement | SVGPolylineElement | SVGEllipseElement | SVGCircleElement | SVGRectElement, ) { const tagName = elem.tagName.toLowerCase() switch (tagName) { case 'path': return elem.getAttribute('d') case 'line': return lineToPathData(elem as SVGLineElement) case 'polygon': return polygonToPathData(elem as SVGPolygonElement) case 'polyline': return polylineToPathData(elem as SVGPolylineElement) case 'ellipse': return ellipseToPathData(elem as SVGEllipseElement) case 'circle': return circleToPathData(elem as SVGCircleElement) case 'rect': return rectangleToPathData(elem as SVGRectElement) default: break } throw new Error(`"${tagName}" cannot be converted to svg path element.`) } // Inspired by d3.js https://github.com/mbostock/d3/blob/master/src/svg/arc.js export function createSlicePathData( innerRadius: number, outerRadius: number, startAngle: number, endAngle: number, ) { const svgArcMax = 2 * Math.PI - 1e-6 const r0 = innerRadius const r1 = outerRadius let a0 = startAngle let a1 = endAngle if (a1 < a0) { const tmp = a0 a0 = a1 a1 = tmp } const da = a1 - a0 const df = da < Math.PI ? '0' : '1' const c0 = Math.cos(a0) const s0 = Math.sin(a0) const c1 = Math.cos(a1) const s1 = Math.sin(a1) return da >= svgArcMax ? r0 ? // eslint-disable-next-line `M0,${r1}A${r1},${r1} 0 1,1 0,${-r1}A${r1},${r1} 0 1,1 0,${r1}M0,${r0}A${r0},${r0} 0 1,0 0,${-r0}A${r0},${r0} 0 1,0 0,${r0}Z` : // eslint-disable-next-line `M0,${r1}A${r1},${r1} 0 1,1 0,${-r1}A${r1},${r1} 0 1,1 0,${r1}Z` : r0 ? // eslint-disable-next-line `M${r1 * c0},${r1 * s0}A${r1},${r1} 0 ${df},1 ${r1 * c1},${r1 * s1}L${ r0 * c1 },${r0 * s1}A${r0},${r0} 0 ${df},0 ${r0 * c0},${r0 * s0}Z` : // eslint-disable-next-line `M${r1 * c0},${r1 * s0}A${r1},${r1} 0 ${df},1 ${r1 * c1},${r1 * s1}L0,0` + `Z` }