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.
- package/README.md +110 -0
- package/dist/smart-bar-chart.js +3863 -0
- package/dist/smart-bar-chart.umd.cjs +20 -0
- package/dist/vite.svg +1 -0
- package/package.json +72 -0
- package/src/lib/SmartBarChart.tsx +526 -0
- package/src/lib/SmartBarChartNative.tsx +528 -0
- package/src/lib/__tests__/SmartBarChart.test.tsx +69 -0
- package/src/lib/components/Bar.tsx +49 -0
- package/src/lib/components/BarNative.tsx +44 -0
- package/src/lib/components/Tooltip.tsx +35 -0
- package/src/lib/components/TooltipNative.tsx +60 -0
- package/src/lib/hooks/useChartData.ts +212 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/native.ts +1 -0
- package/src/lib/services/gemini.ts +79 -0
- package/src/lib/types.ts +64 -0
|
@@ -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
|
+
};
|
package/src/lib/index.ts
ADDED
|
@@ -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
|
+
}
|
package/src/lib/types.ts
ADDED
|
@@ -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
|
+
}
|