rn-native-ios-charts 0.1.0

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 ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "rn-native-ios-charts",
3
+ "version": "0.1.0",
4
+ "description": "Native SwiftUI Charts for React Native / Expo. iOS-only, zero-compromise charts that bypass the limitations of cross-platform chart libraries.",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "keywords": [
8
+ "react-native",
9
+ "expo",
10
+ "swiftui",
11
+ "swift-charts",
12
+ "ios",
13
+ "chart",
14
+ "pie",
15
+ "donut",
16
+ "line",
17
+ "area"
18
+ ],
19
+ "author": "Abdalla Emadeldin",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/abdallaemadeldin/rn-native-ios-charts.git"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/abdallaemadeldin/rn-native-ios-charts/issues"
27
+ },
28
+ "homepage": "https://github.com/abdallaemadeldin/rn-native-ios-charts#readme",
29
+ "files": [
30
+ "src",
31
+ "ios",
32
+ "docs",
33
+ "expo-module.config.json",
34
+ "package.json",
35
+ "README.md",
36
+ "LICENSE",
37
+ "CHANGELOG.md"
38
+ ],
39
+ "peerDependencies": {
40
+ "expo": "*",
41
+ "react": "*",
42
+ "react-native": "*"
43
+ },
44
+ "directories": {
45
+ "doc": "docs"
46
+ },
47
+ "scripts": {
48
+ "test": "echo \"Error: no test specified\" && exit 1"
49
+ }
50
+ }
@@ -0,0 +1,68 @@
1
+ import * as React from "react";
2
+ import type { ColorValue, ViewStyle } from "react-native";
3
+ import { Chart } from "./Chart";
4
+ import type {
5
+ AxisConfig,
6
+ DataPoint,
7
+ Gradient,
8
+ Interpolation,
9
+ LegendConfig,
10
+ SelectedPoint,
11
+ TooltipConfig,
12
+ } from "./types";
13
+
14
+ export type AreaDatum = { x: string; y: number; category?: string };
15
+
16
+ export type AreaChartProps = {
17
+ data: AreaDatum[];
18
+ color?: ColorValue;
19
+ gradient?: Gradient;
20
+ interpolation?: Interpolation;
21
+ xAxis?: AxisConfig;
22
+ yAxis?: AxisConfig;
23
+ legend?: LegendConfig;
24
+ tooltip?: TooltipConfig;
25
+ onSelect?: (point: SelectedPoint) => void;
26
+ animate?: boolean;
27
+ style?: ViewStyle;
28
+ };
29
+
30
+ export function AreaChart({
31
+ data,
32
+ color,
33
+ gradient = { startOpacity: 0.35, endOpacity: 0.02 },
34
+ interpolation = "catmullRom",
35
+ xAxis,
36
+ yAxis,
37
+ legend,
38
+ tooltip,
39
+ onSelect,
40
+ animate,
41
+ style,
42
+ }: AreaChartProps) {
43
+ const points: DataPoint[] = data.map((p) => ({
44
+ x: p.x,
45
+ y: p.y,
46
+ category: p.category,
47
+ }));
48
+ return (
49
+ <Chart
50
+ style={style}
51
+ animate={animate}
52
+ xAxis={xAxis}
53
+ yAxis={yAxis}
54
+ legend={legend}
55
+ tooltip={tooltip}
56
+ onSelect={onSelect}
57
+ marks={[
58
+ {
59
+ type: "area",
60
+ data: points,
61
+ color,
62
+ gradient,
63
+ interpolation,
64
+ },
65
+ ]}
66
+ />
67
+ );
68
+ }
@@ -0,0 +1,74 @@
1
+ import * as React from "react";
2
+ import type { ColorValue, ViewStyle } from "react-native";
3
+ import { Chart } from "./Chart";
4
+ import type {
5
+ AxisConfig,
6
+ DataPoint,
7
+ LegendConfig,
8
+ SelectedPoint,
9
+ TooltipConfig,
10
+ } from "./types";
11
+
12
+ export type BarDatum = {
13
+ x: string;
14
+ y: number;
15
+ /** Optional series key for grouped / colored bars. */
16
+ category?: string;
17
+ color?: ColorValue;
18
+ };
19
+
20
+ export type BarChartProps = {
21
+ data: BarDatum[];
22
+ color?: ColorValue;
23
+ cornerRadius?: number;
24
+ /** Fixed width per bar in pt. 0 = auto. */
25
+ barWidth?: number;
26
+ xAxis?: AxisConfig;
27
+ yAxis?: AxisConfig;
28
+ legend?: LegendConfig;
29
+ tooltip?: TooltipConfig;
30
+ onSelect?: (point: SelectedPoint) => void;
31
+ animate?: boolean;
32
+ style?: ViewStyle;
33
+ };
34
+
35
+ export function BarChart({
36
+ data,
37
+ color,
38
+ cornerRadius = 4,
39
+ barWidth,
40
+ xAxis,
41
+ yAxis,
42
+ legend,
43
+ tooltip,
44
+ onSelect,
45
+ animate,
46
+ style,
47
+ }: BarChartProps) {
48
+ const points: DataPoint[] = data.map((d) => ({
49
+ x: d.x,
50
+ y: d.y,
51
+ category: d.category,
52
+ color: d.color,
53
+ }));
54
+ return (
55
+ <Chart
56
+ style={style}
57
+ animate={animate}
58
+ xAxis={xAxis}
59
+ yAxis={yAxis}
60
+ legend={legend}
61
+ tooltip={tooltip}
62
+ onSelect={onSelect}
63
+ marks={[
64
+ {
65
+ type: "bar",
66
+ data: points,
67
+ color,
68
+ cornerRadius,
69
+ barWidth,
70
+ },
71
+ ]}
72
+ />
73
+ );
74
+ }
package/src/Chart.tsx ADDED
@@ -0,0 +1,50 @@
1
+ import { requireNativeView } from "expo";
2
+ import * as React from "react";
3
+ import { Platform, View } from "react-native";
4
+ import type { ChartProps, SelectedPoint } from "./types";
5
+
6
+ /**
7
+ * Generic SwiftUI Charts view. Render any combination of bar / line /
8
+ * area / point / rectangle / rule / sector marks by passing them in
9
+ * the `marks` array. iOS-only — on other platforms this is a no-op
10
+ * placeholder `View`, so consuming code can mount it unconditionally
11
+ * and feature-detect via `Platform.OS`.
12
+ *
13
+ * Use the convenience wrappers (`PieChart`, `LineChart`, `BarChart`,
14
+ * etc.) for common single-mark cases — they all delegate here.
15
+ */
16
+
17
+ // Expo Modules events deliver the payload wrapped in `nativeEvent`.
18
+ // We re-shape it into the public `SelectedPoint` type at the boundary
19
+ // so consumers don't have to deal with the bridge layout.
20
+ type NativeChartProps = Omit<ChartProps, "onSelect"> & {
21
+ onSelect?: (event: {
22
+ nativeEvent: { x?: string; y?: number };
23
+ }) => void;
24
+ };
25
+
26
+ const NativeChart =
27
+ Platform.OS === "ios"
28
+ ? requireNativeView<NativeChartProps>("NativeIosCharts", "ChartView")
29
+ : null;
30
+
31
+ export function Chart(props: ChartProps) {
32
+ if (!NativeChart) {
33
+ return <View style={props.style} />;
34
+ }
35
+
36
+ const { onSelect, ...rest } = props;
37
+
38
+ // Wrap the user's callback to unwrap the native event shape. Empty
39
+ // payloads (`{}`) signal a cleared selection — emit `null` for them.
40
+ const handleSelect = onSelect
41
+ ? (event: { nativeEvent: { x?: string; y?: number } }) => {
42
+ const { x, y } = event.nativeEvent ?? {};
43
+ const point: SelectedPoint =
44
+ typeof x === "string" && typeof y === "number" ? { x, y } : null;
45
+ onSelect(point);
46
+ }
47
+ : undefined;
48
+
49
+ return <NativeChart {...rest} onSelect={handleSelect} />;
50
+ }
@@ -0,0 +1,104 @@
1
+ import * as React from "react";
2
+ import type { ColorValue, ViewStyle } from "react-native";
3
+ import { Chart } from "./Chart";
4
+ import type {
5
+ AxisConfig,
6
+ DataPoint,
7
+ Gradient,
8
+ Interpolation,
9
+ LegendConfig,
10
+ SelectedPoint,
11
+ Symbol,
12
+ TooltipConfig,
13
+ } from "./types";
14
+
15
+ export type LinePoint = { x: string; y: number; category?: string };
16
+
17
+ export type LineChartProps = {
18
+ data: LinePoint[];
19
+ color?: ColorValue;
20
+ lineWidth?: number;
21
+ interpolation?: Interpolation;
22
+ dashArray?: number[];
23
+ /**
24
+ * Set this to render a gradient-filled area beneath the line as
25
+ * well — the standard "shaded line chart" pattern. Same shape as
26
+ * the generic `Gradient`.
27
+ */
28
+ area?: Gradient | boolean;
29
+ showPoints?: boolean;
30
+ symbol?: Symbol;
31
+ symbolSize?: number;
32
+ xAxis?: AxisConfig;
33
+ yAxis?: AxisConfig;
34
+ legend?: LegendConfig;
35
+ tooltip?: TooltipConfig;
36
+ onSelect?: (point: SelectedPoint) => void;
37
+ animate?: boolean;
38
+ style?: ViewStyle;
39
+ };
40
+
41
+ export function LineChart({
42
+ data,
43
+ color,
44
+ lineWidth = 2.5,
45
+ interpolation = "catmullRom",
46
+ dashArray,
47
+ area,
48
+ showPoints,
49
+ symbol,
50
+ symbolSize,
51
+ xAxis,
52
+ yAxis,
53
+ legend,
54
+ tooltip,
55
+ onSelect,
56
+ animate,
57
+ style,
58
+ }: LineChartProps) {
59
+ const points: DataPoint[] = data.map((p) => ({
60
+ x: p.x,
61
+ y: p.y,
62
+ category: p.category,
63
+ }));
64
+
65
+ const marks = [];
66
+
67
+ // Render the area FIRST (so the line draws on top).
68
+ if (area) {
69
+ const gradient: Gradient =
70
+ typeof area === "object" ? area : { startOpacity: 0.35, endOpacity: 0.02 };
71
+ marks.push({
72
+ type: "area" as const,
73
+ data: points,
74
+ color,
75
+ gradient,
76
+ interpolation,
77
+ });
78
+ }
79
+
80
+ marks.push({
81
+ type: "line" as const,
82
+ data: points,
83
+ color,
84
+ lineWidth,
85
+ dashArray,
86
+ interpolation,
87
+ showPoints,
88
+ symbol,
89
+ symbolSize,
90
+ });
91
+
92
+ return (
93
+ <Chart
94
+ style={style}
95
+ animate={animate}
96
+ xAxis={xAxis}
97
+ yAxis={yAxis}
98
+ legend={legend}
99
+ tooltip={tooltip}
100
+ onSelect={onSelect}
101
+ marks={marks}
102
+ />
103
+ );
104
+ }
@@ -0,0 +1,77 @@
1
+ import * as React from "react";
2
+ import type { ColorValue, ViewStyle } from "react-native";
3
+ import { Chart } from "./Chart";
4
+ import type {
5
+ CenterLabel,
6
+ DataPoint,
7
+ LegendConfig,
8
+ SelectedPoint,
9
+ } from "./types";
10
+
11
+ export type PieSlice = {
12
+ label: string;
13
+ value: number;
14
+ color?: ColorValue;
15
+ };
16
+
17
+ export type PieChartProps = {
18
+ data: PieSlice[];
19
+ /** Donut hole ratio, 0–1. 0 = full pie, 0.62 = thin donut. Default 0.62. */
20
+ innerRadius?: number;
21
+ /** Outer radius ratio, 0–1. 0 = auto. */
22
+ outerRadius?: number;
23
+ /** Gap between adjacent slices, pt. Default 2. */
24
+ angularInset?: number;
25
+ cornerRadius?: number;
26
+ /** Center label rendered inside the donut hole. */
27
+ centerLabel?: CenterLabel;
28
+ legend?: LegendConfig;
29
+ /**
30
+ * Fires when the user taps a slice. The payload `x` is the slice
31
+ * label and `y` is its value. `null` when the selection clears.
32
+ * No visual callout is drawn — wire `centerLabel` from this event
33
+ * to update the donut hole copy.
34
+ */
35
+ onSelect?: (point: SelectedPoint) => void;
36
+ animate?: boolean;
37
+ style?: ViewStyle;
38
+ };
39
+
40
+ export function PieChart({
41
+ data,
42
+ innerRadius = 0.62,
43
+ outerRadius,
44
+ angularInset = 2,
45
+ cornerRadius = 2,
46
+ centerLabel,
47
+ legend,
48
+ onSelect,
49
+ animate,
50
+ style,
51
+ }: PieChartProps) {
52
+ const points: DataPoint[] = data.map((s) => ({
53
+ x: s.label,
54
+ y: s.value,
55
+ color: s.color,
56
+ category: s.label,
57
+ }));
58
+ return (
59
+ <Chart
60
+ style={style}
61
+ animate={animate}
62
+ legend={legend ?? { hidden: true }}
63
+ centerLabel={centerLabel}
64
+ onSelect={onSelect}
65
+ marks={[
66
+ {
67
+ type: "sector",
68
+ data: points,
69
+ innerRadius,
70
+ outerRadius,
71
+ angularInset,
72
+ cornerRadius,
73
+ },
74
+ ]}
75
+ />
76
+ );
77
+ }
@@ -0,0 +1,76 @@
1
+ import * as React from "react";
2
+ import type { ColorValue, ViewStyle } from "react-native";
3
+ import { Chart } from "./Chart";
4
+ import type {
5
+ AxisConfig,
6
+ DataPoint,
7
+ LegendConfig,
8
+ SelectedPoint,
9
+ TooltipConfig,
10
+ } from "./types";
11
+
12
+ export type RangeDatum = {
13
+ x: string;
14
+ yStart: number;
15
+ yEnd: number;
16
+ category?: string;
17
+ color?: ColorValue;
18
+ };
19
+
20
+ export type RangeBarChartProps = {
21
+ data: RangeDatum[];
22
+ color?: ColorValue;
23
+ cornerRadius?: number;
24
+ xAxis?: AxisConfig;
25
+ yAxis?: AxisConfig;
26
+ legend?: LegendConfig;
27
+ tooltip?: TooltipConfig;
28
+ onSelect?: (point: SelectedPoint) => void;
29
+ animate?: boolean;
30
+ style?: ViewStyle;
31
+ };
32
+
33
+ /**
34
+ * Range bars — drawn as `RectangleMark` between `yStart` and `yEnd`.
35
+ * Use for candlestick / OHLC visualisations, Gantt-style timelines,
36
+ * or low/high bands.
37
+ */
38
+ export function RangeBarChart({
39
+ data,
40
+ color,
41
+ cornerRadius = 2,
42
+ xAxis,
43
+ yAxis,
44
+ legend,
45
+ tooltip,
46
+ onSelect,
47
+ animate,
48
+ style,
49
+ }: RangeBarChartProps) {
50
+ const points: DataPoint[] = data.map((d) => ({
51
+ x: d.x,
52
+ y: d.yStart,
53
+ yEnd: d.yEnd,
54
+ category: d.category,
55
+ color: d.color,
56
+ }));
57
+ return (
58
+ <Chart
59
+ style={style}
60
+ animate={animate}
61
+ xAxis={xAxis}
62
+ yAxis={yAxis}
63
+ legend={legend}
64
+ tooltip={tooltip}
65
+ onSelect={onSelect}
66
+ marks={[
67
+ {
68
+ type: "rectangle",
69
+ data: points,
70
+ color,
71
+ cornerRadius,
72
+ },
73
+ ]}
74
+ />
75
+ );
76
+ }
@@ -0,0 +1,73 @@
1
+ import * as React from "react";
2
+ import type { ColorValue, ViewStyle } from "react-native";
3
+ import { Chart } from "./Chart";
4
+ import type {
5
+ AxisConfig,
6
+ DataPoint,
7
+ LegendConfig,
8
+ SelectedPoint,
9
+ Symbol,
10
+ TooltipConfig,
11
+ } from "./types";
12
+
13
+ export type ScatterDatum = {
14
+ x: string;
15
+ y: number;
16
+ category?: string;
17
+ color?: ColorValue;
18
+ };
19
+
20
+ export type ScatterChartProps = {
21
+ data: ScatterDatum[];
22
+ color?: ColorValue;
23
+ symbol?: Symbol;
24
+ symbolSize?: number;
25
+ xAxis?: AxisConfig;
26
+ yAxis?: AxisConfig;
27
+ legend?: LegendConfig;
28
+ tooltip?: TooltipConfig;
29
+ onSelect?: (point: SelectedPoint) => void;
30
+ animate?: boolean;
31
+ style?: ViewStyle;
32
+ };
33
+
34
+ export function ScatterChart({
35
+ data,
36
+ color,
37
+ symbol = "circle",
38
+ symbolSize = 36,
39
+ xAxis,
40
+ yAxis,
41
+ legend,
42
+ tooltip,
43
+ onSelect,
44
+ animate,
45
+ style,
46
+ }: ScatterChartProps) {
47
+ const points: DataPoint[] = data.map((d) => ({
48
+ x: d.x,
49
+ y: d.y,
50
+ category: d.category,
51
+ color: d.color,
52
+ }));
53
+ return (
54
+ <Chart
55
+ style={style}
56
+ animate={animate}
57
+ xAxis={xAxis}
58
+ yAxis={yAxis}
59
+ legend={legend}
60
+ tooltip={tooltip}
61
+ onSelect={onSelect}
62
+ marks={[
63
+ {
64
+ type: "point",
65
+ data: points,
66
+ color,
67
+ symbol,
68
+ symbolSize,
69
+ },
70
+ ]}
71
+ />
72
+ );
73
+ }
package/src/index.ts ADDED
@@ -0,0 +1,43 @@
1
+ // ── Runtime feature detection ──
2
+ export { isChartSupported } from "./support";
3
+
4
+ // ── Generic chart (any combination of marks) ──
5
+ export { Chart } from "./Chart";
6
+
7
+ // ── Convenience wrappers for the common single-mark cases ──
8
+ export { PieChart } from "./PieChart";
9
+ export type { PieChartProps, PieSlice } from "./PieChart";
10
+
11
+ export { LineChart } from "./LineChart";
12
+ export type { LineChartProps, LinePoint } from "./LineChart";
13
+
14
+ export { AreaChart } from "./AreaChart";
15
+ export type { AreaChartProps, AreaDatum } from "./AreaChart";
16
+
17
+ export { BarChart } from "./BarChart";
18
+ export type { BarChartProps, BarDatum } from "./BarChart";
19
+
20
+ export { ScatterChart } from "./ScatterChart";
21
+ export type { ScatterChartProps, ScatterDatum } from "./ScatterChart";
22
+
23
+ export { RangeBarChart } from "./RangeBarChart";
24
+ export type { RangeBarChartProps, RangeDatum } from "./RangeBarChart";
25
+
26
+ // ── Public types — use these to assemble your own marks for the
27
+ // generic `<Chart>` component or to type-share data across screens.
28
+ export type {
29
+ AxisConfig,
30
+ CenterLabel,
31
+ ChartProps,
32
+ DataPoint,
33
+ Gradient,
34
+ GradientStop,
35
+ Interpolation,
36
+ LegendConfig,
37
+ LineCap,
38
+ Mark,
39
+ MarkType,
40
+ SelectedPoint,
41
+ Symbol,
42
+ TooltipConfig,
43
+ } from "./types";
package/src/support.ts ADDED
@@ -0,0 +1,30 @@
1
+ import { Platform } from "react-native";
2
+
3
+ /**
4
+ * Runtime feature-detection helper. Returns true only on devices
5
+ * that can actually render the charts — iOS 17 and above. Use it to
6
+ * mount a fallback chart library (or a placeholder) on older iOS
7
+ * versions and on Android / web:
8
+ *
9
+ * ```tsx
10
+ * import { isChartSupported, LineChart } from "rn-native-ios-charts";
11
+ * import { OtherLineChart } from "some-cross-platform-chart-lib";
12
+ *
13
+ * export function MyLineChart(props) {
14
+ * if (isChartSupported()) return <LineChart {...props} />;
15
+ * return <OtherLineChart {...props} />;
16
+ * }
17
+ * ```
18
+ *
19
+ * The check is cheap (a parse of `Platform.Version`) and stable
20
+ * across the lifetime of the process — safe to call inline in render.
21
+ */
22
+ export function isChartSupported(): boolean {
23
+ if (Platform.OS !== "ios") return false;
24
+ // Platform.Version on iOS is a string like "17.4". Parsing the
25
+ // leading integer is enough; SwiftUI Charts' unified API
26
+ // (`Chart {}`, `SectorMark`, `chartBackground`,
27
+ // `chartXSelection`) all became available in iOS 17.0.
28
+ const major = parseInt(String(Platform.Version), 10);
29
+ return Number.isFinite(major) && major >= 17;
30
+ }