| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496 |
- /**
- * @Author: Caven
- * @Date: 2020-08-21 18:05:39
- */
-
- const TWO_PI = Math.PI * 2
- const FITTING_COUNT = 100
- const ZERO_TOLERANCE = 0.0001
-
- class PlotUtil {
- /**
- * @param pnt1
- * @param pnt2
- * @returns {number}
- */
- static distance(pnt1, pnt2) {
- return Math.sqrt(
- Math.pow(pnt1[0] - pnt2[0], 2) + Math.pow(pnt1[1] - pnt2[1], 2)
- )
- }
-
- /**
- * @param points
- * @returns {number}
- */
- static wholeDistance(points) {
- let distance = 0
- for (let i = 0; i < points.length - 1; i++)
- distance += this.distance(points[i], points[i + 1])
- return distance
- }
-
- /**
- * @param points
- * @returns {number}
- */
- static getBaseLength(points) {
- return Math.pow(this.wholeDistance(points), 0.99)
- }
-
- /**
- * @param pnt1
- * @param pnt2
- * @returns {number[]}
- */
- static mid(pnt1, pnt2) {
- return [(pnt1[0] + pnt2[0]) / 2, (pnt1[1] + pnt2[1]) / 2]
- }
-
- /**
- * @param pnt1
- * @param pnt2
- * @param pnt3
- * @returns {[*, *]|[*, *]|[*, number]}
- */
- static getCircleCenterOfThreePoints(pnt1, pnt2, pnt3) {
- let pntA = [(pnt1[0] + pnt2[0]) / 2, (pnt1[1] + pnt2[1]) / 2]
- let pntB = [pntA[0] - pnt1[1] + pnt2[1], pntA[1] + pnt1[0] - pnt2[0]]
- let pntC = [(pnt1[0] + pnt3[0]) / 2, (pnt1[1] + pnt3[1]) / 2]
- let pntD = [pntC[0] - pnt1[1] + pnt3[1], pntC[1] + pnt1[0] - pnt3[0]]
- return this.getIntersectPoint(pntA, pntB, pntC, pntD)
- }
-
- /**
- * @param pntA
- * @param pntB
- * @param pntC
- * @param pntD
- * @returns {(*|number)[]|*[]}
- */
- static getIntersectPoint(pntA, pntB, pntC, pntD) {
- let x, y, f, e
- if (pntA[1] === pntB[1]) {
- f = (pntD[0] - pntC[0]) / (pntD[1] - pntC[1])
- x = f * (pntA[1] - pntC[1]) + pntC[0]
- y = pntA[1]
- return [x, y]
- }
- if (pntC[1] === pntD[1]) {
- e = (pntB[0] - pntA[0]) / (pntB[1] - pntA[1])
- x = e * (pntC[1] - pntA[1]) + pntA[0]
- y = pntC[1]
- return [x, y]
- }
- e = (pntB[0] - pntA[0]) / (pntB[1] - pntA[1])
- f = (pntD[0] - pntC[0]) / (pntD[1] - pntC[1])
- y = (e * pntA[1] - pntA[0] - f * pntC[1] + pntC[0]) / (e - f)
- x = e * y - e * pntA[1] + pntA[0]
- return [x, y]
- }
-
- /**
- * @param startPnt
- * @param endPnt
- * @returns {number}
- */
- static getAzimuth(startPnt, endPnt) {
- let azimuth
- let angle = Math.asin(
- Math.abs(endPnt[1] - startPnt[1]) / this.distance(startPnt, endPnt)
- )
- if (endPnt[1] >= startPnt[1] && endPnt[0] >= startPnt[0])
- azimuth = angle + Math.PI
- else if (endPnt[1] >= startPnt[1] && endPnt[0] < startPnt[0])
- azimuth = TWO_PI - angle
- else if (endPnt[1] < startPnt[1] && endPnt[0] < startPnt[0]) azimuth = angle
- else if (endPnt[1] < startPnt[1] && endPnt[0] >= startPnt[0])
- azimuth = Math.PI - angle
- return azimuth
- }
-
- /**
- * @param pntA
- * @param pntB
- * @param pntC
- * @returns {number}
- */
- static getAngleOfThreePoints(pntA, pntB, pntC) {
- let angle = this.getAzimuth(pntB, pntA) - this.getAzimuth(pntB, pntC)
- return angle < 0 ? angle + TWO_PI : angle
- }
-
- /**
- * @param pnt1
- * @param pnt2
- * @param pnt3
- * @returns {boolean}
- */
- static isClockWise(pnt1, pnt2, pnt3) {
- return (
- (pnt3[1] - pnt1[1]) * (pnt2[0] - pnt1[0]) >
- (pnt2[1] - pnt1[1]) * (pnt3[0] - pnt1[0])
- )
- }
-
- /**
- * @param t
- * @param startPnt
- * @param endPnt
- * @returns {*[]}
- */
- static getPointOnLine(t, startPnt, endPnt) {
- let x = startPnt[0] + t * (endPnt[0] - startPnt[0])
- let y = startPnt[1] + t * (endPnt[1] - startPnt[1])
- return [x, y]
- }
-
- /**
- * @param t
- * @param startPnt
- * @param cPnt1
- * @param cPnt2
- * @param endPnt
- * @returns {number[]}
- */
- static getCubicValue(t, startPnt, cPnt1, cPnt2, endPnt) {
- t = Math.max(Math.min(t, 1), 0)
- let tp = 1 - t
- let t2 = t * t
- let t3 = t2 * t
- let tp2 = tp * tp
- let tp3 = tp2 * tp
- let x =
- tp3 * startPnt[0] +
- 3 * tp2 * t * cPnt1[0] +
- 3 * tp * t2 * cPnt2[0] +
- t3 * endPnt[0]
- let y =
- tp3 * startPnt[1] +
- 3 * tp2 * t * cPnt1[1] +
- 3 * tp * t2 * cPnt2[1] +
- t3 * endPnt[1]
- return [x, y]
- }
-
- /**
- * @param startPnt
- * @param endPnt
- * @param angle
- * @param distance
- * @param clockWise
- * @returns {*[]}
- */
- static getThirdPoint(startPnt, endPnt, angle, distance, clockWise) {
- let azimuth = this.getAzimuth(startPnt, endPnt)
- let alpha = clockWise ? azimuth + angle : azimuth - angle
- let dx = distance * Math.cos(alpha)
- let dy = distance * Math.sin(alpha)
- return [endPnt[0] + dx, endPnt[1] + dy]
- }
-
- /**
- * @param center
- * @param radius
- * @param startAngle
- * @param endAngle
- * @returns {[]}
- */
- static getArcPoints(center, radius, startAngle, endAngle) {
- let x,
- y,
- pnts = []
- let angleDiff = endAngle - startAngle
- angleDiff = angleDiff < 0 ? angleDiff + TWO_PI : angleDiff
- for (let i = 0; i <= FITTING_COUNT; i++) {
- let angle = startAngle + (angleDiff * i) / FITTING_COUNT
- x = center[0] + radius * Math.cos(angle)
- y = center[1] + radius * Math.sin(angle)
- pnts.push([x, y])
- }
- return pnts
- }
-
- /**
- * @param t
- * @param pnt1
- * @param pnt2
- * @param pnt3
- * @returns {*[][]}
- */
- static getBisectorNormals(t, pnt1, pnt2, pnt3) {
- let normal = this.getNormal(pnt1, pnt2, pnt3)
- let dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1])
- let uX = normal[0] / dist
- let uY = normal[1] / dist
- let d1 = this.distance(pnt1, pnt2)
- let d2 = this.distance(pnt2, pnt3)
- let dt, x, y, bisectorNormalLeft, bisectorNormalRight
- if (dist > ZERO_TOLERANCE) {
- if (this.isClockWise(pnt1, pnt2, pnt3)) {
- dt = t * d1
- x = pnt2[0] - dt * uY
- y = pnt2[1] + dt * uX
- bisectorNormalRight = [x, y]
- dt = t * d2
- x = pnt2[0] + dt * uY
- y = pnt2[1] - dt * uX
- bisectorNormalLeft = [x, y]
- } else {
- dt = t * d1
- x = pnt2[0] + dt * uY
- y = pnt2[1] - dt * uX
- bisectorNormalRight = [x, y]
- dt = t * d2
- x = pnt2[0] - dt * uY
- y = pnt2[1] + dt * uX
- bisectorNormalLeft = [x, y]
- }
- } else {
- x = pnt2[0] + t * (pnt1[0] - pnt2[0])
- y = pnt2[1] + t * (pnt1[1] - pnt2[1])
- bisectorNormalRight = [x, y]
- x = pnt2[0] + t * (pnt3[0] - pnt2[0])
- y = pnt2[1] + t * (pnt3[1] - pnt2[1])
- bisectorNormalLeft = [x, y]
- }
- return [bisectorNormalRight, bisectorNormalLeft]
- }
-
- /**
- * @param pnt1
- * @param pnt2
- * @param pnt3
- * @returns {number[]}
- */
- static getNormal(pnt1, pnt2, pnt3) {
- let dX1 = pnt1[0] - pnt2[0]
- let dY1 = pnt1[1] - pnt2[1]
- let d1 = Math.sqrt(dX1 * dX1 + dY1 * dY1)
- dX1 /= d1
- dY1 /= d1
-
- let dX2 = pnt3[0] - pnt2[0]
- let dY2 = pnt3[1] - pnt2[1]
- let d2 = Math.sqrt(dX2 * dX2 + dY2 * dY2)
- dX2 /= d2
- dY2 /= d2
-
- let uX = dX1 + dX2
- let uY = dY1 + dY2
- return [uX, uY]
- }
-
- /**
- * @param t
- * @param controlPoints
- * @returns {[]}
- */
- static getCurvePoints(t, controlPoints) {
- let leftControl = this.getLeftMostControlPoint(t, controlPoints)
- let normals = [leftControl]
- let pnt1, pnt2, pnt3, normalPoints
- for (let i = 0; i < controlPoints.length - 2; i++) {
- pnt1 = controlPoints[i]
- pnt2 = controlPoints[i + 1]
- pnt3 = controlPoints[i + 2]
- normalPoints = this.getBisectorNormals(t, pnt1, pnt2, pnt3)
- normals = normals.concat(normalPoints)
- }
- let rightControl = this.getRightMostControlPoint(t, controlPoints)
- normals.push(rightControl)
- let points = []
- for (let i = 0; i < controlPoints.length - 1; i++) {
- pnt1 = controlPoints[i]
- pnt2 = controlPoints[i + 1]
- points.push(pnt1)
- for (let t = 0; t < FITTING_COUNT; t++) {
- let pnt = this.getCubicValue(
- t / FITTING_COUNT,
- pnt1,
- normals[i * 2],
- normals[i * 2 + 1],
- pnt2
- )
- points.push(pnt)
- }
- points.push(pnt2)
- }
- return points
- }
-
- /**
- * @param t
- * @param controlPoints
- * @returns {number[]}
- */
- static getLeftMostControlPoint(t, controlPoints) {
- let pnt1 = controlPoints[0]
- let pnt2 = controlPoints[1]
- let pnt3 = controlPoints[2]
- let pnts = this.getBisectorNormals(0, pnt1, pnt2, pnt3)
- let normalRight = pnts[0]
- let normal = this.getNormal(pnt1, pnt2, pnt3)
- let dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1])
- let controlX, controlY
- if (dist > ZERO_TOLERANCE) {
- let mid = this.mid(pnt1, pnt2)
- let pX = pnt1[0] - mid[0]
- let pY = pnt1[1] - mid[1]
- let d1 = this.distance(pnt1, pnt2)
- // normal at midpoint
- let n = 2.0 / d1
- let nX = -n * pY
- let nY = n * pX
- // upper triangle of symmetric transform matrix
- let a11 = nX * nX - nY * nY
- let a12 = 2 * nX * nY
- let a22 = nY * nY - nX * nX
- let dX = normalRight[0] - mid[0]
- let dY = normalRight[1] - mid[1]
- // coordinates of reflected vector
- controlX = mid[0] + a11 * dX + a12 * dY
- controlY = mid[1] + a12 * dX + a22 * dY
- } else {
- controlX = pnt1[0] + t * (pnt2[0] - pnt1[0])
- controlY = pnt1[1] + t * (pnt2[1] - pnt1[1])
- }
- return [controlX, controlY]
- }
-
- /**
- * @param t
- * @param controlPoints
- * @returns {number[]}
- */
- static getRightMostControlPoint(t, controlPoints) {
- let count = controlPoints.length
- let pnt1 = controlPoints[count - 3]
- let pnt2 = controlPoints[count - 2]
- let pnt3 = controlPoints[count - 1]
- let pnts = this.getBisectorNormals(0, pnt1, pnt2, pnt3)
- let normalLeft = pnts[1]
- let normal = this.getNormal(pnt1, pnt2, pnt3)
- let dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1])
- let controlX, controlY
- if (dist > ZERO_TOLERANCE) {
- let mid = this.mid(pnt2, pnt3)
- let pX = pnt3[0] - mid[0]
- let pY = pnt3[1] - mid[1]
-
- let d1 = this.distance(pnt2, pnt3)
- // normal at midpoint
- let n = 2.0 / d1
- let nX = -n * pY
- let nY = n * pX
-
- // upper triangle of symmetric transform matrix
- let a11 = nX * nX - nY * nY
- let a12 = 2 * nX * nY
- let a22 = nY * nY - nX * nX
-
- let dX = normalLeft[0] - mid[0]
- let dY = normalLeft[1] - mid[1]
-
- // coordinates of reflected vector
- controlX = mid[0] + a11 * dX + a12 * dY
- controlY = mid[1] + a12 * dX + a22 * dY
- } else {
- controlX = pnt3[0] + t * (pnt2[0] - pnt3[0])
- controlY = pnt3[1] + t * (pnt2[1] - pnt3[1])
- }
- return [controlX, controlY]
- }
-
- /**
- * @param points
- * @returns {[]|*}
- */
- static getBezierPoints(points) {
- if (points.length <= 2) return points
- let bezierPoints = []
- let n = points.length - 1
- for (let t = 0; t <= 1; t += 0.01) {
- let x = 0
- let y = 0
- for (let index = 0; index <= n; index++) {
- let factor = this.getBinomialFactor(n, index)
- let a = Math.pow(t, index)
- let b = Math.pow(1 - t, n - index)
- x += factor * a * b * points[index][0]
- y += factor * a * b * points[index][1]
- }
- bezierPoints.push([x, y])
- }
- bezierPoints.push(points[n])
- return bezierPoints
- }
-
- /**
- *
- * @param n
- * @param index
- * @returns {number}
- */
- static getBinomialFactor(n, index) {
- return (
- this.getFactorial(n) /
- (this.getFactorial(index) * this.getFactorial(n - index))
- )
- }
-
- /**
- * @param n
- * @returns {number}
- */
- static getFactorial(n) {
- if (n <= 1) return 1
- if (n === 2) return 2
- if (n === 3) return 6
- if (n === 4) return 24
- if (n === 5) return 120
- let result = 1
- for (let i = 1; i <= n; i++) result *= i
- return result
- }
-
- /**
- * @param points
- * @returns {[]|*}
- */
- static getQBSplinePoints(points) {
- if (points.length <= 2) return points
- let n = 2
- let bSplinePoints = []
- let m = points.length - n - 1
- bSplinePoints.push(points[0])
- for (let i = 0; i <= m; i++) {
- for (let t = 0; t <= 1; t += 0.05) {
- let x = 0
- let y = 0
- for (let k = 0; k <= n; k++) {
- let factor = this.getQuadricBSplineFactor(k, t)
- x += factor * points[i + k][0]
- y += factor * points[i + k][1]
- }
- bSplinePoints.push([x, y])
- }
- }
- bSplinePoints.push(points[points.length - 1])
- return bSplinePoints
- }
-
- /**
- * @param k
- * @param t
- * @returns {number}
- */
- static getQuadricBSplineFactor(k, t) {
- if (k === 0) return Math.pow(t - 1, 2) / 2
- if (k === 1) return (-2 * Math.pow(t, 2) + 2 * t + 1) / 2
- if (k === 2) return Math.pow(t, 2) / 2
- return 0
- }
- }
-
- export default PlotUtil
|