Skip to content

Commit

Permalink
feat: candle.type: 'area' support smooth.
Browse files Browse the repository at this point in the history
  • Loading branch information
liihuu committed Mar 16, 2024
1 parent a9bea74 commit 944ab47
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 89 deletions.
9 changes: 4 additions & 5 deletions src/common/Styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,10 @@ export interface LineStyle {
dashedValue: number[]
}

export interface SmoothStyle {
export interface SmoothLineStyle extends LineStyle {
smooth: boolean | number
}

export type SmoothLineStyle = SmoothStyle & LineStyle

export interface StateLineStyle extends LineStyle {
show: boolean
}
Expand Down Expand Up @@ -174,6 +172,7 @@ export interface CandleAreaStyle {
lineSize: number
lineColor: string
value: string
smooth: boolean
backgroundColor: string | GradientColor[]
}

Expand Down Expand Up @@ -430,6 +429,7 @@ function getDefaultCandleStyle (): CandleStyle {
area: {
lineSize: 2,
lineColor: blue,
smooth: false,
value: 'close',
backgroundColor: [{
offset: 0,
Expand Down Expand Up @@ -514,8 +514,7 @@ function getDefaultCandleStyle (): CandleStyle {
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],
smooth: true,
size: 1,
dashedValue: [2, 2],
color
Expand Down
114 changes: 59 additions & 55 deletions src/extension/figure/line.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,66 @@ export function getLinearSlopeIntercept (coordinate1: Coordinate, coordinate2: C
return null
}

export function lineTo (ctx: CanvasRenderingContext2D, coordinates: Coordinate[], smooth: number | boolean): void {
const length = coordinates.length
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++) {
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
}
const lastCoordinate = coordinates[length - 1]
ctx.bezierCurveTo(cpx0, cpy0, lastCoordinate.x, lastCoordinate.y, lastCoordinate.x, lastCoordinate.y)
} else {
for (let i = 1; i < length; i++) {
ctx.lineTo(coordinates[i].x, coordinates[i].y)
}
}
}

export function drawLine (ctx: CanvasRenderingContext2D, attrs: LineAttrs, styles: Partial<SmoothLineStyle>): void {
const { coordinates } = attrs
const length = coordinates.length
if (length > 1) {
if (coordinates.length > 1) {
const { style = LineType.Solid, smooth = false, size = 1, color = 'currentColor', dashedValue = [2, 2] } = styles
ctx.lineWidth = size
ctx.strokeStyle = color
Expand All @@ -90,59 +146,7 @@ export function drawLine (ctx: CanvasRenderingContext2D, attrs: LineAttrs, style
}
ctx.beginPath()
ctx.moveTo(coordinates[0].x, coordinates[0].y)
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++) {
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
}
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)
}
}
lineTo(ctx, coordinates, smooth)
ctx.stroke()
ctx.closePath()
}
Expand Down
50 changes: 21 additions & 29 deletions src/view/CandleAreaView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import ChildrenView from './ChildrenView'

import { isNumber, isArray } from '../common/utils/typeChecks'

import { lineTo } from '../extension/figure/line'

export default class CandleAreaView extends ChildrenView {
override drawImp (ctx: CanvasRenderingContext2D): void {
const widget = this.getWidget()
Expand All @@ -29,49 +31,36 @@ export default class CandleAreaView extends ChildrenView {
const bounding = widget.getBounding()
const yAxis = pane.getAxisComponent()
const candleAreaStyles = chart.getStyles().candle.area
const lineCoordinates: Coordinate[] = []
const areaCoordinates: Coordinate[] = []
const coordinates: Coordinate[] = []
let minY = Number.MAX_SAFE_INTEGER
this.eachChildren((data: VisibleData, barSpace: BarSpace, i: number) => {
let areaStartX: number = 0
this.eachChildren((data: VisibleData, _: BarSpace, i: number) => {
const { data: kLineData, x } = data
const { halfGapBar } = barSpace
// const { halfGapBar } = barSpace
const value = kLineData?.[candleAreaStyles.value]
if (isNumber(value)) {
const y = yAxis.convertToPixel(value)
if (i === 0) {
const startX = x - halfGapBar
areaCoordinates.push({ x: startX, y: bounding.height })
areaCoordinates.push({ x: startX, y })
lineCoordinates.push({ x: startX, y })
areaStartX = x
}
lineCoordinates.push({ x, y })
areaCoordinates.push({ x, y })
coordinates.push({ x, y })
minY = Math.min(minY, y)
}
})
const areaCoordinateCount = areaCoordinates.length
if (areaCoordinateCount > 0) {
const lastCoordinate: Coordinate = areaCoordinates[areaCoordinateCount - 1]
const endX = lastCoordinate.x
lineCoordinates.push({ x: endX, y: lastCoordinate.y })
areaCoordinates.push({ x: endX, y: lastCoordinate.y })
areaCoordinates.push({ x: endX, y: bounding.height })
}

if (lineCoordinates.length > 0) {
if (coordinates.length > 0) {
this.createFigure({
name: 'line',
attrs: { coordinates: lineCoordinates },
attrs: { coordinates },
styles: {
color: candleAreaStyles.lineColor,
size: candleAreaStyles.lineSize
size: candleAreaStyles.lineSize,
smooth: candleAreaStyles.smooth
}
}
)?.draw(ctx)
}

if (areaCoordinates.length > 0) {
// Draw real-time background
// render area
const backgroundColor = candleAreaStyles.backgroundColor
let color: string | CanvasGradient
if (isArray<GradientColor>(backgroundColor)) {
Expand All @@ -86,11 +75,14 @@ export default class CandleAreaView extends ChildrenView {
} else {
color = backgroundColor
}
this.createFigure({
name: 'polygon',
attrs: { coordinates: areaCoordinates },
styles: { color }
})?.draw(ctx)
ctx.fillStyle = color
ctx.beginPath()
ctx.moveTo(areaStartX, bounding.height)
ctx.lineTo(coordinates[0].x, coordinates[0].y)
lineTo(ctx, coordinates, candleAreaStyles.smooth)
ctx.lineTo(coordinates[coordinates.length - 1].x, bounding.height)
ctx.closePath()
ctx.fill()
}
}
}

0 comments on commit 944ab47

Please sign in to comment.