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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import { v4 as uuid } from 'uuid'
  2. import * as vec from 'utils/vec'
  3. import { EllipseShape, ShapeType } from 'types'
  4. import { registerShapeUtils } from './index'
  5. import { boundsContained, getRotatedEllipseBounds } from 'utils/bounds'
  6. import { intersectEllipseBounds } from 'utils/intersections'
  7. import { pointInEllipse } from 'utils/hitTests'
  8. import {
  9. getBoundsFromPoints,
  10. getRotatedCorners,
  11. rotateBounds,
  12. translateBounds,
  13. } from 'utils/utils'
  14. const ellipse = registerShapeUtils<EllipseShape>({
  15. boundsCache: new WeakMap([]),
  16. create(props) {
  17. return {
  18. id: uuid(),
  19. type: ShapeType.Ellipse,
  20. isGenerated: false,
  21. name: 'Ellipse',
  22. parentId: 'page0',
  23. childIndex: 0,
  24. point: [0, 0],
  25. radiusX: 1,
  26. radiusY: 1,
  27. rotation: 0,
  28. isAspectRatioLocked: false,
  29. isLocked: false,
  30. isHidden: false,
  31. style: {
  32. fill: '#c6cacb',
  33. stroke: '#000',
  34. },
  35. ...props,
  36. }
  37. },
  38. render({ id, radiusX, radiusY, style }) {
  39. return (
  40. <ellipse
  41. id={id}
  42. cx={radiusX}
  43. cy={radiusY}
  44. rx={Math.max(0, radiusX - Number(style.strokeWidth) / 2)}
  45. ry={Math.max(0, radiusY - Number(style.strokeWidth) / 2)}
  46. />
  47. )
  48. },
  49. applyStyles(shape, style) {
  50. Object.assign(shape.style, style)
  51. return this
  52. },
  53. getBounds(shape) {
  54. if (!this.boundsCache.has(shape)) {
  55. const { radiusX, radiusY } = shape
  56. const bounds = {
  57. minX: 0,
  58. minY: 0,
  59. maxX: radiusX * 2,
  60. maxY: radiusY * 2,
  61. width: radiusX * 2,
  62. height: radiusY * 2,
  63. }
  64. this.boundsCache.set(shape, bounds)
  65. }
  66. return translateBounds(this.boundsCache.get(shape), shape.point)
  67. },
  68. getRotatedBounds(shape) {
  69. return getRotatedEllipseBounds(
  70. shape.point[0],
  71. shape.point[1],
  72. shape.radiusX,
  73. shape.radiusY,
  74. shape.rotation
  75. )
  76. },
  77. getCenter(shape) {
  78. return [shape.point[0] + shape.radiusX, shape.point[1] + shape.radiusY]
  79. },
  80. hitTest(shape, point) {
  81. return pointInEllipse(
  82. point,
  83. vec.add(shape.point, [shape.radiusX, shape.radiusY]),
  84. shape.radiusX,
  85. shape.radiusY,
  86. shape.rotation
  87. )
  88. },
  89. hitTestBounds(this, shape, brushBounds) {
  90. const shapeBounds = this.getBounds(shape)
  91. return (
  92. boundsContained(shapeBounds, brushBounds) ||
  93. intersectEllipseBounds(
  94. vec.add(shape.point, [shape.radiusX, shape.radiusY]),
  95. shape.radiusX,
  96. shape.radiusY,
  97. brushBounds,
  98. shape.rotation
  99. ).length > 0
  100. )
  101. },
  102. rotateTo(shape, rotation) {
  103. shape.rotation = rotation
  104. return this
  105. },
  106. translateTo(shape, point) {
  107. shape.point = vec.toPrecision(point)
  108. return this
  109. },
  110. transform(shape, bounds, { scaleX, scaleY, initialShape }) {
  111. // TODO: Locked aspect ratio transform
  112. shape.point = [bounds.minX, bounds.minY]
  113. shape.radiusX = bounds.width / 2
  114. shape.radiusY = bounds.height / 2
  115. shape.rotation =
  116. (scaleX < 0 && scaleY >= 0) || (scaleY < 0 && scaleX >= 0)
  117. ? -initialShape.rotation
  118. : initialShape.rotation
  119. return this
  120. },
  121. transformSingle(shape, bounds, info) {
  122. return this.transform(shape, bounds, info)
  123. },
  124. setProperty(shape, prop, value) {
  125. shape[prop] = value
  126. return this
  127. },
  128. canTransform: true,
  129. canChangeAspectRatio: true,
  130. })
  131. export default ellipse