@ctrl-ui/react 0.8.20 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/action-bar.d.ts +1 -2
- package/dist/action-bar.js +7 -8
- package/dist/blocks/design-canvas-inspector.js +4 -1
- package/dist/blocks/file-explorer.js +1 -1
- package/dist/chat-message.js +4 -13
- package/dist/hooks/use-textarea-trigger-menu.d.ts +21 -3
- package/dist/hooks/use-textarea-trigger-menu.js +36 -40
- package/dist/hooks/use-user-ask.d.ts +1 -0
- package/dist/hooks/use-user-ask.js +5 -1
- package/dist/knob-contracts/audio-recorder-knobs.d.ts +1 -1
- package/dist/knob-contracts/audio-recorder-knobs.js +2 -1
- package/dist/knob-contracts/chart-knobs.d.ts +4 -0
- package/dist/knob-contracts/chart-knobs.js +24 -0
- package/dist/knob-contracts/choice-knobs.d.ts +1 -1
- package/dist/knob-contracts/choice-knobs.js +1 -0
- package/dist/knob-contracts/dockable-panel-knobs.d.ts +1 -1
- package/dist/knob-contracts/dockable-panel-knobs.js +2 -1
- package/dist/knob-contracts/emoji-picker-knobs.d.ts +1 -1
- package/dist/knob-contracts/emoji-picker-knobs.js +1 -0
- package/dist/knob-contracts/empty-knobs.d.ts +1 -1
- package/dist/knob-contracts/empty-knobs.js +2 -1
- package/dist/knob-contracts/icon-picker-knobs.d.ts +1 -1
- package/dist/knob-contracts/icon-picker-knobs.js +3 -1
- package/dist/knob-contracts/resizable-knobs.d.ts +1 -1
- package/dist/knob-contracts/resizable-knobs.js +2 -2
- package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
- package/dist/knob-contracts/sidebar-knobs.js +3 -1
- package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
- package/dist/knob-contracts/tabs-knobs.js +2 -1
- package/dist/knob-contracts/tree-knobs.d.ts +1 -1
- package/dist/knob-contracts/tree-knobs.js +2 -1
- package/dist/lib/color.d.ts +2 -6
- package/dist/lib/color.js +45 -28
- package/dist/lib/contrast.d.ts +6 -0
- package/dist/lib/contrast.js +33 -1
- package/dist/lib/gradient.d.ts +17 -0
- package/dist/lib/gradient.js +69 -0
- package/dist/markdown-editor/drag-handle.js +73 -10
- package/dist/markdown-editor/drag-preview.d.ts +3 -0
- package/dist/markdown-editor/drag-preview.js +31 -0
- package/dist/markdown-editor/extensions.d.ts +2 -2
- package/dist/markdown-editor/extensions.js +25 -3
- package/dist/markdown-editor/suggestions.js +5 -5
- package/dist/markdown-editor/task-item.d.ts +3 -0
- package/dist/markdown-editor/task-item.js +50 -0
- package/dist/markdown-editor/toolbar.js +122 -63
- package/dist/markdown-editor.js +3 -3
- package/dist/skin.d.ts +0 -1
- package/dist/styles/index.css +3 -2
- package/dist/styles/recipes/action-bar.css +1 -1
- package/dist/styles/recipes/activity.css +1 -1
- package/dist/styles/recipes/alert.css +9 -10
- package/dist/styles/recipes/app-shell-header.css +2 -2
- package/dist/styles/recipes/audio-recorder.css +15 -4
- package/dist/styles/recipes/audio-visualizer.css +4 -4
- package/dist/styles/recipes/avatar.css +3 -3
- package/dist/styles/recipes/badge.css +2 -2
- package/dist/styles/recipes/button.css +2 -5
- package/dist/styles/recipes/card.css +0 -2
- package/dist/styles/recipes/chart.css +378 -0
- package/dist/styles/recipes/chat-composer-attachment.css +3 -3
- package/dist/styles/recipes/chat-composer.css +3 -3
- package/dist/styles/recipes/chat-layout.css +2 -2
- package/dist/styles/recipes/chat-message-social.css +1 -1
- package/dist/styles/recipes/chat-message.css +1 -1
- package/dist/styles/recipes/choice.css +9 -3
- package/dist/styles/recipes/code-diff.css +1 -1
- package/dist/styles/recipes/code.css +1 -1
- package/dist/styles/recipes/color-picker-details.css +17 -11
- package/dist/styles/recipes/color-picker-values.css +17 -7
- package/dist/styles/recipes/color-picker.css +88 -70
- package/dist/styles/recipes/context.css +2 -2
- package/dist/styles/recipes/dockable-panel.css +9 -2
- package/dist/styles/recipes/dynamic-notification-motion.css +1 -55
- package/dist/styles/recipes/dynamic-notification.css +0 -1
- package/dist/styles/recipes/emoji-picker.css +35 -19
- package/dist/styles/recipes/empty.css +19 -14
- package/dist/styles/recipes/field-parts.css +2 -2
- package/dist/styles/recipes/field.css +6 -4
- package/dist/styles/recipes/filter-bar.css +10 -10
- package/dist/styles/recipes/flow.css +5 -3
- package/dist/styles/recipes/gradient-editor.css +8 -0
- package/dist/styles/recipes/icon-picker.css +45 -10
- package/dist/styles/recipes/inline-attachment.css +4 -4
- package/dist/styles/recipes/inline-citation.css +2 -2
- package/dist/styles/recipes/item.css +4 -4
- package/dist/styles/recipes/markdown-editor.css +35 -24
- package/dist/styles/recipes/markdown.css +23 -27
- package/dist/styles/recipes/page-layout.css +13 -11
- package/dist/styles/recipes/popup-controls.css +2 -2
- package/dist/styles/recipes/popup-lists.css +3 -3
- package/dist/styles/recipes/popup.css +11 -9
- package/dist/styles/recipes/range.css +4 -5
- package/dist/styles/recipes/resizable.css +15 -10
- package/dist/styles/recipes/resize-handle.css +2 -2
- package/dist/styles/recipes/scroll-area.css +1 -1
- package/dist/styles/recipes/sidebar-menu.css +5 -15
- package/dist/styles/recipes/sidebar-mobile.css +4 -4
- package/dist/styles/recipes/sidebar-parts.css +26 -1
- package/dist/styles/recipes/sidebar.css +18 -31
- package/dist/styles/recipes/skeleton.css +10 -5
- package/dist/styles/recipes/stepper.css +1 -1
- package/dist/styles/recipes/switch.css +12 -11
- package/dist/styles/recipes/table-of-contents.css +6 -8
- package/dist/styles/recipes/table.css +4 -4
- package/dist/styles/recipes/tabs.css +10 -5
- package/dist/styles/recipes/task-list.css +3 -3
- package/dist/styles/recipes/tree.css +10 -3
- package/dist/styles/recipes/user-ask-details.css +5 -4
- package/dist/styles/recipes/user-ask.css +5 -5
- package/dist/styles/theme.css +158 -177
- package/dist/ui/chart-cartesian.d.ts +801 -0
- package/dist/ui/chart-cartesian.js +420 -0
- package/dist/ui/chart-colors.d.ts +7 -0
- package/dist/ui/chart-colors.js +27 -0
- package/dist/ui/chart-curves.d.ts +7 -0
- package/dist/ui/chart-curves.js +87 -0
- package/dist/ui/chart-marks.d.ts +68 -0
- package/dist/ui/chart-marks.js +227 -0
- package/dist/ui/chart-polar.d.ts +103 -0
- package/dist/ui/chart-polar.js +124 -0
- package/dist/ui/chart-series.d.ts +36 -0
- package/dist/ui/chart-series.js +71 -0
- package/dist/ui/chart.d.ts +32 -0
- package/dist/ui/chart.js +257 -0
- package/dist/ui/color-picker-area.d.ts +16 -0
- package/dist/ui/color-picker-area.js +175 -0
- package/dist/ui/color-picker-context.d.ts +27 -0
- package/dist/ui/color-picker-context.js +87 -0
- package/dist/ui/color-picker-contrast.d.ts +20 -0
- package/dist/ui/color-picker-contrast.js +129 -0
- package/dist/ui/color-picker-sliders.d.ts +15 -0
- package/dist/ui/color-picker-sliders.js +146 -0
- package/dist/ui/color-picker-swatches.d.ts +23 -0
- package/dist/ui/color-picker-swatches.js +94 -0
- package/dist/ui/color-picker-values.d.ts +59 -0
- package/dist/ui/color-picker-values.js +244 -0
- package/dist/ui/color-picker.d.ts +15 -110
- package/dist/ui/color-picker.js +30 -759
- package/dist/ui/emoji-picker.d.ts +10 -1
- package/dist/ui/emoji-picker.js +71 -14
- package/dist/ui/flow.js +1 -1
- package/dist/ui/gradient-editor-controls.d.ts +19 -0
- package/dist/ui/gradient-editor-controls.js +75 -0
- package/dist/ui/gradient-editor.d.ts +23 -22
- package/dist/ui/gradient-editor.js +77 -123
- package/dist/ui/icon-picker.d.ts +1 -1
- package/dist/ui/icon-picker.js +11 -6
- package/dist/ui/markdown-elements.d.ts +3 -1
- package/dist/ui/markdown-elements.js +62 -22
- package/dist/ui/markdown-heading.d.ts +9 -0
- package/dist/ui/markdown-heading.js +25 -0
- package/dist/ui/resizable.js +24 -9
- package/dist/ui/sidebar-provider.js +3 -12
- package/dist/ui/typography.d.ts +1 -0
- package/dist/ui/typography.js +12 -3
- package/dist/user-ask.js +4 -8
- package/package.json +7 -3
- package/dist/ui/emoji-icon-picker.d.ts +0 -13
- package/dist/ui/emoji-icon-picker.js +0 -50
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { chartColor, chartSeriesColor } from "./chart-colors.js";
|
|
2
|
+
import { chartCurve } from "./chart-curves.js";
|
|
3
|
+
import { chartBarFill, chartBarGradients, chartBars, chartFillPaint, chartGradients, chartOutline, chartPart, chartReferenceMarks, dimUnfocused, quietAxis, quietGrid, seriesTooltip } from "./chart-marks.js";
|
|
4
|
+
import { chartFormatters, chartSeriesRows, chartXScale, resolveChartSeries } from "./chart-series.js";
|
|
5
|
+
import { areaY, barX, barY, crosshair, defineChart, dot, group, lineY, stack, stackRowsY } from "@tanstack/charts";
|
|
6
|
+
import { decorative } from "@tanstack/charts/mark/decorative";
|
|
7
|
+
import { scaleBand } from "@tanstack/charts/scales/band";
|
|
8
|
+
import { scaleLinear } from "@tanstack/charts/scales/linear";
|
|
9
|
+
import { tooltip } from "@tanstack/charts/tooltip";
|
|
10
|
+
//#region src/ui/chart-cartesian.ts
|
|
11
|
+
function cartesianSetup(options, percent) {
|
|
12
|
+
const series = resolveChartSeries(options.series, options.fill ?? "gradient");
|
|
13
|
+
const rows = chartSeriesRows(options.data, options.x, series);
|
|
14
|
+
const formats = chartFormatters(options.locale);
|
|
15
|
+
const formatX = options.formatX ?? formats.x;
|
|
16
|
+
const formatValue = options.formatValue ?? formats.value;
|
|
17
|
+
return {
|
|
18
|
+
series,
|
|
19
|
+
rows,
|
|
20
|
+
formatX,
|
|
21
|
+
valueScale: {
|
|
22
|
+
scale: scaleLinear,
|
|
23
|
+
nice: true,
|
|
24
|
+
grid: options.grid === false ? false : quietGrid,
|
|
25
|
+
axis: quietAxis(options.formatTick ?? (percent ? formats.percent : formats.tick)),
|
|
26
|
+
...options.yScale
|
|
27
|
+
},
|
|
28
|
+
tooltip: options.tooltip === false ? false : seriesTooltip(series.map((entry) => entry.label), formatX, formatValue),
|
|
29
|
+
lastX: rows.at(-1)?.x
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function gradientColors(series) {
|
|
33
|
+
return series.filter((entry) => entry.fill === "gradient").map((entry) => entry.color);
|
|
34
|
+
}
|
|
35
|
+
const SERIES_FOCUS_POINT = {
|
|
36
|
+
id: "focus",
|
|
37
|
+
key: "id",
|
|
38
|
+
x: "x",
|
|
39
|
+
z: "series",
|
|
40
|
+
r: 4,
|
|
41
|
+
fillOpacity: 0,
|
|
42
|
+
strokeOpacity: 0
|
|
43
|
+
};
|
|
44
|
+
function areaChart(options) {
|
|
45
|
+
const stacked = options.stacked ?? false;
|
|
46
|
+
const { series, rows, formatX, valueScale, tooltip, lastX } = cartesianSetup(options, stacked === "percent");
|
|
47
|
+
const curve = chartCurve(options.curve ?? "smooth");
|
|
48
|
+
const stackedRows = stacked ? stackRowsY(rows, {
|
|
49
|
+
x: "x",
|
|
50
|
+
y: "value",
|
|
51
|
+
z: "series",
|
|
52
|
+
offset: stacked === "percent" ? "normalize" : void 0
|
|
53
|
+
}) : [];
|
|
54
|
+
const seriesMarks = series.flatMap((entry) => {
|
|
55
|
+
const areaParts = entry.fill === "hatched" ? ["area", "area-hatched"] : ["area"];
|
|
56
|
+
const fill = chartFillPaint(entry.color, entry.fill, "area");
|
|
57
|
+
const line = {
|
|
58
|
+
id: `line-${entry.key}`,
|
|
59
|
+
key: "id",
|
|
60
|
+
x: "x",
|
|
61
|
+
stroke: chartColor(entry.color),
|
|
62
|
+
strokeDasharray: entry.dashed ? "4 4" : void 0,
|
|
63
|
+
curve
|
|
64
|
+
};
|
|
65
|
+
if (stacked) {
|
|
66
|
+
const seriesRows = stackedRows.filter((row) => row.series === entry.label);
|
|
67
|
+
return [decorative(chartPart(areaY(seriesRows, {
|
|
68
|
+
id: `area-${entry.key}`,
|
|
69
|
+
key: "id",
|
|
70
|
+
x: "x",
|
|
71
|
+
y1: "y1",
|
|
72
|
+
y2: "y2",
|
|
73
|
+
fill,
|
|
74
|
+
curve
|
|
75
|
+
}), ...areaParts)), decorative(chartPart(lineY(seriesRows, {
|
|
76
|
+
...line,
|
|
77
|
+
y: "y2"
|
|
78
|
+
}), "line"))];
|
|
79
|
+
}
|
|
80
|
+
const seriesRows = rows.filter((row) => row.series === entry.label);
|
|
81
|
+
return [decorative(chartPart(areaY(seriesRows, {
|
|
82
|
+
id: `area-${entry.key}`,
|
|
83
|
+
key: "id",
|
|
84
|
+
x: "x",
|
|
85
|
+
y: "value",
|
|
86
|
+
fill,
|
|
87
|
+
curve
|
|
88
|
+
}), ...areaParts)), decorative(chartPart(lineY(seriesRows, {
|
|
89
|
+
...line,
|
|
90
|
+
y: "value"
|
|
91
|
+
}), "line"))];
|
|
92
|
+
});
|
|
93
|
+
const focusPoints = stacked ? dot(stackedRows, {
|
|
94
|
+
...SERIES_FOCUS_POINT,
|
|
95
|
+
y: "y2"
|
|
96
|
+
}) : dot(rows, {
|
|
97
|
+
...SERIES_FOCUS_POINT,
|
|
98
|
+
y: "value"
|
|
99
|
+
});
|
|
100
|
+
return defineChart({
|
|
101
|
+
marks: [
|
|
102
|
+
...seriesMarks,
|
|
103
|
+
...chartReferenceMarks(options.references ?? [], "y", lastX),
|
|
104
|
+
focusPoints,
|
|
105
|
+
crosshair({
|
|
106
|
+
x: true,
|
|
107
|
+
y: false,
|
|
108
|
+
strokeDasharray: "3 3"
|
|
109
|
+
})
|
|
110
|
+
],
|
|
111
|
+
scales: {
|
|
112
|
+
x: {
|
|
113
|
+
...chartXScale(rows, "point", options.xScale),
|
|
114
|
+
axis: quietAxis(formatX)
|
|
115
|
+
},
|
|
116
|
+
y: valueScale
|
|
117
|
+
},
|
|
118
|
+
gradients: chartGradients(gradientColors(series), "area"),
|
|
119
|
+
margin: { top: 8 },
|
|
120
|
+
focus: "group-x",
|
|
121
|
+
maxFocusDistance: Number.POSITIVE_INFINITY,
|
|
122
|
+
tooltip
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function lineChart(options) {
|
|
126
|
+
const { series, rows, formatX, valueScale, tooltip, lastX } = cartesianSetup(options, false);
|
|
127
|
+
const curve = chartCurve(options.curve ?? "smooth");
|
|
128
|
+
const seriesMarks = series.flatMap((entry) => {
|
|
129
|
+
const seriesRows = rows.filter((row) => row.series === entry.label);
|
|
130
|
+
const line = decorative(chartPart(lineY(seriesRows, {
|
|
131
|
+
id: `line-${entry.key}`,
|
|
132
|
+
key: "id",
|
|
133
|
+
x: "x",
|
|
134
|
+
y: "value",
|
|
135
|
+
stroke: chartColor(entry.color),
|
|
136
|
+
strokeDasharray: entry.dashed ? "4 4" : void 0,
|
|
137
|
+
curve
|
|
138
|
+
}), "line"));
|
|
139
|
+
if (!options.dots) return [line];
|
|
140
|
+
return [line, decorative(chartPart(dot(seriesRows, {
|
|
141
|
+
id: `dot-${entry.key}`,
|
|
142
|
+
key: "id",
|
|
143
|
+
x: "x",
|
|
144
|
+
y: "value",
|
|
145
|
+
r: 3,
|
|
146
|
+
fill: chartColor(entry.color),
|
|
147
|
+
stroke: "var(--cui-chart-background)",
|
|
148
|
+
strokeWidth: 1.5
|
|
149
|
+
}), "dot"))];
|
|
150
|
+
});
|
|
151
|
+
return defineChart({
|
|
152
|
+
marks: [
|
|
153
|
+
...seriesMarks,
|
|
154
|
+
...chartReferenceMarks(options.references ?? [], "y", lastX),
|
|
155
|
+
dot(rows, {
|
|
156
|
+
...SERIES_FOCUS_POINT,
|
|
157
|
+
y: "value"
|
|
158
|
+
}),
|
|
159
|
+
crosshair({
|
|
160
|
+
x: true,
|
|
161
|
+
y: false,
|
|
162
|
+
strokeDasharray: "3 3"
|
|
163
|
+
})
|
|
164
|
+
],
|
|
165
|
+
scales: {
|
|
166
|
+
x: {
|
|
167
|
+
...chartXScale(rows, "point", options.xScale),
|
|
168
|
+
axis: quietAxis(formatX)
|
|
169
|
+
},
|
|
170
|
+
y: valueScale
|
|
171
|
+
},
|
|
172
|
+
margin: { top: 8 },
|
|
173
|
+
focus: "group-x",
|
|
174
|
+
maxFocusDistance: Number.POSITIVE_INFINITY,
|
|
175
|
+
tooltip
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function barChart(options) {
|
|
179
|
+
const layoutName = options.layout ?? "grouped";
|
|
180
|
+
const { rows, formatX, valueScale, tooltip } = cartesianSetup(options, layoutName === "percent");
|
|
181
|
+
const layout = {
|
|
182
|
+
grouped: group({ padding: .15 }),
|
|
183
|
+
stacked: void 0,
|
|
184
|
+
percent: stack({ offset: "normalize" })
|
|
185
|
+
}[layoutName];
|
|
186
|
+
const categoryScale = {
|
|
187
|
+
...chartXScale(rows, "band", options.xScale),
|
|
188
|
+
axis: quietAxis(formatX)
|
|
189
|
+
};
|
|
190
|
+
const band = {
|
|
191
|
+
inset: -4,
|
|
192
|
+
radius: 6,
|
|
193
|
+
fillOpacity: .06
|
|
194
|
+
};
|
|
195
|
+
const shared = {
|
|
196
|
+
id: "bars",
|
|
197
|
+
key: "id",
|
|
198
|
+
z: "series",
|
|
199
|
+
stroke: (row) => chartOutline(row.color, row.fill),
|
|
200
|
+
strokeWidth: 1,
|
|
201
|
+
radius: { end: 6 },
|
|
202
|
+
states: dimUnfocused,
|
|
203
|
+
layout
|
|
204
|
+
};
|
|
205
|
+
if (options.orientation === "horizontal") return defineChart({
|
|
206
|
+
marks: [
|
|
207
|
+
crosshair({
|
|
208
|
+
x: false,
|
|
209
|
+
y: { band }
|
|
210
|
+
}),
|
|
211
|
+
chartBars(barX(rows, {
|
|
212
|
+
...shared,
|
|
213
|
+
y: "x",
|
|
214
|
+
x: "value",
|
|
215
|
+
fill: (row) => chartBarFill(row, "x")
|
|
216
|
+
}), "x"),
|
|
217
|
+
...chartReferenceMarks(options.references ?? [], "x", rows[0]?.x)
|
|
218
|
+
],
|
|
219
|
+
scales: {
|
|
220
|
+
x: valueScale,
|
|
221
|
+
y: categoryScale
|
|
222
|
+
},
|
|
223
|
+
gradients: chartBarGradients(rows, "x"),
|
|
224
|
+
margin: { top: 8 },
|
|
225
|
+
focus: "group-y",
|
|
226
|
+
maxFocusDistance: Number.POSITIVE_INFINITY,
|
|
227
|
+
tooltip
|
|
228
|
+
});
|
|
229
|
+
return defineChart({
|
|
230
|
+
marks: [
|
|
231
|
+
crosshair({
|
|
232
|
+
x: { band },
|
|
233
|
+
y: false
|
|
234
|
+
}),
|
|
235
|
+
chartBars(barY(rows, {
|
|
236
|
+
...shared,
|
|
237
|
+
x: "x",
|
|
238
|
+
y: "value",
|
|
239
|
+
fill: (row) => chartBarFill(row, "y")
|
|
240
|
+
}), "y"),
|
|
241
|
+
...chartReferenceMarks(options.references ?? [], "y", rows.at(-1)?.x)
|
|
242
|
+
],
|
|
243
|
+
scales: {
|
|
244
|
+
x: categoryScale,
|
|
245
|
+
y: valueScale
|
|
246
|
+
},
|
|
247
|
+
gradients: chartBarGradients(rows, "y"),
|
|
248
|
+
margin: { top: 8 },
|
|
249
|
+
focus: "group-x",
|
|
250
|
+
maxFocusDistance: Number.POSITIVE_INFINITY,
|
|
251
|
+
tooltip
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function finiteNumber(value) {
|
|
255
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
256
|
+
}
|
|
257
|
+
function scatterRows(options) {
|
|
258
|
+
return options.data.flatMap((datum, index) => {
|
|
259
|
+
const x = finiteNumber(datum[options.x]);
|
|
260
|
+
const y = finiteNumber(datum[options.y]);
|
|
261
|
+
if (x === void 0 || y === void 0) return [];
|
|
262
|
+
const size = options.size === void 0 ? 0 : finiteNumber(datum[options.size]) ?? 0;
|
|
263
|
+
const groupValue = options.group ? datum[options.group] : "";
|
|
264
|
+
const groupName = groupValue instanceof Date ? groupValue.toISOString() : String(groupValue ?? "");
|
|
265
|
+
return {
|
|
266
|
+
id: String(index),
|
|
267
|
+
x,
|
|
268
|
+
y,
|
|
269
|
+
size,
|
|
270
|
+
group: groupName
|
|
271
|
+
};
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
function scatterChart(options) {
|
|
275
|
+
const rows = scatterRows(options);
|
|
276
|
+
const groups = [...new Set(rows.map((row) => row.group))];
|
|
277
|
+
const maxSize = Math.max(1, ...rows.map((row) => row.size));
|
|
278
|
+
const formats = chartFormatters(options.locale);
|
|
279
|
+
const formatX = options.formatX ?? formats.value;
|
|
280
|
+
const formatValue = options.formatValue ?? formats.value;
|
|
281
|
+
const xLabel = options.xLabel ?? options.x;
|
|
282
|
+
const yLabel = options.yLabel ?? options.y;
|
|
283
|
+
const sizeKey = options.size;
|
|
284
|
+
const radius = sizeKey ? (row) => 3 + 11 * Math.sqrt(row.size / maxSize) : 4;
|
|
285
|
+
const dots = groups.map((groupName, groupIndex) => {
|
|
286
|
+
const color = chartColor(options.colors?.[groupName] ?? chartSeriesColor(groupIndex));
|
|
287
|
+
return chartPart(dot(rows.filter((row) => row.group === groupName), {
|
|
288
|
+
id: `dot-${groupName}`,
|
|
289
|
+
key: "id",
|
|
290
|
+
x: "x",
|
|
291
|
+
y: "y",
|
|
292
|
+
r: radius,
|
|
293
|
+
fill: color,
|
|
294
|
+
fillOpacity: .65,
|
|
295
|
+
stroke: color,
|
|
296
|
+
strokeWidth: 1,
|
|
297
|
+
states: dimUnfocused
|
|
298
|
+
}), "dot");
|
|
299
|
+
});
|
|
300
|
+
const rightmostX = rows.length > 0 ? Math.max(...rows.map((row) => row.x)) : void 0;
|
|
301
|
+
return defineChart({
|
|
302
|
+
marks: [...dots, ...chartReferenceMarks(options.references ?? [], "y", rightmostX)],
|
|
303
|
+
scales: {
|
|
304
|
+
x: {
|
|
305
|
+
scale: scaleLinear,
|
|
306
|
+
nice: true,
|
|
307
|
+
grid: quietGrid,
|
|
308
|
+
axis: quietAxis(formats.tick),
|
|
309
|
+
...options.xScale
|
|
310
|
+
},
|
|
311
|
+
y: {
|
|
312
|
+
scale: scaleLinear,
|
|
313
|
+
nice: true,
|
|
314
|
+
grid: quietGrid,
|
|
315
|
+
axis: quietAxis(formats.tick),
|
|
316
|
+
...options.yScale
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
margin: { top: 8 },
|
|
320
|
+
focus: "nearest",
|
|
321
|
+
tooltip: {
|
|
322
|
+
use: tooltip,
|
|
323
|
+
content: (points) => {
|
|
324
|
+
const point = points[0]?.datum;
|
|
325
|
+
if (!point) return { rows: [] };
|
|
326
|
+
const color = chartColor(options.colors?.[point.group] ?? chartSeriesColor(groups.indexOf(point.group)));
|
|
327
|
+
return {
|
|
328
|
+
title: point.group || void 0,
|
|
329
|
+
rows: [
|
|
330
|
+
{
|
|
331
|
+
label: xLabel,
|
|
332
|
+
value: formatX(point.x),
|
|
333
|
+
color
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
label: yLabel,
|
|
337
|
+
value: formatValue(point.y),
|
|
338
|
+
color
|
|
339
|
+
},
|
|
340
|
+
...sizeKey ? [{
|
|
341
|
+
label: sizeKey,
|
|
342
|
+
value: formatValue(point.size),
|
|
343
|
+
color
|
|
344
|
+
}] : []
|
|
345
|
+
]
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
function sparklineChart(options) {
|
|
352
|
+
const color = options.color ?? "blue";
|
|
353
|
+
const fill = options.fill ?? "gradient";
|
|
354
|
+
const type = options.type ?? "area";
|
|
355
|
+
const curve = chartCurve(options.curve ?? "smooth");
|
|
356
|
+
const rows = chartSeriesRows(options.data, options.x, resolveChartSeries([{
|
|
357
|
+
key: options.y,
|
|
358
|
+
color,
|
|
359
|
+
fill
|
|
360
|
+
}]));
|
|
361
|
+
const values = rows.flatMap((row) => row.value === null ? [] : row.value);
|
|
362
|
+
const min = values.length > 0 ? Math.min(...values) : 0;
|
|
363
|
+
const shared = {
|
|
364
|
+
guides: false,
|
|
365
|
+
margin: 2,
|
|
366
|
+
focus: false,
|
|
367
|
+
pointer: false,
|
|
368
|
+
keyboard: false,
|
|
369
|
+
tooltip: false
|
|
370
|
+
};
|
|
371
|
+
const valueScale = {
|
|
372
|
+
scale: scaleLinear,
|
|
373
|
+
nice: false
|
|
374
|
+
};
|
|
375
|
+
if (type === "bar") return defineChart({
|
|
376
|
+
...shared,
|
|
377
|
+
marks: [chartBars(barY(rows, {
|
|
378
|
+
key: "id",
|
|
379
|
+
x: "x",
|
|
380
|
+
y: "value",
|
|
381
|
+
fill: (row) => chartBarFill(row, "y"),
|
|
382
|
+
radius: { end: 2 }
|
|
383
|
+
}), "y")],
|
|
384
|
+
scales: {
|
|
385
|
+
x: {
|
|
386
|
+
scale: () => scaleBand().padding(.2),
|
|
387
|
+
...options.xScale
|
|
388
|
+
},
|
|
389
|
+
y: valueScale
|
|
390
|
+
},
|
|
391
|
+
gradients: chartBarGradients(rows, "y")
|
|
392
|
+
});
|
|
393
|
+
const line = chartPart(lineY(rows, {
|
|
394
|
+
key: "id",
|
|
395
|
+
x: "x",
|
|
396
|
+
y: "value",
|
|
397
|
+
stroke: chartColor(color),
|
|
398
|
+
curve
|
|
399
|
+
}), "line");
|
|
400
|
+
const areaParts = fill === "hatched" ? ["area", "area-hatched"] : ["area"];
|
|
401
|
+
const area = chartPart(areaY(rows, {
|
|
402
|
+
key: "id",
|
|
403
|
+
x: "x",
|
|
404
|
+
y1: min,
|
|
405
|
+
y2: "value",
|
|
406
|
+
fill: chartFillPaint(color, fill, "area"),
|
|
407
|
+
curve
|
|
408
|
+
}), ...areaParts);
|
|
409
|
+
return defineChart({
|
|
410
|
+
...shared,
|
|
411
|
+
marks: type === "area" ? [area, line] : [line],
|
|
412
|
+
scales: {
|
|
413
|
+
x: chartXScale(rows, "point", options.xScale),
|
|
414
|
+
y: valueScale
|
|
415
|
+
},
|
|
416
|
+
gradients: type === "area" && fill === "gradient" ? chartGradients([color], "area") : []
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
//#endregion
|
|
420
|
+
export { areaChart, barChart, lineChart, scatterChart, sparklineChart };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/ui/chart-colors.d.ts
|
|
2
|
+
export declare const CHART_COLORS: readonly ["blue", "purple", "pink", "orange", "green", "yellow", "red", "neutral"];
|
|
3
|
+
export type ChartColor = (typeof CHART_COLORS)[number];
|
|
4
|
+
export declare const CHART_SERIES_COLORS: readonly ["blue", "purple", "pink", "orange", "green", "yellow"];
|
|
5
|
+
export declare function chartColor(color: ChartColor): string;
|
|
6
|
+
export declare function chartSeriesColor(index: number): ChartColor;
|
|
7
|
+
//#endregion
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/ui/chart-colors.ts
|
|
2
|
+
const CHART_COLORS = [
|
|
3
|
+
"blue",
|
|
4
|
+
"purple",
|
|
5
|
+
"pink",
|
|
6
|
+
"orange",
|
|
7
|
+
"green",
|
|
8
|
+
"yellow",
|
|
9
|
+
"red",
|
|
10
|
+
"neutral"
|
|
11
|
+
];
|
|
12
|
+
const CHART_SERIES_COLORS = [
|
|
13
|
+
"blue",
|
|
14
|
+
"purple",
|
|
15
|
+
"pink",
|
|
16
|
+
"orange",
|
|
17
|
+
"green",
|
|
18
|
+
"yellow"
|
|
19
|
+
];
|
|
20
|
+
function chartColor(color) {
|
|
21
|
+
return `var(--chart-${color})`;
|
|
22
|
+
}
|
|
23
|
+
function chartSeriesColor(index) {
|
|
24
|
+
return CHART_SERIES_COLORS[index % CHART_SERIES_COLORS.length] ?? "blue";
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { CHART_COLORS, CHART_SERIES_COLORS, chartColor, chartSeriesColor };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ChartCurve } from "@tanstack/charts";
|
|
2
|
+
//#region src/ui/chart-curves.d.ts
|
|
3
|
+
export type ChartCurveName = "smooth" | "linear" | "step";
|
|
4
|
+
export declare const smoothCurve: ChartCurve;
|
|
5
|
+
export declare const stepCurve: ChartCurve;
|
|
6
|
+
export declare function chartCurve(name: ChartCurveName): ChartCurve | undefined;
|
|
7
|
+
//#endregion
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//#region src/ui/chart-curves.ts
|
|
2
|
+
function monotoneTangents(points) {
|
|
3
|
+
const tangents = points.map(() => 0);
|
|
4
|
+
const last = points.length - 1;
|
|
5
|
+
for (let index = 1; index < last; index += 1) {
|
|
6
|
+
const [x0, y0] = points[index - 1] ?? [0, 0];
|
|
7
|
+
const [x1, y1] = points[index] ?? [0, 0];
|
|
8
|
+
const [x2, y2] = points[index + 1] ?? [0, 0];
|
|
9
|
+
const h0 = x1 - x0;
|
|
10
|
+
const h1 = x2 - x1;
|
|
11
|
+
const s0 = h0 ? (y1 - y0) / h0 : 0;
|
|
12
|
+
const s1 = h1 ? (y2 - y1) / h1 : 0;
|
|
13
|
+
const weightedSlope = (s0 * h1 + s1 * h0) / (h0 + h1);
|
|
14
|
+
tangents[index] = (Math.sign(s0) + Math.sign(s1)) * Math.min(Math.abs(s0), Math.abs(s1), .5 * Math.abs(weightedSlope)) || 0;
|
|
15
|
+
}
|
|
16
|
+
if (last > 0) {
|
|
17
|
+
tangents[0] = endTangent(points[0], points[1], tangents[1] ?? 0);
|
|
18
|
+
tangents[last] = endTangent(points[last - 1], points[last], tangents[last - 1] ?? 0);
|
|
19
|
+
}
|
|
20
|
+
return tangents;
|
|
21
|
+
}
|
|
22
|
+
function endTangent(from, to, neighbourTangent) {
|
|
23
|
+
if (!(from && to)) return neighbourTangent;
|
|
24
|
+
const h = to[0] - from[0];
|
|
25
|
+
return h ? (3 * (to[1] - from[1]) / h - neighbourTangent) / 2 : neighbourTangent;
|
|
26
|
+
}
|
|
27
|
+
function monotoneSegments(points) {
|
|
28
|
+
if (points.length < 3) return points.slice(1).map(([x, y]) => `L${x},${y}`);
|
|
29
|
+
const tangents = monotoneTangents(points);
|
|
30
|
+
return points.slice(1).map(([x1, y1], index) => {
|
|
31
|
+
const [x0, y0] = points[index] ?? [x1, y1];
|
|
32
|
+
const t0 = tangents[index] ?? 0;
|
|
33
|
+
const t1 = tangents[index + 1] ?? 0;
|
|
34
|
+
const dx = (x1 - x0) / 3;
|
|
35
|
+
return `C${x0 + dx},${y0 + dx * t0} ${x1 - dx},${y1 - dx * t1} ${x1},${y1}`;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function reversedMonotoneSegments(points) {
|
|
39
|
+
if (points.length < 3) return points.slice(0, -1).map(([x, y]) => `L${x},${y}`).reverse();
|
|
40
|
+
const tangents = monotoneTangents(points);
|
|
41
|
+
return points.slice(1).map(([x1, y1], index) => {
|
|
42
|
+
const [x0, y0] = points[index] ?? [x1, y1];
|
|
43
|
+
const t0 = tangents[index] ?? 0;
|
|
44
|
+
const t1 = tangents[index + 1] ?? 0;
|
|
45
|
+
const dx = (x1 - x0) / 3;
|
|
46
|
+
return `C${x1 - dx},${y1 - dx * t1} ${x0 + dx},${y0 + dx * t0} ${x0},${y0}`;
|
|
47
|
+
}).reverse();
|
|
48
|
+
}
|
|
49
|
+
function closedArea(top, bottom, topSegments, bottomSegments) {
|
|
50
|
+
const [firstX, firstY] = top[0] ?? [0, 0];
|
|
51
|
+
const [lastX, lastY] = bottom.at(-1) ?? top.at(-1) ?? [0, 0];
|
|
52
|
+
return `M${firstX},${firstY}${topSegments.join("")}L${lastX},${lastY}${bottomSegments.join("")}Z`;
|
|
53
|
+
}
|
|
54
|
+
const smoothCurve = {
|
|
55
|
+
line(points) {
|
|
56
|
+
if (points.length === 0) return "";
|
|
57
|
+
const [x, y] = points[0] ?? [0, 0];
|
|
58
|
+
return `M${x},${y}${monotoneSegments(points).join("")}`;
|
|
59
|
+
},
|
|
60
|
+
area(top, bottom) {
|
|
61
|
+
if (top.length === 0) return "";
|
|
62
|
+
return closedArea(top, bottom, monotoneSegments(top), reversedMonotoneSegments(bottom));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
function stepSegments(points) {
|
|
66
|
+
return points.slice(1).map(([x, y]) => `H${x}V${y}`);
|
|
67
|
+
}
|
|
68
|
+
function reversedStepSegments(points) {
|
|
69
|
+
return points.slice(0, -1).map(([, y], index) => `V${y}H${points[index]?.[0] ?? 0}`).reverse();
|
|
70
|
+
}
|
|
71
|
+
const stepCurve = {
|
|
72
|
+
line(points) {
|
|
73
|
+
if (points.length === 0) return "";
|
|
74
|
+
const [x, y] = points[0] ?? [0, 0];
|
|
75
|
+
return `M${x},${y}${stepSegments(points).join("")}`;
|
|
76
|
+
},
|
|
77
|
+
area(top, bottom) {
|
|
78
|
+
if (top.length === 0) return "";
|
|
79
|
+
return closedArea(top, bottom, stepSegments(top), reversedStepSegments(bottom));
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
function chartCurve(name) {
|
|
83
|
+
if (name === "smooth") return smoothCurve;
|
|
84
|
+
if (name === "step") return stepCurve;
|
|
85
|
+
}
|
|
86
|
+
//#endregion
|
|
87
|
+
export { chartCurve, smoothCurve, stepCurve };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ChartColor } from "./chart-colors.js";
|
|
2
|
+
import { ChartFill, ChartSeriesRow, ChartXValue } from "./chart-series.js";
|
|
3
|
+
import { ChartLinearGradient, ChartTooltipContentContext } from "@tanstack/charts";
|
|
4
|
+
import { decorative } from "@tanstack/charts/mark/decorative";
|
|
5
|
+
//#region src/ui/chart-marks.d.ts
|
|
6
|
+
type AnyChartMark = Parameters<typeof decorative>[0];
|
|
7
|
+
export type ChartPart = "area" | "area-hatched" | "line" | "dot" | "reference";
|
|
8
|
+
export declare function chartPart<TMark extends AnyChartMark>(mark: TMark, ...parts: ChartPart[]): TMark;
|
|
9
|
+
export declare function chartBars<TMark extends AnyChartMark>(mark: TMark, axis: "x" | "y"): TMark;
|
|
10
|
+
export type ChartGradientKind = "area" | "bar-y" | "bar-y-negative" | "bar-x" | "bar-x-negative" | "arc";
|
|
11
|
+
export declare function chartGradient(color: ChartColor, kind: ChartGradientKind): ChartLinearGradient;
|
|
12
|
+
export declare function chartGradients(colors: Iterable<ChartColor>, kind: ChartGradientKind): ChartLinearGradient[];
|
|
13
|
+
export declare function chartHatch(color: ChartColor): string;
|
|
14
|
+
export declare function chartFillPaint(color: ChartColor, fill: ChartFill, kind: ChartGradientKind): string;
|
|
15
|
+
type ChartBarPaintRow = {
|
|
16
|
+
color: ChartColor;
|
|
17
|
+
fill: ChartFill;
|
|
18
|
+
value: number | null;
|
|
19
|
+
};
|
|
20
|
+
export declare function chartBarFill(row: ChartBarPaintRow, axis: "x" | "y"): string;
|
|
21
|
+
export declare function chartBarGradients(rows: readonly ChartBarPaintRow[], axis: "x" | "y"): ChartLinearGradient[];
|
|
22
|
+
export declare function chartOutline(color: ChartColor, fill: ChartFill): string;
|
|
23
|
+
export declare function quietAxis<TValue>(format: (value: TValue) => string): {
|
|
24
|
+
readonly line: false;
|
|
25
|
+
readonly ticks: {
|
|
26
|
+
readonly line: false;
|
|
27
|
+
readonly padding: 8;
|
|
28
|
+
readonly format: (value: TValue) => string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export declare const quietGrid: {
|
|
32
|
+
readonly strokeDasharray: "3 3";
|
|
33
|
+
};
|
|
34
|
+
export declare const dimUnfocused: readonly [{
|
|
35
|
+
readonly when: {
|
|
36
|
+
readonly focus: "unmatched";
|
|
37
|
+
};
|
|
38
|
+
readonly style: {
|
|
39
|
+
readonly opacity: 0.4;
|
|
40
|
+
};
|
|
41
|
+
}];
|
|
42
|
+
export type ChartReference = {
|
|
43
|
+
value: number;
|
|
44
|
+
label?: string;
|
|
45
|
+
color?: ChartColor;
|
|
46
|
+
};
|
|
47
|
+
export declare function chartReferenceMarks(references: readonly ChartReference[], axis: "x" | "y", anchor: ChartXValue | undefined): ((import("@tanstack/charts").CartesianChartMark<ChartReference, number, NonNullable<ChartXValue>, number, NonNullable<ChartXValue>, import("@tanstack/charts/types/core").MarkScaleBindings<undefined, undefined>> & {
|
|
48
|
+
readonly __decorativeMark: import("@tanstack/charts").CartesianChartMark<ChartReference, number, NonNullable<ChartXValue>, number, NonNullable<ChartXValue>, import("@tanstack/charts/types/core").MarkScaleBindings<undefined, undefined>> | import("@tanstack/charts").CartesianChartMark<ChartReference, NonNullable<ChartXValue>, number, NonNullable<ChartXValue>, number, import("@tanstack/charts/types/core").MarkScaleBindings<undefined, undefined>>;
|
|
49
|
+
}) | (import("@tanstack/charts").CartesianChartMark<ChartReference, NonNullable<ChartXValue>, number, NonNullable<ChartXValue>, number, import("@tanstack/charts/types/core").MarkScaleBindings<undefined, undefined>> & {
|
|
50
|
+
readonly __decorativeMark: import("@tanstack/charts").CartesianChartMark<ChartReference, number, NonNullable<ChartXValue>, number, NonNullable<ChartXValue>, import("@tanstack/charts/types/core").MarkScaleBindings<undefined, undefined>> | import("@tanstack/charts").CartesianChartMark<ChartReference, NonNullable<ChartXValue>, number, NonNullable<ChartXValue>, number, import("@tanstack/charts/types/core").MarkScaleBindings<undefined, undefined>>;
|
|
51
|
+
}))[];
|
|
52
|
+
type SeriesPoint = {
|
|
53
|
+
key: string;
|
|
54
|
+
datum: ChartSeriesRow;
|
|
55
|
+
};
|
|
56
|
+
export declare function seriesTooltip(seriesLabels: readonly string[], formatX: (value: ChartXValue) => string, formatValue: (value: number) => string): {
|
|
57
|
+
use: import("@tanstack/charts").ChartTooltipExtension;
|
|
58
|
+
content: (points: readonly SeriesPoint[], context: ChartTooltipContentContext) => {
|
|
59
|
+
title: string | undefined;
|
|
60
|
+
rows: {
|
|
61
|
+
label: string;
|
|
62
|
+
value: string;
|
|
63
|
+
color: string;
|
|
64
|
+
active: boolean;
|
|
65
|
+
}[];
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
//#endregion
|