@matthieumordrel/chart-studio 0.2.1 → 0.2.3

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 (175) hide show
  1. package/README.md +24 -0
  2. package/dist/core/chart-capabilities.d.mts +48 -0
  3. package/dist/core/chart-capabilities.mjs +55 -0
  4. package/dist/core/{colors.d.ts → colors.d.mts} +5 -3
  5. package/dist/core/colors.mjs +55 -0
  6. package/dist/core/config-utils.mjs +79 -0
  7. package/dist/core/date-utils.mjs +49 -0
  8. package/dist/core/define-chart-schema.d.mts +106 -0
  9. package/dist/core/define-chart-schema.mjs +47 -0
  10. package/dist/core/formatting.mjs +349 -0
  11. package/dist/core/infer-columns.d.mts +9 -0
  12. package/dist/core/infer-columns.mjs +481 -0
  13. package/dist/core/metric-utils.d.mts +13 -0
  14. package/dist/core/metric-utils.mjs +121 -0
  15. package/dist/core/pipeline-data-points.mjs +212 -0
  16. package/dist/core/pipeline-helpers.mjs +85 -0
  17. package/dist/core/{pipeline.d.ts → pipeline.d.mts} +21 -24
  18. package/dist/core/pipeline.mjs +153 -0
  19. package/dist/core/types.d.mts +957 -0
  20. package/dist/core/use-chart-options.d.mts +64 -0
  21. package/dist/core/use-chart-options.mjs +7 -0
  22. package/dist/core/use-chart-resolvers.mjs +34 -0
  23. package/dist/core/{use-chart.d.ts → use-chart.d.mts} +12 -9
  24. package/dist/core/use-chart.mjs +299 -0
  25. package/dist/index.d.mts +10 -0
  26. package/dist/index.mjs +8 -0
  27. package/dist/ui/chart-axis-ticks.mjs +65 -0
  28. package/dist/ui/{chart-canvas.d.ts → chart-canvas.d.mts} +13 -6
  29. package/dist/ui/chart-canvas.mjs +461 -0
  30. package/dist/ui/chart-context.d.mts +92 -0
  31. package/dist/ui/chart-context.mjs +112 -0
  32. package/dist/ui/{chart-date-range-badge.d.ts → chart-date-range-badge.d.mts} +10 -4
  33. package/dist/ui/chart-date-range-badge.mjs +49 -0
  34. package/dist/ui/chart-date-range-panel.d.mts +18 -0
  35. package/dist/ui/chart-date-range-panel.mjs +208 -0
  36. package/dist/ui/{chart-date-range.d.ts → chart-date-range.d.mts} +10 -4
  37. package/dist/ui/chart-date-range.mjs +67 -0
  38. package/dist/ui/chart-debug.d.mts +17 -0
  39. package/dist/ui/chart-debug.mjs +169 -0
  40. package/dist/ui/chart-dropdown.mjs +92 -0
  41. package/dist/ui/{chart-filters-panel.d.ts → chart-filters-panel.d.mts} +12 -5
  42. package/dist/ui/chart-filters-panel.mjs +132 -0
  43. package/dist/ui/{chart-filters.d.ts → chart-filters.d.mts} +10 -4
  44. package/dist/ui/chart-filters.mjs +48 -0
  45. package/dist/ui/chart-group-by-selector.d.mts +14 -0
  46. package/dist/ui/chart-group-by-selector.mjs +29 -0
  47. package/dist/ui/{chart-metric-panel.d.ts → chart-metric-panel.d.mts} +12 -5
  48. package/dist/ui/chart-metric-panel.mjs +172 -0
  49. package/dist/ui/chart-metric-selector.d.mts +16 -0
  50. package/dist/ui/chart-metric-selector.mjs +50 -0
  51. package/dist/ui/chart-select.mjs +62 -0
  52. package/dist/ui/{chart-source-switcher.d.ts → chart-source-switcher.d.mts} +10 -4
  53. package/dist/ui/chart-source-switcher.mjs +54 -0
  54. package/dist/ui/chart-time-bucket-selector.d.mts +15 -0
  55. package/dist/ui/chart-time-bucket-selector.mjs +34 -0
  56. package/dist/ui/chart-toolbar-overflow.d.mts +28 -0
  57. package/dist/ui/chart-toolbar-overflow.mjs +209 -0
  58. package/dist/ui/chart-toolbar.d.mts +29 -0
  59. package/dist/ui/chart-toolbar.mjs +56 -0
  60. package/dist/ui/chart-type-selector.d.mts +14 -0
  61. package/dist/ui/chart-type-selector.mjs +33 -0
  62. package/dist/ui/chart-x-axis-selector.d.mts +14 -0
  63. package/dist/ui/chart-x-axis-selector.mjs +25 -0
  64. package/dist/ui/index.d.mts +19 -0
  65. package/dist/ui/index.mjs +18 -0
  66. package/dist/ui/toolbar-types.d.mts +7 -0
  67. package/dist/ui/toolbar-types.mjs +83 -0
  68. package/package.json +11 -10
  69. package/dist/core/chart-capabilities.d.ts +0 -60
  70. package/dist/core/chart-capabilities.d.ts.map +0 -1
  71. package/dist/core/chart-capabilities.js +0 -54
  72. package/dist/core/colors.d.ts.map +0 -1
  73. package/dist/core/colors.js +0 -54
  74. package/dist/core/config-utils.d.ts +0 -43
  75. package/dist/core/config-utils.d.ts.map +0 -1
  76. package/dist/core/config-utils.js +0 -80
  77. package/dist/core/date-utils.d.ts +0 -29
  78. package/dist/core/date-utils.d.ts.map +0 -1
  79. package/dist/core/date-utils.js +0 -58
  80. package/dist/core/define-chart-schema.d.ts +0 -105
  81. package/dist/core/define-chart-schema.d.ts.map +0 -1
  82. package/dist/core/define-chart-schema.js +0 -44
  83. package/dist/core/formatting.d.ts +0 -47
  84. package/dist/core/formatting.d.ts.map +0 -1
  85. package/dist/core/formatting.js +0 -396
  86. package/dist/core/index.d.ts +0 -17
  87. package/dist/core/index.d.ts.map +0 -1
  88. package/dist/core/index.js +0 -12
  89. package/dist/core/infer-columns.d.ts +0 -6
  90. package/dist/core/infer-columns.d.ts.map +0 -1
  91. package/dist/core/infer-columns.js +0 -512
  92. package/dist/core/metric-utils.d.ts +0 -43
  93. package/dist/core/metric-utils.d.ts.map +0 -1
  94. package/dist/core/metric-utils.js +0 -141
  95. package/dist/core/pipeline-data-points.d.ts +0 -23
  96. package/dist/core/pipeline-data-points.d.ts.map +0 -1
  97. package/dist/core/pipeline-data-points.js +0 -235
  98. package/dist/core/pipeline-helpers.d.ts +0 -38
  99. package/dist/core/pipeline-helpers.d.ts.map +0 -1
  100. package/dist/core/pipeline-helpers.js +0 -97
  101. package/dist/core/pipeline.d.ts.map +0 -1
  102. package/dist/core/pipeline.js +0 -156
  103. package/dist/core/types.d.ts +0 -1109
  104. package/dist/core/types.d.ts.map +0 -1
  105. package/dist/core/types.js +0 -14
  106. package/dist/core/use-chart-options.d.ts +0 -66
  107. package/dist/core/use-chart-options.d.ts.map +0 -1
  108. package/dist/core/use-chart-options.js +0 -4
  109. package/dist/core/use-chart-resolvers.d.ts +0 -14
  110. package/dist/core/use-chart-resolvers.d.ts.map +0 -1
  111. package/dist/core/use-chart-resolvers.js +0 -41
  112. package/dist/core/use-chart.d.ts.map +0 -1
  113. package/dist/core/use-chart.js +0 -265
  114. package/dist/index.d.ts +0 -36
  115. package/dist/index.d.ts.map +0 -1
  116. package/dist/index.js +0 -35
  117. package/dist/ui/chart-axis-ticks.d.ts +0 -35
  118. package/dist/ui/chart-axis-ticks.d.ts.map +0 -1
  119. package/dist/ui/chart-axis-ticks.js +0 -79
  120. package/dist/ui/chart-canvas.d.ts.map +0 -1
  121. package/dist/ui/chart-canvas.js +0 -337
  122. package/dist/ui/chart-context.d.ts +0 -89
  123. package/dist/ui/chart-context.d.ts.map +0 -1
  124. package/dist/ui/chart-context.js +0 -128
  125. package/dist/ui/chart-date-range-badge.d.ts.map +0 -1
  126. package/dist/ui/chart-date-range-badge.js +0 -30
  127. package/dist/ui/chart-date-range-panel.d.ts +0 -25
  128. package/dist/ui/chart-date-range-panel.d.ts.map +0 -1
  129. package/dist/ui/chart-date-range-panel.js +0 -125
  130. package/dist/ui/chart-date-range.d.ts.map +0 -1
  131. package/dist/ui/chart-date-range.js +0 -37
  132. package/dist/ui/chart-debug.d.ts +0 -10
  133. package/dist/ui/chart-debug.d.ts.map +0 -1
  134. package/dist/ui/chart-debug.js +0 -126
  135. package/dist/ui/chart-dropdown.d.ts +0 -35
  136. package/dist/ui/chart-dropdown.d.ts.map +0 -1
  137. package/dist/ui/chart-dropdown.js +0 -76
  138. package/dist/ui/chart-filters-panel.d.ts.map +0 -1
  139. package/dist/ui/chart-filters-panel.js +0 -46
  140. package/dist/ui/chart-filters.d.ts.map +0 -1
  141. package/dist/ui/chart-filters.js +0 -26
  142. package/dist/ui/chart-group-by-selector.d.ts +0 -8
  143. package/dist/ui/chart-group-by-selector.d.ts.map +0 -1
  144. package/dist/ui/chart-group-by-selector.js +0 -19
  145. package/dist/ui/chart-metric-panel.d.ts.map +0 -1
  146. package/dist/ui/chart-metric-panel.js +0 -118
  147. package/dist/ui/chart-metric-selector.d.ts +0 -10
  148. package/dist/ui/chart-metric-selector.d.ts.map +0 -1
  149. package/dist/ui/chart-metric-selector.js +0 -27
  150. package/dist/ui/chart-select.d.ts +0 -25
  151. package/dist/ui/chart-select.d.ts.map +0 -1
  152. package/dist/ui/chart-select.js +0 -35
  153. package/dist/ui/chart-source-switcher.d.ts.map +0 -1
  154. package/dist/ui/chart-source-switcher.js +0 -31
  155. package/dist/ui/chart-time-bucket-selector.d.ts +0 -9
  156. package/dist/ui/chart-time-bucket-selector.d.ts.map +0 -1
  157. package/dist/ui/chart-time-bucket-selector.js +0 -25
  158. package/dist/ui/chart-toolbar-overflow.d.ts +0 -29
  159. package/dist/ui/chart-toolbar-overflow.d.ts.map +0 -1
  160. package/dist/ui/chart-toolbar-overflow.js +0 -109
  161. package/dist/ui/chart-toolbar.d.ts +0 -45
  162. package/dist/ui/chart-toolbar.d.ts.map +0 -1
  163. package/dist/ui/chart-toolbar.js +0 -44
  164. package/dist/ui/chart-type-selector.d.ts +0 -8
  165. package/dist/ui/chart-type-selector.d.ts.map +0 -1
  166. package/dist/ui/chart-type-selector.js +0 -22
  167. package/dist/ui/chart-x-axis-selector.d.ts +0 -8
  168. package/dist/ui/chart-x-axis-selector.d.ts.map +0 -1
  169. package/dist/ui/chart-x-axis-selector.js +0 -14
  170. package/dist/ui/index.d.ts +0 -25
  171. package/dist/ui/index.d.ts.map +0 -1
  172. package/dist/ui/index.js +0 -23
  173. package/dist/ui/toolbar-types.d.ts +0 -43
  174. package/dist/ui/toolbar-types.d.ts.map +0 -1
  175. package/dist/ui/toolbar-types.js +0 -50
@@ -0,0 +1,461 @@
1
+ import { getSeriesColor } from "../core/colors.mjs";
2
+ import { createNumericRange, formatChartValue, formatTimeBucketLabel, shouldAllowDecimalTicks } from "../core/formatting.mjs";
3
+ import { useChartContext } from "./chart-context.mjs";
4
+ import { selectVisibleXAxisTicks } from "./chart-axis-ticks.mjs";
5
+ import { useEffect, useRef, useState } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ import { Area, AreaChart, Bar, BarChart, CartesianGrid, LabelList, Legend, Line, LineChart, Pie, PieChart, Tooltip, XAxis, YAxis } from "recharts";
8
+ //#region src/ui/chart-canvas.tsx
9
+ /**
10
+ * Chart canvas — renders the actual recharts chart based on the current state.
11
+ *
12
+ * Supports: bar, line, area (time-series), bar, pie, donut (categorical).
13
+ * Automatically switches between chart types based on the chart instance state.
14
+ */
15
+ /**
16
+ * Estimates the pixel width the YAxis needs so no label is ever clipped.
17
+ * Considers both the visible range and the likely "nice" axis boundary Recharts
18
+ * may choose above it, then measures the widest formatted label and adds a
19
+ * small gutter for tick spacing.
20
+ */
21
+ function estimateYAxisWidth(numericRange, valueColumn) {
22
+ const widestLabel = getYAxisLabelCandidates(numericRange, valueColumn).reduce((maxWidth, label) => Math.max(maxWidth, measureAxisLabelWidth(label)), 0);
23
+ return Math.max(MIN_Y_AXIS_WIDTH, Math.ceil(widestLabel + Y_AXIS_WIDTH_GUTTER));
24
+ }
25
+ /**
26
+ * Font used by the shared chart axis ticks (`text-xs` in the default theme).
27
+ */
28
+ const AXIS_TICK_FONT = "12px system-ui, sans-serif";
29
+ /**
30
+ * Canvas measurement can be unavailable in some environments, so keep a
31
+ * slightly generous character-width fallback.
32
+ */
33
+ const FALLBACK_AXIS_CHARACTER_WIDTH = 8;
34
+ /**
35
+ * Minimum width so short numeric axes still have breathing room.
36
+ */
37
+ const MIN_Y_AXIS_WIDTH = 48;
38
+ /**
39
+ * Extra space for tick margin plus a small anti-clipping buffer.
40
+ */
41
+ const Y_AXIS_WIDTH_GUTTER = 18;
42
+ /**
43
+ * Horizontal breathing room kept between two visible X-axis labels.
44
+ */
45
+ const X_AXIS_MINIMUM_TICK_GAP = 8;
46
+ /**
47
+ * Build a small set of realistic axis labels and size for the widest one.
48
+ * This catches cases where the chart data tops out below the rounded axis
49
+ * tick, such as `950` minutes producing a `1000` minute top tick.
50
+ */
51
+ function getYAxisLabelCandidates(numericRange, valueColumn) {
52
+ return getYAxisCandidateValues(numericRange).map((value) => formatChartValue(value, {
53
+ column: valueColumn,
54
+ surface: "axis",
55
+ numericRange
56
+ }));
57
+ }
58
+ /**
59
+ * Include the visible extrema plus rounded axis boundaries so the width
60
+ * estimate matches what the axis is likely to render.
61
+ */
62
+ function getYAxisCandidateValues(numericRange) {
63
+ if (!numericRange) return [0];
64
+ const maxAbs = Math.max(Math.abs(numericRange.min), Math.abs(numericRange.max));
65
+ const niceMaxAbs = getNiceAxisBoundary(maxAbs);
66
+ const values = new Set([
67
+ 0,
68
+ numericRange.min,
69
+ numericRange.max,
70
+ maxAbs,
71
+ niceMaxAbs
72
+ ]);
73
+ if (numericRange.min < 0) values.add(-niceMaxAbs);
74
+ return Array.from(values);
75
+ }
76
+ /**
77
+ * Approximate the rounded outer tick value chart libraries tend to choose for
78
+ * a human-friendly numeric axis.
79
+ */
80
+ function getNiceAxisBoundary(value) {
81
+ if (!Number.isFinite(value) || value <= 0) return 0;
82
+ const magnitude = 10 ** Math.floor(Math.log10(value));
83
+ const fraction = value / magnitude;
84
+ if (fraction <= 1) return magnitude;
85
+ if (fraction <= 2) return 2 * magnitude;
86
+ if (fraction <= 5) return 5 * magnitude;
87
+ return 10 * magnitude;
88
+ }
89
+ /**
90
+ * Measure axis text width in the browser and fall back to a safe character
91
+ * estimate in non-DOM environments.
92
+ */
93
+ function measureAxisLabelWidth(label) {
94
+ if (typeof document === "undefined") return label.length * FALLBACK_AXIS_CHARACTER_WIDTH;
95
+ const context = document.createElement("canvas").getContext("2d");
96
+ if (!context) return label.length * FALLBACK_AXIS_CHARACTER_WIDTH;
97
+ context.font = AXIS_TICK_FONT;
98
+ return context.measureText(label).width;
99
+ }
100
+ /**
101
+ * Approximate the drawable X-axis width after margins, Y-axis labels, and axis
102
+ * padding have taken their share of the SVG width.
103
+ */
104
+ function getCartesianPlotWidth(totalWidth, yAxisWidth) {
105
+ return Math.max(1, totalWidth - yAxisWidth - CARTESIAN_BASE_MARGIN.left - CARTESIAN_BASE_MARGIN.right - CARTESIAN_X_AXIS_PADDING.left - CARTESIAN_X_AXIS_PADDING.right);
106
+ }
107
+ /**
108
+ * Resolve the raw categorical tick value used by Recharts for one transformed
109
+ * pipeline point.
110
+ */
111
+ function getXAxisTickValue(point) {
112
+ return typeof point["xKey"] === "string" || typeof point["xKey"] === "number" ? point["xKey"] : String(point["xLabel"]);
113
+ }
114
+ /**
115
+ * Important recharts styling — mirrors shadcn's ChartContainer CSS.
116
+ * Ensures proper text colors, grid lines, and outline handling.
117
+ */
118
+ const RECHARTS_STYLES = [
119
+ "text-xs",
120
+ "[&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground",
121
+ "[&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50",
122
+ "[&_.recharts-curve.recharts-tooltip-cursor]:stroke-border",
123
+ "[&_.recharts-layer]:outline-hidden",
124
+ "[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted",
125
+ "[&_.recharts-reference-line_[stroke='#ccc']]:stroke-border",
126
+ "[&_.recharts-sector]:outline-hidden",
127
+ "[&_.recharts-surface]:outline-hidden"
128
+ ].join(" ");
129
+ /**
130
+ * Reserve enough top padding for the tallest bar/point label plus its offset so
131
+ * data labels never clip against the SVG edge.
132
+ */
133
+ const CARTESIAN_BASE_MARGIN = {
134
+ top: 4,
135
+ right: 8,
136
+ left: 0,
137
+ bottom: 0
138
+ };
139
+ /**
140
+ * Vertical headroom required for one top-positioned cartesian data label.
141
+ */
142
+ const CARTESIAN_DATA_LABEL_TOP_CLEARANCE = 28;
143
+ /**
144
+ * Expand the cartesian chart margin only when top-positioned data labels are
145
+ * enabled.
146
+ */
147
+ function getCartesianChartMargin(showDataLabels) {
148
+ return showDataLabels ? {
149
+ ...CARTESIAN_BASE_MARGIN,
150
+ top: CARTESIAN_BASE_MARGIN.top + CARTESIAN_DATA_LABEL_TOP_CLEARANCE
151
+ } : CARTESIAN_BASE_MARGIN;
152
+ }
153
+ /**
154
+ * Keep the first and last rendered values from hugging the chart edges.
155
+ */
156
+ const CARTESIAN_X_AXIS_PADDING = {
157
+ left: 12,
158
+ right: 12
159
+ };
160
+ /**
161
+ * Hook that measures a container's width using ResizeObserver.
162
+ * Avoids the ResponsiveContainer issues with flexbox/grid layouts.
163
+ */
164
+ function useContainerWidth() {
165
+ const ref = useRef(null);
166
+ const [width, setWidth] = useState(0);
167
+ useEffect(() => {
168
+ const el = ref.current;
169
+ if (!el) return;
170
+ const observer = new ResizeObserver((entries) => {
171
+ const entry = entries[0];
172
+ if (entry) setWidth(entry.contentRect.width);
173
+ });
174
+ observer.observe(el);
175
+ return () => observer.disconnect();
176
+ }, []);
177
+ return {
178
+ ref,
179
+ width
180
+ };
181
+ }
182
+ /** Renders the appropriate recharts chart based on the chart instance state. */
183
+ function ChartCanvas({ height = 300, className, showDataLabels = false }) {
184
+ const chart = useChartContext();
185
+ const { chartType, transformedData, series } = chart;
186
+ const { ref, width } = useContainerWidth();
187
+ const xColumn = chart.columns.find((column) => column.id === chart.xAxisId) ?? null;
188
+ const aggregateMetric = chart.metric.kind === "aggregate" ? chart.metric : null;
189
+ const valueColumn = (aggregateMetric ? chart.columns.find((column) => column.id === aggregateMetric.columnId && column.type === "number") : null) ?? {
190
+ type: "number",
191
+ format: void 0
192
+ };
193
+ const numericValues = transformedData.flatMap((point) => series.flatMap((seriesItem) => {
194
+ const value = point[seriesItem.dataKey];
195
+ return typeof value === "number" ? [value] : [];
196
+ }));
197
+ const valueRange = createNumericRange(numericValues);
198
+ const allowDecimalTicks = shouldAllowDecimalTicks(numericValues);
199
+ if (transformedData.length === 0) return /* @__PURE__ */ jsx("div", {
200
+ ref,
201
+ className: `flex items-center justify-center text-sm text-muted-foreground ${className ?? ""}`,
202
+ style: { height },
203
+ children: "No data available"
204
+ });
205
+ return /* @__PURE__ */ jsx("div", {
206
+ ref,
207
+ className: `${RECHARTS_STYLES} ${className ?? ""}`,
208
+ style: { height },
209
+ children: width > 0 && (chartType === "pie" || chartType === "donut" ? /* @__PURE__ */ jsx(PieChartRenderer, {
210
+ data: transformedData,
211
+ series,
212
+ innerRadius: chartType === "donut",
213
+ width,
214
+ height,
215
+ valueColumn,
216
+ valueRange,
217
+ allowDecimalTicks,
218
+ xColumn,
219
+ timeBucket: chart.isTimeSeries ? chart.timeBucket : void 0,
220
+ showDataLabels
221
+ }) : chartType === "line" ? /* @__PURE__ */ jsx(LineChartRenderer, {
222
+ data: transformedData,
223
+ series,
224
+ width,
225
+ height,
226
+ valueColumn,
227
+ valueRange,
228
+ allowDecimalTicks,
229
+ xColumn,
230
+ timeBucket: chart.isTimeSeries ? chart.timeBucket : void 0,
231
+ showDataLabels
232
+ }) : chartType === "area" ? /* @__PURE__ */ jsx(AreaChartRenderer, {
233
+ data: transformedData,
234
+ series,
235
+ width,
236
+ height,
237
+ valueColumn,
238
+ valueRange,
239
+ allowDecimalTicks,
240
+ xColumn,
241
+ timeBucket: chart.isTimeSeries ? chart.timeBucket : void 0,
242
+ showDataLabels
243
+ }) : /* @__PURE__ */ jsx(BarChartRenderer, {
244
+ data: transformedData,
245
+ series,
246
+ width,
247
+ height,
248
+ valueColumn,
249
+ valueRange,
250
+ allowDecimalTicks,
251
+ xColumn,
252
+ timeBucket: chart.isTimeSeries ? chart.timeBucket : void 0,
253
+ showDataLabels
254
+ }))
255
+ });
256
+ }
257
+ /**
258
+ * Shared shell for all Cartesian chart types.
259
+ * Owns the grid, axes, tooltip, and legend — the only things that change
260
+ * per chart type are the root component and the series element.
261
+ */
262
+ function CartesianChartShell({ data, series, width, height, valueColumn, valueRange, allowDecimalTicks, xColumn, timeBucket, showDataLabels, Chart, renderSeries }) {
263
+ const yAxisWidth = estimateYAxisWidth(valueRange, valueColumn);
264
+ const xAxisTickValues = selectVisibleXAxisTicks({
265
+ values: data.map(getXAxisTickValue),
266
+ labels: data.map((point) => formatXAxisValue(getXAxisTickValue(point), xColumn, timeBucket, "axis")),
267
+ plotWidth: getCartesianPlotWidth(width, yAxisWidth),
268
+ minimumTickGap: X_AXIS_MINIMUM_TICK_GAP,
269
+ measureLabelWidth: measureAxisLabelWidth
270
+ });
271
+ return /* @__PURE__ */ jsxs(Chart, {
272
+ data,
273
+ width,
274
+ height,
275
+ margin: getCartesianChartMargin(showDataLabels),
276
+ children: [
277
+ /* @__PURE__ */ jsx(CartesianGrid, {
278
+ vertical: false,
279
+ strokeDasharray: "3 3"
280
+ }),
281
+ /* @__PURE__ */ jsx(XAxis, {
282
+ dataKey: "xKey",
283
+ tickLine: false,
284
+ axisLine: false,
285
+ tickMargin: 8,
286
+ interval: 0,
287
+ padding: CARTESIAN_X_AXIS_PADDING,
288
+ ticks: xAxisTickValues,
289
+ tickFormatter: (value) => formatXAxisValue(value, xColumn, timeBucket, "axis")
290
+ }),
291
+ /* @__PURE__ */ jsx(YAxis, {
292
+ tickLine: false,
293
+ axisLine: false,
294
+ tickMargin: 4,
295
+ allowDecimals: allowDecimalTicks,
296
+ width: yAxisWidth,
297
+ tickFormatter: (value) => typeof value === "number" ? formatChartValue(value, {
298
+ column: valueColumn,
299
+ surface: "axis",
300
+ numericRange: valueRange
301
+ }) : String(value)
302
+ }),
303
+ /* @__PURE__ */ jsx(Tooltip, {
304
+ formatter: (value) => typeof value === "number" ? formatChartValue(value, {
305
+ column: valueColumn,
306
+ surface: "tooltip",
307
+ numericRange: valueRange
308
+ }) : value,
309
+ labelFormatter: (label, payload) => formatTooltipLabel(label, payload, xColumn, timeBucket)
310
+ }),
311
+ series.length > 1 && /* @__PURE__ */ jsx(Legend, {}),
312
+ series.map(renderSeries)
313
+ ]
314
+ });
315
+ }
316
+ /**
317
+ * Resolve the most descriptive tooltip label from the transformed data point.
318
+ */
319
+ function formatTooltipLabel(label, payload, xColumn, timeBucket) {
320
+ if (!xColumn) return String(label);
321
+ const point = payload?.[0]?.payload;
322
+ return formatXAxisValue(typeof point?.["xKey"] === "string" || typeof point?.["xKey"] === "number" ? point["xKey"] : label, xColumn, timeBucket, "tooltip");
323
+ }
324
+ /**
325
+ * Format one X-axis value using the same shared column rules as the rest of the
326
+ * chart while preserving the special bucket labels for inferred date buckets.
327
+ */
328
+ function formatXAxisValue(value, xColumn, timeBucket, surface) {
329
+ if (!xColumn) return String(value);
330
+ if (xColumn.type === "date" && timeBucket && typeof value === "string" && !xColumn.formatter) return formatTimeBucketLabel(value, timeBucket, surface);
331
+ return formatChartValue(value, {
332
+ column: xColumn,
333
+ surface,
334
+ timeBucket
335
+ });
336
+ }
337
+ function BarChartRenderer(props) {
338
+ const { series, showDataLabels, valueColumn, valueRange } = props;
339
+ return /* @__PURE__ */ jsx(CartesianChartShell, {
340
+ ...props,
341
+ Chart: BarChart,
342
+ renderSeries: (s) => /* @__PURE__ */ jsx(Bar, {
343
+ dataKey: s.dataKey,
344
+ name: s.label,
345
+ fill: s.color,
346
+ radius: [
347
+ 4,
348
+ 4,
349
+ 0,
350
+ 0
351
+ ],
352
+ stackId: series.length > 1 ? "stack" : void 0,
353
+ children: showDataLabels && /* @__PURE__ */ jsx(LabelList, {
354
+ position: "top",
355
+ offset: 8,
356
+ formatter: (value) => formatDataLabel(value, valueColumn, valueRange)
357
+ })
358
+ }, s.dataKey)
359
+ });
360
+ }
361
+ function LineChartRenderer(props) {
362
+ const { showDataLabels, valueColumn, valueRange } = props;
363
+ return /* @__PURE__ */ jsx(CartesianChartShell, {
364
+ ...props,
365
+ Chart: LineChart,
366
+ renderSeries: (s) => /* @__PURE__ */ jsx(Line, {
367
+ type: "monotone",
368
+ dataKey: s.dataKey,
369
+ name: s.label,
370
+ stroke: s.color,
371
+ strokeWidth: 2,
372
+ dot: { r: 3 },
373
+ activeDot: { r: 5 },
374
+ children: showDataLabels && /* @__PURE__ */ jsx(LabelList, {
375
+ position: "top",
376
+ offset: 8,
377
+ formatter: (value) => formatDataLabel(value, valueColumn, valueRange)
378
+ })
379
+ }, s.dataKey)
380
+ });
381
+ }
382
+ function AreaChartRenderer(props) {
383
+ const { series, showDataLabels, valueColumn, valueRange } = props;
384
+ return /* @__PURE__ */ jsx(CartesianChartShell, {
385
+ ...props,
386
+ Chart: AreaChart,
387
+ renderSeries: (s) => /* @__PURE__ */ jsx(Area, {
388
+ type: "monotone",
389
+ dataKey: s.dataKey,
390
+ name: s.label,
391
+ stroke: s.color,
392
+ fill: s.color,
393
+ fillOpacity: .3,
394
+ stackId: series.length > 1 ? "stack" : void 0,
395
+ children: showDataLabels && /* @__PURE__ */ jsx(LabelList, {
396
+ position: "top",
397
+ offset: 8,
398
+ formatter: (value) => formatDataLabel(value, valueColumn, valueRange)
399
+ })
400
+ }, s.dataKey)
401
+ });
402
+ }
403
+ function PieChartRenderer({ data, series, innerRadius, width, height, valueColumn, valueRange, xColumn, timeBucket, showDataLabels }) {
404
+ const valueKey = series[0]?.dataKey;
405
+ const pieData = data.map((point, index) => {
406
+ return {
407
+ name: typeof point["xKey"] === "string" || typeof point["xKey"] === "number" ? formatXAxisValue(point["xKey"], xColumn, timeBucket, "tooltip") : String(point["xLabel"]),
408
+ value: valueKey && typeof point[valueKey] === "number" ? point[valueKey] : 0,
409
+ fill: getSeriesColor(index)
410
+ };
411
+ });
412
+ return /* @__PURE__ */ jsxs(PieChart, {
413
+ width,
414
+ height,
415
+ children: [
416
+ /* @__PURE__ */ jsx(Tooltip, { formatter: (value) => typeof value === "number" ? formatChartValue(value, {
417
+ column: valueColumn,
418
+ surface: "tooltip",
419
+ numericRange: valueRange
420
+ }) : value }),
421
+ /* @__PURE__ */ jsx(Legend, {}),
422
+ /* @__PURE__ */ jsx(Pie, {
423
+ data: pieData,
424
+ dataKey: "value",
425
+ nameKey: "name",
426
+ cx: "50%",
427
+ cy: "50%",
428
+ innerRadius: innerRadius ? "40%" : 0,
429
+ outerRadius: "80%",
430
+ label: showDataLabels ? ({ name, value }) => shouldHideDataLabel(value) ? "" : `${name}: ${typeof value === "number" ? formatChartValue(value, {
431
+ column: valueColumn,
432
+ surface: "data-label",
433
+ numericRange: valueRange
434
+ }) : value}` : false,
435
+ labelLine: false
436
+ })
437
+ ]
438
+ });
439
+ }
440
+ /**
441
+ * Format one cartesian data label with the same surface-aware rules used
442
+ * elsewhere in the chart UI.
443
+ */
444
+ function formatDataLabel(value, valueColumn, valueRange) {
445
+ if (shouldHideDataLabel(value)) return "";
446
+ if (typeof value !== "number") return String(value);
447
+ return formatChartValue(value, {
448
+ column: valueColumn,
449
+ surface: "data-label",
450
+ numericRange: valueRange
451
+ });
452
+ }
453
+ /**
454
+ * Suppress zero-value labels in the built-in UI so charts stay quieter by
455
+ * default while tooltips and raw values remain unchanged.
456
+ */
457
+ function shouldHideDataLabel(value) {
458
+ return typeof value === "number" && value === 0;
459
+ }
460
+ //#endregion
461
+ export { ChartCanvas };
@@ -0,0 +1,92 @@
1
+ import { ChartColumn, ChartInstance, ChartInstanceFromSchema, ChartSchema, Metric, ResolvedChartSchemaFromDefinition } from "../core/types.mjs";
2
+ import { ReactElement, ReactNode } from "react";
3
+
4
+ //#region src/ui/chart-context.d.ts
5
+ /**
6
+ * Type-erased chart instance stored in React context.
7
+ * This keeps the default UI primitives honest and safe for both single-source
8
+ * and multi-source charts.
9
+ */
10
+ type ChartContextChart = Omit<ChartInstance<unknown, string>, 'columns' | 'filters'> & {
11
+ columns: readonly ChartColumn<any, string>[];
12
+ filters: Map<string, Set<string>>;
13
+ };
14
+ type AnyChartInstance = {
15
+ activeSourceId: string;
16
+ setActiveSource: (...args: any[]) => unknown;
17
+ hasMultipleSources: boolean;
18
+ sources: Array<{
19
+ id: string;
20
+ label: string;
21
+ }>;
22
+ chartType: ChartContextChart['chartType'];
23
+ setChartType: (...args: any[]) => unknown;
24
+ availableChartTypes: ChartContextChart['availableChartTypes'];
25
+ xAxisId: string | null;
26
+ setXAxis: (...args: any[]) => unknown;
27
+ availableXAxes: ChartContextChart['availableXAxes'];
28
+ groupById: string | null;
29
+ setGroupBy: (...args: any[]) => unknown;
30
+ availableGroupBys: ChartContextChart['availableGroupBys'];
31
+ metric: Metric<any>;
32
+ setMetric: (...args: any[]) => unknown;
33
+ availableMetrics: ChartContextChart['availableMetrics'];
34
+ timeBucket: ChartContextChart['timeBucket'];
35
+ setTimeBucket: (...args: any[]) => unknown;
36
+ availableTimeBuckets: ChartContextChart['availableTimeBuckets'];
37
+ isTimeSeries: boolean;
38
+ filters: Map<any, Set<string>>;
39
+ toggleFilter: (...args: any[]) => unknown;
40
+ clearFilter: (...args: any[]) => unknown;
41
+ clearAllFilters: () => void;
42
+ availableFilters: ChartContextChart['availableFilters'];
43
+ sorting: ChartContextChart['sorting'];
44
+ setSorting: (...args: any[]) => unknown;
45
+ dateRange: ChartContextChart['dateRange'];
46
+ referenceDateId: string | null;
47
+ setReferenceDateId: (...args: any[]) => unknown;
48
+ availableDateColumns: ChartContextChart['availableDateColumns'];
49
+ dateRangeFilter: ChartContextChart['dateRangeFilter'];
50
+ setDateRangeFilter: (...args: any[]) => unknown;
51
+ transformedData: ChartContextChart['transformedData'];
52
+ series: ChartContextChart['series'];
53
+ columns: readonly ChartColumn<any, string>[];
54
+ rawData: readonly unknown[];
55
+ recordCount: number;
56
+ };
57
+ /**
58
+ * Hook to access the chart instance from context.
59
+ * Must be used within a `<Chart>` provider.
60
+ *
61
+ * This hook stays intentionally broad so the default UI primitives remain safe
62
+ * for both single-source and multi-source charts.
63
+ */
64
+ declare function useChartContext(): ChartContextChart;
65
+ /**
66
+ * Typed single-source chart context escape hatch for inferred charts.
67
+ * React cannot infer provider generics through arbitrary subtrees, so callers
68
+ * provide the row type (and optional schema type) explicitly.
69
+ */
70
+ declare function useTypedChartContext<T, const TSchema extends ChartSchema<T, any> | undefined = undefined>(): ChartInstanceFromSchema<T, ResolvedChartSchemaFromDefinition<TSchema>>;
71
+ /**
72
+ * Root provider component. Wraps children with the chart instance context.
73
+ *
74
+ * @example
75
+ * ```tsx
76
+ * <Chart chart={chart}>
77
+ * <ChartToolbar />
78
+ * <ChartCanvas />
79
+ * </Chart>
80
+ * ```
81
+ */
82
+ declare function Chart({
83
+ chart,
84
+ children,
85
+ className
86
+ }: {
87
+ chart: AnyChartInstance;
88
+ children: ReactNode;
89
+ className?: string;
90
+ }): ReactElement;
91
+ //#endregion
92
+ export { Chart, useChartContext, useTypedChartContext };
@@ -0,0 +1,112 @@
1
+ import { createContext, useContext, useMemo } from "react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region src/ui/chart-context.tsx
4
+ /**
5
+ * React context for sharing the chart instance across composable UI components.
6
+ */
7
+ const ChartContext = createContext(null);
8
+ /**
9
+ * Check whether a candidate column ID exists in the current chart.
10
+ */
11
+ function isKnownColumnId(columnIds, columnId) {
12
+ return columnIds.has(columnId);
13
+ }
14
+ /**
15
+ * Create the broad-but-safe chart shape shared through React context.
16
+ */
17
+ function createChartContextChart(chart) {
18
+ const columnIds = new Set(chart.columns.map((column) => column.id));
19
+ return {
20
+ activeSourceId: chart.activeSourceId,
21
+ setActiveSource: chart.setActiveSource,
22
+ hasMultipleSources: chart.hasMultipleSources,
23
+ sources: chart.sources,
24
+ chartType: chart.chartType,
25
+ setChartType: chart.setChartType,
26
+ availableChartTypes: chart.availableChartTypes,
27
+ xAxisId: chart.xAxisId,
28
+ setXAxis: (columnId) => {
29
+ if (!isKnownColumnId(columnIds, columnId)) throw new Error(`Unknown chart column ID: "${columnId}"`);
30
+ chart.setXAxis(columnId);
31
+ },
32
+ availableXAxes: chart.availableXAxes,
33
+ groupById: chart.groupById,
34
+ setGroupBy: (columnId) => {
35
+ if (columnId === null) {
36
+ chart.setGroupBy(null);
37
+ return;
38
+ }
39
+ if (!isKnownColumnId(columnIds, columnId)) throw new Error(`Unknown chart column ID: "${columnId}"`);
40
+ chart.setGroupBy(columnId);
41
+ },
42
+ availableGroupBys: chart.availableGroupBys,
43
+ metric: chart.metric,
44
+ setMetric: (metric) => {
45
+ if (metric.kind === "aggregate" && !isKnownColumnId(columnIds, metric.columnId)) throw new Error(`Unknown metric column ID: "${metric.columnId}"`);
46
+ chart.setMetric(metric);
47
+ },
48
+ availableMetrics: chart.availableMetrics,
49
+ timeBucket: chart.timeBucket,
50
+ setTimeBucket: chart.setTimeBucket,
51
+ availableTimeBuckets: chart.availableTimeBuckets,
52
+ isTimeSeries: chart.isTimeSeries,
53
+ filters: new Map(chart.filters),
54
+ toggleFilter: (columnId, value) => {
55
+ if (!isKnownColumnId(columnIds, columnId)) throw new Error(`Unknown chart column ID: "${columnId}"`);
56
+ chart.toggleFilter(columnId, value);
57
+ },
58
+ clearFilter: (columnId) => {
59
+ if (!isKnownColumnId(columnIds, columnId)) throw new Error(`Unknown chart column ID: "${columnId}"`);
60
+ chart.clearFilter(columnId);
61
+ },
62
+ clearAllFilters: chart.clearAllFilters,
63
+ availableFilters: chart.availableFilters,
64
+ sorting: chart.sorting,
65
+ setSorting: chart.setSorting,
66
+ dateRange: chart.dateRange,
67
+ referenceDateId: chart.referenceDateId,
68
+ setReferenceDateId: (columnId) => {
69
+ if (!isKnownColumnId(columnIds, columnId)) throw new Error(`Unknown chart column ID: "${columnId}"`);
70
+ chart.setReferenceDateId(columnId);
71
+ },
72
+ availableDateColumns: chart.availableDateColumns,
73
+ dateRangeFilter: chart.dateRangeFilter,
74
+ setDateRangeFilter: chart.setDateRangeFilter,
75
+ transformedData: chart.transformedData,
76
+ series: chart.series,
77
+ columns: chart.columns,
78
+ rawData: chart.rawData,
79
+ recordCount: chart.recordCount
80
+ };
81
+ }
82
+ function useChartContext() {
83
+ const ctx = useContext(ChartContext);
84
+ if (!ctx) throw new Error("useChartContext must be used within a <Chart> provider");
85
+ return ctx.chart;
86
+ }
87
+ /**
88
+ * Typed single-source chart context escape hatch for inferred charts.
89
+ * React cannot infer provider generics through arbitrary subtrees, so callers
90
+ * provide the row type (and optional schema type) explicitly.
91
+ */
92
+ function useTypedChartContext() {
93
+ const ctx = useContext(ChartContext);
94
+ if (!ctx) throw new Error("useTypedChartContext must be used within a <Chart> provider");
95
+ if (ctx.chart.hasMultipleSources) throw new Error("useTypedChartContext only supports single-source charts right now. Multi-source charts stay broad because the active source schema can change.");
96
+ return ctx.typedChart;
97
+ }
98
+ function Chart({ chart, children, className }) {
99
+ const contextValue = useMemo(() => ({
100
+ chart: createChartContextChart(chart),
101
+ typedChart: chart
102
+ }), [chart]);
103
+ return /* @__PURE__ */ jsx(ChartContext.Provider, {
104
+ value: contextValue,
105
+ children: /* @__PURE__ */ jsx("div", {
106
+ className,
107
+ children
108
+ })
109
+ });
110
+ }
111
+ //#endregion
112
+ export { Chart, useChartContext, useTypedChartContext };
@@ -1,3 +1,6 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+
3
+ //#region src/ui/chart-date-range-badge.d.ts
1
4
  /**
2
5
  * Read-only badge that always displays the current date range in the toolbar.
3
6
  *
@@ -8,7 +11,10 @@
8
11
  * Read-only badge showing the active date range preset and computed bounds.
9
12
  * Renders nothing if no date columns are available.
10
13
  */
11
- export declare function ChartDateRangeBadge({ className }: {
12
- className?: string;
13
- }): import("react/jsx-runtime").JSX.Element | null;
14
- //# sourceMappingURL=chart-date-range-badge.d.ts.map
14
+ declare function ChartDateRangeBadge({
15
+ className
16
+ }: {
17
+ className?: string;
18
+ }): react_jsx_runtime0.JSX.Element | null;
19
+ //#endregion
20
+ export { ChartDateRangeBadge };