balances dashes for ellipses

This commit is contained in:
Steve Ruiz 2021-06-22 15:28:44 +01:00
parent 03603b5190
commit f6c08508dc
4 changed files with 85 additions and 26 deletions

View file

@ -1,4 +1,4 @@
import { uniqueId } from 'utils/utils'
import { uniqueId, getPerfectEllipseDashProps } from 'utils/utils'
import vec from 'utils/vec'
import { DashStyle, EllipseShape, ShapeType } from 'types'
import { getShapeUtils } from './index'
@ -6,11 +6,7 @@ import { boundsContained, getRotatedEllipseBounds } from 'utils/bounds'
import { intersectEllipseBounds } from 'utils/intersections'
import { pointInEllipse } from 'utils/hitTests'
import { ease, getSvgPathFromStroke, rng, translateBounds } from 'utils/utils'
import {
defaultStyle,
getShapeStyle,
getStrokeDashArray,
} from 'state/shape-styles'
import { defaultStyle, getShapeStyle } from 'state/shape-styles'
import getStroke from 'perfect-freehand'
import { registerShapeUtils } from './register'
@ -43,6 +39,7 @@ const ellipse = registerShapeUtils<EllipseShape>({
render(shape) {
const { id, radiusX, radiusY, style } = shape
const styles = getShapeStyle(style)
const strokeWidth = +styles.strokeWidth
if (style.dash === DashStyle.Solid) {
if (!pathCache.has(shape)) {
@ -57,8 +54,8 @@ const ellipse = registerShapeUtils<EllipseShape>({
id={id}
cx={radiusX}
cy={radiusY}
rx={Math.max(0, radiusX - +styles.strokeWidth / 2)}
ry={Math.max(0, radiusY - +styles.strokeWidth / 2)}
rx={Math.max(0, radiusX - strokeWidth / 2)}
ry={Math.max(0, radiusY - strokeWidth / 2)}
stroke="none"
/>
<path d={path} fill={styles.stroke} />
@ -66,20 +63,30 @@ const ellipse = registerShapeUtils<EllipseShape>({
)
}
const rx = Math.max(0, radiusX - strokeWidth / 2)
const ry = Math.max(0, radiusY - strokeWidth / 2)
const { strokeDasharray, strokeDashoffset } = getPerfectEllipseDashProps(
rx,
ry,
strokeWidth,
shape.style.dash === DashStyle.Dotted ? 'dotted' : 'dashed'
)
return (
<g id={id}>
<ellipse
id={id}
cx={radiusX}
cy={radiusY}
rx={Math.max(0, radiusX - +styles.strokeWidth / 2)}
ry={Math.max(0, radiusY - +styles.strokeWidth / 2)}
rx={rx}
ry={ry}
fill={styles.fill}
stroke={styles.stroke}
strokeDasharray={getStrokeDashArray(
style.dash,
+styles.strokeWidth
).join(' ')}
strokeDasharray={strokeDasharray}
strokeDashoffset={strokeDashoffset}
/>
</g>
)
},

View file

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

View file

@ -929,12 +929,11 @@ const state = createState({
return data.hoveredId === payload.target
},
pointInSelectionBounds(data, payload: PointerInfo) {
if (getSelectedIds(data).size === 0) return false
const bounds = getSelectionBounds(data)
return pointInBounds(
screenToWorld(payload.point, data),
getSelectionBounds(data)
)
if (!bounds) return false
return pointInBounds(screenToWorld(payload.point, data), bounds)
},
pointHitsShape(data, payload: PointerInfo) {
const shape = getShape(data, payload.target)

View file

@ -1791,3 +1791,55 @@ export function updateParents(data: Data, changedShapeIds: string[]): void {
updateParents(data, parentToUpdateIds)
}
export function perimeterOfEllipse(rx: number, ry: number): number {
const h = Math.pow(rx - ry, 2) / Math.pow(rx + ry, 2)
const p = Math.PI * (rx + ry) * (1 + (3 * h) / (10 + Math.sqrt(4 - 3 * h)))
return p
}
/**
* Get the stroke-dasharray and stroke-dashoffset properties for a dashed or dotted ellipse.
* @param rx The radius of the ellipse on the x axis.
* @param ry The radius of the ellipse on the y axis.
* @param strokeWidth The shape's stroke-width property.
* @param style "dashed" or "dotted" (default "dashed")
*/
export function getPerfectEllipseDashProps(
rx: number,
ry: number,
strokeWidth: number,
style: 'dashed' | 'dotted' = 'dashed'
) {
let dashLength: number
let strokeDashoffset: number
let ratio: number
if (style === 'dashed') {
dashLength = strokeWidth * 2
ratio = 1
strokeDashoffset = dashLength / 2
} else {
dashLength = strokeWidth / 4
ratio = 4
strokeDashoffset = 0
}
// Find perimeter of the ellipse
const h = Math.pow(rx - ry, 2) / Math.pow(rx + ry, 2)
const perimeter =
Math.PI * (rx + ry) * (1 + (3 * h) / (10 + Math.sqrt(4 - 3 * h)))
// Find the number of dashes (with one more for good measure)
let dashes = perimeter / dashLength / (2 * ratio)
dashes = dashes - (dashes % 4)
// dashes++
// Find the gap length
const gapLength = (perimeter - dashes * dashLength) / dashes
return {
strokeDasharray: [dashLength, gapLength].join(' '),
strokeDashoffset,
}
}