diff --git a/src/charts/linear/ChartDot.js b/src/charts/linear/ChartDot.js
index 7bf3437..da1751b 100644
--- a/src/charts/linear/ChartDot.js
+++ b/src/charts/linear/ChartDot.js
@@ -1,5 +1,6 @@
import React, { useContext } from 'react';
import Animated from 'react-native-reanimated';
+
import ChartContext from '../../helpers/ChartContext';
import withReanimatedFallback from '../../helpers/withReanimatedFallback';
diff --git a/src/charts/linear/ChartLabels.js b/src/charts/linear/ChartLabels.js
index 45ec9fe..2cbde8e 100644
--- a/src/charts/linear/ChartLabels.js
+++ b/src/charts/linear/ChartLabels.js
@@ -4,6 +4,7 @@ import Animated, {
useAnimatedStyle,
useDerivedValue,
} from 'react-native-reanimated';
+
import ChartContext from '../../helpers/ChartContext';
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
diff --git a/src/charts/linear/ChartPath.js b/src/charts/linear/ChartPath.js
index 496b9d7..1a4fc47 100644
--- a/src/charts/linear/ChartPath.js
+++ b/src/charts/linear/ChartPath.js
@@ -17,6 +17,7 @@ import Animated, {
withTiming,
} from 'react-native-reanimated';
import { Path, Svg } from 'react-native-svg';
+
import ChartContext, {
useGenerateValues as generateValues,
} from '../../helpers/ChartContext';
@@ -64,8 +65,10 @@ function combineConfigs(a, b) {
return r;
}
-const parse = data => {
+const parse = (data, yRange) => {
const { greatestY, smallestY } = findYExtremes(data);
+ const minY = yRange ? yRange[0] : smallestY.y;
+ const maxY = yRange ? yRange[1] : greatestY.y;
const smallestX = data[0];
const greatestX = data[data.length - 1];
return [
@@ -73,7 +76,7 @@ const parse = data => {
originalX: x,
originalY: y,
x: (x - smallestX.x) / (greatestX.x - smallestX.x),
- y: 1 - (y - smallestY.y) / (greatestY.y - smallestY.y),
+ y: 1 - (y - minY) / (maxY - minY),
})),
{
greatestX,
@@ -101,6 +104,13 @@ function setoriginalXYAccordingToPosition(
idx = data.value.length - 1;
}
}
+ if (!data.value[idx]) {
+ // prevent the following error on android:
+ // java.lang.RuntimeException: undefined is not an object (evaluating 'data.value[idx].originalX')
+ // why data.value = [] sometimes onActive?
+ console.warn('No data available for chart', data.value.length, idx);
+ return;
+ }
originalX.value = data.value[idx].originalX.toString();
originalY.value = data.value[idx].originalY
? data.value[idx].originalY.toString()
@@ -197,20 +207,19 @@ export default function ChartPathProvider({
} else {
setData(providedData);
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
}, [providedData]);
const smoothingStrategy = useSharedValue(data.smoothingStrategy);
useEffect(() => {
- if (!data || !data.points) {
+ if (!data || !data.points || data.points.length === 0) {
return;
}
- const [parsedData] = parse(data.points);
+ const [parsedData] = parse(data.points, data.yRange);
const [parsedoriginalData, newExtremes] = parse(
data.nativePoints || data.points
);
- setContextValue(prev => ({ ...prev, ...newExtremes, data }));
+ setContextValue((prev) => ({ ...prev, ...newExtremes, data }));
setExtremes(newExtremes);
if (prevData.value.length !== 0) {
valuesStore.current.prevData = currData.value;
@@ -248,13 +257,12 @@ export default function ChartPathProvider({
currData.value = parsedData;
curroriginalData.value = parsedoriginalData;
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
}, [data]);
const isStarted = useSharedValue(false, 'isStarted');
const onLongPressGestureEvent = useAnimatedGestureHandler({
- onActive: event => {
+ onActive: (event) => {
state.value = event.state;
if (!currData.value || currData.value.length === 0) {
return;
@@ -282,7 +290,7 @@ export default function ChartPathProvider({
);
let idx = 0;
- let ss = smoothingStrategy;
+ const ss = smoothingStrategy;
for (let i = 0; i < currData.value.length; i++) {
if (getValue(currData, i, ss).x > eventX / layoutSize.value.width) {
idx = i;
@@ -332,7 +340,7 @@ export default function ChartPathProvider({
);
positionX.value = eventX;
},
- onCancel: event => {
+ onCancel: (event) => {
isStarted.value = false;
state.value = event.state;
originalX.value = '';
@@ -350,7 +358,7 @@ export default function ChartPathProvider({
);
}
},
- onEnd: event => {
+ onEnd: (event) => {
isStarted.value = false;
state.value = event.state;
originalX.value = '';
@@ -372,7 +380,7 @@ export default function ChartPathProvider({
impactHeavy();
}
},
- onFail: event => {
+ onFail: (event) => {
isStarted.value = false;
state.value = event.state;
originalX.value = '';
@@ -390,11 +398,28 @@ export default function ChartPathProvider({
);
}
},
- onStart: event => {
+ onStart: (event) => {
+ // WARNING: the following code does not run on using iOS, but it does on Android.
+ // I use the same code from onActive except of "progress.value = 1" which was taken from the original onStart.
state.value = event.state;
if (!currData.value || currData.value.length === 0) {
return;
}
+ if (!isStarted.value) {
+ dotScale.value = withSpring(
+ 1,
+ combineConfigs(springDefaultConfig, springConfig)
+ );
+ pathOpacity.value = withTiming(
+ 0,
+ combineConfigs(timingFeedbackDefaultConfig, timingFeedbackConfig)
+ );
+ }
+
+ if (hapticsEnabledValue.value && !isStarted.value) {
+ impactHeavy();
+ }
+ isStarted.value = true;
const eventX = positionXWithMargin(
event.x,
@@ -404,8 +429,9 @@ export default function ChartPathProvider({
progress.value = 1;
let idx = 0;
+ const ss = smoothingStrategy;
for (let i = 0; i < currData.value.length; i++) {
- if (currData.value[i].x > eventX / layoutSize.value.width) {
+ if (getValue(currData, i, ss).x > eventX / layoutSize.value.width) {
idx = i;
break;
}
@@ -413,37 +439,48 @@ export default function ChartPathProvider({
idx = currData.value.length - 1;
}
}
+
+ if (
+ ss.value === 'bezier' &&
+ currData.value.length > 30 &&
+ eventX / layoutSize.value.width >=
+ currData.value[currData.value.length - 2].x
+ ) {
+ const prevLastY = currData.value[currData.value.length - 2].y;
+ const prevLastX = currData.value[currData.value.length - 2].x;
+ const lastY = currData.value[currData.value.length - 1].y;
+ const lastX = currData.value[currData.value.length - 1].x;
+ const progress =
+ (eventX / layoutSize.value.width - prevLastX) / (lastX - prevLastX);
+ positionY.value =
+ (prevLastY + progress * (lastY - prevLastY)) *
+ layoutSize.value.height;
+ } else if (idx === 0) {
+ positionY.value =
+ getValue(currData, idx, ss).y * layoutSize.value.height;
+ } else {
+ // prev + diff over X
+ positionY.value =
+ (getValue(currData, idx - 1, ss).y +
+ (getValue(currData, idx, ss).y -
+ getValue(currData, idx - 1, ss).y) *
+ ((eventX / layoutSize.value.width -
+ getValue(currData, idx - 1, ss).x) /
+ (getValue(currData, idx, ss).x -
+ getValue(currData, idx - 1, ss).x))) *
+ layoutSize.value.height;
+ }
+
setoriginalXYAccordingToPosition(
originalX,
originalY,
eventX / layoutSize.value.width,
curroriginalData
);
- dotScale.value = withSpring(
- 1,
- combineConfigs(springDefaultConfig, springConfig)
- );
-
- if (!android) {
- positionX.value = positionXWithMargin(
- eventX,
- 30,
- layoutSize.value.width
- );
- positionY.value = currData.value[idx].y * layoutSize.value.height;
- pathOpacity.value = withTiming(
- 0,
- combineConfigs(timingFeedbackDefaultConfig, timingFeedbackConfig)
- );
- }
- if (hapticsEnabledValue.value && !isStarted.value) {
- impactHeavy();
- }
- isStarted.value = true;
+ positionX.value = eventX;
},
});
- // @ts-ignore
const dotStyle = useAnimatedStyle(
() => ({
opacity: dotScale.value,
@@ -521,7 +558,7 @@ function ChartPath({
let toValue = currData.value;
let res;
let smoothing = 0;
- let strategy = smoothingStrategy.value;
+ const strategy = smoothingStrategy.value;
if (progress.value !== 1) {
const numOfPoints = Math.round(
fromValue.length +
diff --git a/src/charts/linear/ChartPathProvider.js b/src/charts/linear/ChartPathProvider.js
index ac9f6d1..058219e 100644
--- a/src/charts/linear/ChartPathProvider.js
+++ b/src/charts/linear/ChartPathProvider.js
@@ -1,5 +1,6 @@
import React, { useMemo, useState } from 'react';
import { useAnimatedStyle } from 'react-native-reanimated';
+
import ChartContext, { useGenerateValues } from '../../helpers/ChartContext';
export default function ChartPathProvider({ data: providedData, children }) {
diff --git a/src/helpers/useReactiveSharedValue.js b/src/helpers/useReactiveSharedValue.js
index 73cec8d..bc647ba 100644
--- a/src/helpers/useReactiveSharedValue.js
+++ b/src/helpers/useReactiveSharedValue.js
@@ -1,5 +1,4 @@
import { useEffect } from 'react';
-// eslint-disable-next-line import/no-unresolved
import { useSharedValue } from 'react-native-reanimated';
export default function useReactiveSharedValue(prop, name) {
diff --git a/src/helpers/withReanimatedFallback.js b/src/helpers/withReanimatedFallback.js
index 714d19d..9a38ec4 100644
--- a/src/helpers/withReanimatedFallback.js
+++ b/src/helpers/withReanimatedFallback.js
@@ -2,7 +2,7 @@ import React from 'react';
import { Text, TurboModuleRegistry } from 'react-native';
function ChartFallback() {
- return Charts are not available without Reanimated 2;
+ return Charts are not available without Reanimated 2;
}
export default function withReanimatedFallback(
diff --git a/src/interpolations/bSplineInterpolation.js b/src/interpolations/bSplineInterpolation.js
index fdf6f43..e4b6a87 100644
--- a/src/interpolations/bSplineInterpolation.js
+++ b/src/interpolations/bSplineInterpolation.js
@@ -30,7 +30,7 @@ class BSpline {
seqAt(dim) {
let points = this.points;
let margin = this.degree + 1;
- return function (n) {
+ return function(n) {
if (n < margin) {
return points[0][dim];
} else if (points.length + margin <= n) {