You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PlotUtil.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /**
  2. * @Author: Caven
  3. * @Date: 2020-08-21 18:05:39
  4. */
  5. const TWO_PI = Math.PI * 2
  6. const FITTING_COUNT = 100
  7. const ZERO_TOLERANCE = 0.0001
  8. class PlotUtil {
  9. /**
  10. * @param pnt1
  11. * @param pnt2
  12. * @returns {number}
  13. */
  14. static distance(pnt1, pnt2) {
  15. return Math.sqrt(
  16. Math.pow(pnt1[0] - pnt2[0], 2) + Math.pow(pnt1[1] - pnt2[1], 2)
  17. )
  18. }
  19. /**
  20. * @param points
  21. * @returns {number}
  22. */
  23. static wholeDistance(points) {
  24. let distance = 0
  25. for (let i = 0; i < points.length - 1; i++)
  26. distance += this.distance(points[i], points[i + 1])
  27. return distance
  28. }
  29. /**
  30. * @param points
  31. * @returns {number}
  32. */
  33. static getBaseLength(points) {
  34. return Math.pow(this.wholeDistance(points), 0.99)
  35. }
  36. /**
  37. * @param pnt1
  38. * @param pnt2
  39. * @returns {number[]}
  40. */
  41. static mid(pnt1, pnt2) {
  42. return [(pnt1[0] + pnt2[0]) / 2, (pnt1[1] + pnt2[1]) / 2]
  43. }
  44. /**
  45. * @param pnt1
  46. * @param pnt2
  47. * @param pnt3
  48. * @returns {[*, *]|[*, *]|[*, number]}
  49. */
  50. static getCircleCenterOfThreePoints(pnt1, pnt2, pnt3) {
  51. let pntA = [(pnt1[0] + pnt2[0]) / 2, (pnt1[1] + pnt2[1]) / 2]
  52. let pntB = [pntA[0] - pnt1[1] + pnt2[1], pntA[1] + pnt1[0] - pnt2[0]]
  53. let pntC = [(pnt1[0] + pnt3[0]) / 2, (pnt1[1] + pnt3[1]) / 2]
  54. let pntD = [pntC[0] - pnt1[1] + pnt3[1], pntC[1] + pnt1[0] - pnt3[0]]
  55. return this.getIntersectPoint(pntA, pntB, pntC, pntD)
  56. }
  57. /**
  58. * @param pntA
  59. * @param pntB
  60. * @param pntC
  61. * @param pntD
  62. * @returns {(*|number)[]|*[]}
  63. */
  64. static getIntersectPoint(pntA, pntB, pntC, pntD) {
  65. let x, y, f, e
  66. if (pntA[1] === pntB[1]) {
  67. f = (pntD[0] - pntC[0]) / (pntD[1] - pntC[1])
  68. x = f * (pntA[1] - pntC[1]) + pntC[0]
  69. y = pntA[1]
  70. return [x, y]
  71. }
  72. if (pntC[1] === pntD[1]) {
  73. e = (pntB[0] - pntA[0]) / (pntB[1] - pntA[1])
  74. x = e * (pntC[1] - pntA[1]) + pntA[0]
  75. y = pntC[1]
  76. return [x, y]
  77. }
  78. e = (pntB[0] - pntA[0]) / (pntB[1] - pntA[1])
  79. f = (pntD[0] - pntC[0]) / (pntD[1] - pntC[1])
  80. y = (e * pntA[1] - pntA[0] - f * pntC[1] + pntC[0]) / (e - f)
  81. x = e * y - e * pntA[1] + pntA[0]
  82. return [x, y]
  83. }
  84. /**
  85. * @param startPnt
  86. * @param endPnt
  87. * @returns {number}
  88. */
  89. static getAzimuth(startPnt, endPnt) {
  90. let azimuth
  91. let angle = Math.asin(
  92. Math.abs(endPnt[1] - startPnt[1]) / this.distance(startPnt, endPnt)
  93. )
  94. if (endPnt[1] >= startPnt[1] && endPnt[0] >= startPnt[0])
  95. azimuth = angle + Math.PI
  96. else if (endPnt[1] >= startPnt[1] && endPnt[0] < startPnt[0])
  97. azimuth = TWO_PI - angle
  98. else if (endPnt[1] < startPnt[1] && endPnt[0] < startPnt[0]) azimuth = angle
  99. else if (endPnt[1] < startPnt[1] && endPnt[0] >= startPnt[0])
  100. azimuth = Math.PI - angle
  101. return azimuth
  102. }
  103. /**
  104. * @param pntA
  105. * @param pntB
  106. * @param pntC
  107. * @returns {number}
  108. */
  109. static getAngleOfThreePoints(pntA, pntB, pntC) {
  110. let angle = this.getAzimuth(pntB, pntA) - this.getAzimuth(pntB, pntC)
  111. return angle < 0 ? angle + TWO_PI : angle
  112. }
  113. /**
  114. * @param pnt1
  115. * @param pnt2
  116. * @param pnt3
  117. * @returns {boolean}
  118. */
  119. static isClockWise(pnt1, pnt2, pnt3) {
  120. return (
  121. (pnt3[1] - pnt1[1]) * (pnt2[0] - pnt1[0]) >
  122. (pnt2[1] - pnt1[1]) * (pnt3[0] - pnt1[0])
  123. )
  124. }
  125. /**
  126. * @param t
  127. * @param startPnt
  128. * @param endPnt
  129. * @returns {*[]}
  130. */
  131. static getPointOnLine(t, startPnt, endPnt) {
  132. let x = startPnt[0] + t * (endPnt[0] - startPnt[0])
  133. let y = startPnt[1] + t * (endPnt[1] - startPnt[1])
  134. return [x, y]
  135. }
  136. /**
  137. * @param t
  138. * @param startPnt
  139. * @param cPnt1
  140. * @param cPnt2
  141. * @param endPnt
  142. * @returns {number[]}
  143. */
  144. static getCubicValue(t, startPnt, cPnt1, cPnt2, endPnt) {
  145. t = Math.max(Math.min(t, 1), 0)
  146. let tp = 1 - t
  147. let t2 = t * t
  148. let t3 = t2 * t
  149. let tp2 = tp * tp
  150. let tp3 = tp2 * tp
  151. let x =
  152. tp3 * startPnt[0] +
  153. 3 * tp2 * t * cPnt1[0] +
  154. 3 * tp * t2 * cPnt2[0] +
  155. t3 * endPnt[0]
  156. let y =
  157. tp3 * startPnt[1] +
  158. 3 * tp2 * t * cPnt1[1] +
  159. 3 * tp * t2 * cPnt2[1] +
  160. t3 * endPnt[1]
  161. return [x, y]
  162. }
  163. /**
  164. * @param startPnt
  165. * @param endPnt
  166. * @param angle
  167. * @param distance
  168. * @param clockWise
  169. * @returns {*[]}
  170. */
  171. static getThirdPoint(startPnt, endPnt, angle, distance, clockWise) {
  172. let azimuth = this.getAzimuth(startPnt, endPnt)
  173. let alpha = clockWise ? azimuth + angle : azimuth - angle
  174. let dx = distance * Math.cos(alpha)
  175. let dy = distance * Math.sin(alpha)
  176. return [endPnt[0] + dx, endPnt[1] + dy]
  177. }
  178. /**
  179. * @param center
  180. * @param radius
  181. * @param startAngle
  182. * @param endAngle
  183. * @returns {[]}
  184. */
  185. static getArcPoints(center, radius, startAngle, endAngle) {
  186. let x,
  187. y,
  188. pnts = []
  189. let angleDiff = endAngle - startAngle
  190. angleDiff = angleDiff < 0 ? angleDiff + TWO_PI : angleDiff
  191. for (let i = 0; i <= FITTING_COUNT; i++) {
  192. let angle = startAngle + (angleDiff * i) / FITTING_COUNT
  193. x = center[0] + radius * Math.cos(angle)
  194. y = center[1] + radius * Math.sin(angle)
  195. pnts.push([x, y])
  196. }
  197. return pnts
  198. }
  199. /**
  200. * @param t
  201. * @param pnt1
  202. * @param pnt2
  203. * @param pnt3
  204. * @returns {*[][]}
  205. */
  206. static getBisectorNormals(t, pnt1, pnt2, pnt3) {
  207. let normal = this.getNormal(pnt1, pnt2, pnt3)
  208. let dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1])
  209. let uX = normal[0] / dist
  210. let uY = normal[1] / dist
  211. let d1 = this.distance(pnt1, pnt2)
  212. let d2 = this.distance(pnt2, pnt3)
  213. let dt, x, y, bisectorNormalLeft, bisectorNormalRight
  214. if (dist > ZERO_TOLERANCE) {
  215. if (this.isClockWise(pnt1, pnt2, pnt3)) {
  216. dt = t * d1
  217. x = pnt2[0] - dt * uY
  218. y = pnt2[1] + dt * uX
  219. bisectorNormalRight = [x, y]
  220. dt = t * d2
  221. x = pnt2[0] + dt * uY
  222. y = pnt2[1] - dt * uX
  223. bisectorNormalLeft = [x, y]
  224. } else {
  225. dt = t * d1
  226. x = pnt2[0] + dt * uY
  227. y = pnt2[1] - dt * uX
  228. bisectorNormalRight = [x, y]
  229. dt = t * d2
  230. x = pnt2[0] - dt * uY
  231. y = pnt2[1] + dt * uX
  232. bisectorNormalLeft = [x, y]
  233. }
  234. } else {
  235. x = pnt2[0] + t * (pnt1[0] - pnt2[0])
  236. y = pnt2[1] + t * (pnt1[1] - pnt2[1])
  237. bisectorNormalRight = [x, y]
  238. x = pnt2[0] + t * (pnt3[0] - pnt2[0])
  239. y = pnt2[1] + t * (pnt3[1] - pnt2[1])
  240. bisectorNormalLeft = [x, y]
  241. }
  242. return [bisectorNormalRight, bisectorNormalLeft]
  243. }
  244. /**
  245. * @param pnt1
  246. * @param pnt2
  247. * @param pnt3
  248. * @returns {number[]}
  249. */
  250. static getNormal(pnt1, pnt2, pnt3) {
  251. let dX1 = pnt1[0] - pnt2[0]
  252. let dY1 = pnt1[1] - pnt2[1]
  253. let d1 = Math.sqrt(dX1 * dX1 + dY1 * dY1)
  254. dX1 /= d1
  255. dY1 /= d1
  256. let dX2 = pnt3[0] - pnt2[0]
  257. let dY2 = pnt3[1] - pnt2[1]
  258. let d2 = Math.sqrt(dX2 * dX2 + dY2 * dY2)
  259. dX2 /= d2
  260. dY2 /= d2
  261. let uX = dX1 + dX2
  262. let uY = dY1 + dY2
  263. return [uX, uY]
  264. }
  265. /**
  266. * @param t
  267. * @param controlPoints
  268. * @returns {[]}
  269. */
  270. static getCurvePoints(t, controlPoints) {
  271. let leftControl = this.getLeftMostControlPoint(t, controlPoints)
  272. let normals = [leftControl]
  273. let pnt1, pnt2, pnt3, normalPoints
  274. for (let i = 0; i < controlPoints.length - 2; i++) {
  275. pnt1 = controlPoints[i]
  276. pnt2 = controlPoints[i + 1]
  277. pnt3 = controlPoints[i + 2]
  278. normalPoints = this.getBisectorNormals(t, pnt1, pnt2, pnt3)
  279. normals = normals.concat(normalPoints)
  280. }
  281. let rightControl = this.getRightMostControlPoint(t, controlPoints)
  282. normals.push(rightControl)
  283. let points = []
  284. for (let i = 0; i < controlPoints.length - 1; i++) {
  285. pnt1 = controlPoints[i]
  286. pnt2 = controlPoints[i + 1]
  287. points.push(pnt1)
  288. for (let t = 0; t < FITTING_COUNT; t++) {
  289. let pnt = this.getCubicValue(
  290. t / FITTING_COUNT,
  291. pnt1,
  292. normals[i * 2],
  293. normals[i * 2 + 1],
  294. pnt2
  295. )
  296. points.push(pnt)
  297. }
  298. points.push(pnt2)
  299. }
  300. return points
  301. }
  302. /**
  303. * @param t
  304. * @param controlPoints
  305. * @returns {number[]}
  306. */
  307. static getLeftMostControlPoint(t, controlPoints) {
  308. let pnt1 = controlPoints[0]
  309. let pnt2 = controlPoints[1]
  310. let pnt3 = controlPoints[2]
  311. let pnts = this.getBisectorNormals(0, pnt1, pnt2, pnt3)
  312. let normalRight = pnts[0]
  313. let normal = this.getNormal(pnt1, pnt2, pnt3)
  314. let dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1])
  315. let controlX, controlY
  316. if (dist > ZERO_TOLERANCE) {
  317. let mid = this.mid(pnt1, pnt2)
  318. let pX = pnt1[0] - mid[0]
  319. let pY = pnt1[1] - mid[1]
  320. let d1 = this.distance(pnt1, pnt2)
  321. // normal at midpoint
  322. let n = 2.0 / d1
  323. let nX = -n * pY
  324. let nY = n * pX
  325. // upper triangle of symmetric transform matrix
  326. let a11 = nX * nX - nY * nY
  327. let a12 = 2 * nX * nY
  328. let a22 = nY * nY - nX * nX
  329. let dX = normalRight[0] - mid[0]
  330. let dY = normalRight[1] - mid[1]
  331. // coordinates of reflected vector
  332. controlX = mid[0] + a11 * dX + a12 * dY
  333. controlY = mid[1] + a12 * dX + a22 * dY
  334. } else {
  335. controlX = pnt1[0] + t * (pnt2[0] - pnt1[0])
  336. controlY = pnt1[1] + t * (pnt2[1] - pnt1[1])
  337. }
  338. return [controlX, controlY]
  339. }
  340. /**
  341. * @param t
  342. * @param controlPoints
  343. * @returns {number[]}
  344. */
  345. static getRightMostControlPoint(t, controlPoints) {
  346. let count = controlPoints.length
  347. let pnt1 = controlPoints[count - 3]
  348. let pnt2 = controlPoints[count - 2]
  349. let pnt3 = controlPoints[count - 1]
  350. let pnts = this.getBisectorNormals(0, pnt1, pnt2, pnt3)
  351. let normalLeft = pnts[1]
  352. let normal = this.getNormal(pnt1, pnt2, pnt3)
  353. let dist = Math.sqrt(normal[0] * normal[0] + normal[1] * normal[1])
  354. let controlX, controlY
  355. if (dist > ZERO_TOLERANCE) {
  356. let mid = this.mid(pnt2, pnt3)
  357. let pX = pnt3[0] - mid[0]
  358. let pY = pnt3[1] - mid[1]
  359. let d1 = this.distance(pnt2, pnt3)
  360. // normal at midpoint
  361. let n = 2.0 / d1
  362. let nX = -n * pY
  363. let nY = n * pX
  364. // upper triangle of symmetric transform matrix
  365. let a11 = nX * nX - nY * nY
  366. let a12 = 2 * nX * nY
  367. let a22 = nY * nY - nX * nX
  368. let dX = normalLeft[0] - mid[0]
  369. let dY = normalLeft[1] - mid[1]
  370. // coordinates of reflected vector
  371. controlX = mid[0] + a11 * dX + a12 * dY
  372. controlY = mid[1] + a12 * dX + a22 * dY
  373. } else {
  374. controlX = pnt3[0] + t * (pnt2[0] - pnt3[0])
  375. controlY = pnt3[1] + t * (pnt2[1] - pnt3[1])
  376. }
  377. return [controlX, controlY]
  378. }
  379. /**
  380. * @param points
  381. * @returns {[]|*}
  382. */
  383. static getBezierPoints(points) {
  384. if (points.length <= 2) return points
  385. let bezierPoints = []
  386. let n = points.length - 1
  387. for (let t = 0; t <= 1; t += 0.01) {
  388. let x = 0
  389. let y = 0
  390. for (let index = 0; index <= n; index++) {
  391. let factor = this.getBinomialFactor(n, index)
  392. let a = Math.pow(t, index)
  393. let b = Math.pow(1 - t, n - index)
  394. x += factor * a * b * points[index][0]
  395. y += factor * a * b * points[index][1]
  396. }
  397. bezierPoints.push([x, y])
  398. }
  399. bezierPoints.push(points[n])
  400. return bezierPoints
  401. }
  402. /**
  403. *
  404. * @param n
  405. * @param index
  406. * @returns {number}
  407. */
  408. static getBinomialFactor(n, index) {
  409. return (
  410. this.getFactorial(n) /
  411. (this.getFactorial(index) * this.getFactorial(n - index))
  412. )
  413. }
  414. /**
  415. * @param n
  416. * @returns {number}
  417. */
  418. static getFactorial(n) {
  419. if (n <= 1) return 1
  420. if (n === 2) return 2
  421. if (n === 3) return 6
  422. if (n === 4) return 24
  423. if (n === 5) return 120
  424. let result = 1
  425. for (let i = 1; i <= n; i++) result *= i
  426. return result
  427. }
  428. /**
  429. * @param points
  430. * @returns {[]|*}
  431. */
  432. static getQBSplinePoints(points) {
  433. if (points.length <= 2) return points
  434. let n = 2
  435. let bSplinePoints = []
  436. let m = points.length - n - 1
  437. bSplinePoints.push(points[0])
  438. for (let i = 0; i <= m; i++) {
  439. for (let t = 0; t <= 1; t += 0.05) {
  440. let x = 0
  441. let y = 0
  442. for (let k = 0; k <= n; k++) {
  443. let factor = this.getQuadricBSplineFactor(k, t)
  444. x += factor * points[i + k][0]
  445. y += factor * points[i + k][1]
  446. }
  447. bSplinePoints.push([x, y])
  448. }
  449. }
  450. bSplinePoints.push(points[points.length - 1])
  451. return bSplinePoints
  452. }
  453. /**
  454. * @param k
  455. * @param t
  456. * @returns {number}
  457. */
  458. static getQuadricBSplineFactor(k, t) {
  459. if (k === 0) return Math.pow(t - 1, 2) / 2
  460. if (k === 1) return (-2 * Math.pow(t, 2) + 2 * t + 1) / 2
  461. if (k === 2) return Math.pow(t, 2) / 2
  462. return 0
  463. }
  464. }
  465. export default PlotUtil