@bashem/rn-charts 0.0.2

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 (97) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +35 -0
  3. package/lib/module/index.js +14 -0
  4. package/lib/module/index.js.map +1 -0
  5. package/lib/module/package.json +1 -0
  6. package/lib/module/skia/AreaChart/AreaChart.js +122 -0
  7. package/lib/module/skia/AreaChart/AreaChart.js.map +1 -0
  8. package/lib/module/skia/AreaChart/useAreaChart.js +141 -0
  9. package/lib/module/skia/AreaChart/useAreaChart.js.map +1 -0
  10. package/lib/module/skia/BarChart/BarChart.js +127 -0
  11. package/lib/module/skia/BarChart/BarChart.js.map +1 -0
  12. package/lib/module/skia/BarChart/useBarChart.js +172 -0
  13. package/lib/module/skia/BarChart/useBarChart.js.map +1 -0
  14. package/lib/module/skia/Common/VerticalLabel.js +73 -0
  15. package/lib/module/skia/Common/VerticalLabel.js.map +1 -0
  16. package/lib/module/skia/HeatMap/HeatMap.js +76 -0
  17. package/lib/module/skia/HeatMap/HeatMap.js.map +1 -0
  18. package/lib/module/skia/HeatMap/useHeatMap.js +139 -0
  19. package/lib/module/skia/HeatMap/useHeatMap.js.map +1 -0
  20. package/lib/module/skia/PieChart/PieChart.js +96 -0
  21. package/lib/module/skia/PieChart/PieChart.js.map +1 -0
  22. package/lib/module/skia/PieChart/usePieChart.js +103 -0
  23. package/lib/module/skia/PieChart/usePieChart.js.map +1 -0
  24. package/lib/module/skia/Popup.js +58 -0
  25. package/lib/module/skia/Popup.js.map +1 -0
  26. package/lib/module/skia/Progress/LinearProgress.js +69 -0
  27. package/lib/module/skia/Progress/LinearProgress.js.map +1 -0
  28. package/lib/module/skia/Progress/SemiCircleProgress.js +70 -0
  29. package/lib/module/skia/Progress/SemiCircleProgress.js.map +1 -0
  30. package/lib/module/skia/RadarChart/RadarChart.js +98 -0
  31. package/lib/module/skia/RadarChart/RadarChart.js.map +1 -0
  32. package/lib/module/skia/RadarChart/useRadarChart.js +164 -0
  33. package/lib/module/skia/RadarChart/useRadarChart.js.map +1 -0
  34. package/lib/module/skia/common.js +65 -0
  35. package/lib/module/skia/common.js.map +1 -0
  36. package/lib/module/util/colors.js +182 -0
  37. package/lib/module/util/colors.js.map +1 -0
  38. package/lib/module/util/util.js +71 -0
  39. package/lib/module/util/util.js.map +1 -0
  40. package/lib/typescript/index.d.ts +2 -0
  41. package/lib/typescript/index.d.ts.map +1 -0
  42. package/lib/typescript/package.json +1 -0
  43. package/lib/typescript/src/index.d.ts +10 -0
  44. package/lib/typescript/src/index.d.ts.map +1 -0
  45. package/lib/typescript/src/skia/AreaChart/AreaChart.d.ts +25 -0
  46. package/lib/typescript/src/skia/AreaChart/AreaChart.d.ts.map +1 -0
  47. package/lib/typescript/src/skia/AreaChart/useAreaChart.d.ts +47 -0
  48. package/lib/typescript/src/skia/AreaChart/useAreaChart.d.ts.map +1 -0
  49. package/lib/typescript/src/skia/BarChart/BarChart.d.ts +30 -0
  50. package/lib/typescript/src/skia/BarChart/BarChart.d.ts.map +1 -0
  51. package/lib/typescript/src/skia/BarChart/useBarChart.d.ts +41 -0
  52. package/lib/typescript/src/skia/BarChart/useBarChart.d.ts.map +1 -0
  53. package/lib/typescript/src/skia/Common/VerticalLabel.d.ts +17 -0
  54. package/lib/typescript/src/skia/Common/VerticalLabel.d.ts.map +1 -0
  55. package/lib/typescript/src/skia/HeatMap/HeatMap.d.ts +33 -0
  56. package/lib/typescript/src/skia/HeatMap/HeatMap.d.ts.map +1 -0
  57. package/lib/typescript/src/skia/HeatMap/useHeatMap.d.ts +25 -0
  58. package/lib/typescript/src/skia/HeatMap/useHeatMap.d.ts.map +1 -0
  59. package/lib/typescript/src/skia/PieChart/PieChart.d.ts +27 -0
  60. package/lib/typescript/src/skia/PieChart/PieChart.d.ts.map +1 -0
  61. package/lib/typescript/src/skia/PieChart/usePieChart.d.ts +13 -0
  62. package/lib/typescript/src/skia/PieChart/usePieChart.d.ts.map +1 -0
  63. package/lib/typescript/src/skia/Popup.d.ts +26 -0
  64. package/lib/typescript/src/skia/Popup.d.ts.map +1 -0
  65. package/lib/typescript/src/skia/Progress/LinearProgress.d.ts +18 -0
  66. package/lib/typescript/src/skia/Progress/LinearProgress.d.ts.map +1 -0
  67. package/lib/typescript/src/skia/Progress/SemiCircleProgress.d.ts +18 -0
  68. package/lib/typescript/src/skia/Progress/SemiCircleProgress.d.ts.map +1 -0
  69. package/lib/typescript/src/skia/RadarChart/RadarChart.d.ts +27 -0
  70. package/lib/typescript/src/skia/RadarChart/RadarChart.d.ts.map +1 -0
  71. package/lib/typescript/src/skia/RadarChart/useRadarChart.d.ts +41 -0
  72. package/lib/typescript/src/skia/RadarChart/useRadarChart.d.ts.map +1 -0
  73. package/lib/typescript/src/skia/common.d.ts +31 -0
  74. package/lib/typescript/src/skia/common.d.ts.map +1 -0
  75. package/lib/typescript/src/util/colors.d.ts +4 -0
  76. package/lib/typescript/src/util/colors.d.ts.map +1 -0
  77. package/lib/typescript/src/util/util.d.ts +33 -0
  78. package/lib/typescript/src/util/util.d.ts.map +1 -0
  79. package/package.json +172 -0
  80. package/src/index.tsx +12 -0
  81. package/src/skia/AreaChart/AreaChart.tsx +140 -0
  82. package/src/skia/AreaChart/useAreaChart.ts +180 -0
  83. package/src/skia/BarChart/BarChart.tsx +190 -0
  84. package/src/skia/BarChart/useBarChart.ts +210 -0
  85. package/src/skia/Common/VerticalLabel.tsx +91 -0
  86. package/src/skia/HeatMap/HeatMap.tsx +106 -0
  87. package/src/skia/HeatMap/useHeatMap.ts +175 -0
  88. package/src/skia/PieChart/PieChart.tsx +114 -0
  89. package/src/skia/PieChart/usePieChart.ts +156 -0
  90. package/src/skia/Popup.tsx +125 -0
  91. package/src/skia/Progress/LinearProgress.tsx +84 -0
  92. package/src/skia/Progress/SemiCircleProgress.tsx +82 -0
  93. package/src/skia/RadarChart/RadarChart.tsx +159 -0
  94. package/src/skia/RadarChart/useRadarChart.ts +208 -0
  95. package/src/skia/common.ts +82 -0
  96. package/src/util/colors.ts +186 -0
  97. package/src/util/util.ts +89 -0
@@ -0,0 +1,180 @@
1
+ import { Skia, type SkPath } from "@shopify/react-native-skia";
2
+ import { useMemo, useState } from "react";
3
+ import { getCommonStyleFont, getPaddings } from "../common";
4
+ import type { AreaChartProps, AreaChartStyle } from "./AreaChart";
5
+ import { isDefined } from "../../util/util";
6
+
7
+ export interface AreaData {
8
+ values: number[];
9
+ label?: string;
10
+ color?: string;
11
+ }
12
+
13
+ interface Point {
14
+ x: number;
15
+ y: number;
16
+ }
17
+
18
+ export interface PathData {
19
+ path: SkPath;
20
+ points: Point[];
21
+ values: number[];
22
+ color?: string;
23
+ label?: string;
24
+ }
25
+
26
+ export interface XLable {
27
+ label: string;
28
+ xPosition: number;
29
+ }
30
+
31
+ interface TouchLine {
32
+ col: number;
33
+ x: number;
34
+ y: number[];
35
+ values: number[];
36
+ }
37
+
38
+ function useAreaChart({
39
+ data,
40
+ xLabels,
41
+ maxValue,
42
+ minValue,
43
+ style,
44
+ }: AreaChartProps
45
+ ) {
46
+
47
+ const height = style?.height ?? 200;
48
+ const width = style?.width ?? 200;
49
+ const {
50
+ paddingLeft,
51
+ paddingTop,
52
+ paddingHorizontal,
53
+ paddingVertical,
54
+ } = getPaddings(style);
55
+
56
+ const canvasHeight = height - paddingVertical;
57
+ const labelWidth = 30;
58
+ const chartWidth = width - labelWidth - paddingHorizontal;
59
+ const xLabelHeight = xLabels && xLabels.length > 0 ? (style?.fontSize ?? 12) + 5 : 0;
60
+ const areaCanvasHeight = canvasHeight - xLabelHeight;
61
+
62
+ const { font } = getCommonStyleFont(style);
63
+
64
+ const { maxValueCalculated, minValueCalculated } = useMemo(() => {
65
+ if (isDefined(maxValue) && isDefined(minValue)) {
66
+ return { maxValueCalculated: maxValue, minValueCalculated: minValue };
67
+ }
68
+ let maxValueCalculated = Number.MIN_VALUE;
69
+ let minValueCalculated = Number.MAX_VALUE;
70
+
71
+ data.forEach((datum) => {
72
+ datum.values.forEach((value) => {
73
+ if (value > maxValueCalculated && !isDefined(maxValue)) {
74
+ maxValueCalculated = value;
75
+ }
76
+ if (value < minValueCalculated && !isDefined(minValue)) {
77
+ minValueCalculated = value;
78
+ }
79
+ });
80
+ });
81
+
82
+ return { maxValueCalculated: maxValueCalculated + 10, minValueCalculated };
83
+ }, [data]);
84
+
85
+ const paths = useMemo(() => {
86
+ const pathData: PathData[] = [];
87
+ for (let datum of data) {
88
+ const areaData = datum.values;
89
+ const stepX = chartWidth / areaData.length;
90
+ const p = Skia.Path.Make();
91
+
92
+ p.moveTo(0, areaCanvasHeight);
93
+ const points: Point[] = [];
94
+ const values: number[] = [];
95
+
96
+ areaData.forEach((y, i) => {
97
+ const xPos = i * stepX;
98
+ const yPos = Math.max(0, areaCanvasHeight - ((y - minValueCalculated) / (maxValueCalculated - minValueCalculated)) * areaCanvasHeight);
99
+
100
+ points.push({ x: xPos, y: yPos });
101
+ values.push(y);
102
+ p.lineTo(xPos, yPos);
103
+ });
104
+
105
+ p.lineTo(chartWidth, areaCanvasHeight);
106
+ p.close();
107
+
108
+ pathData.push({
109
+ path: p,
110
+ points,
111
+ values,
112
+ color: datum.color,
113
+ label: datum.label,
114
+ });
115
+ }
116
+
117
+ return pathData;
118
+ }, [data, chartWidth, maxValueCalculated, minValueCalculated]);
119
+
120
+ const xLabelsData: XLable[] = useMemo(() => {
121
+ if (!xLabels || xLabels.length === 0) {
122
+ return [];
123
+ }
124
+
125
+ const stepX = chartWidth / xLabels.length;
126
+ const labels = xLabels.map((label, i) => {
127
+ return {
128
+ label,
129
+ xPosition: i * stepX + font.getSize(),
130
+ };
131
+ });
132
+ return labels;
133
+ }, [xLabels, chartWidth]);
134
+
135
+ const [touchLine, setTouchLine] = useState<TouchLine | undefined>(undefined);
136
+
137
+ const touchHandler = (touchedX: number, touchedY: number) => {
138
+ if (data.length === 0 || (data[0]?.values.length ?? 0) === 0 || touchedX < 0 || touchedY < 0 || touchedX >= chartWidth || touchedY >= areaCanvasHeight) {
139
+ setTouchLine(undefined);
140
+ return;
141
+ }
142
+
143
+ const stepX = chartWidth / data[0]!.values.length;
144
+ const xIndex = Math.round(touchedX / stepX);
145
+ if (xIndex < 0 || xIndex >= data[0]!.values.length) {
146
+ setTouchLine(undefined);
147
+ return;
148
+ }
149
+
150
+ const yValues: number[] = paths.map(path => path.points[xIndex]!.y);
151
+ const values: number[] = paths.map(path => path.values[xIndex]!);
152
+
153
+ setTouchLine({
154
+ col: xIndex,
155
+ x: xIndex * stepX,
156
+ y: yValues,
157
+ values
158
+ });
159
+ };
160
+
161
+ return {
162
+ paths,
163
+ chartWidth,
164
+ canvasHeight,
165
+ paddingLeft,
166
+ paddingTop,
167
+ paddingHorizontal,
168
+ areaCanvasHeight,
169
+ labelWidth,
170
+ maxValue: maxValueCalculated,
171
+ minValue: minValueCalculated,
172
+ xLabelsData,
173
+ xLabelHeight,
174
+ font,
175
+ touchHandler,
176
+ touchLine
177
+ };
178
+ }
179
+
180
+ export default useAreaChart;
@@ -0,0 +1,190 @@
1
+ import { Fragment, useState } from 'react';
2
+ import { View } from 'react-native';
3
+ import {
4
+ GestureDetector,
5
+ Gesture,
6
+ GestureHandlerRootView,
7
+ } from 'react-native-gesture-handler';
8
+
9
+ import { Canvas, Rect, Text, vec, Line } from '@shopify/react-native-skia';
10
+ import { type CommonStyle } from '../common';
11
+ import useBarChart from './useBarChart';
12
+ import VerticalLabel from '../Common/VerticalLabel';
13
+ import Popup, { type PopupStyle } from '../Popup';
14
+
15
+ export interface StackValue {
16
+ value: number;
17
+ label: string;
18
+ id?: string;
19
+ }
20
+
21
+ export interface BarData {
22
+ values: StackValue[];
23
+ label?: string;
24
+ }
25
+
26
+ export interface BarChartStyle extends CommonStyle {
27
+ width?: number;
28
+ height?: number;
29
+ barWidth?: number;
30
+ barSpacing?: number;
31
+ firstBarLeadingSpacing?: number;
32
+ lastBarTrailingSpacing?: number;
33
+ }
34
+
35
+ export interface BarChartProps {
36
+ data: BarData[];
37
+ colors?: Record<string, string>;
38
+ maxValue?: number;
39
+ minValue?: number;
40
+ popupStyle?: PopupStyle<StackValue>;
41
+ style?: BarChartStyle;
42
+ }
43
+
44
+ function BarChart(props: BarChartProps) {
45
+ const {
46
+ maxValueCalculated,
47
+ minValueCalculated,
48
+ canvasHeight,
49
+ canvasWidth,
50
+ paddingRight,
51
+ paddingLeft,
52
+ paddingBottom,
53
+ paddingTop,
54
+ rectangles,
55
+ verticalLabelWidth,
56
+ chartHeight,
57
+ strokeWidth,
58
+ tooltip,
59
+ bottomLabelHeight,
60
+ font,
61
+ onScroll,
62
+ touchHandler,
63
+ totalHeight,
64
+ totalWidth,
65
+ } = useBarChart(props);
66
+
67
+ const dragGesture = Gesture.Pan()
68
+ .runOnJS(true)
69
+ .onChange((event) => {
70
+ onScroll(-event.changeX);
71
+ });
72
+
73
+ const [viewOffset, setViewOffset] = useState({ x: 0, y: 0 });
74
+
75
+ return (
76
+ <GestureHandlerRootView>
77
+ <View
78
+ style={{
79
+ width: totalWidth,
80
+ flexDirection: 'row',
81
+ backgroundColor: props.style?.backgroundColor,
82
+ paddingLeft: paddingLeft,
83
+ paddingRight: paddingRight,
84
+ paddingTop: paddingTop,
85
+ paddingBottom: paddingBottom,
86
+ }}
87
+ ref={(view) => {
88
+ view?.measureInWindow((fx, fy) => {
89
+ setViewOffset((prev) => {
90
+ if (prev.x === fx && prev.y === fy) {
91
+ return prev;
92
+ }
93
+ return { x: fx, y: fy };
94
+ });
95
+ });
96
+ }}
97
+ >
98
+ <VerticalLabel
99
+ maxValue={maxValueCalculated}
100
+ minValue={minValueCalculated}
101
+ labelCount={6}
102
+ styles={{
103
+ width: verticalLabelWidth,
104
+ height: chartHeight,
105
+ strokeWidth,
106
+ }}
107
+ />
108
+ <GestureDetector gesture={dragGesture}>
109
+ <Canvas
110
+ style={{
111
+ width: canvasWidth,
112
+ height: canvasHeight,
113
+ paddingRight: 50,
114
+ backgroundColor: 'red',
115
+ }}
116
+ onTouchStart={(event) =>
117
+ touchHandler(
118
+ event.nativeEvent.locationX,
119
+ event.nativeEvent.locationY
120
+ )
121
+ }
122
+ >
123
+ {/* X axis */}
124
+ <Line
125
+ p1={vec(0, chartHeight)}
126
+ p2={vec(canvasWidth, chartHeight)}
127
+ color="white"
128
+ strokeWidth={strokeWidth}
129
+ />
130
+
131
+ {/* Bars */}
132
+
133
+ {rectangles.map((bar, xIndex) => {
134
+ if (bar.bars.length === 0) return null;
135
+ return (
136
+ <Fragment key={xIndex}>
137
+ <Text
138
+ x={bar.bars[0]!.x}
139
+ y={
140
+ chartHeight +
141
+ font.getSize() +
142
+ (bottomLabelHeight - font.getSize()) / 2
143
+ }
144
+ text={bar.label ?? ''}
145
+ color="white"
146
+ font={font}
147
+ />
148
+ {bar.bars.map((item, yIndex) => {
149
+ let currentData = props.data[xIndex]!.values[yIndex]!;
150
+ let color =
151
+ props?.colors?.[currentData.id ?? currentData.label] ||
152
+ '#4A90E2';
153
+ return (
154
+ <Rect
155
+ key={xIndex + '-' + yIndex}
156
+ x={item.x}
157
+ y={item.y}
158
+ width={item.width}
159
+ height={item.height}
160
+ color={color}
161
+ />
162
+ );
163
+ })}
164
+ </Fragment>
165
+ );
166
+ })}
167
+ </Canvas>
168
+ </GestureDetector>
169
+ {tooltip && (
170
+ <Popup
171
+ popupData={{
172
+ x: tooltip.centerX,
173
+ y: tooltip.centerY,
174
+ data: tooltip.data,
175
+ }}
176
+ popupStyle={props.popupStyle}
177
+ totalWidth={totalWidth}
178
+ totalHeight={totalHeight}
179
+ touchHandler={(x, y) =>
180
+ touchHandler(x - verticalLabelWidth - paddingLeft, y - paddingTop)
181
+ }
182
+ viewOffset={viewOffset}
183
+ />
184
+ )}
185
+ </View>
186
+ </GestureHandlerRootView>
187
+ );
188
+ }
189
+
190
+ export default BarChart;
@@ -0,0 +1,210 @@
1
+ import { useMemo, useState } from "react";
2
+ import { rect } from "@shopify/react-native-skia";
3
+ import { arrayFrom, isDefined } from "../../util/util";
4
+ import type { BarData, BarChartStyle, StackValue, BarChartProps } from "./BarChart";
5
+ import { useWindowDimensions } from "react-native";
6
+ import { getCommonStyleFont, getPaddings } from "../common";
7
+
8
+ export default function useBarChart(
9
+ {
10
+ data,
11
+ style,
12
+ maxValue,
13
+ minValue
14
+ }: BarChartProps
15
+ ) {
16
+ const { maxValueCalculated, minValueCalculated } = useMemo(() => {
17
+ if (isDefined(maxValue) && isDefined(minValue)) {
18
+ return {
19
+ maxValueCalculated: maxValue,
20
+ minValueCalculated: minValue
21
+ };
22
+ }
23
+
24
+ if (data.length === 0) {
25
+ return { maxValueCalculated: maxValue ?? 100, minValueCalculated: minValue ?? 0 };
26
+ }
27
+ let maxValueCalculated = Number.MIN_VALUE;
28
+ let minValueCalculated = Number.MAX_VALUE;
29
+
30
+ data.forEach((item) => {
31
+ const currentValue = item.values.reduce(
32
+ (acc, value) => {
33
+ minValueCalculated = Math.min(minValueCalculated, value.value);
34
+ return acc + value.value;
35
+ },
36
+ 0
37
+ );
38
+ maxValueCalculated = Math.max(maxValueCalculated, currentValue);
39
+ });
40
+ if (isDefined(maxValue))
41
+ maxValueCalculated = maxValue;
42
+
43
+ if (isDefined(minValue))
44
+ minValueCalculated = minValue;
45
+
46
+ return { maxValueCalculated, minValueCalculated };
47
+ }, [data, maxValue]);
48
+
49
+ const steps = useMemo(() => arrayFrom(1, 0.2), []);
50
+ const [tooltip, setTooltip] = useState<{ centerX: number, centerY: number, data: StackValue; } | undefined>(undefined);
51
+ const [startX, setStartX] = useState<number>(0);
52
+
53
+ const {
54
+ paddingLeft,
55
+ paddingRight,
56
+ paddingTop,
57
+ paddingBottom
58
+ } = getPaddings(style);
59
+
60
+ const chartBarWidth = style?.barWidth ?? 100;
61
+ const chartBarSpacing = style?.barSpacing ?? 0;
62
+ const verticalLabelWidth = 35;
63
+ const chartHeight = style?.height ?? 200;
64
+ const strokeWidth = 2;
65
+ const bottomLabelHeight = 20;
66
+ const canvasHeight = chartHeight + bottomLabelHeight;
67
+ const { width: windowWidth } = useWindowDimensions();
68
+ const totalWidth = style?.width ?? windowWidth;
69
+ const totalHeight = chartHeight;
70
+
71
+ const initialSpacing = style?.firstBarLeadingSpacing ?? 0;
72
+ const endSpacing = style?.lastBarTrailingSpacing ?? chartBarSpacing;
73
+
74
+ const scrollAreaWidth = initialSpacing + data.length * chartBarWidth + (Math.max(0, data.length - 1) * chartBarSpacing) + endSpacing;
75
+ const canvasWidth = Math.min(scrollAreaWidth, totalWidth - verticalLabelWidth - paddingRight - paddingLeft);
76
+ const { font } = getCommonStyleFont(style);
77
+
78
+ const rectangles = useMemo(() => {
79
+ let leftBoundary = Math.max(0, startX);
80
+ let rightBoundary = startX + totalWidth;
81
+
82
+ let startArrayIndex = Math.floor(Math.max(leftBoundary - initialSpacing, 0) / (chartBarWidth + chartBarSpacing));
83
+ let endArrayIndex = Math.min(Math.ceil(rightBoundary / (chartBarWidth + chartBarSpacing)), data.length);
84
+
85
+ return data.slice(startArrayIndex, endArrayIndex)
86
+ .map((bar, xIndex) => {
87
+ let previousHeight = 0;
88
+ const x = initialSpacing + (xIndex + startArrayIndex) * (chartBarWidth + chartBarSpacing) - leftBoundary;
89
+ return {
90
+ bars: bar.values.map((item, yIndex) => {
91
+ const barHeight =
92
+ ((item.value - minValueCalculated) /
93
+ (maxValueCalculated - minValueCalculated)) *
94
+ chartHeight;
95
+
96
+ const y =
97
+ chartHeight - barHeight - previousHeight - strokeWidth;
98
+
99
+ previousHeight += barHeight;
100
+ return rect(x, y, chartBarWidth, barHeight);
101
+ }),
102
+ label: bar.label,
103
+ dataIndex: xIndex + startArrayIndex,
104
+ x: x
105
+ };
106
+ });
107
+ }, [
108
+ data,
109
+ chartBarWidth,
110
+ chartBarSpacing,
111
+ maxValueCalculated,
112
+ minValueCalculated,
113
+ strokeWidth,
114
+ startX
115
+ ]);
116
+
117
+ const touchHandler = (touchedX: number, touchedY: number) => {
118
+ if (rectangles.length === 0 || touchedX < 0 || touchedY < 0 || touchedX >= canvasWidth || touchedY >= chartHeight) {
119
+ setTooltip(undefined);
120
+ return;
121
+ }
122
+
123
+ let xIndex = -1;
124
+ let startingXIndex = 0;
125
+
126
+ if (touchedX >= rectangles[0]!.x && touchedX <= rectangles[0]!.x + rectangles[0]!.bars[0]!.width) {
127
+ xIndex = 0;
128
+ startingXIndex = Math.max(0, rectangles[0]!.x);
129
+ } else if (touchedX >= rectangles[0]!.x) {
130
+ xIndex = Math.floor((touchedX - (rectangles[0]!.x + chartBarWidth) - chartBarSpacing) / (chartBarWidth + chartBarSpacing)) + 1;
131
+ startingXIndex = rectangles[xIndex]!.x;
132
+ }
133
+
134
+ if (xIndex === -1 || (touchedX < rectangles[xIndex]!.x || touchedX > rectangles[xIndex]!.x + chartBarWidth)) {
135
+ console.log('Touch is outside the bar width, ignoring.');
136
+ setTooltip(undefined);
137
+ return;
138
+ }
139
+ xIndex = rectangles[xIndex]!.dataIndex;
140
+
141
+ let yIndex = 0;
142
+ let yPassed = 0;
143
+ let categoryData = data[xIndex]?.values || [];
144
+ let lastBarHeight = 0;
145
+
146
+ while (
147
+ yIndex < categoryData.length &&
148
+ yPassed < chartHeight - touchedY
149
+ ) {
150
+ const barHeight =
151
+ ((categoryData[yIndex]!.value - minValueCalculated) /
152
+ (maxValueCalculated - minValueCalculated)) *
153
+ chartHeight;
154
+ yPassed += barHeight;
155
+ lastBarHeight = barHeight;
156
+ yIndex++;
157
+ }
158
+
159
+ if (yIndex === 0 || (yIndex === categoryData.length && touchedY < chartHeight - yPassed)) {
160
+ console.log('Touch is outside the bar height, ignoring.');
161
+ setTooltip(undefined);
162
+ return;
163
+ }
164
+
165
+ setTooltip({
166
+ centerX: startingXIndex + chartBarWidth / 2,
167
+ centerY:
168
+ chartHeight - yPassed - strokeWidth + lastBarHeight / 2,
169
+ data: categoryData[yIndex - 1]!,
170
+ });
171
+ };
172
+
173
+ function onScroll(translateX: number) {
174
+ setTooltip(undefined);
175
+ setStartX((prev) => {
176
+ let newX = prev + translateX;
177
+ if (newX < 0) return 0;
178
+ if (newX + canvasWidth > scrollAreaWidth)
179
+ return Math.max(0, scrollAreaWidth - canvasWidth);
180
+ return newX;
181
+ });
182
+ }
183
+
184
+ return {
185
+ maxValueCalculated,
186
+ minValueCalculated,
187
+ canvasHeight,
188
+ canvasWidth,
189
+ steps,
190
+ scrollAreaWidth,
191
+ chartHeight,
192
+ paddingTop,
193
+ paddingBottom,
194
+ paddingLeft,
195
+ paddingRight,
196
+ verticalLabelWidth,
197
+ chartBarWidth,
198
+ chartBarSpacing,
199
+ strokeWidth,
200
+ rectangles,
201
+ tooltip,
202
+ bottomLabelHeight,
203
+ font,
204
+ setTooltip,
205
+ touchHandler,
206
+ onScroll,
207
+ totalHeight,
208
+ totalWidth
209
+ };
210
+ }
@@ -0,0 +1,91 @@
1
+ import { Canvas, Line, Text, Skia } from '@shopify/react-native-skia';
2
+ import { getFont, type CommonStyle } from '../common';
3
+
4
+ interface VerticalLabelStyles extends CommonStyle {
5
+ width: number;
6
+ height: number;
7
+ strokeWidth?: number;
8
+ strokeColor?: string;
9
+ textColor?: string;
10
+ }
11
+
12
+ interface VerticalLabelProps {
13
+ minValue: number;
14
+ maxValue: number;
15
+ labelCount: number;
16
+ styles: VerticalLabelStyles;
17
+ }
18
+
19
+ function VerticalLabel({
20
+ minValue,
21
+ maxValue,
22
+ labelCount,
23
+ styles,
24
+ }: VerticalLabelProps) {
25
+ const {
26
+ width,
27
+ height,
28
+ strokeWidth = 2,
29
+ strokeColor = 'white',
30
+ textColor = 'white',
31
+ paddingTop = 0,
32
+ paddingRight = 0,
33
+ paddingBottom = 0,
34
+ paddingLeft = 0,
35
+ fontSize = 12,
36
+ backgroundColor,
37
+ } = styles;
38
+
39
+ // Generate evenly spaced values
40
+ const stepValue =
41
+ labelCount > 1 ? (maxValue - minValue) / (labelCount - 1) : 0;
42
+ const labels = Array.from(
43
+ { length: labelCount },
44
+ (_, i) => minValue + i * stepValue
45
+ );
46
+
47
+ const font = getFont(fontSize);
48
+
49
+ const usableHeight = height - paddingTop - paddingBottom - fontSize;
50
+ const stepY = labelCount > 1 ? usableHeight / (labelCount - 1) : 0;
51
+
52
+ // Precompute text paint
53
+ const paint = Skia.Paint();
54
+ paint.setColor(Skia.Color(textColor));
55
+
56
+ return (
57
+ <Canvas style={{ width, height, backgroundColor }}>
58
+ <Line
59
+ p1={{ x: width - paddingRight - strokeWidth / 2, y: paddingTop }}
60
+ p2={{
61
+ x: width - paddingRight - strokeWidth / 2,
62
+ y: height - paddingBottom,
63
+ }}
64
+ color={strokeColor}
65
+ strokeWidth={strokeWidth}
66
+ />
67
+
68
+ {labels.map((label, i) => {
69
+ const y = height - paddingBottom - stepY * i;
70
+ const text = label.toFixed(0);
71
+
72
+ // measure text width for right-align
73
+ const textWidth = font.measureText(text).width;
74
+ const x = width - paddingRight - strokeWidth - 4 - textWidth;
75
+
76
+ return (
77
+ <Text
78
+ key={i}
79
+ x={x}
80
+ y={y}
81
+ text={text}
82
+ font={font}
83
+ color={textColor}
84
+ />
85
+ );
86
+ })}
87
+ </Canvas>
88
+ );
89
+ }
90
+
91
+ export default VerticalLabel;