@optilogic/charts 1.0.0-beta.8 → 1.0.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.
Files changed (37) hide show
  1. package/dist/index.cjs +3034 -174
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +489 -192
  4. package/dist/index.d.ts +489 -192
  5. package/dist/index.js +3006 -175
  6. package/dist/index.js.map +1 -1
  7. package/package.json +2 -2
  8. package/src/cartesian/area-chart.tsx +177 -0
  9. package/src/cartesian/bar-chart.tsx +217 -0
  10. package/src/cartesian/composed-chart.tsx +222 -0
  11. package/src/cartesian/line-chart.tsx +159 -0
  12. package/src/cartesian/scatter-chart.tsx +158 -0
  13. package/src/cartesian/waterfall-chart.tsx +171 -0
  14. package/src/dashboard/chart-builder.tsx +310 -0
  15. package/src/dashboard/chart-renderer.tsx +250 -0
  16. package/src/dashboard/kpi-card.tsx +121 -0
  17. package/src/dashboard/scenario-comparison.tsx +235 -0
  18. package/src/dashboard/sparkline.tsx +86 -0
  19. package/src/index.ts +50 -19
  20. package/src/radial/donut-chart.tsx +135 -0
  21. package/src/radial/pie-chart.tsx +153 -0
  22. package/src/radial/radar-chart.tsx +111 -0
  23. package/src/radial/radial-bar-chart.tsx +115 -0
  24. package/src/shared/chart-container.tsx +104 -0
  25. package/src/shared/chart-legend.tsx +57 -0
  26. package/src/shared/chart-tooltip.tsx +159 -0
  27. package/src/shared/colors.ts +37 -0
  28. package/src/shared/formatters.ts +51 -0
  29. package/src/shared/types.ts +66 -0
  30. package/src/shared/use-live-data.ts +83 -0
  31. package/src/specialized/funnel-chart.tsx +93 -0
  32. package/src/specialized/gantt-chart.tsx +416 -0
  33. package/src/specialized/heatmap-chart.tsx +250 -0
  34. package/src/specialized/sankey-chart.tsx +155 -0
  35. package/src/specialized/treemap-chart.tsx +121 -0
  36. package/src/bar-chart.tsx +0 -337
  37. package/src/line-chart.tsx +0 -266
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optilogic/charts",
3
- "version": "1.0.0-beta.8",
3
+ "version": "1.0.0",
4
4
  "description": "Chart components for Optilogic - LineChart and BarChart built on Recharts",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -24,7 +24,7 @@
24
24
  "README.md"
25
25
  ],
26
26
  "dependencies": {
27
- "@optilogic/core": "1.0.0-beta.8"
27
+ "@optilogic/core": "1.0.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "react": "^18.0.0 || ^19.0.0",
@@ -0,0 +1,177 @@
1
+ import * as React from "react";
2
+ import {
3
+ AreaChart as RechartsAreaChart,
4
+ Area,
5
+ XAxis,
6
+ YAxis,
7
+ CartesianGrid,
8
+ Tooltip,
9
+ Legend,
10
+ } from "recharts";
11
+ import { getChartColor } from "../shared/colors";
12
+ import { resolveTooltipProps } from "../shared/chart-tooltip";
13
+ import { ChartLegendContent, resolveLegendConfig } from "../shared/chart-legend";
14
+ import { ChartContainer } from "../shared/chart-container";
15
+ import type {
16
+ BaseChartProps,
17
+ ChartXAxis,
18
+ ChartYAxis,
19
+ ChartGrid,
20
+ ChartLegend,
21
+ ChartTooltipProp,
22
+ } from "../shared/types";
23
+
24
+ export interface AreaChartSeries {
25
+ dataKey: string;
26
+ name: string;
27
+ color?: string;
28
+ fillOpacity?: number;
29
+ strokeWidth?: number;
30
+ type?: "monotone" | "linear" | "step" | "basis" | "natural";
31
+ stackId?: string;
32
+ }
33
+
34
+ export interface AreaChartProps extends BaseChartProps {
35
+ data: Record<string, unknown>[];
36
+ series: AreaChartSeries[];
37
+ xAxis: ChartXAxis;
38
+ yAxis?: ChartYAxis;
39
+ grid?: boolean | ChartGrid;
40
+ legend?: boolean | ChartLegend;
41
+ tooltip?: ChartTooltipProp;
42
+ loading?: boolean;
43
+ }
44
+
45
+ const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>(
46
+ (
47
+ {
48
+ data,
49
+ series,
50
+ xAxis,
51
+ yAxis,
52
+ grid = true,
53
+ tooltip = true,
54
+ legend = false,
55
+ animate = true,
56
+ live = false,
57
+ className,
58
+ height = "100%",
59
+ margin = { top: 8, right: 12, left: 0, bottom: 4 },
60
+ loading,
61
+ },
62
+ ref,
63
+ ) => {
64
+ const gridConfig = React.useMemo(() => {
65
+ if (grid === false) return null;
66
+ if (grid === true) return { vertical: false, horizontal: true };
67
+ return grid;
68
+ }, [grid]);
69
+
70
+ const legendConfig = resolveLegendConfig(legend);
71
+ const tooltipProps = resolveTooltipProps(tooltip);
72
+ const isAnimated = live ? false : animate;
73
+
74
+ return (
75
+ <ChartContainer
76
+ ref={ref}
77
+ className={className}
78
+ height={height}
79
+ loading={loading}
80
+ empty={!data?.length}
81
+ >
82
+ <RechartsAreaChart data={data} margin={margin}>
83
+ <defs>
84
+ {series.map((s, index) => {
85
+ const color = getChartColor(index, s.color);
86
+ return (
87
+ <linearGradient
88
+ key={s.dataKey}
89
+ id={`area-gradient-${s.dataKey}`}
90
+ x1="0"
91
+ y1="0"
92
+ x2="0"
93
+ y2="1"
94
+ >
95
+ <stop offset="5%" stopColor={color} stopOpacity={0.3} />
96
+ <stop offset="95%" stopColor={color} stopOpacity={0.05} />
97
+ </linearGradient>
98
+ );
99
+ })}
100
+ </defs>
101
+ {gridConfig && (
102
+ <CartesianGrid
103
+ strokeDasharray="3 3"
104
+ stroke="hsl(var(--divider))"
105
+ vertical={gridConfig.vertical ?? false}
106
+ horizontal={gridConfig.horizontal ?? true}
107
+ />
108
+ )}
109
+ <XAxis
110
+ dataKey={xAxis.dataKey}
111
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
112
+ tickLine={false}
113
+ axisLine={false}
114
+ minTickGap={xAxis.minTickGap ?? 24}
115
+ tickFormatter={xAxis.tickFormatter}
116
+ label={
117
+ xAxis.label
118
+ ? {
119
+ value: xAxis.label,
120
+ position: "insideBottom",
121
+ offset: -4,
122
+ fontSize: 11,
123
+ fill: "hsl(var(--muted-foreground))",
124
+ }
125
+ : undefined
126
+ }
127
+ />
128
+ <YAxis
129
+ domain={yAxis?.domain ?? ["auto", "auto"]}
130
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
131
+ tickLine={false}
132
+ axisLine={false}
133
+ width={yAxis?.width ?? 30}
134
+ tickFormatter={yAxis?.tickFormatter}
135
+ label={
136
+ yAxis?.label
137
+ ? {
138
+ value: yAxis.label,
139
+ angle: -90,
140
+ position: "insideLeft",
141
+ fontSize: 11,
142
+ fill: "hsl(var(--muted-foreground))",
143
+ }
144
+ : undefined
145
+ }
146
+ />
147
+ {tooltipProps && <Tooltip {...tooltipProps} />}
148
+ {legendConfig && (
149
+ <Legend
150
+ verticalAlign={legendConfig.position ?? "top"}
151
+ align={legendConfig.align ?? "right"}
152
+ content={<ChartLegendContent />}
153
+ />
154
+ )}
155
+ {series.map((s, index) => (
156
+ <Area
157
+ key={s.dataKey}
158
+ type={s.type ?? "monotone"}
159
+ dataKey={s.dataKey}
160
+ name={s.name}
161
+ stroke={getChartColor(index, s.color)}
162
+ strokeWidth={s.strokeWidth ?? 2}
163
+ fill={`url(#area-gradient-${s.dataKey})`}
164
+ fillOpacity={s.fillOpacity ?? 1}
165
+ stackId={s.stackId}
166
+ isAnimationActive={isAnimated}
167
+ animationDuration={isAnimated ? 300 : 0}
168
+ />
169
+ ))}
170
+ </RechartsAreaChart>
171
+ </ChartContainer>
172
+ );
173
+ },
174
+ );
175
+
176
+ AreaChart.displayName = "AreaChart";
177
+ export { AreaChart };
@@ -0,0 +1,217 @@
1
+ import * as React from "react";
2
+ import {
3
+ BarChart as RechartsBarChart,
4
+ Bar,
5
+ XAxis,
6
+ YAxis,
7
+ CartesianGrid,
8
+ Tooltip,
9
+ Legend,
10
+ } from "recharts";
11
+ import { getChartColor } from "../shared/colors";
12
+ import { resolveTooltipProps } from "../shared/chart-tooltip";
13
+ import { ChartLegendContent, resolveLegendConfig } from "../shared/chart-legend";
14
+ import { ChartContainer } from "../shared/chart-container";
15
+ import type {
16
+ BaseChartProps,
17
+ ChartXAxis,
18
+ ChartYAxis,
19
+ ChartGrid,
20
+ ChartLegend,
21
+ ChartTooltipProp,
22
+ } from "../shared/types";
23
+
24
+ export interface BarChartSeries {
25
+ dataKey: string;
26
+ name: string;
27
+ color?: string;
28
+ stackId?: string;
29
+ radius?: number | [number, number, number, number];
30
+ }
31
+
32
+ export interface BarChartProps extends BaseChartProps {
33
+ data: Record<string, unknown>[];
34
+ series: BarChartSeries[];
35
+ layout?: "vertical" | "horizontal";
36
+ xAxis: ChartXAxis;
37
+ yAxis?: ChartYAxis;
38
+ grid?: boolean | ChartGrid;
39
+ legend?: boolean | ChartLegend;
40
+ tooltip?: ChartTooltipProp;
41
+ barSize?: number;
42
+ barGap?: number;
43
+ barCategoryGap?: number | string;
44
+ loading?: boolean;
45
+ }
46
+
47
+ const BarChart = React.forwardRef<HTMLDivElement, BarChartProps>(
48
+ (
49
+ {
50
+ data,
51
+ series,
52
+ layout = "vertical",
53
+ xAxis,
54
+ yAxis,
55
+ grid = true,
56
+ tooltip = true,
57
+ legend = false,
58
+ animate = true,
59
+ live = false,
60
+ barSize,
61
+ barGap,
62
+ barCategoryGap,
63
+ className,
64
+ height = "100%",
65
+ margin = { top: 8, right: 12, left: 0, bottom: 4 },
66
+ loading,
67
+ },
68
+ ref,
69
+ ) => {
70
+ const gridConfig = React.useMemo(() => {
71
+ if (grid === false) return null;
72
+ if (grid === true) return { vertical: false, horizontal: true };
73
+ return grid;
74
+ }, [grid]);
75
+
76
+ const legendConfig = resolveLegendConfig(legend);
77
+ const tooltipProps = resolveTooltipProps(tooltip);
78
+ const isAnimated = live ? false : animate;
79
+ const isHorizontal = layout === "horizontal";
80
+
81
+ return (
82
+ <ChartContainer
83
+ ref={ref}
84
+ className={className}
85
+ height={height}
86
+ loading={loading}
87
+ empty={!data?.length}
88
+ >
89
+ <RechartsBarChart
90
+ data={data}
91
+ layout={isHorizontal ? "vertical" : "horizontal"}
92
+ margin={margin}
93
+ barSize={barSize}
94
+ barGap={barGap}
95
+ barCategoryGap={barCategoryGap}
96
+ >
97
+ {gridConfig && (
98
+ <CartesianGrid
99
+ strokeDasharray="3 3"
100
+ stroke="hsl(var(--divider))"
101
+ vertical={
102
+ isHorizontal
103
+ ? (gridConfig.horizontal ?? true)
104
+ : (gridConfig.vertical ?? false)
105
+ }
106
+ horizontal={
107
+ isHorizontal
108
+ ? (gridConfig.vertical ?? false)
109
+ : (gridConfig.horizontal ?? true)
110
+ }
111
+ />
112
+ )}
113
+ {isHorizontal ? (
114
+ <XAxis
115
+ type="number"
116
+ domain={yAxis?.domain ?? ["auto", "auto"]}
117
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
118
+ tickLine={false}
119
+ axisLine={false}
120
+ tickFormatter={yAxis?.tickFormatter}
121
+ label={
122
+ yAxis?.label
123
+ ? {
124
+ value: yAxis.label,
125
+ position: "insideBottom",
126
+ offset: -4,
127
+ fontSize: 11,
128
+ fill: "hsl(var(--muted-foreground))",
129
+ }
130
+ : undefined
131
+ }
132
+ />
133
+ ) : (
134
+ <XAxis
135
+ dataKey={xAxis.dataKey}
136
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
137
+ tickLine={false}
138
+ axisLine={false}
139
+ minTickGap={xAxis.minTickGap ?? 24}
140
+ tickFormatter={xAxis.tickFormatter}
141
+ label={
142
+ xAxis.label
143
+ ? {
144
+ value: xAxis.label,
145
+ position: "insideBottom",
146
+ offset: -4,
147
+ fontSize: 11,
148
+ fill: "hsl(var(--muted-foreground))",
149
+ }
150
+ : undefined
151
+ }
152
+ />
153
+ )}
154
+ {isHorizontal ? (
155
+ <YAxis
156
+ type="category"
157
+ dataKey={xAxis.dataKey}
158
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
159
+ tickLine={false}
160
+ axisLine={false}
161
+ width={yAxis?.width ?? 80}
162
+ tickFormatter={xAxis.tickFormatter}
163
+ />
164
+ ) : (
165
+ <YAxis
166
+ domain={yAxis?.domain ?? ["auto", "auto"]}
167
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
168
+ tickLine={false}
169
+ axisLine={false}
170
+ width={yAxis?.width ?? 30}
171
+ tickFormatter={yAxis?.tickFormatter}
172
+ label={
173
+ yAxis?.label
174
+ ? {
175
+ value: yAxis.label,
176
+ angle: -90,
177
+ position: "insideLeft",
178
+ fontSize: 11,
179
+ fill: "hsl(var(--muted-foreground))",
180
+ }
181
+ : undefined
182
+ }
183
+ />
184
+ )}
185
+ {tooltipProps && (
186
+ <Tooltip
187
+ {...tooltipProps}
188
+ cursor={{ fill: "hsl(var(--accent))", fillOpacity: 0.3 }}
189
+ />
190
+ )}
191
+ {legendConfig && (
192
+ <Legend
193
+ verticalAlign={legendConfig.position ?? "top"}
194
+ align={legendConfig.align ?? "right"}
195
+ content={<ChartLegendContent />}
196
+ />
197
+ )}
198
+ {series.map((s, index) => (
199
+ <Bar
200
+ key={s.dataKey}
201
+ dataKey={s.dataKey}
202
+ name={s.name}
203
+ fill={getChartColor(index, s.color)}
204
+ stackId={s.stackId}
205
+ radius={s.radius ?? 0}
206
+ isAnimationActive={isAnimated}
207
+ animationDuration={isAnimated ? 300 : 0}
208
+ />
209
+ ))}
210
+ </RechartsBarChart>
211
+ </ChartContainer>
212
+ );
213
+ },
214
+ );
215
+
216
+ BarChart.displayName = "BarChart";
217
+ export { BarChart };
@@ -0,0 +1,222 @@
1
+ import * as React from "react";
2
+ import {
3
+ ComposedChart as RechartsComposedChart,
4
+ Line,
5
+ Bar,
6
+ Area,
7
+ XAxis,
8
+ YAxis,
9
+ CartesianGrid,
10
+ Tooltip,
11
+ Legend,
12
+ } from "recharts";
13
+ import { getChartColor } from "../shared/colors";
14
+ import { resolveTooltipProps } from "../shared/chart-tooltip";
15
+ import { ChartLegendContent, resolveLegendConfig } from "../shared/chart-legend";
16
+ import { ChartContainer } from "../shared/chart-container";
17
+ import type {
18
+ BaseChartProps,
19
+ ChartXAxis,
20
+ ChartYAxis,
21
+ ChartGrid,
22
+ ChartLegend,
23
+ ChartTooltipProp,
24
+ } from "../shared/types";
25
+
26
+ export interface ComposedChartSeries {
27
+ type: "line" | "bar" | "area";
28
+ dataKey: string;
29
+ name: string;
30
+ color?: string;
31
+ yAxisId?: string;
32
+ stackId?: string;
33
+ strokeWidth?: number;
34
+ strokeDasharray?: string;
35
+ fillOpacity?: number;
36
+ radius?: number | [number, number, number, number];
37
+ }
38
+
39
+ export interface ComposedChartProps extends BaseChartProps {
40
+ data: Record<string, unknown>[];
41
+ series: ComposedChartSeries[];
42
+ xAxis: ChartXAxis;
43
+ yAxis?: ChartYAxis;
44
+ /** Secondary Y axis displayed on the right */
45
+ yAxisRight?: ChartYAxis & { id: string };
46
+ grid?: boolean | ChartGrid;
47
+ legend?: boolean | ChartLegend;
48
+ tooltip?: ChartTooltipProp;
49
+ loading?: boolean;
50
+ }
51
+
52
+ const ComposedChart = React.forwardRef<HTMLDivElement, ComposedChartProps>(
53
+ (
54
+ {
55
+ data,
56
+ series,
57
+ xAxis,
58
+ yAxis,
59
+ yAxisRight,
60
+ grid = true,
61
+ tooltip = true,
62
+ legend = false,
63
+ animate = true,
64
+ live = false,
65
+ className,
66
+ height = "100%",
67
+ margin = { top: 8, right: 12, left: 0, bottom: 4 },
68
+ loading,
69
+ },
70
+ ref,
71
+ ) => {
72
+ const gridConfig = React.useMemo(() => {
73
+ if (grid === false) return null;
74
+ if (grid === true) return { vertical: false, horizontal: true };
75
+ return grid;
76
+ }, [grid]);
77
+
78
+ const legendConfig = resolveLegendConfig(legend);
79
+ const tooltipProps = resolveTooltipProps(tooltip);
80
+ const isAnimated = live ? false : animate;
81
+
82
+ return (
83
+ <ChartContainer
84
+ ref={ref}
85
+ className={className}
86
+ height={height}
87
+ loading={loading}
88
+ empty={!data?.length}
89
+ >
90
+ <RechartsComposedChart data={data} margin={margin}>
91
+ {gridConfig && (
92
+ <CartesianGrid
93
+ strokeDasharray="3 3"
94
+ stroke="hsl(var(--divider))"
95
+ vertical={gridConfig.vertical ?? false}
96
+ horizontal={gridConfig.horizontal ?? true}
97
+ />
98
+ )}
99
+ <XAxis
100
+ dataKey={xAxis.dataKey}
101
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
102
+ tickLine={false}
103
+ axisLine={false}
104
+ minTickGap={xAxis.minTickGap ?? 24}
105
+ tickFormatter={xAxis.tickFormatter}
106
+ label={
107
+ xAxis.label
108
+ ? {
109
+ value: xAxis.label,
110
+ position: "insideBottom",
111
+ offset: -4,
112
+ fontSize: 11,
113
+ fill: "hsl(var(--muted-foreground))",
114
+ }
115
+ : undefined
116
+ }
117
+ />
118
+ <YAxis
119
+ yAxisId="left"
120
+ domain={yAxis?.domain ?? ["auto", "auto"]}
121
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
122
+ tickLine={false}
123
+ axisLine={false}
124
+ width={yAxis?.width ?? 40}
125
+ tickFormatter={yAxis?.tickFormatter}
126
+ label={
127
+ yAxis?.label
128
+ ? {
129
+ value: yAxis.label,
130
+ angle: -90,
131
+ position: "insideLeft",
132
+ fontSize: 11,
133
+ fill: "hsl(var(--muted-foreground))",
134
+ }
135
+ : undefined
136
+ }
137
+ />
138
+ {yAxisRight && (
139
+ <YAxis
140
+ yAxisId={yAxisRight.id}
141
+ orientation="right"
142
+ domain={yAxisRight.domain ?? ["auto", "auto"]}
143
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
144
+ tickLine={false}
145
+ axisLine={false}
146
+ width={yAxisRight.width ?? 40}
147
+ tickFormatter={yAxisRight.tickFormatter}
148
+ label={
149
+ yAxisRight.label
150
+ ? {
151
+ value: yAxisRight.label,
152
+ angle: 90,
153
+ position: "insideRight",
154
+ fontSize: 11,
155
+ fill: "hsl(var(--muted-foreground))",
156
+ }
157
+ : undefined
158
+ }
159
+ />
160
+ )}
161
+ {tooltipProps && <Tooltip {...tooltipProps} />}
162
+ {legendConfig && (
163
+ <Legend
164
+ verticalAlign={legendConfig.position ?? "top"}
165
+ align={legendConfig.align ?? "right"}
166
+ content={<ChartLegendContent />}
167
+ />
168
+ )}
169
+ {series.map((s, index) => {
170
+ const color = getChartColor(index, s.color);
171
+ const yAxisId = s.yAxisId ?? "left";
172
+ const props = {
173
+ key: s.dataKey,
174
+ dataKey: s.dataKey,
175
+ name: s.name,
176
+ yAxisId,
177
+ isAnimationActive: isAnimated,
178
+ animationDuration: isAnimated ? 300 : 0,
179
+ };
180
+ switch (s.type) {
181
+ case "line":
182
+ return (
183
+ <Line
184
+ {...props}
185
+ type="monotone"
186
+ stroke={color}
187
+ strokeWidth={s.strokeWidth ?? 2}
188
+ strokeDasharray={s.strokeDasharray}
189
+ dot={false}
190
+ />
191
+ );
192
+ case "bar":
193
+ return (
194
+ <Bar
195
+ {...props}
196
+ fill={color}
197
+ stackId={s.stackId}
198
+ radius={s.radius ?? 0}
199
+ />
200
+ );
201
+ case "area":
202
+ return (
203
+ <Area
204
+ {...props}
205
+ type="monotone"
206
+ stroke={color}
207
+ fill={color}
208
+ fillOpacity={s.fillOpacity ?? 0.15}
209
+ strokeWidth={s.strokeWidth ?? 2}
210
+ stackId={s.stackId}
211
+ />
212
+ );
213
+ }
214
+ })}
215
+ </RechartsComposedChart>
216
+ </ChartContainer>
217
+ );
218
+ },
219
+ );
220
+
221
+ ComposedChart.displayName = "ComposedChart";
222
+ export { ComposedChart };