Browse Source

balances dashes for ellipses

main
Steve Ruiz 4 years ago
parent
commit
f6c08508dc
4 changed files with 85 additions and 26 deletions
  1. 28
    21
      state/shape-utils/ellipse.tsx
  2. 1
    0
      state/shape-utils/rectangle.tsx
  3. 4
    5
      state/state.ts
  4. 52
    0
      utils/utils.ts

+ 28
- 21
state/shape-utils/ellipse.tsx View File

@@ -1,4 +1,4 @@
1
-import { uniqueId } from 'utils/utils'
1
+import { uniqueId, getPerfectEllipseDashProps } from 'utils/utils'
2 2
 import vec from 'utils/vec'
3 3
 import { DashStyle, EllipseShape, ShapeType } from 'types'
4 4
 import { getShapeUtils } from './index'
@@ -6,11 +6,7 @@ import { boundsContained, getRotatedEllipseBounds } from 'utils/bounds'
6 6
 import { intersectEllipseBounds } from 'utils/intersections'
7 7
 import { pointInEllipse } from 'utils/hitTests'
8 8
 import { ease, getSvgPathFromStroke, rng, translateBounds } from 'utils/utils'
9
-import {
10
-  defaultStyle,
11
-  getShapeStyle,
12
-  getStrokeDashArray,
13
-} from 'state/shape-styles'
9
+import { defaultStyle, getShapeStyle } from 'state/shape-styles'
14 10
 import getStroke from 'perfect-freehand'
15 11
 import { registerShapeUtils } from './register'
16 12
 
@@ -43,6 +39,7 @@ const ellipse = registerShapeUtils<EllipseShape>({
43 39
   render(shape) {
44 40
     const { id, radiusX, radiusY, style } = shape
45 41
     const styles = getShapeStyle(style)
42
+    const strokeWidth = +styles.strokeWidth
46 43
 
47 44
     if (style.dash === DashStyle.Solid) {
48 45
       if (!pathCache.has(shape)) {
@@ -57,8 +54,8 @@ const ellipse = registerShapeUtils<EllipseShape>({
57 54
             id={id}
58 55
             cx={radiusX}
59 56
             cy={radiusY}
60
-            rx={Math.max(0, radiusX - +styles.strokeWidth / 2)}
61
-            ry={Math.max(0, radiusY - +styles.strokeWidth / 2)}
57
+            rx={Math.max(0, radiusX - strokeWidth / 2)}
58
+            ry={Math.max(0, radiusY - strokeWidth / 2)}
62 59
             stroke="none"
63 60
           />
64 61
           <path d={path} fill={styles.stroke} />
@@ -66,20 +63,30 @@ const ellipse = registerShapeUtils<EllipseShape>({
66 63
       )
67 64
     }
68 65
 
66
+    const rx = Math.max(0, radiusX - strokeWidth / 2)
67
+    const ry = Math.max(0, radiusY - strokeWidth / 2)
68
+
69
+    const { strokeDasharray, strokeDashoffset } = getPerfectEllipseDashProps(
70
+      rx,
71
+      ry,
72
+      strokeWidth,
73
+      shape.style.dash === DashStyle.Dotted ? 'dotted' : 'dashed'
74
+    )
75
+
69 76
     return (
70
-      <ellipse
71
-        id={id}
72
-        cx={radiusX}
73
-        cy={radiusY}
74
-        rx={Math.max(0, radiusX - +styles.strokeWidth / 2)}
75
-        ry={Math.max(0, radiusY - +styles.strokeWidth / 2)}
76
-        fill={styles.fill}
77
-        stroke={styles.stroke}
78
-        strokeDasharray={getStrokeDashArray(
79
-          style.dash,
80
-          +styles.strokeWidth
81
-        ).join(' ')}
82
-      />
77
+      <g id={id}>
78
+        <ellipse
79
+          id={id}
80
+          cx={radiusX}
81
+          cy={radiusY}
82
+          rx={rx}
83
+          ry={ry}
84
+          fill={styles.fill}
85
+          stroke={styles.stroke}
86
+          strokeDasharray={strokeDasharray}
87
+          strokeDashoffset={strokeDashoffset}
88
+        />
89
+      </g>
83 90
     )
84 91
   },
85 92
 

+ 1
- 0
state/shape-utils/rectangle.tsx View File

@@ -82,6 +82,7 @@ const rectangle = registerShapeUtils<RectangleShape>({
82 82
           style.dash,
83 83
           +styles.strokeWidth
84 84
         ).join(' ')}
85
+        strokeDashoffset={-(size[0] + size[1])}
85 86
       />
86 87
     )
87 88
   },

+ 4
- 5
state/state.ts View File

@@ -929,12 +929,11 @@ const state = createState({
929 929
       return data.hoveredId === payload.target
930 930
     },
931 931
     pointInSelectionBounds(data, payload: PointerInfo) {
932
-      if (getSelectedIds(data).size === 0) return false
932
+      const bounds = getSelectionBounds(data)
933 933
 
934
-      return pointInBounds(
935
-        screenToWorld(payload.point, data),
936
-        getSelectionBounds(data)
937
-      )
934
+      if (!bounds) return false
935
+
936
+      return pointInBounds(screenToWorld(payload.point, data), bounds)
938 937
     },
939 938
     pointHitsShape(data, payload: PointerInfo) {
940 939
       const shape = getShape(data, payload.target)

+ 52
- 0
utils/utils.ts View File

@@ -1791,3 +1791,55 @@ export function updateParents(data: Data, changedShapeIds: string[]): void {
1791 1791
 
1792 1792
   updateParents(data, parentToUpdateIds)
1793 1793
 }
1794
+
1795
+export function perimeterOfEllipse(rx: number, ry: number): number {
1796
+  const h = Math.pow(rx - ry, 2) / Math.pow(rx + ry, 2)
1797
+  const p = Math.PI * (rx + ry) * (1 + (3 * h) / (10 + Math.sqrt(4 - 3 * h)))
1798
+  return p
1799
+}
1800
+
1801
+/**
1802
+ * Get the stroke-dasharray and stroke-dashoffset properties for a dashed or dotted ellipse.
1803
+ * @param rx The radius of the ellipse on the x axis.
1804
+ * @param ry The radius of the ellipse on the y axis.
1805
+ * @param strokeWidth The shape's stroke-width property.
1806
+ * @param style "dashed" or "dotted" (default "dashed")
1807
+ */
1808
+export function getPerfectEllipseDashProps(
1809
+  rx: number,
1810
+  ry: number,
1811
+  strokeWidth: number,
1812
+  style: 'dashed' | 'dotted' = 'dashed'
1813
+) {
1814
+  let dashLength: number
1815
+  let strokeDashoffset: number
1816
+  let ratio: number
1817
+
1818
+  if (style === 'dashed') {
1819
+    dashLength = strokeWidth * 2
1820
+    ratio = 1
1821
+    strokeDashoffset = dashLength / 2
1822
+  } else {
1823
+    dashLength = strokeWidth / 4
1824
+    ratio = 4
1825
+    strokeDashoffset = 0
1826
+  }
1827
+
1828
+  // Find perimeter of the ellipse
1829
+  const h = Math.pow(rx - ry, 2) / Math.pow(rx + ry, 2)
1830
+  const perimeter =
1831
+    Math.PI * (rx + ry) * (1 + (3 * h) / (10 + Math.sqrt(4 - 3 * h)))
1832
+
1833
+  // Find the number of dashes (with one more for good measure)
1834
+  let dashes = perimeter / dashLength / (2 * ratio)
1835
+  dashes = dashes - (dashes % 4)
1836
+  // dashes++
1837
+
1838
+  // Find the gap length
1839
+  const gapLength = (perimeter - dashes * dashLength) / dashes
1840
+
1841
+  return {
1842
+    strokeDasharray: [dashLength, gapLength].join(' '),
1843
+    strokeDashoffset,
1844
+  }
1845
+}

Loading…
Cancel
Save