@gram-ai/elements 1.25.1 → 1.26.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/dist/components/Chat/stories/Charts.stories.d.ts +37 -0
- package/dist/components/Chat/stories/GenerativeUI.stories.d.ts +17 -0
- package/dist/components/Chat/stories/MessageFeedback.stories.d.ts +1 -1
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/buttonVariants.d.ts +1 -1
- package/dist/components/ui/charts.stories.d.ts +43 -0
- package/dist/components/ui/generative-ui.stories.d.ts +53 -0
- package/dist/contexts/ChatIdContext.d.ts +11 -0
- package/dist/contexts/contexts.d.ts +1 -0
- package/dist/elements.cjs +1 -1
- package/dist/elements.css +1 -1
- package/dist/elements.js +7 -6
- package/dist/index-BJnv49-A.js +37057 -0
- package/dist/index-BJnv49-A.js.map +1 -0
- package/dist/index-BpJstUh1.cjs +280 -0
- package/dist/index-BpJstUh1.cjs.map +1 -0
- package/dist/index-CUitXazZ.js +30426 -0
- package/dist/index-CUitXazZ.js.map +1 -0
- package/dist/index-ChW-CSuu.cjs +147 -0
- package/dist/index-ChW-CSuu.cjs.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/plugins/chart/catalog.d.ts +123 -0
- package/dist/plugins/chart/index.d.ts +1 -1
- package/dist/plugins/chart/ui/area-chart.d.ts +16 -0
- package/dist/plugins/chart/ui/bar-chart.d.ts +16 -0
- package/dist/plugins/chart/ui/donut-chart.d.ts +17 -0
- package/dist/plugins/chart/ui/index.d.ts +7 -0
- package/dist/plugins/chart/ui/line-chart.d.ts +17 -0
- package/dist/plugins/chart/ui/pie-chart.d.ts +15 -0
- package/dist/plugins/chart/ui/radar-chart.d.ts +14 -0
- package/dist/plugins/chart/ui/scatter-chart.d.ts +18 -0
- package/dist/plugins/components/MacOSWindowFrame.d.ts +13 -0
- package/dist/plugins/components/PluginLoadingState.d.ts +1 -1
- package/dist/plugins/generative-ui/catalog.d.ts +293 -0
- package/dist/plugins/generative-ui/ui/accordion-wrapper.d.ts +18 -0
- package/dist/plugins/generative-ui/ui/accordion.d.ts +7 -0
- package/dist/plugins/generative-ui/ui/action-button.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/alert-wrapper.d.ts +9 -0
- package/dist/plugins/generative-ui/ui/alert.d.ts +9 -0
- package/dist/plugins/generative-ui/ui/avatar-wrapper.d.ts +9 -0
- package/dist/plugins/generative-ui/ui/avatar.d.ts +11 -0
- package/dist/plugins/generative-ui/ui/badge.d.ts +12 -0
- package/dist/plugins/generative-ui/ui/button-wrapper.d.ts +15 -0
- package/dist/plugins/generative-ui/ui/button.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/card-wrapper.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/card.d.ts +9 -0
- package/dist/plugins/generative-ui/ui/checkbox-wrapper.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/checkbox.d.ts +4 -0
- package/dist/plugins/generative-ui/ui/data-table.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/dialog.d.ts +17 -0
- package/dist/plugins/generative-ui/ui/dropdown-menu.d.ts +25 -0
- package/dist/plugins/generative-ui/ui/grid.d.ts +6 -0
- package/dist/plugins/generative-ui/ui/index.d.ts +40 -0
- package/dist/plugins/generative-ui/ui/input-wrapper.d.ts +11 -0
- package/dist/plugins/generative-ui/ui/input.d.ts +3 -0
- package/dist/plugins/generative-ui/ui/label.d.ts +4 -0
- package/dist/plugins/generative-ui/ui/list.d.ts +6 -0
- package/dist/plugins/generative-ui/ui/metric.d.ts +7 -0
- package/dist/plugins/generative-ui/ui/pagination.d.ts +13 -0
- package/dist/plugins/generative-ui/ui/popover.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/progress.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/radio-group.d.ts +5 -0
- package/dist/plugins/generative-ui/ui/select-wrapper.d.ts +13 -0
- package/dist/plugins/generative-ui/ui/select.d.ts +15 -0
- package/dist/plugins/generative-ui/ui/separator.d.ts +4 -0
- package/dist/plugins/generative-ui/ui/skeleton-wrapper.d.ts +9 -0
- package/dist/plugins/generative-ui/ui/skeleton.d.ts +2 -0
- package/dist/plugins/generative-ui/ui/stack.d.ts +8 -0
- package/dist/plugins/generative-ui/ui/switch.d.ts +6 -0
- package/dist/plugins/generative-ui/ui/table.d.ts +10 -0
- package/dist/plugins/generative-ui/ui/tabs-wrapper.d.ts +21 -0
- package/dist/plugins/generative-ui/ui/tabs.d.ts +11 -0
- package/dist/plugins/generative-ui/ui/text.d.ts +7 -0
- package/dist/plugins/generative-ui/ui/textarea.d.ts +3 -0
- package/dist/plugins/generative-ui/ui/tooltip.d.ts +7 -0
- package/dist/plugins.cjs +1 -1
- package/dist/plugins.js +1 -1
- package/dist/{profiler-CijCgLrw.js → profiler-D4Tw5ecI.js} +2 -2
- package/dist/{profiler-CijCgLrw.js.map → profiler-D4Tw5ecI.js.map} +1 -1
- package/dist/{profiler-DAT0DL1W.cjs → profiler-DCWYDZ1F.cjs} +2 -2
- package/dist/{profiler-DAT0DL1W.cjs.map → profiler-DCWYDZ1F.cjs.map} +1 -1
- package/dist/{startRecording-DotsE8QT.cjs → startRecording-3sTskM3H.cjs} +2 -2
- package/dist/{startRecording-DotsE8QT.cjs.map → startRecording-3sTskM3H.cjs.map} +1 -1
- package/dist/{startRecording-gmhENmf0.js → startRecording-BHhcCWQE.js} +2 -2
- package/dist/{startRecording-gmhENmf0.js.map → startRecording-BHhcCWQE.js.map} +1 -1
- package/dist/types/index.d.ts +4 -4
- package/package.json +4 -1
- package/src/components/Chat/stories/Charts.stories.tsx +260 -0
- package/src/components/Chat/stories/ConnectionConfiguration.stories.tsx +6 -6
- package/src/components/Chat/stories/GenerativeUI.stories.tsx +113 -0
- package/src/components/Chat/stories/MessageFeedback.stories.tsx +6 -6
- package/src/components/Chat/stories/ToolApproval.stories.tsx +10 -10
- package/src/components/Chat/stories/Tools.stories.tsx +122 -104
- package/src/components/Chat/stories/Variants.stories.tsx +1 -1
- package/src/components/Replay.stories.tsx +1 -1
- package/src/components/Replay.tsx +18 -13
- package/src/components/ShadowRoot.tsx +5 -1
- package/src/components/assistant-ui/message-feedback.tsx +6 -7
- package/src/components/assistant-ui/thread.tsx +76 -11
- package/src/components/ui/charts.stories.tsx +246 -0
- package/src/components/ui/generative-ui.stories.tsx +557 -0
- package/src/components/ui/generative-ui.tsx +60 -360
- package/src/components/ui/tool-ui.stories.tsx +6 -3
- package/src/contexts/ChatIdContext.tsx +21 -0
- package/src/contexts/ElementsProvider.tsx +77 -37
- package/src/contexts/contexts.ts +2 -0
- package/src/hooks/useAuth.ts +18 -3
- package/src/hooks/useFollowOnSuggestions.ts +6 -1
- package/src/index.ts +1 -0
- package/src/plugins/chart/catalog.ts +141 -0
- package/src/plugins/chart/component.tsx +79 -125
- package/src/plugins/chart/index.ts +141 -89
- package/src/plugins/chart/ui/area-chart.tsx +133 -0
- package/src/plugins/chart/ui/bar-chart.tsx +137 -0
- package/src/plugins/chart/ui/donut-chart.tsx +167 -0
- package/src/plugins/chart/ui/index.ts +7 -0
- package/src/plugins/chart/ui/line-chart.tsx +135 -0
- package/src/plugins/chart/ui/pie-chart.tsx +148 -0
- package/src/plugins/chart/ui/radar-chart.tsx +105 -0
- package/src/plugins/chart/ui/scatter-chart.tsx +132 -0
- package/src/plugins/components/MacOSWindowFrame.tsx +55 -0
- package/src/plugins/components/PluginLoadingState.tsx +9 -13
- package/src/plugins/generative-ui/catalog.ts +277 -0
- package/src/plugins/generative-ui/component.tsx +112 -21
- package/src/plugins/generative-ui/index.ts +20 -141
- package/src/plugins/generative-ui/ui/accordion-wrapper.tsx +57 -0
- package/src/plugins/generative-ui/ui/accordion.tsx +66 -0
- package/src/plugins/generative-ui/ui/action-button.tsx +68 -0
- package/src/plugins/generative-ui/ui/alert-wrapper.tsx +26 -0
- package/src/plugins/generative-ui/ui/alert.tsx +66 -0
- package/src/plugins/generative-ui/ui/avatar-wrapper.tsx +22 -0
- package/src/plugins/generative-ui/ui/avatar.tsx +109 -0
- package/src/plugins/generative-ui/ui/badge.tsx +65 -0
- package/src/plugins/generative-ui/ui/button-wrapper.tsx +32 -0
- package/src/plugins/generative-ui/ui/button.tsx +65 -0
- package/src/plugins/generative-ui/ui/card-wrapper.tsx +36 -0
- package/src/plugins/generative-ui/ui/card.tsx +92 -0
- package/src/plugins/generative-ui/ui/checkbox-wrapper.tsx +39 -0
- package/src/plugins/generative-ui/ui/checkbox.tsx +32 -0
- package/src/plugins/generative-ui/ui/data-table.tsx +53 -0
- package/src/plugins/generative-ui/ui/dialog.tsx +158 -0
- package/src/plugins/generative-ui/ui/dropdown-menu.tsx +257 -0
- package/src/plugins/generative-ui/ui/grid.tsx +29 -0
- package/src/plugins/generative-ui/ui/index.ts +43 -0
- package/src/plugins/generative-ui/ui/input-wrapper.tsx +38 -0
- package/src/plugins/generative-ui/ui/input.tsx +21 -0
- package/src/plugins/generative-ui/ui/label.tsx +24 -0
- package/src/plugins/generative-ui/ui/list.tsx +34 -0
- package/src/plugins/generative-ui/ui/metric.tsx +53 -0
- package/src/plugins/generative-ui/ui/pagination.tsx +127 -0
- package/src/plugins/generative-ui/ui/popover.tsx +89 -0
- package/src/plugins/generative-ui/ui/progress.tsx +57 -0
- package/src/plugins/generative-ui/ui/radio-group.tsx +45 -0
- package/src/plugins/generative-ui/ui/select-wrapper.tsx +41 -0
- package/src/plugins/generative-ui/ui/select.tsx +190 -0
- package/src/plugins/generative-ui/ui/separator.tsx +28 -0
- package/src/plugins/generative-ui/ui/skeleton-wrapper.tsx +30 -0
- package/src/plugins/generative-ui/ui/skeleton.tsx +13 -0
- package/src/plugins/generative-ui/ui/stack.tsx +54 -0
- package/src/plugins/generative-ui/ui/switch.tsx +35 -0
- package/src/plugins/generative-ui/ui/table.tsx +116 -0
- package/src/plugins/generative-ui/ui/tabs-wrapper.tsx +51 -0
- package/src/plugins/generative-ui/ui/tabs.tsx +92 -0
- package/src/plugins/generative-ui/ui/text.tsx +33 -0
- package/src/plugins/generative-ui/ui/textarea.tsx +18 -0
- package/src/plugins/generative-ui/ui/tooltip.tsx +57 -0
- package/src/types/index.ts +4 -4
- package/dist/components/Chat/stories/Plugins.stories.d.ts +0 -12
- package/dist/index-C3UbmFRR.cjs +0 -178
- package/dist/index-C3UbmFRR.cjs.map +0 -1
- package/dist/index-CtyV0c-T.js +0 -27225
- package/dist/index-CtyV0c-T.js.map +0 -1
- package/dist/index-DxJwZ5Kc.js +0 -39975
- package/dist/index-DxJwZ5Kc.js.map +0 -1
- package/dist/index-iUSSoKFz.cjs +0 -251
- package/dist/index-iUSSoKFz.cjs.map +0 -1
- package/src/components/Chat/stories/Plugins.stories.tsx +0 -158
|
@@ -2,108 +2,160 @@ import { Plugin } from '@/types/plugins'
|
|
|
2
2
|
import { ChartRenderer } from './component'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* This plugin renders
|
|
5
|
+
* This plugin renders charts using json-render format.
|
|
6
6
|
*/
|
|
7
7
|
export const chart: Plugin = {
|
|
8
|
-
language: '
|
|
9
|
-
prompt: `
|
|
8
|
+
language: 'chart',
|
|
9
|
+
prompt: `WHEN TO USE CHARTS:
|
|
10
|
+
Create charts to visualize numerical data when it helps users understand patterns, trends, or comparisons. Use the 'chart' code block format.
|
|
10
11
|
|
|
11
12
|
CRITICAL JSON REQUIREMENTS:
|
|
12
13
|
- The code block MUST contain ONLY valid, parseable JSON
|
|
13
14
|
- NO comments (no // or /* */ anywhere)
|
|
14
15
|
- NO trailing commas
|
|
15
16
|
- Use double quotes for all strings and keys
|
|
16
|
-
- NO text before or after the JSON object
|
|
17
17
|
- The JSON must start with { and end with }
|
|
18
18
|
|
|
19
|
+
AVAILABLE CHART TYPES:
|
|
20
|
+
|
|
21
|
+
BarChart - Compare categorical data
|
|
22
|
+
props: {
|
|
23
|
+
title?: string,
|
|
24
|
+
data: [{ label: string, value: number, color?: string }],
|
|
25
|
+
layout?: "vertical" | "horizontal",
|
|
26
|
+
showGrid?: boolean,
|
|
27
|
+
showLegend?: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
LineChart - Show trends over time
|
|
31
|
+
props: {
|
|
32
|
+
title?: string,
|
|
33
|
+
data: [{ label: string, [series]: number }],
|
|
34
|
+
series?: string[],
|
|
35
|
+
showGrid?: boolean,
|
|
36
|
+
showLegend?: boolean,
|
|
37
|
+
showDots?: boolean,
|
|
38
|
+
curved?: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
AreaChart - Show volume over time
|
|
42
|
+
props: {
|
|
43
|
+
title?: string,
|
|
44
|
+
data: [{ label: string, [series]: number }],
|
|
45
|
+
series?: string[],
|
|
46
|
+
stacked?: boolean,
|
|
47
|
+
showGrid?: boolean,
|
|
48
|
+
showLegend?: boolean
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
PieChart - Show proportions (2-6 categories)
|
|
52
|
+
props: {
|
|
53
|
+
title?: string,
|
|
54
|
+
data: [{ label: string, value: number, color?: string }],
|
|
55
|
+
showLabels?: boolean,
|
|
56
|
+
showLegend?: boolean
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
DonutChart - Proportions with center metric
|
|
60
|
+
props: {
|
|
61
|
+
title?: string,
|
|
62
|
+
data: [{ label: string, value: number, color?: string }],
|
|
63
|
+
showLabels?: boolean,
|
|
64
|
+
showLegend?: boolean,
|
|
65
|
+
innerLabel?: string,
|
|
66
|
+
innerValue?: string | number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
ScatterChart - Show correlation
|
|
70
|
+
props: {
|
|
71
|
+
title?: string,
|
|
72
|
+
data: [{ x: number, y: number, label?: string, size?: number, color?: string }],
|
|
73
|
+
xLabel?: string,
|
|
74
|
+
yLabel?: string,
|
|
75
|
+
showGrid?: boolean
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
RadarChart - Compare multiple attributes (3-8 dimensions)
|
|
79
|
+
props: {
|
|
80
|
+
title?: string,
|
|
81
|
+
data: [{ label: string, value: number }],
|
|
82
|
+
showLegend?: boolean
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
STRUCTURE:
|
|
86
|
+
{
|
|
87
|
+
"type": "ChartType",
|
|
88
|
+
"props": { ... }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
EXAMPLE - BAR CHART:
|
|
92
|
+
{
|
|
93
|
+
"type": "BarChart",
|
|
94
|
+
"props": {
|
|
95
|
+
"title": "Sales by Region",
|
|
96
|
+
"data": [
|
|
97
|
+
{ "label": "North", "value": 120000 },
|
|
98
|
+
{ "label": "South", "value": 98000 },
|
|
99
|
+
{ "label": "East", "value": 145000 },
|
|
100
|
+
{ "label": "West", "value": 87000 }
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
EXAMPLE - LINE CHART (multiple series):
|
|
106
|
+
{
|
|
107
|
+
"type": "LineChart",
|
|
108
|
+
"props": {
|
|
109
|
+
"title": "Monthly Revenue",
|
|
110
|
+
"data": [
|
|
111
|
+
{ "label": "Jan", "revenue": 45000, "costs": 32000 },
|
|
112
|
+
{ "label": "Feb", "revenue": 52000, "costs": 35000 },
|
|
113
|
+
{ "label": "Mar", "revenue": 61000, "costs": 38000 }
|
|
114
|
+
],
|
|
115
|
+
"series": ["revenue", "costs"]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
EXAMPLE - PIE CHART:
|
|
120
|
+
{
|
|
121
|
+
"type": "PieChart",
|
|
122
|
+
"props": {
|
|
123
|
+
"title": "Market Share",
|
|
124
|
+
"data": [
|
|
125
|
+
{ "label": "Product A", "value": 45 },
|
|
126
|
+
{ "label": "Product B", "value": 30 },
|
|
127
|
+
{ "label": "Product C", "value": 25 }
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
EXAMPLE - DONUT CHART:
|
|
133
|
+
{
|
|
134
|
+
"type": "DonutChart",
|
|
135
|
+
"props": {
|
|
136
|
+
"title": "Budget Allocation",
|
|
137
|
+
"data": [
|
|
138
|
+
{ "label": "Marketing", "value": 35 },
|
|
139
|
+
{ "label": "Engineering", "value": 45 },
|
|
140
|
+
{ "label": "Operations", "value": 20 }
|
|
141
|
+
],
|
|
142
|
+
"innerValue": "$1.2M",
|
|
143
|
+
"innerLabel": "Total Budget"
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
CHART SELECTION GUIDE:
|
|
148
|
+
- Comparing categories → BarChart
|
|
149
|
+
- Trends over time → LineChart
|
|
150
|
+
- Volume/magnitude over time → AreaChart
|
|
151
|
+
- Part-to-whole (2-6 items) → PieChart or DonutChart
|
|
152
|
+
- Correlation between variables → ScatterChart
|
|
153
|
+
- Multi-dimensional comparison → RadarChart
|
|
154
|
+
|
|
19
155
|
CONTENT GUIDELINES:
|
|
20
156
|
- Outside the code block, describe trends and insights found in the data
|
|
21
157
|
- Do not describe visual properties or technical implementation details
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
The Vega spec will be parsed with JSON.parse() - if it fails, the chart will error. Ensure strict JSON validity.
|
|
25
|
-
|
|
26
|
-
REQUIRED STRUCTURE:
|
|
27
|
-
Every spec needs: "$schema", "width", "height", "data", "scales", "marks". Include "padding" (5 or object) and "axes" for readability.
|
|
28
|
-
Data format:
|
|
29
|
-
{"name": "table", "values": [{"category": "A", "amount": 28}]}
|
|
30
|
-
SCALES - Choose the right type:
|
|
31
|
-
- "band": categorical x-axis (bar charts) - domain from data field, range: "width", padding: 0.1
|
|
32
|
-
- "linear": numerical axes - domain from data field, range: "width"/"height", nice: true
|
|
33
|
-
- "time"/"utc": temporal data
|
|
34
|
-
- "ordinal": for colors use range: {"scheme": "category10"} or range: ["#1f77b4", "#ff7f0e", "#2ca02c"]
|
|
35
|
-
MARKS - Common types:
|
|
36
|
-
- "rect": bar charts (requires x, width, y, y2)
|
|
37
|
-
- "line": time series (requires x, y)
|
|
38
|
-
- "area": filled areas (requires x, y, y2)
|
|
39
|
-
- "symbol": scatter plots (requires x, y)
|
|
40
|
-
CHART PATTERNS:
|
|
41
|
-
Bar: band scale (x) + linear scale (y) + rect marks. Set y2: {"scale": "yscale", "value": 0}
|
|
42
|
-
Line: linear/point scale (x) + linear scale (y) + line mark. Add "interpolate": "monotone"
|
|
43
|
-
Scatter: linear scales (both) + symbol marks
|
|
44
|
-
Area: like line but use area mark with y2: {"scale": "yscale", "value": 0}
|
|
45
|
-
Stacked: add transform [{"type": "stack", "groupby": ["x"], "field": "y"}], use y0/y1 fields
|
|
46
|
-
CRITICAL RULES:
|
|
47
|
-
1. Data must contain at least one record with valid (non-null) values for ALL fields used in scales
|
|
48
|
-
2. ONLY reference fields that actually exist in your data - never use datum.meta, datum.id, or any field not in your values array
|
|
49
|
-
3. Always include y2 for rect/area marks (or bars/areas have zero height)
|
|
50
|
-
4. Use "band" for categories, not "linear"
|
|
51
|
-
5. For position scales use "range": "width" or "height". For color scales NEVER use "range": "category10" - use "range": {"scheme": "category10"} or an array
|
|
52
|
-
6. Match scale/data names exactly between definition and usage
|
|
53
|
-
7. Include "from": {"data": "dataName"} on marks
|
|
54
|
-
8. Add padding to prevent label cutoff
|
|
55
|
-
EXAMPLE: COMPLETE BAR CHART
|
|
56
|
-
{
|
|
57
|
-
"$schema": "https://vega.github.io/schema/vega/v5.json",
|
|
58
|
-
"width": 500,
|
|
59
|
-
"height": 300,
|
|
60
|
-
"padding": 5,
|
|
61
|
-
"data": [
|
|
62
|
-
{
|
|
63
|
-
"name": "table",
|
|
64
|
-
"values": [
|
|
65
|
-
{"category": "A", "amount": 28},
|
|
66
|
-
{"category": "B", "amount": 55},
|
|
67
|
-
{"category": "C", "amount": 43}
|
|
68
|
-
]
|
|
69
|
-
}
|
|
70
|
-
],
|
|
71
|
-
"scales": [
|
|
72
|
-
{
|
|
73
|
-
"name": "xscale",
|
|
74
|
-
"type": "band",
|
|
75
|
-
"domain": {"data": "table", "field": "category"},
|
|
76
|
-
"range": "width",
|
|
77
|
-
"padding": 0.1
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
"name": "yscale",
|
|
81
|
-
"type": "linear",
|
|
82
|
-
"domain": {"data": "table", "field": "amount"},
|
|
83
|
-
"range": "height",
|
|
84
|
-
"nice": true
|
|
85
|
-
}
|
|
86
|
-
],
|
|
87
|
-
"axes": [
|
|
88
|
-
{"scale": "xscale", "orient": "bottom"},
|
|
89
|
-
{"scale": "yscale", "orient": "left", "title": "Amount"}
|
|
90
|
-
],
|
|
91
|
-
"marks": [
|
|
92
|
-
{
|
|
93
|
-
"type": "rect",
|
|
94
|
-
"from": {"data": "table"},
|
|
95
|
-
"encode": {
|
|
96
|
-
"enter": {
|
|
97
|
-
"x": {"scale": "xscale", "field": "category"},
|
|
98
|
-
"width": {"scale": "xscale", "band": 1},
|
|
99
|
-
"y": {"scale": "yscale", "field": "amount"},
|
|
100
|
-
"y2": {"scale": "yscale", "value": 0},
|
|
101
|
-
"fill": {"value": "steelblue"}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
]
|
|
106
|
-
}`,
|
|
158
|
+
- Focus on what the data means, not how it's displayed`,
|
|
107
159
|
Component: ChartRenderer,
|
|
108
160
|
Header: undefined,
|
|
109
161
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
import { FC, useMemo } from 'react'
|
|
5
|
+
import {
|
|
6
|
+
AreaChart as RechartsAreaChart,
|
|
7
|
+
Area,
|
|
8
|
+
XAxis,
|
|
9
|
+
YAxis,
|
|
10
|
+
CartesianGrid,
|
|
11
|
+
Tooltip,
|
|
12
|
+
Legend,
|
|
13
|
+
ResponsiveContainer,
|
|
14
|
+
TooltipProps,
|
|
15
|
+
} from 'recharts'
|
|
16
|
+
|
|
17
|
+
const CustomTooltip = ({ active, payload }: TooltipProps<number, string>) => {
|
|
18
|
+
if (!active || !payload || payload.length === 0) return null
|
|
19
|
+
return (
|
|
20
|
+
<div className="bg-background text-foreground border-border rounded-md border px-2 py-1.5 text-xs shadow-sm">
|
|
21
|
+
{payload.map((entry, index) => (
|
|
22
|
+
<div key={index} className="flex items-center gap-2">
|
|
23
|
+
<span
|
|
24
|
+
className="h-2 w-2 rounded-full"
|
|
25
|
+
style={{ backgroundColor: entry.color }}
|
|
26
|
+
/>
|
|
27
|
+
<span>{entry.name}:</span>
|
|
28
|
+
<span className="font-medium">
|
|
29
|
+
{typeof entry.value === 'number'
|
|
30
|
+
? entry.value.toLocaleString()
|
|
31
|
+
: entry.value}
|
|
32
|
+
</span>
|
|
33
|
+
</div>
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const COLORS = [
|
|
40
|
+
'var(--chart-1)',
|
|
41
|
+
'var(--chart-2)',
|
|
42
|
+
'var(--chart-3)',
|
|
43
|
+
'var(--chart-4)',
|
|
44
|
+
'var(--chart-5)',
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
interface SeriesDataPoint {
|
|
48
|
+
label: string
|
|
49
|
+
[key: string]: string | number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface AreaChartProps {
|
|
53
|
+
title?: string
|
|
54
|
+
data: SeriesDataPoint[]
|
|
55
|
+
series?: string[]
|
|
56
|
+
stacked?: boolean
|
|
57
|
+
showGrid?: boolean
|
|
58
|
+
showLegend?: boolean
|
|
59
|
+
className?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const AreaChart: FC<AreaChartProps> = ({
|
|
63
|
+
title,
|
|
64
|
+
data,
|
|
65
|
+
series,
|
|
66
|
+
stacked = false,
|
|
67
|
+
showGrid = true,
|
|
68
|
+
showLegend = true,
|
|
69
|
+
className,
|
|
70
|
+
}) => {
|
|
71
|
+
// Auto-detect series from data keys if not provided
|
|
72
|
+
const seriesKeys = useMemo(() => {
|
|
73
|
+
if (series && series.length > 0) return series
|
|
74
|
+
if (data.length === 0) return []
|
|
75
|
+
const keys = Object.keys(data[0]).filter((k) => k !== 'label')
|
|
76
|
+
return keys
|
|
77
|
+
}, [data, series])
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className={cn('flex flex-col gap-2', className)}>
|
|
81
|
+
{title && (
|
|
82
|
+
<h3 className="text-foreground text-sm font-medium">{title}</h3>
|
|
83
|
+
)}
|
|
84
|
+
<div className="h-[250px] w-full">
|
|
85
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
86
|
+
<RechartsAreaChart
|
|
87
|
+
data={data}
|
|
88
|
+
margin={{ top: 10, right: 10, left: 10, bottom: 10 }}
|
|
89
|
+
>
|
|
90
|
+
{showGrid && (
|
|
91
|
+
<CartesianGrid
|
|
92
|
+
strokeDasharray="3 3"
|
|
93
|
+
className="stroke-muted/30"
|
|
94
|
+
/>
|
|
95
|
+
)}
|
|
96
|
+
<XAxis
|
|
97
|
+
dataKey="label"
|
|
98
|
+
tick={{ fill: 'var(--foreground)', fontSize: 12 }}
|
|
99
|
+
axisLine={{ stroke: 'var(--border)' }}
|
|
100
|
+
tickLine={{ stroke: 'var(--border)' }}
|
|
101
|
+
/>
|
|
102
|
+
<YAxis
|
|
103
|
+
tick={{ fill: 'var(--foreground)', fontSize: 12 }}
|
|
104
|
+
axisLine={{ stroke: 'var(--border)' }}
|
|
105
|
+
tickLine={{ stroke: 'var(--border)' }}
|
|
106
|
+
/>
|
|
107
|
+
<Tooltip content={<CustomTooltip />} />
|
|
108
|
+
{showLegend && seriesKeys.length > 1 && (
|
|
109
|
+
<Legend
|
|
110
|
+
wrapperStyle={{ color: 'var(--foreground)' }}
|
|
111
|
+
formatter={(value) => (
|
|
112
|
+
<span style={{ color: 'var(--foreground)' }}>{value}</span>
|
|
113
|
+
)}
|
|
114
|
+
/>
|
|
115
|
+
)}
|
|
116
|
+
{seriesKeys.map((key, index) => (
|
|
117
|
+
<Area
|
|
118
|
+
key={key}
|
|
119
|
+
type="monotone"
|
|
120
|
+
dataKey={key}
|
|
121
|
+
stackId={stacked ? 'stack' : undefined}
|
|
122
|
+
stroke={COLORS[index % COLORS.length]}
|
|
123
|
+
fill={COLORS[index % COLORS.length]}
|
|
124
|
+
fillOpacity={0.3}
|
|
125
|
+
isAnimationActive={false}
|
|
126
|
+
/>
|
|
127
|
+
))}
|
|
128
|
+
</RechartsAreaChart>
|
|
129
|
+
</ResponsiveContainer>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
import { FC } from 'react'
|
|
5
|
+
import {
|
|
6
|
+
BarChart as RechartsBarChart,
|
|
7
|
+
Bar,
|
|
8
|
+
XAxis,
|
|
9
|
+
YAxis,
|
|
10
|
+
CartesianGrid,
|
|
11
|
+
Tooltip,
|
|
12
|
+
Legend,
|
|
13
|
+
Cell,
|
|
14
|
+
ResponsiveContainer,
|
|
15
|
+
TooltipProps,
|
|
16
|
+
} from 'recharts'
|
|
17
|
+
|
|
18
|
+
const CustomTooltip = ({ active, payload }: TooltipProps<number, string>) => {
|
|
19
|
+
if (!active || !payload || payload.length === 0) return null
|
|
20
|
+
const value = payload[0]?.value
|
|
21
|
+
return (
|
|
22
|
+
<div className="bg-background text-foreground border-border rounded-md border px-2 py-1 text-xs shadow-sm">
|
|
23
|
+
{typeof value === 'number' ? value.toLocaleString() : value}
|
|
24
|
+
</div>
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const COLORS = [
|
|
29
|
+
'var(--chart-1)',
|
|
30
|
+
'var(--chart-2)',
|
|
31
|
+
'var(--chart-3)',
|
|
32
|
+
'var(--chart-4)',
|
|
33
|
+
'var(--chart-5)',
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
interface DataPoint {
|
|
37
|
+
label: string
|
|
38
|
+
value: number
|
|
39
|
+
color?: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface BarChartProps {
|
|
43
|
+
title?: string
|
|
44
|
+
data: DataPoint[]
|
|
45
|
+
layout?: 'vertical' | 'horizontal'
|
|
46
|
+
showGrid?: boolean
|
|
47
|
+
showLegend?: boolean
|
|
48
|
+
className?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const BarChart: FC<BarChartProps> = ({
|
|
52
|
+
title,
|
|
53
|
+
data,
|
|
54
|
+
layout = 'vertical',
|
|
55
|
+
showGrid = true,
|
|
56
|
+
showLegend = false,
|
|
57
|
+
className,
|
|
58
|
+
}) => {
|
|
59
|
+
const isHorizontal = layout === 'horizontal'
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className={cn('flex flex-col gap-2', className)}>
|
|
63
|
+
{title && (
|
|
64
|
+
<h3 className="text-foreground text-sm font-medium">{title}</h3>
|
|
65
|
+
)}
|
|
66
|
+
<div className="h-[250px] w-full">
|
|
67
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
68
|
+
<RechartsBarChart
|
|
69
|
+
data={data}
|
|
70
|
+
layout={isHorizontal ? 'vertical' : 'horizontal'}
|
|
71
|
+
margin={{ top: 10, right: 10, left: 10, bottom: 10 }}
|
|
72
|
+
>
|
|
73
|
+
{showGrid && (
|
|
74
|
+
<CartesianGrid
|
|
75
|
+
strokeDasharray="3 3"
|
|
76
|
+
className="stroke-muted/30"
|
|
77
|
+
/>
|
|
78
|
+
)}
|
|
79
|
+
{isHorizontal ? (
|
|
80
|
+
<>
|
|
81
|
+
<XAxis
|
|
82
|
+
type="number"
|
|
83
|
+
tick={{ fill: 'var(--foreground)', fontSize: 12 }}
|
|
84
|
+
axisLine={{ stroke: 'var(--border)' }}
|
|
85
|
+
tickLine={{ stroke: 'var(--border)' }}
|
|
86
|
+
/>
|
|
87
|
+
<YAxis
|
|
88
|
+
dataKey="label"
|
|
89
|
+
type="category"
|
|
90
|
+
width={80}
|
|
91
|
+
tick={{ fill: 'var(--foreground)', fontSize: 12 }}
|
|
92
|
+
axisLine={{ stroke: 'var(--border)' }}
|
|
93
|
+
tickLine={{ stroke: 'var(--border)' }}
|
|
94
|
+
/>
|
|
95
|
+
</>
|
|
96
|
+
) : (
|
|
97
|
+
<>
|
|
98
|
+
<XAxis
|
|
99
|
+
dataKey="label"
|
|
100
|
+
tick={{ fill: 'var(--foreground)', fontSize: 12 }}
|
|
101
|
+
axisLine={{ stroke: 'var(--border)' }}
|
|
102
|
+
tickLine={{ stroke: 'var(--border)' }}
|
|
103
|
+
/>
|
|
104
|
+
<YAxis
|
|
105
|
+
tick={{ fill: 'var(--foreground)', fontSize: 12 }}
|
|
106
|
+
axisLine={{ stroke: 'var(--border)' }}
|
|
107
|
+
tickLine={{ stroke: 'var(--border)' }}
|
|
108
|
+
/>
|
|
109
|
+
</>
|
|
110
|
+
)}
|
|
111
|
+
<Tooltip content={<CustomTooltip />} />
|
|
112
|
+
{showLegend && (
|
|
113
|
+
<Legend
|
|
114
|
+
wrapperStyle={{ color: 'var(--foreground)' }}
|
|
115
|
+
formatter={(value) => (
|
|
116
|
+
<span style={{ color: 'var(--foreground)' }}>{value}</span>
|
|
117
|
+
)}
|
|
118
|
+
/>
|
|
119
|
+
)}
|
|
120
|
+
<Bar
|
|
121
|
+
dataKey="value"
|
|
122
|
+
radius={[4, 4, 0, 0]}
|
|
123
|
+
isAnimationActive={false}
|
|
124
|
+
>
|
|
125
|
+
{data.map((entry, index) => (
|
|
126
|
+
<Cell
|
|
127
|
+
key={`cell-${index}`}
|
|
128
|
+
fill={entry.color || COLORS[index % COLORS.length]}
|
|
129
|
+
/>
|
|
130
|
+
))}
|
|
131
|
+
</Bar>
|
|
132
|
+
</RechartsBarChart>
|
|
133
|
+
</ResponsiveContainer>
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
)
|
|
137
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { cn } from '@/lib/utils'
|
|
4
|
+
import { FC } from 'react'
|
|
5
|
+
import {
|
|
6
|
+
PieChart as RechartsPieChart,
|
|
7
|
+
Pie,
|
|
8
|
+
Cell,
|
|
9
|
+
Tooltip,
|
|
10
|
+
Legend,
|
|
11
|
+
ResponsiveContainer,
|
|
12
|
+
TooltipProps,
|
|
13
|
+
} from 'recharts'
|
|
14
|
+
|
|
15
|
+
const CustomTooltip = ({ active, payload }: TooltipProps<number, string>) => {
|
|
16
|
+
if (!active || !payload || payload.length === 0) return null
|
|
17
|
+
const entry = payload[0]
|
|
18
|
+
return (
|
|
19
|
+
<div className="bg-background text-foreground border-border rounded-md border px-2 py-1 text-xs shadow-sm">
|
|
20
|
+
<span className="font-medium">
|
|
21
|
+
{typeof entry?.value === 'number'
|
|
22
|
+
? entry.value.toLocaleString()
|
|
23
|
+
: entry?.value}
|
|
24
|
+
</span>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const COLORS = [
|
|
30
|
+
'var(--chart-1)',
|
|
31
|
+
'var(--chart-2)',
|
|
32
|
+
'var(--chart-3)',
|
|
33
|
+
'var(--chart-4)',
|
|
34
|
+
'var(--chart-5)',
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
interface DataPoint {
|
|
38
|
+
label: string
|
|
39
|
+
value: number
|
|
40
|
+
color?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface DonutChartProps {
|
|
44
|
+
title?: string
|
|
45
|
+
data: DataPoint[]
|
|
46
|
+
showLabels?: boolean
|
|
47
|
+
showLegend?: boolean
|
|
48
|
+
innerLabel?: string
|
|
49
|
+
innerValue?: string | number
|
|
50
|
+
className?: string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const DonutChart: FC<DonutChartProps> = ({
|
|
54
|
+
title,
|
|
55
|
+
data,
|
|
56
|
+
showLabels = false,
|
|
57
|
+
showLegend = true,
|
|
58
|
+
innerLabel,
|
|
59
|
+
innerValue,
|
|
60
|
+
className,
|
|
61
|
+
}) => {
|
|
62
|
+
// Transform data to use 'name' for Recharts
|
|
63
|
+
const chartData = data.map((d) => ({
|
|
64
|
+
name: d.label,
|
|
65
|
+
value: d.value,
|
|
66
|
+
color: d.color,
|
|
67
|
+
}))
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div className={cn('flex flex-col gap-2', className)}>
|
|
71
|
+
{title && (
|
|
72
|
+
<h3 className="text-foreground text-sm font-medium">{title}</h3>
|
|
73
|
+
)}
|
|
74
|
+
<div className="relative h-[320px] w-full">
|
|
75
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
76
|
+
<RechartsPieChart
|
|
77
|
+
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
|
|
78
|
+
>
|
|
79
|
+
<Pie
|
|
80
|
+
data={chartData}
|
|
81
|
+
cx="50%"
|
|
82
|
+
cy="45%"
|
|
83
|
+
innerRadius={50}
|
|
84
|
+
outerRadius={80}
|
|
85
|
+
paddingAngle={2}
|
|
86
|
+
dataKey="value"
|
|
87
|
+
label={
|
|
88
|
+
showLabels
|
|
89
|
+
? ({
|
|
90
|
+
name,
|
|
91
|
+
percent,
|
|
92
|
+
cx,
|
|
93
|
+
cy,
|
|
94
|
+
midAngle,
|
|
95
|
+
outerRadius,
|
|
96
|
+
}: {
|
|
97
|
+
name?: string
|
|
98
|
+
percent?: number
|
|
99
|
+
cx?: number
|
|
100
|
+
cy?: number
|
|
101
|
+
midAngle?: number
|
|
102
|
+
outerRadius?: number
|
|
103
|
+
}) => {
|
|
104
|
+
const RADIAN = Math.PI / 180
|
|
105
|
+
const radius = (outerRadius ?? 80) + 25
|
|
106
|
+
const x =
|
|
107
|
+
(cx ?? 0) +
|
|
108
|
+
radius * Math.cos(-((midAngle ?? 0) * RADIAN))
|
|
109
|
+
const y =
|
|
110
|
+
(cy ?? 0) +
|
|
111
|
+
radius * Math.sin(-((midAngle ?? 0) * RADIAN))
|
|
112
|
+
return (
|
|
113
|
+
<text
|
|
114
|
+
x={x}
|
|
115
|
+
y={y}
|
|
116
|
+
fill="var(--foreground)"
|
|
117
|
+
textAnchor={x > (cx ?? 0) ? 'start' : 'end'}
|
|
118
|
+
dominantBaseline="central"
|
|
119
|
+
fontSize={12}
|
|
120
|
+
>
|
|
121
|
+
{`${name ?? ''} (${((percent ?? 0) * 100).toFixed(0)}%)`}
|
|
122
|
+
</text>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
: undefined
|
|
126
|
+
}
|
|
127
|
+
labelLine={showLabels}
|
|
128
|
+
isAnimationActive={false}
|
|
129
|
+
>
|
|
130
|
+
{chartData.map((entry, index) => (
|
|
131
|
+
<Cell
|
|
132
|
+
key={`cell-${index}`}
|
|
133
|
+
fill={entry.color || COLORS[index % COLORS.length]}
|
|
134
|
+
/>
|
|
135
|
+
))}
|
|
136
|
+
</Pie>
|
|
137
|
+
<Tooltip content={<CustomTooltip />} />
|
|
138
|
+
{showLegend && (
|
|
139
|
+
<Legend
|
|
140
|
+
verticalAlign="bottom"
|
|
141
|
+
wrapperStyle={{ color: 'var(--foreground)', paddingTop: 20 }}
|
|
142
|
+
formatter={(value) => (
|
|
143
|
+
<span style={{ color: 'var(--foreground)' }}>{value}</span>
|
|
144
|
+
)}
|
|
145
|
+
/>
|
|
146
|
+
)}
|
|
147
|
+
</RechartsPieChart>
|
|
148
|
+
</ResponsiveContainer>
|
|
149
|
+
{/* Center label - positioned at 45% from top to match pie cy */}
|
|
150
|
+
{(innerLabel !== undefined || innerValue !== undefined) && (
|
|
151
|
+
<div className="pointer-events-none absolute inset-x-0 top-[45%] flex -translate-y-1/2 flex-col items-center">
|
|
152
|
+
{innerValue !== undefined && (
|
|
153
|
+
<span className="text-foreground text-2xl font-bold">
|
|
154
|
+
{innerValue}
|
|
155
|
+
</span>
|
|
156
|
+
)}
|
|
157
|
+
{innerLabel && (
|
|
158
|
+
<span className="text-muted-foreground text-xs">
|
|
159
|
+
{innerLabel}
|
|
160
|
+
</span>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { BarChart, type BarChartProps } from './bar-chart'
|
|
2
|
+
export { LineChart, type LineChartProps } from './line-chart'
|
|
3
|
+
export { AreaChart, type AreaChartProps } from './area-chart'
|
|
4
|
+
export { PieChart, type PieChartProps } from './pie-chart'
|
|
5
|
+
export { DonutChart, type DonutChartProps } from './donut-chart'
|
|
6
|
+
export { ScatterChart, type ScatterChartProps } from './scatter-chart'
|
|
7
|
+
export { RadarChart, type RadarChartProps } from './radar-chart'
|