gifted-charts-core 0.0.10 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gifted-charts-core",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Mathematical and logical utilities used by react-gifted-charts and react-native-gifted-charts",
5
5
  "main": "index.ts",
6
6
  "files": [
@@ -17,6 +17,8 @@ import {
17
17
  getAxesAndRulesProps,
18
18
  getCurvePathWithSegments,
19
19
  getExtendedContainerHeightWithPadding,
20
+ getInterpolatedData,
21
+ getLineSegmentsForMissingValues,
20
22
  getMaxValue,
21
23
  getNoOfSections,
22
24
  getPathWithHighlight,
@@ -32,7 +34,12 @@ interface extendedLineChartPropsType extends LineChartPropsType {
32
34
  }
33
35
 
34
36
  export const useLineChart = (props: extendedLineChartPropsType) => {
35
- const { animations } = props;
37
+ const {
38
+ animations,
39
+ showDataPointsForMissingValues,
40
+ interpolateMissingValues = true,
41
+ onlyPositive,
42
+ } = props;
36
43
  const curvature = props.curvature ?? LineDefaults.curvature;
37
44
  const curveType = props.curveType ?? LineDefaults.curveType;
38
45
  const [scrollX, setScrollX] = useState(0);
@@ -110,70 +117,114 @@ export const useLineChart = (props: extendedLineChartPropsType) => {
110
117
  if (!props.data) {
111
118
  return [];
112
119
  }
120
+ const nullishHandledData = getInterpolatedData(
121
+ props.data,
122
+ showDataPointsForMissingValues,
123
+ interpolateMissingValues,
124
+ onlyPositive
125
+ );
113
126
  if (props.yAxisOffset) {
114
- return clone(props.data).map((item) => {
127
+ return nullishHandledData.map((item) => {
115
128
  item.value = item.value - (props.yAxisOffset ?? 0);
116
129
  return item;
117
130
  });
118
131
  }
119
- return props.data;
132
+ return nullishHandledData;
120
133
  }, [props.yAxisOffset, props.data]);
121
134
  const data2 = useMemo(() => {
122
135
  if (!props.data2) {
123
136
  return [];
124
137
  }
138
+ const nullishHandledData = getInterpolatedData(
139
+ props.data2,
140
+ showDataPointsForMissingValues,
141
+ interpolateMissingValues,
142
+ onlyPositive
143
+ );
125
144
  if (props.yAxisOffset) {
126
- return clone(props.data2).map((item) => {
145
+ return nullishHandledData.map((item) => {
127
146
  item.value = item.value - (props.yAxisOffset ?? 0);
128
147
  return item;
129
148
  });
130
149
  }
131
- return props.data2;
150
+ return nullishHandledData;
132
151
  }, [props.yAxisOffset, props.data2]);
133
152
  const data3 = useMemo(() => {
134
153
  if (!props.data3) {
135
154
  return [];
136
155
  }
156
+ const nullishHandledData = getInterpolatedData(
157
+ props.data3,
158
+ showDataPointsForMissingValues,
159
+ interpolateMissingValues,
160
+ onlyPositive
161
+ );
137
162
  if (props.yAxisOffset) {
138
- return clone(props.data3).map((item) => {
163
+ return nullishHandledData.map((item) => {
139
164
  item.value = item.value - (props.yAxisOffset ?? 0);
140
165
  return item;
141
166
  });
142
167
  }
143
- return props.data3;
168
+ return nullishHandledData;
144
169
  }, [props.yAxisOffset, props.data3]);
145
170
  const data4 = useMemo(() => {
146
171
  if (!props.data4) {
147
172
  return [];
148
173
  }
174
+ const nullishHandledData = getInterpolatedData(
175
+ props.data4,
176
+ showDataPointsForMissingValues,
177
+ interpolateMissingValues,
178
+ onlyPositive
179
+ );
149
180
  if (props.yAxisOffset) {
150
- return clone(props.data4).map((item) => {
181
+ return nullishHandledData.map((item) => {
151
182
  item.value = item.value - (props.yAxisOffset ?? 0);
152
183
  return item;
153
184
  });
154
185
  }
155
- return props.data4;
186
+ return nullishHandledData;
156
187
  }, [props.yAxisOffset, props.data4]);
157
188
  const data5 = useMemo(() => {
158
189
  if (!props.data5) {
159
190
  return [];
160
191
  }
192
+ const nullishHandledData = getInterpolatedData(
193
+ props.data5,
194
+ showDataPointsForMissingValues,
195
+ interpolateMissingValues,
196
+ onlyPositive
197
+ );
161
198
  if (props.yAxisOffset) {
162
- return clone(props.data5).map((item) => {
199
+ return nullishHandledData.map((item) => {
163
200
  item.value = item.value - (props.yAxisOffset ?? 0);
164
201
  return item;
165
202
  });
166
203
  }
167
- return props.data5;
204
+ return nullishHandledData;
168
205
  }, [props.yAxisOffset, props.data5]);
169
206
 
170
207
  const secondaryData =
171
208
  getSecondaryDataWithOffsetIncluded(
172
209
  props.secondaryData,
173
- props.secondaryYAxis
210
+ props.secondaryYAxis,
211
+ showDataPointsForMissingValues,
212
+ interpolateMissingValues,
213
+ onlyPositive
174
214
  ) || [];
175
215
 
176
- const dataSet = props.dataSet;
216
+ let dataSet = props.dataSet;
217
+ if (dataSet?.length) {
218
+ dataSet = dataSet.map((dataSetItem) => ({
219
+ ...dataSetItem,
220
+ data: getInterpolatedData(
221
+ dataSetItem.data,
222
+ showDataPointsForMissingValues,
223
+ interpolateMissingValues,
224
+ onlyPositive
225
+ ),
226
+ }));
227
+ }
177
228
  const data0 = useMemo(() => {
178
229
  if (props.yAxisOffset) {
179
230
  return dataSet?.[0]?.data;
@@ -224,7 +275,9 @@ export const useLineChart = (props: extendedLineChartPropsType) => {
224
275
  const startIndex5 = props.startIndex5 || 0;
225
276
  const endIndex5 = props.endIndex5 ?? data5.length - 1;
226
277
 
227
- const lineSegments = props.lineSegments;
278
+ const lineSegments = !interpolateMissingValues
279
+ ? getLineSegmentsForMissingValues(data)
280
+ : props.lineSegments;
228
281
  const lineSegments2 = props.lineSegments2;
229
282
  const lineSegments3 = props.lineSegments3;
230
283
  const lineSegments4 = props.lineSegments4;
@@ -242,7 +295,7 @@ export const useLineChart = (props: extendedLineChartPropsType) => {
242
295
  const endSpacing =
243
296
  props.endSpacing ?? (adjustToWidth ? 0 : LineDefaults.endSpacing);
244
297
 
245
- const thickness = props.thickness || LineDefaults.thickness;
298
+ const thickness = props.thickness ?? LineDefaults.thickness;
246
299
 
247
300
  const yAxisLabelWidth =
248
301
  props.yAxisLabelWidth ??
@@ -693,24 +746,27 @@ export const useLineChart = (props: extendedLineChartPropsType) => {
693
746
  );
694
747
  };
695
748
 
696
- const getNextPoint = (data, index, around) => {
749
+ const getNextPoint = (data, index, around, before) => {
697
750
  const isLast = index === data.length - 1;
698
- return isLast && !around
751
+ return isLast && !(around || before)
699
752
  ? " "
700
753
  : " L" +
701
- (getX(index) + (around ? (isLast ? 0 : spacing / 2) : spacing)) +
754
+ (getX(index) +
755
+ (around ? (isLast ? 0 : spacing / 2) : before ? 0 : spacing)) +
702
756
  " " +
703
757
  getY(data[index].value) +
704
758
  " ";
705
759
  };
706
760
  const getStepPath = (data, i) => {
707
761
  const around = edgePosition === EdgePosition.AROUND_DATA_POINT;
762
+ const before = edgePosition === EdgePosition.BEFORE_DATA_POINT;
708
763
  return (
709
764
  "L" +
710
- (getX(i) - (around && i > 0 ? spacing / 2 : 0)) +
765
+ (getX(i) -
766
+ (around && i > 0 ? spacing / 2 : before && i > 0 ? spacing : 0)) +
711
767
  " " +
712
768
  getY(data[i].value) +
713
- getNextPoint(data, i, around)
769
+ getNextPoint(data, i, around, before)
714
770
  );
715
771
  };
716
772
 
@@ -319,6 +319,10 @@ export type LineChartPropsType = {
319
319
  onEndReached?: () => void;
320
320
  onStartReached?: () => void;
321
321
  endReachedOffset?: number;
322
+
323
+ showDataPointsForMissingValues?: boolean;
324
+ interpolateMissingValues?: boolean;
325
+ onlyPositive?: boolean;
322
326
  };
323
327
 
324
328
  export type lineDataItem = {
@@ -234,7 +234,7 @@ export const LineDefaults = {
234
234
  stripWidth: 2,
235
235
  unFocusOnPressOut: true,
236
236
  delayBeforeUnFocus: 300,
237
- edgePosition: EdgePosition.AT_DATA_POINT,
237
+ edgePosition: EdgePosition.AFTER_DATA_POINT,
238
238
  endReachedOffset: defaultEndReachedOffset,
239
239
  };
240
240
 
@@ -1,3 +1,4 @@
1
+ import { lineDataItem } from "../LineChart/types";
1
2
  import {
2
3
  AxesAndRulesDefaults,
3
4
  BarDefaults,
@@ -720,15 +721,25 @@ export const getExtendedContainerHeightWithPadding = (
720
721
 
721
722
  export const getSecondaryDataWithOffsetIncluded = (
722
723
  secondaryData?: any,
723
- secondaryYAxis?: any
724
+ secondaryYAxis?: any,
725
+ showDataPointsForMissingValues?: boolean,
726
+ interpolateMissingValues?: boolean,
727
+ onlyPositive?: boolean
724
728
  ) => {
725
- if (secondaryData && secondaryYAxis?.yAxisOffset) {
726
- return secondaryData?.map((item) => {
729
+ if (!secondaryData) return secondaryData;
730
+ const nullishHandledData = getInterpolatedData(
731
+ secondaryData,
732
+ showDataPointsForMissingValues,
733
+ interpolateMissingValues,
734
+ onlyPositive
735
+ );
736
+ if (secondaryYAxis?.yAxisOffset) {
737
+ return nullishHandledData.map((item) => {
727
738
  item.value = item.value - (secondaryYAxis?.yAxisOffset ?? 0);
728
739
  return item;
729
740
  });
730
741
  }
731
- return secondaryData;
742
+ return nullishHandledData;
732
743
  };
733
744
 
734
745
  export const getArrowProperty = (
@@ -1209,3 +1220,155 @@ export const getBarWidth = (
1209
1220
  }
1210
1221
  return localBarWidth;
1211
1222
  };
1223
+
1224
+ export const getInterpolatedData = (
1225
+ dataParam: lineDataItem[],
1226
+ showDataPointsForMissingValues?: boolean,
1227
+ interpolateMissingValues?: boolean,
1228
+ onlyPositive?: boolean
1229
+ ): lineDataItem[] => {
1230
+ if (!interpolateMissingValues) {
1231
+ return dataParam.map((item) => {
1232
+ if (typeof item.value !== "number") {
1233
+ if (showDataPointsForMissingValues) return { ...item, value: 0 };
1234
+ return { ...item, value: 0, hideDataPoint: true };
1235
+ }
1236
+ return item;
1237
+ });
1238
+ }
1239
+ if (!interpolateMissingValues) return dataParam;
1240
+ const data = clone(dataParam);
1241
+ const n = data.length;
1242
+
1243
+ /************** PRE-PROCESSING **************/
1244
+ let numericValue;
1245
+ const numericValuesLength = data.filter((item) => {
1246
+ const isNum = typeof item.value === "number";
1247
+ if (isNum) {
1248
+ numericValue = item.value;
1249
+ return true;
1250
+ }
1251
+ return false;
1252
+ }).length;
1253
+
1254
+ if (!numericValuesLength) return [];
1255
+
1256
+ if (numericValuesLength === 1) {
1257
+ data.forEach((item) => {
1258
+ if (!showDataPointsForMissingValues && typeof item.value !== "number") {
1259
+ item.hideDataPoint = true;
1260
+ }
1261
+ item.value = numericValue;
1262
+ });
1263
+ return data;
1264
+ }
1265
+ /**********************************************************************/
1266
+
1267
+ data.forEach((item, index) => {
1268
+ if (typeof item.value === "number") return;
1269
+ // Cut the line in 2 halves-> pre and post
1270
+ // Now there are 4 possibilities-
1271
+ // 1. Both pre and post have valid values
1272
+ // 2. Only pre has valid value
1273
+ // 3. Only post has valid value
1274
+ // 4. None has valid value -> this is already handled in preprocessing
1275
+
1276
+ const pre = data.slice(0, index);
1277
+ const post = data.slice(index + 1, n);
1278
+
1279
+ const preValidIndex = pre.findLastIndex(
1280
+ (item) => typeof item.value === "number"
1281
+ );
1282
+ const postValidInd = post.findIndex(
1283
+ (item) => typeof item.value === "number"
1284
+ );
1285
+ const postValidIndex = postValidInd + index + 1;
1286
+
1287
+ let count, step;
1288
+
1289
+ // 1. Both pre and post have valid values
1290
+ if (preValidIndex !== -1 && postValidInd !== -1) {
1291
+ count = postValidIndex - preValidIndex;
1292
+ step = (data[postValidIndex].value - data[preValidIndex].value) / count;
1293
+ data[index].value =
1294
+ data[preValidIndex].value + step * (index - preValidIndex);
1295
+ }
1296
+
1297
+ // 2. Only pre has valid value
1298
+ else if (preValidIndex !== -1 && postValidInd === -1) {
1299
+ // Now there are 2 possibilities-
1300
+ // 1. There's only 1 valid value in the pre -> this is already handled in preprocessing
1301
+ // 2. There are more than valid values in pre
1302
+ const secondPre = data.slice(0, preValidIndex);
1303
+ const secondPreIndex = secondPre.findLastIndex(
1304
+ (item) => typeof item.value === "number"
1305
+ );
1306
+
1307
+ count = preValidIndex - secondPreIndex;
1308
+ step = (data[secondPreIndex].value - data[preValidIndex].value) / count;
1309
+ data[index].value =
1310
+ data[preValidIndex].value - step * (index - preValidIndex);
1311
+ }
1312
+
1313
+ // 3. Only post has valid value
1314
+ else if (preValidIndex === -1 && postValidInd !== -1) {
1315
+ // Now there are 2 possibilities-
1316
+ // 1. There's only 1 valid value in the post -> this is already handled in preprocessing
1317
+ // 2. There are more than valid values in post
1318
+
1319
+ const secondPost = data.slice(postValidIndex + 1, n);
1320
+ const secondPostInd = secondPost.findIndex(
1321
+ (item) => typeof item.value === "number"
1322
+ );
1323
+ const secondPostIndex = secondPostInd + postValidIndex + 1;
1324
+
1325
+ count = secondPostIndex - postValidIndex;
1326
+ step = (data[secondPostIndex].value - data[postValidIndex].value) / count;
1327
+ data[index].value =
1328
+ data[postValidIndex].value - step * (postValidIndex - index);
1329
+ }
1330
+
1331
+ // hide data point (since it is interpolated)
1332
+ if (!showDataPointsForMissingValues) {
1333
+ item.hideDataPoint = true;
1334
+ }
1335
+ });
1336
+ return onlyPositive
1337
+ ? data.map((item) => ({ ...item, value: Math.max(item.value, 0) }))
1338
+ : data;
1339
+ };
1340
+
1341
+ export const getLineSegmentsForMissingValues = (
1342
+ data: lineDataItem[]
1343
+ ): LineSegment[] => {
1344
+ let i,
1345
+ n = data.length;
1346
+ const numericValuesLength = data.filter(
1347
+ (item) => typeof item.value === "number"
1348
+ ).length;
1349
+ if (!numericValuesLength) return [];
1350
+ const segments: LineSegment[] = [];
1351
+ for (i = 0; i < n; i++) {
1352
+ if (typeof data[i].value !== "number") {
1353
+ const nextValidInd = data
1354
+ .slice(i + 1, n)
1355
+ .findIndex((item) => typeof item.value === "number");
1356
+ if (nextValidInd === -1) {
1357
+ segments.push({
1358
+ startIndex: Math.max(i - 1, 0),
1359
+ endIndex: n,
1360
+ color: "transparent",
1361
+ });
1362
+ break;
1363
+ }
1364
+ const nextValidIndex = nextValidInd + i + 1;
1365
+ segments.push({
1366
+ startIndex: Math.max(i - 1, 0),
1367
+ endIndex: nextValidIndex,
1368
+ color: "transparent",
1369
+ });
1370
+ i = nextValidIndex;
1371
+ }
1372
+ }
1373
+ return segments;
1374
+ };
@@ -16,8 +16,9 @@ export enum CurveType {
16
16
  }
17
17
 
18
18
  export enum EdgePosition {
19
- AT_DATA_POINT,
19
+ AFTER_DATA_POINT,
20
20
  AROUND_DATA_POINT,
21
+ BEFORE_DATA_POINT,
21
22
  }
22
23
 
23
24
  export type RulesConfig = {