@turtleclub/ui 0.7.0-beta.3 → 0.7.0-beta.30
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/.turbo/turbo-build.log +143 -132
- package/CHANGELOG.md +152 -0
- package/dist/index.cjs +76 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +36068 -17674
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/types/components/charts/area-chart.d.ts +108 -0
- package/dist/types/components/charts/area-chart.d.ts.map +1 -0
- package/dist/types/components/charts/bar-chart.d.ts +110 -0
- package/dist/types/components/charts/bar-chart.d.ts.map +1 -0
- package/dist/types/components/charts/index.d.ts +5 -0
- package/dist/types/components/charts/index.d.ts.map +1 -0
- package/dist/types/components/charts/pie-chart.d.ts +94 -0
- package/dist/types/components/charts/pie-chart.d.ts.map +1 -0
- package/dist/types/components/charts/radial-chart.d.ts +151 -0
- package/dist/types/components/charts/radial-chart.d.ts.map +1 -0
- package/dist/types/components/features/data-table/data-table.d.ts +7 -4
- package/dist/types/components/features/data-table/data-table.d.ts.map +1 -1
- package/dist/types/components/features/data-table/sort-dropdown.d.ts.map +1 -1
- package/dist/types/components/features/search-bar.d.ts +1 -0
- package/dist/types/components/features/search-bar.d.ts.map +1 -1
- package/dist/types/components/molecules/swap-input.d.ts +3 -0
- package/dist/types/components/molecules/swap-input.d.ts.map +1 -1
- package/dist/types/components/molecules/token-selector.d.ts +2 -1
- package/dist/types/components/molecules/token-selector.d.ts.map +1 -1
- package/dist/types/components/ui/avatar.d.ts +2 -2
- package/dist/types/components/ui/avatar.d.ts.map +1 -1
- package/dist/types/components/ui/chart.d.ts +18 -4
- package/dist/types/components/ui/chart.d.ts.map +1 -1
- package/dist/types/components/ui/combobox.d.ts +21 -0
- package/dist/types/components/ui/combobox.d.ts.map +1 -1
- package/dist/types/components/ui/dialog.d.ts.map +1 -1
- package/dist/types/components/ui/dropdown.d.ts +2 -1
- package/dist/types/components/ui/dropdown.d.ts.map +1 -1
- package/dist/types/components/ui/index.d.ts +1 -0
- package/dist/types/components/ui/index.d.ts.map +1 -1
- package/dist/types/components/ui/multi-select.d.ts.map +1 -1
- package/dist/types/components/ui/segment-control.d.ts +1 -0
- package/dist/types/components/ui/segment-control.d.ts.map +1 -1
- package/dist/types/components/ui/slider.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/components/charts/QUICK_REFERENCE.md +323 -0
- package/src/components/charts/README.md +658 -0
- package/src/components/charts/RECHARTS_FEATURES.md +458 -0
- package/src/components/charts/area-chart.tsx +248 -0
- package/src/components/charts/bar-chart.tsx +362 -0
- package/src/components/charts/index.ts +4 -0
- package/src/components/charts/pie-chart.tsx +277 -0
- package/src/components/charts/radial-chart.tsx +312 -0
- package/src/components/features/data-table/data-table.tsx +136 -125
- package/src/components/features/data-table/sort-dropdown.tsx +8 -11
- package/src/components/features/search-bar.tsx +6 -1
- package/src/components/molecules/swap-input.tsx +44 -30
- package/src/components/molecules/token-selector.tsx +10 -1
- package/src/components/ui/avatar.tsx +8 -15
- package/src/components/ui/chart.tsx +100 -109
- package/src/components/ui/combobox.tsx +150 -137
- package/src/components/ui/dialog.tsx +9 -23
- package/src/components/ui/dropdown.tsx +3 -1
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/multi-select.tsx +325 -307
- package/src/components/ui/segment-control.tsx +7 -2
- package/src/components/ui/slider.tsx +6 -11
- package/src/index.ts +1 -0
- package/src/styles/globals.css +4 -0
- package/src/styles/themes/semantic.css +26 -56
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
Bar,
|
|
5
|
+
BarChart as RechartsBarChart,
|
|
6
|
+
CartesianGrid,
|
|
7
|
+
XAxis,
|
|
8
|
+
YAxis,
|
|
9
|
+
Cell,
|
|
10
|
+
} from "recharts";
|
|
11
|
+
import {
|
|
12
|
+
ChartConfig,
|
|
13
|
+
ChartContainer,
|
|
14
|
+
ChartLegend,
|
|
15
|
+
ChartLegendContent,
|
|
16
|
+
ChartTooltip,
|
|
17
|
+
ChartTooltipContent,
|
|
18
|
+
} from "../ui/chart";
|
|
19
|
+
import { cn } from "@/lib/utils";
|
|
20
|
+
|
|
21
|
+
export interface BarChartData {
|
|
22
|
+
[key: string]: string | number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface BarChartProps {
|
|
26
|
+
/**
|
|
27
|
+
* Array of data objects to be displayed in the chart
|
|
28
|
+
*/
|
|
29
|
+
data: BarChartData[];
|
|
30
|
+
/**
|
|
31
|
+
* Configuration for chart styling and labels
|
|
32
|
+
*/
|
|
33
|
+
config: ChartConfig;
|
|
34
|
+
/**
|
|
35
|
+
* Key from data objects to use for X-axis categories
|
|
36
|
+
*/
|
|
37
|
+
categoryKey: string;
|
|
38
|
+
/**
|
|
39
|
+
* Array of keys from data objects to display as bars
|
|
40
|
+
*/
|
|
41
|
+
dataKeys: string[];
|
|
42
|
+
/**
|
|
43
|
+
* Chart layout orientation
|
|
44
|
+
* @default "horizontal"
|
|
45
|
+
*/
|
|
46
|
+
layout?: "horizontal" | "vertical";
|
|
47
|
+
/**
|
|
48
|
+
* Show grid lines. Can be boolean for both axes or object for granular control
|
|
49
|
+
* @default true
|
|
50
|
+
*/
|
|
51
|
+
showGrid?: boolean | { horizontal?: boolean; vertical?: boolean };
|
|
52
|
+
/**
|
|
53
|
+
* Show legend
|
|
54
|
+
* @default false
|
|
55
|
+
*/
|
|
56
|
+
showLegend?: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Show tooltip on hover
|
|
59
|
+
* @default true
|
|
60
|
+
*/
|
|
61
|
+
showTooltip?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Stack bars on top of each other
|
|
64
|
+
* @default false
|
|
65
|
+
*/
|
|
66
|
+
stacked?: boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Border radius for bars
|
|
69
|
+
* @default 2
|
|
70
|
+
*/
|
|
71
|
+
barRadius?: number;
|
|
72
|
+
/**
|
|
73
|
+
* Enable gradient fill for bars with directional support based on value
|
|
74
|
+
* Vertical bars: positive (bottom-to-top), negative (top-to-bottom)
|
|
75
|
+
* Horizontal bars: positive (left-to-right), negative (right-to-left)
|
|
76
|
+
* @default false
|
|
77
|
+
*/
|
|
78
|
+
gradient?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Function to conditionally determine bar color based on data entry
|
|
81
|
+
* Receives the data entry and index, returns a color string or undefined to use default
|
|
82
|
+
* Can return gradient URLs when used with gradient definitions
|
|
83
|
+
* @example (entry) => entry.value > 100 ? 'hsl(var(--destructive))' : undefined
|
|
84
|
+
*/
|
|
85
|
+
getBarColor?: (
|
|
86
|
+
entry: BarChartData,
|
|
87
|
+
index: number,
|
|
88
|
+
dataKey: string,
|
|
89
|
+
) => string | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* Custom className for the container
|
|
92
|
+
*/
|
|
93
|
+
className?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Chart height aspect ratio
|
|
96
|
+
* @default "aspect-video"
|
|
97
|
+
*/
|
|
98
|
+
aspectRatio?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Make chart grow to fill parent container (ignores aspectRatio if true)
|
|
101
|
+
* @default false
|
|
102
|
+
*/
|
|
103
|
+
grow?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* Custom formatter function for Y-axis tick labels
|
|
106
|
+
* @example (value) => `$${value.toLocaleString()}`
|
|
107
|
+
* @example (value) => formatCompactNumber(value)
|
|
108
|
+
*/
|
|
109
|
+
yAxisFormatter?: (value: number) => string;
|
|
110
|
+
/**
|
|
111
|
+
* Custom formatter function for X-axis tick labels
|
|
112
|
+
* @example (value) => new Date(value).toLocaleDateString()
|
|
113
|
+
* @example (value) => formatCompactNumber(value)
|
|
114
|
+
*/
|
|
115
|
+
xAxisFormatter?: (value: any) => string;
|
|
116
|
+
/** X-axis tick interval strategy / step (passed to Recharts XAxis `interval`)
|
|
117
|
+
* Useful when you need predictable tick density instead of Recharts auto-skip.
|
|
118
|
+
*/
|
|
119
|
+
xAxisInterval?:
|
|
120
|
+
| number
|
|
121
|
+
| "preserveStart"
|
|
122
|
+
| "preserveEnd"
|
|
123
|
+
| "preserveStartEnd"
|
|
124
|
+
| "equidistantPreserveStart";
|
|
125
|
+
/**
|
|
126
|
+
* Minimum gap between X-axis ticks in px (passed to Recharts XAxis `minTickGap`)
|
|
127
|
+
*/
|
|
128
|
+
xAxisMinTickGap?: number;
|
|
129
|
+
/**
|
|
130
|
+
* Custom formatter function for tooltip values
|
|
131
|
+
* @example (value, name, item, index, payload) => `$${value.toLocaleString()}`
|
|
132
|
+
*/
|
|
133
|
+
tooltipFormatter?: (
|
|
134
|
+
value: any,
|
|
135
|
+
name: any,
|
|
136
|
+
item: any,
|
|
137
|
+
index: number,
|
|
138
|
+
payload: any,
|
|
139
|
+
) => React.ReactNode;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function BarChart({
|
|
143
|
+
data,
|
|
144
|
+
config,
|
|
145
|
+
categoryKey,
|
|
146
|
+
dataKeys,
|
|
147
|
+
layout = "horizontal",
|
|
148
|
+
showGrid = true,
|
|
149
|
+
showLegend = false,
|
|
150
|
+
showTooltip = true,
|
|
151
|
+
stacked = false,
|
|
152
|
+
barRadius = 5,
|
|
153
|
+
gradient = false,
|
|
154
|
+
getBarColor,
|
|
155
|
+
className,
|
|
156
|
+
aspectRatio = "aspect-video",
|
|
157
|
+
grow = false,
|
|
158
|
+
yAxisFormatter,
|
|
159
|
+
xAxisFormatter,
|
|
160
|
+
xAxisInterval,
|
|
161
|
+
xAxisMinTickGap,
|
|
162
|
+
tooltipFormatter,
|
|
163
|
+
}: BarChartProps) {
|
|
164
|
+
const isVertical = layout === "vertical";
|
|
165
|
+
const stackId = stacked ? "stack" : undefined;
|
|
166
|
+
|
|
167
|
+
// Parse grid configuration
|
|
168
|
+
const gridConfig =
|
|
169
|
+
typeof showGrid === "boolean"
|
|
170
|
+
? { horizontal: showGrid, vertical: showGrid }
|
|
171
|
+
: {
|
|
172
|
+
horizontal: showGrid?.horizontal ?? false,
|
|
173
|
+
vertical: showGrid?.vertical ?? false,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// Internal gradient color function that handles directional gradients
|
|
177
|
+
const internalGetBarColor = gradient
|
|
178
|
+
? (entry: BarChartData, index: number, dataKey: string) => {
|
|
179
|
+
const value = entry[dataKey] as number;
|
|
180
|
+
const isNegative = value < 0;
|
|
181
|
+
const gradientId = `gradient-bar-${dataKey}-${isNegative ? "neg" : "pos"}`;
|
|
182
|
+
|
|
183
|
+
// If user provided custom color function, use it
|
|
184
|
+
if (getBarColor) {
|
|
185
|
+
const customColor = getBarColor(entry, index, dataKey);
|
|
186
|
+
if (customColor) return customColor;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return `url(#${gradientId})`;
|
|
190
|
+
}
|
|
191
|
+
: getBarColor;
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<ChartContainer
|
|
195
|
+
config={config}
|
|
196
|
+
className={cn(grow ? "h-full w-full" : aspectRatio, className)}
|
|
197
|
+
>
|
|
198
|
+
<RechartsBarChart
|
|
199
|
+
accessibilityLayer
|
|
200
|
+
data={data}
|
|
201
|
+
layout={layout}
|
|
202
|
+
margin={{
|
|
203
|
+
top: 10,
|
|
204
|
+
right: 10,
|
|
205
|
+
bottom: 10,
|
|
206
|
+
left: 10,
|
|
207
|
+
}}
|
|
208
|
+
>
|
|
209
|
+
{(gridConfig.horizontal || gridConfig.vertical) && (
|
|
210
|
+
<CartesianGrid
|
|
211
|
+
strokeDasharray="3 3"
|
|
212
|
+
horizontal={gridConfig.horizontal}
|
|
213
|
+
vertical={gridConfig.vertical}
|
|
214
|
+
/>
|
|
215
|
+
)}
|
|
216
|
+
|
|
217
|
+
{isVertical ? (
|
|
218
|
+
<>
|
|
219
|
+
<YAxis
|
|
220
|
+
dataKey={categoryKey}
|
|
221
|
+
type="category"
|
|
222
|
+
tickLine={false}
|
|
223
|
+
tickMargin={10}
|
|
224
|
+
axisLine={false}
|
|
225
|
+
tickFormatter={(value) => {
|
|
226
|
+
if (yAxisFormatter) {
|
|
227
|
+
return yAxisFormatter(value);
|
|
228
|
+
}
|
|
229
|
+
return (
|
|
230
|
+
config[value as keyof typeof config]?.label?.toString() ||
|
|
231
|
+
value
|
|
232
|
+
);
|
|
233
|
+
}}
|
|
234
|
+
width={yAxisFormatter ? 50 : undefined}
|
|
235
|
+
/>
|
|
236
|
+
<XAxis
|
|
237
|
+
type="number"
|
|
238
|
+
tickLine={false}
|
|
239
|
+
axisLine={false}
|
|
240
|
+
interval={xAxisInterval}
|
|
241
|
+
minTickGap={xAxisMinTickGap}
|
|
242
|
+
tickFormatter={(value) =>
|
|
243
|
+
xAxisFormatter ? xAxisFormatter(value) : value
|
|
244
|
+
}
|
|
245
|
+
/>
|
|
246
|
+
</>
|
|
247
|
+
) : (
|
|
248
|
+
<>
|
|
249
|
+
<XAxis
|
|
250
|
+
dataKey={categoryKey}
|
|
251
|
+
tickLine={false}
|
|
252
|
+
tickMargin={10}
|
|
253
|
+
axisLine={false}
|
|
254
|
+
interval={xAxisInterval}
|
|
255
|
+
minTickGap={xAxisMinTickGap}
|
|
256
|
+
tickFormatter={(value) => {
|
|
257
|
+
if (xAxisFormatter) {
|
|
258
|
+
return xAxisFormatter(value);
|
|
259
|
+
}
|
|
260
|
+
return (
|
|
261
|
+
config[value as keyof typeof config]?.label?.toString() ||
|
|
262
|
+
value
|
|
263
|
+
);
|
|
264
|
+
}}
|
|
265
|
+
/>
|
|
266
|
+
<YAxis
|
|
267
|
+
tickLine={false}
|
|
268
|
+
axisLine={false}
|
|
269
|
+
tickFormatter={(value) =>
|
|
270
|
+
yAxisFormatter ? yAxisFormatter(value) : value
|
|
271
|
+
}
|
|
272
|
+
width={yAxisFormatter ? 50 : undefined}
|
|
273
|
+
/>
|
|
274
|
+
</>
|
|
275
|
+
)}
|
|
276
|
+
|
|
277
|
+
{showTooltip && (
|
|
278
|
+
<ChartTooltip
|
|
279
|
+
cursor={false}
|
|
280
|
+
content={
|
|
281
|
+
<ChartTooltipContent
|
|
282
|
+
indicator={stacked ? "line" : "dot"}
|
|
283
|
+
formatter={tooltipFormatter}
|
|
284
|
+
/>
|
|
285
|
+
}
|
|
286
|
+
/>
|
|
287
|
+
)}
|
|
288
|
+
|
|
289
|
+
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
|
290
|
+
|
|
291
|
+
<defs>
|
|
292
|
+
{gradient &&
|
|
293
|
+
dataKeys.flatMap((key) => [
|
|
294
|
+
// Positive gradient
|
|
295
|
+
<linearGradient
|
|
296
|
+
key={`${key}-pos`}
|
|
297
|
+
id={`gradient-bar-${key}-pos`}
|
|
298
|
+
x1={isVertical ? "0" : "0"}
|
|
299
|
+
y1={isVertical ? "0" : "1"}
|
|
300
|
+
x2={isVertical ? "1" : "0"}
|
|
301
|
+
y2={isVertical ? "0" : "0"}
|
|
302
|
+
>
|
|
303
|
+
<stop
|
|
304
|
+
offset="0%"
|
|
305
|
+
stopColor={`var(--color-${key})`}
|
|
306
|
+
stopOpacity={0.1}
|
|
307
|
+
/>
|
|
308
|
+
<stop
|
|
309
|
+
offset="100%"
|
|
310
|
+
stopColor={`var(--color-${key})`}
|
|
311
|
+
stopOpacity={1}
|
|
312
|
+
/>
|
|
313
|
+
</linearGradient>,
|
|
314
|
+
// Negative gradient (reversed direction)
|
|
315
|
+
<linearGradient
|
|
316
|
+
key={`${key}-neg`}
|
|
317
|
+
id={`gradient-bar-${key}-neg`}
|
|
318
|
+
x1={isVertical ? "1" : "0"}
|
|
319
|
+
y1={isVertical ? "0" : "0"}
|
|
320
|
+
x2={isVertical ? "0" : "0"}
|
|
321
|
+
y2={isVertical ? "0" : "1"}
|
|
322
|
+
>
|
|
323
|
+
<stop
|
|
324
|
+
offset="0%"
|
|
325
|
+
stopColor={`var(--color-${key})`}
|
|
326
|
+
stopOpacity={0.1}
|
|
327
|
+
/>
|
|
328
|
+
<stop
|
|
329
|
+
offset="100%"
|
|
330
|
+
stopColor={`var(--color-${key})`}
|
|
331
|
+
stopOpacity={1}
|
|
332
|
+
/>
|
|
333
|
+
</linearGradient>,
|
|
334
|
+
])}
|
|
335
|
+
</defs>
|
|
336
|
+
|
|
337
|
+
{dataKeys.map((key) => (
|
|
338
|
+
<Bar
|
|
339
|
+
key={key}
|
|
340
|
+
dataKey={key}
|
|
341
|
+
fill={`var(--color-${key})`}
|
|
342
|
+
radius={barRadius}
|
|
343
|
+
stackId={stackId}
|
|
344
|
+
>
|
|
345
|
+
{(gradient || getBarColor) &&
|
|
346
|
+
data.map((entry, index) => {
|
|
347
|
+
const fillColor = internalGetBarColor
|
|
348
|
+
? internalGetBarColor(entry, index, key)
|
|
349
|
+
: `var(--color-${key})`;
|
|
350
|
+
return (
|
|
351
|
+
<Cell
|
|
352
|
+
key={`cell-${key}-${index}`}
|
|
353
|
+
fill={fillColor || `var(--color-${key})`}
|
|
354
|
+
/>
|
|
355
|
+
);
|
|
356
|
+
})}
|
|
357
|
+
</Bar>
|
|
358
|
+
))}
|
|
359
|
+
</RechartsBarChart>
|
|
360
|
+
</ChartContainer>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Pie, PieChart as RechartsPieChart, Cell, Sector, PolarAngleAxis } from "recharts";
|
|
4
|
+
import {
|
|
5
|
+
ChartConfig,
|
|
6
|
+
ChartContainer,
|
|
7
|
+
ChartLegend,
|
|
8
|
+
ChartLegendContent,
|
|
9
|
+
ChartTooltip,
|
|
10
|
+
ChartTooltipContent,
|
|
11
|
+
} from "../ui/chart";
|
|
12
|
+
|
|
13
|
+
export interface PieChartData {
|
|
14
|
+
[key: string]: string | number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface PieChartProps {
|
|
18
|
+
/**
|
|
19
|
+
* Array of data objects to be displayed in the chart
|
|
20
|
+
*/
|
|
21
|
+
data: PieChartData[];
|
|
22
|
+
/**
|
|
23
|
+
* Configuration for chart styling and labels
|
|
24
|
+
*/
|
|
25
|
+
config: ChartConfig;
|
|
26
|
+
/**
|
|
27
|
+
* Key from data objects to use for segment names
|
|
28
|
+
*/
|
|
29
|
+
nameKey: string;
|
|
30
|
+
/**
|
|
31
|
+
* Key from data objects to use for segment values
|
|
32
|
+
*/
|
|
33
|
+
dataKey: string;
|
|
34
|
+
/**
|
|
35
|
+
* Show legend
|
|
36
|
+
* @default true
|
|
37
|
+
*/
|
|
38
|
+
showLegend?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Show tooltip on hover
|
|
41
|
+
* @default true
|
|
42
|
+
*/
|
|
43
|
+
showTooltip?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Show labels on segments
|
|
46
|
+
* @default false
|
|
47
|
+
*/
|
|
48
|
+
showLabels?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Inner radius for donut chart (0-100)
|
|
51
|
+
* Set to 0 for full pie chart
|
|
52
|
+
* @default 0
|
|
53
|
+
*/
|
|
54
|
+
innerRadius?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Outer radius (0-100)
|
|
57
|
+
* @default 80
|
|
58
|
+
*/
|
|
59
|
+
outerRadius?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Padding angle between segments in degrees
|
|
62
|
+
* @default 0
|
|
63
|
+
*/
|
|
64
|
+
paddingAngle?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Custom className for the container
|
|
67
|
+
*/
|
|
68
|
+
className?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Chart height aspect ratio
|
|
71
|
+
* @default "aspect-square"
|
|
72
|
+
*/
|
|
73
|
+
aspectRatio?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Custom label formatter function
|
|
76
|
+
*/
|
|
77
|
+
labelFormatter?: (value: number, entry: PieChartData) => string;
|
|
78
|
+
/**
|
|
79
|
+
* Custom formatter function for tooltip values
|
|
80
|
+
* @example (value, name, item, index, payload) => `$${value.toLocaleString()}`
|
|
81
|
+
*/
|
|
82
|
+
tooltipFormatter?: (
|
|
83
|
+
value: any,
|
|
84
|
+
name: any,
|
|
85
|
+
item: any,
|
|
86
|
+
index: number,
|
|
87
|
+
payload: any
|
|
88
|
+
) => React.ReactNode;
|
|
89
|
+
/**
|
|
90
|
+
* Scale outer radius based on value percentage (larger values = larger radius)
|
|
91
|
+
* Creates a "bursting" effect where segments extend outward based on their size
|
|
92
|
+
* @default false
|
|
93
|
+
*/
|
|
94
|
+
scaledRadius?: boolean;
|
|
95
|
+
/**
|
|
96
|
+
* Enable gradient fill for segments (radial gradient from center outward)
|
|
97
|
+
* @default false
|
|
98
|
+
*/
|
|
99
|
+
gradient?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Show radial grid lines in the background
|
|
102
|
+
* @default false
|
|
103
|
+
*/
|
|
104
|
+
showRadialGrid?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Number of radial lines to display (evenly spaced around the circle)
|
|
107
|
+
* @default 8
|
|
108
|
+
*/
|
|
109
|
+
radialGridLines?: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function PieChart({
|
|
113
|
+
data,
|
|
114
|
+
config,
|
|
115
|
+
nameKey,
|
|
116
|
+
dataKey,
|
|
117
|
+
showLegend = true,
|
|
118
|
+
showTooltip = true,
|
|
119
|
+
showLabels = false,
|
|
120
|
+
innerRadius = 0,
|
|
121
|
+
outerRadius = 80,
|
|
122
|
+
paddingAngle = 0,
|
|
123
|
+
className,
|
|
124
|
+
aspectRatio = "aspect-square",
|
|
125
|
+
labelFormatter,
|
|
126
|
+
scaledRadius = false,
|
|
127
|
+
gradient = false,
|
|
128
|
+
showRadialGrid = false,
|
|
129
|
+
radialGridLines = 8,
|
|
130
|
+
tooltipFormatter,
|
|
131
|
+
}: PieChartProps) {
|
|
132
|
+
// Calculate total and max value for scaling
|
|
133
|
+
const maxValue = Math.max(...data.map((entry) => Number(entry[dataKey]) || 0));
|
|
134
|
+
|
|
135
|
+
const renderLabel = showLabels
|
|
136
|
+
? (entry: any) => {
|
|
137
|
+
if (labelFormatter) {
|
|
138
|
+
return labelFormatter(entry[dataKey], entry);
|
|
139
|
+
}
|
|
140
|
+
return `${entry[dataKey]}`;
|
|
141
|
+
}
|
|
142
|
+
: undefined;
|
|
143
|
+
|
|
144
|
+
// Custom active shape renderer for scaled radius
|
|
145
|
+
const renderActiveShape = (props: any) => {
|
|
146
|
+
const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, fill } = props;
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<g>
|
|
150
|
+
<Sector
|
|
151
|
+
cx={cx}
|
|
152
|
+
cy={cy}
|
|
153
|
+
innerRadius={innerRadius}
|
|
154
|
+
outerRadius={outerRadius}
|
|
155
|
+
startAngle={startAngle}
|
|
156
|
+
endAngle={endAngle}
|
|
157
|
+
fill={fill}
|
|
158
|
+
/>
|
|
159
|
+
</g>
|
|
160
|
+
);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// Custom shape renderer for scaled radius and gradient
|
|
164
|
+
const renderCustomShape = (props: any) => {
|
|
165
|
+
const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, fill, value, index } = props;
|
|
166
|
+
|
|
167
|
+
// Calculate scaled radius if enabled
|
|
168
|
+
let scaledOuterRadius = outerRadius;
|
|
169
|
+
if (scaledRadius && maxValue > 0) {
|
|
170
|
+
const valueNum = Number(value) || 0;
|
|
171
|
+
const percentage = valueNum / maxValue;
|
|
172
|
+
// Scale between 40% and 100% of the max outer radius
|
|
173
|
+
const minScale = 0.4;
|
|
174
|
+
scaledOuterRadius = outerRadius * (minScale + percentage * (1 - minScale));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (gradient) {
|
|
178
|
+
return (
|
|
179
|
+
<>
|
|
180
|
+
<defs>
|
|
181
|
+
<radialGradient
|
|
182
|
+
id={`fillGradient${index}`}
|
|
183
|
+
cx={cx}
|
|
184
|
+
cy={cy}
|
|
185
|
+
r={scaledOuterRadius}
|
|
186
|
+
gradientUnits="userSpaceOnUse"
|
|
187
|
+
>
|
|
188
|
+
<stop offset="0%" stopColor={fill} stopOpacity={0} />
|
|
189
|
+
<stop offset="100%" stopColor={fill} stopOpacity={1} />
|
|
190
|
+
</radialGradient>
|
|
191
|
+
<clipPath id={`clipPath${index}`}>
|
|
192
|
+
<Sector
|
|
193
|
+
cx={cx}
|
|
194
|
+
cy={cy}
|
|
195
|
+
innerRadius={innerRadius}
|
|
196
|
+
outerRadius={scaledOuterRadius}
|
|
197
|
+
startAngle={startAngle}
|
|
198
|
+
endAngle={endAngle}
|
|
199
|
+
/>
|
|
200
|
+
</clipPath>
|
|
201
|
+
</defs>
|
|
202
|
+
<Sector
|
|
203
|
+
cx={cx}
|
|
204
|
+
cy={cy}
|
|
205
|
+
innerRadius={innerRadius}
|
|
206
|
+
outerRadius={scaledOuterRadius}
|
|
207
|
+
startAngle={startAngle}
|
|
208
|
+
endAngle={endAngle}
|
|
209
|
+
clipPath={`url(#clipPath${index})`}
|
|
210
|
+
fill={`url(#fillGradient${index})`}
|
|
211
|
+
/>
|
|
212
|
+
</>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<Sector
|
|
218
|
+
cx={cx}
|
|
219
|
+
cy={cy}
|
|
220
|
+
innerRadius={innerRadius}
|
|
221
|
+
outerRadius={scaledOuterRadius}
|
|
222
|
+
startAngle={startAngle}
|
|
223
|
+
endAngle={endAngle}
|
|
224
|
+
fill={fill}
|
|
225
|
+
/>
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
return (
|
|
230
|
+
<ChartContainer config={config} className={className || aspectRatio}>
|
|
231
|
+
<RechartsPieChart>
|
|
232
|
+
{showRadialGrid && (
|
|
233
|
+
<PolarAngleAxis
|
|
234
|
+
dataKey="gridLines"
|
|
235
|
+
tick={false}
|
|
236
|
+
axisLine={{
|
|
237
|
+
stroke: "hsl(var(--border))",
|
|
238
|
+
strokeOpacity: 0.5,
|
|
239
|
+
strokeWidth: 0.5,
|
|
240
|
+
}}
|
|
241
|
+
domain={[0, radialGridLines]}
|
|
242
|
+
tickCount={radialGridLines}
|
|
243
|
+
/>
|
|
244
|
+
)}
|
|
245
|
+
|
|
246
|
+
{showTooltip && (
|
|
247
|
+
<ChartTooltip
|
|
248
|
+
cursor={false}
|
|
249
|
+
content={
|
|
250
|
+
<ChartTooltipContent hideLabel nameKey={nameKey} formatter={tooltipFormatter} />
|
|
251
|
+
}
|
|
252
|
+
/>
|
|
253
|
+
)}
|
|
254
|
+
|
|
255
|
+
{showLegend && <ChartLegend content={<ChartLegendContent nameKey={nameKey} />} />}
|
|
256
|
+
|
|
257
|
+
<Pie
|
|
258
|
+
data={data}
|
|
259
|
+
dataKey={dataKey}
|
|
260
|
+
nameKey={nameKey}
|
|
261
|
+
innerRadius={`${innerRadius}%`}
|
|
262
|
+
outerRadius={`${outerRadius}%`}
|
|
263
|
+
paddingAngle={paddingAngle}
|
|
264
|
+
label={renderLabel}
|
|
265
|
+
activeShape={scaledRadius || gradient ? renderActiveShape : undefined}
|
|
266
|
+
shape={scaledRadius || gradient ? renderCustomShape : undefined}
|
|
267
|
+
>
|
|
268
|
+
{data.map((entry, index) => {
|
|
269
|
+
const name = entry[nameKey] as string;
|
|
270
|
+
const color = `var(--color-${name})`;
|
|
271
|
+
return <Cell key={`cell-${index}`} fill={color} />;
|
|
272
|
+
})}
|
|
273
|
+
</Pie>
|
|
274
|
+
</RechartsPieChart>
|
|
275
|
+
</ChartContainer>
|
|
276
|
+
);
|
|
277
|
+
}
|