diff --git a/src/common/Styles.ts b/src/common/Styles.ts index 4f8d53790..9025f7fb8 100644 --- a/src/common/Styles.ts +++ b/src/common/Styles.ts @@ -44,10 +44,12 @@ export interface LineStyle { dashedValue: number[] } -export interface SmoothLineStyle extends LineStyle { - smooth: boolean +export interface SmoothStyle { + smooth: boolean | number } +export type SmoothLineStyle = SmoothStyle & LineStyle + export interface StateLineStyle extends LineStyle { show: boolean } @@ -513,6 +515,7 @@ function getDefaultIndicatorStyle (): IndicatorStyle { const lines = ['#FF9600', '#935EBD', blue, '#E11D74', '#01C5C4'].map(color => ({ style: LineType.Solid, smooth: false, + smoothRange: [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER], size: 1, dashedValue: [2, 2], color diff --git a/src/extension/figure/line.ts b/src/extension/figure/line.ts index c77b2f280..9577c5522 100644 --- a/src/extension/figure/line.ts +++ b/src/extension/figure/line.ts @@ -17,26 +17,7 @@ import type Coordinate from '../../common/Coordinate' import { type SmoothLineStyle, LineType } from '../../common/Styles' import { type FigureTemplate, DEVIATION } from '../../component/Figure' - -function getDistance (coordinate1: Coordinate, coordinate2: Coordinate): number { - return Math.sqrt(Math.pow(coordinate1.x + coordinate2.x, 2) + Math.pow(coordinate1.y + coordinate2.y, 2)) -} - -function getSmoothControlCoordinate (coordinates: [Coordinate, Coordinate, Coordinate]): Coordinate[] { - const d01 = getDistance(coordinates[0], coordinates[1]) - const d12 = getDistance(coordinates[1], coordinates[2]) - const d02 = d01 + d12 - const vector = [coordinates[2].x - coordinates[0].x, coordinates[2].y - coordinates[0].y] - return [ - { - x: coordinates[1].x - vector[0] * 0.5 * d01 / d02, - y: coordinates[1].y - vector[1] * 0.5 * d01 / d02 - }, { - x: coordinates[1].x + vector[0] * 0.5 * d01 / d02, - y: coordinates[1].y + vector[1] * 0.5 * d01 / d02 - } - ] -} +import { isNumber } from '../../common/utils/typeChecks' export function checkCoordinateOnLine (coordinate: Coordinate, line: LineAttrs): boolean { const coordinates = line.coordinates @@ -68,7 +49,7 @@ export function checkCoordinateOnLine (coordinate: Coordinate, line: LineAttrs): } export function getLinearYFromSlopeIntercept (kb: Nullable, coordinate: Coordinate): number { - if (kb != null) { + if (kb !== null) { return coordinate.x * kb[0] + kb[1] } return coordinate.y @@ -99,7 +80,7 @@ export function drawLine (ctx: CanvasRenderingContext2D, attrs: LineAttrs, style const { coordinates } = attrs const length = coordinates.length if (length > 1) { - const { style = LineType.Solid, smooth, size = 1, color = 'currentColor', dashedValue = [2, 2] } = styles + const { style = LineType.Solid, smooth = false, size = 1, color = 'currentColor', dashedValue = [2, 2] } = styles ctx.lineWidth = size ctx.strokeStyle = color if (style === LineType.Dashed) { @@ -109,30 +90,54 @@ export function drawLine (ctx: CanvasRenderingContext2D, attrs: LineAttrs, style } ctx.beginPath() ctx.moveTo(coordinates[0].x, coordinates[0].y) - - if ((smooth ?? false) && length > 2) { - let controlCoordinates: Coordinate[] = [] + const smoothParam = isNumber(smooth) ? (smooth > 0 && smooth < 1 ? smooth : 0) : (smooth ? 0.5 : 0) + if ((smoothParam > 0) && length > 2) { + let cpx0 = coordinates[0].x + let cpy0 = coordinates[0].y for (let i = 1; i < length - 1; i++) { - controlCoordinates = controlCoordinates.concat(getSmoothControlCoordinate([coordinates[i - 1], coordinates[i], coordinates[i + 1]])) - } - ctx.quadraticCurveTo(controlCoordinates[0].x, controlCoordinates[0].y, coordinates[1].x, coordinates[1].y) - let i = 2 - for (; i < length - 1; i++) { - ctx.bezierCurveTo( - controlCoordinates[(i - 2) * 2 + 1].x, - controlCoordinates[(i - 2) * 2 + 1].y, - controlCoordinates[(i - 1) * 2].x, - controlCoordinates[(i - 1) * 2].y, - coordinates[i].x, - coordinates[i].y - ) + const prevCoordinate = coordinates[i - 1] + const coordinate = coordinates[i] + const nextCoordinate = coordinates[i + 1] + const dx01 = coordinate.x - prevCoordinate.x + const dy01 = coordinate.y - prevCoordinate.y + const dx12 = nextCoordinate.x - coordinate.x + const dy12 = nextCoordinate.y - coordinate.y + let dx02 = nextCoordinate.x - prevCoordinate.x + let dy02 = nextCoordinate.y - prevCoordinate.y + const prevSegmentLength = Math.sqrt(dx01 * dx01 + dy01 * dy01) + const nextSegmentLength = Math.sqrt(dx12 * dx12 + dy12 * dy12) + const segmentLengthRatio = nextSegmentLength / (nextSegmentLength + prevSegmentLength) + + let nextCpx = coordinate.x + dx02 * smoothParam * segmentLengthRatio + let nextCpy = coordinate.y + dy02 * smoothParam * segmentLengthRatio + nextCpx = Math.min(nextCpx, Math.max(nextCoordinate.x, coordinate.x)) + nextCpy = Math.min(nextCpy, Math.max(nextCoordinate.y, coordinate.y)) + nextCpx = Math.max(nextCpx, Math.min(nextCoordinate.x, coordinate.x)) + nextCpy = Math.max(nextCpy, Math.min(nextCoordinate.y, coordinate.y)) + + dx02 = nextCpx - coordinate.x + dy02 = nextCpy - coordinate.y + + let cpx1 = coordinate.x - dx02 * prevSegmentLength / nextSegmentLength + let cpy1 = coordinate.y - dy02 * prevSegmentLength / nextSegmentLength + + cpx1 = Math.min(cpx1, Math.max(prevCoordinate.x, coordinate.x)) + cpy1 = Math.min(cpy1, Math.max(prevCoordinate.y, coordinate.y)) + cpx1 = Math.max(cpx1, Math.min(prevCoordinate.x, coordinate.x)) + cpy1 = Math.max(cpy1, Math.min(prevCoordinate.y, coordinate.y)) + + dx02 = coordinate.x - cpx1 + dy02 = coordinate.y - cpy1 + nextCpx = coordinate.x + dx02 * nextSegmentLength / prevSegmentLength + nextCpy = coordinate.y + dy02 * nextSegmentLength / prevSegmentLength + + ctx.bezierCurveTo(cpx0, cpy0, cpx1, cpy1, coordinate.x, coordinate.y) + + cpx0 = nextCpx + cpy0 = nextCpy } - ctx.quadraticCurveTo( - controlCoordinates[(i - 2) * 2 + 1].x, - controlCoordinates[(i - 2) * 2 + 1].y, - coordinates[i].x, - coordinates[i].y - ) + const lastCoordinate = coordinates[length - 1] + ctx.bezierCurveTo(cpx0, cpy0, lastCoordinate.x, lastCoordinate.y, lastCoordinate.x, lastCoordinate.y) } else { for (let i = 1; i < coordinates.length; i++) { ctx.lineTo(coordinates[i].x, coordinates[i].y)