@optilogic/charts 1.0.0-beta.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +3034 -174
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +489 -192
- package/dist/index.d.ts +489 -192
- package/dist/index.js +3006 -175
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/cartesian/area-chart.tsx +177 -0
- package/src/cartesian/bar-chart.tsx +217 -0
- package/src/cartesian/composed-chart.tsx +222 -0
- package/src/cartesian/line-chart.tsx +159 -0
- package/src/cartesian/scatter-chart.tsx +158 -0
- package/src/cartesian/waterfall-chart.tsx +171 -0
- package/src/dashboard/chart-builder.tsx +310 -0
- package/src/dashboard/chart-renderer.tsx +250 -0
- package/src/dashboard/kpi-card.tsx +121 -0
- package/src/dashboard/scenario-comparison.tsx +235 -0
- package/src/dashboard/sparkline.tsx +86 -0
- package/src/index.ts +50 -19
- package/src/radial/donut-chart.tsx +135 -0
- package/src/radial/pie-chart.tsx +153 -0
- package/src/radial/radar-chart.tsx +111 -0
- package/src/radial/radial-bar-chart.tsx +115 -0
- package/src/shared/chart-container.tsx +104 -0
- package/src/shared/chart-legend.tsx +57 -0
- package/src/shared/chart-tooltip.tsx +159 -0
- package/src/shared/colors.ts +37 -0
- package/src/shared/formatters.ts +51 -0
- package/src/shared/types.ts +66 -0
- package/src/shared/use-live-data.ts +83 -0
- package/src/specialized/funnel-chart.tsx +93 -0
- package/src/specialized/gantt-chart.tsx +416 -0
- package/src/specialized/heatmap-chart.tsx +250 -0
- package/src/specialized/sankey-chart.tsx +155 -0
- package/src/specialized/treemap-chart.tsx +121 -0
- package/src/bar-chart.tsx +0 -337
- package/src/line-chart.tsx +0 -266
package/src/bar-chart.tsx
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* BarChart
|
|
3
|
-
*
|
|
4
|
-
* A reusable, theme-aware bar chart component built on Recharts.
|
|
5
|
-
* Supports vertical/horizontal layouts, stacked/grouped bars,
|
|
6
|
-
* configurable axes, animations, and styling that integrates
|
|
7
|
-
* with the project's CSS variable theming system.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import * as React from "react";
|
|
11
|
-
import {
|
|
12
|
-
BarChart as RechartsBarChart,
|
|
13
|
-
Bar,
|
|
14
|
-
XAxis,
|
|
15
|
-
YAxis,
|
|
16
|
-
CartesianGrid,
|
|
17
|
-
Tooltip,
|
|
18
|
-
Legend,
|
|
19
|
-
ResponsiveContainer,
|
|
20
|
-
} from "recharts";
|
|
21
|
-
import { cn } from "@optilogic/core";
|
|
22
|
-
import { CHART_COLORS, getChartColor } from "./line-chart";
|
|
23
|
-
|
|
24
|
-
/** Configuration for a single bar series */
|
|
25
|
-
export interface BarChartSeries {
|
|
26
|
-
/** Key in data objects for this series' values */
|
|
27
|
-
dataKey: string;
|
|
28
|
-
/** Display name shown in legend/tooltip */
|
|
29
|
-
name: string;
|
|
30
|
-
/** Custom color (defaults to theme chart colors) */
|
|
31
|
-
color?: string;
|
|
32
|
-
/** Stack ID - bars with the same stackId will be stacked */
|
|
33
|
-
stackId?: string;
|
|
34
|
-
/** Border radius for bars */
|
|
35
|
-
radius?: number | [number, number, number, number];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/** X-axis configuration */
|
|
39
|
-
export interface BarChartXAxis {
|
|
40
|
-
/** Key in data objects for x-axis values (category axis in vertical layout) */
|
|
41
|
-
dataKey: string;
|
|
42
|
-
/** Axis label */
|
|
43
|
-
label?: string;
|
|
44
|
-
/** Custom tick formatter */
|
|
45
|
-
tickFormatter?: (value: unknown) => string;
|
|
46
|
-
/** Minimum gap between ticks in pixels */
|
|
47
|
-
minTickGap?: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Y-axis configuration */
|
|
51
|
-
export interface BarChartYAxis {
|
|
52
|
-
/** Domain bounds [min, max] - use "auto" for automatic */
|
|
53
|
-
domain?: [
|
|
54
|
-
number | "auto" | "dataMin" | "dataMax",
|
|
55
|
-
number | "auto" | "dataMin" | "dataMax"
|
|
56
|
-
];
|
|
57
|
-
/** Axis label */
|
|
58
|
-
label?: string;
|
|
59
|
-
/** Custom tick formatter */
|
|
60
|
-
tickFormatter?: (value: unknown) => string;
|
|
61
|
-
/** Axis width in pixels */
|
|
62
|
-
width?: number;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/** Grid configuration */
|
|
66
|
-
export interface BarChartGrid {
|
|
67
|
-
/** Show vertical grid lines */
|
|
68
|
-
vertical?: boolean;
|
|
69
|
-
/** Show horizontal grid lines */
|
|
70
|
-
horizontal?: boolean;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Legend configuration */
|
|
74
|
-
export interface BarChartLegend {
|
|
75
|
-
/** Vertical position */
|
|
76
|
-
position?: "top" | "bottom";
|
|
77
|
-
/** Horizontal alignment */
|
|
78
|
-
align?: "left" | "center" | "right";
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface BarChartProps {
|
|
82
|
-
/** Array of data points */
|
|
83
|
-
data: Record<string, unknown>[];
|
|
84
|
-
/** Series configurations */
|
|
85
|
-
series: BarChartSeries[];
|
|
86
|
-
/** Chart layout orientation */
|
|
87
|
-
layout?: "vertical" | "horizontal";
|
|
88
|
-
/** X-axis configuration */
|
|
89
|
-
xAxis: BarChartXAxis;
|
|
90
|
-
/** Y-axis configuration */
|
|
91
|
-
yAxis?: BarChartYAxis;
|
|
92
|
-
/** Grid configuration (true for default, false to hide, or object for fine control) */
|
|
93
|
-
grid?: boolean | BarChartGrid;
|
|
94
|
-
/** Show tooltip on hover */
|
|
95
|
-
tooltip?: boolean;
|
|
96
|
-
/** Legend configuration (true for default, false to hide, or object for fine control) */
|
|
97
|
-
legend?: boolean | BarChartLegend;
|
|
98
|
-
/** Enable animations */
|
|
99
|
-
animate?: boolean;
|
|
100
|
-
/** Bar width in pixels */
|
|
101
|
-
barSize?: number;
|
|
102
|
-
/** Gap between bars in a group (pixels) */
|
|
103
|
-
barGap?: number;
|
|
104
|
-
/** Gap between bar groups (pixels or percentage) */
|
|
105
|
-
barCategoryGap?: number | string;
|
|
106
|
-
/** Additional CSS classes */
|
|
107
|
-
className?: string;
|
|
108
|
-
/** Chart height (default: 100%) */
|
|
109
|
-
height?: number | string;
|
|
110
|
-
/** Chart margins */
|
|
111
|
-
margin?: { top?: number; right?: number; bottom?: number; left?: number };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* A flexible, theme-aware bar chart component.
|
|
116
|
-
*
|
|
117
|
-
* @example
|
|
118
|
-
* // Grouped bar chart
|
|
119
|
-
* <BarChart
|
|
120
|
-
* data={salesData}
|
|
121
|
-
* series={[
|
|
122
|
-
* { dataKey: "revenue", name: "Revenue" },
|
|
123
|
-
* { dataKey: "profit", name: "Profit" }
|
|
124
|
-
* ]}
|
|
125
|
-
* xAxis={{ dataKey: "month" }}
|
|
126
|
-
* legend={{ position: "top" }}
|
|
127
|
-
* />
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
* // Stacked bar chart
|
|
131
|
-
* <BarChart
|
|
132
|
-
* data={salesData}
|
|
133
|
-
* series={[
|
|
134
|
-
* { dataKey: "q1", name: "Q1", stackId: "revenue" },
|
|
135
|
-
* { dataKey: "q2", name: "Q2", stackId: "revenue" }
|
|
136
|
-
* ]}
|
|
137
|
-
* xAxis={{ dataKey: "category" }}
|
|
138
|
-
* />
|
|
139
|
-
*/
|
|
140
|
-
const BarChart = React.forwardRef<HTMLDivElement, BarChartProps>(
|
|
141
|
-
(
|
|
142
|
-
{
|
|
143
|
-
data,
|
|
144
|
-
series,
|
|
145
|
-
layout = "vertical",
|
|
146
|
-
xAxis,
|
|
147
|
-
yAxis,
|
|
148
|
-
grid = true,
|
|
149
|
-
tooltip = true,
|
|
150
|
-
legend = false,
|
|
151
|
-
animate = true,
|
|
152
|
-
barSize,
|
|
153
|
-
barGap,
|
|
154
|
-
barCategoryGap,
|
|
155
|
-
className,
|
|
156
|
-
height = "100%",
|
|
157
|
-
margin = { top: 8, right: 12, left: 0, bottom: 4 },
|
|
158
|
-
},
|
|
159
|
-
ref
|
|
160
|
-
) => {
|
|
161
|
-
const gridConfig = React.useMemo(() => {
|
|
162
|
-
if (grid === false) return null;
|
|
163
|
-
if (grid === true) return { vertical: false, horizontal: true };
|
|
164
|
-
return grid;
|
|
165
|
-
}, [grid]);
|
|
166
|
-
|
|
167
|
-
const legendConfig = React.useMemo(() => {
|
|
168
|
-
if (legend === false) return null;
|
|
169
|
-
if (legend === true)
|
|
170
|
-
return { position: "top" as const, align: "right" as const };
|
|
171
|
-
return legend;
|
|
172
|
-
}, [legend]);
|
|
173
|
-
|
|
174
|
-
const isHorizontal = layout === "horizontal";
|
|
175
|
-
|
|
176
|
-
return (
|
|
177
|
-
<div ref={ref} className={cn("w-full", className)} style={{ height }}>
|
|
178
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
179
|
-
<RechartsBarChart
|
|
180
|
-
data={data}
|
|
181
|
-
layout={isHorizontal ? "vertical" : "horizontal"}
|
|
182
|
-
margin={margin}
|
|
183
|
-
barSize={barSize}
|
|
184
|
-
barGap={barGap}
|
|
185
|
-
barCategoryGap={barCategoryGap}
|
|
186
|
-
>
|
|
187
|
-
{gridConfig && (
|
|
188
|
-
<CartesianGrid
|
|
189
|
-
strokeDasharray="3 3"
|
|
190
|
-
stroke="hsl(var(--divider))"
|
|
191
|
-
vertical={
|
|
192
|
-
isHorizontal
|
|
193
|
-
? (gridConfig.horizontal ?? true)
|
|
194
|
-
: (gridConfig.vertical ?? false)
|
|
195
|
-
}
|
|
196
|
-
horizontal={
|
|
197
|
-
isHorizontal
|
|
198
|
-
? (gridConfig.vertical ?? false)
|
|
199
|
-
: (gridConfig.horizontal ?? true)
|
|
200
|
-
}
|
|
201
|
-
/>
|
|
202
|
-
)}
|
|
203
|
-
|
|
204
|
-
{isHorizontal ? (
|
|
205
|
-
<XAxis
|
|
206
|
-
type="number"
|
|
207
|
-
domain={yAxis?.domain ?? ["auto", "auto"]}
|
|
208
|
-
tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
|
|
209
|
-
tickLine={false}
|
|
210
|
-
axisLine={false}
|
|
211
|
-
tickFormatter={yAxis?.tickFormatter}
|
|
212
|
-
label={
|
|
213
|
-
yAxis?.label
|
|
214
|
-
? {
|
|
215
|
-
value: yAxis.label,
|
|
216
|
-
position: "insideBottom",
|
|
217
|
-
offset: -4,
|
|
218
|
-
fontSize: 11,
|
|
219
|
-
fill: "hsl(var(--muted-foreground))",
|
|
220
|
-
}
|
|
221
|
-
: undefined
|
|
222
|
-
}
|
|
223
|
-
/>
|
|
224
|
-
) : (
|
|
225
|
-
<XAxis
|
|
226
|
-
dataKey={xAxis.dataKey}
|
|
227
|
-
tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
|
|
228
|
-
tickLine={false}
|
|
229
|
-
axisLine={false}
|
|
230
|
-
minTickGap={xAxis.minTickGap ?? 24}
|
|
231
|
-
tickFormatter={xAxis.tickFormatter}
|
|
232
|
-
label={
|
|
233
|
-
xAxis.label
|
|
234
|
-
? {
|
|
235
|
-
value: xAxis.label,
|
|
236
|
-
position: "insideBottom",
|
|
237
|
-
offset: -4,
|
|
238
|
-
fontSize: 11,
|
|
239
|
-
fill: "hsl(var(--muted-foreground))",
|
|
240
|
-
}
|
|
241
|
-
: undefined
|
|
242
|
-
}
|
|
243
|
-
/>
|
|
244
|
-
)}
|
|
245
|
-
|
|
246
|
-
{isHorizontal ? (
|
|
247
|
-
<YAxis
|
|
248
|
-
type="category"
|
|
249
|
-
dataKey={xAxis.dataKey}
|
|
250
|
-
tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
|
|
251
|
-
tickLine={false}
|
|
252
|
-
axisLine={false}
|
|
253
|
-
width={yAxis?.width ?? 80}
|
|
254
|
-
tickFormatter={xAxis.tickFormatter}
|
|
255
|
-
label={
|
|
256
|
-
xAxis.label
|
|
257
|
-
? {
|
|
258
|
-
value: xAxis.label,
|
|
259
|
-
angle: -90,
|
|
260
|
-
position: "insideLeft",
|
|
261
|
-
fontSize: 11,
|
|
262
|
-
fill: "hsl(var(--muted-foreground))",
|
|
263
|
-
}
|
|
264
|
-
: undefined
|
|
265
|
-
}
|
|
266
|
-
/>
|
|
267
|
-
) : (
|
|
268
|
-
<YAxis
|
|
269
|
-
domain={yAxis?.domain ?? ["auto", "auto"]}
|
|
270
|
-
tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
|
|
271
|
-
tickLine={false}
|
|
272
|
-
axisLine={false}
|
|
273
|
-
width={yAxis?.width ?? 30}
|
|
274
|
-
tickFormatter={yAxis?.tickFormatter}
|
|
275
|
-
label={
|
|
276
|
-
yAxis?.label
|
|
277
|
-
? {
|
|
278
|
-
value: yAxis.label,
|
|
279
|
-
angle: -90,
|
|
280
|
-
position: "insideLeft",
|
|
281
|
-
fontSize: 11,
|
|
282
|
-
fill: "hsl(var(--muted-foreground))",
|
|
283
|
-
}
|
|
284
|
-
: undefined
|
|
285
|
-
}
|
|
286
|
-
/>
|
|
287
|
-
)}
|
|
288
|
-
|
|
289
|
-
{tooltip && (
|
|
290
|
-
<Tooltip
|
|
291
|
-
contentStyle={{
|
|
292
|
-
background: "hsl(var(--card))",
|
|
293
|
-
border: "1px solid hsl(var(--border))",
|
|
294
|
-
borderRadius: 6,
|
|
295
|
-
fontSize: 11,
|
|
296
|
-
}}
|
|
297
|
-
labelStyle={{
|
|
298
|
-
color: "hsl(var(--foreground))",
|
|
299
|
-
fontWeight: 500,
|
|
300
|
-
marginBottom: 4,
|
|
301
|
-
}}
|
|
302
|
-
itemStyle={{
|
|
303
|
-
color: "hsl(var(--foreground))",
|
|
304
|
-
}}
|
|
305
|
-
cursor={{ fill: "hsl(var(--accent))", fillOpacity: 0.3 }}
|
|
306
|
-
/>
|
|
307
|
-
)}
|
|
308
|
-
|
|
309
|
-
{legendConfig && (
|
|
310
|
-
<Legend
|
|
311
|
-
verticalAlign={legendConfig.position ?? "top"}
|
|
312
|
-
align={legendConfig.align ?? "right"}
|
|
313
|
-
wrapperStyle={{ fontSize: 11, paddingBottom: 4 }}
|
|
314
|
-
/>
|
|
315
|
-
)}
|
|
316
|
-
|
|
317
|
-
{series.map((s, index) => (
|
|
318
|
-
<Bar
|
|
319
|
-
key={s.dataKey}
|
|
320
|
-
dataKey={s.dataKey}
|
|
321
|
-
name={s.name}
|
|
322
|
-
fill={getChartColor(index, s.color)}
|
|
323
|
-
stackId={s.stackId}
|
|
324
|
-
radius={s.radius ?? 0}
|
|
325
|
-
isAnimationActive={animate}
|
|
326
|
-
/>
|
|
327
|
-
))}
|
|
328
|
-
</RechartsBarChart>
|
|
329
|
-
</ResponsiveContainer>
|
|
330
|
-
</div>
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
BarChart.displayName = "BarChart";
|
|
336
|
-
|
|
337
|
-
export { BarChart };
|
package/src/line-chart.tsx
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LineChart
|
|
3
|
-
*
|
|
4
|
-
* A reusable, theme-aware line chart component built on Recharts.
|
|
5
|
-
* Supports multiple series, configurable axes, animations, and styling
|
|
6
|
-
* that integrates with the project's CSS variable theming system.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import * as React from "react";
|
|
10
|
-
import {
|
|
11
|
-
LineChart as RechartsLineChart,
|
|
12
|
-
Line,
|
|
13
|
-
XAxis,
|
|
14
|
-
YAxis,
|
|
15
|
-
CartesianGrid,
|
|
16
|
-
Tooltip,
|
|
17
|
-
Legend,
|
|
18
|
-
ResponsiveContainer,
|
|
19
|
-
} from "recharts";
|
|
20
|
-
import { cn } from "@optilogic/core";
|
|
21
|
-
|
|
22
|
-
/** Theme-aware chart colors from CSS variables */
|
|
23
|
-
export const CHART_COLORS = [
|
|
24
|
-
"hsl(var(--chart-1))",
|
|
25
|
-
"hsl(var(--chart-2))",
|
|
26
|
-
"hsl(var(--chart-3))",
|
|
27
|
-
"hsl(var(--chart-4))",
|
|
28
|
-
"hsl(var(--chart-5))",
|
|
29
|
-
];
|
|
30
|
-
|
|
31
|
-
export function getChartColor(index: number, custom?: string): string {
|
|
32
|
-
if (custom) return custom;
|
|
33
|
-
return CHART_COLORS[index % CHART_COLORS.length] ?? "hsl(var(--chart-1))";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Configuration for a single line series */
|
|
37
|
-
export interface LineChartSeries {
|
|
38
|
-
/** Key in data objects for this series' values */
|
|
39
|
-
dataKey: string;
|
|
40
|
-
/** Display name shown in legend/tooltip */
|
|
41
|
-
name: string;
|
|
42
|
-
/** Custom color (defaults to theme chart colors) */
|
|
43
|
-
color?: string;
|
|
44
|
-
/** Line stroke width (default: 2) */
|
|
45
|
-
strokeWidth?: number;
|
|
46
|
-
/** Show dots on data points (default: false) */
|
|
47
|
-
dot?: boolean;
|
|
48
|
-
/** Line interpolation type */
|
|
49
|
-
type?: "monotone" | "linear" | "step" | "basis" | "natural";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/** X-axis configuration */
|
|
53
|
-
export interface LineChartXAxis {
|
|
54
|
-
/** Key in data objects for x-axis values */
|
|
55
|
-
dataKey: string;
|
|
56
|
-
/** Axis label */
|
|
57
|
-
label?: string;
|
|
58
|
-
/** Custom tick formatter */
|
|
59
|
-
tickFormatter?: (value: unknown) => string;
|
|
60
|
-
/** Minimum gap between ticks in pixels */
|
|
61
|
-
minTickGap?: number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Y-axis configuration */
|
|
65
|
-
export interface LineChartYAxis {
|
|
66
|
-
/** Domain bounds [min, max] - use "auto" for automatic */
|
|
67
|
-
domain?: [
|
|
68
|
-
number | "auto" | "dataMin" | "dataMax",
|
|
69
|
-
number | "auto" | "dataMin" | "dataMax"
|
|
70
|
-
];
|
|
71
|
-
/** Axis label */
|
|
72
|
-
label?: string;
|
|
73
|
-
/** Custom tick formatter */
|
|
74
|
-
tickFormatter?: (value: unknown) => string;
|
|
75
|
-
/** Axis width in pixels */
|
|
76
|
-
width?: number;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/** Grid configuration */
|
|
80
|
-
export interface LineChartGrid {
|
|
81
|
-
/** Show vertical grid lines */
|
|
82
|
-
vertical?: boolean;
|
|
83
|
-
/** Show horizontal grid lines */
|
|
84
|
-
horizontal?: boolean;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Legend configuration */
|
|
88
|
-
export interface LineChartLegend {
|
|
89
|
-
/** Vertical position */
|
|
90
|
-
position?: "top" | "bottom";
|
|
91
|
-
/** Horizontal alignment */
|
|
92
|
-
align?: "left" | "center" | "right";
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface LineChartProps {
|
|
96
|
-
/** Array of data points */
|
|
97
|
-
data: Record<string, unknown>[];
|
|
98
|
-
/** Series configurations */
|
|
99
|
-
series: LineChartSeries[];
|
|
100
|
-
/** X-axis configuration */
|
|
101
|
-
xAxis: LineChartXAxis;
|
|
102
|
-
/** Y-axis configuration */
|
|
103
|
-
yAxis?: LineChartYAxis;
|
|
104
|
-
/** Grid configuration (true for default, false to hide, or object for fine control) */
|
|
105
|
-
grid?: boolean | LineChartGrid;
|
|
106
|
-
/** Show tooltip on hover */
|
|
107
|
-
tooltip?: boolean;
|
|
108
|
-
/** Legend configuration (true for default, false to hide, or object for fine control) */
|
|
109
|
-
legend?: boolean | LineChartLegend;
|
|
110
|
-
/** Enable animations */
|
|
111
|
-
animate?: boolean;
|
|
112
|
-
/** Additional CSS classes */
|
|
113
|
-
className?: string;
|
|
114
|
-
/** Chart height (default: 100%) */
|
|
115
|
-
height?: number | string;
|
|
116
|
-
/** Chart margins */
|
|
117
|
-
margin?: { top?: number; right?: number; bottom?: number; left?: number };
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* A flexible, theme-aware line chart component.
|
|
122
|
-
*
|
|
123
|
-
* @example
|
|
124
|
-
* <LineChart
|
|
125
|
-
* data={metrics}
|
|
126
|
-
* series={[
|
|
127
|
-
* { dataKey: "cpu", name: "CPU %" },
|
|
128
|
-
* { dataKey: "memory", name: "Memory %", color: "hsl(var(--success))" }
|
|
129
|
-
* ]}
|
|
130
|
-
* xAxis={{ dataKey: "time", tickFormatter: formatTime }}
|
|
131
|
-
* yAxis={{ domain: [0, 100] }}
|
|
132
|
-
* animate
|
|
133
|
-
* />
|
|
134
|
-
*/
|
|
135
|
-
const LineChart = React.forwardRef<HTMLDivElement, LineChartProps>(
|
|
136
|
-
(
|
|
137
|
-
{
|
|
138
|
-
data,
|
|
139
|
-
series,
|
|
140
|
-
xAxis,
|
|
141
|
-
yAxis,
|
|
142
|
-
grid = true,
|
|
143
|
-
tooltip = true,
|
|
144
|
-
legend = false,
|
|
145
|
-
animate = false,
|
|
146
|
-
className,
|
|
147
|
-
height = "100%",
|
|
148
|
-
margin = { top: 8, right: 12, left: 0, bottom: 4 },
|
|
149
|
-
},
|
|
150
|
-
ref
|
|
151
|
-
) => {
|
|
152
|
-
const gridConfig = React.useMemo(() => {
|
|
153
|
-
if (grid === false) return null;
|
|
154
|
-
if (grid === true) return { vertical: false, horizontal: true };
|
|
155
|
-
return grid;
|
|
156
|
-
}, [grid]);
|
|
157
|
-
|
|
158
|
-
const legendConfig = React.useMemo(() => {
|
|
159
|
-
if (legend === false) return null;
|
|
160
|
-
if (legend === true)
|
|
161
|
-
return { position: "top" as const, align: "right" as const };
|
|
162
|
-
return legend;
|
|
163
|
-
}, [legend]);
|
|
164
|
-
|
|
165
|
-
return (
|
|
166
|
-
<div ref={ref} className={cn("w-full", className)} style={{ height }}>
|
|
167
|
-
<ResponsiveContainer width="100%" height="100%">
|
|
168
|
-
<RechartsLineChart data={data} margin={margin}>
|
|
169
|
-
{gridConfig && (
|
|
170
|
-
<CartesianGrid
|
|
171
|
-
strokeDasharray="3 3"
|
|
172
|
-
stroke="hsl(var(--divider))"
|
|
173
|
-
vertical={gridConfig.vertical ?? false}
|
|
174
|
-
horizontal={gridConfig.horizontal ?? true}
|
|
175
|
-
/>
|
|
176
|
-
)}
|
|
177
|
-
|
|
178
|
-
<XAxis
|
|
179
|
-
dataKey={xAxis.dataKey}
|
|
180
|
-
tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
|
|
181
|
-
tickLine={false}
|
|
182
|
-
axisLine={false}
|
|
183
|
-
minTickGap={xAxis.minTickGap ?? 24}
|
|
184
|
-
tickFormatter={xAxis.tickFormatter}
|
|
185
|
-
label={
|
|
186
|
-
xAxis.label
|
|
187
|
-
? {
|
|
188
|
-
value: xAxis.label,
|
|
189
|
-
position: "insideBottom",
|
|
190
|
-
offset: -4,
|
|
191
|
-
fontSize: 11,
|
|
192
|
-
fill: "hsl(var(--muted-foreground))",
|
|
193
|
-
}
|
|
194
|
-
: undefined
|
|
195
|
-
}
|
|
196
|
-
/>
|
|
197
|
-
|
|
198
|
-
<YAxis
|
|
199
|
-
domain={yAxis?.domain ?? ["auto", "auto"]}
|
|
200
|
-
tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
|
|
201
|
-
tickLine={false}
|
|
202
|
-
axisLine={false}
|
|
203
|
-
width={yAxis?.width ?? 30}
|
|
204
|
-
tickFormatter={yAxis?.tickFormatter}
|
|
205
|
-
label={
|
|
206
|
-
yAxis?.label
|
|
207
|
-
? {
|
|
208
|
-
value: yAxis.label,
|
|
209
|
-
angle: -90,
|
|
210
|
-
position: "insideLeft",
|
|
211
|
-
fontSize: 11,
|
|
212
|
-
fill: "hsl(var(--muted-foreground))",
|
|
213
|
-
}
|
|
214
|
-
: undefined
|
|
215
|
-
}
|
|
216
|
-
/>
|
|
217
|
-
|
|
218
|
-
{tooltip && (
|
|
219
|
-
<Tooltip
|
|
220
|
-
contentStyle={{
|
|
221
|
-
background: "hsl(var(--card))",
|
|
222
|
-
border: "1px solid hsl(var(--border))",
|
|
223
|
-
borderRadius: 6,
|
|
224
|
-
fontSize: 11,
|
|
225
|
-
}}
|
|
226
|
-
labelStyle={{
|
|
227
|
-
color: "hsl(var(--foreground))",
|
|
228
|
-
fontWeight: 500,
|
|
229
|
-
marginBottom: 4,
|
|
230
|
-
}}
|
|
231
|
-
itemStyle={{
|
|
232
|
-
color: "hsl(var(--foreground))",
|
|
233
|
-
}}
|
|
234
|
-
/>
|
|
235
|
-
)}
|
|
236
|
-
|
|
237
|
-
{legendConfig && (
|
|
238
|
-
<Legend
|
|
239
|
-
verticalAlign={legendConfig.position ?? "top"}
|
|
240
|
-
align={legendConfig.align ?? "right"}
|
|
241
|
-
wrapperStyle={{ fontSize: 11, paddingBottom: 4 }}
|
|
242
|
-
/>
|
|
243
|
-
)}
|
|
244
|
-
|
|
245
|
-
{series.map((s, index) => (
|
|
246
|
-
<Line
|
|
247
|
-
key={s.dataKey}
|
|
248
|
-
type={s.type ?? "monotone"}
|
|
249
|
-
dataKey={s.dataKey}
|
|
250
|
-
name={s.name}
|
|
251
|
-
stroke={getChartColor(index, s.color)}
|
|
252
|
-
strokeWidth={s.strokeWidth ?? 2}
|
|
253
|
-
dot={s.dot ?? false}
|
|
254
|
-
isAnimationActive={animate}
|
|
255
|
-
/>
|
|
256
|
-
))}
|
|
257
|
-
</RechartsLineChart>
|
|
258
|
-
</ResponsiveContainer>
|
|
259
|
-
</div>
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
LineChart.displayName = "LineChart";
|
|
265
|
-
|
|
266
|
-
export { LineChart };
|