f3225d21dd3e597176b2bc8a47189ccdf5a17553d4d3ff683bf523e6da7af05ff90531151353eafd410dd22ac9e6ddcc9db2e329ecd43f2e33ff87d743ebde 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826
  1. import beziercurve from '@jiaminghi/bezier-curve'
  2. import {
  3. deepClone,
  4. eliminateBlur,
  5. checkPointIsInCircle,
  6. getTwoPointDistance,
  7. checkPointIsInSector,
  8. getRegularPolygonPoints,
  9. checkPointIsInPolygon,
  10. checkPointIsNearPolyline,
  11. checkPointIsInRect
  12. } from '../plugin/util'
  13. import {
  14. drawPolylinePath,
  15. drawBezierCurvePath
  16. } from '../plugin/canvas'
  17. const { polylineToBezierCurve, bezierCurveToPolyline } = beziercurve
  18. export const circle = {
  19. shape: {
  20. rx: 0,
  21. ry: 0,
  22. r: 0
  23. },
  24. validator ({ shape }) {
  25. const { rx, ry, r } = shape
  26. if (typeof rx !== 'number' || typeof ry !== 'number' || typeof r !== 'number') {
  27. console.error('Circle shape configuration is abnormal!')
  28. return false
  29. }
  30. return true
  31. },
  32. draw ({ ctx }, { shape }) {
  33. ctx.beginPath()
  34. const { rx, ry, r } = shape
  35. ctx.arc(rx, ry, r > 0 ? r : 0.01, 0, Math.PI * 2)
  36. ctx.fill()
  37. ctx.stroke()
  38. ctx.closePath()
  39. },
  40. hoverCheck (position, { shape }) {
  41. const { rx, ry, r } = shape
  42. return checkPointIsInCircle(position, rx, ry, r)
  43. },
  44. setGraphCenter (e, { shape, style }) {
  45. const { rx, ry } = shape
  46. style.graphCenter = [rx, ry]
  47. },
  48. move ({ movementX, movementY }, { shape }) {
  49. this.attr('shape', {
  50. rx: shape.rx + movementX,
  51. ry: shape.ry + movementY
  52. })
  53. }
  54. }
  55. export const ellipse = {
  56. shape: {
  57. rx: 0,
  58. ry: 0,
  59. hr: 0,
  60. vr: 0
  61. },
  62. validator ({ shape }) {
  63. const { rx, ry, hr, vr } = shape
  64. if (typeof rx !== 'number' || typeof ry !== 'number' || typeof hr !== 'number' || typeof vr !== 'number') {
  65. console.error('Ellipse shape configuration is abnormal!')
  66. return false
  67. }
  68. return true
  69. },
  70. draw ({ ctx }, { shape }) {
  71. ctx.beginPath()
  72. let { rx, ry, hr, vr } = shape
  73. ctx.ellipse(rx, ry, hr > 0 ? hr : 0.01, vr > 0 ? vr : 0.01, 0, 0, Math.PI * 2)
  74. ctx.fill()
  75. ctx.stroke()
  76. ctx.closePath()
  77. },
  78. hoverCheck (position, { shape }) {
  79. const { rx, ry, hr, vr } = shape
  80. const a = Math.max(hr, vr)
  81. const b = Math.min(hr, vr)
  82. const c = Math.sqrt(a * a - b * b)
  83. const leftFocusPoint = [rx - c, ry]
  84. const rightFocusPoint = [rx + c, ry]
  85. const distance = getTwoPointDistance(position, leftFocusPoint) + getTwoPointDistance(position, rightFocusPoint)
  86. return distance <= 2 * a
  87. },
  88. setGraphCenter (e, { shape, style }) {
  89. const { rx, ry } = shape
  90. style.graphCenter = [rx, ry]
  91. },
  92. move ({ movementX, movementY }, { shape }) {
  93. this.attr('shape', {
  94. rx: shape.rx + movementX,
  95. ry: shape.ry + movementY
  96. })
  97. }
  98. }
  99. export const rect = {
  100. shape: {
  101. x: 0,
  102. y: 0,
  103. w: 0,
  104. h: 0
  105. },
  106. validator ({ shape }) {
  107. const { x, y, w, h } = shape
  108. if (typeof x !== 'number' || typeof y !== 'number' || typeof w !== 'number' || typeof h !== 'number') {
  109. console.error('Rect shape configuration is abnormal!')
  110. return false
  111. }
  112. return true
  113. },
  114. draw ({ ctx }, { shape }) {
  115. ctx.beginPath()
  116. let { x, y, w, h } = shape
  117. ctx.rect(x, y, w, h)
  118. ctx.fill()
  119. ctx.stroke()
  120. ctx.closePath()
  121. },
  122. hoverCheck (position, { shape }) {
  123. let { x, y, w, h } = shape
  124. return checkPointIsInRect(position, x, y, w, h)
  125. },
  126. setGraphCenter (e, { shape, style }) {
  127. const { x, y, w, h } = shape
  128. style.graphCenter = [x + w / 2, y + h / 2]
  129. },
  130. move ({ movementX, movementY }, { shape }) {
  131. this.attr('shape', {
  132. x: shape.x + movementX,
  133. y: shape.y + movementY
  134. })
  135. }
  136. }
  137. export const ring = {
  138. shape: {
  139. rx: 0,
  140. ry: 0,
  141. r: 0
  142. },
  143. validator ({ shape }) {
  144. const { rx, ry, r } = shape
  145. if (typeof rx !== 'number' || typeof ry !== 'number' || typeof r !== 'number') {
  146. console.error('Ring shape configuration is abnormal!')
  147. return false
  148. }
  149. return true
  150. },
  151. draw ({ ctx }, { shape }) {
  152. ctx.beginPath()
  153. const { rx, ry, r } = shape
  154. ctx.arc(rx, ry, r > 0 ? r : 0.01, 0, Math.PI * 2)
  155. ctx.stroke()
  156. ctx.closePath()
  157. },
  158. hoverCheck (position, { shape, style }) {
  159. const { rx, ry, r } = shape
  160. const { lineWidth } = style
  161. const halfLineWidth = lineWidth / 2
  162. const minDistance = r - halfLineWidth
  163. const maxDistance = r + halfLineWidth
  164. const distance = getTwoPointDistance(position, [rx, ry])
  165. return (distance >= minDistance && distance <= maxDistance)
  166. },
  167. setGraphCenter (e, { shape, style }) {
  168. const { rx, ry } = shape
  169. style.graphCenter = [rx, ry]
  170. },
  171. move ({ movementX, movementY }, { shape }) {
  172. this.attr('shape', {
  173. rx: shape.rx + movementX,
  174. ry: shape.ry + movementY
  175. })
  176. }
  177. }
  178. export const arc = {
  179. shape: {
  180. rx: 0,
  181. ry: 0,
  182. r: 0,
  183. startAngle: 0,
  184. endAngle: 0,
  185. clockWise: true
  186. },
  187. validator ({ shape }) {
  188. const keys = ['rx', 'ry', 'r', 'startAngle', 'endAngle']
  189. if (keys.find(key => typeof shape[key] !== 'number')) {
  190. console.error('Arc shape configuration is abnormal!')
  191. return false
  192. }
  193. return true
  194. },
  195. draw ({ ctx }, { shape }) {
  196. ctx.beginPath()
  197. const { rx, ry, r, startAngle, endAngle, clockWise } = shape
  198. ctx.arc(rx, ry, r > 0 ? r : 0.001, startAngle, endAngle, !clockWise)
  199. ctx.stroke()
  200. ctx.closePath()
  201. },
  202. hoverCheck (position, { shape, style }) {
  203. const { rx, ry, r, startAngle, endAngle, clockWise } = shape
  204. const { lineWidth } = style
  205. const halfLineWidth = lineWidth / 2
  206. const insideRadius = r - halfLineWidth
  207. const outsideRadius = r + halfLineWidth
  208. return !checkPointIsInSector(position, rx, ry, insideRadius, startAngle, endAngle, clockWise) &&
  209. checkPointIsInSector(position, rx, ry, outsideRadius, startAngle, endAngle, clockWise)
  210. },
  211. setGraphCenter (e, { shape, style }) {
  212. const { rx, ry } = shape
  213. style.graphCenter = [rx, ry]
  214. },
  215. move ({ movementX, movementY }, { shape }) {
  216. this.attr('shape', {
  217. rx: shape.rx + movementX,
  218. ry: shape.ry + movementY
  219. })
  220. }
  221. }
  222. export const sector = {
  223. shape: {
  224. rx: 0,
  225. ry: 0,
  226. r: 0,
  227. startAngle: 0,
  228. endAngle: 0,
  229. clockWise: true
  230. },
  231. validator ({ shape }) {
  232. const keys = ['rx', 'ry', 'r', 'startAngle', 'endAngle']
  233. if (keys.find(key => typeof shape[key] !== 'number')) {
  234. console.error('Sector shape configuration is abnormal!')
  235. return false
  236. }
  237. return true
  238. },
  239. draw ({ ctx }, { shape }) {
  240. ctx.beginPath()
  241. const { rx, ry, r, startAngle, endAngle, clockWise } = shape
  242. ctx.arc(rx, ry, r > 0 ? r : 0.01, startAngle, endAngle, !clockWise)
  243. ctx.lineTo(rx, ry)
  244. ctx.closePath()
  245. ctx.stroke()
  246. ctx.fill()
  247. },
  248. hoverCheck (position, { shape }) {
  249. const { rx, ry, r, startAngle, endAngle, clockWise } = shape
  250. return checkPointIsInSector(position, rx, ry, r, startAngle, endAngle, clockWise)
  251. },
  252. setGraphCenter (e, { shape, style }) {
  253. const { rx, ry } = shape
  254. style.graphCenter = [rx, ry]
  255. },
  256. move ({ movementX, movementY }, { shape }) {
  257. const { rx, ry } = shape
  258. this.attr('shape', {
  259. rx: rx + movementX,
  260. ry: ry + movementY
  261. })
  262. }
  263. }
  264. export const regPolygon = {
  265. shape: {
  266. rx: 0,
  267. ry: 0,
  268. r: 0,
  269. side: 0
  270. },
  271. validator ({ shape }) {
  272. const { side } = shape
  273. const keys = ['rx', 'ry', 'r', 'side']
  274. if (keys.find(key => typeof shape[key] !== 'number')) {
  275. console.error('RegPolygon shape configuration is abnormal!')
  276. return false
  277. }
  278. if (side < 3) {
  279. console.error('RegPolygon at least trigon!')
  280. return false
  281. }
  282. return true
  283. },
  284. draw ({ ctx }, { shape, cache }) {
  285. ctx.beginPath()
  286. const { rx, ry, r, side } = shape
  287. if (!cache.points || cache.rx !== rx || cache.ry !== ry || cache.r !== r || cache.side !== side) {
  288. const points = getRegularPolygonPoints(rx, ry, r, side)
  289. Object.assign(cache, { points, rx, ry, r, side })
  290. }
  291. const { points } = cache
  292. drawPolylinePath(ctx, points)
  293. ctx.closePath()
  294. ctx.stroke()
  295. ctx.fill()
  296. },
  297. hoverCheck (position, { cache }) {
  298. let { points } = cache
  299. return checkPointIsInPolygon(position, points)
  300. },
  301. setGraphCenter (e, { shape, style }) {
  302. const { rx, ry } = shape
  303. style.graphCenter = [rx, ry]
  304. },
  305. move ({ movementX, movementY }, { shape, cache }) {
  306. const { rx, ry } = shape
  307. cache.rx += movementX
  308. cache.ry += movementY
  309. this.attr('shape', {
  310. rx: rx + movementX,
  311. ry: ry + movementY
  312. })
  313. cache.points = cache.points.map(([x, y]) => [x + movementX, y + movementY])
  314. }
  315. }
  316. export const polyline = {
  317. shape: {
  318. points: [],
  319. close: false
  320. },
  321. validator ({ shape }) {
  322. const { points } = shape
  323. if (!(points instanceof Array)) {
  324. console.error('Polyline points should be an array!')
  325. return false
  326. }
  327. return true
  328. },
  329. draw ({ ctx }, { shape, style: { lineWidth } }) {
  330. ctx.beginPath()
  331. let { points, close } = shape
  332. if (lineWidth === 1) points = eliminateBlur(points)
  333. drawPolylinePath(ctx, points)
  334. if (close) {
  335. ctx.closePath()
  336. ctx.fill()
  337. ctx.stroke()
  338. } else {
  339. ctx.stroke()
  340. }
  341. },
  342. hoverCheck (position, { shape, style }) {
  343. const { points, close } = shape
  344. const { lineWidth } = style
  345. if (close) {
  346. return checkPointIsInPolygon(position, points)
  347. } else {
  348. return checkPointIsNearPolyline(position, points, lineWidth)
  349. }
  350. },
  351. setGraphCenter (e, { shape, style }) {
  352. const { points } = shape
  353. style.graphCenter = points[0]
  354. },
  355. move ({ movementX, movementY }, { shape }) {
  356. const { points } = shape
  357. const moveAfterPoints = points.map(([x, y]) => [x + movementX, y + movementY])
  358. this.attr('shape', {
  359. points: moveAfterPoints
  360. })
  361. }
  362. }
  363. export const smoothline = {
  364. shape: {
  365. points: [],
  366. close: false
  367. },
  368. validator ({ shape }) {
  369. const { points } = shape
  370. if (!(points instanceof Array)) {
  371. console.error('Smoothline points should be an array!')
  372. return false
  373. }
  374. return true
  375. },
  376. draw ({ ctx }, { shape, cache }) {
  377. const { points, close } = shape
  378. if (!cache.points || cache.points.toString() !== points.toString()) {
  379. const bezierCurve = polylineToBezierCurve(points, close)
  380. const hoverPoints = bezierCurveToPolyline(bezierCurve)
  381. Object.assign(cache, {
  382. points: deepClone(points, true),
  383. bezierCurve,
  384. hoverPoints
  385. })
  386. }
  387. const { bezierCurve } = cache
  388. ctx.beginPath()
  389. drawBezierCurvePath(ctx, bezierCurve.slice(1), bezierCurve[0])
  390. if (close) {
  391. ctx.closePath()
  392. ctx.fill()
  393. ctx.stroke()
  394. } else {
  395. ctx.stroke()
  396. }
  397. },
  398. hoverCheck (position, { cache, shape, style }) {
  399. const { hoverPoints } = cache
  400. const { close } = shape
  401. const { lineWidth } = style
  402. if (close) {
  403. return checkPointIsInPolygon(position, hoverPoints)
  404. } else {
  405. return checkPointIsNearPolyline(position, hoverPoints, lineWidth)
  406. }
  407. },
  408. setGraphCenter (e, { shape, style }) {
  409. const { points } = shape
  410. style.graphCenter = points[0]
  411. },
  412. move ({ movementX, movementY }, { shape, cache }) {
  413. const { points } = shape
  414. const moveAfterPoints = points.map(([x, y]) => [x + movementX, y + movementY])
  415. cache.points = moveAfterPoints
  416. const [fx, fy] = cache.bezierCurve[0]
  417. const curves = cache.bezierCurve.slice(1)
  418. cache.bezierCurve = [
  419. [fx + movementX, fy + movementY],
  420. ...curves.map(curve => curve.map(([x, y]) => [x + movementX, y + movementY]))
  421. ]
  422. cache.hoverPoints = cache.hoverPoints.map(([x, y]) => [x + movementX, y + movementY])
  423. this.attr('shape', {
  424. points: moveAfterPoints
  425. })
  426. }
  427. }
  428. export const bezierCurve = {
  429. shape: {
  430. points: [],
  431. close: false
  432. },
  433. validator ({ shape }) {
  434. const { points } = shape
  435. if (!(points instanceof Array)) {
  436. console.error('BezierCurve points should be an array!')
  437. return false
  438. }
  439. return true
  440. },
  441. draw ({ ctx }, { shape, cache }) {
  442. let { points, close } = shape
  443. if (!cache.points || cache.points.toString() !== points.toString()) {
  444. const hoverPoints = bezierCurveToPolyline(points, 20)
  445. Object.assign(cache, {
  446. points: deepClone(points, true),
  447. hoverPoints
  448. })
  449. }
  450. ctx.beginPath()
  451. drawBezierCurvePath(ctx, points.slice(1), points[0])
  452. if (close) {
  453. ctx.closePath()
  454. ctx.fill()
  455. ctx.stroke()
  456. } else {
  457. ctx.stroke()
  458. }
  459. },
  460. hoverCheck (position, { cache, shape, style }) {
  461. const { hoverPoints } = cache
  462. const { close } = shape
  463. const { lineWidth } = style
  464. if (close) {
  465. return checkPointIsInPolygon(position, hoverPoints)
  466. } else {
  467. return checkPointIsNearPolyline(position, hoverPoints, lineWidth)
  468. }
  469. },
  470. setGraphCenter (e, { shape, style }) {
  471. const { points } = shape
  472. style.graphCenter = points[0]
  473. },
  474. move ({ movementX, movementY }, { shape, cache }) {
  475. const { points } = shape
  476. const [fx, fy] = points[0]
  477. const curves = points.slice(1)
  478. const bezierCurve = [
  479. [fx + movementX, fy + movementY],
  480. ...curves.map(curve => curve.map(([x, y]) => [x + movementX, y + movementY]))
  481. ]
  482. cache.points = bezierCurve
  483. cache.hoverPoints = cache.hoverPoints.map(([x, y]) => [x + movementX, y + movementY])
  484. this.attr('shape', {
  485. points: bezierCurve
  486. })
  487. }
  488. }
  489. export const text = {
  490. shape: {
  491. content: '',
  492. position: [],
  493. maxWidth: undefined,
  494. rowGap: 0
  495. },
  496. validator ({ shape }) {
  497. const { content, position, rowGap } = shape
  498. if (typeof content !== 'string') {
  499. console.error('Text content should be a string!')
  500. return false
  501. }
  502. if (!(position instanceof Array)) {
  503. console.error('Text position should be an array!')
  504. return false
  505. }
  506. if (typeof rowGap !== 'number') {
  507. console.error('Text rowGap should be a number!')
  508. return false
  509. }
  510. return true
  511. },
  512. draw ({ ctx }, { shape }) {
  513. let { content, position, maxWidth, rowGap } = shape
  514. const { textBaseline, font } = ctx
  515. const fontSize = parseInt(font.replace(/\D/g, ''))
  516. let [x, y] = position
  517. content = content.split('\n')
  518. const rowNum = content.length
  519. const lineHeight = fontSize + rowGap
  520. const allHeight = rowNum * lineHeight - rowGap
  521. let offset = 0
  522. if (textBaseline === 'middle') {
  523. offset = allHeight / 2
  524. y += fontSize / 2
  525. }
  526. if (textBaseline === 'bottom') {
  527. offset = allHeight
  528. y += fontSize
  529. }
  530. position = new Array(rowNum).fill(0).map((foo, i) => [x, y + i * lineHeight - offset])
  531. ctx.beginPath()
  532. content.forEach((text, i) => {
  533. ctx.fillText(text, ...position[i], maxWidth)
  534. ctx.strokeText(text, ...position[i], maxWidth)
  535. })
  536. ctx.closePath()
  537. },
  538. hoverCheck (position, { shape, style }) {
  539. return false
  540. },
  541. setGraphCenter (e, { shape, style }) {
  542. const { position } = shape
  543. style.graphCenter = [...position]
  544. },
  545. move ({ movementX, movementY }, { shape }) {
  546. const { position: [x, y] } = shape
  547. this.attr('shape', {
  548. position: [x + movementX, y + movementY]
  549. })
  550. }
  551. }
  552. const graphs = new Map([
  553. ['circle', circle],
  554. ['ellipse', ellipse],
  555. ['rect', rect],
  556. ['ring', ring],
  557. ['arc', arc],
  558. ['sector', sector],
  559. ['regPolygon', regPolygon],
  560. ['polyline', polyline],
  561. ['smoothline', smoothline],
  562. ['bezierCurve', bezierCurve],
  563. ['text', text]
  564. ])
  565. export default graphs
  566. /**
  567. * @description Extend new graph
  568. * @param {String} name Name of Graph
  569. * @param {Object} config Configuration of Graph
  570. * @return {Undefined} Void
  571. */
  572. export function extendNewGraph (name, config) {
  573. if (!name || !config) {
  574. console.error('ExtendNewGraph Missing Parameters!')
  575. return
  576. }
  577. if (!config.shape) {
  578. console.error('Required attribute of shape to extendNewGraph!')
  579. return
  580. }
  581. if (!config.validator) {
  582. console.error('Required function of validator to extendNewGraph!')
  583. return
  584. }
  585. if (!config.draw) {
  586. console.error('Required function of draw to extendNewGraph!')
  587. return
  588. }
  589. graphs.set(name, config)
  590. }