@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.
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
@@ -0,0 +1,159 @@
1
+ import * as React from "react";
2
+ import {
3
+ LineChart as RechartsLineChart,
4
+ Line,
5
+ XAxis,
6
+ YAxis,
7
+ CartesianGrid,
8
+ Tooltip,
9
+ Legend,
10
+ } from "recharts";
11
+ import { cn } from "@optilogic/core";
12
+ import { getChartColor } from "../shared/colors";
13
+ import { resolveTooltipProps, ChartTooltipContent } from "../shared/chart-tooltip";
14
+ import { ChartLegendContent, resolveLegendConfig } from "../shared/chart-legend";
15
+ import { ChartContainer } from "../shared/chart-container";
16
+ import type {
17
+ BaseChartProps,
18
+ ChartXAxis,
19
+ ChartYAxis,
20
+ ChartGrid,
21
+ ChartLegend,
22
+ ChartTooltipProp,
23
+ } from "../shared/types";
24
+
25
+ export interface LineChartSeries {
26
+ dataKey: string;
27
+ name: string;
28
+ color?: string;
29
+ strokeWidth?: number;
30
+ dot?: boolean;
31
+ type?: "monotone" | "linear" | "step" | "basis" | "natural";
32
+ strokeDasharray?: string;
33
+ }
34
+
35
+ export interface LineChartProps extends BaseChartProps {
36
+ data: Record<string, unknown>[];
37
+ series: LineChartSeries[];
38
+ xAxis: ChartXAxis;
39
+ yAxis?: ChartYAxis;
40
+ grid?: boolean | ChartGrid;
41
+ legend?: boolean | ChartLegend;
42
+ tooltip?: ChartTooltipProp;
43
+ loading?: boolean;
44
+ }
45
+
46
+ const LineChart = React.forwardRef<HTMLDivElement, LineChartProps>(
47
+ (
48
+ {
49
+ data,
50
+ series,
51
+ xAxis,
52
+ yAxis,
53
+ grid = true,
54
+ tooltip = true,
55
+ legend = false,
56
+ animate = false,
57
+ live = false,
58
+ className,
59
+ height = "100%",
60
+ margin = { top: 8, right: 12, left: 0, bottom: 4 },
61
+ loading,
62
+ },
63
+ ref,
64
+ ) => {
65
+ const gridConfig = React.useMemo(() => {
66
+ if (grid === false) return null;
67
+ if (grid === true) return { vertical: false, horizontal: true };
68
+ return grid;
69
+ }, [grid]);
70
+
71
+ const legendConfig = resolveLegendConfig(legend);
72
+ const tooltipProps = resolveTooltipProps(tooltip);
73
+ const isAnimated = live ? false : animate;
74
+
75
+ return (
76
+ <ChartContainer
77
+ ref={ref}
78
+ className={className}
79
+ height={height}
80
+ loading={loading}
81
+ empty={!data?.length}
82
+ >
83
+ <RechartsLineChart data={data} margin={margin}>
84
+ {gridConfig && (
85
+ <CartesianGrid
86
+ strokeDasharray="3 3"
87
+ stroke="hsl(var(--divider))"
88
+ vertical={gridConfig.vertical ?? false}
89
+ horizontal={gridConfig.horizontal ?? true}
90
+ />
91
+ )}
92
+ <XAxis
93
+ dataKey={xAxis.dataKey}
94
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
95
+ tickLine={false}
96
+ axisLine={false}
97
+ minTickGap={xAxis.minTickGap ?? 24}
98
+ tickFormatter={xAxis.tickFormatter}
99
+ label={
100
+ xAxis.label
101
+ ? {
102
+ value: xAxis.label,
103
+ position: "insideBottom",
104
+ offset: -4,
105
+ fontSize: 11,
106
+ fill: "hsl(var(--muted-foreground))",
107
+ }
108
+ : undefined
109
+ }
110
+ />
111
+ <YAxis
112
+ domain={yAxis?.domain ?? ["auto", "auto"]}
113
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
114
+ tickLine={false}
115
+ axisLine={false}
116
+ width={yAxis?.width ?? 30}
117
+ tickFormatter={yAxis?.tickFormatter}
118
+ label={
119
+ yAxis?.label
120
+ ? {
121
+ value: yAxis.label,
122
+ angle: -90,
123
+ position: "insideLeft",
124
+ fontSize: 11,
125
+ fill: "hsl(var(--muted-foreground))",
126
+ }
127
+ : undefined
128
+ }
129
+ />
130
+ {tooltipProps && <Tooltip {...tooltipProps} />}
131
+ {legendConfig && (
132
+ <Legend
133
+ verticalAlign={legendConfig.position ?? "top"}
134
+ align={legendConfig.align ?? "right"}
135
+ content={<ChartLegendContent />}
136
+ />
137
+ )}
138
+ {series.map((s, index) => (
139
+ <Line
140
+ key={s.dataKey}
141
+ type={s.type ?? "monotone"}
142
+ dataKey={s.dataKey}
143
+ name={s.name}
144
+ stroke={getChartColor(index, s.color)}
145
+ strokeWidth={s.strokeWidth ?? 2}
146
+ strokeDasharray={s.strokeDasharray}
147
+ dot={s.dot ?? false}
148
+ isAnimationActive={isAnimated}
149
+ animationDuration={isAnimated ? 300 : 0}
150
+ />
151
+ ))}
152
+ </RechartsLineChart>
153
+ </ChartContainer>
154
+ );
155
+ },
156
+ );
157
+
158
+ LineChart.displayName = "LineChart";
159
+ export { LineChart };
@@ -0,0 +1,158 @@
1
+ import * as React from "react";
2
+ import {
3
+ ScatterChart as RechartsScatterChart,
4
+ Scatter,
5
+ XAxis,
6
+ YAxis,
7
+ ZAxis,
8
+ CartesianGrid,
9
+ Tooltip,
10
+ Legend,
11
+ } from "recharts";
12
+ import { getChartColor } from "../shared/colors";
13
+ import { resolveTooltipProps } from "../shared/chart-tooltip";
14
+ import { ChartLegendContent, resolveLegendConfig } from "../shared/chart-legend";
15
+ import { ChartContainer } from "../shared/chart-container";
16
+ import type {
17
+ BaseChartProps,
18
+ ChartXAxis,
19
+ ChartYAxis,
20
+ ChartGrid,
21
+ ChartLegend,
22
+ ChartTooltipProp,
23
+ } from "../shared/types";
24
+
25
+ export interface ScatterChartSeries {
26
+ name: string;
27
+ data: Record<string, unknown>[];
28
+ color?: string;
29
+ /** Key for bubble size (optional, creates bubble chart) */
30
+ zDataKey?: string;
31
+ }
32
+
33
+ export interface ScatterChartProps extends BaseChartProps {
34
+ series: ScatterChartSeries[];
35
+ xAxis: ChartXAxis & { name?: string };
36
+ yAxis?: ChartYAxis & { name?: string };
37
+ zAxis?: { range?: [number, number] };
38
+ grid?: boolean | ChartGrid;
39
+ legend?: boolean | ChartLegend;
40
+ tooltip?: ChartTooltipProp;
41
+ loading?: boolean;
42
+ }
43
+
44
+ const ScatterChart = React.forwardRef<HTMLDivElement, ScatterChartProps>(
45
+ (
46
+ {
47
+ series,
48
+ xAxis,
49
+ yAxis,
50
+ zAxis,
51
+ grid = true,
52
+ tooltip = true,
53
+ legend = false,
54
+ animate = true,
55
+ live = false,
56
+ className,
57
+ height = "100%",
58
+ margin = { top: 8, right: 12, left: 0, bottom: 4 },
59
+ loading,
60
+ },
61
+ ref,
62
+ ) => {
63
+ const gridConfig = React.useMemo(() => {
64
+ if (grid === false) return null;
65
+ if (grid === true) return { vertical: true, horizontal: true };
66
+ return grid;
67
+ }, [grid]);
68
+
69
+ const legendConfig = resolveLegendConfig(legend);
70
+ const tooltipProps = resolveTooltipProps(tooltip);
71
+ const isAnimated = live ? false : animate;
72
+ const hasZ = series.some((s) => s.zDataKey);
73
+
74
+ return (
75
+ <ChartContainer
76
+ ref={ref}
77
+ className={className}
78
+ height={height}
79
+ loading={loading}
80
+ empty={!series.some((s) => s.data?.length)}
81
+ >
82
+ <RechartsScatterChart margin={margin}>
83
+ {gridConfig && (
84
+ <CartesianGrid
85
+ strokeDasharray="3 3"
86
+ stroke="hsl(var(--divider))"
87
+ vertical={gridConfig.vertical ?? true}
88
+ horizontal={gridConfig.horizontal ?? true}
89
+ />
90
+ )}
91
+ <XAxis
92
+ dataKey={xAxis.dataKey}
93
+ name={xAxis.name ?? xAxis.dataKey}
94
+ type="number"
95
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
96
+ tickLine={false}
97
+ axisLine={false}
98
+ tickFormatter={xAxis.tickFormatter}
99
+ label={
100
+ xAxis.label
101
+ ? {
102
+ value: xAxis.label,
103
+ position: "insideBottom",
104
+ offset: -4,
105
+ fontSize: 11,
106
+ fill: "hsl(var(--muted-foreground))",
107
+ }
108
+ : undefined
109
+ }
110
+ />
111
+ <YAxis
112
+ dataKey={yAxis?.domain ? undefined : "y"}
113
+ name={yAxis?.name}
114
+ type="number"
115
+ domain={yAxis?.domain ?? ["auto", "auto"]}
116
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
117
+ tickLine={false}
118
+ axisLine={false}
119
+ width={yAxis?.width ?? 40}
120
+ tickFormatter={yAxis?.tickFormatter}
121
+ label={
122
+ yAxis?.label
123
+ ? {
124
+ value: yAxis.label,
125
+ angle: -90,
126
+ position: "insideLeft",
127
+ fontSize: 11,
128
+ fill: "hsl(var(--muted-foreground))",
129
+ }
130
+ : undefined
131
+ }
132
+ />
133
+ {hasZ && <ZAxis range={zAxis?.range ?? [40, 400]} />}
134
+ {tooltipProps && <Tooltip {...tooltipProps} />}
135
+ {legendConfig && (
136
+ <Legend
137
+ verticalAlign={legendConfig.position ?? "top"}
138
+ align={legendConfig.align ?? "right"}
139
+ content={<ChartLegendContent />}
140
+ />
141
+ )}
142
+ {series.map((s, index) => (
143
+ <Scatter
144
+ key={s.name}
145
+ name={s.name}
146
+ data={s.data}
147
+ fill={getChartColor(index, s.color)}
148
+ isAnimationActive={isAnimated}
149
+ />
150
+ ))}
151
+ </RechartsScatterChart>
152
+ </ChartContainer>
153
+ );
154
+ },
155
+ );
156
+
157
+ ScatterChart.displayName = "ScatterChart";
158
+ export { ScatterChart };
@@ -0,0 +1,171 @@
1
+ import * as React from "react";
2
+ import {
3
+ BarChart as RechartsBarChart,
4
+ Bar,
5
+ XAxis,
6
+ YAxis,
7
+ CartesianGrid,
8
+ Tooltip,
9
+ Cell,
10
+ ReferenceLine,
11
+ } from "recharts";
12
+ import { getSemanticColor } from "../shared/colors";
13
+ import { resolveTooltipProps } from "../shared/chart-tooltip";
14
+ import { ChartContainer } from "../shared/chart-container";
15
+ import type { BaseChartProps, ChartXAxis, ChartYAxis, ChartTooltipProp } from "../shared/types";
16
+
17
+ export interface WaterfallItem {
18
+ name: string;
19
+ value: number;
20
+ /** Mark as a summary/total bar rather than a delta */
21
+ isTotal?: boolean;
22
+ }
23
+
24
+ export interface WaterfallChartProps extends BaseChartProps {
25
+ data: WaterfallItem[];
26
+ xAxis?: Partial<ChartXAxis>;
27
+ yAxis?: ChartYAxis;
28
+ tooltip?: ChartTooltipProp;
29
+ positiveColor?: string;
30
+ negativeColor?: string;
31
+ totalColor?: string;
32
+ loading?: boolean;
33
+ }
34
+
35
+ interface WaterfallRow {
36
+ name: string;
37
+ invisible: number;
38
+ delta: number;
39
+ isTotal: boolean;
40
+ rawValue: number;
41
+ }
42
+
43
+ function buildWaterfallData(items: WaterfallItem[]): WaterfallRow[] {
44
+ let running = 0;
45
+ return items.map((item) => {
46
+ if (item.isTotal) {
47
+ const row: WaterfallRow = {
48
+ name: item.name,
49
+ invisible: 0,
50
+ delta: running,
51
+ isTotal: true,
52
+ rawValue: running,
53
+ };
54
+ return row;
55
+ }
56
+ const start = running;
57
+ running += item.value;
58
+ const base = item.value >= 0 ? start : running;
59
+ return {
60
+ name: item.name,
61
+ invisible: base,
62
+ delta: Math.abs(item.value),
63
+ isTotal: false,
64
+ rawValue: item.value,
65
+ };
66
+ });
67
+ }
68
+
69
+ const WaterfallChart = React.forwardRef<HTMLDivElement, WaterfallChartProps>(
70
+ (
71
+ {
72
+ data,
73
+ xAxis,
74
+ yAxis,
75
+ tooltip = true,
76
+ positiveColor,
77
+ negativeColor,
78
+ totalColor,
79
+ animate = true,
80
+ live = false,
81
+ className,
82
+ height = "100%",
83
+ margin = { top: 8, right: 12, left: 0, bottom: 4 },
84
+ loading,
85
+ },
86
+ ref,
87
+ ) => {
88
+ const rows = React.useMemo(() => buildWaterfallData(data), [data]);
89
+ const tooltipProps = resolveTooltipProps(tooltip);
90
+ const isAnimated = live ? false : animate;
91
+
92
+ const posColor = positiveColor ?? getSemanticColor("positive");
93
+ const negColor = negativeColor ?? getSemanticColor("negative");
94
+ const totColor = totalColor ?? "hsl(var(--chart-3))";
95
+
96
+ return (
97
+ <ChartContainer
98
+ ref={ref}
99
+ className={className}
100
+ height={height}
101
+ loading={loading}
102
+ empty={!data?.length}
103
+ >
104
+ <RechartsBarChart data={rows} margin={margin}>
105
+ <CartesianGrid
106
+ strokeDasharray="3 3"
107
+ stroke="hsl(var(--divider))"
108
+ vertical={false}
109
+ />
110
+ <XAxis
111
+ dataKey="name"
112
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
113
+ tickLine={false}
114
+ axisLine={false}
115
+ tickFormatter={xAxis?.tickFormatter}
116
+ />
117
+ <YAxis
118
+ domain={yAxis?.domain ?? ["auto", "auto"]}
119
+ tick={{ fontSize: 10, fill: "hsl(var(--muted-foreground))" }}
120
+ tickLine={false}
121
+ axisLine={false}
122
+ width={yAxis?.width ?? 40}
123
+ tickFormatter={yAxis?.tickFormatter}
124
+ label={
125
+ yAxis?.label
126
+ ? {
127
+ value: yAxis.label,
128
+ angle: -90,
129
+ position: "insideLeft",
130
+ fontSize: 11,
131
+ fill: "hsl(var(--muted-foreground))",
132
+ }
133
+ : undefined
134
+ }
135
+ />
136
+ <ReferenceLine y={0} stroke="hsl(var(--divider))" />
137
+ {tooltipProps && <Tooltip {...tooltipProps} />}
138
+ <Bar
139
+ dataKey="invisible"
140
+ stackId="waterfall"
141
+ fill="transparent"
142
+ isAnimationActive={false}
143
+ />
144
+ <Bar
145
+ dataKey="delta"
146
+ stackId="waterfall"
147
+ isAnimationActive={isAnimated}
148
+ animationDuration={isAnimated ? 300 : 0}
149
+ radius={[2, 2, 0, 0]}
150
+ >
151
+ {rows.map((row, i) => (
152
+ <Cell
153
+ key={i}
154
+ fill={
155
+ row.isTotal
156
+ ? totColor
157
+ : row.rawValue >= 0
158
+ ? posColor
159
+ : negColor
160
+ }
161
+ />
162
+ ))}
163
+ </Bar>
164
+ </RechartsBarChart>
165
+ </ChartContainer>
166
+ );
167
+ },
168
+ );
169
+
170
+ WaterfallChart.displayName = "WaterfallChart";
171
+ export { WaterfallChart };