UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering

308 lines (263 loc) 7.29 kB
/* eslint-disable default-param-last */ import { Point, PointLike } from '../point' const regexSupportedData = new RegExp(`^[\\s\\dLMCZz,.]*$`) // eslint-disable-line prefer-regex-literals export function isValid(data: any) { if (typeof data !== 'string') { return false } return regexSupportedData.test(data) } /** * Returns the remainder of division of `n` by `m`. You should use this * instead of the built-in operation as the built-in operation does not * properly handle negative numbers. */ function mod(n: number, m: number) { return ((n % m) + m) % m } export interface DrawPointsOptions { round?: number initialMove?: boolean close?: boolean exclude?: number[] } function draw( points: PointLike[], round?: number, initialMove?: boolean, close?: boolean, exclude?: number[], ) { if (!points || points.length === 0) return '' const data: (string | number)[] = [] const end = points[points.length - 1] const rounded = round != null && round > 0 const arcSize = round || 0 // Adds virtual waypoint in the center between start and end point if (close && rounded) { points = points.slice() // eslint-disable-line const p0 = points[0] const wp = new Point(end.x + (p0.x - end.x) / 2, end.y + (p0.y - end.y) / 2) points.splice(0, 0, wp) } let pt = points[0] let i = 1 // Draws the line segments if (initialMove) { data.push('M', pt.x, pt.y) } else { data.push('L', pt.x, pt.y) } while (i < (close ? points.length : points.length - 1)) { let tmp = points[mod(i, points.length)] let dx = pt.x - tmp.x let dy = pt.y - tmp.y if ( rounded && (dx !== 0 || dy !== 0) && (exclude == null || exclude.indexOf(i - 1) < 0) ) { // Draws a line from the last point to the current // point with a spacing of size off the current point // into direction of the last point let dist = Math.sqrt(dx * dx + dy * dy) const nx1 = (dx * Math.min(arcSize, dist / 2)) / dist const ny1 = (dy * Math.min(arcSize, dist / 2)) / dist const x1 = tmp.x + nx1 const y1 = tmp.y + ny1 data.push('L', x1, y1) // Draws a curve from the last point to the current // point with a spacing of size off the current point // into direction of the next point let next = points[mod(i + 1, points.length)] // Uses next non-overlapping point while ( i < points.length - 2 && Math.round(next.x - tmp.x) === 0 && Math.round(next.y - tmp.y) === 0 ) { next = points[mod(i + 2, points.length)] i += 1 } dx = next.x - tmp.x dy = next.y - tmp.y dist = Math.max(1, Math.sqrt(dx * dx + dy * dy)) const nx2 = (dx * Math.min(arcSize, dist / 2)) / dist const ny2 = (dy * Math.min(arcSize, dist / 2)) / dist const x2 = tmp.x + nx2 const y2 = tmp.y + ny2 data.push('Q', tmp.x, tmp.y, x2, y2) tmp = new Point(x2, y2) } else { data.push('L', tmp.x, tmp.y) } pt = tmp i += 1 } if (close) { data.push('Z') } else { data.push('L', end.x, end.y) } return data.map((v) => (typeof v === 'string' ? v : +v.toFixed(3))).join(' ') } export function drawPoints( points: PointLike[], options: DrawPointsOptions = {}, ) { const pts: PointLike[] = [] if (points && points.length) { points.forEach((p) => { if (Array.isArray(p)) { pts.push({ x: p[0], y: p[1] }) } else { pts.push({ x: p.x, y: p.y }) } }) } return draw( pts, options.round, options.initialMove == null || options.initialMove, options.close, options.exclude, ) } /** * Converts the given arc to a series of curves. */ export function arcToCurves( x0: number, y0: number, r1: number, r2: number, angle = 0, largeArcFlag = 0, sweepFlag = 0, x: number, y: number, ) { if (r1 === 0 || r2 === 0) { return [] } x -= x0 // eslint-disable-line y -= y0 // eslint-disable-line r1 = Math.abs(r1) // eslint-disable-line r2 = Math.abs(r2) // eslint-disable-line const ctx = -x / 2 const cty = -y / 2 const cpsi = Math.cos((angle * Math.PI) / 180) const spsi = Math.sin((angle * Math.PI) / 180) const rxd = cpsi * ctx + spsi * cty const ryd = -1 * spsi * ctx + cpsi * cty const rxdd = rxd * rxd const rydd = ryd * ryd const r1x = r1 * r1 const r2y = r2 * r2 const lamda = rxdd / r1x + rydd / r2y let sds if (lamda > 1) { r1 = Math.sqrt(lamda) * r1 // eslint-disable-line r2 = Math.sqrt(lamda) * r2 // eslint-disable-line sds = 0 } else { let seif = 1 if (largeArcFlag === sweepFlag) { seif = -1 } sds = seif * Math.sqrt( (r1x * r2y - r1x * rydd - r2y * rxdd) / (r1x * rydd + r2y * rxdd), ) } const txd = (sds * r1 * ryd) / r2 const tyd = (-1 * sds * r2 * rxd) / r1 const tx = cpsi * txd - spsi * tyd + x / 2 const ty = spsi * txd + cpsi * tyd + y / 2 let rad = Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) - Math.atan2(0, 1) let s1 = rad >= 0 ? rad : 2 * Math.PI + rad rad = Math.atan2((-ryd - tyd) / r2, (-rxd - txd) / r1) - Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) let dr = rad >= 0 ? rad : 2 * Math.PI + rad if (sweepFlag === 0 && dr > 0) { dr -= 2 * Math.PI } else if (sweepFlag !== 0 && dr < 0) { dr += 2 * Math.PI } const sse = (dr * 2) / Math.PI const seg = Math.ceil(sse < 0 ? -1 * sse : sse) const segr = dr / seg const t = ((8 / 3) * Math.sin(segr / 4) * Math.sin(segr / 4)) / Math.sin(segr / 2) const cpsir1 = cpsi * r1 const cpsir2 = cpsi * r2 const spsir1 = spsi * r1 const spsir2 = spsi * r2 let mc = Math.cos(s1) let ms = Math.sin(s1) let x2 = -t * (cpsir1 * ms + spsir2 * mc) let y2 = -t * (spsir1 * ms - cpsir2 * mc) let x3 = 0 let y3 = 0 const result = [] for (let n = 0; n < seg; n += 1) { s1 += segr mc = Math.cos(s1) ms = Math.sin(s1) x3 = cpsir1 * mc - spsir2 * ms + tx y3 = spsir1 * mc + cpsir2 * ms + ty const dx = -t * (cpsir1 * ms + spsir2 * mc) const dy = -t * (spsir1 * ms - cpsir2 * mc) // CurveTo updates x0, y0 so need to restore it const index = n * 6 result[index] = Number(x2 + x0) result[index + 1] = Number(y2 + y0) result[index + 2] = Number(x3 - dx + x0) result[index + 3] = Number(y3 - dy + y0) result[index + 4] = Number(x3 + x0) result[index + 5] = Number(y3 + y0) x2 = x3 + dx y2 = y3 + dy } return result.map((num) => +num.toFixed(2)) } export function drawArc( startX: number, startY: number, rx: number, ry: number, xAxisRotation = 0, largeArcFlag: 0 | 1 = 0, sweepFlag: 0 | 1 = 0, stopX: number, stopY: number, ) { const data: (string | number)[] = [] const points = arcToCurves( startX, startY, rx, ry, xAxisRotation, largeArcFlag, sweepFlag, stopX, stopY, ) if (points != null) { for (let i = 0, ii = points.length; i < ii; i += 6) { data.push( 'C', points[i], points[i + 1], points[i + 2], points[i + 3], points[i + 4], points[i + 5], ) } } return data.join(' ') }