Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add enableSmoothing prop #100

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/AnimatedLineGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export function AnimatedLineGraph({
verticalPadding = lineThickness,
TopAxisLabel,
BottomAxisLabel,
disableSmoothing = false,
...props
}: AnimatedLineGraphProps): React.ReactElement {
const [width, setWidth] = useState(0)
Expand Down Expand Up @@ -169,7 +170,7 @@ export function AnimatedLineGraph({
() => Math.floor(lineWidth) + horizontalPadding
)
const indicatorY = useDerivedValue(
() => getYForX(commands.value, indicatorX.value) || 0
() => getYForX(commands.value, indicatorX.value, disableSmoothing) || 0
)

const indicatorPulseColor = useMemo(() => hexToRgba(color, 0.4), [color])
Expand All @@ -196,6 +197,7 @@ export function AnimatedLineGraph({
verticalPadding,
canvasHeight: height,
canvasWidth: width,
disableSmoothing,
}

if (shouldFillGradient) {
Expand Down Expand Up @@ -370,7 +372,7 @@ export function AnimatedLineGraph({
(fingerX: number) => {
'worklet'

const y = getYForX(commands.value, fingerX)
const y = getYForX(commands.value, fingerX, disableSmoothing)

if (y != null) {
circleX.value = fingerX
Expand Down
55 changes: 34 additions & 21 deletions src/CreateGraphPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ type GraphPathConfig = {
* Range of the graph's x and y-axis
*/
range: GraphPathRange
/**
* Disables smoothing of the graph line to increase accuracy of graph according to the dataset
*/
disableSmoothing: boolean
}

type GraphPathConfigWithGradient = GraphPathConfig & {
Expand Down Expand Up @@ -140,6 +144,7 @@ function createGraphPathBase({
canvasHeight: height,
canvasWidth: width,
shouldFillGradient,
disableSmoothing,
}: GraphPathConfigWithGradient | GraphPathConfigWithoutGradient):
| SkPath
| GraphPathWithGradient {
Expand Down Expand Up @@ -208,27 +213,35 @@ function createGraphPathBase({
for (let i = 0; i < points.length; i++) {
const point = points[i]!

// first point needs to start the path
if (i === 0) path.moveTo(point.x, point.y)

const prev = points[i - 1]
const prevPrev = points[i - 2]

if (prev == null) continue

const p0 = prevPrev ?? prev
const p1 = prev
const cp1x = (2 * p0.x + p1.x) / 3
const cp1y = (2 * p0.y + p1.y) / 3
const cp2x = (p0.x + 2 * p1.x) / 3
const cp2y = (p0.y + 2 * p1.y) / 3
const cp3x = (p0.x + 4 * p1.x + point.x) / 6
const cp3y = (p0.y + 4 * p1.y + point.y) / 6

path.cubicTo(cp1x, cp1y, cp2x, cp2y, cp3x, cp3y)

if (i === points.length - 1) {
path.cubicTo(point.x, point.y, point.x, point.y, point.x, point.y)
// Start the path or add a line directly to the next point
if (i === 0) {
path.moveTo(point.x, point.y)
} else {
if (disableSmoothing) {
// Direct line to the next point for no smoothing
path.lineTo(point.x, point.y)
} else {
// Continue using smoothing
const prev = points[i - 1]
const prevPrev = points[i - 2]

if (prev == null) continue

const p0 = prevPrev ?? prev
const p1 = prev
const cp1x = (2 * p0.x + p1.x) / 3
const cp1y = (2 * p0.y + p1.y) / 3
const cp2x = (p0.x + 2 * p1.x) / 3
const cp2y = (p0.y + 2 * p1.y) / 3
const cp3x = (p0.x + 4 * p1.x + point.x) / 6
const cp3y = (p0.y + 4 * p1.y + point.y) / 6

path.cubicTo(cp1x, cp1y, cp2x, cp2y, cp3x, cp3y)

if (i === points.length - 1) {
path.cubicTo(point.x, point.y, point.x, point.y, point.x, point.y)
}
}
}
}

Expand Down
99 changes: 94 additions & 5 deletions src/GetYForX.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import type { Vector, PathCommand } from '@shopify/react-native-skia'
import { PathVerb, vec } from '@shopify/react-native-skia'

// code from William Candillon
const GET_Y_FOR_X_PRECISION = 2

// code from William Candillon
const round = (value: number, precision = 0): number => {
'worklet'

Expand Down Expand Up @@ -156,15 +157,103 @@ export const selectCurve = (
return undefined
}

const linearInterpolation = (x: number, from: Vector, to: Vector): number => {
// Handles vertical lines or when 'from' and 'to' have the same x-coordinate
if (from.x === to.x) return from.y // Return the y-value of 'from' (or 'to') if the line is vertical

// Calculate the y-coordinate for the given x using linear interpolation
// (y - y1) / (x - x1) = (y2 - y1) / (x2 - x1)
// This equation comes from the slope formula m = (y2 - y1) / (x2 - x1),
// rearranged to find 'y' given 'x'.
return from.y + ((to.y - from.y) * (x - from.x)) / (to.x - from.x)
}

export const selectSegment = (
cmds: PathCommand[],
x: number,
disableSmoothing: boolean
): Cubic | { from: Vector; to: Vector } | undefined => {
'worklet'

// Starting point for path segments
let from: Vector = vec(0, 0)

for (let i = 0; i < cmds.length; i++) {
const cmd = cmds[i]
// Skip null commands, ensuring robustness
if (cmd == null) continue

switch (cmd[0]) {
case PathVerb.Move:
// Set the starting point for the next segment
from = vec(cmd[1], cmd[2])
break
case PathVerb.Line:
// Handle direct line segments
const lineTo = vec(cmd[1], cmd[2])
// Check if 'x' is within the horizontal span of the line segment
if (
x >= Math.min(from.x, lineTo.x) &&
x <= Math.max(from.x, lineTo.x)
) {
// Return the segment as a simple line
return { from, to: lineTo }
}
// Update 'from' to the endpoint of the line for the next segment
from = lineTo
break
case PathVerb.Cubic:
// Handle cubic bezier curves
const cubicTo = vec(cmd[5], cmd[6])
if (disableSmoothing) {
// Treat the cubic curve as a straight line if smoothing is disabled
if (
x >= Math.min(from.x, cubicTo.x) &&
x <= Math.max(from.x, cubicTo.x)
) {
return { from, to: cubicTo }
}
} else {
// Construct the cubic curve segment if smoothing is enabled
const c1 = vec(cmd[1], cmd[2])
const c2 = vec(cmd[3], cmd[4])
if (
x >= Math.min(from.x, cubicTo.x) &&
x <= Math.max(from.x, cubicTo.x)
) {
return { from, c1, c2, to: cubicTo }
}
}
// Move 'from' to the end of the cubic curve
from = cubicTo
break
}
}

// Return undefined if no segment matches the given 'x'
return undefined
}

export const getYForX = (
cmds: PathCommand[],
x: number,
precision = 2
disableSmoothing: boolean
): number | undefined => {
'worklet'

const c = selectCurve(cmds, x)
if (c == null) return undefined
const segment = selectSegment(cmds, x, disableSmoothing)
if (!segment) return undefined

return cubicBezierYForX(x, c.from, c.c1, c.c2, c.to, precision)
if ('c1' in segment) {
return cubicBezierYForX(
x,
segment.from,
segment.c1,
segment.c2,
segment.to,
GET_Y_FOR_X_PRECISION
)
} else {
return linearInterpolation(x, segment.from, segment.to)
}
}
4 changes: 4 additions & 0 deletions src/LineGraphProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ interface BaseLineGraphProps extends ViewProps {
* Enable the Fade-In Gradient Effect at the beginning of the Graph
*/
enableFadeInMask?: boolean
/**
* Disables smoothing of the graph line to increase accuracy of graph according to the dataset
*/
disableSmoothing?: boolean
}

export type StaticLineGraphProps = BaseLineGraphProps & {
Expand Down
4 changes: 3 additions & 1 deletion src/StaticLineGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function StaticLineGraph({
color,
lineThickness = 3,
enableFadeInMask,
disableSmoothing = false,
style,
...props
}: StaticLineGraphProps): React.ReactElement {
Expand Down Expand Up @@ -49,8 +50,9 @@ export function StaticLineGraph({
canvasWidth: width,
horizontalPadding: lineThickness,
verticalPadding: lineThickness,
disableSmoothing,
}),
[height, lineThickness, pathRange, pointsInRange, width]
[disableSmoothing, height, lineThickness, pathRange, pointsInRange, width]
)

const gradientColors = useMemo(
Expand Down