universal-adaptive-bars 0.0.1

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.
@@ -0,0 +1,44 @@
1
+ import React, { useMemo } from 'react';
2
+ import { Rect } from 'react-native-svg';
3
+ import type { DataPoint } from '../types';
4
+
5
+ interface BarNativeProps {
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ data: DataPoint;
11
+ onClick?: (data: DataPoint) => void;
12
+ isActive?: boolean;
13
+ isDimmed?: boolean;
14
+ accessibilityLabel?: string;
15
+ accessibilityRole?: string;
16
+ }
17
+
18
+ export const BarNative: React.FC<BarNativeProps> = ({
19
+ x, y, width, height, data, onClick, isActive, isDimmed, accessibilityLabel
20
+ }) => {
21
+ const fillColor = useMemo(() => {
22
+ if (data.isPrediction) return '#8e44ad';
23
+ if (isActive) return '#e74c3c';
24
+ return data.color || '#3498db';
25
+ }, [data, isActive]);
26
+
27
+ return (
28
+ <Rect
29
+ x={x}
30
+ y={y}
31
+ width={width}
32
+ height={height}
33
+ fill={fillColor}
34
+ rx={4}
35
+ ry={4}
36
+ onPress={() => onClick?.(data)}
37
+ stroke={isActive ? '#c0392b' : 'none'}
38
+ strokeWidth={isActive ? 2 : 0}
39
+ opacity={isDimmed ? 0.3 : (data.isPrediction ? 0.7 : 1)}
40
+ // @ts-ignore
41
+ accessibilityLabel={accessibilityLabel}
42
+ />
43
+ );
44
+ };
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import type { DataPoint } from '../types';
3
+
4
+ interface TooltipProps {
5
+ data: DataPoint;
6
+ position: { x: number; y: number };
7
+ }
8
+
9
+ export const Tooltip: React.FC<TooltipProps> = ({ data, position }) => {
10
+ return (
11
+ <div
12
+ style={{
13
+ position: 'absolute',
14
+ left: position.x,
15
+ top: position.y,
16
+ transform: 'translate(-50%, -100%)',
17
+ marginBottom: '8px',
18
+ backgroundColor: '#333',
19
+ color: '#fff',
20
+ padding: '8px 12px',
21
+ borderRadius: '4px',
22
+ fontSize: '12px',
23
+ pointerEvents: 'none',
24
+ zIndex: 10,
25
+ boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
26
+ }}
27
+ >
28
+ <div style={{ fontWeight: 'bold', marginBottom: 2 }}>{data.label}</div>
29
+ <div>Value: {data.value}</div>
30
+ {data.isPrediction && (
31
+ <div style={{ marginTop: 2, color: '#a29bfe', fontStyle: 'italic' }}>Prediction</div>
32
+ )}
33
+ </div>
34
+ );
35
+ };
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet } from 'react-native';
3
+ import type { DataPoint } from '../types';
4
+
5
+ interface TooltipNativeProps {
6
+ data: DataPoint;
7
+ position: { x: number; y: number };
8
+ }
9
+
10
+ export const TooltipNative: React.FC<TooltipNativeProps> = ({ data, position }) => {
11
+ return (
12
+ <View
13
+ style={[
14
+ styles.tooltip,
15
+ {
16
+ left: position.x,
17
+ top: position.y,
18
+ transform: [{ translateX: -50 }, { translateY: -100 }] as any, // React Native transform style
19
+ }
20
+ ]}
21
+ >
22
+ <Text style={styles.label}>{data.label}</Text>
23
+ <Text style={styles.value}>Value: {data.value}</Text>
24
+ {data.isPrediction && (
25
+ <Text style={styles.prediction}>Prediction</Text>
26
+ )}
27
+ </View>
28
+ );
29
+ };
30
+
31
+ const styles = StyleSheet.create({
32
+ tooltip: {
33
+ position: 'absolute',
34
+ backgroundColor: '#333',
35
+ padding: 8,
36
+ borderRadius: 4,
37
+ zIndex: 10,
38
+ elevation: 5,
39
+ shadowColor: '#000',
40
+ shadowOffset: { width: 0, height: 2 },
41
+ shadowOpacity: 0.25,
42
+ shadowRadius: 3.84,
43
+ },
44
+ label: {
45
+ color: '#fff',
46
+ fontWeight: 'bold',
47
+ marginBottom: 2,
48
+ fontSize: 12
49
+ },
50
+ value: {
51
+ color: '#fff',
52
+ fontSize: 12
53
+ },
54
+ prediction: {
55
+ marginTop: 2,
56
+ color: '#a29bfe',
57
+ fontStyle: 'italic',
58
+ fontSize: 10
59
+ }
60
+ });
@@ -0,0 +1,212 @@
1
+ import { useMemo } from 'react';
2
+ import { parseISO, format, startOfWeek, endOfWeek, startOfMonth, isValid } from 'date-fns';
3
+ import type { DataPoint, ChartView, RawDataPoint } from '../types';
4
+
5
+ interface UseChartDataProps {
6
+ data: RawDataPoint[];
7
+ view: ChartView;
8
+ dataKeys: {
9
+ label: string;
10
+ value: string | string[]; // Single key or array of keys
11
+ date: string;
12
+ };
13
+ colors?: string[];
14
+ }
15
+
16
+ // Default Premium Palette
17
+ const DEFAULT_PALETTE = ['#6366f1', '#8b5cf6', '#ec4899', '#10b981', '#f59e0b', '#3b82f6', '#ef4444'];
18
+
19
+ export const useChartData = ({ data, view, dataKeys, colors = DEFAULT_PALETTE }: UseChartDataProps) => {
20
+ const processedData = useMemo(() => {
21
+ if (!data || data.length === 0) return [];
22
+
23
+ const standardData: DataPoint[] = data.map((item, index) => {
24
+ const dateRaw = item[dataKeys.date];
25
+ const date = dateRaw instanceof Date ? dateRaw : parseISO(dateRaw as string);
26
+
27
+ // Handle Stacked vs Single Value
28
+ let totalValue = 0;
29
+ let stackedValues: { key: string; value: number; color: string }[] = [];
30
+
31
+ if (Array.isArray(dataKeys.value)) {
32
+ // Stacked
33
+ dataKeys.value.forEach((key, kIndex) => {
34
+ const val = Number(item[key]) || 0;
35
+ totalValue += val;
36
+ stackedValues.push({
37
+ key,
38
+ value: val,
39
+ color: colors[kIndex % colors.length]
40
+ });
41
+ });
42
+ } else {
43
+ // Single
44
+ totalValue = Number(item[dataKeys.value]) || 0;
45
+ }
46
+
47
+ return {
48
+ id: (item.id as string) || `item-${index}`,
49
+ label: (item[dataKeys.label] as string) || '',
50
+ value: totalValue,
51
+ stackedValues: stackedValues.length > 0 ? stackedValues : undefined,
52
+ date: isValid(date) ? date : new Date(),
53
+ color: stackedValues.length === 0 ? colors[0] : undefined // Default color for single bar
54
+ } as DataPoint;
55
+ });
56
+
57
+ const sorted = standardData.sort((a, b) => a.date.getTime() - b.date.getTime());
58
+
59
+ // Helper for grouping
60
+ const groupBy = <T>(array: T[], keyFn: (item: T) => string) => {
61
+ return array.reduce((result: any, item: T) => {
62
+ const key = keyFn(item);
63
+ (result[key] = result[key] || []).push(item);
64
+ return result;
65
+ }, {});
66
+ };
67
+
68
+ // Aggregation Logic
69
+ let aggregated: DataPoint[] = [];
70
+
71
+ if (view === 'day') {
72
+ const grouped = groupBy(sorted, (d) => format(d.date, 'yyyy-MM-dd'));
73
+ aggregated = Object.entries(grouped).map(([key, group]) => {
74
+ const dayGroup = group as DataPoint[];
75
+ const first = dayGroup[0];
76
+ const total = dayGroup.reduce((sum, item) => sum + item.value, 0);
77
+
78
+ let mergedStack: any[] = [];
79
+ if (first.stackedValues) {
80
+ const stackMap = new Map<string, number>();
81
+ dayGroup.forEach(d => {
82
+ d.stackedValues?.forEach(s => {
83
+ const current = stackMap.get(s.key) || 0;
84
+ stackMap.set(s.key, current + s.value);
85
+ });
86
+ });
87
+ mergedStack = Array.from(stackMap.entries()).map(([k, v], i) => ({
88
+ key: k,
89
+ value: v,
90
+ color: colors[i % colors.length]
91
+ }));
92
+ }
93
+
94
+ return {
95
+ ...first,
96
+ id: key,
97
+ label: format(first.date, 'E dd'),
98
+ value: total,
99
+ stackedValues: mergedStack.length > 0 ? mergedStack : undefined
100
+ };
101
+ });
102
+ }
103
+ else if (view === 'week') {
104
+ const grouped = groupBy(sorted, (d) => format(startOfWeek(d.date), 'yyyy-MM-dd'));
105
+ aggregated = Object.entries(grouped).map(([key, group]) => {
106
+ const weekGroup = group as DataPoint[];
107
+ const first = weekGroup[0];
108
+ const total = weekGroup.reduce((sum, item) => sum + item.value, 0);
109
+
110
+ let mergedStack: any[] = [];
111
+ if (first.stackedValues) {
112
+ const stackMap = new Map<string, number>();
113
+ weekGroup.forEach(d => {
114
+ d.stackedValues?.forEach(s => {
115
+ const current = stackMap.get(s.key) || 0;
116
+ stackMap.set(s.key, current + s.value);
117
+ });
118
+ });
119
+ mergedStack = Array.from(stackMap.entries()).map(([k, v], i) => ({
120
+ key: k,
121
+ value: v,
122
+ color: colors[i % colors.length]
123
+ }));
124
+ }
125
+
126
+ const weekStart = startOfWeek(first.date);
127
+ const weekEnd = endOfWeek(first.date);
128
+ const label = `${format(weekStart, 'MMM dd')}-${format(weekEnd, 'MMM dd')}`;
129
+
130
+ return {
131
+ ...first,
132
+ id: key,
133
+ label: label,
134
+ value: total,
135
+ stackedValues: mergedStack.length > 0 ? mergedStack : undefined
136
+ };
137
+ });
138
+ }
139
+ else if (view === 'month') {
140
+ const grouped = groupBy(sorted, (d) => format(startOfMonth(d.date), 'yyyy-MM'));
141
+ aggregated = Object.entries(grouped).map(([key, group]) => {
142
+ const monthGroup = group as DataPoint[];
143
+ const first = monthGroup[0];
144
+ const total = monthGroup.reduce((sum, item) => sum + item.value, 0);
145
+
146
+ let mergedStack: any[] = [];
147
+ if (first.stackedValues) {
148
+ const stackMap = new Map<string, number>();
149
+ monthGroup.forEach(d => {
150
+ d.stackedValues?.forEach(s => {
151
+ const current = stackMap.get(s.key) || 0;
152
+ stackMap.set(s.key, current + s.value);
153
+ });
154
+ });
155
+ mergedStack = Array.from(stackMap.entries()).map(([k, v], i) => ({
156
+ key: k,
157
+ value: v,
158
+ color: colors[i % colors.length]
159
+ }));
160
+ }
161
+
162
+ return {
163
+ ...first,
164
+ id: key,
165
+ label: format(first.date, 'MMM yy'),
166
+ value: total,
167
+ stackedValues: mergedStack.length > 0 ? mergedStack : undefined
168
+ };
169
+ });
170
+ }
171
+ else if (view === 'year') {
172
+ const grouped = groupBy(sorted, (d) => format(d.date, 'yyyy'));
173
+ aggregated = Object.entries(grouped).map(([key, group]) => {
174
+ const yearGroup = group as DataPoint[];
175
+ const first = yearGroup[0];
176
+ const total = yearGroup.reduce((sum, item) => sum + item.value, 0);
177
+
178
+ let mergedStack: any[] = [];
179
+ if (first.stackedValues) {
180
+ const stackMap = new Map<string, number>();
181
+ yearGroup.forEach(d => {
182
+ d.stackedValues?.forEach(s => {
183
+ const current = stackMap.get(s.key) || 0;
184
+ stackMap.set(s.key, current + s.value);
185
+ });
186
+ });
187
+ mergedStack = Array.from(stackMap.entries()).map(([k, v], i) => ({
188
+ key: k,
189
+ value: v,
190
+ color: colors[i % colors.length]
191
+ }));
192
+ }
193
+
194
+ return {
195
+ ...first,
196
+ id: key,
197
+ label: key, // '2023'
198
+ value: total,
199
+ stackedValues: mergedStack.length > 0 ? mergedStack : undefined
200
+ };
201
+ });
202
+ }
203
+ else {
204
+ aggregated = sorted;
205
+ }
206
+
207
+ return aggregated;
208
+
209
+ }, [data, view, dataKeys, colors]);
210
+
211
+ return processedData;
212
+ };
@@ -0,0 +1,2 @@
1
+ export * from './SmartBarChart';
2
+
@@ -0,0 +1 @@
1
+ export * from './SmartBarChartNative';
@@ -0,0 +1,79 @@
1
+ import { GoogleGenerativeAI } from '@google/generative-ai';
2
+ import type { DataPoint, ChartView } from '../types';
3
+ import { addDays, addWeeks, addMonths, format } from 'date-fns';
4
+
5
+ export class GeminiService {
6
+ private genAI: GoogleGenerativeAI;
7
+ private model: any;
8
+
9
+ constructor(apiKey: string, modelName: string = 'gemini-1.5-flash') {
10
+ this.genAI = new GoogleGenerativeAI(apiKey);
11
+ this.model = this.genAI.getGenerativeModel({ model: modelName });
12
+ }
13
+
14
+ async predictNext(data: DataPoint[], count: number = 3, view: ChartView = 'month'): Promise<DataPoint[]> {
15
+ if (data.length === 0) return [];
16
+
17
+ const prompt = `
18
+ I have a time series data:
19
+ ${data.map(d => `${d.label}: ${d.value}`).join('\n')}
20
+
21
+ Predict the next ${count} values based on the trend.
22
+ Return ONLY a JSON array of numbers. Example: [10, 12, 15].
23
+ Do not include any explanation or markdown formatting.
24
+ `;
25
+
26
+ try {
27
+ const result = await this.model.generateContent(prompt);
28
+ const response = await result.response;
29
+ const text = response.text();
30
+ const cleaned = text.replace(/```json/g, '').replace(/```/g, '').trim();
31
+ const predictedValues: number[] = JSON.parse(cleaned);
32
+
33
+ const lastPoint = data[data.length - 1];
34
+ const lastDate = lastPoint.date;
35
+
36
+ return predictedValues.map((val, index) => {
37
+ let newDate = lastDate;
38
+ if (view === 'day') newDate = addDays(lastDate, index + 1);
39
+ else if (view === 'week') newDate = addWeeks(lastDate, index + 1);
40
+ else if (view === 'month') newDate = addMonths(lastDate, index + 1);
41
+
42
+ let label = '';
43
+ if (view === 'day') label = format(newDate, 'eee dd');
44
+ else if (view === 'week') label = `W${index + 1}`;
45
+ else if (view === 'month') label = format(newDate, 'MMM');
46
+ else label = format(newDate, 'yyyy');
47
+
48
+ return {
49
+ id: `prediction-${index}`,
50
+ label,
51
+ value: val,
52
+ date: newDate,
53
+ isPrediction: true,
54
+ color: '#8e44ad'
55
+ } as DataPoint;
56
+ });
57
+
58
+ } catch (e) {
59
+ console.error("Gemini Prediction Failed", e);
60
+ return [];
61
+ }
62
+ }
63
+
64
+ async analyze(data: DataPoint[]): Promise<string> {
65
+ const prompt = `
66
+ Analyze the following data trend:
67
+ ${data.map(d => `${d.label}: ${d.value}`).join(', ')}
68
+
69
+ Provide a short, insightful summary (max 2 sentences).
70
+ `;
71
+ try {
72
+ const result = await this.model.generateContent(prompt);
73
+ return result.response.text();
74
+ } catch (e) {
75
+ console.error("Gemini Analysis Failed", e);
76
+ return "Analysis unavailable.";
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,64 @@
1
+ export interface DataPoint {
2
+ id: string;
3
+ label: string;
4
+ value: number; // Total value (sum of stack)
5
+ stackedValues?: { key: string; value: number; color: string }[];
6
+ date: Date; // ISO string or Date object
7
+ color?: string;
8
+ tooltip?: string;
9
+ isPrediction?: boolean;
10
+ }
11
+
12
+ export interface RawDataPoint {
13
+ [key: string]: any;
14
+ }
15
+
16
+ export type ChartView = 'day' | 'week' | 'month' | 'year';
17
+
18
+ export interface ChartTheme {
19
+ background?: string;
20
+ bar?: {
21
+ radius?: number;
22
+ opacity?: number;
23
+ maxWidth?: number;
24
+ };
25
+ grid?: {
26
+ stroke?: string;
27
+ strokeDasharray?: string;
28
+ visible?: boolean;
29
+ };
30
+ axis?: {
31
+ labelColor?: string;
32
+ tickColor?: string;
33
+ fontSize?: number;
34
+ };
35
+ tooltip?: {
36
+ backgroundColor?: string;
37
+ textColor?: string;
38
+ borderRadius?: number;
39
+ };
40
+ }
41
+
42
+ export interface SmartBarChartProps {
43
+ data: RawDataPoint[];
44
+ view?: ChartView;
45
+ variant?: 'default' | 'stacked';
46
+ colors?: string[];
47
+ theme?: ChartTheme; // New
48
+ axisLabels?: { x?: string; y?: string };
49
+ onViewChange?: (view: ChartView) => void;
50
+ dataKeys: {
51
+ label: string;
52
+ value: string | string[];
53
+ date: string;
54
+ };
55
+ geminiConfig?: {
56
+ apiKey?: string;
57
+ model?: string;
58
+ };
59
+ onPredict?: (currentData: DataPoint[]) => Promise<DataPoint[]>;
60
+ onAnalyze?: (currentData: DataPoint[]) => Promise<string>;
61
+ height?: number;
62
+ width?: number | string;
63
+ className?: string;
64
+ }