Skip to content

Commit

Permalink
opt: opt smooth line render
Browse files Browse the repository at this point in the history
  • Loading branch information
liihuu committed Mar 15, 2024
1 parent 6b82e29 commit a9bea74
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 46 deletions.
7 changes: 5 additions & 2 deletions src/common/Styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down
93 changes: 49 additions & 44 deletions src/extension/figure/line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -68,7 +49,7 @@ export function checkCoordinateOnLine (coordinate: Coordinate, line: LineAttrs):
}

export function getLinearYFromSlopeIntercept (kb: Nullable<number[]>, coordinate: Coordinate): number {
if (kb != null) {
if (kb !== null) {
return coordinate.x * kb[0] + kb[1]
}
return coordinate.y
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
Expand Down

0 comments on commit a9bea74

Please sign in to comment.