@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.
- package/README.md +1 -1
- package/lib/module/skia/AreaChart/AreaChart.js +60 -51
- package/lib/module/skia/AreaChart/AreaChart.js.map +1 -1
- package/lib/module/skia/AreaChart/useAreaChart.js +18 -14
- package/lib/module/skia/AreaChart/useAreaChart.js.map +1 -1
- package/lib/module/skia/BarChart/BarChart.js +156 -69
- package/lib/module/skia/BarChart/BarChart.js.map +1 -1
- package/lib/module/skia/BarChart/useBarChart.js +53 -41
- package/lib/module/skia/BarChart/useBarChart.js.map +1 -1
- package/lib/module/skia/Common/HorizontalLabelView.js +121 -0
- package/lib/module/skia/Common/HorizontalLabelView.js.map +1 -0
- package/lib/module/skia/Common/VerticalLabelView.js +100 -0
- package/lib/module/skia/Common/VerticalLabelView.js.map +1 -0
- package/lib/module/skia/Common/useComponentLayout.js +13 -0
- package/lib/module/skia/Common/useComponentLayout.js.map +1 -0
- package/lib/module/skia/HeatMap/DateHeatMap.js +472 -0
- package/lib/module/skia/HeatMap/DateHeatMap.js.map +1 -0
- package/lib/module/skia/HeatMap/HeatMap.js +121 -33
- package/lib/module/skia/HeatMap/HeatMap.js.map +1 -1
- package/lib/module/skia/HeatMap/useHeatMap.js +132 -48
- package/lib/module/skia/HeatMap/useHeatMap.js.map +1 -1
- package/lib/module/skia/PieChart/PieChart.js +21 -17
- package/lib/module/skia/PieChart/PieChart.js.map +1 -1
- package/lib/module/skia/PieChart/usePieChart.js +241 -30
- package/lib/module/skia/PieChart/usePieChart.js.map +1 -1
- package/lib/module/skia/Popup.js +7 -5
- package/lib/module/skia/Popup.js.map +1 -1
- package/lib/module/skia/common.js +4 -41
- package/lib/module/skia/common.js.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/skia/AreaChart/AreaChart.d.ts +15 -3
- package/lib/typescript/src/skia/AreaChart/AreaChart.d.ts.map +1 -1
- package/lib/typescript/src/skia/AreaChart/useAreaChart.d.ts +13 -14
- package/lib/typescript/src/skia/AreaChart/useAreaChart.d.ts.map +1 -1
- package/lib/typescript/src/skia/BarChart/BarChart.d.ts +16 -2
- package/lib/typescript/src/skia/BarChart/BarChart.d.ts.map +1 -1
- package/lib/typescript/src/skia/BarChart/useBarChart.d.ts +17 -5
- package/lib/typescript/src/skia/BarChart/useBarChart.d.ts.map +1 -1
- package/lib/typescript/src/skia/Common/HorizontalLabelView.d.ts +33 -0
- package/lib/typescript/src/skia/Common/HorizontalLabelView.d.ts.map +1 -0
- package/lib/typescript/src/skia/Common/VerticalLabelView.d.ts +24 -0
- package/lib/typescript/src/skia/Common/VerticalLabelView.d.ts.map +1 -0
- package/lib/typescript/src/skia/Common/useComponentLayout.d.ts +4 -0
- package/lib/typescript/src/skia/Common/useComponentLayout.d.ts.map +1 -0
- package/lib/typescript/src/skia/HeatMap/DateHeatMap.d.ts +1 -0
- package/lib/typescript/src/skia/HeatMap/DateHeatMap.d.ts.map +1 -0
- package/lib/typescript/src/skia/HeatMap/HeatMap.d.ts +28 -12
- package/lib/typescript/src/skia/HeatMap/HeatMap.d.ts.map +1 -1
- package/lib/typescript/src/skia/HeatMap/useHeatMap.d.ts +15 -4
- package/lib/typescript/src/skia/HeatMap/useHeatMap.d.ts.map +1 -1
- package/lib/typescript/src/skia/PieChart/PieChart.d.ts +5 -1
- package/lib/typescript/src/skia/PieChart/PieChart.d.ts.map +1 -1
- package/lib/typescript/src/skia/PieChart/usePieChart.d.ts +6 -1
- package/lib/typescript/src/skia/PieChart/usePieChart.d.ts.map +1 -1
- package/lib/typescript/src/skia/Popup.d.ts.map +1 -1
- package/lib/typescript/src/skia/common.d.ts +3 -10
- package/lib/typescript/src/skia/common.d.ts.map +1 -1
- package/package.json +6 -3
- package/src/index.tsx +6 -4
- package/src/skia/AreaChart/AreaChart.tsx +85 -62
- package/src/skia/AreaChart/useAreaChart.ts +25 -26
- package/src/skia/BarChart/BarChart.tsx +163 -95
- package/src/skia/BarChart/useBarChart.ts +55 -44
- package/src/skia/Common/HorizontalLabelView.tsx +153 -0
- package/src/skia/Common/VerticalLabelView.tsx +113 -0
- package/src/skia/Common/useComponentLayout.ts +14 -0
- package/src/skia/HeatMap/DateHeatMap.tsx +470 -0
- package/src/skia/HeatMap/HeatMap.tsx +168 -54
- package/src/skia/HeatMap/useHeatMap.ts +139 -65
- package/src/skia/PieChart/PieChart.tsx +16 -11
- package/src/skia/PieChart/usePieChart.ts +316 -66
- package/src/skia/Popup.tsx +38 -36
- package/src/skia/common.ts +8 -46
- package/lib/module/skia/Common/VerticalLabel.js +0 -73
- package/lib/module/skia/Common/VerticalLabel.js.map +0 -1
- package/lib/typescript/src/skia/Common/VerticalLabel.d.ts +0 -17
- package/lib/typescript/src/skia/Common/VerticalLabel.d.ts.map +0 -1
- package/src/skia/Common/VerticalLabel.tsx +0 -91
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import React, { type Ref } from 'react';
|
|
2
|
-
import { View
|
|
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
|
|
9
|
-
|
|
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
|
|
18
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
76
|
-
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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.
|
|
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={
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
22
|
-
const
|
|
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;
|
|
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
|
|
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
|
-
|
|
48
|
+
let nextRow = 0;
|
|
49
|
+
let nextCol = 0;
|
|
50
|
+
let xLabelsRect: SkHostRect[] = [];
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
let cellsInRange: CellDatum[] = [];
|
|
53
|
+
let totalInterGroupSpacing = 0;
|
|
54
|
+
let groupInfos: { startingX: number, endingX: number, startingCellIndex: number, lastCellIndex: number; }[] = [];
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
);
|
|
56
|
+
data.forEach((datum, index) => {
|
|
57
|
+
let startingRow = datum.startingRow;
|
|
58
|
+
let currentCol = nextCol;
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
}, [
|
|
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
|
|
105
|
-
const
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
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 {
|
|
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 +
|
|
70
|
-
left: paddingLeft +
|
|
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
|
|
85
|
-
height
|
|
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}
|
|
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={
|
|
104
|
-
totalHeight={
|
|
108
|
+
totalWidth={width}
|
|
109
|
+
totalHeight={height}
|
|
105
110
|
touchHandler={(x, y) => touchHandler(x - paddingLeft, y - paddingTop)}
|
|
106
111
|
viewOffset={viewOffset}
|
|
107
112
|
popupStyle={popupStyle}
|