@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.
- package/README.md +24 -0
- package/dist/core/chart-capabilities.d.mts +48 -0
- package/dist/core/chart-capabilities.mjs +55 -0
- package/dist/core/{colors.d.ts → colors.d.mts} +5 -3
- package/dist/core/colors.mjs +55 -0
- package/dist/core/config-utils.mjs +79 -0
- package/dist/core/date-utils.mjs +49 -0
- package/dist/core/define-chart-schema.d.mts +106 -0
- package/dist/core/define-chart-schema.mjs +47 -0
- package/dist/core/formatting.mjs +349 -0
- package/dist/core/infer-columns.d.mts +9 -0
- package/dist/core/infer-columns.mjs +481 -0
- package/dist/core/metric-utils.d.mts +13 -0
- package/dist/core/metric-utils.mjs +121 -0
- package/dist/core/pipeline-data-points.mjs +212 -0
- package/dist/core/pipeline-helpers.mjs +85 -0
- package/dist/core/{pipeline.d.ts → pipeline.d.mts} +21 -24
- package/dist/core/pipeline.mjs +153 -0
- package/dist/core/types.d.mts +957 -0
- package/dist/core/use-chart-options.d.mts +64 -0
- package/dist/core/use-chart-options.mjs +7 -0
- package/dist/core/use-chart-resolvers.mjs +34 -0
- package/dist/core/{use-chart.d.ts → use-chart.d.mts} +12 -9
- package/dist/core/use-chart.mjs +299 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.mjs +8 -0
- package/dist/ui/chart-axis-ticks.mjs +65 -0
- package/dist/ui/{chart-canvas.d.ts → chart-canvas.d.mts} +13 -6
- package/dist/ui/chart-canvas.mjs +461 -0
- package/dist/ui/chart-context.d.mts +92 -0
- package/dist/ui/chart-context.mjs +112 -0
- package/dist/ui/{chart-date-range-badge.d.ts → chart-date-range-badge.d.mts} +10 -4
- package/dist/ui/chart-date-range-badge.mjs +49 -0
- package/dist/ui/chart-date-range-panel.d.mts +18 -0
- package/dist/ui/chart-date-range-panel.mjs +208 -0
- package/dist/ui/{chart-date-range.d.ts → chart-date-range.d.mts} +10 -4
- package/dist/ui/chart-date-range.mjs +67 -0
- package/dist/ui/chart-debug.d.mts +17 -0
- package/dist/ui/chart-debug.mjs +169 -0
- package/dist/ui/chart-dropdown.mjs +92 -0
- package/dist/ui/{chart-filters-panel.d.ts → chart-filters-panel.d.mts} +12 -5
- package/dist/ui/chart-filters-panel.mjs +132 -0
- package/dist/ui/{chart-filters.d.ts → chart-filters.d.mts} +10 -4
- package/dist/ui/chart-filters.mjs +48 -0
- package/dist/ui/chart-group-by-selector.d.mts +14 -0
- package/dist/ui/chart-group-by-selector.mjs +29 -0
- package/dist/ui/{chart-metric-panel.d.ts → chart-metric-panel.d.mts} +12 -5
- package/dist/ui/chart-metric-panel.mjs +172 -0
- package/dist/ui/chart-metric-selector.d.mts +16 -0
- package/dist/ui/chart-metric-selector.mjs +50 -0
- package/dist/ui/chart-select.mjs +62 -0
- package/dist/ui/{chart-source-switcher.d.ts → chart-source-switcher.d.mts} +10 -4
- package/dist/ui/chart-source-switcher.mjs +54 -0
- package/dist/ui/chart-time-bucket-selector.d.mts +15 -0
- package/dist/ui/chart-time-bucket-selector.mjs +34 -0
- package/dist/ui/chart-toolbar-overflow.d.mts +28 -0
- package/dist/ui/chart-toolbar-overflow.mjs +209 -0
- package/dist/ui/chart-toolbar.d.mts +29 -0
- package/dist/ui/chart-toolbar.mjs +56 -0
- package/dist/ui/chart-type-selector.d.mts +14 -0
- package/dist/ui/chart-type-selector.mjs +33 -0
- package/dist/ui/chart-x-axis-selector.d.mts +14 -0
- package/dist/ui/chart-x-axis-selector.mjs +25 -0
- package/dist/ui/index.d.mts +19 -0
- package/dist/ui/index.mjs +18 -0
- package/dist/ui/toolbar-types.d.mts +7 -0
- package/dist/ui/toolbar-types.mjs +83 -0
- package/package.json +11 -10
- package/dist/core/chart-capabilities.d.ts +0 -60
- package/dist/core/chart-capabilities.d.ts.map +0 -1
- package/dist/core/chart-capabilities.js +0 -54
- package/dist/core/colors.d.ts.map +0 -1
- package/dist/core/colors.js +0 -54
- package/dist/core/config-utils.d.ts +0 -43
- package/dist/core/config-utils.d.ts.map +0 -1
- package/dist/core/config-utils.js +0 -80
- package/dist/core/date-utils.d.ts +0 -29
- package/dist/core/date-utils.d.ts.map +0 -1
- package/dist/core/date-utils.js +0 -58
- package/dist/core/define-chart-schema.d.ts +0 -105
- package/dist/core/define-chart-schema.d.ts.map +0 -1
- package/dist/core/define-chart-schema.js +0 -44
- package/dist/core/formatting.d.ts +0 -47
- package/dist/core/formatting.d.ts.map +0 -1
- package/dist/core/formatting.js +0 -396
- package/dist/core/index.d.ts +0 -17
- package/dist/core/index.d.ts.map +0 -1
- package/dist/core/index.js +0 -12
- package/dist/core/infer-columns.d.ts +0 -6
- package/dist/core/infer-columns.d.ts.map +0 -1
- package/dist/core/infer-columns.js +0 -512
- package/dist/core/metric-utils.d.ts +0 -43
- package/dist/core/metric-utils.d.ts.map +0 -1
- package/dist/core/metric-utils.js +0 -141
- package/dist/core/pipeline-data-points.d.ts +0 -23
- package/dist/core/pipeline-data-points.d.ts.map +0 -1
- package/dist/core/pipeline-data-points.js +0 -235
- package/dist/core/pipeline-helpers.d.ts +0 -38
- package/dist/core/pipeline-helpers.d.ts.map +0 -1
- package/dist/core/pipeline-helpers.js +0 -97
- package/dist/core/pipeline.d.ts.map +0 -1
- package/dist/core/pipeline.js +0 -156
- package/dist/core/types.d.ts +0 -1109
- package/dist/core/types.d.ts.map +0 -1
- package/dist/core/types.js +0 -14
- package/dist/core/use-chart-options.d.ts +0 -66
- package/dist/core/use-chart-options.d.ts.map +0 -1
- package/dist/core/use-chart-options.js +0 -4
- package/dist/core/use-chart-resolvers.d.ts +0 -14
- package/dist/core/use-chart-resolvers.d.ts.map +0 -1
- package/dist/core/use-chart-resolvers.js +0 -41
- package/dist/core/use-chart.d.ts.map +0 -1
- package/dist/core/use-chart.js +0 -265
- package/dist/index.d.ts +0 -36
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -35
- package/dist/ui/chart-axis-ticks.d.ts +0 -35
- package/dist/ui/chart-axis-ticks.d.ts.map +0 -1
- package/dist/ui/chart-axis-ticks.js +0 -79
- package/dist/ui/chart-canvas.d.ts.map +0 -1
- package/dist/ui/chart-canvas.js +0 -337
- package/dist/ui/chart-context.d.ts +0 -89
- package/dist/ui/chart-context.d.ts.map +0 -1
- package/dist/ui/chart-context.js +0 -128
- package/dist/ui/chart-date-range-badge.d.ts.map +0 -1
- package/dist/ui/chart-date-range-badge.js +0 -30
- package/dist/ui/chart-date-range-panel.d.ts +0 -25
- package/dist/ui/chart-date-range-panel.d.ts.map +0 -1
- package/dist/ui/chart-date-range-panel.js +0 -125
- package/dist/ui/chart-date-range.d.ts.map +0 -1
- package/dist/ui/chart-date-range.js +0 -37
- package/dist/ui/chart-debug.d.ts +0 -10
- package/dist/ui/chart-debug.d.ts.map +0 -1
- package/dist/ui/chart-debug.js +0 -126
- package/dist/ui/chart-dropdown.d.ts +0 -35
- package/dist/ui/chart-dropdown.d.ts.map +0 -1
- package/dist/ui/chart-dropdown.js +0 -76
- package/dist/ui/chart-filters-panel.d.ts.map +0 -1
- package/dist/ui/chart-filters-panel.js +0 -46
- package/dist/ui/chart-filters.d.ts.map +0 -1
- package/dist/ui/chart-filters.js +0 -26
- package/dist/ui/chart-group-by-selector.d.ts +0 -8
- package/dist/ui/chart-group-by-selector.d.ts.map +0 -1
- package/dist/ui/chart-group-by-selector.js +0 -19
- package/dist/ui/chart-metric-panel.d.ts.map +0 -1
- package/dist/ui/chart-metric-panel.js +0 -118
- package/dist/ui/chart-metric-selector.d.ts +0 -10
- package/dist/ui/chart-metric-selector.d.ts.map +0 -1
- package/dist/ui/chart-metric-selector.js +0 -27
- package/dist/ui/chart-select.d.ts +0 -25
- package/dist/ui/chart-select.d.ts.map +0 -1
- package/dist/ui/chart-select.js +0 -35
- package/dist/ui/chart-source-switcher.d.ts.map +0 -1
- package/dist/ui/chart-source-switcher.js +0 -31
- package/dist/ui/chart-time-bucket-selector.d.ts +0 -9
- package/dist/ui/chart-time-bucket-selector.d.ts.map +0 -1
- package/dist/ui/chart-time-bucket-selector.js +0 -25
- package/dist/ui/chart-toolbar-overflow.d.ts +0 -29
- package/dist/ui/chart-toolbar-overflow.d.ts.map +0 -1
- package/dist/ui/chart-toolbar-overflow.js +0 -109
- package/dist/ui/chart-toolbar.d.ts +0 -45
- package/dist/ui/chart-toolbar.d.ts.map +0 -1
- package/dist/ui/chart-toolbar.js +0 -44
- package/dist/ui/chart-type-selector.d.ts +0 -8
- package/dist/ui/chart-type-selector.d.ts.map +0 -1
- package/dist/ui/chart-type-selector.js +0 -22
- package/dist/ui/chart-x-axis-selector.d.ts +0 -8
- package/dist/ui/chart-x-axis-selector.d.ts.map +0 -1
- package/dist/ui/chart-x-axis-selector.js +0 -14
- package/dist/ui/index.d.ts +0 -25
- package/dist/ui/index.d.ts.map +0 -1
- package/dist/ui/index.js +0 -23
- package/dist/ui/toolbar-types.d.ts +0 -43
- package/dist/ui/toolbar-types.d.ts.map +0 -1
- 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
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
14
|
+
declare function ChartDateRangeBadge({
|
|
15
|
+
className
|
|
16
|
+
}: {
|
|
17
|
+
className?: string;
|
|
18
|
+
}): react_jsx_runtime0.JSX.Element | null;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { ChartDateRangeBadge };
|