From 06f2fd1bd4a2037174020dce6074b115b2771c17 Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Tue, 25 Oct 2022 14:39:54 +0100 Subject: [PATCH 1/6] fix: Prettier semis --- .../GraphExample.xcodeproj/project.pbxproj | 4 +- example/src/screens/GraphPage.tsx | 89 ++--- src/AnimatedLineGraph.tsx | 303 +++++++++--------- src/LineGraphProps.ts | 70 ++-- 4 files changed, 246 insertions(+), 220 deletions(-) diff --git a/example/ios/GraphExample.xcodeproj/project.pbxproj b/example/ios/GraphExample.xcodeproj/project.pbxproj index 962d37f..3004a62 100644 --- a/example/ios/GraphExample.xcodeproj/project.pbxproj +++ b/example/ios/GraphExample.xcodeproj/project.pbxproj @@ -265,7 +265,7 @@ TestTargetID = 13B07F861A680F5B00A75B9A; }; 13B07F861A680F5B00A75B9A = { - DevelopmentTeam = CJW62Q77E7; + DevelopmentTeam = TFP6882UUN; LastSwiftMigration = 1120; }; 2D02E47A1E0B4A5D006451C7 = { @@ -542,7 +542,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = CJW62Q77E7; + DEVELOPMENT_TEAM = TFP6882UUN; ENABLE_BITCODE = NO; INFOPLIST_FILE = GraphExample/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; diff --git a/example/src/screens/GraphPage.tsx b/example/src/screens/GraphPage.tsx index c2fc13b..aa0b496 100644 --- a/example/src/screens/GraphPage.tsx +++ b/example/src/screens/GraphPage.tsx @@ -1,42 +1,44 @@ -import React, { useCallback, useMemo, useState } from 'react' -import { View, StyleSheet, Text, Button } from 'react-native' -import { LineGraph } from 'react-native-graph' -import StaticSafeAreaInsets from 'react-native-static-safe-area-insets' -import type { GraphRange } from '../../../src/LineGraphProps' -import { SelectionDot } from '../components/CustomSelectionDot' -import { Toggle } from '../components/Toggle' +import React, { useCallback, useMemo, useState } from 'react'; +import { View, StyleSheet, Text, Button } from 'react-native'; +import { LineGraph } from 'react-native-graph'; +import StaticSafeAreaInsets from 'react-native-static-safe-area-insets'; +import type { GraphRange } from '../../../src/LineGraphProps'; +import { SelectionDot } from '../components/CustomSelectionDot'; +import { Toggle } from '../components/Toggle'; import { generateRandomGraphData, generateSinusGraphData, -} from '../data/GraphData' -import { useColors } from '../hooks/useColors' -import { hapticFeedback } from '../utils/HapticFeedback' +} from '../data/GraphData'; +import { useColors } from '../hooks/useColors'; +import { hapticFeedback } from '../utils/HapticFeedback'; -const POINT_COUNT = 70 -const POINTS = generateRandomGraphData(POINT_COUNT) -const COLOR = '#6a7ee7' -const GRADIENT_FILL_COLORS = ['#7476df5D', '#7476df4D', '#7476df00'] -const SMALL_POINTS = generateSinusGraphData(9) +const POINT_COUNT = 70; +const POINTS = generateRandomGraphData(POINT_COUNT); +const COLOR = '#6a7ee7'; +const GRADIENT_FILL_COLORS = ['#7476df5D', '#7476df4D', '#7476df00']; +const GRADIENT_LINE_COLORS = ['#FF0000', '#00FF00', '#0000FF']; +const SMALL_POINTS = generateSinusGraphData(9); export function GraphPage() { - const colors = useColors() + const colors = useColors(); - const [isAnimated, setIsAnimated] = useState(true) - const [enablePanGesture, setEnablePanGesture] = useState(true) - const [enableFadeInEffect, setEnableFadeInEffect] = useState(false) + const [isAnimated, setIsAnimated] = useState(true); + const [enablePanGesture, setEnablePanGesture] = useState(true); + const [enableFadeInEffect, setEnableFadeInEffect] = useState(false); const [enableCustomSelectionDot, setEnableCustomSelectionDot] = - useState(false) - const [enableGradient, setEnableGradient] = useState(false) - const [enableRange, setEnableRange] = useState(false) - const [enableIndicator, setEnableIndicator] = useState(false) - const [indicatorPulsating, setIndicatorPulsating] = useState(false) + useState(false); + const [enableFillGradient, setEnableFillGradient] = useState(false); + const [enableLineGradient, setEnableLineGradient] = useState(false); + const [enableRange, setEnableRange] = useState(false); + const [enableIndicator, setEnableIndicator] = useState(false); + const [indicatorPulsating, setIndicatorPulsating] = useState(false); - const [points, setPoints] = useState(POINTS) + const [points, setPoints] = useState(POINTS); const refreshData = useCallback(() => { - setPoints(generateRandomGraphData(POINT_COUNT)) - hapticFeedback('impactLight') - }, []) + setPoints(generateRandomGraphData(POINT_COUNT)); + hapticFeedback('impactLight'); + }, []); const highestDate = useMemo( () => @@ -44,10 +46,10 @@ export function GraphPage() { ? points[points.length - 1]!.date : undefined, [points] - ) + ); const range: GraphRange | undefined = useMemo(() => { // if range is disabled, default to infinite range (undefined) - if (!enableRange) return undefined + if (!enableRange) return undefined; if (points.length !== 0 && highestDate != null) { return { @@ -59,16 +61,16 @@ export function GraphPage() { min: -200, max: 200, }, - } + }; } else { return { y: { min: -200, max: 200, }, - } + }; } - }, [enableRange, highestDate, points]) + }, [enableRange, highestDate, points]); return ( @@ -89,9 +91,11 @@ export function GraphPage() { hapticFeedback('impactLight')} @@ -126,9 +130,14 @@ export function GraphPage() { setIsEnabled={setEnableCustomSelectionDot} /> + - ) + ); } const styles = StyleSheet.create({ @@ -186,4 +195,4 @@ const styles = StyleSheet.create({ justifyContent: 'center', paddingHorizontal: 15, }, -}) +}); diff --git a/src/AnimatedLineGraph.tsx b/src/AnimatedLineGraph.tsx index 2b3a882..ac8893b 100644 --- a/src/AnimatedLineGraph.tsx +++ b/src/AnimatedLineGraph.tsx @@ -1,5 +1,11 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' -import { View, StyleSheet, LayoutChangeEvent } from 'react-native' +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { View, StyleSheet, LayoutChangeEvent } from 'react-native'; import { Canvas, runSpring, @@ -16,16 +22,16 @@ import { mix, Circle, Shadow, -} from '@shopify/react-native-skia' -import type { AnimatedLineGraphProps } from './LineGraphProps' -import { SelectionDot as DefaultSelectionDot } from './SelectionDot' +} from '@shopify/react-native-skia'; +import type { AnimatedLineGraphProps } from './LineGraphProps'; +import { SelectionDot as DefaultSelectionDot } from './SelectionDot'; import { createGraphPath, createGraphPathWithGradient, getGraphPathRange, GraphPathRange, pixelFactorX, -} from './CreateGraphPath' +} from './CreateGraphPath'; import Reanimated, { runOnJS, useAnimatedReaction, @@ -36,22 +42,22 @@ import Reanimated, { withSequence, withTiming, withDelay, -} from 'react-native-reanimated' -import { getSixDigitHex } from './utils/getSixDigitHex' -import { GestureDetector } from 'react-native-gesture-handler' -import { usePanGesture } from './hooks/usePanGesture' -import { getYForX } from './GetYForX' -import { hexToRgba } from './utils/hexToRgba' - -const INDICATOR_RADIUS = 7 -const INDICATOR_BORDER_MULTIPLIER = 1.3 +} from 'react-native-reanimated'; +import { getSixDigitHex } from './utils/getSixDigitHex'; +import { GestureDetector } from 'react-native-gesture-handler'; +import { usePanGesture } from './hooks/usePanGesture'; +import { getYForX } from './GetYForX'; +import { hexToRgba } from './utils/hexToRgba'; + +const INDICATOR_RADIUS = 7; +const INDICATOR_BORDER_MULTIPLIER = 1.3; const INDICATOR_PULSE_BLUR_RADIUS_SMALL = - INDICATOR_RADIUS * INDICATOR_BORDER_MULTIPLIER + INDICATOR_RADIUS * INDICATOR_BORDER_MULTIPLIER; const INDICATOR_PULSE_BLUR_RADIUS_BIG = - INDICATOR_RADIUS * INDICATOR_BORDER_MULTIPLIER + 20 + INDICATOR_RADIUS * INDICATOR_BORDER_MULTIPLIER + 20; // weird rea type bug -const ReanimatedView = Reanimated.View as any +const ReanimatedView = Reanimated.View as any; export function AnimatedLineGraph({ points, @@ -75,27 +81,27 @@ export function AnimatedLineGraph({ BottomAxisLabel, ...props }: AnimatedLineGraphProps): React.ReactElement { - const [width, setWidth] = useState(0) - const [height, setHeight] = useState(0) - const interpolateProgress = useValue(0) - - const { gesture, isActive, x } = usePanGesture({ holdDuration: 300 }) - const circleX = useValue(0) - const circleY = useValue(0) - const pathEnd = useValue(0) - const indicatorRadius = useValue(enableIndicator ? INDICATOR_RADIUS : 0) + const [width, setWidth] = useState(0); + const [height, setHeight] = useState(0); + const interpolateProgress = useValue(0); + + const { gesture, isActive, x } = usePanGesture({ holdDuration: 300 }); + const circleX = useValue(0); + const circleY = useValue(0); + const pathEnd = useValue(0); + const indicatorRadius = useValue(enableIndicator ? INDICATOR_RADIUS : 0); const indicatorBorderRadius = useComputedValue( () => indicatorRadius.current * INDICATOR_BORDER_MULTIPLIER, [indicatorRadius] - ) + ); const pulseTrigger = useDerivedValue(() => { - 'worklet' - return isActive.value ? 1 : 0 - }, []) - const indicatorPulseAnimation = useSharedValue(0) - const indicatorPulseRadius = useValue(INDICATOR_PULSE_BLUR_RADIUS_SMALL) - const indicatorPulseOpacity = useValue(1) + 'worklet'; + return isActive.value ? 1 : 0; + }, []); + const indicatorPulseAnimation = useSharedValue(0); + const indicatorPulseRadius = useValue(INDICATOR_PULSE_BLUR_RADIUS_SMALL); + const indicatorPulseOpacity = useValue(1); const positions = useComputedValue( () => [ @@ -106,39 +112,39 @@ export function AnimatedLineGraph({ 1, ], [pathEnd] - ) + ); const onLayout = useCallback( ({ nativeEvent: { layout } }: LayoutChangeEvent) => { - setWidth(Math.round(layout.width)) - setHeight(Math.round(layout.height)) + setWidth(Math.round(layout.width)); + setHeight(Math.round(layout.height)); }, [] - ) + ); const straightLine = useMemo(() => { - const path = Skia.Path.Make() - path.moveTo(0, height / 2) + const path = Skia.Path.Make(); + path.moveTo(0, height / 2); for (let i = 0; i < width - 1; i += 2) { - const x = i - const y = height / 2 - path.cubicTo(x, y, x, y, x, y) + const x = i; + const y = height / 2; + path.cubicTo(x, y, x, y, x, y); } - return path - }, [height, width]) + return path; + }, [height, width]); - const paths = useValue<{ from?: SkPath; to?: SkPath }>({}) - const gradientPaths = useValue<{ from?: SkPath; to?: SkPath }>({}) - const commands = useRef([]) - const [commandsChanged, setCommandsChanged] = useState(0) + const paths = useValue<{ from?: SkPath; to?: SkPath }>({}); + const gradientPaths = useValue<{ from?: SkPath; to?: SkPath }>({}); + const commands = useRef([]); + const [commandsChanged, setCommandsChanged] = useState(0); const pathRange: GraphPathRange = useMemo( () => getGraphPathRange(points, range), [points, range] - ) + ); const drawingWidth = useMemo(() => { - const lastPoint = points[points.length - 1]! + const lastPoint = points[points.length - 1]!; return Math.max( Math.floor( @@ -146,8 +152,8 @@ export function AnimatedLineGraph({ pixelFactorX(lastPoint.date, pathRange.x.min, pathRange.x.max) ), 0 - ) - }, [horizontalPadding, pathRange.x.max, pathRange.x.min, points, width]) + ); + }, [horizontalPadding, pathRange.x.max, pathRange.x.min, points, width]); const indicatorX = useMemo( () => @@ -155,31 +161,31 @@ export function AnimatedLineGraph({ ? Math.floor(drawingWidth) + horizontalPadding : undefined, [commandsChanged, drawingWidth, horizontalPadding] - ) + ); const indicatorY = useMemo( () => commandsChanged >= 0 && indicatorX != null ? getYForX(commands.current, indicatorX) : undefined, [commandsChanged, indicatorX] - ) + ); - const indicatorPulseColor = useMemo(() => hexToRgba(color, 0.4), [color]) + const indicatorPulseColor = useMemo(() => hexToRgba(color, 0.4), [color]); - const shouldFillGradient = gradientFillColors != null + const shouldFillGradient = gradientFillColors != null; useEffect(() => { if (height < 1 || width < 1) { // view is not yet measured! - return + return; } if (points.length < 1) { // points are still empty! - return + return; } - let path - let gradientPath + let path; + let gradientPath; const createGraphPathProps = { points: points, @@ -188,59 +194,59 @@ export function AnimatedLineGraph({ verticalPadding: verticalPadding, canvasHeight: height, canvasWidth: width, - } + }; if (shouldFillGradient) { const { path: pathNew, gradientPath: gradientPathNew } = - createGraphPathWithGradient(createGraphPathProps) + createGraphPathWithGradient(createGraphPathProps); - path = pathNew - gradientPath = gradientPathNew + path = pathNew; + gradientPath = gradientPathNew; } else { - path = createGraphPath(createGraphPathProps) + path = createGraphPath(createGraphPathProps); } - commands.current = path.toCmds() + commands.current = path.toCmds(); if (gradientPath != null) { - const previous = gradientPaths.current - let from: SkPath = previous.to ?? straightLine + const previous = gradientPaths.current; + let from: SkPath = previous.to ?? straightLine; if (previous.from != null && interpolateProgress.current < 1) from = - from.interpolate(previous.from, interpolateProgress.current) ?? from + from.interpolate(previous.from, interpolateProgress.current) ?? from; if (gradientPath.isInterpolatable(from)) { gradientPaths.current = { from: from, to: gradientPath, - } + }; } else { gradientPaths.current = { from: gradientPath, to: gradientPath, - } + }; } } - const previous = paths.current - let from: SkPath = previous.to ?? straightLine + const previous = paths.current; + let from: SkPath = previous.to ?? straightLine; if (previous.from != null && interpolateProgress.current < 1) from = - from.interpolate(previous.from, interpolateProgress.current) ?? from + from.interpolate(previous.from, interpolateProgress.current) ?? from; if (path.isInterpolatable(from)) { paths.current = { from: from, to: path, - } + }; } else { paths.current = { from: path, to: path, - } + }; } - setCommandsChanged(commandsChanged + 1) + setCommandsChanged(commandsChanged + 1); runSpring( interpolateProgress, @@ -251,7 +257,7 @@ export function AnimatedLineGraph({ damping: 400, velocity: 0, } - ) + ); // eslint-disable-next-line react-hooks/exhaustive-deps }, [ height, @@ -266,54 +272,58 @@ export function AnimatedLineGraph({ straightLine, verticalPadding, width, - ]) + ]); const gradientColors = useMemo(() => { - if (enableFadeInMask) { - return [ - `${getSixDigitHex(color)}00`, - `${getSixDigitHex(color)}ff`, - `${getSixDigitHex(color)}ff`, - `${getSixDigitHex(color)}33`, - `${getSixDigitHex(color)}33`, - ] + if (typeof color === 'string') { + if (enableFadeInMask) { + return [ + `${getSixDigitHex(color)}00`, + `${getSixDigitHex(color)}ff`, + `${getSixDigitHex(color)}ff`, + `${getSixDigitHex(color)}33`, + `${getSixDigitHex(color)}33`, + ]; + } else { + return [ + color, + color, + color, + `${getSixDigitHex(color)}33`, + `${getSixDigitHex(color)}33`, + ]; + } } else { - return [ - color, - color, - color, - `${getSixDigitHex(color)}33`, - `${getSixDigitHex(color)}33`, - ] + return color; } - }, [color, enableFadeInMask]) + }, [color, enableFadeInMask]); const path = useComputedValue( () => { - const from = paths.current.from ?? straightLine - const to = paths.current.to ?? straightLine + const from = paths.current.from ?? straightLine; + const to = paths.current.to ?? straightLine; - return to.interpolate(from, interpolateProgress.current) + return to.interpolate(from, interpolateProgress.current); }, // RN Skia deals with deps differently. They are actually the required SkiaValues that the derived value listens to, not react values. [interpolateProgress] - ) + ); const gradientPath = useComputedValue( () => { - const from = gradientPaths.current.from ?? straightLine - const to = gradientPaths.current.to ?? straightLine + const from = gradientPaths.current.from ?? straightLine; + const to = gradientPaths.current.to ?? straightLine; - return to.interpolate(from, interpolateProgress.current) + return to.interpolate(from, interpolateProgress.current); }, // RN Skia deals with deps differently. They are actually the required SkiaValues that the derived value listens to, not react values. [interpolateProgress] - ) + ); const stopPulsating = useCallback(() => { - cancelAnimation(indicatorPulseAnimation) - indicatorPulseAnimation.value = 0 - }, [indicatorPulseAnimation]) + cancelAnimation(indicatorPulseAnimation); + indicatorPulseAnimation.value = 0; + }, [indicatorPulseAnimation]); const startPulsating = useCallback(() => { indicatorPulseAnimation.value = withRepeat( @@ -328,31 +338,34 @@ export function AnimatedLineGraph({ ) ), -1 - ) - }, [indicatorPulseAnimation]) + ); + }, [indicatorPulseAnimation]); const setFingerX = useCallback( (fingerX: number) => { - const lowerBound = horizontalPadding - const upperBound = drawingWidth + horizontalPadding + const lowerBound = horizontalPadding; + const upperBound = drawingWidth + horizontalPadding; - const fingerXInRange = Math.min(Math.max(fingerX, lowerBound), upperBound) - const y = getYForX(commands.current, fingerXInRange) + const fingerXInRange = Math.min( + Math.max(fingerX, lowerBound), + upperBound + ); + const y = getYForX(commands.current, fingerXInRange); if (y != null) { - circleY.current = y - circleX.current = fingerXInRange + circleY.current = y; + circleX.current = fingerXInRange; } if (fingerX > lowerBound && fingerX < upperBound && isActive.value) - pathEnd.current = fingerX / width + pathEnd.current = fingerX / width; - const actualFingerX = fingerX - horizontalPadding + const actualFingerX = fingerX - horizontalPadding; - const index = Math.round((actualFingerX / upperBound) * points.length) - const pointIndex = Math.min(Math.max(index, 0), points.length - 1) - const dataPoint = points[pointIndex] - if (dataPoint != null) onPointSelected?.(dataPoint) + const index = Math.round((actualFingerX / upperBound) * points.length); + const pointIndex = Math.min(Math.max(index, 0), points.length - 1); + const dataPoint = points[pointIndex]; + if (dataPoint != null) onPointSelected?.(dataPoint); }, [ circleX, @@ -365,7 +378,7 @@ export function AnimatedLineGraph({ points, width, ] - ) + ); const setIsActive = useCallback( (active: boolean) => { @@ -374,15 +387,15 @@ export function AnimatedLineGraph({ stiffness: 1000, damping: 50, velocity: 0, - }) + }); if (active) { - onGestureStart?.() - stopPulsating() + onGestureStart?.(); + stopPulsating(); } else { - onGestureEnd?.() - pathEnd.current = 1 - startPulsating() + onGestureEnd?.(); + pathEnd.current = 1; + startPulsating(); } }, [ @@ -393,37 +406,37 @@ export function AnimatedLineGraph({ startPulsating, stopPulsating, ] - ) + ); useAnimatedReaction( () => x.value, (fingerX) => { if (isActive.value || fingerX) { - runOnJS(setFingerX)(fingerX) + runOnJS(setFingerX)(fingerX); } }, [isActive, setFingerX, width, x] - ) + ); useAnimatedReaction( () => isActive.value, (active) => { - runOnJS(setIsActive)(active) + runOnJS(setIsActive)(active); }, [isActive, setIsActive] - ) + ); useEffect(() => { if (points.length !== 0 && commands.current.length !== 0) - pathEnd.current = 1 - }, [commands, pathEnd, points.length]) + pathEnd.current = 1; + }, [commands, pathEnd, points.length]); useEffect(() => { if (indicatorPulsating) { - startPulsating() + startPulsating(); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [indicatorPulsating]) + }, [indicatorPulsating]); useSharedValueEffect( () => { @@ -432,15 +445,19 @@ export function AnimatedLineGraph({ indicatorPulseAnimation.value, INDICATOR_PULSE_BLUR_RADIUS_SMALL, INDICATOR_PULSE_BLUR_RADIUS_BIG - ) - indicatorPulseOpacity.current = mix(indicatorPulseAnimation.value, 1, 0) + ); + indicatorPulseOpacity.current = mix( + indicatorPulseAnimation.value, + 1, + 0 + ); } else { - indicatorPulseRadius.current = 0 + indicatorPulseRadius.current = 0; } }, indicatorPulseAnimation, pulseTrigger - ) + ); return ( @@ -538,7 +555,7 @@ export function AnimatedLineGraph({ - ) + ); } const styles = StyleSheet.create({ @@ -554,4 +571,4 @@ const styles = StyleSheet.create({ axisRow: { height: 17, }, -}) +}); diff --git a/src/LineGraphProps.ts b/src/LineGraphProps.ts index 172eef0..5afc6aa 100644 --- a/src/LineGraphProps.ts +++ b/src/LineGraphProps.ts @@ -1,112 +1,112 @@ -import type React from 'react' -import type { ViewProps } from 'react-native' -import type { GraphPathRange } from './CreateGraphPath' -import type { SharedValue } from 'react-native-reanimated' -import type { Color, SkiaMutableValue } from '@shopify/react-native-skia' +import type React from 'react'; +import type { ViewProps } from 'react-native'; +import type { GraphPathRange } from './CreateGraphPath'; +import type { SharedValue } from 'react-native-reanimated'; +import type { Color, SkiaMutableValue } from '@shopify/react-native-skia'; export interface GraphPoint { - value: number - date: Date + value: number; + date: Date; } -export type GraphRange = Partial +export type GraphRange = Partial; export interface SelectionDotProps { - isActive: SharedValue - color: BaseLineGraphProps['color'] - lineThickness: BaseLineGraphProps['lineThickness'] - circleX: SkiaMutableValue - circleY: SkiaMutableValue + isActive: SharedValue; + color: BaseLineGraphProps['color']; + lineThickness: BaseLineGraphProps['lineThickness']; + circleX: SkiaMutableValue; + circleY: SkiaMutableValue; } interface BaseLineGraphProps extends ViewProps { /** * All points to be marked in the graph. Coordinate system will adjust to scale automatically. */ - points: GraphPoint[] + points: GraphPoint[]; /** * Range of the graph's x and y-axis. The range must be greater * than the range given by the points. */ - range?: GraphRange + range?: GraphRange; /** - * Color of the graph line (path) + * Color of the graph line (path) either as a single solid color or an array of colors for a line gradient */ - color: string + color: string | Color[]; /** * (Optional) Colors for the fill gradient below the graph line */ - gradientFillColors?: Color[] + gradientFillColors?: Color[]; /** * The width of the graph line (path) * * @default 3 */ - lineThickness?: number + lineThickness?: number; /** * Enable the Fade-In Gradient Effect at the beginning of the Graph */ - enableFadeInMask?: boolean + enableFadeInMask?: boolean; } export type StaticLineGraphProps = BaseLineGraphProps & { /* any static-only line graph props? */ -} +}; export type AnimatedLineGraphProps = BaseLineGraphProps & { /** * Whether to enable Graph scrubbing/pan gesture. */ - enablePanGesture?: boolean + enablePanGesture?: boolean; /** * The color of the selection dot when the user is panning the graph. */ - selectionDotShadowColor?: string + selectionDotShadowColor?: string; /** * Horizontal padding applied to graph, so the pan gesture dot doesn't get cut off horizontally */ - horizontalPadding?: number + horizontalPadding?: number; /** * Vertical padding applied to graph, so the pan gesture dot doesn't get cut off vertically */ - verticalPadding?: number + verticalPadding?: number; /** * Enables an indicator which is displayed at the end of the graph */ - enableIndicator?: boolean + enableIndicator?: boolean; /** * Let's the indicator pulsate */ - indicatorPulsating?: boolean + indicatorPulsating?: boolean; /** * Called for each point while the user is scrubbing/panning through the graph */ - onPointSelected?: (point: GraphPoint) => void + onPointSelected?: (point: GraphPoint) => void; /** * Called once the user starts scrubbing/panning through the graph */ - onGestureStart?: () => void + onGestureStart?: () => void; /** * Called once the user stopped scrubbing/panning through the graph */ - onGestureEnd?: () => void + onGestureEnd?: () => void; /** * The element that renders the selection dot */ - SelectionDot?: React.ComponentType | null + SelectionDot?: React.ComponentType | null; /** * The element that gets rendered above the Graph (usually the "max" point/value of the Graph) */ - TopAxisLabel?: () => React.ReactElement | null + TopAxisLabel?: () => React.ReactElement | null; /** * The element that gets rendered below the Graph (usually the "min" point/value of the Graph) */ - BottomAxisLabel?: () => React.ReactElement | null -} + BottomAxisLabel?: () => React.ReactElement | null; +}; export type LineGraphProps = | ({ animated: true } & AnimatedLineGraphProps) - | ({ animated: false } & StaticLineGraphProps) + | ({ animated: false } & StaticLineGraphProps); From 7e514bd98112c6f22610f03546471167004343e5 Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Tue, 25 Oct 2022 14:50:41 +0100 Subject: [PATCH 2/6] fix: Conflicting prettier rules --- example/src/screens/GraphPage.tsx | 74 ++++---- package.json | 8 +- src/AnimatedLineGraph.tsx | 275 ++++++++++++++---------------- 3 files changed, 173 insertions(+), 184 deletions(-) diff --git a/example/src/screens/GraphPage.tsx b/example/src/screens/GraphPage.tsx index aa0b496..6b96767 100644 --- a/example/src/screens/GraphPage.tsx +++ b/example/src/screens/GraphPage.tsx @@ -1,44 +1,44 @@ -import React, { useCallback, useMemo, useState } from 'react'; -import { View, StyleSheet, Text, Button } from 'react-native'; -import { LineGraph } from 'react-native-graph'; -import StaticSafeAreaInsets from 'react-native-static-safe-area-insets'; -import type { GraphRange } from '../../../src/LineGraphProps'; -import { SelectionDot } from '../components/CustomSelectionDot'; -import { Toggle } from '../components/Toggle'; +import React, { useCallback, useMemo, useState } from 'react' +import { View, StyleSheet, Text, Button } from 'react-native' +import { LineGraph } from 'react-native-graph' +import StaticSafeAreaInsets from 'react-native-static-safe-area-insets' +import type { GraphRange } from '../../../src/LineGraphProps' +import { SelectionDot } from '../components/CustomSelectionDot' +import { Toggle } from '../components/Toggle' import { generateRandomGraphData, generateSinusGraphData, -} from '../data/GraphData'; -import { useColors } from '../hooks/useColors'; -import { hapticFeedback } from '../utils/HapticFeedback'; +} from '../data/GraphData' +import { useColors } from '../hooks/useColors' +import { hapticFeedback } from '../utils/HapticFeedback' -const POINT_COUNT = 70; -const POINTS = generateRandomGraphData(POINT_COUNT); -const COLOR = '#6a7ee7'; -const GRADIENT_FILL_COLORS = ['#7476df5D', '#7476df4D', '#7476df00']; -const GRADIENT_LINE_COLORS = ['#FF0000', '#00FF00', '#0000FF']; -const SMALL_POINTS = generateSinusGraphData(9); +const POINT_COUNT = 70 +const POINTS = generateRandomGraphData(POINT_COUNT) +const COLOR = '#6a7ee7' +const GRADIENT_FILL_COLORS = ['#7476df5D', '#7476df4D', '#7476df00'] +const GRADIENT_LINE_COLORS = ['#FF0000', '#00FF00', '#0000FF'] +const SMALL_POINTS = generateSinusGraphData(9) export function GraphPage() { - const colors = useColors(); + const colors = useColors() - const [isAnimated, setIsAnimated] = useState(true); - const [enablePanGesture, setEnablePanGesture] = useState(true); - const [enableFadeInEffect, setEnableFadeInEffect] = useState(false); + const [isAnimated, setIsAnimated] = useState(true) + const [enablePanGesture, setEnablePanGesture] = useState(true) + const [enableFadeInEffect, setEnableFadeInEffect] = useState(false) const [enableCustomSelectionDot, setEnableCustomSelectionDot] = - useState(false); - const [enableFillGradient, setEnableFillGradient] = useState(false); - const [enableLineGradient, setEnableLineGradient] = useState(false); - const [enableRange, setEnableRange] = useState(false); - const [enableIndicator, setEnableIndicator] = useState(false); - const [indicatorPulsating, setIndicatorPulsating] = useState(false); + useState(false) + const [enableFillGradient, setEnableFillGradient] = useState(false) + const [enableLineGradient, setEnableLineGradient] = useState(false) + const [enableRange, setEnableRange] = useState(false) + const [enableIndicator, setEnableIndicator] = useState(false) + const [indicatorPulsating, setIndicatorPulsating] = useState(false) - const [points, setPoints] = useState(POINTS); + const [points, setPoints] = useState(POINTS) const refreshData = useCallback(() => { - setPoints(generateRandomGraphData(POINT_COUNT)); - hapticFeedback('impactLight'); - }, []); + setPoints(generateRandomGraphData(POINT_COUNT)) + hapticFeedback('impactLight') + }, []) const highestDate = useMemo( () => @@ -46,10 +46,10 @@ export function GraphPage() { ? points[points.length - 1]!.date : undefined, [points] - ); + ) const range: GraphRange | undefined = useMemo(() => { // if range is disabled, default to infinite range (undefined) - if (!enableRange) return undefined; + if (!enableRange) return undefined if (points.length !== 0 && highestDate != null) { return { @@ -61,16 +61,16 @@ export function GraphPage() { min: -200, max: 200, }, - }; + } } else { return { y: { min: -200, max: 200, }, - }; + } } - }, [enableRange, highestDate, points]); + }, [enableRange, highestDate, points]) return ( @@ -158,7 +158,7 @@ export function GraphPage() { - ); + ) } const styles = StyleSheet.create({ @@ -195,4 +195,4 @@ const styles = StyleSheet.create({ justifyContent: 'center', paddingHorizontal: 15, }, -}); +}) diff --git a/package.json b/package.json index 56912dc..6bb1110 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,8 @@ "tabWidth": 2, "semi": false, "trailingComma": "es5", - "useTabs": false + "useTabs": false, + "semi": false } ] } @@ -126,7 +127,8 @@ "singleQuote": true, "tabWidth": 2, "trailingComma": "es5", - "useTabs": false + "useTabs": false, + "semi": false }, "react-native-builder-bob": { "source": "src", @@ -142,4 +144,4 @@ ] ] } -} +} \ No newline at end of file diff --git a/src/AnimatedLineGraph.tsx b/src/AnimatedLineGraph.tsx index ac8893b..e0d838e 100644 --- a/src/AnimatedLineGraph.tsx +++ b/src/AnimatedLineGraph.tsx @@ -1,11 +1,5 @@ -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from 'react'; -import { View, StyleSheet, LayoutChangeEvent } from 'react-native'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { View, StyleSheet, LayoutChangeEvent } from 'react-native' import { Canvas, runSpring, @@ -22,16 +16,16 @@ import { mix, Circle, Shadow, -} from '@shopify/react-native-skia'; -import type { AnimatedLineGraphProps } from './LineGraphProps'; -import { SelectionDot as DefaultSelectionDot } from './SelectionDot'; +} from '@shopify/react-native-skia' +import type { AnimatedLineGraphProps } from './LineGraphProps' +import { SelectionDot as DefaultSelectionDot } from './SelectionDot' import { createGraphPath, createGraphPathWithGradient, getGraphPathRange, GraphPathRange, pixelFactorX, -} from './CreateGraphPath'; +} from './CreateGraphPath' import Reanimated, { runOnJS, useAnimatedReaction, @@ -42,22 +36,22 @@ import Reanimated, { withSequence, withTiming, withDelay, -} from 'react-native-reanimated'; -import { getSixDigitHex } from './utils/getSixDigitHex'; -import { GestureDetector } from 'react-native-gesture-handler'; -import { usePanGesture } from './hooks/usePanGesture'; -import { getYForX } from './GetYForX'; -import { hexToRgba } from './utils/hexToRgba'; - -const INDICATOR_RADIUS = 7; -const INDICATOR_BORDER_MULTIPLIER = 1.3; +} from 'react-native-reanimated' +import { getSixDigitHex } from './utils/getSixDigitHex' +import { GestureDetector } from 'react-native-gesture-handler' +import { usePanGesture } from './hooks/usePanGesture' +import { getYForX } from './GetYForX' +import { hexToRgba } from './utils/hexToRgba' + +const INDICATOR_RADIUS = 7 +const INDICATOR_BORDER_MULTIPLIER = 1.3 const INDICATOR_PULSE_BLUR_RADIUS_SMALL = - INDICATOR_RADIUS * INDICATOR_BORDER_MULTIPLIER; + INDICATOR_RADIUS * INDICATOR_BORDER_MULTIPLIER const INDICATOR_PULSE_BLUR_RADIUS_BIG = - INDICATOR_RADIUS * INDICATOR_BORDER_MULTIPLIER + 20; + INDICATOR_RADIUS * INDICATOR_BORDER_MULTIPLIER + 20 // weird rea type bug -const ReanimatedView = Reanimated.View as any; +const ReanimatedView = Reanimated.View as any export function AnimatedLineGraph({ points, @@ -81,27 +75,27 @@ export function AnimatedLineGraph({ BottomAxisLabel, ...props }: AnimatedLineGraphProps): React.ReactElement { - const [width, setWidth] = useState(0); - const [height, setHeight] = useState(0); - const interpolateProgress = useValue(0); - - const { gesture, isActive, x } = usePanGesture({ holdDuration: 300 }); - const circleX = useValue(0); - const circleY = useValue(0); - const pathEnd = useValue(0); - const indicatorRadius = useValue(enableIndicator ? INDICATOR_RADIUS : 0); + const [width, setWidth] = useState(0) + const [height, setHeight] = useState(0) + const interpolateProgress = useValue(0) + + const { gesture, isActive, x } = usePanGesture({ holdDuration: 300 }) + const circleX = useValue(0) + const circleY = useValue(0) + const pathEnd = useValue(0) + const indicatorRadius = useValue(enableIndicator ? INDICATOR_RADIUS : 0) const indicatorBorderRadius = useComputedValue( () => indicatorRadius.current * INDICATOR_BORDER_MULTIPLIER, [indicatorRadius] - ); + ) const pulseTrigger = useDerivedValue(() => { - 'worklet'; - return isActive.value ? 1 : 0; - }, []); - const indicatorPulseAnimation = useSharedValue(0); - const indicatorPulseRadius = useValue(INDICATOR_PULSE_BLUR_RADIUS_SMALL); - const indicatorPulseOpacity = useValue(1); + 'worklet' + return isActive.value ? 1 : 0 + }, []) + const indicatorPulseAnimation = useSharedValue(0) + const indicatorPulseRadius = useValue(INDICATOR_PULSE_BLUR_RADIUS_SMALL) + const indicatorPulseOpacity = useValue(1) const positions = useComputedValue( () => [ @@ -112,39 +106,39 @@ export function AnimatedLineGraph({ 1, ], [pathEnd] - ); + ) const onLayout = useCallback( ({ nativeEvent: { layout } }: LayoutChangeEvent) => { - setWidth(Math.round(layout.width)); - setHeight(Math.round(layout.height)); + setWidth(Math.round(layout.width)) + setHeight(Math.round(layout.height)) }, [] - ); + ) const straightLine = useMemo(() => { - const path = Skia.Path.Make(); - path.moveTo(0, height / 2); + const path = Skia.Path.Make() + path.moveTo(0, height / 2) for (let i = 0; i < width - 1; i += 2) { - const x = i; - const y = height / 2; - path.cubicTo(x, y, x, y, x, y); + const x = i + const y = height / 2 + path.cubicTo(x, y, x, y, x, y) } - return path; - }, [height, width]); + return path + }, [height, width]) - const paths = useValue<{ from?: SkPath; to?: SkPath }>({}); - const gradientPaths = useValue<{ from?: SkPath; to?: SkPath }>({}); - const commands = useRef([]); - const [commandsChanged, setCommandsChanged] = useState(0); + const paths = useValue<{ from?: SkPath; to?: SkPath }>({}) + const gradientPaths = useValue<{ from?: SkPath; to?: SkPath }>({}) + const commands = useRef([]) + const [commandsChanged, setCommandsChanged] = useState(0) const pathRange: GraphPathRange = useMemo( () => getGraphPathRange(points, range), [points, range] - ); + ) const drawingWidth = useMemo(() => { - const lastPoint = points[points.length - 1]!; + const lastPoint = points[points.length - 1]! return Math.max( Math.floor( @@ -152,8 +146,8 @@ export function AnimatedLineGraph({ pixelFactorX(lastPoint.date, pathRange.x.min, pathRange.x.max) ), 0 - ); - }, [horizontalPadding, pathRange.x.max, pathRange.x.min, points, width]); + ) + }, [horizontalPadding, pathRange.x.max, pathRange.x.min, points, width]) const indicatorX = useMemo( () => @@ -161,31 +155,31 @@ export function AnimatedLineGraph({ ? Math.floor(drawingWidth) + horizontalPadding : undefined, [commandsChanged, drawingWidth, horizontalPadding] - ); + ) const indicatorY = useMemo( () => commandsChanged >= 0 && indicatorX != null ? getYForX(commands.current, indicatorX) : undefined, [commandsChanged, indicatorX] - ); + ) - const indicatorPulseColor = useMemo(() => hexToRgba(color, 0.4), [color]); + const indicatorPulseColor = useMemo(() => hexToRgba(color, 0.4), [color]) - const shouldFillGradient = gradientFillColors != null; + const shouldFillGradient = gradientFillColors != null useEffect(() => { if (height < 1 || width < 1) { // view is not yet measured! - return; + return } if (points.length < 1) { // points are still empty! - return; + return } - let path; - let gradientPath; + let path + let gradientPath const createGraphPathProps = { points: points, @@ -194,59 +188,59 @@ export function AnimatedLineGraph({ verticalPadding: verticalPadding, canvasHeight: height, canvasWidth: width, - }; + } if (shouldFillGradient) { const { path: pathNew, gradientPath: gradientPathNew } = - createGraphPathWithGradient(createGraphPathProps); + createGraphPathWithGradient(createGraphPathProps) - path = pathNew; - gradientPath = gradientPathNew; + path = pathNew + gradientPath = gradientPathNew } else { - path = createGraphPath(createGraphPathProps); + path = createGraphPath(createGraphPathProps) } - commands.current = path.toCmds(); + commands.current = path.toCmds() if (gradientPath != null) { - const previous = gradientPaths.current; - let from: SkPath = previous.to ?? straightLine; + const previous = gradientPaths.current + let from: SkPath = previous.to ?? straightLine if (previous.from != null && interpolateProgress.current < 1) from = - from.interpolate(previous.from, interpolateProgress.current) ?? from; + from.interpolate(previous.from, interpolateProgress.current) ?? from if (gradientPath.isInterpolatable(from)) { gradientPaths.current = { from: from, to: gradientPath, - }; + } } else { gradientPaths.current = { from: gradientPath, to: gradientPath, - }; + } } } - const previous = paths.current; - let from: SkPath = previous.to ?? straightLine; + const previous = paths.current + let from: SkPath = previous.to ?? straightLine if (previous.from != null && interpolateProgress.current < 1) from = - from.interpolate(previous.from, interpolateProgress.current) ?? from; + from.interpolate(previous.from, interpolateProgress.current) ?? from if (path.isInterpolatable(from)) { paths.current = { from: from, to: path, - }; + } } else { paths.current = { from: path, to: path, - }; + } } - setCommandsChanged(commandsChanged + 1); + setCommandsChanged(commandsChanged + 1) runSpring( interpolateProgress, @@ -257,7 +251,7 @@ export function AnimatedLineGraph({ damping: 400, velocity: 0, } - ); + ) // eslint-disable-next-line react-hooks/exhaustive-deps }, [ height, @@ -272,7 +266,7 @@ export function AnimatedLineGraph({ straightLine, verticalPadding, width, - ]); + ]) const gradientColors = useMemo(() => { if (typeof color === 'string') { @@ -283,7 +277,7 @@ export function AnimatedLineGraph({ `${getSixDigitHex(color)}ff`, `${getSixDigitHex(color)}33`, `${getSixDigitHex(color)}33`, - ]; + ] } else { return [ color, @@ -291,39 +285,39 @@ export function AnimatedLineGraph({ color, `${getSixDigitHex(color)}33`, `${getSixDigitHex(color)}33`, - ]; + ] } } else { - return color; + return color } - }, [color, enableFadeInMask]); + }, [color, enableFadeInMask]) const path = useComputedValue( () => { - const from = paths.current.from ?? straightLine; - const to = paths.current.to ?? straightLine; + const from = paths.current.from ?? straightLine + const to = paths.current.to ?? straightLine - return to.interpolate(from, interpolateProgress.current); + return to.interpolate(from, interpolateProgress.current) }, // RN Skia deals with deps differently. They are actually the required SkiaValues that the derived value listens to, not react values. [interpolateProgress] - ); + ) const gradientPath = useComputedValue( () => { - const from = gradientPaths.current.from ?? straightLine; - const to = gradientPaths.current.to ?? straightLine; + const from = gradientPaths.current.from ?? straightLine + const to = gradientPaths.current.to ?? straightLine - return to.interpolate(from, interpolateProgress.current); + return to.interpolate(from, interpolateProgress.current) }, // RN Skia deals with deps differently. They are actually the required SkiaValues that the derived value listens to, not react values. [interpolateProgress] - ); + ) const stopPulsating = useCallback(() => { - cancelAnimation(indicatorPulseAnimation); - indicatorPulseAnimation.value = 0; - }, [indicatorPulseAnimation]); + cancelAnimation(indicatorPulseAnimation) + indicatorPulseAnimation.value = 0 + }, [indicatorPulseAnimation]) const startPulsating = useCallback(() => { indicatorPulseAnimation.value = withRepeat( @@ -338,34 +332,31 @@ export function AnimatedLineGraph({ ) ), -1 - ); - }, [indicatorPulseAnimation]); + ) + }, [indicatorPulseAnimation]) const setFingerX = useCallback( (fingerX: number) => { - const lowerBound = horizontalPadding; - const upperBound = drawingWidth + horizontalPadding; + const lowerBound = horizontalPadding + const upperBound = drawingWidth + horizontalPadding - const fingerXInRange = Math.min( - Math.max(fingerX, lowerBound), - upperBound - ); - const y = getYForX(commands.current, fingerXInRange); + const fingerXInRange = Math.min(Math.max(fingerX, lowerBound), upperBound) + const y = getYForX(commands.current, fingerXInRange) if (y != null) { - circleY.current = y; - circleX.current = fingerXInRange; + circleY.current = y + circleX.current = fingerXInRange } if (fingerX > lowerBound && fingerX < upperBound && isActive.value) - pathEnd.current = fingerX / width; + pathEnd.current = fingerX / width - const actualFingerX = fingerX - horizontalPadding; + const actualFingerX = fingerX - horizontalPadding - const index = Math.round((actualFingerX / upperBound) * points.length); - const pointIndex = Math.min(Math.max(index, 0), points.length - 1); - const dataPoint = points[pointIndex]; - if (dataPoint != null) onPointSelected?.(dataPoint); + const index = Math.round((actualFingerX / upperBound) * points.length) + const pointIndex = Math.min(Math.max(index, 0), points.length - 1) + const dataPoint = points[pointIndex] + if (dataPoint != null) onPointSelected?.(dataPoint) }, [ circleX, @@ -378,7 +369,7 @@ export function AnimatedLineGraph({ points, width, ] - ); + ) const setIsActive = useCallback( (active: boolean) => { @@ -387,15 +378,15 @@ export function AnimatedLineGraph({ stiffness: 1000, damping: 50, velocity: 0, - }); + }) if (active) { - onGestureStart?.(); - stopPulsating(); + onGestureStart?.() + stopPulsating() } else { - onGestureEnd?.(); - pathEnd.current = 1; - startPulsating(); + onGestureEnd?.() + pathEnd.current = 1 + startPulsating() } }, [ @@ -406,37 +397,37 @@ export function AnimatedLineGraph({ startPulsating, stopPulsating, ] - ); + ) useAnimatedReaction( () => x.value, (fingerX) => { if (isActive.value || fingerX) { - runOnJS(setFingerX)(fingerX); + runOnJS(setFingerX)(fingerX) } }, [isActive, setFingerX, width, x] - ); + ) useAnimatedReaction( () => isActive.value, (active) => { - runOnJS(setIsActive)(active); + runOnJS(setIsActive)(active) }, [isActive, setIsActive] - ); + ) useEffect(() => { if (points.length !== 0 && commands.current.length !== 0) - pathEnd.current = 1; - }, [commands, pathEnd, points.length]); + pathEnd.current = 1 + }, [commands, pathEnd, points.length]) useEffect(() => { if (indicatorPulsating) { - startPulsating(); + startPulsating() } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [indicatorPulsating]); + }, [indicatorPulsating]) useSharedValueEffect( () => { @@ -445,19 +436,15 @@ export function AnimatedLineGraph({ indicatorPulseAnimation.value, INDICATOR_PULSE_BLUR_RADIUS_SMALL, INDICATOR_PULSE_BLUR_RADIUS_BIG - ); - indicatorPulseOpacity.current = mix( - indicatorPulseAnimation.value, - 1, - 0 - ); + ) + indicatorPulseOpacity.current = mix(indicatorPulseAnimation.value, 1, 0) } else { - indicatorPulseRadius.current = 0; + indicatorPulseRadius.current = 0 } }, indicatorPulseAnimation, pulseTrigger - ); + ) return ( @@ -555,7 +542,7 @@ export function AnimatedLineGraph({ - ); + ) } const styles = StyleSheet.create({ @@ -571,4 +558,4 @@ const styles = StyleSheet.create({ axisRow: { height: 17, }, -}); +}) From cae0adb4b0f175d786065115751bb6ca2bed8206 Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:21:31 +0100 Subject: [PATCH 3/6] fix: Prettier --- src/LineGraphProps.ts | 68 +++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/LineGraphProps.ts b/src/LineGraphProps.ts index 5afc6aa..445c8e8 100644 --- a/src/LineGraphProps.ts +++ b/src/LineGraphProps.ts @@ -1,112 +1,112 @@ -import type React from 'react'; -import type { ViewProps } from 'react-native'; -import type { GraphPathRange } from './CreateGraphPath'; -import type { SharedValue } from 'react-native-reanimated'; -import type { Color, SkiaMutableValue } from '@shopify/react-native-skia'; +import type React from 'react' +import type { ViewProps } from 'react-native' +import type { GraphPathRange } from './CreateGraphPath' +import type { SharedValue } from 'react-native-reanimated' +import type { Color, SkiaMutableValue } from '@shopify/react-native-skia' export interface GraphPoint { - value: number; - date: Date; + value: number + date: Date } -export type GraphRange = Partial; +export type GraphRange = Partial export interface SelectionDotProps { - isActive: SharedValue; - color: BaseLineGraphProps['color']; - lineThickness: BaseLineGraphProps['lineThickness']; - circleX: SkiaMutableValue; - circleY: SkiaMutableValue; + isActive: SharedValue + color: BaseLineGraphProps['color'] + lineThickness: BaseLineGraphProps['lineThickness'] + circleX: SkiaMutableValue + circleY: SkiaMutableValue } interface BaseLineGraphProps extends ViewProps { /** * All points to be marked in the graph. Coordinate system will adjust to scale automatically. */ - points: GraphPoint[]; + points: GraphPoint[] /** * Range of the graph's x and y-axis. The range must be greater * than the range given by the points. */ - range?: GraphRange; + range?: GraphRange /** * Color of the graph line (path) either as a single solid color or an array of colors for a line gradient */ - color: string | Color[]; + color: string | string[] /** * (Optional) Colors for the fill gradient below the graph line */ - gradientFillColors?: Color[]; + gradientFillColors?: Color[] /** * The width of the graph line (path) * * @default 3 */ - lineThickness?: number; + lineThickness?: number /** * Enable the Fade-In Gradient Effect at the beginning of the Graph */ - enableFadeInMask?: boolean; + enableFadeInMask?: boolean } export type StaticLineGraphProps = BaseLineGraphProps & { /* any static-only line graph props? */ -}; +} export type AnimatedLineGraphProps = BaseLineGraphProps & { /** * Whether to enable Graph scrubbing/pan gesture. */ - enablePanGesture?: boolean; + enablePanGesture?: boolean /** * The color of the selection dot when the user is panning the graph. */ - selectionDotShadowColor?: string; + selectionDotShadowColor?: string /** * Horizontal padding applied to graph, so the pan gesture dot doesn't get cut off horizontally */ - horizontalPadding?: number; + horizontalPadding?: number /** * Vertical padding applied to graph, so the pan gesture dot doesn't get cut off vertically */ - verticalPadding?: number; + verticalPadding?: number /** * Enables an indicator which is displayed at the end of the graph */ - enableIndicator?: boolean; + enableIndicator?: boolean /** * Let's the indicator pulsate */ - indicatorPulsating?: boolean; + indicatorPulsating?: boolean /** * Called for each point while the user is scrubbing/panning through the graph */ - onPointSelected?: (point: GraphPoint) => void; + onPointSelected?: (point: GraphPoint) => void /** * Called once the user starts scrubbing/panning through the graph */ - onGestureStart?: () => void; + onGestureStart?: () => void /** * Called once the user stopped scrubbing/panning through the graph */ - onGestureEnd?: () => void; + onGestureEnd?: () => void /** * The element that renders the selection dot */ - SelectionDot?: React.ComponentType | null; + SelectionDot?: React.ComponentType | null /** * The element that gets rendered above the Graph (usually the "max" point/value of the Graph) */ - TopAxisLabel?: () => React.ReactElement | null; + TopAxisLabel?: () => React.ReactElement | null /** * The element that gets rendered below the Graph (usually the "min" point/value of the Graph) */ - BottomAxisLabel?: () => React.ReactElement | null; -}; + BottomAxisLabel?: () => React.ReactElement | null +} export type LineGraphProps = | ({ animated: true } & AnimatedLineGraphProps) - | ({ animated: false } & StaticLineGraphProps); + | ({ animated: false } & StaticLineGraphProps) From 3f25cf4336997ee8224760fdde0e46f7178d914c Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:22:19 +0100 Subject: [PATCH 4/6] feat: Line gradient positions --- src/AnimatedLineGraph.tsx | 40 ++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/AnimatedLineGraph.tsx b/src/AnimatedLineGraph.tsx index e0d838e..79079f8 100644 --- a/src/AnimatedLineGraph.tsx +++ b/src/AnimatedLineGraph.tsx @@ -97,16 +97,20 @@ export function AnimatedLineGraph({ const indicatorPulseRadius = useValue(INDICATOR_PULSE_BLUR_RADIUS_SMALL) const indicatorPulseOpacity = useValue(1) - const positions = useComputedValue( - () => [ - 0, - Math.min(0.15, pathEnd.current), - pathEnd.current, - pathEnd.current, - 1, - ], - [pathEnd] - ) + const positions = useComputedValue(() => { + if (typeof color === 'string') { + return [ + 0, + Math.min(0.15, pathEnd.current), + pathEnd.current, + pathEnd.current, + 1, + ] + } else { + return color.map((_, index) => index / (color.length - 1)) + } + }, [pathEnd, color]) + const onLayout = useCallback( ({ nativeEvent: { layout } }: LayoutChangeEvent) => { setWidth(Math.round(layout.width)) @@ -115,6 +119,8 @@ export function AnimatedLineGraph({ [] ) + console.log(positions, color) + const straightLine = useMemo(() => { const path = Skia.Path.Make() path.moveTo(0, height / 2) @@ -164,7 +170,15 @@ export function AnimatedLineGraph({ [commandsChanged, indicatorX] ) - const indicatorPulseColor = useMemo(() => hexToRgba(color, 0.4), [color]) + const primaryColor = useMemo(() => { + if (typeof color === 'string') return color + return color[0] ?? '#FFF' + }, [color]) + + const indicatorPulseColor = useMemo( + () => hexToRgba(primaryColor, 0.4), + [primaryColor] + ) const shouldFillGradient = gradientFillColors != null @@ -494,7 +508,7 @@ export function AnimatedLineGraph({ {SelectionDot != null && ( )} From 47ba80364c5a80fa9fba049040f560c1db6e5695 Mon Sep 17 00:00:00 2001 From: Thomas Coldwell <31568400+thomas-coldwell@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:32:18 +0100 Subject: [PATCH 5/6] fix: Add support to static line graph --- src/AnimatedLineGraph.tsx | 2 -- src/StaticLineGraph.tsx | 19 +++++++++++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/AnimatedLineGraph.tsx b/src/AnimatedLineGraph.tsx index 79079f8..ec6160c 100644 --- a/src/AnimatedLineGraph.tsx +++ b/src/AnimatedLineGraph.tsx @@ -119,8 +119,6 @@ export function AnimatedLineGraph({ [] ) - console.log(positions, color) - const straightLine = useMemo(() => { const path = Skia.Path.Make() path.moveTo(0, height / 2) diff --git a/src/StaticLineGraph.tsx b/src/StaticLineGraph.tsx index b7149bf..00ffc5b 100644 --- a/src/StaticLineGraph.tsx +++ b/src/StaticLineGraph.tsx @@ -47,12 +47,23 @@ export function StaticLineGraph({ [height, lineThickness, pathRange, points, width] ) + const primaryColor = useMemo(() => { + if (typeof color === 'string') return color + return color[0] ?? '#FFF' + }, [color]) + const gradientColors = useMemo( - () => [`${getSixDigitHex(color)}00`, `${getSixDigitHex(color)}ff`], + () => + typeof color === 'string' + ? [`${getSixDigitHex(color)}00`, `${getSixDigitHex(color)}ff`] + : color, [color] ) const gradientFrom = useMemo(() => vec(0, 0), []) - const gradientTo = useMemo(() => vec(width * 0.15, 0), [width]) + const gradientTo = useMemo( + () => vec(typeof color === 'string' ? width * 0.15 : width, 0), + [width, color] + ) return ( @@ -60,12 +71,12 @@ export function StaticLineGraph({ - {enableFadeInMask && ( + {(enableFadeInMask || typeof color !== 'string') && ( Date: Tue, 25 Oct 2022 16:03:29 +0100 Subject: [PATCH 6/6] fix: selection dot color --- src/SelectionDot.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/SelectionDot.tsx b/src/SelectionDot.tsx index e72098b..499749e 100644 --- a/src/SelectionDot.tsx +++ b/src/SelectionDot.tsx @@ -1,4 +1,4 @@ -import React, { useCallback } from 'react' +import React, { useCallback, useMemo } from 'react' import { runOnJS, useAnimatedReaction } from 'react-native-reanimated' import { runSpring, @@ -45,6 +45,11 @@ export function SelectionDot({ [isActive, setIsActive] ) + const primaryColor = useMemo(() => { + if (typeof color === 'string') return color + return color[0] ?? '#FFF' + }, [color]) + return ( - +