@arcfusionz/arc-analytic-ui 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +13 -0
  2. package/dist/components/AnomalyCard/AnomalyCard.chart.d.ts +64 -0
  3. package/dist/components/AnomalyCard/AnomalyCard.chart.js +341 -0
  4. package/dist/components/AnomalyCard/AnomalyCard.d.ts +169 -0
  5. package/dist/components/AnomalyCard/AnomalyCard.js +1019 -0
  6. package/dist/components/AnomalyCard/AnomalyCard.model.d.ts +508 -0
  7. package/dist/components/AnomalyCard/AnomalyCard.model.js +553 -0
  8. package/dist/components/AnomalyCard/index.d.ts +4 -0
  9. package/dist/components/AnomalyCard/index.js +4 -0
  10. package/dist/components/ChartCard/ChartCard.chart.d.ts +72 -0
  11. package/dist/components/ChartCard/ChartCard.chart.js +1333 -0
  12. package/dist/components/ChartCard/ChartCard.d.ts +206 -0
  13. package/dist/components/ChartCard/ChartCard.js +718 -0
  14. package/dist/components/ChartCard/ChartCard.model.d.ts +452 -0
  15. package/dist/components/ChartCard/ChartCard.model.js +1223 -0
  16. package/dist/components/ChartCard/index.d.ts +4 -0
  17. package/dist/components/ChartCard/index.js +4 -0
  18. package/dist/components/ComparisonCard/ComparisonCard.chart.d.ts +80 -0
  19. package/dist/components/ComparisonCard/ComparisonCard.chart.js +337 -0
  20. package/dist/components/ComparisonCard/ComparisonCard.d.ts +200 -0
  21. package/dist/components/ComparisonCard/ComparisonCard.js +1325 -0
  22. package/dist/components/ComparisonCard/ComparisonCard.model.d.ts +309 -0
  23. package/dist/components/ComparisonCard/ComparisonCard.model.js +263 -0
  24. package/dist/components/ComparisonCard/index.d.ts +4 -0
  25. package/dist/components/ComparisonCard/index.js +4 -0
  26. package/dist/components/ForecastCard/ForecastCard.chart.d.ts +82 -0
  27. package/dist/components/ForecastCard/ForecastCard.chart.js +416 -0
  28. package/dist/components/ForecastCard/ForecastCard.d.ts +148 -0
  29. package/dist/components/ForecastCard/ForecastCard.js +1029 -0
  30. package/dist/components/ForecastCard/ForecastCard.model.d.ts +497 -0
  31. package/dist/components/ForecastCard/ForecastCard.model.js +554 -0
  32. package/dist/components/ForecastCard/index.d.ts +4 -0
  33. package/dist/components/ForecastCard/index.js +4 -0
  34. package/dist/components/HighlightCard/HighlightCard.chart.d.ts +122 -0
  35. package/dist/components/HighlightCard/HighlightCard.chart.js +660 -0
  36. package/dist/components/HighlightCard/HighlightCard.d.ts +202 -0
  37. package/dist/components/HighlightCard/HighlightCard.js +1286 -0
  38. package/dist/components/HighlightCard/HighlightCard.model.d.ts +692 -0
  39. package/dist/components/HighlightCard/HighlightCard.model.js +875 -0
  40. package/dist/components/HighlightCard/index.d.ts +4 -0
  41. package/dist/components/HighlightCard/index.js +4 -0
  42. package/dist/components/InsightCard/InsightCard.chart.d.ts +91 -0
  43. package/dist/components/InsightCard/InsightCard.chart.js +471 -0
  44. package/dist/components/InsightCard/InsightCard.d.ts +161 -0
  45. package/dist/components/InsightCard/InsightCard.js +961 -0
  46. package/dist/components/InsightCard/InsightCard.model.d.ts +405 -0
  47. package/dist/components/InsightCard/InsightCard.model.js +421 -0
  48. package/dist/components/InsightCard/index.d.ts +4 -0
  49. package/dist/components/InsightCard/index.js +4 -0
  50. package/dist/components/MetricCard/MetricCard.chart.d.ts +79 -0
  51. package/dist/components/MetricCard/MetricCard.chart.js +435 -0
  52. package/dist/components/MetricCard/MetricCard.d.ts +227 -0
  53. package/dist/components/MetricCard/MetricCard.js +1293 -0
  54. package/dist/components/MetricCard/MetricCard.model.d.ts +521 -0
  55. package/dist/components/MetricCard/MetricCard.model.js +627 -0
  56. package/dist/components/MetricCard/index.d.ts +4 -0
  57. package/dist/components/MetricCard/index.js +4 -0
  58. package/dist/components/ProfileCard/ProfileCard.chart.d.ts +64 -0
  59. package/dist/components/ProfileCard/ProfileCard.chart.js +427 -0
  60. package/dist/components/ProfileCard/ProfileCard.d.ts +140 -0
  61. package/dist/components/ProfileCard/ProfileCard.js +735 -0
  62. package/dist/components/ProfileCard/ProfileCard.model.d.ts +279 -0
  63. package/dist/components/ProfileCard/ProfileCard.model.js +244 -0
  64. package/dist/components/ProfileCard/index.d.ts +4 -0
  65. package/dist/components/ProfileCard/index.js +4 -0
  66. package/dist/components/ProgressCard/ProgressCard.chart.d.ts +73 -0
  67. package/dist/components/ProgressCard/ProgressCard.chart.js +373 -0
  68. package/dist/components/ProgressCard/ProgressCard.d.ts +137 -0
  69. package/dist/components/ProgressCard/ProgressCard.js +908 -0
  70. package/dist/components/ProgressCard/ProgressCard.model.d.ts +385 -0
  71. package/dist/components/ProgressCard/ProgressCard.model.js +389 -0
  72. package/dist/components/ProgressCard/index.d.ts +4 -0
  73. package/dist/components/ProgressCard/index.js +4 -0
  74. package/dist/components/ReportCard/ReportCard.chart.d.ts +45 -0
  75. package/dist/components/ReportCard/ReportCard.chart.js +231 -0
  76. package/dist/components/ReportCard/ReportCard.d.ts +153 -0
  77. package/dist/components/ReportCard/ReportCard.js +1118 -0
  78. package/dist/components/ReportCard/ReportCard.model.d.ts +392 -0
  79. package/dist/components/ReportCard/ReportCard.model.js +399 -0
  80. package/dist/components/ReportCard/index.d.ts +4 -0
  81. package/dist/components/ReportCard/index.js +4 -0
  82. package/dist/components/SankeyCard/SankeyCard.chart.d.ts +75 -0
  83. package/dist/components/SankeyCard/SankeyCard.chart.js +249 -0
  84. package/dist/components/SankeyCard/SankeyCard.d.ts +185 -0
  85. package/dist/components/SankeyCard/SankeyCard.js +788 -0
  86. package/dist/components/SankeyCard/SankeyCard.model.d.ts +501 -0
  87. package/dist/components/SankeyCard/SankeyCard.model.js +789 -0
  88. package/dist/components/SankeyCard/index.d.ts +4 -0
  89. package/dist/components/SankeyCard/index.js +4 -0
  90. package/dist/components/StatusCard/StatusCard.chart.d.ts +60 -0
  91. package/dist/components/StatusCard/StatusCard.chart.js +188 -0
  92. package/dist/components/StatusCard/StatusCard.d.ts +176 -0
  93. package/dist/components/StatusCard/StatusCard.js +989 -0
  94. package/dist/components/StatusCard/StatusCard.model.d.ts +555 -0
  95. package/dist/components/StatusCard/StatusCard.model.js +776 -0
  96. package/dist/components/StatusCard/index.d.ts +4 -0
  97. package/dist/components/StatusCard/index.js +4 -0
  98. package/dist/components/TableCard/TableCard.chart.d.ts +53 -0
  99. package/dist/components/TableCard/TableCard.chart.js +192 -0
  100. package/dist/components/TableCard/TableCard.d.ts +140 -0
  101. package/dist/components/TableCard/TableCard.js +811 -0
  102. package/dist/components/TableCard/TableCard.model.d.ts +381 -0
  103. package/dist/components/TableCard/TableCard.model.js +475 -0
  104. package/dist/components/TableCard/index.d.ts +4 -0
  105. package/dist/components/TableCard/index.js +4 -0
  106. package/dist/components/TargetCard/TargetCard.chart.d.ts +52 -0
  107. package/dist/components/TargetCard/TargetCard.chart.js +273 -0
  108. package/dist/components/TargetCard/TargetCard.d.ts +143 -0
  109. package/dist/components/TargetCard/TargetCard.js +910 -0
  110. package/dist/components/TargetCard/TargetCard.model.d.ts +468 -0
  111. package/dist/components/TargetCard/TargetCard.model.js +581 -0
  112. package/dist/components/TargetCard/index.d.ts +4 -0
  113. package/dist/components/TargetCard/index.js +4 -0
  114. package/dist/components/TreeCard/TreeCard.chart.d.ts +80 -0
  115. package/dist/components/TreeCard/TreeCard.chart.js +178 -0
  116. package/dist/components/TreeCard/TreeCard.d.ts +182 -0
  117. package/dist/components/TreeCard/TreeCard.js +1187 -0
  118. package/dist/components/TreeCard/TreeCard.model.d.ts +330 -0
  119. package/dist/components/TreeCard/TreeCard.model.js +401 -0
  120. package/dist/components/TreeCard/index.d.ts +4 -0
  121. package/dist/components/TreeCard/index.js +4 -0
  122. package/dist/components/TrendCard/TrendCard.chart.d.ts +75 -0
  123. package/dist/components/TrendCard/TrendCard.chart.js +393 -0
  124. package/dist/components/TrendCard/TrendCard.d.ts +224 -0
  125. package/dist/components/TrendCard/TrendCard.js +1053 -0
  126. package/dist/components/TrendCard/TrendCard.model.d.ts +545 -0
  127. package/dist/components/TrendCard/TrendCard.model.js +623 -0
  128. package/dist/components/TrendCard/index.d.ts +4 -0
  129. package/dist/components/TrendCard/index.js +4 -0
  130. package/dist/index.d.ts +76 -0
  131. package/dist/index.js +57 -0
  132. package/dist/lib/vega/ArcVegaChart.d.ts +24 -0
  133. package/dist/lib/vega/ArcVegaChart.js +42 -0
  134. package/dist/lib/vega/ArcVegaSurface.d.ts +13 -0
  135. package/dist/lib/vega/ArcVegaSurface.js +38 -0
  136. package/dist/lib/vega/axisTicks.js +156 -0
  137. package/dist/lib/vega/config.d.ts +29 -0
  138. package/dist/lib/vega/config.js +113 -0
  139. package/dist/lib/vega/embed.d.ts +29 -0
  140. package/dist/lib/vega/embed.js +29 -0
  141. package/dist/lib/vega/engine.d.ts +10 -0
  142. package/dist/lib/vega/engine.js +6 -0
  143. package/dist/lib/vega/formatters.d.ts +125 -0
  144. package/dist/lib/vega/formatters.js +277 -0
  145. package/dist/lib/vega/index.d.ts +11 -0
  146. package/dist/lib/vega/load.d.ts +13 -0
  147. package/dist/lib/vega/load.js +21 -0
  148. package/dist/lib/vega/time.d.ts +45 -0
  149. package/dist/lib/vega/time.js +105 -0
  150. package/dist/lib/vega/tokens.d.ts +73 -0
  151. package/dist/lib/vega/tokens.js +137 -0
  152. package/dist/lib/vega/tooltip.d.ts +45 -0
  153. package/dist/lib/vega/tooltip.js +124 -0
  154. package/dist/lib/vega/useArcVega.d.ts +43 -0
  155. package/dist/lib/vega/useArcVega.js +121 -0
  156. package/package.json +60 -0
@@ -0,0 +1,1333 @@
1
+ import { AXIS_LABEL_LIMIT_EXPR } from "../../lib/vega/axisTicks.js";
2
+ import { arcVegaConfig } from "../../lib/vega/config.js";
3
+ import { CHART_TYPES } from "./ChartCard.model.js";
4
+ //#region src/components/ChartCard/ChartCard.chart.ts
5
+ /** Long-form rows, one per reading — `ChartRow` from the model. */
6
+ const CHART_TABLE_DATASET = "table";
7
+ /** Distinct axis categories `{ x }` — the radar's spokes and labels. */
8
+ const CHART_AXES_DATASET = "axes";
9
+ /** The hover selection: nearest point, so a pointer anywhere near a mark reads it. */
10
+ const CHART_HOVER_PARAM = "hover";
11
+ /** Responsive x tick values, recomputed by Vega when the domain or the width changes. */
12
+ const CHART_X_TICKS_PARAM = "chart_x_ticks";
13
+ /** Series labels, pushed as a signal so a violin's category axis needs no spec rebuild. */
14
+ const CHART_SERIES_NAMES_PARAM = "chart_series_names";
15
+ const POINT_SIZE = 64;
16
+ const BAR_WIDTH_EXPR = "min(24, bandwidth('x') * 0.8)";
17
+ const BAR_HEIGHT_EXPR = "min(24, bandwidth('y') * 0.8)";
18
+ const PLOT_PADDING = 6;
19
+ const STACK_GAP_PX = 1;
20
+ const AREA_FILL_OPACITY = .12;
21
+ const POLAR_OUTER = "min(width, height) / 2 - 4";
22
+ const RADAR_RADIUS = "max(24, min(width, height) / 2 - 46)";
23
+ /**
24
+ * A radar axis on the unit plane. Both scales map one unit to the SAME pixel
25
+ * radius, so the web stays circular however wide the panel is — and because
26
+ * the domain is derived from the frame rather than fixed, a wide card spends
27
+ * its extra width on the label gutter instead of stretching the web into an
28
+ * ellipse.
29
+ */
30
+ const radarPlane = (field, extent) => ({
31
+ field,
32
+ type: "quantitative",
33
+ axis: null,
34
+ scale: {
35
+ nice: false,
36
+ zero: false,
37
+ domainMin: { expr: `-(${extent} / 2) / (${RADAR_RADIUS})` },
38
+ domainMax: { expr: `(${extent} / 2) / (${RADAR_RADIUS})` }
39
+ }
40
+ });
41
+ const RADAR_ORDER = {
42
+ field: "ai",
43
+ type: "quantitative"
44
+ };
45
+ const VIOLIN_HALF_WIDTH = .42;
46
+ const DENSITY_STEPS = 64;
47
+ const PUNCHCARD_SIZE_RANGE = [0, 320];
48
+ const BUBBLE_SIZE_RANGE = [16, 800];
49
+ const IDENTITY_COLOR = {
50
+ field: "c",
51
+ type: "nominal",
52
+ scale: null,
53
+ legend: null
54
+ };
55
+ const SERIES_DETAIL = {
56
+ field: "s",
57
+ type: "nominal"
58
+ };
59
+ /** The magnitude scale for the forms whose colour IS the measure. */
60
+ function magnitudeColor(tokens, format, title) {
61
+ return {
62
+ field: "v",
63
+ type: "quantitative",
64
+ title,
65
+ scale: { range: tokens.sequential },
66
+ legend: null,
67
+ format,
68
+ formatType: "arcNumber"
69
+ };
70
+ }
71
+ /**
72
+ * The name to draw on an axis whose field the consumer did not name: a
73
+ * histogram counts, a density has no unit, a box plot reads the measure. Each
74
+ * of those has a sensible fallback — but a fallback is still an axis title,
75
+ * and axis titles are off unless the consumer asked for them.
76
+ */
77
+ function axisTitle(options, preferred, fallback) {
78
+ if (!options.axisTitles) return null;
79
+ return preferred ?? fallback;
80
+ }
81
+ /**
82
+ * The measure axis: no domain line, hairline grid instead, rounded ticks, and
83
+ * the card's own number grammar — so an axis label reads the way the tooltip
84
+ * and the table read it.
85
+ */
86
+ function measureAxis(options, title, format) {
87
+ if (!options.axes) return null;
88
+ return {
89
+ title: title ?? null,
90
+ domain: false,
91
+ ticks: false,
92
+ grid: options.grid,
93
+ tickCount: options.yTicks,
94
+ labelOverlap: true,
95
+ labelPadding: 6,
96
+ labelLimit: 80,
97
+ format: format ?? options.valueFormat,
98
+ formatType: "arcNumber"
99
+ };
100
+ }
101
+ /**
102
+ * The category or time axis: a domain line, no grid, and the package's
103
+ * responsive tick selection — explicit values that keep the endpoints, an
104
+ * inward alignment at the edges, and a bounded label width — so a narrow card
105
+ * thins its labels instead of overlapping them.
106
+ */
107
+ function positionAxis(options, title, discrete) {
108
+ if (!options.axes) return null;
109
+ const { xType, axisTimeFormat } = options;
110
+ const tickFormat = xType === "temporal" ? axisTimeFormat : "compact";
111
+ const tickKind = xType === "temporal" ? "time" : xType === "quantitative" ? "number" : "text";
112
+ const args = `domain('x'), width, ${discrete}, ${JSON.stringify(tickFormat)}, ${JSON.stringify(tickKind)}`;
113
+ const labels = xType === "temporal" ? {
114
+ format: axisTimeFormat,
115
+ formatType: "arcTime"
116
+ } : xType === "quantitative" ? {
117
+ format: "compact",
118
+ formatType: "arcNumber"
119
+ } : {};
120
+ return {
121
+ title: title ?? null,
122
+ orient: "bottom",
123
+ domain: true,
124
+ ticks: false,
125
+ grid: false,
126
+ values: { expr: CHART_X_TICKS_PARAM },
127
+ labelAngle: 0,
128
+ labelAlign: { expr: `arcAxisLabelAlign(datum.value, ${args})` },
129
+ labelFlush: false,
130
+ labelOverlap: false,
131
+ labelLimit: { expr: AXIS_LABEL_LIMIT_EXPR },
132
+ labelPadding: 4,
133
+ ...labels
134
+ };
135
+ }
136
+ /** A plain nominal axis for the discrete side of a horizontal or table-shaped plot. */
137
+ function categoryAxis(options, title, orient) {
138
+ if (!options.axes) return null;
139
+ return {
140
+ title: title ?? null,
141
+ orient,
142
+ domain: false,
143
+ ticks: false,
144
+ grid: false,
145
+ labelOverlap: true,
146
+ labelLimit: orient === "left" ? 120 : 80,
147
+ labelPadding: 6
148
+ };
149
+ }
150
+ /** The x position for the cartesian forms whose x is the primary dimension. */
151
+ function xPosition(options, discrete) {
152
+ const axis = positionAxis(options, options.xTitle, discrete);
153
+ if (discrete) return {
154
+ field: "x",
155
+ type: "ordinal",
156
+ sort: null,
157
+ axis,
158
+ scale: { paddingOuter: .05 }
159
+ };
160
+ return {
161
+ field: "x",
162
+ type: options.xType,
163
+ axis,
164
+ scale: {
165
+ nice: false,
166
+ ...options.xType === "quantitative" ? { zero: false } : {}
167
+ }
168
+ };
169
+ }
170
+ /** The measure position, with the zero baseline the form requires. */
171
+ function measurePosition(options, { zero, stack, format }) {
172
+ return {
173
+ field: "v",
174
+ type: "quantitative",
175
+ title: options.valueTitle,
176
+ axis: measureAxis(options, options.yTitle, format),
177
+ scale: {
178
+ zero,
179
+ nice: options.axes
180
+ },
181
+ ...stack === false ? { stack: null } : { stack }
182
+ };
183
+ }
184
+ /** The tooltip channel: the dimension, the series when there is more than one, the measure, and any second measure. */
185
+ function tooltipChannel(options, extras = []) {
186
+ const info = CHART_TYPES[options.type];
187
+ const channel = [];
188
+ if (info.fields.x !== "none") channel.push(options.xType === "temporal" ? {
189
+ field: "x",
190
+ type: "temporal",
191
+ title: options.xTitle ?? "",
192
+ format: options.timeFormat,
193
+ formatType: "arcTime"
194
+ } : options.xType === "quantitative" ? {
195
+ field: "x",
196
+ type: "quantitative",
197
+ title: options.xTitle ?? "",
198
+ format: "number",
199
+ formatType: "arcNumber"
200
+ } : {
201
+ field: "x",
202
+ type: "nominal",
203
+ title: options.xTitle ?? ""
204
+ });
205
+ channel.push({
206
+ field: "n",
207
+ type: "nominal",
208
+ title: options.seriesTitle
209
+ });
210
+ channel.push({
211
+ field: "v",
212
+ type: "quantitative",
213
+ title: options.valueTitle,
214
+ format: options.valueFormat,
215
+ formatType: "arcNumber"
216
+ });
217
+ if (info.fields.size && options.sizeTitle) channel.push({
218
+ field: "z",
219
+ type: "quantitative",
220
+ title: options.sizeTitle,
221
+ format: options.valueFormat,
222
+ formatType: "arcNumber"
223
+ });
224
+ return [...channel, ...extras];
225
+ }
226
+ /**
227
+ * The nearest-point hover every form shares: a `point` selection that lights
228
+ * the mark under the pointer and hands the Arc tooltip handler the row. The
229
+ * marks carry `tooltip` themselves rather than through a separate transparent
230
+ * layer, because unlike a multi-series trend this card reads ONE mark at a
231
+ * time — there is no shared-x column to light up.
232
+ *
233
+ * It is declared on ONE layer, never at the top level: Vega-Lite pushes a
234
+ * top-level selection param down into every layer of a layered spec, and the
235
+ * duplicated signals fail the parse outright ("Duplicate signal name:
236
+ * hover_tuple"). Sibling layers still read it — a layer param is hoisted to a
237
+ * top-level Vega signal, which is what makes `{param: "hover"}` conditions
238
+ * work across the stack.
239
+ */
240
+ const HOVER_PARAM_DEF = {
241
+ name: CHART_HOVER_PARAM,
242
+ select: {
243
+ type: "point",
244
+ on: "pointerover",
245
+ clear: "pointerout"
246
+ }
247
+ };
248
+ /**
249
+ * Forms that take no selection param. Vega-Lite refuses one on a composite
250
+ * mark ("Selection not supported for boxplot yet") and the empty selection
251
+ * store then starves the shared scale into an infinite extent.
252
+ */
253
+ const SELECTION_FREE = ["boxplot"];
254
+ /** Dim every mark but the hovered one — the redundant half of the tooltip, never its replacement. */
255
+ function hoverOpacity(options, base = 1) {
256
+ if (!options.tooltip) return {};
257
+ return { opacity: {
258
+ condition: {
259
+ param: CHART_HOVER_PARAM,
260
+ empty: true,
261
+ value: base
262
+ },
263
+ value: base * .45
264
+ } };
265
+ }
266
+ /**
267
+ * One layer stack per form. Each returns the layers to draw and, where the
268
+ * form needs them, extra top-level transforms — nothing here reads data, only
269
+ * its shape.
270
+ */
271
+ function buildLayers(options, tokens) {
272
+ const { type } = options;
273
+ const info = CHART_TYPES[type];
274
+ const tooltip = options.tooltip ? { tooltip: tooltipChannel(options) } : {};
275
+ const encodedTooltip = options.tooltip ? { tooltip: { content: "encoding" } } : {};
276
+ const discreteX = options.xType === "nominal" || options.xType === "ordinal";
277
+ switch (type) {
278
+ case "bar":
279
+ case "bar-grouped":
280
+ case "bar-stacked":
281
+ case "bar-normalized": {
282
+ const grouped = type === "bar-grouped";
283
+ const stack = info.stack;
284
+ const normalized = type === "bar-normalized";
285
+ return { layers: [{
286
+ mark: {
287
+ type: "bar",
288
+ cornerRadiusEnd: 2,
289
+ ...grouped ? {} : { width: { expr: BAR_WIDTH_EXPR } },
290
+ ...stack === false ? {} : {
291
+ stroke: tokens.background,
292
+ strokeWidth: STACK_GAP_PX
293
+ },
294
+ ...encodedTooltip
295
+ },
296
+ encoding: {
297
+ x: xPosition(options, true),
298
+ y: measurePosition(options, {
299
+ zero: true,
300
+ stack,
301
+ format: normalized ? "percent:0" : void 0
302
+ }),
303
+ ...grouped ? { xOffset: {
304
+ field: "i",
305
+ type: "ordinal",
306
+ sort: null
307
+ } } : { detail: SERIES_DETAIL },
308
+ fill: IDENTITY_COLOR,
309
+ ...hoverOpacity(options),
310
+ ...tooltip
311
+ }
312
+ }] };
313
+ }
314
+ case "bar-horizontal": return { layers: [{
315
+ mark: {
316
+ type: "bar",
317
+ cornerRadiusEnd: 2,
318
+ height: { expr: BAR_HEIGHT_EXPR }
319
+ },
320
+ encoding: {
321
+ y: {
322
+ field: "x",
323
+ type: "ordinal",
324
+ sort: null,
325
+ axis: categoryAxis(options, options.xTitle, "left")
326
+ },
327
+ x: measurePosition(options, {
328
+ zero: true,
329
+ stack: false
330
+ }),
331
+ detail: SERIES_DETAIL,
332
+ fill: IDENTITY_COLOR,
333
+ ...hoverOpacity(options),
334
+ ...tooltip
335
+ }
336
+ }] };
337
+ case "bar-diverging": return { layers: [{
338
+ mark: {
339
+ type: "bar",
340
+ cornerRadiusEnd: 2,
341
+ height: { expr: BAR_HEIGHT_EXPR }
342
+ },
343
+ encoding: {
344
+ y: {
345
+ field: "x",
346
+ type: "ordinal",
347
+ sort: null,
348
+ axis: categoryAxis(options, options.xTitle, "left")
349
+ },
350
+ x: measurePosition(options, {
351
+ zero: true,
352
+ stack: false
353
+ }),
354
+ fill: {
355
+ condition: {
356
+ test: "datum.v < 0",
357
+ value: tokens.divergingNegative
358
+ },
359
+ value: tokens.divergingPositive
360
+ },
361
+ ...hoverOpacity(options),
362
+ ...tooltip
363
+ }
364
+ }, {
365
+ mark: {
366
+ type: "rule",
367
+ stroke: tokens.axis,
368
+ strokeWidth: 1,
369
+ aria: false
370
+ },
371
+ encoding: { x: {
372
+ datum: 0,
373
+ type: "quantitative"
374
+ } }
375
+ }] };
376
+ case "bar-range": {
377
+ const rangeType = options.xType === "temporal" ? "temporal" : "quantitative";
378
+ return { layers: [{
379
+ mark: {
380
+ type: "bar",
381
+ cornerRadius: 3,
382
+ height: { expr: BAR_HEIGHT_EXPR }
383
+ },
384
+ encoding: {
385
+ y: {
386
+ field: "n",
387
+ type: "nominal",
388
+ sort: null,
389
+ axis: categoryAxis(options, axisTitle(options, options.xTitle, options.seriesTitle), "left")
390
+ },
391
+ x: {
392
+ field: "x",
393
+ type: rangeType,
394
+ title: options.xTitle ?? null,
395
+ axis: rangeType === "temporal" ? {
396
+ ...measureAxis(options, options.xTitle),
397
+ format: options.axisTimeFormat,
398
+ formatType: "arcTime"
399
+ } : measureAxis(options, options.xTitle, "compact"),
400
+ scale: {
401
+ nice: false,
402
+ ...rangeType === "quantitative" ? { zero: false } : {}
403
+ }
404
+ },
405
+ x2: { field: "x2" },
406
+ fill: IDENTITY_COLOR,
407
+ ...hoverOpacity(options),
408
+ ...tooltip
409
+ }
410
+ }] };
411
+ }
412
+ case "lollipop": {
413
+ const yField = {
414
+ field: "x",
415
+ type: "ordinal",
416
+ sort: null,
417
+ axis: categoryAxis(options, options.xTitle, "left")
418
+ };
419
+ const xField = measurePosition(options, {
420
+ zero: true,
421
+ stack: false
422
+ });
423
+ return { layers: [{
424
+ mark: {
425
+ type: "rule",
426
+ strokeWidth: 1.5,
427
+ aria: false
428
+ },
429
+ encoding: {
430
+ y: yField,
431
+ x: xField,
432
+ x2: { datum: 0 },
433
+ stroke: IDENTITY_COLOR
434
+ }
435
+ }, {
436
+ mark: {
437
+ type: "point",
438
+ filled: true,
439
+ size: 96,
440
+ stroke: tokens.background,
441
+ strokeWidth: 1.5
442
+ },
443
+ encoding: {
444
+ y: yField,
445
+ x: xField,
446
+ fill: IDENTITY_COLOR,
447
+ ...hoverOpacity(options),
448
+ ...tooltip
449
+ }
450
+ }] };
451
+ }
452
+ case "waterfall": return { layers: [{
453
+ mark: {
454
+ type: "bar",
455
+ cornerRadius: 2,
456
+ width: { expr: BAR_WIDTH_EXPR }
457
+ },
458
+ encoding: {
459
+ x: xPosition(options, true),
460
+ y: {
461
+ field: "lo",
462
+ type: "quantitative",
463
+ title: options.valueTitle,
464
+ axis: measureAxis(options, options.yTitle),
465
+ scale: {
466
+ zero: true,
467
+ nice: options.axes
468
+ },
469
+ stack: null
470
+ },
471
+ y2: { field: "hi" },
472
+ fill: IDENTITY_COLOR,
473
+ ...hoverOpacity(options),
474
+ ...options.tooltip ? { tooltip: tooltipChannel(options, [{
475
+ field: "hi",
476
+ type: "quantitative",
477
+ title: "Running total",
478
+ format: options.valueFormat,
479
+ formatType: "arcNumber"
480
+ }]) } : {}
481
+ }
482
+ }, {
483
+ mark: {
484
+ type: "rule",
485
+ stroke: tokens.axis,
486
+ strokeWidth: 1,
487
+ aria: false
488
+ },
489
+ encoding: { y: {
490
+ datum: 0,
491
+ type: "quantitative"
492
+ } }
493
+ }] };
494
+ case "line":
495
+ case "line-points":
496
+ case "step":
497
+ case "connected-scatter": {
498
+ const withPoints = type === "line-points" || type === "connected-scatter";
499
+ const x = type === "connected-scatter" ? {
500
+ field: "x",
501
+ type: "quantitative",
502
+ axis: measureAxis(options, options.xTitle, "compact"),
503
+ scale: {
504
+ zero: false,
505
+ nice: options.axes
506
+ }
507
+ } : xPosition(options, discreteX);
508
+ const y = measurePosition(options, {
509
+ zero: false,
510
+ stack: false
511
+ });
512
+ const layers = [{
513
+ mark: {
514
+ type: "line",
515
+ strokeWidth: 2,
516
+ strokeCap: "round",
517
+ strokeJoin: "round",
518
+ interpolate: type === "step" ? "step-after" : "linear",
519
+ invalid: "break-paths-show-domains"
520
+ },
521
+ encoding: {
522
+ x,
523
+ y,
524
+ detail: SERIES_DETAIL,
525
+ stroke: IDENTITY_COLOR,
526
+ ...type === "connected-scatter" ? { order: {
527
+ field: "o",
528
+ type: "quantitative"
529
+ } } : {}
530
+ }
531
+ }];
532
+ if (withPoints) layers.push({
533
+ mark: {
534
+ type: "point",
535
+ filled: true,
536
+ size: POINT_SIZE,
537
+ stroke: tokens.background,
538
+ strokeWidth: 1.5
539
+ },
540
+ encoding: {
541
+ x,
542
+ y,
543
+ fill: IDENTITY_COLOR,
544
+ ...hoverOpacity(options),
545
+ ...tooltip
546
+ }
547
+ });
548
+ else layers.push({
549
+ mark: {
550
+ type: "point",
551
+ filled: true,
552
+ size: POINT_SIZE,
553
+ opacity: 0,
554
+ aria: false
555
+ },
556
+ encoding: {
557
+ x,
558
+ y,
559
+ fill: IDENTITY_COLOR,
560
+ ...tooltip
561
+ }
562
+ });
563
+ return { layers };
564
+ }
565
+ case "trail": return { layers: [{
566
+ mark: {
567
+ type: "trail",
568
+ interpolate: "linear"
569
+ },
570
+ encoding: {
571
+ x: xPosition(options, discreteX),
572
+ y: measurePosition(options, {
573
+ zero: false,
574
+ stack: false
575
+ }),
576
+ detail: SERIES_DETAIL,
577
+ color: IDENTITY_COLOR,
578
+ size: {
579
+ field: "z",
580
+ type: "quantitative",
581
+ legend: null,
582
+ title: options.sizeTitle ?? null
583
+ },
584
+ ...tooltip
585
+ }
586
+ }] };
587
+ case "area":
588
+ case "area-stacked":
589
+ case "area-normalized":
590
+ case "streamgraph": {
591
+ const stack = info.stack;
592
+ const normalized = type === "area-normalized";
593
+ const stream = type === "streamgraph";
594
+ const x = xPosition(options, discreteX);
595
+ const y = measurePosition(options, {
596
+ zero: stack !== false,
597
+ stack,
598
+ format: normalized ? "percent:0" : void 0
599
+ });
600
+ const streamY = stream ? {
601
+ ...y,
602
+ axis: null,
603
+ scale: {
604
+ zero: true,
605
+ nice: false
606
+ }
607
+ } : y;
608
+ return { layers: [{
609
+ mark: {
610
+ type: "area",
611
+ line: false,
612
+ interpolate: stream ? "monotone" : "linear",
613
+ fillOpacity: stack === false ? AREA_FILL_OPACITY : .9,
614
+ invalid: "break-paths-show-domains",
615
+ ...stack === false ? {} : {
616
+ stroke: tokens.background,
617
+ strokeWidth: STACK_GAP_PX
618
+ },
619
+ ...stream ? { aria: true } : {}
620
+ },
621
+ encoding: {
622
+ x,
623
+ y: streamY,
624
+ detail: SERIES_DETAIL,
625
+ fill: IDENTITY_COLOR,
626
+ ...hoverOpacity(options, stack === false ? AREA_FILL_OPACITY : .9),
627
+ ...tooltip
628
+ }
629
+ }, ...stack === false && !stream ? [{
630
+ mark: {
631
+ type: "line",
632
+ strokeWidth: 2,
633
+ strokeCap: "round",
634
+ strokeJoin: "round",
635
+ invalid: "break-paths-show-domains",
636
+ aria: false
637
+ },
638
+ encoding: {
639
+ x,
640
+ y: streamY,
641
+ detail: SERIES_DETAIL,
642
+ stroke: IDENTITY_COLOR
643
+ }
644
+ }] : []] };
645
+ }
646
+ case "slope": {
647
+ const x = {
648
+ field: "x",
649
+ type: "ordinal",
650
+ sort: null,
651
+ axis: options.axes ? {
652
+ title: options.xTitle ?? null,
653
+ orient: "bottom",
654
+ domain: false,
655
+ ticks: false,
656
+ grid: false,
657
+ labelPadding: 8,
658
+ labelLimit: 100
659
+ } : null,
660
+ scale: { padding: .35 }
661
+ };
662
+ const y = measurePosition(options, {
663
+ zero: false,
664
+ stack: false
665
+ });
666
+ return { layers: [{
667
+ mark: {
668
+ type: "line",
669
+ strokeWidth: 2,
670
+ strokeCap: "round"
671
+ },
672
+ encoding: {
673
+ x,
674
+ y,
675
+ detail: SERIES_DETAIL,
676
+ stroke: IDENTITY_COLOR
677
+ }
678
+ }, {
679
+ mark: {
680
+ type: "point",
681
+ filled: true,
682
+ size: POINT_SIZE,
683
+ stroke: tokens.background,
684
+ strokeWidth: 1.5
685
+ },
686
+ encoding: {
687
+ x,
688
+ y,
689
+ fill: IDENTITY_COLOR,
690
+ ...hoverOpacity(options),
691
+ ...tooltip
692
+ }
693
+ }] };
694
+ }
695
+ case "pie":
696
+ case "donut":
697
+ case "radial": {
698
+ const radial = type === "radial";
699
+ const outer = { expr: POLAR_OUTER };
700
+ return { layers: [{
701
+ mark: {
702
+ type: "arc",
703
+ padAngle: .012,
704
+ ...type === "donut" ? { innerRadius: { expr: `(${POLAR_OUTER}) * ${options.innerRadius ?? .6}` } } : {},
705
+ ...radial ? {
706
+ stroke: tokens.background,
707
+ strokeWidth: 1
708
+ } : {},
709
+ ...type !== "radial" ? { outerRadius: outer } : {}
710
+ },
711
+ encoding: {
712
+ theta: {
713
+ field: "v",
714
+ type: "quantitative",
715
+ stack: true,
716
+ title: options.valueTitle
717
+ },
718
+ ...radial ? { radius: {
719
+ field: "v",
720
+ type: "quantitative",
721
+ scale: {
722
+ type: "sqrt",
723
+ zero: true,
724
+ range: [12, outer]
725
+ },
726
+ title: options.valueTitle
727
+ } } : {},
728
+ fill: IDENTITY_COLOR,
729
+ ...hoverOpacity(options),
730
+ ...tooltip
731
+ }
732
+ }] };
733
+ }
734
+ case "scatter":
735
+ case "bubble":
736
+ case "dot": {
737
+ const quantitativeY = type !== "dot" || options.yType === "quantitative";
738
+ const x = type === "dot" && discreteX ? xPosition(options, true) : options.xType === "quantitative" ? {
739
+ field: "x",
740
+ type: "quantitative",
741
+ title: options.xTitle ?? null,
742
+ axis: measureAxis(options, options.xTitle, "compact"),
743
+ scale: {
744
+ zero: false,
745
+ nice: options.axes
746
+ }
747
+ } : xPosition(options, discreteX);
748
+ const y = quantitativeY ? measurePosition(options, {
749
+ zero: false,
750
+ stack: false
751
+ }) : {
752
+ field: "y",
753
+ type: "nominal",
754
+ sort: null,
755
+ axis: categoryAxis(options, options.yTitle, "left")
756
+ };
757
+ return { layers: [{
758
+ mark: {
759
+ type: "point",
760
+ filled: true,
761
+ size: POINT_SIZE,
762
+ fillOpacity: type === "bubble" ? .7 : .85,
763
+ stroke: tokens.background,
764
+ strokeWidth: 1
765
+ },
766
+ encoding: {
767
+ x,
768
+ y,
769
+ fill: IDENTITY_COLOR,
770
+ ...type === "bubble" ? { size: {
771
+ field: "z",
772
+ type: "quantitative",
773
+ scale: {
774
+ type: "sqrt",
775
+ zero: true,
776
+ range: BUBBLE_SIZE_RANGE
777
+ },
778
+ legend: null,
779
+ title: options.sizeTitle ?? null
780
+ } } : {},
781
+ ...hoverOpacity(options),
782
+ ...tooltip
783
+ }
784
+ }] };
785
+ }
786
+ case "heatmap-binned": return { layers: [{
787
+ mark: {
788
+ type: "rect",
789
+ ...encodedTooltip
790
+ },
791
+ encoding: {
792
+ x: {
793
+ field: "x",
794
+ type: "quantitative",
795
+ bin: { maxbins: options.maxBins },
796
+ title: options.xTitle ?? null,
797
+ axis: measureAxis(options, options.xTitle, "compact")
798
+ },
799
+ y: {
800
+ field: "v",
801
+ type: "quantitative",
802
+ bin: { maxbins: options.maxBins },
803
+ title: options.yTitle ?? options.valueTitle,
804
+ axis: measureAxis(options, options.yTitle)
805
+ },
806
+ color: {
807
+ aggregate: "count",
808
+ type: "quantitative",
809
+ title: "Count",
810
+ scale: { range: tokens.sequential },
811
+ legend: null
812
+ }
813
+ }
814
+ }] };
815
+ case "histogram": return { layers: [{
816
+ mark: {
817
+ type: "bar",
818
+ cornerRadiusEnd: 2,
819
+ stroke: tokens.background,
820
+ strokeWidth: STACK_GAP_PX,
821
+ ...encodedTooltip
822
+ },
823
+ encoding: {
824
+ x: {
825
+ field: "v",
826
+ type: "quantitative",
827
+ bin: { maxbins: options.maxBins },
828
+ title: options.xTitle ?? options.valueTitle,
829
+ axis: measureAxis(options, axisTitle(options, options.xTitle, options.valueTitle))
830
+ },
831
+ y: {
832
+ aggregate: "count",
833
+ type: "quantitative",
834
+ title: options.yTitle ?? "Count",
835
+ axis: measureAxis(options, axisTitle(options, options.yTitle, "Count"), "integer"),
836
+ stack: "zero"
837
+ },
838
+ fill: IDENTITY_COLOR,
839
+ detail: SERIES_DETAIL
840
+ }
841
+ }] };
842
+ case "density": return {
843
+ transform: [{
844
+ density: "v",
845
+ groupby: [
846
+ "s",
847
+ "n",
848
+ "c"
849
+ ],
850
+ steps: DENSITY_STEPS,
851
+ as: ["value", "density"]
852
+ }],
853
+ layers: [{
854
+ mark: {
855
+ type: "area",
856
+ line: true,
857
+ fillOpacity: AREA_FILL_OPACITY * 2,
858
+ interpolate: "monotone",
859
+ ...encodedTooltip
860
+ },
861
+ encoding: {
862
+ x: {
863
+ field: "value",
864
+ type: "quantitative",
865
+ title: options.xTitle ?? options.valueTitle,
866
+ axis: measureAxis(options, axisTitle(options, options.xTitle, options.valueTitle)),
867
+ scale: {
868
+ nice: false,
869
+ zero: false
870
+ }
871
+ },
872
+ y: {
873
+ field: "density",
874
+ type: "quantitative",
875
+ title: options.yTitle ?? "Density",
876
+ axis: options.axes ? {
877
+ title: options.yTitle ?? null,
878
+ domain: false,
879
+ ticks: false,
880
+ grid: options.grid,
881
+ labels: false
882
+ } : null,
883
+ stack: null
884
+ },
885
+ detail: SERIES_DETAIL,
886
+ fill: IDENTITY_COLOR,
887
+ stroke: IDENTITY_COLOR
888
+ }
889
+ }]
890
+ };
891
+ case "violin": {
892
+ const half = VIOLIN_HALF_WIDTH;
893
+ return {
894
+ transform: [
895
+ {
896
+ density: "v",
897
+ groupby: [
898
+ "s",
899
+ "n",
900
+ "c",
901
+ "i"
902
+ ],
903
+ steps: DENSITY_STEPS,
904
+ as: ["value", "density"]
905
+ },
906
+ { joinaggregate: [{
907
+ op: "max",
908
+ field: "density",
909
+ as: "dmax"
910
+ }] },
911
+ {
912
+ calculate: `datum.i - datum.density / datum.dmax * ${half}`,
913
+ as: "xlo"
914
+ },
915
+ {
916
+ calculate: `datum.i + datum.density / datum.dmax * ${half}`,
917
+ as: "xhi"
918
+ }
919
+ ],
920
+ layers: [{
921
+ mark: {
922
+ type: "area",
923
+ orient: "horizontal",
924
+ interpolate: "monotone",
925
+ fillOpacity: .85,
926
+ ...encodedTooltip
927
+ },
928
+ encoding: {
929
+ y: {
930
+ field: "value",
931
+ type: "quantitative",
932
+ title: options.yTitle ?? options.valueTitle,
933
+ axis: measureAxis(options, axisTitle(options, options.yTitle, options.valueTitle)),
934
+ scale: {
935
+ nice: false,
936
+ zero: false
937
+ }
938
+ },
939
+ x: {
940
+ field: "xlo",
941
+ type: "quantitative",
942
+ title: options.xTitle ?? null,
943
+ axis: options.axes ? {
944
+ title: options.xTitle ?? null,
945
+ orient: "bottom",
946
+ domain: false,
947
+ ticks: false,
948
+ grid: false,
949
+ values: { expr: "sequence(0, ceil(domain('x')[1]) + 1)" },
950
+ labelExpr: `${CHART_SERIES_NAMES_PARAM}[datum.value] || ''`,
951
+ labelLimit: 90,
952
+ labelPadding: 6
953
+ } : null,
954
+ scale: {
955
+ nice: false,
956
+ zero: false,
957
+ padding: 8
958
+ }
959
+ },
960
+ x2: { field: "xhi" },
961
+ detail: SERIES_DETAIL,
962
+ fill: IDENTITY_COLOR
963
+ }
964
+ }]
965
+ };
966
+ }
967
+ case "boxplot": return { layers: [{
968
+ mark: {
969
+ type: "boxplot",
970
+ extent: options.extent ?? "min-max",
971
+ size: 28,
972
+ median: {
973
+ color: tokens.background,
974
+ strokeWidth: 2
975
+ },
976
+ outliers: {
977
+ size: POINT_SIZE / 2,
978
+ fillOpacity: .7
979
+ },
980
+ ...options.tooltip ? { tooltip: true } : {}
981
+ },
982
+ encoding: {
983
+ x: xPosition(options, true),
984
+ y: {
985
+ field: "v",
986
+ type: "quantitative",
987
+ title: options.yTitle ?? options.valueTitle,
988
+ axis: measureAxis(options, axisTitle(options, options.yTitle, options.valueTitle)),
989
+ scale: {
990
+ zero: false,
991
+ nice: options.axes
992
+ }
993
+ },
994
+ color: IDENTITY_COLOR
995
+ }
996
+ }] };
997
+ case "strip": return { layers: [{
998
+ mark: {
999
+ type: "tick",
1000
+ thickness: 2,
1001
+ opacity: .55,
1002
+ ...encodedTooltip
1003
+ },
1004
+ encoding: {
1005
+ x: {
1006
+ field: "v",
1007
+ type: "quantitative",
1008
+ title: options.xTitle ?? options.valueTitle,
1009
+ axis: measureAxis(options, axisTitle(options, options.xTitle, options.valueTitle)),
1010
+ scale: {
1011
+ zero: false,
1012
+ nice: options.axes
1013
+ }
1014
+ },
1015
+ ...options.hasX ? { y: {
1016
+ field: "x",
1017
+ type: "nominal",
1018
+ sort: null,
1019
+ axis: categoryAxis(options, options.yTitle, "left")
1020
+ } } : {},
1021
+ color: IDENTITY_COLOR,
1022
+ ...tooltip
1023
+ }
1024
+ }] };
1025
+ case "errorbar":
1026
+ case "errorband": {
1027
+ const band = type === "errorband";
1028
+ const x = xPosition(options, discreteX);
1029
+ const y = {
1030
+ field: "v",
1031
+ type: "quantitative",
1032
+ title: options.yTitle ?? options.valueTitle,
1033
+ axis: measureAxis(options, axisTitle(options, options.yTitle, options.valueTitle)),
1034
+ scale: {
1035
+ zero: false,
1036
+ nice: options.axes
1037
+ }
1038
+ };
1039
+ return { layers: [{
1040
+ mark: {
1041
+ type: band ? "errorband" : "errorbar",
1042
+ extent: options.extent ?? "ci",
1043
+ ...band ? {
1044
+ borders: false,
1045
+ opacity: .18,
1046
+ interpolate: "monotone"
1047
+ } : {
1048
+ ticks: true,
1049
+ thickness: 1.5
1050
+ }
1051
+ },
1052
+ encoding: {
1053
+ x,
1054
+ y,
1055
+ ...band ? { fill: IDENTITY_COLOR } : { color: IDENTITY_COLOR }
1056
+ }
1057
+ }, {
1058
+ mark: band ? {
1059
+ type: "line",
1060
+ strokeWidth: 2,
1061
+ interpolate: "monotone",
1062
+ ...encodedTooltip
1063
+ } : {
1064
+ type: "point",
1065
+ filled: true,
1066
+ size: POINT_SIZE,
1067
+ stroke: tokens.background,
1068
+ strokeWidth: 1.5,
1069
+ ...encodedTooltip
1070
+ },
1071
+ encoding: {
1072
+ x,
1073
+ y: {
1074
+ ...y,
1075
+ aggregate: "mean",
1076
+ field: "v"
1077
+ },
1078
+ detail: SERIES_DETAIL,
1079
+ ...band ? { stroke: IDENTITY_COLOR } : { fill: IDENTITY_COLOR }
1080
+ }
1081
+ }] };
1082
+ }
1083
+ case "heatmap": return { layers: [{
1084
+ mark: {
1085
+ type: "rect",
1086
+ cornerRadius: 3,
1087
+ stroke: tokens.background,
1088
+ strokeWidth: 2
1089
+ },
1090
+ encoding: {
1091
+ x: {
1092
+ field: "x",
1093
+ type: "nominal",
1094
+ sort: null,
1095
+ axis: categoryAxis(options, options.xTitle, "bottom")
1096
+ },
1097
+ y: {
1098
+ field: "y",
1099
+ type: "nominal",
1100
+ sort: null,
1101
+ axis: categoryAxis(options, options.yTitle, "left")
1102
+ },
1103
+ color: magnitudeColor(tokens, options.valueFormat, options.valueTitle),
1104
+ ...hoverOpacity(options),
1105
+ ...tooltip
1106
+ }
1107
+ }] };
1108
+ case "punchcard": return { layers: [{
1109
+ mark: {
1110
+ type: "circle",
1111
+ opacity: .9
1112
+ },
1113
+ encoding: {
1114
+ x: {
1115
+ field: "x",
1116
+ type: "nominal",
1117
+ sort: null,
1118
+ axis: categoryAxis(options, options.xTitle, "bottom")
1119
+ },
1120
+ y: {
1121
+ field: "y",
1122
+ type: "nominal",
1123
+ sort: null,
1124
+ axis: categoryAxis(options, options.yTitle, "left")
1125
+ },
1126
+ size: {
1127
+ field: "v",
1128
+ type: "quantitative",
1129
+ title: options.valueTitle,
1130
+ scale: {
1131
+ type: "sqrt",
1132
+ zero: true,
1133
+ range: PUNCHCARD_SIZE_RANGE
1134
+ },
1135
+ legend: null
1136
+ },
1137
+ color: magnitudeColor(tokens, options.valueFormat, options.valueTitle),
1138
+ ...hoverOpacity(options),
1139
+ ...tooltip
1140
+ }
1141
+ }] };
1142
+ case "radar": {
1143
+ const x = radarPlane("px", "width");
1144
+ const y = radarPlane("py", "height");
1145
+ return { layers: [
1146
+ {
1147
+ data: { name: CHART_AXES_DATASET },
1148
+ mark: {
1149
+ type: "line",
1150
+ stroke: tokens.grid,
1151
+ strokeWidth: 1,
1152
+ aria: false
1153
+ },
1154
+ encoding: {
1155
+ x: radarPlane("px", "width"),
1156
+ y: radarPlane("py", "height"),
1157
+ detail: {
1158
+ field: "x",
1159
+ type: "nominal"
1160
+ },
1161
+ order: {
1162
+ field: "k",
1163
+ type: "quantitative"
1164
+ }
1165
+ }
1166
+ },
1167
+ {
1168
+ data: { name: CHART_AXES_DATASET },
1169
+ transform: [{ filter: "datum.k > 0" }],
1170
+ mark: {
1171
+ type: "line",
1172
+ interpolate: "linear-closed",
1173
+ stroke: tokens.grid,
1174
+ strokeWidth: 1,
1175
+ aria: false
1176
+ },
1177
+ encoding: {
1178
+ x: radarPlane("px", "width"),
1179
+ y: radarPlane("py", "height"),
1180
+ detail: {
1181
+ field: "k",
1182
+ type: "nominal"
1183
+ },
1184
+ order: RADAR_ORDER
1185
+ }
1186
+ },
1187
+ {
1188
+ data: { name: CHART_AXES_DATASET },
1189
+ transform: [{ filter: "datum.k == 1" }],
1190
+ mark: {
1191
+ type: "text",
1192
+ fontSize: 10,
1193
+ lineHeight: 12,
1194
+ lineBreak: " ",
1195
+ fill: tokens.axisLabel,
1196
+ limit: { expr: "max(52, width * 0.26)" },
1197
+ align: { expr: "datum.lx > 0.15 ? 'left' : datum.lx < -0.15 ? 'right' : 'center'" },
1198
+ baseline: { expr: "datum.ly > 0.15 ? 'bottom' : datum.ly < -0.15 ? 'top' : 'middle'" },
1199
+ aria: false
1200
+ },
1201
+ encoding: {
1202
+ x: radarPlane("lx", "width"),
1203
+ y: radarPlane("ly", "height"),
1204
+ text: {
1205
+ field: "x",
1206
+ type: "nominal"
1207
+ }
1208
+ }
1209
+ },
1210
+ {
1211
+ mark: {
1212
+ type: "line",
1213
+ interpolate: "linear-closed",
1214
+ strokeWidth: 0,
1215
+ fillOpacity: .22,
1216
+ aria: false
1217
+ },
1218
+ encoding: {
1219
+ x,
1220
+ y,
1221
+ detail: SERIES_DETAIL,
1222
+ fill: IDENTITY_COLOR,
1223
+ order: RADAR_ORDER
1224
+ }
1225
+ },
1226
+ {
1227
+ mark: {
1228
+ type: "line",
1229
+ interpolate: "linear-closed",
1230
+ strokeWidth: 2,
1231
+ strokeJoin: "round"
1232
+ },
1233
+ encoding: {
1234
+ x,
1235
+ y,
1236
+ detail: SERIES_DETAIL,
1237
+ stroke: IDENTITY_COLOR,
1238
+ order: RADAR_ORDER
1239
+ }
1240
+ },
1241
+ {
1242
+ mark: {
1243
+ type: "point",
1244
+ filled: true,
1245
+ size: POINT_SIZE * .6,
1246
+ stroke: tokens.background,
1247
+ strokeWidth: 1
1248
+ },
1249
+ encoding: {
1250
+ x,
1251
+ y,
1252
+ fill: IDENTITY_COLOR,
1253
+ ...hoverOpacity(options),
1254
+ ...tooltip
1255
+ }
1256
+ }
1257
+ ] };
1258
+ }
1259
+ default: throw new Error(`ChartCard: no encoding for type ${String(type)}`);
1260
+ }
1261
+ }
1262
+ /**
1263
+ * Builds the plot for one form. Every spec is a single or layered view — never
1264
+ * a facet, concat or repeat — so `autosize: "fit"` applies and the width and
1265
+ * height can come from the View API rather than from a rebuild.
1266
+ *
1267
+ * The Vega legend is always `null`: the legend is real Arc controls, because
1268
+ * Vega offers no keyboard navigation of its own marks.
1269
+ */
1270
+ function buildChartSpec(options, tokens) {
1271
+ const info = CHART_TYPES[options.type];
1272
+ const { layers, transform } = buildLayers(options, tokens);
1273
+ if (options.tooltip && !SELECTION_FREE.includes(options.type)) {
1274
+ const target = [...layers].reverse().find((layer) => {
1275
+ const encoding = layer.encoding;
1276
+ const mark = layer.mark;
1277
+ return encoding?.tooltip != null || mark?.tooltip != null;
1278
+ }) ?? layers[layers.length - 1];
1279
+ target.params = [...target.params ?? [], HOVER_PARAM_DEF];
1280
+ }
1281
+ const params = [];
1282
+ if (options.axes && !info.polar && !info.horizontal && ![
1283
+ "heatmap",
1284
+ "punchcard",
1285
+ "violin",
1286
+ "histogram",
1287
+ "density",
1288
+ "slope",
1289
+ "heatmap-binned",
1290
+ "strip",
1291
+ "connected-scatter"
1292
+ ].includes(options.type)) {
1293
+ const discrete = options.xType === "nominal" || options.xType === "ordinal" || [
1294
+ "bar",
1295
+ "bar-grouped",
1296
+ "bar-stacked",
1297
+ "bar-normalized",
1298
+ "waterfall",
1299
+ "boxplot"
1300
+ ].includes(options.type);
1301
+ const tickFormat = options.xType === "temporal" ? options.axisTimeFormat : "compact";
1302
+ const tickKind = options.xType === "temporal" ? "time" : options.xType === "quantitative" ? "number" : "text";
1303
+ params.push({
1304
+ name: CHART_X_TICKS_PARAM,
1305
+ expr: `arcAxisTicks(domain('x'), width, ${discrete}, ${JSON.stringify(tickFormat)}, ${JSON.stringify(tickKind)})`
1306
+ });
1307
+ }
1308
+ if (options.type === "violin") params.push({
1309
+ name: CHART_SERIES_NAMES_PARAM,
1310
+ value: []
1311
+ });
1312
+ return {
1313
+ $schema: "https://vega.github.io/schema/vega-lite/v6.json",
1314
+ width: 320,
1315
+ height: options.height,
1316
+ autosize: {
1317
+ type: "fit",
1318
+ contains: "padding"
1319
+ },
1320
+ padding: PLOT_PADDING,
1321
+ background: "transparent",
1322
+ data: { name: CHART_TABLE_DATASET },
1323
+ ...transform ? { transform } : {},
1324
+ ...params.length > 0 ? { params } : {},
1325
+ layer: layers,
1326
+ config: arcVegaConfig(tokens, {
1327
+ locale: options.locale,
1328
+ timeZone: options.timeZone
1329
+ })
1330
+ };
1331
+ }
1332
+ //#endregion
1333
+ export { CHART_AXES_DATASET, CHART_HOVER_PARAM, CHART_SERIES_NAMES_PARAM, CHART_TABLE_DATASET, CHART_X_TICKS_PARAM, buildChartSpec };