@bashem/rn-charts 0.0.4 → 0.0.5

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.
Files changed (79) hide show
  1. package/README.md +1 -1
  2. package/lib/module/skia/AreaChart/AreaChart.js +60 -51
  3. package/lib/module/skia/AreaChart/AreaChart.js.map +1 -1
  4. package/lib/module/skia/AreaChart/useAreaChart.js +18 -14
  5. package/lib/module/skia/AreaChart/useAreaChart.js.map +1 -1
  6. package/lib/module/skia/BarChart/BarChart.js +156 -69
  7. package/lib/module/skia/BarChart/BarChart.js.map +1 -1
  8. package/lib/module/skia/BarChart/useBarChart.js +53 -41
  9. package/lib/module/skia/BarChart/useBarChart.js.map +1 -1
  10. package/lib/module/skia/Common/HorizontalLabelView.js +121 -0
  11. package/lib/module/skia/Common/HorizontalLabelView.js.map +1 -0
  12. package/lib/module/skia/Common/VerticalLabelView.js +100 -0
  13. package/lib/module/skia/Common/VerticalLabelView.js.map +1 -0
  14. package/lib/module/skia/Common/useComponentLayout.js +13 -0
  15. package/lib/module/skia/Common/useComponentLayout.js.map +1 -0
  16. package/lib/module/skia/HeatMap/DateHeatMap.js +472 -0
  17. package/lib/module/skia/HeatMap/DateHeatMap.js.map +1 -0
  18. package/lib/module/skia/HeatMap/HeatMap.js +121 -33
  19. package/lib/module/skia/HeatMap/HeatMap.js.map +1 -1
  20. package/lib/module/skia/HeatMap/useHeatMap.js +132 -48
  21. package/lib/module/skia/HeatMap/useHeatMap.js.map +1 -1
  22. package/lib/module/skia/PieChart/PieChart.js +21 -17
  23. package/lib/module/skia/PieChart/PieChart.js.map +1 -1
  24. package/lib/module/skia/PieChart/usePieChart.js +241 -30
  25. package/lib/module/skia/PieChart/usePieChart.js.map +1 -1
  26. package/lib/module/skia/Popup.js +7 -5
  27. package/lib/module/skia/Popup.js.map +1 -1
  28. package/lib/module/skia/common.js +4 -41
  29. package/lib/module/skia/common.js.map +1 -1
  30. package/lib/typescript/src/index.d.ts +1 -0
  31. package/lib/typescript/src/index.d.ts.map +1 -1
  32. package/lib/typescript/src/skia/AreaChart/AreaChart.d.ts +15 -3
  33. package/lib/typescript/src/skia/AreaChart/AreaChart.d.ts.map +1 -1
  34. package/lib/typescript/src/skia/AreaChart/useAreaChart.d.ts +13 -14
  35. package/lib/typescript/src/skia/AreaChart/useAreaChart.d.ts.map +1 -1
  36. package/lib/typescript/src/skia/BarChart/BarChart.d.ts +16 -2
  37. package/lib/typescript/src/skia/BarChart/BarChart.d.ts.map +1 -1
  38. package/lib/typescript/src/skia/BarChart/useBarChart.d.ts +17 -5
  39. package/lib/typescript/src/skia/BarChart/useBarChart.d.ts.map +1 -1
  40. package/lib/typescript/src/skia/Common/HorizontalLabelView.d.ts +33 -0
  41. package/lib/typescript/src/skia/Common/HorizontalLabelView.d.ts.map +1 -0
  42. package/lib/typescript/src/skia/Common/VerticalLabelView.d.ts +24 -0
  43. package/lib/typescript/src/skia/Common/VerticalLabelView.d.ts.map +1 -0
  44. package/lib/typescript/src/skia/Common/useComponentLayout.d.ts +4 -0
  45. package/lib/typescript/src/skia/Common/useComponentLayout.d.ts.map +1 -0
  46. package/lib/typescript/src/skia/HeatMap/DateHeatMap.d.ts +1 -0
  47. package/lib/typescript/src/skia/HeatMap/DateHeatMap.d.ts.map +1 -0
  48. package/lib/typescript/src/skia/HeatMap/HeatMap.d.ts +28 -12
  49. package/lib/typescript/src/skia/HeatMap/HeatMap.d.ts.map +1 -1
  50. package/lib/typescript/src/skia/HeatMap/useHeatMap.d.ts +15 -4
  51. package/lib/typescript/src/skia/HeatMap/useHeatMap.d.ts.map +1 -1
  52. package/lib/typescript/src/skia/PieChart/PieChart.d.ts +5 -1
  53. package/lib/typescript/src/skia/PieChart/PieChart.d.ts.map +1 -1
  54. package/lib/typescript/src/skia/PieChart/usePieChart.d.ts +6 -1
  55. package/lib/typescript/src/skia/PieChart/usePieChart.d.ts.map +1 -1
  56. package/lib/typescript/src/skia/Popup.d.ts.map +1 -1
  57. package/lib/typescript/src/skia/common.d.ts +3 -10
  58. package/lib/typescript/src/skia/common.d.ts.map +1 -1
  59. package/package.json +6 -3
  60. package/src/index.tsx +6 -4
  61. package/src/skia/AreaChart/AreaChart.tsx +85 -62
  62. package/src/skia/AreaChart/useAreaChart.ts +25 -26
  63. package/src/skia/BarChart/BarChart.tsx +163 -95
  64. package/src/skia/BarChart/useBarChart.ts +55 -44
  65. package/src/skia/Common/HorizontalLabelView.tsx +153 -0
  66. package/src/skia/Common/VerticalLabelView.tsx +113 -0
  67. package/src/skia/Common/useComponentLayout.ts +14 -0
  68. package/src/skia/HeatMap/DateHeatMap.tsx +470 -0
  69. package/src/skia/HeatMap/HeatMap.tsx +168 -54
  70. package/src/skia/HeatMap/useHeatMap.ts +139 -65
  71. package/src/skia/PieChart/PieChart.tsx +16 -11
  72. package/src/skia/PieChart/usePieChart.ts +316 -66
  73. package/src/skia/Popup.tsx +38 -36
  74. package/src/skia/common.ts +8 -46
  75. package/lib/module/skia/Common/VerticalLabel.js +0 -73
  76. package/lib/module/skia/Common/VerticalLabel.js.map +0 -1
  77. package/lib/typescript/src/skia/Common/VerticalLabel.d.ts +0 -17
  78. package/lib/typescript/src/skia/Common/VerticalLabel.d.ts.map +0 -1
  79. package/src/skia/Common/VerticalLabel.tsx +0 -91
@@ -1,21 +1,26 @@
1
1
  import React, { type Ref } from 'react';
2
- import { View, Modal } from 'react-native';
3
- import { Canvas, Group, Rect } from '@shopify/react-native-skia';
2
+ import { View } from 'react-native';
3
+ import { Canvas, Group, Rect, rect, type SkHostRect } from '@shopify/react-native-skia';
4
4
  import useHeatMap from './useHeatMap';
5
- import type { CommonStyle } from '../common';
5
+ import type { CommonStyle, HandleOutSideTouch } from '../common';
6
6
  import Popup, { type PopupStyle } from '../Popup';
7
+ import VerticalLabelView, { type VerticalLabelStyle } from "../Common/VerticalLabelView";
8
+ import HorizontalLabelView, { type HorizontalLabelStyleExtended } from "../Common/HorizontalLabelView";
7
9
 
8
- export type DayData = {
9
- date: string;
10
+ export type CellDatum = {
11
+ rowIndex: number;
12
+ colIndex: number;
13
+ groupIndex: number;
10
14
  value: number;
11
- dayOfWeek: number;
12
- week: number;
13
15
  x: number;
14
16
  y: number;
15
17
  };
16
18
 
17
- export interface HandleOutSideTouch {
18
- touchedOutside: () => void;
19
+ export interface HeatMapDataGroup {
20
+ cols: number;
21
+ startingRow: number;
22
+ endingRow: number;
23
+ data?: Record<number, Record<number, number>>;
19
24
  }
20
25
 
21
26
  export interface HeatMapStyle extends CommonStyle {
@@ -23,22 +28,55 @@ export interface HeatMapStyle extends CommonStyle {
23
28
  cellGap?: number;
24
29
  cellMaxColor?: string;
25
30
  cellMinColor?: string;
31
+ horizontalLabelStyle?: HorizontalLabelStyleExtended;
32
+ verticalLabelStyle?: VerticalLabelStyle;
33
+ interGroupSpacing?: number;
26
34
  }
27
35
 
28
36
  export interface HeatMapProps {
29
- startDate: string;
30
- endDate: string;
31
- data?: Record<string, number>;
37
+ rows: number;
38
+ data: HeatMapDataGroup[];
39
+ coalesceGroups?: boolean;
32
40
  style?: HeatMapStyle;
33
41
  minValue?: number;
34
42
  maxValue?: number;
43
+ xLabelSkiaView?: (
44
+ index: number,
45
+ rect: SkHostRect,
46
+ ) => React.JSX.Element | undefined;
47
+ yLabelSkiaView?: (
48
+ index: number,
49
+ yPosition: number
50
+ ) => React.JSX.Element | undefined;
51
+
52
+ xLabelView?: (
53
+ index: number,
54
+ width: number
55
+ ) => React.JSX.Element | undefined;
56
+ yLabelView?: (
57
+ index: number
58
+ ) => React.JSX.Element | undefined;
59
+
60
+ cellSkiaView?: (
61
+ rect: SkHostRect,
62
+ day: CellDatum
63
+ ) => React.JSX.Element | undefined;
64
+
65
+ onSelectSkiaView?: (
66
+ rect: SkHostRect,
67
+ day: CellDatum
68
+ ) => React.JSX.Element | undefined;
69
+ onSelectView?: (
70
+ rect: SkHostRect,
71
+ day: CellDatum
72
+ ) => React.JSX.Element | undefined;
35
73
  ref?: Ref<HandleOutSideTouch | undefined>;
36
- popupStyle?: PopupStyle<DayData>;
74
+ popupStyle?: PopupStyle<CellDatum>;
37
75
  }
38
76
 
39
- function HeatMap(props: HeatMapProps) {
77
+ function HeatMap({ yLabelView, yLabelSkiaView, xLabelView, xLabelSkiaView, cellSkiaView, onSelectView, onSelectSkiaView, ...props }: HeatMapProps) {
40
78
  const {
41
- daysInRange,
79
+ cellData,
42
80
  totalWidth,
43
81
  totalHeight,
44
82
  popupData,
@@ -48,59 +86,135 @@ function HeatMap(props: HeatMapProps) {
48
86
  getColor,
49
87
  cellSize,
50
88
  onTouchOutside,
89
+ horizontalLabelHeight,
90
+ verticalLabelWidth,
91
+ setHorizontalLabelHeight,
92
+ setVerticalLabelWidth,
93
+ numberOfRows,
94
+ paddingTop,
95
+ paddingBottom,
96
+ paddingLeft,
97
+ paddingRight,
98
+ xLabelsRect
51
99
  } = useHeatMap(props);
52
100
 
53
101
  const [viewOffset, setViewOffset] = React.useState({ x: 0, y: 0 });
102
+ const onSelectSkiaViewMemo = React.useMemo(() => {
103
+ if (!popupData || !onSelectSkiaView) {
104
+ return undefined;
105
+ }
106
+ return onSelectSkiaView?.(rect(popupData.x, popupData.y, cellSize, cellSize), popupData.data);
107
+ }, [popupData, onSelectSkiaView, cellSize]);
108
+
109
+ const onSelectViewMemo = React.useMemo(() => {
110
+ if (!popupData || !onSelectView) {
111
+ return undefined;
112
+ }
113
+ return onSelectView(rect(popupData.x, popupData.y, cellSize, cellSize), popupData.data);
114
+ }, [popupData, onSelectView, cellSize]);
54
115
 
55
- return (
56
- <View
57
- style={{ backgroundColor: props.style?.backgroundColor }}
58
- ref={(view) => {
59
- view?.measureInWindow((fx, fy) => {
60
- setViewOffset((prev) => {
61
- if (prev.x === fx && prev.y === fy) {
62
- return prev;
63
- }
64
- return { x: fx, y: fy };
65
- });
116
+ return <View
117
+ style={{ backgroundColor: props.style?.backgroundColor, paddingLeft, paddingRight, paddingTop, paddingBottom }}
118
+ ref={(view) => {
119
+ view?.measureInWindow((fx, fy) => {
120
+ setViewOffset((prev) => {
121
+ if (prev.x === fx && prev.y === fy) {
122
+ return prev;
123
+ }
124
+ return { x: fx, y: fy };
66
125
  });
67
- }}
68
- >
69
- <Canvas
70
- style={{ width: totalWidth, height: totalHeight }}
71
- onTouchStart={(event) =>
72
- touchHandler(event.nativeEvent.locationX, event.nativeEvent.locationY)
126
+ });
127
+ }}
128
+ >
129
+ <View style={{ flexDirection: "row" }}>
130
+ {
131
+ (yLabelView || yLabelSkiaView) && (<VerticalLabelView
132
+ onLayout={(event) => {
133
+ setVerticalLabelWidth(event.nativeEvent.layout.width);
134
+ }}
135
+ labelPercentages={Array.from({ length: numberOfRows }, (_, i) => (i + 1) / numberOfRows)}
136
+ styles={{
137
+ height: totalHeight,
138
+ top: horizontalLabelHeight,
139
+ verticalLabelStyle: props.style?.verticalLabelStyle,
140
+ }}
141
+ labelSkiaView={(_percentage, yPosition, index) => yLabelSkiaView?.(index, yPosition)}
142
+ >
143
+ {(_percentage, index) => yLabelView?.(index)}
144
+ </VerticalLabelView>)
145
+ }
146
+ <View style={{ width: totalWidth, height: totalHeight }}>
147
+ {
148
+ (xLabelView || xLabelSkiaView) &&
149
+ <HorizontalLabelView
150
+ labels={xLabelsRect.map((rect) => rect)}
151
+ positions={xLabelsRect.map(rect => rect.x)}
152
+ style={{
153
+ width: totalWidth,
154
+ horizontalLabelStyle: props.style?.horizontalLabelStyle,
155
+ }}
156
+ onLayout={(event) => setHorizontalLabelHeight(event.nativeEvent.layout.height)}
157
+ labelSkiaView={(yPostion, index, data) => xLabelSkiaView?.(index, data)}
158
+ >
159
+ {(index, rect) => xLabelView?.(index, rect.width)}
160
+ </HorizontalLabelView>
73
161
  }
74
- >
75
- <Group>
76
- {daysInRange.map((day) => {
77
- return (
78
- <Rect
79
- key={day.date}
80
- x={day.x}
81
- y={day.y}
82
- width={cellSize}
83
- height={cellSize}
84
- color={getColor(day.value)}
85
- />
86
- );
87
- })}
88
- </Group>
89
- </Canvas>
162
+ <Canvas
163
+ style={{ width: totalWidth, height: totalHeight }}
164
+ onTouchStart={(event) =>
165
+ touchHandler(event.nativeEvent.locationX, event.nativeEvent.locationY)
166
+ }
167
+ >
168
+ <Group>
169
+ {cellData.map((datum) => {
170
+ let skiaView = cellSkiaView?.(rect(datum.x, datum.y, cellSize, cellSize), datum);
171
+ if (skiaView !== undefined) { return skiaView; }
172
+ return (
173
+ <Rect
174
+ key={datum.x + '-' + datum.y}
175
+ x={datum.x}
176
+ y={datum.y}
177
+ width={cellSize}
178
+ height={cellSize}
179
+ color={getColor(datum.value)}
180
+ />
181
+ );
182
+ })}
183
+ {onSelectSkiaViewMemo}
184
+ </Group>
185
+ </Canvas>
186
+ </View>
187
+ </View>
90
188
 
91
- {popupData && props.popupStyle && (
189
+ {
190
+ popupData && (<>
191
+ {onSelectViewMemo &&
192
+ <View
193
+ style={{
194
+ position: "absolute",
195
+ top: popupData.y + (props.style?.horizontalLabelStyle?.viewPosition === "bottom" ? 0 : horizontalLabelHeight) + paddingTop,
196
+ left: popupData.x + verticalLabelWidth + paddingLeft
197
+ }}
198
+ >
199
+ {onSelectViewMemo}
200
+ </View>
201
+ }
92
202
  <Popup
93
- popupData={{ x: popupData.x, y: popupData.y, data: popupData.day }}
203
+ popupData={{ x: popupData.x + verticalLabelWidth + paddingLeft, y: popupData.y + (props.style?.horizontalLabelStyle?.viewPosition === "bottom" ? 0 : horizontalLabelHeight) + paddingTop, data: popupData.data }}
94
204
  totalWidth={totalWidth}
95
205
  totalHeight={totalHeight}
96
- touchHandler={touchHandler}
206
+ touchHandler={(x, y) => {
207
+ console.log(x, y)
208
+ touchHandler(x - verticalLabelWidth - paddingLeft, y - (props.style?.horizontalLabelStyle?.viewPosition === "bottom" ? 0 : horizontalLabelHeight) - paddingTop);
209
+ }}
97
210
  onTouchOutside={onTouchOutside}
98
211
  popupStyle={props.popupStyle}
99
212
  viewOffset={viewOffset}
100
213
  />
101
- )}
102
- </View>
103
- );
214
+ </>
215
+ )
216
+ }
217
+ </View >;
104
218
  }
105
219
 
106
220
  export default HeatMap;
@@ -1,14 +1,16 @@
1
1
  import type { View } from "react-native-reanimated/lib/typescript/Animated";
2
- import type { DayData, HeatMapProps } from "./HeatMap";
2
+ import type { CellDatum, HeatMapProps } from "./HeatMap";
3
3
  import { useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ import { getPaddings } from "../common";
5
+ import { rect, type SkHostRect } from "@shopify/react-native-skia";
4
6
 
5
7
  function useHeatMap({
6
- startDate,
7
- endDate,
8
+ rows,
8
9
  data,
9
10
  style,
10
11
  minValue,
11
12
  maxValue,
13
+ coalesceGroups,
12
14
  ref,
13
15
  popupStyle
14
16
  }: HeatMapProps) {
@@ -18,11 +20,18 @@ function useHeatMap({
18
20
  const cellMaxColor = style?.cellMaxColor ?? '#50f555ff';
19
21
  const cellMinColor = style?.cellMinColor ?? '#ffffffff';
20
22
 
21
- const numberOfDaysInWeek = 7;
22
- const numberOfMsInDay = 1000 * 60 * 60 * 24;
23
+ const [verticalLabelWidth, setVerticalLabelWidth] = useState<number>(0);
24
+ const [horizontalLabelHeight, setHorizontalLabelHeight] = useState<number>(0);
25
+ const {
26
+ paddingLeft,
27
+ paddingRight,
28
+ paddingTop,
29
+ paddingBottom
30
+ } = getPaddings(style);
23
31
 
32
+ const numberOfRows = rows;
24
33
  const [popupData, setPopupData] = useState<
25
- { x: number; y: number; day: DayData; } | undefined
34
+ { x: number; y: number; data: CellDatum; } | undefined
26
35
  >(undefined);
27
36
 
28
37
  const [popupDimension, setPopupDimension] = useState({
@@ -32,54 +41,82 @@ function useHeatMap({
32
41
 
33
42
  const popupRef = useRef<View>(null);
34
43
 
35
- const formatDate = (date: Date) =>
36
- `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(
37
- 2,
38
- '0'
39
- )}-${String(date.getDate()).padStart(2, '0')}`;
40
-
41
- const { daysInRange, computedMin, computedMax } = useMemo(() => {
42
- const start = new Date(startDate);
43
- const end = new Date(endDate);
44
-
45
- const output: DayData[] = [];
44
+ const { cellData, computedMin, computedMax, numberOfCols, totalInterGroupSpacing, xLabelsRect, groupInfos } = useMemo(() => {
46
45
  let computedMax = Number.MIN_VALUE;
47
46
  let computedMin = Number.MAX_VALUE;
48
47
 
49
- const startDayOfWeek = start.getDay();
48
+ let nextRow = 0;
49
+ let nextCol = 0;
50
+ let xLabelsRect: SkHostRect[] = [];
50
51
 
51
- for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
52
- const dateStr = formatDate(d);
53
- const value = data?.[dateStr] ?? 0;
52
+ let cellsInRange: CellDatum[] = [];
53
+ let totalInterGroupSpacing = 0;
54
+ let groupInfos: { startingX: number, endingX: number, startingCellIndex: number, lastCellIndex: number; }[] = [];
54
55
 
55
- const dayOfWeek = d.getDay();
56
- const daysFromStart = Math.floor(
57
- (d.getTime() - start.getTime()) / numberOfMsInDay
58
- );
56
+ data.forEach((datum, index) => {
57
+ let startingRow = datum.startingRow;
58
+ let currentCol = nextCol;
59
59
 
60
- const week = Math.floor(
61
- (startDayOfWeek + daysFromStart) / numberOfDaysInWeek
62
- );
60
+ if (index !== 0 && (coalesceGroups === false || datum.startingRow < nextRow)) {
61
+ currentCol++;
62
+ let additionalGroupSpacing = coalesceGroups ? 0 : (style?.interGroupSpacing ?? 0);
63
+ totalInterGroupSpacing += additionalGroupSpacing;
64
+ }
63
65
 
64
- computedMax = Math.max(computedMax, value);
65
- computedMin = Math.min(computedMin, value);
66
+ let minX = Number.MAX_VALUE;
67
+ let maxX = Number.MIN_VALUE;
68
+ let firstIndexInGroup = cellsInRange.length;
69
+ let lastIndexInGroup = firstIndexInGroup;
70
+
71
+ for (let i = 0; i < datum.cols; i++) {
72
+ for (let j = startingRow; j < (i === datum.cols - 1 ? datum.endingRow + 1 : rows); j++) {
73
+ const value = datum.data?.[j]?.[i] ?? 0;
74
+ computedMax = Math.max(computedMax, value);
75
+ computedMin = Math.min(computedMin, value);
76
+
77
+ let x = currentCol * (cellSize + cellGap) + totalInterGroupSpacing;
78
+ let y = j * (cellSize + cellGap);
79
+ minX = Math.min(minX, x);
80
+ maxX = Math.max(maxX, x);
81
+ cellsInRange.push({
82
+ rowIndex: j,
83
+ colIndex: currentCol,
84
+ groupIndex: data.indexOf(datum),
85
+ value,
86
+ x: x,
87
+ y: y,
88
+ });
89
+ lastIndexInGroup = cellsInRange.length - 1;
90
+ nextRow = j + 1;
91
+ }
92
+
93
+ startingRow = 0;
94
+ if (nextRow == rows) {
95
+ currentCol++;
96
+ nextRow = 0;
97
+ }
98
+ }
66
99
 
67
- output.push({
68
- date: dateStr,
69
- value,
70
- dayOfWeek,
71
- week,
72
- x: week * (cellSize + cellGap),
73
- y: dayOfWeek * (cellSize + cellGap),
100
+ nextCol = Math.max(nextCol, currentCol);
101
+ xLabelsRect.push(rect(minX, 0, maxX - minX + cellSize + 1, style?.horizontalLabelStyle?.height ?? -1));
102
+ groupInfos.push({
103
+ startingX: minX,
104
+ endingX: maxX + cellSize,
105
+ startingCellIndex: firstIndexInGroup,
106
+ lastCellIndex: lastIndexInGroup
74
107
  });
75
- }
108
+ });
76
109
 
77
110
  return {
78
- daysInRange: output,
111
+ cellData: cellsInRange,
112
+ numberOfCols: nextCol + (nextRow > 0 ? 1 : 0),
113
+ totalInterGroupSpacing,
114
+ xLabelsRect,
115
+ groupInfos,
79
116
  computedMin: minValue !== undefined ? minValue : computedMin,
80
117
  computedMax: maxValue !== undefined ? maxValue : computedMax,
81
118
  };
82
- }, [startDate, endDate, data, minValue, maxValue, cellSize, cellGap]);
119
+ }, [rows, data, minValue, maxValue, cellSize, cellGap]);
83
120
 
84
121
  // --- COLOR LOGIC ---
85
122
  const getColor = (value: number) => {
@@ -101,9 +138,8 @@ function useHeatMap({
101
138
  };
102
139
 
103
140
  // Heatmap size
104
- const numWeeks = Math.ceil(daysInRange.length / 7 + 1);
105
- const totalWidth = numWeeks * (cellSize + cellGap);
106
- const totalHeight = 7 * (cellSize + cellGap);
141
+ const totalWidth = numberOfCols * (cellSize + cellGap) + totalInterGroupSpacing + verticalLabelWidth;
142
+ const totalHeight = rows * (cellSize + cellGap) + horizontalLabelHeight;
107
143
 
108
144
  // --- POPUP MEASUREMENT ---
109
145
  useLayoutEffect(() => {
@@ -121,27 +157,55 @@ function useHeatMap({
121
157
  return;
122
158
  }
123
159
 
124
- const col = Math.floor(x / (cellSize + cellGap));
125
- const row = Math.floor(y / (cellSize + cellGap));
126
-
127
- const start = new Date(startDate);
128
- const startDayOfWeek = start.getDay();
129
-
130
- const index = col * numberOfDaysInWeek + row;
131
-
132
- if (
133
- index >= startDayOfWeek &&
134
- index - startDayOfWeek < daysInRange.length
135
- ) {
136
- const day = daysInRange[index - startDayOfWeek];
137
- if (day) {
138
- setPopupData({
139
- x: col * (cellSize + cellGap),
140
- y: row * (cellSize + cellGap),
141
- day,
142
- });
143
- return;
160
+ let cellIndex: number | undefined = undefined;
161
+
162
+ for (const [index, group] of groupInfos.entries()) {
163
+ if (x > group.endingX) continue;
164
+ if (x < group.startingX) continue;
165
+
166
+ let groupData = data[index];
167
+ if (!groupData) break;
168
+
169
+ let colIndex = Math.floor((x - group.startingX) / (cellSize + cellGap));
170
+ let rowIndex = Math.floor(y / (cellSize + cellGap));
171
+ let firstRowCells = rows - groupData.startingRow;
172
+
173
+ if (colIndex < 0 || colIndex >= groupData.cols || rowIndex < 0 || rowIndex >= rows) {
174
+ break;
175
+ }
176
+
177
+ let cellColStartingCellIndex = group.startingCellIndex + Math.max(0, colIndex - 1) * rows;
178
+ if (colIndex !== 0) {
179
+ cellColStartingCellIndex += firstRowCells;
180
+ cellIndex = cellColStartingCellIndex + rowIndex;
181
+ } else {
182
+ cellIndex = cellColStartingCellIndex + rowIndex - groupData.startingRow;
144
183
  }
184
+
185
+ if (cellIndex < 0 || cellIndex >= cellData.length) {
186
+ console.error("Calculated cell index is out of bounds:", cellIndex);
187
+ cellIndex = undefined;
188
+ break;
189
+ }
190
+ let cell = cellData[cellIndex];
191
+ if (!cell || x < cell.x || x > cell.x + cellSize || y < cell.y || y > cell.y + cellSize) {
192
+ cellIndex = undefined;
193
+ }
194
+ break;
195
+ }
196
+
197
+ if (cellIndex === undefined) {
198
+ setPopupData(undefined);
199
+ return;
200
+ }
201
+ const cellDatum = cellData[cellIndex];
202
+ if (cellDatum) {
203
+ setPopupData({
204
+ x: cellDatum.x,
205
+ y: cellDatum.y,
206
+ data: cellDatum,
207
+ });
208
+ return;
145
209
  }
146
210
 
147
211
  setPopupData(undefined);
@@ -157,7 +221,7 @@ function useHeatMap({
157
221
  }), [ref]);
158
222
 
159
223
  return {
160
- daysInRange,
224
+ cellData,
161
225
  computedMin,
162
226
  computedMax,
163
227
  totalWidth,
@@ -168,7 +232,17 @@ function useHeatMap({
168
232
  touchHandler,
169
233
  getColor,
170
234
  cellSize,
171
- onTouchOutside
235
+ onTouchOutside,
236
+ verticalLabelWidth,
237
+ setVerticalLabelWidth,
238
+ horizontalLabelHeight,
239
+ setHorizontalLabelHeight,
240
+ numberOfRows,
241
+ paddingLeft,
242
+ paddingRight,
243
+ paddingTop,
244
+ paddingBottom,
245
+ xLabelsRect
172
246
  };
173
247
  }
174
248
 
@@ -1,6 +1,6 @@
1
1
  import { Canvas, Path } from '@shopify/react-native-skia';
2
2
  import { type CommonStyle } from '../common';
3
- import { Text, View } from 'react-native';
3
+ import { View } from 'react-native';
4
4
  import { usePieChart } from './usePieChart';
5
5
  import Popup, { type PopupStyle } from '../Popup';
6
6
  import { useState } from 'react';
@@ -15,27 +15,31 @@ export type PieSlice = {
15
15
  value: number;
16
16
  color?: string;
17
17
  label?: string;
18
+ radius?: number;
18
19
  };
19
20
 
20
21
  export type PieChartProps = {
21
22
  slices: PieSlice[];
22
23
  style: PieChartStyles;
23
24
  centerView?: React.ReactNode;
25
+ centerSkiaView?: (centerX: number, centerY: number, radius: number) => React.ReactNode;
24
26
  onSliceTouch?: (slice: PieSlice | undefined) => void;
25
27
  popupStyle?: PopupStyle<PieSlice>;
26
28
  };
27
29
 
28
30
  export interface PieChartStyles extends CommonStyle {
31
+ startAngle?: number;
29
32
  radius?: number;
30
33
  innerRadius?: number;
31
34
  innerColor?: string;
35
+ interSliceGap?: number;
32
36
  }
33
37
 
34
- function PieChart(props: PieChartProps) {
35
- const { radius, innerRadius, paths, popupData, touchHandler } =
38
+ function PieChart({ popupStyle, centerSkiaView, centerView, ...props }: PieChartProps) {
39
+ const { radius, width, height, cx, cy, innerRadius, paths, popupData, touchHandler } =
36
40
  usePieChart(props);
37
- const { style, centerView, popupStyle } = props;
38
41
 
42
+ const { style } = props;
39
43
  const paddingTop = style.paddingTop ?? style.padding ?? 0;
40
44
  const paddingBottom = style.paddingBottom ?? style.padding ?? 0;
41
45
  const paddingLeft = style.paddingLeft ?? style.padding ?? 0;
@@ -66,8 +70,8 @@ function PieChart(props: PieChartProps) {
66
70
  <View
67
71
  style={{
68
72
  position: 'absolute',
69
- top: paddingTop + radius - innerRadius,
70
- left: paddingLeft + radius - innerRadius,
73
+ top: paddingTop + cx - innerRadius,
74
+ left: paddingLeft + cy - innerRadius,
71
75
  width: innerRadius * 2,
72
76
  height: innerRadius * 2,
73
77
  borderRadius: innerRadius,
@@ -81,8 +85,8 @@ function PieChart(props: PieChartProps) {
81
85
  )}
82
86
  <Canvas
83
87
  style={{
84
- width: radius * 2,
85
- height: radius * 2,
88
+ width,
89
+ height,
86
90
  backgroundColor: style.backgroundColor ?? 'transparent',
87
91
  }}
88
92
  onTouchStart={(event) =>
@@ -90,8 +94,9 @@ function PieChart(props: PieChartProps) {
90
94
  }
91
95
  >
92
96
  {paths.map(({ path, color }, index) => (
93
- <Path key={index} path={path} color={color} stroke={{ width: 5 }} />
97
+ <Path key={index} path={path} color={color} />
94
98
  ))}
99
+ {centerSkiaView && centerSkiaView(cx, cy, innerRadius)}
95
100
  </Canvas>
96
101
  {popupData && (
97
102
  <Popup
@@ -100,8 +105,8 @@ function PieChart(props: PieChartProps) {
100
105
  y: popupData.centerY,
101
106
  data: popupData.data,
102
107
  }}
103
- totalWidth={radius * 2}
104
- totalHeight={radius * 2}
108
+ totalWidth={width}
109
+ totalHeight={height}
105
110
  touchHandler={(x, y) => touchHandler(x - paddingLeft, y - paddingTop)}
106
111
  viewOffset={viewOffset}
107
112
  popupStyle={popupStyle}