@silverpoint/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,4770 @@
1
+ import { scaleLinear, scaleBand } from 'd3-scale';
2
+ import { curveStepAfter, curveNatural, curveLinear, curveMonotoneX, line, curveStep, curveStepBefore, area } from 'd3-shape';
3
+ import { chordDirected } from 'd3-chord';
4
+
5
+ // src/render/round.ts
6
+ function round2(value) {
7
+ const rounded = Math.round(value * 100) / 100;
8
+ return Object.is(rounded, -0) ? 0 : rounded;
9
+ }
10
+ var NUMBER = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?/g;
11
+ function roundPathData(d) {
12
+ return d.replace(NUMBER, (token) => String(round2(Number(token))));
13
+ }
14
+ function roundRect(rect) {
15
+ return {
16
+ x: round2(rect.x),
17
+ y: round2(rect.y),
18
+ width: round2(rect.width),
19
+ height: round2(rect.height)
20
+ };
21
+ }
22
+ function roundStroke(stroke) {
23
+ return { ...stroke, d: roundPathData(stroke.d) };
24
+ }
25
+ function roundTile(tile) {
26
+ return {
27
+ ...tile,
28
+ width: round2(tile.width),
29
+ height: round2(tile.height),
30
+ angle: round2(tile.angle),
31
+ strokes: tile.strokes.map(roundStroke)
32
+ };
33
+ }
34
+ function roundGeometry(geometry) {
35
+ return {
36
+ viewBox: roundRect(geometry.viewBox),
37
+ plot: roundRect(geometry.plot),
38
+ strokes: geometry.strokes.map(roundStroke),
39
+ labels: geometry.labels.map((label) => ({ ...label, x: round2(label.x), y: round2(label.y) })),
40
+ hitAreas: geometry.hitAreas.map((hit) => ({
41
+ ...hit,
42
+ x: round2(hit.x),
43
+ y: round2(hit.y),
44
+ ...hit.box ? { box: roundRect(hit.box) } : {}
45
+ })),
46
+ defs: geometry.defs.map(roundTile)
47
+ };
48
+ }
49
+
50
+ // src/render/serialize.ts
51
+ function serializeGeometry(geometry) {
52
+ return JSON.stringify(geometry);
53
+ }
54
+ function parseGeometry(json) {
55
+ return JSON.parse(json);
56
+ }
57
+ function pathBytes(geometry) {
58
+ let bytes = 0;
59
+ for (const stroke of geometry.strokes) bytes += stroke.d.length;
60
+ for (const tile of geometry.defs) {
61
+ for (const stroke of tile.strokes) bytes += stroke.d.length;
62
+ }
63
+ return bytes;
64
+ }
65
+ var PATH_BYTE_BUDGET = 4e4;
66
+
67
+ // src/render/seed.ts
68
+ var FNV_OFFSET_BASIS = 2166136261;
69
+ var FNV_PRIME = 16777619;
70
+ function utf8Bytes(input) {
71
+ const bytes = [];
72
+ for (const char of input) {
73
+ const code = char.codePointAt(0) ?? 0;
74
+ if (code < 128) {
75
+ bytes.push(code);
76
+ } else if (code < 2048) {
77
+ bytes.push(192 | code >> 6, 128 | code & 63);
78
+ } else if (code < 65536) {
79
+ bytes.push(224 | code >> 12, 128 | code >> 6 & 63, 128 | code & 63);
80
+ } else {
81
+ bytes.push(
82
+ 240 | code >> 18,
83
+ 128 | code >> 12 & 63,
84
+ 128 | code >> 6 & 63,
85
+ 128 | code & 63
86
+ );
87
+ }
88
+ }
89
+ return bytes;
90
+ }
91
+ function fnv1a32(input) {
92
+ let hash = FNV_OFFSET_BASIS;
93
+ for (const byte of utf8Bytes(input)) {
94
+ hash ^= byte;
95
+ hash = Math.imul(hash, FNV_PRIME);
96
+ }
97
+ return hash >>> 0;
98
+ }
99
+ function deriveSeed(id) {
100
+ return fnv1a32(id);
101
+ }
102
+ function resolveSeed(seed, id) {
103
+ if (seed === void 0) return deriveSeed(id);
104
+ if (typeof seed === "string") return fnv1a32(seed);
105
+ return Number.isFinite(seed) ? Math.trunc(seed) >>> 0 : 0;
106
+ }
107
+
108
+ // src/render/prng.ts
109
+ function createPrng(seed) {
110
+ let state = seed >>> 0;
111
+ return () => {
112
+ state = state + 1831565813 >>> 0;
113
+ let t = state;
114
+ t = Math.imul(t ^ t >>> 15, t | 1);
115
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
116
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
117
+ };
118
+ }
119
+
120
+ // src/charts/shared/dates.ts
121
+ var DAY_MS = 864e5;
122
+ var ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})$/;
123
+ function parseIsoDate(iso) {
124
+ const match = ISO_DATE.exec(iso);
125
+ if (!match) return Number.NaN;
126
+ return Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3])) / DAY_MS;
127
+ }
128
+ function isoDate(day) {
129
+ return new Date(day * DAY_MS).toISOString().slice(0, 10);
130
+ }
131
+ function addDays(day, days) {
132
+ return day + days;
133
+ }
134
+ function weekday(day) {
135
+ return ((day + 4) % 7 + 7) % 7;
136
+ }
137
+
138
+ // src/diagnostics/diagnose.ts
139
+ var SilverpointError = class extends Error {
140
+ code;
141
+ constructor(code, message) {
142
+ super(message);
143
+ this.name = "SilverpointError";
144
+ this.code = code;
145
+ }
146
+ };
147
+ var consoleSink = (_code, message) => {
148
+ console.warn(message);
149
+ };
150
+ var sink = consoleSink;
151
+ var reported = /* @__PURE__ */ new Set();
152
+ function __setDiagnosticSink(next) {
153
+ sink = next ?? consoleSink;
154
+ reported.clear();
155
+ return () => {
156
+ sink = consoleSink;
157
+ reported.clear();
158
+ };
159
+ }
160
+ function format(code, chart, detail, template) {
161
+ const specifics = detail.message ? ` ${detail.message}` : "";
162
+ return `[${code}] ${chart}: ${template.what} (\`${detail.property}\`).${specifics} ${template.todo}. (${template.req})`;
163
+ }
164
+ var ERRORS = {
165
+ SP009: {
166
+ req: "REQ-097",
167
+ what: "a scale bound cannot be derived from the data and was not supplied",
168
+ todo: "Pass the named property explicitly"
169
+ },
170
+ SP012: {
171
+ req: "REQ-127",
172
+ what: "the ground does not reach the minimum contrast",
173
+ todo: "Recompute the palette; colours are not chosen by eye"
174
+ }
175
+ };
176
+ function diagnose(code, chart, detail) {
177
+ const error = ERRORS[code];
178
+ if (error) {
179
+ throw new SilverpointError(code, format(code, chart, detail, error));
180
+ }
181
+ if (process.env.NODE_ENV !== "production") {
182
+ developmentDiagnose(code, chart, detail);
183
+ }
184
+ }
185
+ function developmentDiagnose(code, chart, detail) {
186
+ const template = WARNINGS[code];
187
+ if (!template) return;
188
+ const message = format(code, chart, detail, template);
189
+ if (code === "SP005") {
190
+ throw new SilverpointError(code, message);
191
+ }
192
+ if (reported.has(message)) return;
193
+ reported.add(message);
194
+ sink(code, message);
195
+ }
196
+ var WARNINGS = {
197
+ SP001: {
198
+ req: "REQ-007",
199
+ what: "the dataset is empty; the ground's empty state is drawn",
200
+ todo: "Pass at least one row, or omit `data` to see the demo dataset"
201
+ },
202
+ SP002: {
203
+ req: "REQ-008",
204
+ what: "a value cannot be drawn as given (null, not finite, or outside the data contract); it is corrected or omitted",
205
+ todo: "Check the value against the chart's data contract (Data Model \xA72)"
206
+ },
207
+ SP003: {
208
+ req: "REQ-009",
209
+ what: "the container measures 0 px; the render is deferred",
210
+ todo: "Give the container a size, or pin `width` and `height`"
211
+ },
212
+ SP004: {
213
+ req: "REQ-010",
214
+ what: "the scale domain has zero length; it is expanded with the ground's domainPadding",
215
+ todo: "Nothing to do if the data is constant; otherwise check the accessor"
216
+ },
217
+ SP005: {
218
+ req: "REQ-025",
219
+ what: "more than one element is marked with white heightening; only the first is applied",
220
+ todo: "Heighten a single element per chart"
221
+ },
222
+ SP006: {
223
+ req: "REQ-026",
224
+ what: "the inker is not registered; falling back to NullInker",
225
+ todo: "Register the inker or fix the ground's `inker` token"
226
+ },
227
+ SP007: {
228
+ req: "REQ-045",
229
+ what: "the ground is not registered; falling back to silverpoint",
230
+ todo: "Call registerGround() before rendering, or fix the name"
231
+ },
232
+ SP008: {
233
+ req: "REQ-096",
234
+ what: "the data volume is above the family's threshold",
235
+ todo: "Aggregate the data before charting it"
236
+ },
237
+ SP010: {
238
+ req: "REQ-091",
239
+ what: "the chord ring has more than 12 categories",
240
+ todo: "Group the smaller categories"
241
+ },
242
+ SP011: {
243
+ req: "NFR \xA77",
244
+ what: "the path byte budget of 40 KB is exceeded",
245
+ todo: "Use `hatchFill: 'tile'` or a larger hatch gap"
246
+ },
247
+ SP013: {
248
+ req: "REQ-032",
249
+ what: "the @silverpoint/fonts face failed to load; rendering fell back to the system stack",
250
+ todo: "Install @silverpoint/fonts and import its stylesheet; golden images will not match until then"
251
+ }
252
+ };
253
+
254
+ // src/render/heighten.ts
255
+ function isOutlineOf(candidate, heightened) {
256
+ return candidate.part === "ink" && (candidate.paint ?? "stroke") === "stroke" && candidate.d === heightened.d;
257
+ }
258
+ function enforceHeightening(geometry, chart) {
259
+ const heightened = geometry.strokes.filter((stroke) => stroke.part === "heighten");
260
+ if (heightened.length === 0) return geometry;
261
+ if (heightened.length > 1 && process.env.NODE_ENV !== "production") {
262
+ diagnose("SP005", chart, {
263
+ property: "heighten",
264
+ message: `${heightened.length} heightened elements were declared.`
265
+ });
266
+ }
267
+ const [kept] = heightened;
268
+ const strokes = [];
269
+ for (const stroke of geometry.strokes) {
270
+ if (stroke.part === "heighten" && stroke !== kept) continue;
271
+ strokes.push(stroke);
272
+ if (stroke === kept && !geometry.strokes.some((other) => isOutlineOf(other, kept))) {
273
+ strokes.push({ d: kept.d, role: kept.role, part: "ink", paint: "stroke" });
274
+ }
275
+ }
276
+ return { ...geometry, strokes };
277
+ }
278
+
279
+ // src/render/view.ts
280
+ function pathView(stroke) {
281
+ const paint = stroke.paint ?? "stroke";
282
+ return {
283
+ d: stroke.d,
284
+ part: `sp-${stroke.part}`,
285
+ role: stroke.role,
286
+ paint,
287
+ dash: stroke.dash ?? null,
288
+ fill: paint === "tile" && stroke.tile ? `url(#${stroke.tile})` : null
289
+ };
290
+ }
291
+ function toSvgView(model, style) {
292
+ const { geometry, ids } = model;
293
+ const { viewBox } = geometry;
294
+ return {
295
+ svg: {
296
+ viewBox: `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`,
297
+ width: String(viewBox.width),
298
+ height: String(viewBox.height),
299
+ class: `sp-chart sp-ground-${style.ground}`,
300
+ substrate: style.substrate,
301
+ mode: style.mode,
302
+ role: "img",
303
+ labelledby: `${ids.title} ${ids.desc}`
304
+ },
305
+ title: { id: ids.title, text: model.name },
306
+ desc: { id: ids.desc, text: model.description },
307
+ patterns: geometry.defs.map((tile) => ({
308
+ id: tile.id,
309
+ width: String(tile.width),
310
+ height: String(tile.height),
311
+ transform: `rotate(${tile.angle})`,
312
+ paths: tile.strokes.map(pathView)
313
+ })),
314
+ paths: geometry.strokes.map(pathView),
315
+ texts: geometry.labels.map((label) => ({
316
+ x: String(label.x),
317
+ y: String(label.y),
318
+ part: `sp-${label.part}`,
319
+ kind: label.kind,
320
+ anchor: label.anchor,
321
+ text: label.text
322
+ }))
323
+ };
324
+ }
325
+ var ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" };
326
+ function escape(value) {
327
+ return value.replace(/[&<>"]/g, (char) => ESCAPES[char] ?? char);
328
+ }
329
+ function attributes(pairs) {
330
+ return pairs.filter((pair) => pair[1] !== null).map(([name, value]) => ` ${name}="${escape(value)}"`).join("");
331
+ }
332
+ function pathString(path) {
333
+ return `<path${attributes([
334
+ ["d", path.d],
335
+ ["part", path.part],
336
+ ["data-role", path.role],
337
+ ["data-paint", path.paint],
338
+ ["data-dash", path.dash],
339
+ ["fill", path.fill]
340
+ ])}></path>`;
341
+ }
342
+ function svgString(view) {
343
+ const { svg } = view;
344
+ const open = `<svg${attributes([
345
+ ["xmlns", "http://www.w3.org/2000/svg"],
346
+ ["viewBox", svg.viewBox],
347
+ ["width", svg.width],
348
+ ["height", svg.height],
349
+ ["class", svg.class],
350
+ ["data-substrate", svg.substrate],
351
+ ["data-mode", svg.mode],
352
+ ["role", svg.role],
353
+ ["aria-labelledby", svg.labelledby]
354
+ ])}>`;
355
+ const title = `<title id="${escape(view.title.id)}">${escape(view.title.text)}</title>`;
356
+ const desc = `<desc id="${escape(view.desc.id)}">${escape(view.desc.text)}</desc>`;
357
+ const defs = view.patterns.length === 0 ? "" : `<defs>${view.patterns.map(
358
+ (pattern) => `<pattern${attributes([
359
+ ["id", pattern.id],
360
+ ["width", pattern.width],
361
+ ["height", pattern.height],
362
+ ["patternUnits", "userSpaceOnUse"],
363
+ ["patternTransform", pattern.transform]
364
+ ])}>${pattern.paths.map(pathString).join("")}</pattern>`
365
+ ).join("")}</defs>`;
366
+ const paths = view.paths.map(pathString).join("");
367
+ const texts = view.texts.map(
368
+ (text) => `<text${attributes([
369
+ ["x", text.x],
370
+ ["y", text.y],
371
+ ["part", text.part],
372
+ ["data-kind", text.kind],
373
+ ["text-anchor", text.anchor]
374
+ ])}>${escape(text.text)}</text>`
375
+ ).join("");
376
+ return `${open}${title}${desc}${defs}${paths}${texts}</svg>`;
377
+ }
378
+
379
+ // src/diagnostics/typeface.ts
380
+ var DISPLAY_FACE_PROBE = '16px "EB Garamond"';
381
+ async function checkTypeface(fonts, chart, face = DISPLAY_FACE_PROBE) {
382
+ let loaded;
383
+ try {
384
+ const faces = await fonts.load(face);
385
+ loaded = faces.length > 0 && fonts.check(face);
386
+ } catch {
387
+ loaded = false;
388
+ }
389
+ if (!loaded && process.env.NODE_ENV !== "production") {
390
+ diagnose("SP013", chart, { property: "font-family", message: `Probe: ${face}.` });
391
+ }
392
+ return loaded;
393
+ }
394
+
395
+ // src/scales/util.ts
396
+ function min(values) {
397
+ let result;
398
+ for (const value of values) {
399
+ if (Number.isFinite(value) && (result === void 0 || value < result)) result = value;
400
+ }
401
+ return result;
402
+ }
403
+ function max(values) {
404
+ let result;
405
+ for (const value of values) {
406
+ if (Number.isFinite(value) && (result === void 0 || value > result)) result = value;
407
+ }
408
+ return result;
409
+ }
410
+ function extent(values) {
411
+ const low = min(values);
412
+ const high = max(values);
413
+ return low === void 0 || high === void 0 ? void 0 : [low, high];
414
+ }
415
+ function expandDomain(domain, padding, site, fromZero = false) {
416
+ const [low, high] = domain;
417
+ if (low !== high) return [low, high];
418
+ const spread = low === 0 ? padding : Math.abs(low) * padding;
419
+ if (fromZero && low === 0) {
420
+ if (process.env.NODE_ENV !== "production") {
421
+ diagnose("SP004", site.chart, { property: site.property, message: `Domain [0, 0] became [0, ${2 * spread}].` });
422
+ }
423
+ return [0, 2 * spread];
424
+ }
425
+ if (process.env.NODE_ENV !== "production") {
426
+ diagnose("SP004", site.chart, {
427
+ property: site.property,
428
+ message: `Domain [${low}, ${high}] became [${low - spread}, ${high + spread}].`
429
+ });
430
+ }
431
+ return [low - spread, high + spread];
432
+ }
433
+ function linearScale(domain, range, options) {
434
+ const expanded = expandDomain(domain, options.padding, options, options.fromZero);
435
+ const scale = scaleLinear().domain(expanded).range(range);
436
+ if (options.nice) scale.nice(4);
437
+ const [low, high] = scale.domain();
438
+ const result = ((value) => scale(value));
439
+ Object.defineProperty(result, "domain", { value: [low, high] });
440
+ result.ticks = (count) => scale.ticks(count);
441
+ return result;
442
+ }
443
+ function bandScale(domain, range, options = {}) {
444
+ const scale = scaleBand().domain(domain).range(range).paddingInner(options.paddingInner ?? 0).paddingOuter(options.paddingOuter ?? 0);
445
+ const bandwidth = scale.bandwidth();
446
+ const result = ((value) => scale(value));
447
+ Object.defineProperty(result, "bandwidth", { value: bandwidth });
448
+ result.center = (value) => {
449
+ const start = scale(value);
450
+ return start === void 0 ? void 0 : start + bandwidth / 2;
451
+ };
452
+ return result;
453
+ }
454
+
455
+ // src/ink/null-inker.ts
456
+ var NullInker = Object.freeze({
457
+ name: "null",
458
+ ink(geometry, _options) {
459
+ return geometry;
460
+ }
461
+ });
462
+
463
+ // src/ink/registry.ts
464
+ var registry = /* @__PURE__ */ new Map([[NullInker.name, NullInker]]);
465
+ function registerInker(inker) {
466
+ registry.set(inker.name, inker);
467
+ }
468
+ function resolveInker(name, chart, builtins = []) {
469
+ const found = registry.get(name) ?? builtins.find((inker) => inker.name === name);
470
+ if (found) return found;
471
+ if (process.env.NODE_ENV !== "production") {
472
+ diagnose("SP006", chart, { property: "inker", message: `Inker "${name}" is not registered.` });
473
+ }
474
+ return NullInker;
475
+ }
476
+
477
+ // src/config/resolve.ts
478
+ var DEFAULTS = Object.freeze({
479
+ ground: "silverpoint",
480
+ substrate: "cream",
481
+ mode: "ink",
482
+ chrome: "card",
483
+ hatchFill: "tile",
484
+ height: 160,
485
+ dataTable: "hidden",
486
+ locale: "en"
487
+ });
488
+ function resolveConfig(props, provider = {}, environment = {}) {
489
+ const mode = environment.forcedPrecision ? "precision" : props.mode ?? provider.mode ?? DEFAULTS.mode;
490
+ return {
491
+ ground: props.ground ?? provider.ground ?? DEFAULTS.ground,
492
+ substrate: props.substrate ?? provider.substrate ?? DEFAULTS.substrate,
493
+ mode,
494
+ chrome: props.chrome ?? DEFAULTS.chrome,
495
+ hatchFill: props.hatchFill ?? DEFAULTS.hatchFill,
496
+ height: props.height ?? DEFAULTS.height,
497
+ dataTable: props.dataTable ?? DEFAULTS.dataTable,
498
+ locale: props.locale ?? provider.locale ?? environment.locale ?? DEFAULTS.locale
499
+ };
500
+ }
501
+ var FORCED_PRECISION_QUERY = "(prefers-contrast: more), (forced-colors: active)";
502
+ function instanceId(chart, token) {
503
+ const slug = chart.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
504
+ const safe = String(token).replace(/[^A-Za-z0-9_-]/g, "");
505
+ return `sp-${slug}-${safe}`;
506
+ }
507
+
508
+ // src/charts/shared/format.ts
509
+ function read(accessor, datum, index) {
510
+ return typeof accessor === "function" ? accessor(datum, index) : datum[accessor];
511
+ }
512
+ function accessorName(accessor, fallback) {
513
+ return typeof accessor === "string" ? accessor : fallback;
514
+ }
515
+ var FORMATTERS = /* @__PURE__ */ new Map();
516
+ var MAX_FORMATTERS = 64;
517
+ function formatNumber(value, locale, options) {
518
+ const resolved = options ?? { maximumFractionDigits: 2 };
519
+ const read2 = [];
520
+ for (const name in resolved) read2.push(`${name}=${JSON.stringify(resolved[name])}`);
521
+ const key = `${locale}|${read2.sort().join("&")}`;
522
+ let formatter = FORMATTERS.get(key);
523
+ if (!formatter) {
524
+ if (FORMATTERS.size >= MAX_FORMATTERS) FORMATTERS.clear();
525
+ formatter = new Intl.NumberFormat(locale, resolved);
526
+ FORMATTERS.set(key, formatter);
527
+ }
528
+ return formatter.format(value);
529
+ }
530
+ function formatValue(value, locale, options) {
531
+ if (typeof value === "number") {
532
+ return Number.isFinite(value) ? formatNumber(value, locale, options) : "\u2014";
533
+ }
534
+ return value === null || value === void 0 ? "\u2014" : String(value);
535
+ }
536
+ function formatCategory(value, locale) {
537
+ return formatValue(value, locale, typeof value === "number" ? { useGrouping: false, maximumFractionDigits: 2 } : void 0);
538
+ }
539
+ function circlePath(cx, cy, r) {
540
+ return `M${cx - r},${cy}A${r},${r},0,1,0,${cx + r},${cy}A${r},${r},0,1,0,${cx - r},${cy}Z`;
541
+ }
542
+
543
+ // src/charts/shared/card.ts
544
+ var CARD_PADDING = 16;
545
+ var TITLE_ROW = 20;
546
+ var VALUE_ROW = 52;
547
+ var HEADER_GAP = 8;
548
+ var FOOTER_ROW = 30;
549
+ function cardLayout(props, options) {
550
+ const { width, areaHeight: areaHeight2, chrome, locale } = options;
551
+ if (chrome === "bare") {
552
+ const box = { x: 0, y: 0, width, height: areaHeight2 };
553
+ return { viewBox: box, area: box, strokes: [], labels: [] };
554
+ }
555
+ const labels = [];
556
+ const strokes = [];
557
+ const left = CARD_PADDING;
558
+ const right = width - CARD_PADDING;
559
+ let cursor = CARD_PADDING;
560
+ if (props.title !== void 0 || props.badge !== void 0) {
561
+ const baseline = cursor + 11;
562
+ if (props.title !== void 0) {
563
+ labels.push({ x: left, y: baseline, text: props.title, kind: "title", part: "text", anchor: "start" });
564
+ }
565
+ if (props.badge !== void 0) {
566
+ labels.push({ x: right, y: baseline, text: props.badge, kind: "badge", part: "axis", anchor: "end" });
567
+ }
568
+ cursor += TITLE_ROW;
569
+ }
570
+ if (props.value !== void 0 || props.unit !== void 0) {
571
+ if (props.value !== void 0) {
572
+ labels.push({
573
+ x: left,
574
+ y: cursor + 30,
575
+ text: formatValue(props.value, locale, props.numberFormat),
576
+ kind: "value",
577
+ part: "text",
578
+ anchor: "start"
579
+ });
580
+ }
581
+ if (props.unit !== void 0) {
582
+ labels.push({ x: left, y: cursor + 46, text: props.unit, kind: "unit", part: "axis", anchor: "start" });
583
+ }
584
+ cursor += VALUE_ROW;
585
+ }
586
+ if (cursor > CARD_PADDING) cursor += HEADER_GAP;
587
+ const area5 = { x: left, y: cursor, width: right - left, height: areaHeight2 };
588
+ cursor += areaHeight2;
589
+ if (props.footerLeft !== void 0 || props.footerRight !== void 0) {
590
+ const ruleY = cursor + 8;
591
+ strokes.push({ d: `M${left},${ruleY}H${right}`, role: "ornament", part: "rule" });
592
+ if (props.footerLeft !== void 0) {
593
+ labels.push({ x: left, y: ruleY + 18, text: props.footerLeft, kind: "footer", part: "axis", anchor: "start" });
594
+ }
595
+ if (props.footerRight !== void 0) {
596
+ labels.push({ x: right, y: ruleY + 18, text: props.footerRight, kind: "footer", part: "axis", anchor: "end" });
597
+ }
598
+ cursor += FOOTER_ROW;
599
+ }
600
+ const height = cursor + CARD_PADDING;
601
+ const inset2 = 0.5;
602
+ strokes.unshift({
603
+ d: `M${inset2},${inset2}H${width - inset2}V${height - inset2}H${inset2}Z`,
604
+ role: "ornament",
605
+ part: "rule"
606
+ });
607
+ return { viewBox: { x: 0, y: 0, width, height }, area: area5, strokes, labels };
608
+ }
609
+
610
+ // src/charts/shared/cells.ts
611
+ function rectPath(rect) {
612
+ const { x, y, width, height } = rect;
613
+ return `M${x},${y}H${x + width}V${y + height}H${x}Z`;
614
+ }
615
+ function inset(rect, by) {
616
+ return {
617
+ x: rect.x + by,
618
+ y: rect.y + by,
619
+ width: Math.max(rect.width - 2 * by, 0),
620
+ height: Math.max(rect.height - 2 * by, 0)
621
+ };
622
+ }
623
+ function finite(raw) {
624
+ return typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
625
+ }
626
+ function toneOf(ratio) {
627
+ if (!(ratio > 0)) return 0;
628
+ if (ratio <= 0.25) return 1;
629
+ if (ratio <= 0.5) return 2;
630
+ if (ratio <= 0.75) return 3;
631
+ return 4;
632
+ }
633
+ function toneLevel(raw) {
634
+ return Math.min(Math.max(Math.round(raw), 0), 4);
635
+ }
636
+ function warnValue(chart, property, message) {
637
+ if (process.env.NODE_ENV !== "production") diagnose("SP002", chart, { property, message });
638
+ }
639
+
640
+ // src/charts/shared/cartesian.ts
641
+ var AXIS_BAND = 20;
642
+ var PLOT_INSET = 6;
643
+ var LABEL_SPACING = 48;
644
+ function cartesianPlot(area5) {
645
+ return {
646
+ x: area5.x + PLOT_INSET,
647
+ y: area5.y + PLOT_INSET,
648
+ width: Math.max(area5.width - 2 * PLOT_INSET, 1),
649
+ height: Math.max(area5.height - AXIS_BAND - PLOT_INSET, 1)
650
+ };
651
+ }
652
+ function readSeries(data, accessor, fallbackName) {
653
+ let missing = 0;
654
+ const points = data.map((datum, index) => {
655
+ const raw = read(accessor, datum, index);
656
+ const value = typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
657
+ if (value === void 0) missing += 1;
658
+ return { index, datum, value };
659
+ });
660
+ return { key: accessorName(accessor, fallbackName), points, missing };
661
+ }
662
+ var POINTS_PER_SERIES = 500;
663
+ function checkVolume(chart, series) {
664
+ for (const s of series) checkCount(chart, s.key, s.points.length);
665
+ }
666
+ function checkCount(chart, property, count) {
667
+ if (process.env.NODE_ENV === "production" || count <= POINTS_PER_SERIES) return;
668
+ diagnose("SP008", chart, { property, message: `${count} points; the ceiling is ${POINTS_PER_SERIES} per series.` });
669
+ }
670
+ function seriesValues(series) {
671
+ return series.flatMap((s) => s.points.flatMap((p) => p.value === void 0 ? [] : [p.value]));
672
+ }
673
+ function valueAxis(plot, domain, options) {
674
+ const bottom = plot.y + plot.height;
675
+ const low = options.zero === false ? domain[0] : Math.min(0, domain[0]);
676
+ const high = options.zero === false ? domain[1] : Math.max(0, domain[1]);
677
+ const scale = linearScale([low, high], [bottom, plot.y], {
678
+ chart: options.chart,
679
+ property: options.property,
680
+ padding: options.padding,
681
+ nice: options.nice !== false,
682
+ fromZero: options.zero !== false
683
+ });
684
+ const strokes = [];
685
+ const labels = [];
686
+ for (const tick of scale.ticks(4)) {
687
+ const ty = scale(tick);
688
+ if (tick === scale.domain[0]) continue;
689
+ strokes.push({ d: `M${plot.x},${ty}H${plot.x + plot.width}`, role: "ornament", part: "grid" });
690
+ labels.push({ x: plot.x, y: ty - 3, text: formatNumber(tick, options.locale, options.numberFormat), kind: "tick", part: "axis", anchor: "start" });
691
+ }
692
+ strokes.push({ d: `M${plot.x},${bottom}H${plot.x + plot.width}`, role: "ornament", part: "rule" });
693
+ return { scale, strokes, labels };
694
+ }
695
+ function categoryAxis(xValues, plot, options) {
696
+ const numeric = options.numeric !== false && xValues.length > 0 && xValues.every((v) => typeof v === "number" && Number.isFinite(v));
697
+ if (numeric) {
698
+ const numbers = xValues;
699
+ const x = linearScale(extent(numbers) ?? [0, 0], [plot.x, plot.x + plot.width], {
700
+ chart: options.chart,
701
+ property: options.property,
702
+ padding: options.padding
703
+ });
704
+ return { at: (index) => x(numbers[index] ?? 0), bandwidth: 0 };
705
+ }
706
+ const keys = xValues.map((_, index) => String(index));
707
+ const band = bandScale(keys, [plot.x, plot.x + plot.width], { paddingInner: options.paddingInner, paddingOuter: options.paddingOuter });
708
+ return { at: (index) => band.center(String(index)) ?? plot.x, bandwidth: band.bandwidth };
709
+ }
710
+ function categoryLabels(texts, at2, area5, plot) {
711
+ const count = texts.length;
712
+ const step = Math.max(1, Math.ceil(count / Math.max(2, Math.floor(plot.width / LABEL_SPACING))));
713
+ const labels = [];
714
+ for (let index = 0; index < count; index += step) {
715
+ labels.push({
716
+ x: at2(index),
717
+ y: area5.y + area5.height - 5,
718
+ text: texts[index] ?? "",
719
+ kind: "tick",
720
+ part: "axis",
721
+ anchor: index === 0 ? "start" : "middle"
722
+ });
723
+ }
724
+ return labels;
725
+ }
726
+ var LEGEND_BAND = 22;
727
+ var TICK_CHAR = 5.2;
728
+ var LEGEND_CHAR = TICK_CHAR;
729
+ function legend(area5, entries) {
730
+ const strokes = [];
731
+ const labels = [];
732
+ let x = area5.x + PLOT_INSET;
733
+ for (const entry of entries) {
734
+ const swatch = { x, y: area5.y + 4, width: 12, height: 7 };
735
+ strokes.push({ d: rectPath(swatch), role: "ornament", part: entry.part, ...entry.tone ? { tone: entry.tone } : {}, ...entry.dash ? { dash: entry.dash } : {} });
736
+ labels.push({ x: x + 16, y: area5.y + 11, text: entry.name, kind: "tick", part: "axis", anchor: "start" });
737
+ x += 16 + entry.name.length * LEGEND_CHAR + 12;
738
+ }
739
+ return { area: { x: area5.x, y: area5.y + LEGEND_BAND, width: area5.width, height: Math.max(area5.height - LEGEND_BAND, 1) }, strokes, labels };
740
+ }
741
+ function pillPath(rect) {
742
+ const { x, y, width, height } = rect;
743
+ const r = Math.min(width, height) / 2;
744
+ if (r <= 0) return `M${x},${y}H${x + width}V${y + height}H${x}Z`;
745
+ return `M${x},${y + r}A${r},${r},0,0,1,${x + r},${y}H${x + width - r}A${r},${r},0,0,1,${x + width},${y + r}V${y + height - r}A${r},${r},0,0,1,${x + width - r},${y + height}H${x + r}A${r},${r},0,0,1,${x},${y + height - r}Z`;
746
+ }
747
+ function horizontalValueAxis(plot, area5, domain, options) {
748
+ const scale = linearScale([Math.min(0, domain[0]), Math.max(0, domain[1])], [plot.x, plot.x + plot.width], {
749
+ chart: options.chart,
750
+ property: options.property,
751
+ padding: options.padding,
752
+ nice: true,
753
+ fromZero: true
754
+ });
755
+ const strokes = [];
756
+ const labels = [];
757
+ for (const tick of scale.ticks(4)) {
758
+ const tx = scale(tick);
759
+ if (tick !== 0) strokes.push({ d: `M${tx},${plot.y}V${plot.y + plot.height}`, role: "ornament", part: "grid" });
760
+ labels.push({ x: tx, y: area5.y + area5.height - 5, text: formatNumber(tick, options.locale, options.numberFormat), kind: "tick", part: "axis", anchor: "middle" });
761
+ }
762
+ strokes.push({ d: `M${scale(0)},${plot.y}V${plot.y + plot.height}`, role: "ornament", part: "rule" });
763
+ return { scale, strokes, labels };
764
+ }
765
+
766
+ // src/charts/shared/shell.ts
767
+ function modelBase(chart, props, context, fallbackName, table) {
768
+ const { id } = context;
769
+ const name = props.label ?? props.title ?? fallbackName;
770
+ return {
771
+ chart,
772
+ id,
773
+ chrome: props.chrome ?? "card",
774
+ name,
775
+ ids: { title: `${id}-title`, desc: `${id}-desc`, table: `${id}-table` },
776
+ table: { caption: name, ...table },
777
+ dataTable: props.dataTable ?? "hidden"
778
+ };
779
+ }
780
+ function areaHeight(props) {
781
+ return props.height ?? 160;
782
+ }
783
+ function measure(base, props, context) {
784
+ const width = props.width ?? context.width;
785
+ const height = areaHeight(props);
786
+ if (width !== void 0 && width > 0 && height > 0) return { width, height };
787
+ if (width !== void 0 && process.env.NODE_ENV !== "production") {
788
+ diagnose("SP003", base.chart, {
789
+ property: width <= 0 ? "width" : "height",
790
+ message: `Measured ${width} \xD7 ${height} px.`
791
+ });
792
+ }
793
+ const box = { x: 0, y: 0, width: 0, height: 0 };
794
+ return {
795
+ deferred: {
796
+ ...base,
797
+ status: "deferred",
798
+ geometry: { viewBox: box, plot: box, strokes: [], labels: [], hitAreas: [], defs: [] },
799
+ description: props.description ?? base.name
800
+ }
801
+ };
802
+ }
803
+ function emptyModel(base, props, context, frame, datasetEmpty) {
804
+ if (datasetEmpty && process.env.NODE_ENV !== "production") {
805
+ diagnose("SP001", base.chart, { property: "data" });
806
+ }
807
+ const { plot } = frame;
808
+ const strokes = [...frame.strokes];
809
+ if (context.emptyState.rule) {
810
+ strokes.push({ d: `M${plot.x},${plot.y + plot.height}H${plot.x + plot.width}`, role: "ornament", part: "rule" });
811
+ }
812
+ const labels = [
813
+ ...frame.labels,
814
+ {
815
+ x: plot.x + plot.width / 2,
816
+ y: plot.y + plot.height / 2,
817
+ text: context.emptyState.text,
818
+ kind: "empty",
819
+ part: "axis",
820
+ anchor: "middle"
821
+ }
822
+ ];
823
+ return {
824
+ ...base,
825
+ status: "ready",
826
+ description: props.description ?? `${base.name}. ${context.emptyState.text}.`,
827
+ geometry: roundGeometry({ viewBox: frame.viewBox, plot, strokes, labels, hitAreas: [], defs: [] })
828
+ };
829
+ }
830
+ function readyModel(base, description, geometry) {
831
+ return { ...base, status: "ready", description, geometry: roundGeometry({ ...geometry, defs: [] }) };
832
+ }
833
+
834
+ // src/charts/line-chart/demo.ts
835
+ var LINE_CHART_DEMO = /* @__PURE__ */ Object.freeze(
836
+ /* @__PURE__ */ [
837
+ { hour: "00", hits: 18, baseline: 22 },
838
+ { hour: "02", hits: 14, baseline: 18 },
839
+ { hour: "04", hits: 11, baseline: 15 },
840
+ { hour: "06", hits: 16, baseline: 17 },
841
+ { hour: "08", hits: 34, baseline: 30 },
842
+ { hour: "10", hits: 58, baseline: 48 },
843
+ { hour: "12", hits: 71, baseline: 60 },
844
+ { hour: "14", hits: 66, baseline: 62 },
845
+ { hour: "16", hits: 74, baseline: 64 },
846
+ { hour: "18", hits: 88, baseline: 70 },
847
+ { hour: "20", hits: 62, baseline: 55 },
848
+ { hour: "22", hits: 41, baseline: 40 }
849
+ ].map((row) => Object.freeze(row))
850
+ );
851
+ var LINE_CHART_DEMO_KEYS = /* @__PURE__ */ Object.freeze({
852
+ xKey: "hour",
853
+ valueKey: "hits",
854
+ secondaryKey: "baseline"
855
+ });
856
+
857
+ // src/charts/line-chart/line-chart.ts
858
+ var CHART = "LineChart";
859
+ var HEIGHTEN_RADIUS = 3.5;
860
+ var CURVES = {
861
+ monotone: curveMonotoneX,
862
+ linear: curveLinear,
863
+ natural: curveNatural,
864
+ step: curveStepAfter
865
+ };
866
+ function buildLineChart(props, context) {
867
+ const usesDemo = props.data === void 0;
868
+ const data = props.data ?? LINE_CHART_DEMO;
869
+ const xKey = usesDemo ? LINE_CHART_DEMO_KEYS.xKey : props.xKey ?? "x";
870
+ const valueKey = usesDemo ? LINE_CHART_DEMO_KEYS.valueKey : props.valueKey ?? "value";
871
+ const secondaryKey = usesDemo ? LINE_CHART_DEMO_KEYS.secondaryKey : props.secondaryKey;
872
+ const chrome = props.chrome ?? "card";
873
+ const { locale } = context;
874
+ const numberFormat = props.numberFormat;
875
+ const primary = readSeries(data, valueKey, "value");
876
+ const secondary = secondaryKey !== void 0 && (props.series ?? "all") === "all" ? readSeries(data, secondaryKey, "secondary") : void 0;
877
+ const series = secondary ? [primary, secondary] : [primary];
878
+ checkVolume(CHART, series);
879
+ const xName = accessorName(xKey, "x");
880
+ const xValues = data.map((datum, index) => read(xKey, datum, index));
881
+ const xLabels = xValues.map((value) => formatCategory(value, locale));
882
+ const base = modelBase(CHART, props, context, "Line chart", {
883
+ columns: [xName, ...series.map((s) => s.key)],
884
+ rows: data.map((_, index) => [
885
+ xLabels[index] ?? "",
886
+ ...series.map((s) => formatValue(s.points[index]?.value, locale, numberFormat))
887
+ ])
888
+ });
889
+ const { name } = base;
890
+ const size = measure(base, props, context);
891
+ if ("deferred" in size) return size.deferred;
892
+ const { width } = size;
893
+ const card = cardLayout(props, { width, areaHeight: size.height, chrome, locale });
894
+ const { area: area5 } = card;
895
+ const plot = cartesianPlot(area5);
896
+ const strokes = [...card.strokes];
897
+ const labels = [...card.labels];
898
+ const hitAreas = [];
899
+ for (const s of series) {
900
+ if (s.missing > 0 && data.length > 0 && process.env.NODE_ENV !== "production") {
901
+ diagnose("SP002", CHART, {
902
+ property: s.key,
903
+ message: `${s.missing} of ${data.length} values were omitted from the stroke; set \`connectNulls\` to bridge the gap.`
904
+ });
905
+ }
906
+ }
907
+ const valueExtent = extent(seriesValues(series));
908
+ if (data.length === 0 || valueExtent === void 0) {
909
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
910
+ }
911
+ const axis = valueAxis(plot, valueExtent, { chart: CHART, property: primary.key, padding: context.domainPadding, locale, numberFormat });
912
+ const y = axis.scale;
913
+ strokes.push(...axis.strokes);
914
+ labels.push(...axis.labels);
915
+ const xAt = categoryAxis(xValues, plot, { chart: CHART, property: xName, padding: context.domainPadding }).at;
916
+ const curve = CURVES[props.curve ?? "monotone"];
917
+ const connectNulls = props.connectNulls ?? false;
918
+ const pathFor = (s) => {
919
+ const generator = line().x((p) => xAt(p.index)).y((p) => y(p.value ?? 0)).curve(curve);
920
+ const points = connectNulls ? s.points.filter((p) => p.value !== void 0) : s.points;
921
+ if (!connectNulls) generator.defined((p) => p.value !== void 0);
922
+ return generator(points) ?? "";
923
+ };
924
+ const ordered = secondary ? [secondary, primary] : [primary];
925
+ for (const s of ordered) {
926
+ const d = pathFor(s);
927
+ if (d === "") continue;
928
+ strokes.push(
929
+ s === primary ? { d, role: "encoding", part: "ink" } : { d, role: "encoding", part: "ink-secondary", dash: "dotted" }
930
+ );
931
+ }
932
+ for (const s of series) {
933
+ for (const p of s.points) {
934
+ if (p.value === void 0) continue;
935
+ hitAreas.push({
936
+ seriesKey: s.key,
937
+ index: p.index,
938
+ datum: p.datum,
939
+ value: p.value,
940
+ x: xAt(p.index),
941
+ y: y(p.value)
942
+ });
943
+ }
944
+ }
945
+ const live = [...primary.points].reverse().find((p) => p.value !== void 0);
946
+ if (live?.value !== void 0) {
947
+ const d = circlePath(xAt(live.index), y(live.value), HEIGHTEN_RADIUS);
948
+ strokes.push({ d, role: "encoding", part: "heighten", paint: "fill" });
949
+ strokes.push({ d, role: "encoding", part: "ink", paint: "stroke" });
950
+ }
951
+ const count = data.length;
952
+ labels.push(...categoryLabels(xLabels, xAt, area5, plot));
953
+ const ranges = series.map((s) => {
954
+ const seriesExtent = extent(s.points.flatMap((p) => p.value === void 0 ? [] : [p.value]));
955
+ return seriesExtent ? `${s.key} ranges from ${formatNumber(seriesExtent[0], locale, numberFormat)} to ${formatNumber(seriesExtent[1], locale, numberFormat)}` : `${s.key} has no values`;
956
+ });
957
+ const description = props.description ?? `${name}. Line chart of ${count} ${xName} values${secondary ? " in 2 series" : ""}; ${ranges.join("; ")}.`;
958
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
959
+ }
960
+ var lineChart = /* @__PURE__ */ Object.freeze({
961
+ name: CHART,
962
+ build: buildLineChart
963
+ });
964
+
965
+ // src/interaction/hit-test.ts
966
+ var MIN_TOUCH_TARGET = 24;
967
+ function toActiveItem(hit) {
968
+ return {
969
+ seriesKey: hit.seriesKey,
970
+ index: hit.index,
971
+ datum: hit.datum,
972
+ value: hit.value,
973
+ point: { x: hit.x, y: hit.y }
974
+ };
975
+ }
976
+ function inside(rect, x, y) {
977
+ return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height;
978
+ }
979
+ function spacing(hitAreas) {
980
+ let smallest = Number.POSITIVE_INFINITY;
981
+ for (let i = 1; i < hitAreas.length; i += 1) {
982
+ const previous = hitAreas[i - 1];
983
+ const current = hitAreas[i];
984
+ if (previous.seriesKey !== current.seriesKey) continue;
985
+ const gap = Math.abs(current.x - previous.x);
986
+ if (gap > 0 && gap < smallest) smallest = gap;
987
+ }
988
+ return smallest;
989
+ }
990
+ function resolveActive(geometry, pointer, kind = "mouse") {
991
+ if (!Number.isFinite(pointer.x) || !Number.isFinite(pointer.y)) return null;
992
+ if (!inside(geometry.plot, pointer.x, pointer.y)) return null;
993
+ let boxed;
994
+ for (const hit of geometry.hitAreas) {
995
+ if (!hit.box || !inside(hit.box, pointer.x, pointer.y)) continue;
996
+ if (!boxed?.box || hit.box.width * hit.box.height < boxed.box.width * boxed.box.height) boxed = hit;
997
+ }
998
+ if (boxed) return toActiveItem(boxed);
999
+ let best;
1000
+ let bestDx = Number.POSITIVE_INFINITY;
1001
+ let bestDy = Number.POSITIVE_INFINITY;
1002
+ for (const hit of geometry.hitAreas) {
1003
+ const dx = Math.abs(hit.x - pointer.x);
1004
+ const dy = Math.abs(hit.y - pointer.y);
1005
+ if (dx < bestDx || dx === bestDx && dy < bestDy) {
1006
+ best = hit;
1007
+ bestDx = dx;
1008
+ bestDy = dy;
1009
+ }
1010
+ }
1011
+ if (!best) return null;
1012
+ if (kind === "touch") {
1013
+ const reach = Math.max(MIN_TOUCH_TARGET / 2, spacing(geometry.hitAreas) / 2);
1014
+ if (bestDx > reach) return null;
1015
+ }
1016
+ return toActiveItem(best);
1017
+ }
1018
+ function toSvgPoint(client, box, viewBox) {
1019
+ if (box.width <= 0 || box.height <= 0) return { x: Number.NaN, y: Number.NaN };
1020
+ return {
1021
+ x: viewBox.x + (client.x - box.left) * viewBox.width / box.width,
1022
+ y: viewBox.y + (client.y - box.top) * viewBox.height / box.height
1023
+ };
1024
+ }
1025
+ var NAVIGATION_KEYS = ["ArrowRight", "ArrowDown", "ArrowLeft", "ArrowUp", "Home", "End", "Escape"];
1026
+ function stepActive(geometry, current, key) {
1027
+ if (!NAVIGATION_KEYS.includes(key)) return void 0;
1028
+ if (key === "Escape") return null;
1029
+ const grid = geometry.hitAreas.length > 0 && geometry.hitAreas.every((hit) => hit.cell !== void 0);
1030
+ if (grid) return stepGrid(geometry.hitAreas, current, key);
1031
+ const seriesKey = current?.seriesKey ?? geometry.hitAreas[0]?.seriesKey;
1032
+ const points = geometry.hitAreas.filter((hit) => hit.seriesKey === seriesKey);
1033
+ if (points.length === 0) return null;
1034
+ const position = current ? points.findIndex((hit) => hit.index === current.index) : -1;
1035
+ let next;
1036
+ switch (key) {
1037
+ case "Home":
1038
+ next = 0;
1039
+ break;
1040
+ case "End":
1041
+ next = points.length - 1;
1042
+ break;
1043
+ case "ArrowRight":
1044
+ case "ArrowDown":
1045
+ next = position < 0 ? 0 : Math.min(position + 1, points.length - 1);
1046
+ break;
1047
+ default:
1048
+ next = position < 0 ? 0 : Math.max(position - 1, 0);
1049
+ }
1050
+ return toActiveItem(points[next]);
1051
+ }
1052
+ function stepGrid(hits, current, key) {
1053
+ const order = [...hits].sort((a, b) => (a.cell?.row ?? 0) - (b.cell?.row ?? 0) || (a.cell?.column ?? 0) - (b.cell?.column ?? 0));
1054
+ const here = current ? hits.find((hit) => hit.seriesKey === current.seriesKey && hit.index === current.index) : void 0;
1055
+ if (!here?.cell) return toActiveItem(key === "End" ? order.at(-1) : order[0]);
1056
+ if (key === "Home" || key === "End") {
1057
+ const row2 = order.filter((hit) => hit.cell?.row === here.cell?.row);
1058
+ return toActiveItem(key === "End" ? row2.at(-1) : row2[0]);
1059
+ }
1060
+ const horizontal = key === "ArrowRight" || key === "ArrowLeft";
1061
+ const sign = key === "ArrowRight" || key === "ArrowDown" ? 1 : -1;
1062
+ const { column, row } = here.cell;
1063
+ let next;
1064
+ for (const hit of hits) {
1065
+ if (!hit.cell) continue;
1066
+ const along2 = horizontal ? hit.cell.column - column : hit.cell.row - row;
1067
+ const across = horizontal ? hit.cell.row - row : hit.cell.column - column;
1068
+ if (across !== 0 || along2 * sign <= 0) continue;
1069
+ const best = next?.cell ? Math.abs(horizontal ? next.cell.column - column : next.cell.row - row) : Number.POSITIVE_INFINITY;
1070
+ if (Math.abs(along2) < best) next = hit;
1071
+ }
1072
+ return toActiveItem(next ?? here);
1073
+ }
1074
+ function readout(model, active) {
1075
+ const { geometry, table } = model;
1076
+ const column = Math.max(table.columns.indexOf(active.seriesKey), 1);
1077
+ const row = table.rows[active.index] ?? [];
1078
+ const heading = row[0] ?? "";
1079
+ const value = row[column] ?? String(active.value);
1080
+ const { viewBox } = geometry;
1081
+ const percent = (part, whole) => `${whole > 0 ? round2(part / whole * 100) : 0}%`;
1082
+ return {
1083
+ heading,
1084
+ text: `${active.seriesKey}: ${value}`,
1085
+ left: percent(active.point.x - viewBox.x, viewBox.width),
1086
+ top: percent(active.point.y - viewBox.y, viewBox.height),
1087
+ announcement: `${table.columns[0] ?? ""} ${heading}, ${active.seriesKey} ${value}`,
1088
+ marker: { d: circlePath(active.point.x, active.point.y, 6), role: "ornament", part: "ink", paint: "stroke" }
1089
+ };
1090
+ }
1091
+
1092
+ // src/interaction/reduce.ts
1093
+ function sameActive(a, b) {
1094
+ return a === b || a !== null && b !== null && a.seriesKey === b.seriesKey && a.index === b.index;
1095
+ }
1096
+ var SELECT_KEYS = ["Enter", " "];
1097
+ function reduceInteraction(geometry, current, event) {
1098
+ const settle = (active, extra = {}) => ({
1099
+ active,
1100
+ changed: !sameActive(current, active),
1101
+ handled: true,
1102
+ ...extra
1103
+ });
1104
+ switch (event.type) {
1105
+ case "pointer":
1106
+ case "click": {
1107
+ const active = resolveActive(geometry, toSvgPoint(event.client, event.box, geometry.viewBox), event.kind);
1108
+ return event.type === "click" && active ? settle(active, { selected: active }) : settle(active);
1109
+ }
1110
+ case "leave":
1111
+ case "blur":
1112
+ return settle(null);
1113
+ case "focus":
1114
+ return settle(current ?? stepActive(geometry, null, "Home") ?? null);
1115
+ case "key": {
1116
+ if (SELECT_KEYS.includes(event.key)) {
1117
+ return current ? settle(current, { selected: current }) : { active: null, changed: false, handled: false };
1118
+ }
1119
+ const next = stepActive(geometry, current, event.key);
1120
+ return next === void 0 ? { active: current, changed: false, handled: false } : settle(next);
1121
+ }
1122
+ }
1123
+ }
1124
+
1125
+ // src/charts/bullet-chart/demo.ts
1126
+ var BULLET_CHART_DEMO = /* @__PURE__ */ Object.freeze(
1127
+ /* @__PURE__ */ [
1128
+ { title: "Revenue", actual: 72, target: 80 },
1129
+ { title: "Profit", actual: 58, target: 65 },
1130
+ { title: "New users", actual: 86, target: 75 },
1131
+ { title: "Retention", actual: 41, target: 60 }
1132
+ ].map((row) => Object.freeze(row))
1133
+ );
1134
+
1135
+ // src/charts/bullet-chart/bullet-chart.ts
1136
+ var CHART2 = "BulletChart";
1137
+ var PLOT_INSET2 = 6;
1138
+ var MAX_BAR = 12;
1139
+ var MARKER_REACH = 3;
1140
+ function clampPercent(value, property, row) {
1141
+ if (value >= 0 && value <= 100) return value;
1142
+ warnValue(CHART2, property, `Row ${row} is ${value}; it is drawn at the nearest end of 0-100.`);
1143
+ return Math.min(Math.max(value, 0), 100);
1144
+ }
1145
+ function buildBulletChart(props, context) {
1146
+ const usesDemo = props.data === void 0;
1147
+ const data = props.data ?? BULLET_CHART_DEMO;
1148
+ const titleKey = usesDemo ? "title" : props.titleKey ?? "title";
1149
+ const actualKey = usesDemo ? "actual" : props.actualKey ?? "actual";
1150
+ const targetKey = usesDemo ? "target" : props.targetKey ?? "target";
1151
+ const { locale } = context;
1152
+ const numberFormat = props.numberFormat;
1153
+ const rows = data.map((datum, index) => ({
1154
+ index,
1155
+ datum,
1156
+ title: formatCategory(read(titleKey, datum, index), locale),
1157
+ actual: finite(read(actualKey, datum, index)),
1158
+ target: finite(read(targetKey, datum, index))
1159
+ }));
1160
+ const actualName = accessorName(actualKey, "actual");
1161
+ const targetName = accessorName(targetKey, "target");
1162
+ const base = modelBase(CHART2, props, context, "Bullet chart", {
1163
+ columns: [accessorName(titleKey, "title"), actualName, targetName],
1164
+ rows: rows.map((r) => [r.title, formatValue(r.actual, locale, numberFormat), formatValue(r.target, locale, numberFormat)])
1165
+ });
1166
+ const size = measure(base, props, context);
1167
+ if ("deferred" in size) return size.deferred;
1168
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
1169
+ const plot = inset(card.area, PLOT_INSET2);
1170
+ const strokes = [...card.strokes];
1171
+ const labels = [...card.labels];
1172
+ const hitAreas = [];
1173
+ if (rows.length === 0) {
1174
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, true);
1175
+ }
1176
+ const rowHeight = plot.height / rows.length;
1177
+ const barHeight = Math.min(MAX_BAR, rowHeight * 0.35);
1178
+ const at2 = (percent) => plot.x + percent / 100 * plot.width;
1179
+ let met = 0;
1180
+ for (const row of rows) {
1181
+ const top = plot.y + row.index * rowHeight;
1182
+ const barY = top + rowHeight - barHeight - Math.min(MARKER_REACH + 1, rowHeight * 0.1);
1183
+ const baseline = barY - 5;
1184
+ labels.push({ x: plot.x, y: baseline, text: row.title, kind: "tick", part: "text", anchor: "start" });
1185
+ strokes.push({ d: rectPath({ x: plot.x, y: barY, width: plot.width, height: barHeight }), role: "ornament", part: "rule" });
1186
+ if (row.actual === void 0) {
1187
+ warnValue(CHART2, actualName, `Row ${row.index} has no finite value; its bar is omitted.`);
1188
+ } else {
1189
+ const actual = clampPercent(row.actual, actualName, row.index);
1190
+ const bar = { x: plot.x, y: barY, width: at2(actual) - plot.x, height: barHeight };
1191
+ if (bar.width > 0) strokes.push({ d: rectPath(bar), role: "encoding", part: "ink", tone: 2 });
1192
+ labels.push({ x: plot.x + plot.width, y: baseline, text: formatNumber(row.actual, locale, numberFormat), kind: "tick", part: "axis", anchor: "end" });
1193
+ hitAreas.push({ seriesKey: actualName, index: row.index, datum: row.datum, value: row.actual, x: at2(actual), y: barY + barHeight / 2, box: bar });
1194
+ if (row.target !== void 0 && row.actual >= row.target) met += 1;
1195
+ }
1196
+ if (row.target === void 0) {
1197
+ warnValue(CHART2, targetName, `Row ${row.index} has no finite target; its marker is omitted.`);
1198
+ } else {
1199
+ const x = at2(clampPercent(row.target, targetName, row.index));
1200
+ strokes.push({ d: `M${x},${barY - MARKER_REACH}V${barY + barHeight + MARKER_REACH}`, role: "encoding", part: "ink", weight: 2 });
1201
+ }
1202
+ }
1203
+ const description = props.description ?? `${base.name}. Bullet chart of ${rows.length} targets on a 0 to 100 scale; ${met} of ${rows.length} reach their target.`;
1204
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
1205
+ }
1206
+ var bulletChart = /* @__PURE__ */ Object.freeze({
1207
+ name: CHART2,
1208
+ build: buildBulletChart
1209
+ });
1210
+
1211
+ // src/charts/pyramid-chart/demo.ts
1212
+ var PYRAMID_CHART_DEMO = /* @__PURE__ */ Object.freeze(
1213
+ /* @__PURE__ */ [
1214
+ { label: "Leads", width: 18 },
1215
+ { label: "Managers", width: 42 },
1216
+ { label: "Specialists", width: 70 },
1217
+ { label: "Staff", width: 100 }
1218
+ ].map((row) => Object.freeze(row))
1219
+ );
1220
+
1221
+ // src/charts/pyramid-chart/pyramid-chart.ts
1222
+ var CHART3 = "PyramidChart";
1223
+ var PLOT_INSET3 = 6;
1224
+ var LABEL_COLUMN = 72;
1225
+ var VALUE_COLUMN = 48;
1226
+ var TIER_GAP = 2;
1227
+ function buildPyramidChart(props, context) {
1228
+ const usesDemo = props.data === void 0;
1229
+ const data = props.data ?? PYRAMID_CHART_DEMO;
1230
+ const labelKey = usesDemo ? "label" : props.labelKey ?? "label";
1231
+ const widthKey = usesDemo ? "width" : props.widthKey ?? "width";
1232
+ const toneKey = usesDemo ? void 0 : props.toneKey;
1233
+ const { locale } = context;
1234
+ const numberFormat = props.numberFormat;
1235
+ const widthName = accessorName(widthKey, "width");
1236
+ const toneName = toneKey === void 0 ? void 0 : accessorName(toneKey, "tone");
1237
+ const rows = data.map((datum, index) => ({
1238
+ index,
1239
+ datum,
1240
+ label: formatCategory(read(labelKey, datum, index), locale),
1241
+ width: finite(read(widthKey, datum, index)),
1242
+ tone: toneKey === void 0 ? void 0 : finite(read(toneKey, datum, index))
1243
+ }));
1244
+ const base = modelBase(CHART3, props, context, "Pyramid chart", {
1245
+ columns: [accessorName(labelKey, "label"), widthName, ...toneName ? [toneName] : []],
1246
+ rows: rows.map((r) => [
1247
+ r.label,
1248
+ formatValue(r.width, locale, numberFormat),
1249
+ ...toneName ? [formatValue(r.tone, locale, void 0)] : []
1250
+ ])
1251
+ });
1252
+ const size = measure(base, props, context);
1253
+ if ("deferred" in size) return size.deferred;
1254
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
1255
+ const plot = inset(card.area, PLOT_INSET3);
1256
+ const strokes = [...card.strokes];
1257
+ const labels = [...card.labels];
1258
+ const hitAreas = [];
1259
+ if (rows.length === 0) {
1260
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, true);
1261
+ }
1262
+ const track = { x: plot.x + LABEL_COLUMN, width: Math.max(plot.width - LABEL_COLUMN - VALUE_COLUMN, 1) };
1263
+ const centre = track.x + track.width / 2;
1264
+ const tierHeight = plot.height / rows.length;
1265
+ let previous = Number.NEGATIVE_INFINITY;
1266
+ let monotonic = true;
1267
+ for (const row of rows) {
1268
+ const top = plot.y + row.index * tierHeight;
1269
+ const baseline = top + tierHeight / 2 + 4;
1270
+ labels.push({ x: plot.x, y: baseline, text: row.label, kind: "tick", part: "text", anchor: "start" });
1271
+ if (row.width === void 0) {
1272
+ warnValue(CHART3, widthName, `Tier ${row.index} has no finite width; it is omitted.`);
1273
+ continue;
1274
+ }
1275
+ if (row.width < previous) monotonic = false;
1276
+ previous = row.width;
1277
+ const percent = Math.min(Math.max(row.width, 0), 100);
1278
+ if (percent !== row.width) warnValue(CHART3, widthName, `Tier ${row.index} is ${row.width}; it is drawn at the nearest end of 0-100.`);
1279
+ const width = percent / 100 * track.width;
1280
+ const tier = { x: centre - width / 2, y: top + TIER_GAP / 2, width, height: Math.max(tierHeight - TIER_GAP, 0) };
1281
+ const tone = row.tone === void 0 ? toneOf(percent / 100) : toneLevel(row.tone);
1282
+ if (row.tone !== void 0 && tone !== row.tone) {
1283
+ warnValue(CHART3, toneName ?? "tone", `Tier ${row.index} has tone ${row.tone}; it is drawn at level ${tone}.`);
1284
+ }
1285
+ if (width > 0) strokes.push({ d: rectPath(tier), role: "encoding", part: "ink", ...tone > 0 ? { tone } : {} });
1286
+ const right = plot.x + plot.width;
1287
+ labels.push({ x: right, y: baseline, text: formatNumber(row.width, locale, numberFormat), kind: "tick", part: "axis", anchor: "end" });
1288
+ if (row.tone !== void 0) {
1289
+ labels.push({ x: right, y: baseline + 11, text: `tone ${tone}`, kind: "tick", part: "axis", anchor: "end" });
1290
+ }
1291
+ hitAreas.push({ seriesKey: widthName, index: row.index, datum: row.datum, value: row.width, x: centre, y: tier.y + tier.height / 2, box: tier });
1292
+ }
1293
+ if (!monotonic) warnValue(CHART3, widthName, "The widths do not increase from the top tier down; the pyramid is drawn as given.");
1294
+ const description = props.description ?? `${base.name}. Pyramid of ${rows.length} tiers, from ${rows[0]?.label} at the top to ${rows[rows.length - 1]?.label} at the base; width on a 0 to 100 scale.`;
1295
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
1296
+ }
1297
+ var pyramidChart = /* @__PURE__ */ Object.freeze({
1298
+ name: CHART3,
1299
+ build: buildPyramidChart
1300
+ });
1301
+
1302
+ // src/charts/heatmap-chart/demo.ts
1303
+ var HEATMAP_CHART_DEMO = /* @__PURE__ */ Object.freeze(
1304
+ /* @__PURE__ */ [
1305
+ { label: "Mon", values: Object.freeze([12, 34, 58, 71, 66, 40]) },
1306
+ { label: "Tue", values: Object.freeze([18, 42, 77, 88, 70, 35]) },
1307
+ { label: "Wed", values: Object.freeze([9, 38, 64, 93, 81, 47]) },
1308
+ { label: "Thu", values: Object.freeze([15, 29, 55, 68, 59, 31]) },
1309
+ { label: "Fri", values: Object.freeze([6, 21, 43, 49, 37, 14]) }
1310
+ ].map((row) => Object.freeze(row))
1311
+ );
1312
+
1313
+ // src/charts/heatmap-chart/heatmap-chart.ts
1314
+ var CHART4 = "HeatmapChart";
1315
+ var PLOT_INSET4 = 6;
1316
+ var LABEL_COLUMN2 = 40;
1317
+ var CELL_GAP = 2;
1318
+ var HEADER_BAND = 14;
1319
+ var DIGIT_WIDTH = 5.5;
1320
+ var LINE_HEIGHT = 12;
1321
+ var CELL_SCALE = { 0: 0.4, 1: 0.55, 2: 0.7, 3: 0.85, 4: 1 };
1322
+ function buildHeatmapChart(props, context) {
1323
+ const usesDemo = props.data === void 0;
1324
+ const data = props.data ?? HEATMAP_CHART_DEMO;
1325
+ const labelKey = usesDemo ? "label" : props.labelKey ?? "label";
1326
+ const valuesKey = usesDemo ? "values" : props.valuesKey ?? "values";
1327
+ const scaleMax = props.scaleMax !== void 0 && props.scaleMax > 0 ? props.scaleMax : 100;
1328
+ const { locale } = context;
1329
+ const numberFormat = props.numberFormat;
1330
+ const rows = data.map((datum, index) => {
1331
+ const raw = read(valuesKey, datum, index);
1332
+ return {
1333
+ index,
1334
+ datum,
1335
+ label: formatCategory(read(labelKey, datum, index), locale),
1336
+ values: Array.isArray(raw) ? raw.map(finite) : void 0
1337
+ };
1338
+ });
1339
+ const lengths = rows.flatMap((r) => r.values ? [r.values.length] : []);
1340
+ const columnCount = lengths.length > 0 ? lengths.reduce((a, b) => Math.min(a, b)) : 0;
1341
+ const named = props.columnLabels ?? [];
1342
+ const columns = Array.from({ length: columnCount }, (_, i) => named[i] ?? `#${i + 1}`);
1343
+ const base = modelBase(CHART4, props, context, "Heatmap", {
1344
+ columns: [accessorName(labelKey, "label"), ...columns],
1345
+ rows: rows.map((r) => [r.label, ...columns.map((_, c) => formatValue(r.values?.[c], locale, numberFormat))])
1346
+ });
1347
+ const size = measure(base, props, context);
1348
+ if ("deferred" in size) return size.deferred;
1349
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
1350
+ const plot = inset(card.area, PLOT_INSET4);
1351
+ const strokes = [...card.strokes];
1352
+ const labels = [...card.labels];
1353
+ const hitAreas = [];
1354
+ const valuesName = accessorName(valuesKey, "values");
1355
+ for (const row of rows) {
1356
+ if (!row.values) warnValue(CHART4, valuesName, `Row ${row.index} has no array of values; it is left blank.`);
1357
+ }
1358
+ if (lengths.some((length) => length !== columnCount)) {
1359
+ warnValue(CHART4, valuesName, `Rows have ${columnCount} to ${lengths.reduce((a, b) => Math.max(a, b))} values; the first ${columnCount} of each are drawn.`);
1360
+ }
1361
+ if (named.length > columnCount && columnCount > 0) {
1362
+ warnValue(CHART4, "columnLabels", `${named.length} column labels for ${columnCount} columns; the extra ones are ignored.`);
1363
+ }
1364
+ const drawable = rows.some((r) => r.values?.slice(0, columnCount).some((v) => v !== void 0));
1365
+ if (!drawable) {
1366
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, rows.length === 0);
1367
+ }
1368
+ const header = named.length > 0 ? HEADER_BAND : 0;
1369
+ const cellWidth = (plot.width - LABEL_COLUMN2) / columnCount;
1370
+ const cellHeight = (plot.height - header) / rows.length;
1371
+ if (header > 0) {
1372
+ columns.forEach((name, c) => {
1373
+ labels.push({ x: plot.x + LABEL_COLUMN2 + (c + 0.5) * cellWidth, y: plot.y + 10, text: name, kind: "tick", part: "axis", anchor: "middle" });
1374
+ });
1375
+ }
1376
+ let widest = 0;
1377
+ for (const r of rows) for (const v of r.values ?? []) if (v !== void 0) widest = Math.max(widest, formatNumber(v, locale, numberFormat).length);
1378
+ const printed = widest * DIGIT_WIDTH <= cellWidth - CELL_GAP - 2 && cellHeight >= LINE_HEIGHT;
1379
+ let omitted = 0;
1380
+ for (const row of rows) {
1381
+ const top = plot.y + header + row.index * cellHeight;
1382
+ labels.push({ x: plot.x, y: top + cellHeight / 2 + 4, text: row.label, kind: "tick", part: "text", anchor: "start" });
1383
+ for (let c = 0; c < columnCount; c += 1) {
1384
+ const value = row.values?.[c];
1385
+ const cell = inset({ x: plot.x + LABEL_COLUMN2 + c * cellWidth, y: top, width: cellWidth, height: cellHeight }, CELL_GAP / 2);
1386
+ const cx = cell.x + cell.width / 2;
1387
+ const cy = cell.y + cell.height / 2;
1388
+ if (value === void 0) {
1389
+ omitted += 1;
1390
+ if (printed) labels.push({ x: cx, y: cy + 4, text: "\u2014", kind: "tick", part: "axis", anchor: "middle" });
1391
+ continue;
1392
+ }
1393
+ const tone = toneOf(value / scaleMax);
1394
+ const scale = printed ? 1 : CELL_SCALE[tone];
1395
+ const drawn = { x: cx - cell.width * scale / 2, y: cy - cell.height * scale / 2, width: cell.width * scale, height: cell.height * scale };
1396
+ strokes.push({ d: rectPath(drawn), role: "encoding", part: "ink", ...tone > 0 ? { tone } : {} });
1397
+ if (printed) labels.push({ x: cx, y: cy + 4, text: formatNumber(value, locale, numberFormat), kind: "tick", part: "text", anchor: "middle" });
1398
+ hitAreas.push({ seriesKey: columns[c], index: row.index, datum: row.datum, value, x: cx, y: cy, box: cell, cell: { column: c, row: row.index } });
1399
+ }
1400
+ }
1401
+ if (omitted > 0) warnValue(CHART4, valuesName, `${omitted} cells have no finite value and are left blank.`);
1402
+ let low = Number.POSITIVE_INFINITY;
1403
+ let high = Number.NEGATIVE_INFINITY;
1404
+ for (const h of hitAreas) {
1405
+ low = Math.min(low, h.value);
1406
+ high = Math.max(high, h.value);
1407
+ }
1408
+ const description = props.description ?? `${base.name}. Heatmap of ${rows.length} rows by ${columnCount} columns, toned against ${formatNumber(scaleMax, locale, numberFormat)}; values range from ${formatNumber(low, locale, numberFormat)} to ${formatNumber(high, locale, numberFormat)}${printed ? "" : "; values are shown by cell size"}.`;
1409
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
1410
+ }
1411
+ var heatmapChart = /* @__PURE__ */ Object.freeze({
1412
+ name: CHART4,
1413
+ build: buildHeatmapChart
1414
+ });
1415
+
1416
+ // src/charts/treemap-chart/demo.ts
1417
+ var TREEMAP_CHART_DEMO = /* @__PURE__ */ Object.freeze(
1418
+ /* @__PURE__ */ [
1419
+ { label: "Search", share: 48, cols: 3, rows: 4 },
1420
+ { label: "Direct", share: 26, cols: 3, rows: 2 },
1421
+ { label: "Social", share: 17, cols: 2, rows: 2 },
1422
+ { label: "Email", share: 9, cols: 1, rows: 2 }
1423
+ ].map((row) => Object.freeze(row))
1424
+ );
1425
+
1426
+ // src/charts/treemap-chart/treemap-chart.ts
1427
+ var CHART5 = "TreemapChart";
1428
+ var PLOT_INSET5 = 6;
1429
+ var TILE_GAP = 3;
1430
+ var TEXT_INSET = 6;
1431
+ var LINE = 13;
1432
+ var DEFAULT_COLUMNS = 6;
1433
+ var DEFAULT_ROWS = 4;
1434
+ var wholeCount = (raw) => {
1435
+ const value = finite(raw);
1436
+ return value !== void 0 && Number.isInteger(value) && value > 0 ? value : void 0;
1437
+ };
1438
+ function place(taken, cols, rows) {
1439
+ const gridRows = taken.length;
1440
+ const gridColumns = taken[0]?.length ?? 0;
1441
+ for (let row = 0; row + rows <= gridRows; row += 1) {
1442
+ for (let column = 0; column + cols <= gridColumns; column += 1) {
1443
+ let free = true;
1444
+ for (let r = row; r < row + rows && free; r += 1) {
1445
+ for (let c = column; c < column + cols && free; c += 1) free = !taken[r]?.[c];
1446
+ }
1447
+ if (!free) continue;
1448
+ for (let r = row; r < row + rows; r += 1) for (let c = column; c < column + cols; c += 1) taken[r][c] = true;
1449
+ return { column, row };
1450
+ }
1451
+ }
1452
+ return void 0;
1453
+ }
1454
+ function buildTreemapChart(props, context) {
1455
+ const usesDemo = props.data === void 0;
1456
+ const data = props.data ?? TREEMAP_CHART_DEMO;
1457
+ const labelKey = usesDemo ? "label" : props.labelKey ?? "label";
1458
+ const shareKey = usesDemo ? "share" : props.shareKey ?? "share";
1459
+ const gridColumns = wholeCount(props.columns) ?? DEFAULT_COLUMNS;
1460
+ const gridRows = wholeCount(props.rows) ?? DEFAULT_ROWS;
1461
+ const { locale } = context;
1462
+ const numberFormat = props.numberFormat;
1463
+ const shareName = accessorName(shareKey, "share");
1464
+ const tiles = data.map((datum, index) => ({
1465
+ index,
1466
+ datum,
1467
+ label: formatCategory(read(labelKey, datum, index), locale),
1468
+ share: finite(read(shareKey, datum, index)),
1469
+ cols: wholeCount(datum.cols),
1470
+ rows: wholeCount(datum.rows),
1471
+ tone: finite(datum.tone)
1472
+ }));
1473
+ const shareText = (share) => share === void 0 ? "\u2014" : `${formatNumber(share, locale, numberFormat)}%`;
1474
+ const base = modelBase(CHART5, props, context, "Treemap", {
1475
+ columns: [accessorName(labelKey, "label"), shareName],
1476
+ rows: tiles.map((t) => [t.label, shareText(t.share)])
1477
+ });
1478
+ const size = measure(base, props, context);
1479
+ if ("deferred" in size) return size.deferred;
1480
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
1481
+ const plot = inset(card.area, PLOT_INSET5);
1482
+ const strokes = [...card.strokes];
1483
+ const labels = [...card.labels];
1484
+ const hitAreas = [];
1485
+ const taken = Array.from({ length: gridRows }, () => Array(gridColumns).fill(false));
1486
+ const cellWidth = plot.width / gridColumns;
1487
+ const cellHeight = plot.height / gridRows;
1488
+ const placed = [];
1489
+ for (const tile of tiles) {
1490
+ if (tile.cols === void 0 || tile.rows === void 0) {
1491
+ warnValue(CHART5, "cols", `Tile ${tile.index} needs whole, positive cols and rows; it is omitted.`);
1492
+ continue;
1493
+ }
1494
+ const spot = place(taken, tile.cols, tile.rows);
1495
+ if (!spot) {
1496
+ warnValue(CHART5, "cols", `Tile ${tile.index} (${tile.cols} \xD7 ${tile.rows}) no longer fits the ${gridColumns} \xD7 ${gridRows} grid; it is omitted.`);
1497
+ continue;
1498
+ }
1499
+ const box = { x: plot.x + spot.column * cellWidth, y: plot.y + spot.row * cellHeight, width: tile.cols * cellWidth, height: tile.rows * cellHeight };
1500
+ const drawn = inset(box, TILE_GAP / 2);
1501
+ const tone = tile.tone === void 0 ? toneOf((tile.share ?? 0) / 100) : toneLevel(tile.tone);
1502
+ strokes.push({ d: rectPath(drawn), role: "encoding", part: "ink", ...tone > 0 ? { tone } : {} });
1503
+ const x = drawn.x + TEXT_INSET;
1504
+ labels.push({ x, y: drawn.y + LINE, text: tile.label, kind: "tick", part: "text", anchor: "start" });
1505
+ labels.push({ x, y: drawn.y + 2 * LINE, text: shareText(tile.share), kind: "tick", part: "axis", anchor: "start" });
1506
+ if (tile.tone !== void 0) labels.push({ x, y: drawn.y + 3 * LINE, text: `tone ${tone}`, kind: "tick", part: "axis", anchor: "start" });
1507
+ placed.push(tile);
1508
+ hitAreas.push({ seriesKey: shareName, index: tile.index, datum: tile.datum, value: tile.share ?? 0, x: box.x + box.width / 2, y: box.y + box.height / 2, box });
1509
+ }
1510
+ if (hitAreas.length === 0) {
1511
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, tiles.length === 0);
1512
+ }
1513
+ const description = props.description ?? `${base.name}. Treemap of ${hitAreas.length} tiles in a ${gridColumns} by ${gridRows} grid; ${placed.map((t) => `${t.label} ${shareText(t.share)}`).join(", ")}${placed.length < tiles.length ? `; ${tiles.length - placed.length} omitted` : ""}.`;
1514
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
1515
+ }
1516
+ var treemapChart = /* @__PURE__ */ Object.freeze({
1517
+ name: CHART5,
1518
+ build: buildTreemapChart
1519
+ });
1520
+
1521
+ // src/charts/sankey-chart/demo.ts
1522
+ var SANKEY_CHART_DEMO = /* @__PURE__ */ Object.freeze(
1523
+ /* @__PURE__ */ [
1524
+ { source: "Search", target: "Visit", value: 48 },
1525
+ { source: "Social", target: "Visit", value: 27 },
1526
+ { source: "Email", target: "Visit", value: 15 },
1527
+ { source: "Visit", target: "Signup", value: 42 },
1528
+ { source: "Visit", target: "Bounce", value: 48 },
1529
+ { source: "Signup", target: "Paid", value: 18 }
1530
+ ].map((row) => Object.freeze(row))
1531
+ );
1532
+
1533
+ // src/charts/sankey-chart/sankey-chart.ts
1534
+ var CHART6 = "SankeyChart";
1535
+ var PLOT_INSET6 = 6;
1536
+ var NODE_WIDTH = 8;
1537
+ var NODE_GAP = 10;
1538
+ var MAX_GAP_SHARE = 0.5;
1539
+ var LABEL_GAP = 4;
1540
+ function reaches(edges, from, to) {
1541
+ const stack = [from];
1542
+ const seen = /* @__PURE__ */ new Set();
1543
+ while (stack.length > 0) {
1544
+ const name = stack.pop();
1545
+ if (name === to) return true;
1546
+ if (seen.has(name)) continue;
1547
+ seen.add(name);
1548
+ stack.push(...edges.get(name) ?? []);
1549
+ }
1550
+ return false;
1551
+ }
1552
+ function buildSankeyChart(props, context) {
1553
+ const usesDemo = props.data === void 0;
1554
+ const data = props.data ?? SANKEY_CHART_DEMO;
1555
+ const sourceKey = usesDemo ? "source" : props.sourceKey ?? "source";
1556
+ const targetKey = usesDemo ? "target" : props.targetKey ?? "target";
1557
+ const valueKey = usesDemo ? "value" : props.valueKey ?? "value";
1558
+ const { locale } = context;
1559
+ const numberFormat = props.numberFormat;
1560
+ const valueName = accessorName(valueKey, "value");
1561
+ const flows = [];
1562
+ const edges = /* @__PURE__ */ new Map();
1563
+ data.forEach((datum, index) => {
1564
+ const rawSource = read(sourceKey, datum, index);
1565
+ const rawTarget = read(targetKey, datum, index);
1566
+ const missing = rawSource === null || rawSource === void 0 ? "source" : rawTarget === null || rawTarget === void 0 ? "target" : void 0;
1567
+ if (missing) {
1568
+ warnValue(CHART6, accessorName(missing === "source" ? sourceKey : targetKey, missing), `Flow ${index} has no ${missing}; it is omitted.`);
1569
+ return;
1570
+ }
1571
+ const source = formatCategory(rawSource, locale);
1572
+ const target = formatCategory(rawTarget, locale);
1573
+ const value = finite(read(valueKey, datum, index));
1574
+ if (value === void 0 || value <= 0) {
1575
+ warnValue(CHART6, valueName, `Flow ${index} (${source} \u2192 ${target}) is ${String(value ?? "not finite")}; flows must be positive, so it is omitted.`);
1576
+ return;
1577
+ }
1578
+ if (reaches(edges, target, source)) {
1579
+ warnValue(CHART6, accessorName(targetKey, "target"), `Flow ${index} (${source} \u2192 ${target}) would close a cycle; it is omitted.`);
1580
+ return;
1581
+ }
1582
+ edges.set(source, [...edges.get(source) ?? [], target]);
1583
+ flows.push({ index, datum, source, target, value });
1584
+ });
1585
+ const base = modelBase(CHART6, props, context, "Sankey diagram", {
1586
+ columns: ["flow", valueName],
1587
+ rows: flows.map((f) => [`${f.source} \u2192 ${f.target}`, formatNumber(f.value, locale, numberFormat)])
1588
+ });
1589
+ const size = measure(base, props, context);
1590
+ if ("deferred" in size) return size.deferred;
1591
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
1592
+ const plot = inset(card.area, PLOT_INSET6);
1593
+ const strokes = [...card.strokes];
1594
+ const labels = [...card.labels];
1595
+ const hitAreas = [];
1596
+ if (flows.length === 0) {
1597
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
1598
+ }
1599
+ const nodes = /* @__PURE__ */ new Map();
1600
+ const node = (name) => {
1601
+ let found = nodes.get(name);
1602
+ if (!found) {
1603
+ found = { name, layer: 0, inflow: 0, outflow: 0, x: 0, y: 0, height: 0, outCursor: 0, inCursor: 0 };
1604
+ nodes.set(name, found);
1605
+ }
1606
+ return found;
1607
+ };
1608
+ for (const flow of flows) {
1609
+ node(flow.source).outflow += flow.value;
1610
+ node(flow.target).inflow += flow.value;
1611
+ }
1612
+ for (let pass = 0; pass < nodes.size; pass += 1) {
1613
+ for (const flow of flows) {
1614
+ const target = node(flow.target);
1615
+ target.layer = Math.max(target.layer, node(flow.source).layer + 1);
1616
+ }
1617
+ }
1618
+ const layers = [];
1619
+ for (const n of nodes.values()) (layers[n.layer] ??= []).push(n);
1620
+ const throughput = (n) => Math.max(n.inflow, n.outflow);
1621
+ const gapOf = (count) => count > 1 ? Math.min(NODE_GAP, plot.height * MAX_GAP_SHARE / (count - 1)) : 0;
1622
+ const crowded = layers.filter((layer) => layer.length > 1 && gapOf(layer.length) < NODE_GAP);
1623
+ if (crowded.length > 0) {
1624
+ warnValue(CHART6, accessorName(targetKey, "target"), `A layer of ${Math.max(...crowded.map((l) => l.length))} nodes is crowded; its gaps are narrowed to fit.`);
1625
+ }
1626
+ const scale = layers.reduce(
1627
+ (smallest, layer) => Math.min(smallest, (plot.height - (layer.length - 1) * gapOf(layer.length)) / layer.reduce((sum, n) => sum + throughput(n), 0)),
1628
+ Number.POSITIVE_INFINITY
1629
+ );
1630
+ const step = layers.length > 1 ? (plot.width - NODE_WIDTH) / (layers.length - 1) : 0;
1631
+ layers.forEach((layer, index) => {
1632
+ const gap = gapOf(layer.length);
1633
+ const height = layer.reduce((sum, n) => sum + throughput(n) * scale, 0) + (layer.length - 1) * gap;
1634
+ let y = plot.y + (plot.height - height) / 2;
1635
+ for (const n of layer) {
1636
+ n.x = plot.x + index * step;
1637
+ n.y = y;
1638
+ n.height = throughput(n) * scale;
1639
+ n.outCursor = y;
1640
+ n.inCursor = y;
1641
+ y += n.height + gap;
1642
+ }
1643
+ });
1644
+ flows.forEach((flow, row) => {
1645
+ const source = node(flow.source);
1646
+ const target = node(flow.target);
1647
+ const width = flow.value * scale;
1648
+ const x0 = source.x + NODE_WIDTH;
1649
+ const x1 = target.x;
1650
+ const y0 = source.outCursor;
1651
+ const y1 = target.inCursor;
1652
+ source.outCursor += width;
1653
+ target.inCursor += width;
1654
+ const mx = (x0 + x1) / 2;
1655
+ const d = `M${x0},${y0}C${mx},${y0},${mx},${y1},${x1},${y1}V${y1 + width}C${mx},${y1 + width},${mx},${y0 + width},${x0},${y0 + width}Z`;
1656
+ strokes.push({ d, role: "encoding", part: "ink-secondary", tone: 1 });
1657
+ hitAreas.push({ seriesKey: valueName, index: row, datum: flow.datum, value: flow.value, x: mx, y: (y0 + y1) / 2 + width / 2 });
1658
+ });
1659
+ const last = layers.length - 1;
1660
+ for (const n of nodes.values()) {
1661
+ strokes.push({ d: rectPath({ x: n.x, y: n.y, width: NODE_WIDTH, height: n.height }), role: "encoding", part: "ink", tone: 3 });
1662
+ const end = n.layer === last && last > 0;
1663
+ labels.push({
1664
+ x: end ? n.x - LABEL_GAP : n.x + NODE_WIDTH + LABEL_GAP,
1665
+ y: n.y + n.height / 2 + 4,
1666
+ text: `${n.name} ${formatNumber(throughput(n), locale, numberFormat)}`,
1667
+ kind: "tick",
1668
+ part: "text",
1669
+ anchor: end ? "end" : "start"
1670
+ });
1671
+ }
1672
+ const total = [...nodes.values()].filter((n) => n.inflow === 0).reduce((sum, n) => sum + n.outflow, 0);
1673
+ const description = props.description ?? `${base.name}. Sankey diagram of ${flows.length} flows between ${nodes.size} nodes in ${layers.length} layers; total flow of ${formatNumber(total, locale, numberFormat)}.`;
1674
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
1675
+ }
1676
+ var sankeyChart = /* @__PURE__ */ Object.freeze({
1677
+ name: CHART6,
1678
+ build: buildSankeyChart
1679
+ });
1680
+
1681
+ // src/charts/activity-grid/demo.ts
1682
+ var ACTIVITY_GRID_DEMO_END = "2026-06-30";
1683
+ var ACTIVITY_GRID_DEMO_SEED = 1592;
1684
+ var DAYS = 26 * 7;
1685
+ var ACTIVITY_GRID_DEMO = /* @__PURE__ */ Object.freeze(
1686
+ /* @__PURE__ */ (() => {
1687
+ const next = createPrng(ACTIVITY_GRID_DEMO_SEED);
1688
+ const first = parseIsoDate(ACTIVITY_GRID_DEMO_END) - (DAYS - 1);
1689
+ return Array.from({ length: DAYS }, (_, i) => {
1690
+ const day = first + i;
1691
+ const weekend = weekday(day) === 0 || weekday(day) === 6;
1692
+ const r = next();
1693
+ const count = Math.floor(r * r * (weekend ? 5 : 14));
1694
+ const level = count === 0 ? 0 : count <= 2 ? 1 : count <= 5 ? 2 : count <= 9 ? 3 : 4;
1695
+ return Object.freeze({ date: isoDate(day), count, level });
1696
+ });
1697
+ })()
1698
+ );
1699
+
1700
+ // src/charts/activity-grid/activity-grid.ts
1701
+ var CHART7 = "ActivityGrid";
1702
+ var PLOT_INSET7 = 6;
1703
+ var MONTH_BAND = 14;
1704
+ var DEFAULT_WEEKS = 26;
1705
+ var DAY_MS2 = 864e5;
1706
+ var CELL_SCALE2 = { 0: 0.4, 1: 0.55, 2: 0.7, 3: 0.85, 4: 1 };
1707
+ var CELL_FILL = 0.86;
1708
+ var MONTH_GAP = 3;
1709
+ function buildActivityGrid(props, context) {
1710
+ const usesDemo = props.data === void 0;
1711
+ const data = props.data ?? ACTIVITY_GRID_DEMO;
1712
+ const dateKey = usesDemo ? "date" : props.dateKey ?? "date";
1713
+ const countKey = usesDemo ? "count" : props.countKey ?? "count";
1714
+ const levelKey = usesDemo ? "level" : props.levelKey ?? "level";
1715
+ const weeks = props.weeks !== void 0 && Number.isInteger(props.weeks) && props.weeks > 0 ? props.weeks : DEFAULT_WEEKS;
1716
+ const { locale } = context;
1717
+ const numberFormat = props.numberFormat;
1718
+ const countName = accessorName(countKey, "count");
1719
+ const levelName = accessorName(levelKey, "level");
1720
+ const parsed = [];
1721
+ data.forEach((datum, index) => {
1722
+ const iso = String(read(dateKey, datum, index));
1723
+ const day = parseIsoDate(iso);
1724
+ if (Number.isNaN(day) || isoDate(day) !== iso) {
1725
+ warnValue(CHART7, accessorName(dateKey, "date"), `Row ${index} (${iso}) is not a calendar date in ISO form; it is omitted.`);
1726
+ return;
1727
+ }
1728
+ const raw = finite(read(levelKey, datum, index));
1729
+ const level = raw === void 0 ? 0 : toneLevel(raw);
1730
+ if (raw !== level) warnValue(CHART7, levelName, `Row ${index} has level ${String(raw)}; it is drawn at level ${level}.`);
1731
+ parsed.push({ datum, day, count: finite(read(countKey, datum, index)), level });
1732
+ });
1733
+ let days = [...parsed].sort((a, b) => a.day - b.day);
1734
+ const unique = days.filter((d, i) => i === 0 || d.day !== days[i - 1]?.day);
1735
+ if (unique.length < days.length) {
1736
+ warnValue(CHART7, accessorName(dateKey, "date"), `${days.length - unique.length} rows repeat a date; the first row of each day is kept.`);
1737
+ days = unique;
1738
+ }
1739
+ const partial = days.length % 7;
1740
+ if (partial > 0) {
1741
+ warnValue(CHART7, "data", `${days.length} days are not whole weeks; the oldest ${partial} are omitted.`);
1742
+ days = days.slice(partial);
1743
+ }
1744
+ days = days.slice(-weeks * 7);
1745
+ if (days.some((d, i) => i > 0 && d.day !== (days[i - 1]?.day ?? 0) + 1)) {
1746
+ warnValue(CHART7, accessorName(dateKey, "date"), "The dates are not consecutive; cells are placed by position, one per row.");
1747
+ }
1748
+ const base = modelBase(CHART7, props, context, "Activity grid", {
1749
+ columns: [accessorName(dateKey, "date"), countName, levelName],
1750
+ rows: days.map((d) => [isoDate(d.day), formatValue(d.count, locale, numberFormat), String(d.level)])
1751
+ });
1752
+ const size = measure(base, props, context);
1753
+ if ("deferred" in size) return size.deferred;
1754
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
1755
+ const plot = inset(card.area, PLOT_INSET7);
1756
+ const strokes = [...card.strokes];
1757
+ const labels = [...card.labels];
1758
+ const hitAreas = [];
1759
+ if (days.length === 0) {
1760
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
1761
+ }
1762
+ const columns = days.length / 7;
1763
+ const slot = Math.min(plot.width / columns, (plot.height - MONTH_BAND) / 7);
1764
+ const top = plot.y + MONTH_BAND;
1765
+ const month = new Intl.DateTimeFormat(locale, { month: "short", timeZone: "UTC" });
1766
+ const months = [];
1767
+ days.forEach((d, index) => {
1768
+ const column = Math.floor(index / 7);
1769
+ const row = index % 7;
1770
+ if (row === 0) {
1771
+ const name = month.format(new Date(d.day * DAY_MS2));
1772
+ const last = months.at(-1);
1773
+ if (name !== last?.name) {
1774
+ if (last && column - last.column < MONTH_GAP) {
1775
+ if (months.length === 1) months[0] = { column, name };
1776
+ } else {
1777
+ months.push({ column, name });
1778
+ }
1779
+ }
1780
+ }
1781
+ const side = slot * CELL_FILL * CELL_SCALE2[d.level];
1782
+ const cx = plot.x + column * slot + slot / 2;
1783
+ const cy = top + row * slot + slot / 2;
1784
+ const cell = { x: cx - side / 2, y: cy - side / 2, width: side, height: side };
1785
+ strokes.push({ d: rectPath(cell), role: "encoding", part: "ink", ...d.level > 0 ? { tone: d.level } : {} });
1786
+ hitAreas.push({ seriesKey: countName, index, datum: d.datum, value: d.count ?? 0, x: cx, y: cy, box: cell, cell: { column, row } });
1787
+ });
1788
+ for (const { column, name } of months) {
1789
+ labels.push({ x: plot.x + column * slot, y: plot.y + 10, text: name, kind: "tick", part: "axis", anchor: "start" });
1790
+ }
1791
+ const total = days.reduce((sum, d) => sum + (d.count ?? 0), 0);
1792
+ const active = days.filter((d) => d.level > 0).length;
1793
+ const description = props.description ?? `${base.name}. Activity grid of ${columns} weeks, ${isoDate(days[0].day)} to ${isoDate(days[days.length - 1].day)}; ${formatNumber(total, locale, numberFormat)} in total, active on ${active} of ${days.length} days.`;
1794
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
1795
+ }
1796
+ var activityGrid = /* @__PURE__ */ Object.freeze({
1797
+ name: CHART7,
1798
+ build: buildActivityGrid
1799
+ });
1800
+
1801
+ // src/charts/step-chart/demo.ts
1802
+ var STEP_CHART_DEMO = /* @__PURE__ */ Object.freeze(
1803
+ /* @__PURE__ */ [
1804
+ { x: "Jan", value: 12 },
1805
+ { x: "Feb", value: 12 },
1806
+ { x: "Mar", value: 18 },
1807
+ { x: "Apr", value: 18 },
1808
+ { x: "May", value: 15 },
1809
+ { x: "Jun", value: 24 },
1810
+ { x: "Jul", value: 24 },
1811
+ { x: "Aug", value: 30 }
1812
+ ].map((row) => Object.freeze(row))
1813
+ );
1814
+
1815
+ // src/charts/step-chart/step-chart.ts
1816
+ var CHART8 = "StepChart";
1817
+ var HEIGHTEN_RADIUS2 = 3.5;
1818
+ var STEPS = {
1819
+ after: curveStepAfter,
1820
+ before: curveStepBefore,
1821
+ middle: curveStep
1822
+ };
1823
+ function buildStepChart(props, context) {
1824
+ const usesDemo = props.data === void 0;
1825
+ const data = props.data ?? STEP_CHART_DEMO;
1826
+ const xKey = usesDemo ? "x" : props.xKey ?? "x";
1827
+ const valueKey = usesDemo ? "value" : props.valueKey ?? "value";
1828
+ const { locale } = context;
1829
+ const numberFormat = props.numberFormat;
1830
+ const series = readSeries(data, valueKey, "value");
1831
+ checkVolume(CHART8, [series]);
1832
+ const xName = accessorName(xKey, "x");
1833
+ const xValues = data.map((datum, index) => read(xKey, datum, index));
1834
+ const xLabels = xValues.map((value) => formatCategory(value, locale));
1835
+ const base = modelBase(CHART8, props, context, "Step chart", {
1836
+ columns: [xName, series.key],
1837
+ rows: data.map((_, i) => [xLabels[i] ?? "", formatValue(series.points[i]?.value, locale, numberFormat)])
1838
+ });
1839
+ const size = measure(base, props, context);
1840
+ if ("deferred" in size) return size.deferred;
1841
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
1842
+ const plot = cartesianPlot(card.area);
1843
+ const strokes = [...card.strokes];
1844
+ const labels = [...card.labels];
1845
+ const hitAreas = [];
1846
+ if (series.missing > 0 && data.length > 0) warnValue(CHART8, series.key, `${series.missing} of ${data.length} values are not finite; the steps skip them.`);
1847
+ const range = extent(seriesValues([series]));
1848
+ if (data.length === 0 || range === void 0) {
1849
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
1850
+ }
1851
+ const axis = valueAxis(plot, range, { chart: CHART8, property: series.key, padding: context.domainPadding, locale, numberFormat });
1852
+ const y = axis.scale;
1853
+ strokes.push(...axis.strokes);
1854
+ labels.push(...axis.labels);
1855
+ const xAt = categoryAxis(xValues, plot, { chart: CHART8, property: xName, padding: context.domainPadding }).at;
1856
+ const d = line().defined((p) => p.value !== void 0).x((p) => xAt(p.index)).y((p) => y(p.value ?? 0)).curve(STEPS[props.step ?? "after"])(series.points);
1857
+ if (d) strokes.push({ d, role: "encoding", part: "ink" });
1858
+ for (const p of series.points) {
1859
+ if (p.value === void 0) continue;
1860
+ hitAreas.push({ seriesKey: series.key, index: p.index, datum: p.datum, value: p.value, x: xAt(p.index), y: y(p.value) });
1861
+ }
1862
+ const live = hitAreas.at(-1);
1863
+ if (live) {
1864
+ const mark = circlePath(live.x, live.y, HEIGHTEN_RADIUS2);
1865
+ strokes.push({ d: mark, role: "encoding", part: "heighten", paint: "fill" });
1866
+ strokes.push({ d: mark, role: "encoding", part: "ink", paint: "stroke" });
1867
+ }
1868
+ labels.push(...categoryLabels(xLabels, xAt, card.area, plot));
1869
+ const description = props.description ?? `${base.name}. Step chart of ${data.length} ${xName} values, stepping ${props.step ?? "after"} each point; ${series.key} ranges from ${formatNumber(range[0], locale, numberFormat)} to ${formatNumber(range[1], locale, numberFormat)}.`;
1870
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
1871
+ }
1872
+ var stepChart = /* @__PURE__ */ Object.freeze({ name: CHART8, build: buildStepChart });
1873
+ var CURVES2 = {
1874
+ monotone: curveMonotoneX,
1875
+ linear: curveLinear,
1876
+ natural: curveNatural,
1877
+ step: curveStepAfter
1878
+ };
1879
+
1880
+ // src/charts/sparkline-rows/demo.ts
1881
+ var SPARKLINE_ROWS_DEMO = /* @__PURE__ */ Object.freeze(
1882
+ /* @__PURE__ */ [
1883
+ { name: "API", readout: "99.9%", points: Object.freeze([98, 99, 99, 97, 99, 100, 99, 100]) },
1884
+ { name: "Queue", points: Object.freeze([12, 18, 15, 22, 19, 25, 21, 17]) },
1885
+ { name: "Cache", readout: "84%", points: Object.freeze([70, 72, 78, 75, 80, 83, 81, 84]) },
1886
+ { name: "Search", points: Object.freeze([240, 220, 260, 210, 190, 205, 180, 170]) }
1887
+ ].map((row) => Object.freeze(row))
1888
+ );
1889
+
1890
+ // src/charts/sparkline-rows/sparkline-rows.ts
1891
+ var CHART9 = "SparklineRows";
1892
+ var PLOT_INSET8 = 6;
1893
+ var NAME_COLUMN = 64;
1894
+ var READOUT_COLUMN = 52;
1895
+ var ROW_PAD = 4;
1896
+ function pointValue(point, index, pointKey) {
1897
+ if (pointKey !== void 0) return typeof point === "object" && point !== null ? finite(read(pointKey, point, index)) : void 0;
1898
+ if (typeof point === "number") return finite(point);
1899
+ return typeof point === "object" && point !== null ? finite(point.value) : void 0;
1900
+ }
1901
+ function buildSparklineRows(props, context) {
1902
+ const usesDemo = props.data === void 0;
1903
+ const all = props.data ?? SPARKLINE_ROWS_DEMO;
1904
+ const data = props.rows !== void 0 && Number.isInteger(props.rows) && props.rows >= 0 ? all.slice(0, props.rows) : all;
1905
+ if (props.rows !== void 0 && !(Number.isInteger(props.rows) && props.rows >= 0)) warnValue(CHART9, "rows", `${String(props.rows)} is not a whole number of rows \u2265 0; every row is shown.`);
1906
+ const nameKey = usesDemo ? "name" : props.nameKey ?? "name";
1907
+ const readoutKey = usesDemo ? "readout" : props.readoutKey ?? "readout";
1908
+ const seriesKey = usesDemo ? "points" : props.seriesKey ?? "points";
1909
+ const pointKey = usesDemo ? void 0 : props.pointKey;
1910
+ const { locale } = context;
1911
+ const numberFormat = props.numberFormat;
1912
+ const readoutName = accessorName(readoutKey, "readout");
1913
+ const rows = data.map((datum, index) => {
1914
+ const raw = read(seriesKey, datum, index);
1915
+ const points = Array.isArray(raw) ? raw.map((p, i) => pointValue(p, i, pointKey)) : [];
1916
+ const values = points.flatMap((v) => v === void 0 ? [] : [v]);
1917
+ const given = read(readoutKey, datum, index);
1918
+ const last = [...points].reverse().find((v) => v !== void 0);
1919
+ return {
1920
+ index,
1921
+ datum,
1922
+ name: formatCategory(read(nameKey, datum, index), locale),
1923
+ points,
1924
+ values,
1925
+ last,
1926
+ readout: given !== void 0 && given !== null ? formatValue(given, locale, numberFormat) : last === void 0 ? "\u2014" : formatNumber(last, locale, numberFormat)
1927
+ };
1928
+ });
1929
+ for (const row of rows) checkCount(CHART9, row.name, row.points.length);
1930
+ const base = modelBase(CHART9, props, context, "Sparkline rows", {
1931
+ columns: [accessorName(nameKey, "name"), readoutName],
1932
+ rows: rows.map((r) => [r.name, r.readout])
1933
+ });
1934
+ const size = measure(base, props, context);
1935
+ if ("deferred" in size) return size.deferred;
1936
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
1937
+ const plot = inset(card.area, PLOT_INSET8);
1938
+ const strokes = [...card.strokes];
1939
+ const labels = [...card.labels];
1940
+ const hitAreas = [];
1941
+ if (rows.length === 0 || rows.every((r) => r.values.length === 0)) {
1942
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, rows.length === 0);
1943
+ }
1944
+ const rowHeight = plot.height / rows.length;
1945
+ const longest = Math.max(...rows.map((r) => r.points.length));
1946
+ const lineX = plot.x + NAME_COLUMN;
1947
+ const lineWidth = Math.max(plot.width - NAME_COLUMN - READOUT_COLUMN, 1);
1948
+ const xAt = (i) => longest > 1 ? lineX + i / (longest - 1) * lineWidth : lineX + lineWidth / 2;
1949
+ for (const row of rows) {
1950
+ const band = { x: plot.x, y: plot.y + row.index * rowHeight, width: plot.width, height: rowHeight };
1951
+ const baseline = band.y + rowHeight / 2 + 4;
1952
+ labels.push({ x: plot.x, y: baseline, text: row.name, kind: "tick", part: "text", anchor: "start" });
1953
+ labels.push({ x: plot.x + plot.width, y: baseline, text: row.readout, kind: "tick", part: "text", anchor: "end" });
1954
+ if (row.values.length === 0) {
1955
+ warnValue(CHART9, accessorName(seriesKey, "points"), `Row ${row.index} (${row.name}) has no finite points; its sparkline is omitted.`);
1956
+ continue;
1957
+ }
1958
+ const low = Math.min(...row.values);
1959
+ const high = Math.max(...row.values);
1960
+ const top = band.y + ROW_PAD;
1961
+ const span = Math.max(rowHeight - 2 * ROW_PAD, 1);
1962
+ const yAt = (v) => high === low ? top + span / 2 : top + span - (v - low) / (high - low) * span;
1963
+ const indexed = row.points.map((value, i) => ({ value, i }));
1964
+ const d = line().defined((p) => p.value !== void 0).x((p) => xAt(p.i)).y((p) => yAt(p.value ?? 0)).curve(CURVES2.monotone)(indexed);
1965
+ if (d) strokes.push({ d, role: "encoding", part: "ink" });
1966
+ const lastIndex = row.points.lastIndexOf(row.last);
1967
+ hitAreas.push({ seriesKey: readoutName, index: row.index, datum: row.datum, value: row.last ?? 0, x: xAt(lastIndex), y: yAt(row.last ?? 0), box: band });
1968
+ }
1969
+ const description = props.description ?? `${base.name}. ${rows.length} rows: ${rows.map((r) => `${r.name} ${r.readout}`).join(", ")}.`;
1970
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
1971
+ }
1972
+ var sparklineRows = /* @__PURE__ */ Object.freeze({ name: CHART9, build: buildSparklineRows });
1973
+
1974
+ // src/charts/kpi-card/demo.ts
1975
+ var KPI_CARD_DEMO = /* @__PURE__ */ Object.freeze(
1976
+ /* @__PURE__ */ [118, 124, 121, 132, 140, 136, 129, 142, 151, 147, 155, 162, 158, 171].map((value) => Object.freeze({ value }))
1977
+ );
1978
+ var KPI_CARD_DEMO_PROPS = /* @__PURE__ */ Object.freeze({ metric: "orders per day", delta: 8.2 });
1979
+
1980
+ // src/charts/kpi-card/kpi-card.ts
1981
+ var CHART10 = "KpiCard";
1982
+ var PLOT_INSET9 = 6;
1983
+ var DELTA_BAND = 18;
1984
+ var MARK = 7;
1985
+ var CHAR = 5.6;
1986
+ function buildKpiCard(props, context) {
1987
+ const usesDemo = props.data === void 0;
1988
+ const data = props.data ?? KPI_CARD_DEMO;
1989
+ const valueKey = usesDemo ? "value" : props.valueKey ?? "value";
1990
+ const metric = usesDemo ? props.metric ?? KPI_CARD_DEMO_PROPS.metric : props.metric;
1991
+ const delta = usesDemo ? props.delta ?? KPI_CARD_DEMO_PROPS.delta : props.delta;
1992
+ const { locale } = context;
1993
+ const numberFormat = props.numberFormat;
1994
+ const series = readSeries(data, valueKey, "value");
1995
+ checkVolume(CHART10, [series]);
1996
+ const last = [...series.points].reverse().find((p) => p.value !== void 0)?.value;
1997
+ const base = modelBase(CHART10, props, context, metric ?? "KPI", {
1998
+ columns: ["point", series.key],
1999
+ rows: series.points.map((p) => [String(p.index + 1), formatValue(p.value, locale, numberFormat)])
2000
+ });
2001
+ const size = measure(base, props, context);
2002
+ if ("deferred" in size) return size.deferred;
2003
+ const card = cardLayout({ ...props, value: props.value ?? last, unit: props.unit ?? metric }, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
2004
+ const drawing = inset(card.area, PLOT_INSET9);
2005
+ const plot = { x: drawing.x, y: drawing.y + DELTA_BAND, width: drawing.width, height: Math.max(drawing.height - DELTA_BAND, 1) };
2006
+ const strokes = [...card.strokes];
2007
+ const labels = [...card.labels];
2008
+ const hitAreas = [];
2009
+ const deltaText = delta !== void 0 && Number.isFinite(delta) ? formatNumber(delta, locale, { ...numberFormat ?? { maximumFractionDigits: 2 }, signDisplay: "always" }) : void 0;
2010
+ if (delta !== void 0 && deltaText !== void 0) {
2011
+ const text = deltaText;
2012
+ const right = drawing.x + drawing.width;
2013
+ labels.push({ x: right, y: drawing.y + 11, text, kind: "tick", part: "text", anchor: "end" });
2014
+ const tone = props.deltaTone ?? (delta > 0 ? "up" : delta < 0 ? "down" : "flat");
2015
+ const x0 = right - text.length * CHAR - MARK - 6;
2016
+ const top2 = drawing.y + 3;
2017
+ const bottom2 = top2 + MARK;
2018
+ const d = tone === "up" ? `M${x0},${bottom2}L${x0 + MARK / 2},${top2}L${x0 + MARK},${bottom2}Z` : tone === "down" ? `M${x0},${top2}L${x0 + MARK / 2},${bottom2}L${x0 + MARK},${top2}Z` : `M${x0},${top2}L${x0 + MARK},${(top2 + bottom2) / 2}L${x0},${bottom2}Z`;
2019
+ strokes.push({ d, role: "encoding", part: "ink", paint: "fill" });
2020
+ }
2021
+ if (series.missing > 0 && data.length > 0) warnValue(CHART10, series.key, `${series.missing} of ${data.length} values are not finite; the sparkline breaks there.`);
2022
+ const range = extent(seriesValues([series]));
2023
+ if (data.length === 0 || range === void 0) {
2024
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
2025
+ }
2026
+ const bottom = plot.y + plot.height;
2027
+ const y = linearScale(range, [bottom, plot.y], { chart: CHART10, property: series.key, padding: context.domainPadding });
2028
+ const xAt = (i) => data.length > 1 ? plot.x + i / (data.length - 1) * plot.width : plot.x + plot.width / 2;
2029
+ const defined = (p) => p.value !== void 0;
2030
+ const shape = area().defined(defined).x((p) => xAt(p.index)).y0(bottom).y1((p) => y(p.value ?? 0)).curve(CURVES2.monotone)(series.points);
2031
+ if (shape) strokes.push({ d: shape, role: "encoding", part: "ink", paint: "stroke", tone: 1 });
2032
+ const top = line().defined(defined).x((p) => xAt(p.index)).y((p) => y(p.value ?? 0)).curve(CURVES2.monotone)(series.points);
2033
+ if (top) strokes.push({ d: top, role: "encoding", part: "ink" });
2034
+ for (const p of series.points) {
2035
+ if (p.value === void 0) continue;
2036
+ hitAreas.push({ seriesKey: series.key, index: p.index, datum: p.datum, value: p.value, x: xAt(p.index), y: y(p.value) });
2037
+ }
2038
+ const description = props.description ?? `${base.name}. ${metric ?? accessorName(valueKey, "value")} at ${formatValue(props.value ?? last, locale, numberFormat)}${deltaText !== void 0 ? `, change ${deltaText}` : ""}; ${data.length} points from ${formatNumber(range[0], locale, numberFormat)} to ${formatNumber(range[1], locale, numberFormat)}.`;
2039
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
2040
+ }
2041
+ var kpiCard = /* @__PURE__ */ Object.freeze({ name: CHART10, build: buildKpiCard });
2042
+
2043
+ // src/charts/bar-chart/demo.ts
2044
+ var BAR_CHART_DEMO = /* @__PURE__ */ Object.freeze(
2045
+ /* @__PURE__ */ [
2046
+ { x: "Q1", value: 42, previous: 35 },
2047
+ { x: "Q2", value: 58, previous: 44 },
2048
+ { x: "Q3", value: 51, previous: 49 },
2049
+ { x: "Q4", value: 73, previous: 60 }
2050
+ ].map((row) => Object.freeze(row))
2051
+ );
2052
+ var BAR_CHART_DEMO_KEYS = /* @__PURE__ */ Object.freeze({ xKey: "x", valueKey: "value", secondaryKey: "previous" });
2053
+
2054
+ // src/charts/bar-chart/bar-chart.ts
2055
+ var CHART11 = "BarChart";
2056
+ var ROW_LABELS = 56;
2057
+ var BAR_FILL = 0.7;
2058
+ function buildBarChart(props, context) {
2059
+ const usesDemo = props.data === void 0;
2060
+ const data = props.data ?? BAR_CHART_DEMO;
2061
+ const xKey = usesDemo ? BAR_CHART_DEMO_KEYS.xKey : props.xKey ?? "x";
2062
+ const valueKey = usesDemo ? BAR_CHART_DEMO_KEYS.valueKey : props.valueKey ?? "value";
2063
+ const secondaryKey = usesDemo ? BAR_CHART_DEMO_KEYS.secondaryKey : props.secondaryKey;
2064
+ const rows = props.orientation === "rows";
2065
+ const { locale } = context;
2066
+ const numberFormat = props.numberFormat;
2067
+ const series = [readSeries(data, valueKey, "value"), ...secondaryKey === void 0 ? [] : [readSeries(data, secondaryKey, "secondary")]];
2068
+ checkVolume(CHART11, series);
2069
+ const xName = accessorName(xKey, "x");
2070
+ const xValues = data.map((datum, index) => read(xKey, datum, index));
2071
+ const xLabels = xValues.map((value) => formatCategory(value, locale));
2072
+ const base = modelBase(CHART11, props, context, "Bar chart", {
2073
+ columns: [xName, ...series.map((s) => s.key)],
2074
+ rows: data.map((_, i) => [xLabels[i] ?? "", ...series.map((s) => formatValue(s.points[i]?.value, locale, numberFormat))])
2075
+ });
2076
+ const size = measure(base, props, context);
2077
+ if ("deferred" in size) return size.deferred;
2078
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
2079
+ const paired = series.length > 1;
2080
+ const key = paired ? legend(card.area, series.map((s, i) => i === 0 ? { name: s.key, part: "ink", tone: 2 } : { name: s.key, part: "ink-secondary", tone: 1, dash: "dotted" })) : { area: card.area, strokes: [], labels: [] };
2081
+ const area5 = key.area;
2082
+ const plot = rows ? { x: area5.x + ROW_LABELS, y: area5.y + PLOT_INSET, width: Math.max(area5.width - ROW_LABELS - PLOT_INSET, 1), height: Math.max(area5.height - AXIS_BAND - PLOT_INSET, 1) } : cartesianPlot(area5);
2083
+ const strokes = [...card.strokes, ...key.strokes];
2084
+ const labels = [...card.labels, ...key.labels];
2085
+ const hitAreas = [];
2086
+ for (const s of series) if (s.missing > 0 && data.length > 0) warnValue(CHART11, s.key, `${s.missing} of ${data.length} values are not finite; their bars are omitted.`);
2087
+ const range = extent(seriesValues(series));
2088
+ if (data.length === 0 || range === void 0) {
2089
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
2090
+ }
2091
+ const axisOptions = { chart: CHART11, property: series[0]?.key ?? "value", padding: context.domainPadding, locale, numberFormat };
2092
+ const axis = rows ? horizontalValueAxis(plot, area5, range, axisOptions) : valueAxis(plot, range, axisOptions);
2093
+ const v = axis.scale;
2094
+ strokes.push(...axis.strokes);
2095
+ labels.push(...axis.labels);
2096
+ const along2 = rows ? { x: plot.y, y: plot.x, width: plot.height, height: plot.width } : plot;
2097
+ const categories = categoryAxis(xValues, along2, { chart: CHART11, property: xName, padding: context.domainPadding, numeric: false, paddingInner: 0.3, paddingOuter: 0.15 });
2098
+ const thickness = categories.bandwidth * BAR_FILL / series.length;
2099
+ series.forEach((s, k) => {
2100
+ for (const p of s.points) {
2101
+ if (p.value === void 0) continue;
2102
+ const centre = categories.at(p.index) - categories.bandwidth * BAR_FILL / 2 + thickness * (k + 0.5);
2103
+ const from = v(Math.min(0, p.value));
2104
+ const to = v(Math.max(0, p.value));
2105
+ const bar = rows ? { x: from, y: centre - thickness / 2, width: to - from, height: thickness } : { x: centre - thickness / 2, y: to, width: thickness, height: from - to };
2106
+ strokes.push({ d: pillPath(bar), role: "encoding", part: k === 0 ? "ink" : "ink-secondary", tone: k === 0 ? 2 : 1, ...k === 0 ? {} : { dash: "dotted" } });
2107
+ const end = v(p.value);
2108
+ hitAreas.push({
2109
+ seriesKey: s.key,
2110
+ index: p.index,
2111
+ datum: p.datum,
2112
+ value: p.value,
2113
+ x: rows ? end : centre,
2114
+ y: rows ? centre : end,
2115
+ box: bar,
2116
+ ...paired ? { cell: rows ? { column: k, row: p.index } : { column: p.index, row: k } } : {}
2117
+ });
2118
+ }
2119
+ });
2120
+ if (rows) {
2121
+ xLabels.forEach((text, i) => labels.push({ x: area5.x + PLOT_INSET, y: categories.at(i) + 4, text, kind: "tick", part: "axis", anchor: "start" }));
2122
+ } else {
2123
+ labels.push(...categoryLabels(xLabels, categories.at, area5, plot));
2124
+ }
2125
+ const ranges = series.map((s) => {
2126
+ const r = extent(seriesValues([s]));
2127
+ return r ? `${s.key} from ${formatNumber(r[0], locale, numberFormat)} to ${formatNumber(r[1], locale, numberFormat)}` : `${s.key} has no values`;
2128
+ });
2129
+ const description = props.description ?? `${base.name}. Bar chart in ${rows ? "rows" : "columns"} of ${data.length} ${xName} values; ${ranges.join("; ")}.`;
2130
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
2131
+ }
2132
+ var barChart = /* @__PURE__ */ Object.freeze({ name: CHART11, build: buildBarChart });
2133
+
2134
+ // src/charts/stacked-bar-chart/demo.ts
2135
+ var STACKED_BAR_CHART_DEMO = /* @__PURE__ */ Object.freeze(
2136
+ /* @__PURE__ */ [
2137
+ { x: "Jan", free: 40, pro: 18, team: 6 },
2138
+ { x: "Feb", free: 46, pro: 21, team: 9 },
2139
+ { x: "Mar", free: 38, pro: 26, team: 11 },
2140
+ { x: "Apr", free: 52, pro: 24, team: 14 },
2141
+ { x: "May", free: 49, pro: 31, team: 15 }
2142
+ ].map((row) => Object.freeze(row))
2143
+ );
2144
+ var STACKED_BAR_CHART_DEMO_KEYS = /* @__PURE__ */ Object.freeze(["free", "pro", "team"]);
2145
+
2146
+ // src/charts/stacked-bar-chart/stacked-bar-chart.ts
2147
+ var CHART12 = "StackedBarChart";
2148
+ var TONES = [1, 2, 3, 4];
2149
+ function buildStackedBarChart(props, context) {
2150
+ const usesDemo = props.data === void 0;
2151
+ const data = props.data ?? STACKED_BAR_CHART_DEMO;
2152
+ const xKey = usesDemo ? "x" : props.xKey ?? "x";
2153
+ const keys = usesDemo ? STACKED_BAR_CHART_DEMO_KEYS : props.keys ?? STACKED_BAR_CHART_DEMO_KEYS;
2154
+ const names = keys.map((k, i) => (usesDemo ? void 0 : props.names?.[i]) ?? k);
2155
+ const { locale } = context;
2156
+ const numberFormat = props.numberFormat;
2157
+ const series = keys.map((k, i) => ({ ...readSeries(data, k, k), key: names[i] ?? k }));
2158
+ checkVolume(CHART12, series);
2159
+ const xName = accessorName(xKey, "x");
2160
+ const xValues = data.map((datum, index) => read(xKey, datum, index));
2161
+ const xLabels = xValues.map((value) => formatCategory(value, locale));
2162
+ const base = modelBase(CHART12, props, context, "Stacked bar chart", {
2163
+ columns: [xName, ...names],
2164
+ rows: data.map((_, i) => [xLabels[i] ?? "", ...series.map((s) => formatValue(s.points[i]?.value, locale, numberFormat))])
2165
+ });
2166
+ const size = measure(base, props, context);
2167
+ if ("deferred" in size) return size.deferred;
2168
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
2169
+ const key = legend(card.area, names.map((name, k) => ({ name, part: "ink", tone: TONES[k % TONES.length] })));
2170
+ const plot = cartesianPlot(key.area);
2171
+ const strokes = [...card.strokes, ...key.strokes];
2172
+ const labels = [...card.labels, ...key.labels];
2173
+ const hitAreas = [];
2174
+ let missing = 0;
2175
+ let negative = 0;
2176
+ const totals = data.map(
2177
+ (_, i) => series.reduce((sum, s) => {
2178
+ const value = s.points[i]?.value;
2179
+ if (value === void 0) {
2180
+ missing += 1;
2181
+ return sum;
2182
+ }
2183
+ if (value < 0) negative += 1;
2184
+ return sum + Math.max(value, 0);
2185
+ }, 0)
2186
+ );
2187
+ if (missing > 0 && data.length > 0) warnValue(CHART12, keys[0] ?? "keys", `${missing} values are not finite; their segments are omitted.`);
2188
+ if (negative > 0) warnValue(CHART12, keys[0] ?? "keys", `${negative} negative values cannot stack; they are drawn at zero.`);
2189
+ if (data.length === 0 || keys.length === 0) {
2190
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
2191
+ }
2192
+ const axis = valueAxis(plot, [0, Math.max(0, ...totals)], { chart: CHART12, property: keys[0] ?? "value", padding: context.domainPadding, locale, numberFormat });
2193
+ const y = axis.scale;
2194
+ strokes.push(...axis.strokes);
2195
+ labels.push(...axis.labels);
2196
+ const categories = categoryAxis(xValues, plot, { chart: CHART12, property: xName, padding: context.domainPadding, numeric: false, paddingInner: 0.35, paddingOuter: 0.2 });
2197
+ const width = categories.bandwidth;
2198
+ data.forEach((_, i) => {
2199
+ let below = 0;
2200
+ series.forEach((s, k) => {
2201
+ const p = s.points[i];
2202
+ if (p?.value === void 0) return;
2203
+ const height = Math.max(p.value, 0);
2204
+ const segment = { x: categories.at(i) - width / 2, y: y(below + height), width, height: y(below) - y(below + height) };
2205
+ if (segment.height > 0) strokes.push({ d: rectPath(segment), role: "encoding", part: "ink", tone: TONES[k % TONES.length] });
2206
+ hitAreas.push({ seriesKey: s.key, index: i, datum: p.datum, value: p.value, x: categories.at(i), y: segment.y, box: segment, cell: { column: i, row: k } });
2207
+ below += height;
2208
+ });
2209
+ });
2210
+ labels.push(...categoryLabels(xLabels, categories.at, key.area, plot));
2211
+ const largest = Math.max(...totals);
2212
+ const description = props.description ?? `${base.name}. Stacked bars of ${keys.length} keys (${names.join(", ")}, bottom to top) over ${data.length} ${xName} values; the tallest stack totals ${formatNumber(largest, locale, numberFormat)}.`;
2213
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
2214
+ }
2215
+ var stackedBarChart = /* @__PURE__ */ Object.freeze({ name: CHART12, build: buildStackedBarChart });
2216
+
2217
+ // src/charts/composed-chart/demo.ts
2218
+ var COMPOSED_CHART_DEMO = /* @__PURE__ */ Object.freeze(
2219
+ /* @__PURE__ */ [
2220
+ { x: "Jan", revenue: 42, margin: 18 },
2221
+ { x: "Feb", revenue: 48, margin: 21 },
2222
+ { x: "Mar", revenue: 39, margin: 17 },
2223
+ { x: "Apr", revenue: 55, margin: 26 },
2224
+ { x: "May", revenue: 61, margin: 30 },
2225
+ { x: "Jun", revenue: 58, margin: 27 }
2226
+ ].map((row) => Object.freeze(row))
2227
+ );
2228
+ var COMPOSED_CHART_DEMO_KEYS = /* @__PURE__ */ Object.freeze({ xKey: "x", barKey: "revenue", lineKey: "margin" });
2229
+
2230
+ // src/charts/composed-chart/composed-chart.ts
2231
+ var CHART13 = "ComposedChart";
2232
+ var BAR_FILL2 = 0.6;
2233
+ function buildComposedChart(props, context) {
2234
+ const usesDemo = props.data === void 0;
2235
+ const data = props.data ?? COMPOSED_CHART_DEMO;
2236
+ const xKey = usesDemo ? COMPOSED_CHART_DEMO_KEYS.xKey : props.xKey ?? "x";
2237
+ const barKey = usesDemo ? COMPOSED_CHART_DEMO_KEYS.barKey : props.barKey ?? "bar";
2238
+ const lineKey = usesDemo ? COMPOSED_CHART_DEMO_KEYS.lineKey : props.lineKey ?? "line";
2239
+ const showLine = props.showLine ?? true;
2240
+ const { locale } = context;
2241
+ const numberFormat = props.numberFormat;
2242
+ const bars = readSeries(data, barKey, "bar");
2243
+ const trend = readSeries(data, lineKey, "line");
2244
+ const series = showLine ? [bars, trend] : [bars];
2245
+ checkVolume(CHART13, series);
2246
+ const xName = accessorName(xKey, "x");
2247
+ const xValues = data.map((datum, index) => read(xKey, datum, index));
2248
+ const xLabels = xValues.map((value) => formatCategory(value, locale));
2249
+ const base = modelBase(CHART13, props, context, "Composed chart", {
2250
+ columns: [xName, ...series.map((s) => s.key)],
2251
+ rows: data.map((_, i) => [xLabels[i] ?? "", ...series.map((s) => formatValue(s.points[i]?.value, locale, numberFormat))])
2252
+ });
2253
+ const size = measure(base, props, context);
2254
+ if ("deferred" in size) return size.deferred;
2255
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
2256
+ const key = legend(card.area, [
2257
+ { name: bars.key, part: "ink", tone: 2 },
2258
+ ...showLine ? [{ name: trend.key, part: "ink-secondary" }] : []
2259
+ ]);
2260
+ const plot = cartesianPlot(key.area);
2261
+ const strokes = [...card.strokes, ...key.strokes];
2262
+ const labels = [...card.labels, ...key.labels];
2263
+ const hitAreas = [];
2264
+ for (const s of series) if (s.missing > 0 && data.length > 0) warnValue(CHART13, s.key, `${s.missing} of ${data.length} values are not finite; they are omitted.`);
2265
+ const range = extent(seriesValues(series));
2266
+ if (data.length === 0 || range === void 0) {
2267
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
2268
+ }
2269
+ const axis = valueAxis(plot, range, { chart: CHART13, property: bars.key, padding: context.domainPadding, locale, numberFormat });
2270
+ const y = axis.scale;
2271
+ strokes.push(...axis.strokes);
2272
+ labels.push(...axis.labels);
2273
+ const categories = categoryAxis(xValues, plot, { chart: CHART13, property: xName, padding: context.domainPadding, numeric: false, paddingInner: 0.3, paddingOuter: 0.15 });
2274
+ const width = categories.bandwidth * BAR_FILL2;
2275
+ for (const p of bars.points) {
2276
+ if (p.value === void 0) continue;
2277
+ const top = y(Math.max(0, p.value));
2278
+ const bottom = y(Math.min(0, p.value));
2279
+ const bar = { x: categories.at(p.index) - width / 2, y: top, width, height: bottom - top };
2280
+ if (bar.height > 0) strokes.push({ d: rectPath(bar), role: "encoding", part: "ink", tone: 2 });
2281
+ hitAreas.push({ seriesKey: bars.key, index: p.index, datum: p.datum, value: p.value, x: categories.at(p.index), y: y(p.value), box: bar, cell: { column: p.index, row: 0 } });
2282
+ }
2283
+ if (showLine) {
2284
+ const d = line().defined((p) => p.value !== void 0).x((p) => categories.at(p.index)).y((p) => y(p.value ?? 0)).curve(CURVES2.monotone)(trend.points);
2285
+ if (d) strokes.push({ d, role: "encoding", part: "ink-secondary" });
2286
+ for (const p of trend.points) {
2287
+ if (p.value === void 0) continue;
2288
+ hitAreas.push({ seriesKey: trend.key, index: p.index, datum: p.datum, value: p.value, x: categories.at(p.index), y: y(p.value), cell: { column: p.index, row: 1 } });
2289
+ }
2290
+ }
2291
+ labels.push(...categoryLabels(xLabels, categories.at, key.area, plot));
2292
+ const description = props.description ?? `${base.name}. Columns of ${bars.key}${showLine ? ` with a line of ${trend.key}` : ""} over ${data.length} ${xName} values, on one scale from ${formatNumber(range[0], locale, numberFormat)} to ${formatNumber(range[1], locale, numberFormat)}.`;
2293
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
2294
+ }
2295
+ var composedChart = /* @__PURE__ */ Object.freeze({ name: CHART13, build: buildComposedChart });
2296
+
2297
+ // src/charts/waterfall-chart/demo.ts
2298
+ var WATERFALL_CHART_DEMO = /* @__PURE__ */ Object.freeze(
2299
+ /* @__PURE__ */ [
2300
+ { step: "Opening", base: 120 },
2301
+ { step: "Sales", delta: 64 },
2302
+ { step: "Services", delta: 22 },
2303
+ { step: "Payroll", delta: -58 },
2304
+ { step: "Rent", delta: -24 },
2305
+ { step: "Closing", base: 124 }
2306
+ ].map((row) => Object.freeze(row))
2307
+ );
2308
+
2309
+ // src/charts/waterfall-chart/waterfall-chart.ts
2310
+ var CHART14 = "WaterfallChart";
2311
+ var BAR_FILL3 = 0.62;
2312
+ function buildWaterfallChart(props, context) {
2313
+ const usesDemo = props.data === void 0;
2314
+ const data = props.data ?? WATERFALL_CHART_DEMO;
2315
+ const stepKey = usesDemo ? "step" : props.stepKey ?? "step";
2316
+ const baseKey = usesDemo ? "base" : props.baseKey ?? "base";
2317
+ const deltaKey = usesDemo ? "delta" : props.deltaKey ?? "delta";
2318
+ const { locale } = context;
2319
+ const numberFormat = props.numberFormat;
2320
+ const baseName = accessorName(baseKey, "base");
2321
+ const deltaName = accessorName(deltaKey, "delta");
2322
+ const steps = [];
2323
+ let running = 0;
2324
+ let skipped = 0;
2325
+ data.forEach((datum, index) => {
2326
+ const total = finite(read(baseKey, datum, index));
2327
+ const delta = finite(read(deltaKey, datum, index));
2328
+ if (total !== void 0) {
2329
+ if (delta !== void 0) warnValue(CHART14, deltaName, `Row ${index} has both a ${baseName} and a ${deltaName}; a total resets the running total, so its ${deltaName} is ignored.`);
2330
+ steps.push({ index, datum, kind: "total", value: total, from: 0, to: total });
2331
+ running = total;
2332
+ } else if (delta !== void 0) {
2333
+ steps.push({ index, datum, kind: "delta", value: delta, from: running, to: running + delta });
2334
+ running += delta;
2335
+ } else {
2336
+ skipped += 1;
2337
+ }
2338
+ });
2339
+ checkCount(CHART14, deltaName, data.length);
2340
+ const stepName = accessorName(stepKey, "step");
2341
+ const stepLabels = data.map((datum, index) => formatCategory(read(stepKey, datum, index), locale));
2342
+ const signed = (value) => formatNumber(value, locale, { ...numberFormat ?? { maximumFractionDigits: 2 }, signDisplay: "always" });
2343
+ const base = modelBase(CHART14, props, context, "Waterfall chart", {
2344
+ columns: [stepName, baseName, deltaName],
2345
+ rows: data.map((datum, index) => [
2346
+ stepLabels[index] ?? "",
2347
+ formatValue(finite(read(baseKey, datum, index)), locale, numberFormat),
2348
+ formatValue(finite(read(deltaKey, datum, index)), locale, numberFormat)
2349
+ ])
2350
+ });
2351
+ const size = measure(base, props, context);
2352
+ if ("deferred" in size) return size.deferred;
2353
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
2354
+ const plot = cartesianPlot(card.area);
2355
+ const strokes = [...card.strokes];
2356
+ const labels = [...card.labels];
2357
+ const hitAreas = [];
2358
+ if (skipped > 0 && data.length > 0) warnValue(CHART14, deltaName, `${skipped} rows have neither a finite ${baseName} nor a finite ${deltaName}; they are omitted.`);
2359
+ if (steps.length === 0) {
2360
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
2361
+ }
2362
+ const levels = steps.flatMap((s) => [s.from, s.to]);
2363
+ const axis = valueAxis(plot, [Math.min(...levels), Math.max(...levels)], { chart: CHART14, property: deltaName, padding: context.domainPadding, locale, numberFormat });
2364
+ const y = axis.scale;
2365
+ strokes.push(...axis.strokes);
2366
+ labels.push(...axis.labels);
2367
+ const categories = categoryAxis(stepLabels, plot, { chart: CHART14, property: stepName, padding: context.domainPadding, numeric: false, paddingInner: 0.3, paddingOuter: 0.15 });
2368
+ const width = categories.bandwidth * BAR_FILL3;
2369
+ steps.forEach((step, n) => {
2370
+ const top = y(Math.max(step.from, step.to));
2371
+ const bar = { x: categories.at(step.index) - width / 2, y: top, width, height: y(Math.min(step.from, step.to)) - top };
2372
+ const tone = step.kind === "total" ? 3 : step.value >= 0 ? 1 : 2;
2373
+ if (bar.height > 0) strokes.push({ d: rectPath(bar), role: "encoding", part: "ink", tone });
2374
+ const next = steps[n + 1];
2375
+ if (next?.kind === "delta") {
2376
+ strokes.push({ d: `M${bar.x + width},${y(step.to)}H${categories.at(next.index) - width / 2}`, role: "ornament", part: "grid", dash: "dotted" });
2377
+ }
2378
+ labels.push({
2379
+ x: categories.at(step.index),
2380
+ y: top - 3,
2381
+ text: step.kind === "total" ? formatNumber(step.value, locale, numberFormat) : signed(step.value),
2382
+ kind: "tick",
2383
+ part: "text",
2384
+ anchor: "middle"
2385
+ });
2386
+ hitAreas.push({
2387
+ seriesKey: step.kind === "total" ? baseName : deltaName,
2388
+ index: step.index,
2389
+ datum: step.datum,
2390
+ value: step.value,
2391
+ x: categories.at(step.index),
2392
+ y: y(step.to),
2393
+ box: bar,
2394
+ cell: { column: n, row: 0 }
2395
+ });
2396
+ });
2397
+ labels.push(...categoryLabels(stepLabels, categories.at, card.area, plot));
2398
+ const description = props.description ?? `${base.name}. Waterfall of ${steps.length} steps, ending at ${formatNumber(running, locale, numberFormat)}.`;
2399
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
2400
+ }
2401
+ var waterfallChart = /* @__PURE__ */ Object.freeze({ name: CHART14, build: buildWaterfallChart });
2402
+
2403
+ // src/charts/funnel-chart/demo.ts
2404
+ var FUNNEL_CHART_DEMO = /* @__PURE__ */ Object.freeze(
2405
+ /* @__PURE__ */ [
2406
+ { stage: "Visits", value: 12400 },
2407
+ { stage: "Product", value: 6800 },
2408
+ { stage: "Cart", value: 2900 },
2409
+ { stage: "Checkout", value: 1500 },
2410
+ { stage: "Paid", value: 980 }
2411
+ ].map((row) => Object.freeze(row))
2412
+ );
2413
+
2414
+ // src/charts/funnel-chart/funnel-chart.ts
2415
+ var CHART15 = "FunnelChart";
2416
+ var PLOT_INSET10 = 6;
2417
+ var LABEL_COLUMN3 = 64;
2418
+ var VALUE_COLUMN2 = 64;
2419
+ var STAGE_GAP = 3;
2420
+ function buildFunnelChart(props, context) {
2421
+ const usesDemo = props.data === void 0;
2422
+ const data = props.data ?? FUNNEL_CHART_DEMO;
2423
+ const stageKey = usesDemo ? "stage" : props.stageKey ?? "stage";
2424
+ const valueKey = usesDemo ? "value" : props.valueKey ?? "value";
2425
+ const { locale } = context;
2426
+ const numberFormat = props.numberFormat;
2427
+ const valueName = accessorName(valueKey, "value");
2428
+ checkCount(CHART15, valueName, data.length);
2429
+ const stages = data.map((datum, index) => ({
2430
+ index,
2431
+ datum,
2432
+ stage: formatCategory(read(stageKey, datum, index), locale),
2433
+ value: finite(read(valueKey, datum, index))
2434
+ }));
2435
+ const entry = stages.find((s) => s.value !== void 0 && s.value > 0)?.value;
2436
+ const share = (value) => entry ? `${formatNumber(value / entry * 100, locale, { maximumFractionDigits: 0 })}%` : "\u2014";
2437
+ const base = modelBase(CHART15, props, context, "Funnel chart", {
2438
+ columns: [accessorName(stageKey, "stage"), valueName, "share"],
2439
+ rows: stages.map((s) => [s.stage, formatValue(s.value, locale, numberFormat), s.value === void 0 ? "\u2014" : share(Math.max(s.value, 0))])
2440
+ });
2441
+ const size = measure(base, props, context);
2442
+ if ("deferred" in size) return size.deferred;
2443
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
2444
+ const plot = inset(card.area, PLOT_INSET10);
2445
+ const strokes = [...card.strokes];
2446
+ const labels = [...card.labels];
2447
+ const hitAreas = [];
2448
+ const largest = Math.max(0, ...stages.map((s) => s.value ?? 0));
2449
+ if (stages.length > 0 && largest <= 0) warnValue(CHART15, valueName, `No stage has a ${valueName} above zero; there is no funnel to draw.`);
2450
+ if (stages.length === 0 || largest <= 0) {
2451
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, stages.length === 0);
2452
+ }
2453
+ const track = { x: plot.x + LABEL_COLUMN3, width: Math.max(plot.width - LABEL_COLUMN3 - VALUE_COLUMN2, 1) };
2454
+ const centre = track.x + track.width / 2;
2455
+ const rowHeight = plot.height / stages.length;
2456
+ const right = plot.x + plot.width;
2457
+ for (const s of stages) {
2458
+ const top = plot.y + s.index * rowHeight;
2459
+ const baseline = top + rowHeight / 2 + 4;
2460
+ labels.push({ x: plot.x, y: baseline, text: s.stage, kind: "tick", part: "text", anchor: "start" });
2461
+ if (s.value === void 0) {
2462
+ warnValue(CHART15, valueName, `Stage ${s.index} has no finite value; it is omitted.`);
2463
+ continue;
2464
+ }
2465
+ const value = Math.max(s.value, 0);
2466
+ if (value !== s.value) warnValue(CHART15, valueName, `Stage ${s.index} is ${s.value}; a funnel stage cannot be negative, so it is drawn at zero.`);
2467
+ const width = value / largest * track.width;
2468
+ const stage = { x: centre - width / 2, y: top + STAGE_GAP / 2, width, height: Math.max(rowHeight - STAGE_GAP, 0) };
2469
+ const tone = toneOf(value / largest);
2470
+ if (width > 0) strokes.push({ d: rectPath(stage), role: "encoding", part: "ink", ...tone > 0 ? { tone } : {} });
2471
+ labels.push({ x: right, y: baseline - 6, text: formatNumber(s.value, locale, numberFormat), kind: "tick", part: "text", anchor: "end" });
2472
+ labels.push({ x: right, y: baseline + 5, text: share(value), kind: "tick", part: "axis", anchor: "end" });
2473
+ hitAreas.push({ seriesKey: valueName, index: s.index, datum: s.datum, value: s.value, x: centre, y: stage.y + stage.height / 2, box: stage });
2474
+ }
2475
+ const last = [...stages].reverse().find((s) => s.value !== void 0);
2476
+ const description = props.description ?? `${base.name}. Funnel of ${stages.length} stages, from ${stages[0]?.stage} to ${last?.stage}; ${last?.value === void 0 ? "" : `${share(Math.max(last.value, 0))} of the entry reach the last stage`}.`;
2477
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
2478
+ }
2479
+ var funnelChart = /* @__PURE__ */ Object.freeze({ name: CHART15, build: buildFunnelChart });
2480
+
2481
+ // src/charts/shared/polar.ts
2482
+ function polarFrame(area5, labelRoom = 0) {
2483
+ return {
2484
+ cx: area5.x + area5.width / 2,
2485
+ cy: area5.y + area5.height / 2,
2486
+ radius: Math.max(Math.min(area5.width, area5.height) / 2 - PLOT_INSET - labelRoom, 1)
2487
+ };
2488
+ }
2489
+ var RIM_LABELS = 14;
2490
+ function rimLabels(frame, entries, most = 24) {
2491
+ const step = Math.max(1, Math.ceil(entries.length / most));
2492
+ return entries.filter((_, i) => i % step === 0).map((e) => polarLabel(frame, e.angle, frame.radius + 8, e.text));
2493
+ }
2494
+ function pointAt(frame, angle, r) {
2495
+ return { x: frame.cx + r * Math.sin(angle), y: frame.cy - r * Math.cos(angle) };
2496
+ }
2497
+ var EPSILON = 1e-9;
2498
+ var at = (frame, angle, r) => {
2499
+ const p = pointAt(frame, angle, r);
2500
+ return `${p.x},${p.y}`;
2501
+ };
2502
+ function arcTo(frame, r, end, clockwise) {
2503
+ return `A${r},${r},0,0,${clockwise ? 1 : 0},${at(frame, end, r)}`;
2504
+ }
2505
+ function sweep(frame, r, start, end, clockwise) {
2506
+ if (Math.abs(end - start) <= Math.PI) return arcTo(frame, r, end, clockwise);
2507
+ const middle = (start + end) / 2;
2508
+ return arcTo(frame, r, middle, clockwise) + arcTo(frame, r, end, clockwise);
2509
+ }
2510
+ function arcPath(frame, r, start, end) {
2511
+ if (Math.abs(end - start) < EPSILON || r <= 0) return "";
2512
+ return `M${at(frame, start, r)}${sweep(frame, r, start, end, end > start)}`;
2513
+ }
2514
+ function sectorPath(frame, shape) {
2515
+ const { inner, outer, start, end } = shape;
2516
+ if (Math.abs(end - start) < EPSILON || outer <= 0) return "";
2517
+ const clockwise = end > start;
2518
+ const outerArc = `M${at(frame, start, outer)}${sweep(frame, outer, start, end, clockwise)}`;
2519
+ if (inner <= 0) return `${outerArc}L${frame.cx},${frame.cy}Z`;
2520
+ return `${outerArc}L${at(frame, end, inner)}${sweep(frame, inner, end, start, !clockwise)}Z`;
2521
+ }
2522
+ var POLE = 0.1;
2523
+ function polarLabel(frame, angle, r, text, kind = "tick") {
2524
+ const p = pointAt(frame, angle, r);
2525
+ const side = Math.sin(angle);
2526
+ const anchor = side > POLE ? "start" : side < -POLE ? "end" : "middle";
2527
+ return { x: p.x, y: p.y + 3, text, kind, part: "axis", anchor };
2528
+ }
2529
+ function readSectors(data, nameKey, valueKey, chart, locale) {
2530
+ const valueName = accessorName(valueKey, "value");
2531
+ const seen = /* @__PURE__ */ new Map();
2532
+ const sectors = [];
2533
+ data.forEach((datum, index) => {
2534
+ const value = finite(read(valueKey, datum, index));
2535
+ if (value === void 0 || value < 0) {
2536
+ warnValue(chart, valueName, `Row ${index} has a ${valueName} of ${String(read(valueKey, datum, index))}; a sector needs a finite value \u2265 0, so it is omitted.`);
2537
+ return;
2538
+ }
2539
+ const given = read(nameKey, datum, index);
2540
+ if (given === void 0 || given === null || given === "") warnValue(chart, accessorName(nameKey, "name"), `Row ${index} has no name; it is shown as "\u2014".`);
2541
+ const raw = formatCategory(given, locale);
2542
+ const count = (seen.get(raw) ?? 0) + 1;
2543
+ seen.set(raw, count);
2544
+ if (count > 1) warnValue(chart, accessorName(nameKey, "name"), `The name "${raw}" repeats; row ${index} is shown as "${raw} (${count})".`);
2545
+ sectors.push({ index, datum, name: count > 1 ? `${raw} (${count})` : raw, value });
2546
+ });
2547
+ return sectors;
2548
+ }
2549
+ var SECTORS_PER_CHART = 60;
2550
+ function checkSectors(chart, property, count) {
2551
+ if (process.env.NODE_ENV === "production" || count <= SECTORS_PER_CHART) return;
2552
+ diagnose("SP008", chart, { property, message: `${count} sectors; the ceiling is ${SECTORS_PER_CHART}.` });
2553
+ }
2554
+ var RING_TONES = [1, 2, 3, 4];
2555
+ function ringTone(index, count) {
2556
+ const tone = RING_TONES[index % RING_TONES.length];
2557
+ if (count > 1 && index === count - 1 && tone === RING_TONES[0]) return RING_TONES[2];
2558
+ return tone;
2559
+ }
2560
+ function ringTones(values) {
2561
+ const drawn = values.filter((v) => v > 0).length;
2562
+ let k = 0;
2563
+ return values.map((v) => v > 0 ? ringTone(k++, drawn) : ringTone(Math.min(k, Math.max(drawn - 1, 0)), drawn));
2564
+ }
2565
+ var LEGEND_ROW = 14;
2566
+ function sectorLegend(area5, entries) {
2567
+ const strokes = [];
2568
+ const labels = [];
2569
+ const fits2 = Math.max(Math.floor(area5.height / LEGEND_ROW), 1);
2570
+ const shown = entries.length > fits2 ? fits2 - 1 : entries.length;
2571
+ const top = area5.y + (area5.height - Math.min(entries.length, fits2) * LEGEND_ROW) / 2;
2572
+ entries.slice(0, shown).forEach((entry, row) => {
2573
+ const y = top + row * LEGEND_ROW;
2574
+ strokes.push({ d: rectPath({ x: area5.x, y: y + 3, width: 9, height: 7 }), role: "ornament", part: "ink", tone: entry.tone });
2575
+ labels.push({ x: area5.x + 14, y: y + 10, text: entry.name, kind: "tick", part: "text", anchor: "start" });
2576
+ labels.push({ x: area5.x + area5.width, y: y + 10, text: entry.share, kind: "tick", part: "axis", anchor: "end" });
2577
+ });
2578
+ if (shown < entries.length) {
2579
+ labels.push({ x: area5.x + 14, y: top + shown * LEGEND_ROW + 10, text: `+${entries.length - shown} more`, kind: "tick", part: "axis", anchor: "start" });
2580
+ }
2581
+ return { strokes, labels };
2582
+ }
2583
+
2584
+ // src/charts/donut-chart/demo.ts
2585
+ var DONUT_CHART_DEMO = /* @__PURE__ */ Object.freeze(
2586
+ /* @__PURE__ */ [
2587
+ { name: "Rent", value: 38 },
2588
+ { name: "Food", value: 22 },
2589
+ { name: "Savings", value: 15 },
2590
+ { name: "Transport", value: 14 },
2591
+ { name: "Leisure", value: 11 }
2592
+ ].map((row) => Object.freeze(row))
2593
+ );
2594
+
2595
+ // src/charts/donut-chart/donut-chart.ts
2596
+ var CHART16 = "DonutChart";
2597
+ var HOLE = 0.62;
2598
+ var LEGEND_ASPECT = 1.5;
2599
+ var LEGEND_GAP = 14;
2600
+ function buildDonutChart(props, context) {
2601
+ const usesDemo = props.data === void 0;
2602
+ const data = props.data ?? DONUT_CHART_DEMO;
2603
+ const nameKey = usesDemo ? "name" : props.nameKey ?? "name";
2604
+ const valueKey = usesDemo ? "value" : props.valueKey ?? "value";
2605
+ const { locale } = context;
2606
+ const numberFormat = props.numberFormat;
2607
+ const valueName = accessorName(valueKey, "value");
2608
+ checkSectors(CHART16, valueName, data.length);
2609
+ const sectors = readSectors(data, nameKey, valueKey, CHART16, locale);
2610
+ const total = sectors.reduce((sum, s) => sum + s.value, 0);
2611
+ const share = (value) => total > 0 ? `${formatNumber(value / total * 100, locale, { maximumFractionDigits: 0 })}%` : "\u2014";
2612
+ const byRow = new Map(sectors.map((s) => [s.index, s]));
2613
+ const base = modelBase(CHART16, props, context, "Donut chart", {
2614
+ columns: [accessorName(nameKey, "name"), valueName, "share"],
2615
+ rows: data.map((_, index) => {
2616
+ const s = byRow.get(index);
2617
+ return s ? [s.name, formatValue(s.value, locale, numberFormat), share(s.value)] : ["\u2014", "\u2014", "\u2014"];
2618
+ })
2619
+ });
2620
+ const size = measure(base, props, context);
2621
+ if ("deferred" in size) return size.deferred;
2622
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
2623
+ const plot = card.area;
2624
+ const strokes = [...card.strokes];
2625
+ const labels = [...card.labels];
2626
+ const hitAreas = [];
2627
+ if (sectors.length > 0 && total <= 0) warnValue(CHART16, valueName, "Every value is zero; a donut has nothing to divide.");
2628
+ if (sectors.length === 0 || total <= 0) {
2629
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
2630
+ }
2631
+ const withLegend = (props.legend ?? true) && plot.width >= plot.height * LEGEND_ASPECT;
2632
+ const ringArea = withLegend ? { x: plot.x, y: plot.y, width: plot.height, height: plot.height } : plot;
2633
+ const frame = polarFrame(ringArea);
2634
+ const inner = frame.radius * HOLE;
2635
+ const tones = ringTones(sectors.map((s) => s.value));
2636
+ let start = 0;
2637
+ sectors.forEach((s, k) => {
2638
+ const end = start + s.value / total * 2 * Math.PI;
2639
+ const tone = tones[k];
2640
+ const d = sectorPath(frame, { inner, outer: frame.radius, start, end });
2641
+ if (d) strokes.push({ d, role: "encoding", part: "ink", tone });
2642
+ const middle = pointAt(frame, (start + end) / 2, (inner + frame.radius) / 2);
2643
+ hitAreas.push({ seriesKey: valueName, index: s.index, datum: s.datum, value: s.value, x: middle.x, y: middle.y });
2644
+ start = end;
2645
+ });
2646
+ const centre = props.centerValue ?? formatNumber(total, locale, numberFormat);
2647
+ const hasLabel = props.centerLabel !== void 0 && props.centerLabel !== "";
2648
+ labels.push({ x: frame.cx, y: frame.cy + (hasLabel ? 2 : 6), text: typeof centre === "number" ? formatNumber(centre, locale, numberFormat) : centre, kind: "value", part: "text", anchor: "middle" });
2649
+ if (hasLabel) labels.push({ x: frame.cx, y: frame.cy + 16, text: props.centerLabel, kind: "tick", part: "axis", anchor: "middle" });
2650
+ if (withLegend) {
2651
+ const x = ringArea.x + ringArea.width + LEGEND_GAP;
2652
+ const key = sectorLegend(
2653
+ { x, y: plot.y, width: Math.max(plot.x + plot.width - x, 1), height: plot.height },
2654
+ sectors.map((s, k) => ({ name: s.name, share: share(s.value), tone: tones[k] }))
2655
+ );
2656
+ strokes.push(...key.strokes);
2657
+ labels.push(...key.labels);
2658
+ }
2659
+ const largest = sectors.reduce((a, b) => b.value > a.value ? b : a);
2660
+ const description = props.description ?? `${base.name}. Donut of ${sectors.length} sectors totalling ${formatNumber(total, locale, numberFormat)}; the largest is ${largest.name} at ${share(largest.value)}.`;
2661
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
2662
+ }
2663
+ var donutChart = /* @__PURE__ */ Object.freeze({ name: "DonutChart", build: buildDonutChart });
2664
+
2665
+ // src/charts/radar-chart/demo.ts
2666
+ var RADAR_CHART_DEMO = /* @__PURE__ */ Object.freeze(
2667
+ /* @__PURE__ */ [
2668
+ { subject: "Speed", value: 8 },
2669
+ { subject: "Range", value: 6 },
2670
+ { subject: "Comfort", value: 7 },
2671
+ { subject: "Safety", value: 9 },
2672
+ { subject: "Cost", value: 4 },
2673
+ { subject: "Style", value: 6 }
2674
+ ].map((row) => Object.freeze(row))
2675
+ );
2676
+
2677
+ // src/charts/radar-chart/radar-chart.ts
2678
+ var CHART17 = "RadarChart";
2679
+ var validDomain = (d) => !!d && Number.isFinite(d[0]) && Number.isFinite(d[1]) && d[0] < d[1];
2680
+ function buildRadarChart(props, context) {
2681
+ const usesDemo = props.data === void 0;
2682
+ const data = props.data ?? RADAR_CHART_DEMO;
2683
+ const subjectKey = usesDemo ? "subject" : props.subjectKey ?? "subject";
2684
+ const valueKey = usesDemo ? "value" : props.valueKey ?? "value";
2685
+ const { locale } = context;
2686
+ const numberFormat = props.numberFormat;
2687
+ const valueName = accessorName(valueKey, "value");
2688
+ checkSectors(CHART17, valueName, data.length);
2689
+ const subjects = readSectors(data, subjectKey, valueKey, CHART17, locale);
2690
+ const byRow = new Map(subjects.map((s) => [s.index, s]));
2691
+ const base = modelBase(CHART17, props, context, "Radar chart", {
2692
+ columns: [accessorName(subjectKey, "subject"), valueName],
2693
+ rows: data.map((_, index) => {
2694
+ const s = byRow.get(index);
2695
+ return s ? [s.name, formatValue(s.value, locale, numberFormat)] : ["\u2014", "\u2014"];
2696
+ })
2697
+ });
2698
+ const size = measure(base, props, context);
2699
+ if ("deferred" in size) return size.deferred;
2700
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
2701
+ const plot = card.area;
2702
+ const strokes = [...card.strokes];
2703
+ const labels = [...card.labels];
2704
+ const hitAreas = [];
2705
+ const pinned = validDomain(props.domain) ? props.domain : void 0;
2706
+ if (subjects.length > 0 && subjects.length < 3) warnValue(CHART17, accessorName(subjectKey, "subject"), `${subjects.length} subjects cannot close a polygon; a radar needs at least three.`);
2707
+ const flat = !pinned && subjects.length > 0 && subjects.every((s) => s.value === 0);
2708
+ if (flat) warnValue(CHART17, valueName, `Every ${valueName} is zero and no \`domain\` is given; there is no scale to draw on.`);
2709
+ if (subjects.length === 0 || flat) return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
2710
+ const frame = polarFrame(plot, RIM_LABELS);
2711
+ const radius = linearScale(pinned ?? [0, Math.max(...subjects.map((s) => s.value))], [0, frame.radius], {
2712
+ chart: CHART17,
2713
+ property: "domain",
2714
+ padding: context.domainPadding,
2715
+ nice: !pinned
2716
+ });
2717
+ const [low, high] = radius.domain;
2718
+ const slot = 2 * Math.PI / subjects.length;
2719
+ const angleOf = (k) => k * slot;
2720
+ const ring = (r) => subjects.map((_, k) => pointAt(frame, angleOf(k), r)).map((p, k) => `${k === 0 ? "M" : "L"}${p.x},${p.y}`).join("") + "Z";
2721
+ for (const tick of radius.ticks(4)) {
2722
+ const r = radius(tick);
2723
+ if (r <= 0) continue;
2724
+ strokes.push({ d: ring(r), role: "ornament", part: "grid" });
2725
+ if (r > frame.radius - RIM_LABELS) continue;
2726
+ labels.push({ x: frame.cx + 3, y: frame.cy - r - 2, text: formatNumber(tick, locale, numberFormat), kind: "tick", part: "axis", anchor: "start" });
2727
+ }
2728
+ subjects.forEach((_, k) => {
2729
+ const end = pointAt(frame, angleOf(k), frame.radius);
2730
+ strokes.push({ d: `M${frame.cx},${frame.cy}L${end.x},${end.y}`, role: "ornament", part: "grid" });
2731
+ });
2732
+ let outside = 0;
2733
+ const vertices = subjects.map((s, k) => {
2734
+ const held = Math.min(Math.max(s.value, low), high);
2735
+ if (held !== s.value) outside += 1;
2736
+ return { s, point: pointAt(frame, angleOf(k), radius(held)) };
2737
+ });
2738
+ if (outside > 0) warnValue(CHART17, valueName, `${outside} values fall outside the domain [${low}, ${high}]; they are held at its edge.`);
2739
+ strokes.push({ d: vertices.map((v, k) => `${k === 0 ? "M" : "L"}${v.point.x},${v.point.y}`).join("") + "Z", role: "encoding", part: "ink", tone: 1 });
2740
+ for (const v of vertices) hitAreas.push({ seriesKey: valueName, index: v.s.index, datum: v.s.datum, value: v.s.value, x: v.point.x, y: v.point.y });
2741
+ labels.push(...rimLabels(frame, subjects.map((s, k) => ({ angle: angleOf(k), text: s.name }))));
2742
+ const top = subjects.reduce((a, b) => b.value > a.value ? b : a);
2743
+ const bottom = subjects.reduce((a, b) => b.value < a.value ? b : a);
2744
+ const description = props.description ?? `${base.name}. Radar of ${subjects.length} subjects on a scale from ${formatNumber(low, locale, numberFormat)} to ${formatNumber(high, locale, numberFormat)}; highest ${top.name} at ${formatNumber(top.value, locale, numberFormat)}, lowest ${bottom.name} at ${formatNumber(bottom.value, locale, numberFormat)}.`;
2745
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
2746
+ }
2747
+ var radarChart = /* @__PURE__ */ Object.freeze({ name: "RadarChart", build: buildRadarChart });
2748
+
2749
+ // src/charts/polar-bar-chart/demo.ts
2750
+ var POLAR_BAR_CHART_DEMO = /* @__PURE__ */ Object.freeze(
2751
+ /* @__PURE__ */ [
2752
+ { name: "Jan", value: 32 },
2753
+ { name: "Feb", value: 28 },
2754
+ { name: "Mar", value: 41 },
2755
+ { name: "Apr", value: 47 },
2756
+ { name: "May", value: 55 },
2757
+ { name: "Jun", value: 61 },
2758
+ { name: "Jul", value: 66 },
2759
+ { name: "Aug", value: 63 },
2760
+ { name: "Sep", value: 52 },
2761
+ { name: "Oct", value: 44 },
2762
+ { name: "Nov", value: 37 },
2763
+ { name: "Dec", value: 49 }
2764
+ ].map((row) => Object.freeze(row))
2765
+ );
2766
+
2767
+ // src/charts/polar-bar-chart/polar-bar-chart.ts
2768
+ var CHART18 = "PolarBarChart";
2769
+ var HOLE2 = 0.22;
2770
+ var SLOT_GAP = 0.14;
2771
+ function buildPolarBarChart(props, context) {
2772
+ const usesDemo = props.data === void 0;
2773
+ const data = props.data ?? POLAR_BAR_CHART_DEMO;
2774
+ const nameKey = usesDemo ? "name" : props.nameKey ?? "name";
2775
+ const valueKey = usesDemo ? "value" : props.valueKey ?? "value";
2776
+ const { locale } = context;
2777
+ const numberFormat = props.numberFormat;
2778
+ const valueName = accessorName(valueKey, "value");
2779
+ checkSectors(CHART18, valueName, data.length);
2780
+ const bars = readSectors(data, nameKey, valueKey, CHART18, locale);
2781
+ const byRow = new Map(bars.map((s) => [s.index, s]));
2782
+ const base = modelBase(CHART18, props, context, "Polar bar chart", {
2783
+ columns: [accessorName(nameKey, "name"), valueName],
2784
+ rows: data.map((_, index) => {
2785
+ const s = byRow.get(index);
2786
+ return s ? [s.name, formatValue(s.value, locale, numberFormat)] : ["\u2014", "\u2014"];
2787
+ })
2788
+ });
2789
+ const size = measure(base, props, context);
2790
+ if ("deferred" in size) return size.deferred;
2791
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
2792
+ const plot = card.area;
2793
+ const strokes = [...card.strokes];
2794
+ const labels = [...card.labels];
2795
+ const hitAreas = [];
2796
+ const largest = Math.max(0, ...bars.map((b) => b.value));
2797
+ if (bars.length > 0 && largest <= 0) warnValue(CHART18, valueName, "Every value is zero; no bar has a length.");
2798
+ if (bars.length === 0 || largest <= 0) return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
2799
+ const frame = polarFrame(plot, RIM_LABELS);
2800
+ const hole = frame.radius * HOLE2;
2801
+ const reach = frame.radius - hole;
2802
+ const slot = 2 * Math.PI / bars.length;
2803
+ strokes.push({ d: arcPath(frame, hole, 0, 2 * Math.PI), role: "ornament", part: "rule" });
2804
+ bars.forEach((b, k) => {
2805
+ const start = k * slot + slot * SLOT_GAP / 2;
2806
+ const end = (k + 1) * slot - slot * SLOT_GAP / 2;
2807
+ const tip = hole + b.value / largest * reach;
2808
+ const d = sectorPath(frame, { inner: hole, outer: tip, start, end });
2809
+ if (d && tip > hole) strokes.push({ d, role: "encoding", part: "ink", tone: 2 });
2810
+ const at2 = pointAt(frame, (start + end) / 2, tip);
2811
+ hitAreas.push({ seriesKey: valueName, index: b.index, datum: b.datum, value: b.value, x: at2.x, y: at2.y });
2812
+ });
2813
+ labels.push(...rimLabels(frame, bars.map((b, k) => ({ angle: (k + 0.5) * slot, text: b.name }))));
2814
+ const top = bars.reduce((a, b) => b.value > a.value ? b : a);
2815
+ const description = props.description ?? `${base.name}. Polar bars for ${bars.length} categories around the circle; the longest is ${top.name} at ${formatNumber(top.value, locale, numberFormat)}.`;
2816
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
2817
+ }
2818
+ var polarBarChart = /* @__PURE__ */ Object.freeze({ name: "PolarBarChart", build: buildPolarBarChart });
2819
+
2820
+ // src/charts/coxcomb-chart/demo.ts
2821
+ var COXCOMB_CHART_DEMO = /* @__PURE__ */ Object.freeze(
2822
+ /* @__PURE__ */ [
2823
+ { name: "Mon", value: 42 },
2824
+ { name: "Tue", value: 35 },
2825
+ { name: "Wed", value: 31 },
2826
+ { name: "Thu", value: 38 },
2827
+ { name: "Fri", value: 27 },
2828
+ { name: "Sat", value: 12 },
2829
+ { name: "Sun", value: 9 }
2830
+ ].map((row) => Object.freeze(row))
2831
+ );
2832
+
2833
+ // src/charts/coxcomb-chart/coxcomb-chart.ts
2834
+ var CHART19 = "CoxcombChart";
2835
+ function buildCoxcombChart(props, context) {
2836
+ const usesDemo = props.data === void 0;
2837
+ const data = props.data ?? COXCOMB_CHART_DEMO;
2838
+ const nameKey = usesDemo ? "name" : props.nameKey ?? "name";
2839
+ const valueKey = usesDemo ? "value" : props.valueKey ?? "value";
2840
+ const { locale } = context;
2841
+ const numberFormat = props.numberFormat;
2842
+ const valueName = accessorName(valueKey, "value");
2843
+ checkSectors(CHART19, valueName, data.length);
2844
+ const sectors = readSectors(data, nameKey, valueKey, CHART19, locale);
2845
+ const byRow = new Map(sectors.map((s) => [s.index, s]));
2846
+ const base = modelBase(CHART19, props, context, "Coxcomb chart", {
2847
+ columns: [accessorName(nameKey, "name"), valueName],
2848
+ rows: data.map((_, index) => {
2849
+ const s = byRow.get(index);
2850
+ return s ? [s.name, formatValue(s.value, locale, numberFormat)] : ["\u2014", "\u2014"];
2851
+ })
2852
+ });
2853
+ const size = measure(base, props, context);
2854
+ if ("deferred" in size) return size.deferred;
2855
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
2856
+ const plot = card.area;
2857
+ const strokes = [...card.strokes];
2858
+ const labels = [...card.labels];
2859
+ const hitAreas = [];
2860
+ const largest = Math.max(0, ...sectors.map((s) => s.value));
2861
+ if (sectors.length > 0 && largest <= 0) warnValue(CHART19, valueName, "Every value is zero; no sector has an area.");
2862
+ if (sectors.length === 0 || largest <= 0) return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
2863
+ const frame = polarFrame(plot, RIM_LABELS);
2864
+ const start = Number.isFinite(props.startAngle) ? props.startAngle * Math.PI / 180 : 0;
2865
+ const slot = 2 * Math.PI / sectors.length;
2866
+ strokes.push({ d: arcPath(frame, frame.radius, 0, 2 * Math.PI), role: "ornament", part: "grid" });
2867
+ const tones = ringTones(sectors.map((s) => s.value));
2868
+ sectors.forEach((s, k) => {
2869
+ const from = start + k * slot;
2870
+ const r = frame.radius * Math.sqrt(s.value / largest);
2871
+ const d = sectorPath(frame, { inner: 0, outer: r, start: from, end: from + slot });
2872
+ if (d) strokes.push({ d, role: "encoding", part: "ink", tone: tones[k] });
2873
+ const at2 = pointAt(frame, from + slot / 2, r * 0.6);
2874
+ hitAreas.push({ seriesKey: valueName, index: s.index, datum: s.datum, value: s.value, x: at2.x, y: at2.y });
2875
+ });
2876
+ labels.push(...rimLabels(frame, sectors.map((s, k) => ({ angle: start + (k + 0.5) * slot, text: s.name }))));
2877
+ const top = sectors.reduce((a, b) => b.value > a.value ? b : a);
2878
+ const description = props.description ?? `${base.name}. Coxcomb of ${sectors.length} equal-angle sectors whose area is proportional to the value; the largest is ${top.name} at ${formatNumber(top.value, locale, numberFormat)}.`;
2879
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
2880
+ }
2881
+ var coxcombChart = /* @__PURE__ */ Object.freeze({ name: "CoxcombChart", build: buildCoxcombChart });
2882
+
2883
+ // src/charts/shared/tracks.ts
2884
+ var HOLE3 = 0.3;
2885
+ var BAND_FILL = 0.72;
2886
+ var LEGEND_WIDTH = 110;
2887
+ var LEGEND_GAP2 = 12;
2888
+ function buildTracks(family, props, context) {
2889
+ const { chart } = family;
2890
+ const usesDemo = props.data === void 0;
2891
+ const data = props.data ?? family.demo;
2892
+ const nameKey = usesDemo ? "name" : props.nameKey ?? "name";
2893
+ const valueKey = usesDemo ? "value" : props.valueKey ?? "value";
2894
+ const { locale } = context;
2895
+ const numberFormat = props.numberFormat;
2896
+ const valueName = accessorName(valueKey, "value");
2897
+ checkSectors(chart, valueName, data.length);
2898
+ const sectors = readSectors(data, nameKey, valueKey, chart, locale);
2899
+ const byRow = new Map(sectors.map((s) => [s.index, s]));
2900
+ const base = modelBase(chart, props, context, family.fallbackName, {
2901
+ columns: [accessorName(nameKey, "name"), valueName],
2902
+ rows: data.map((_, index) => {
2903
+ const s = byRow.get(index);
2904
+ return s ? [s.name, formatValue(s.value, locale, numberFormat)] : ["\u2014", "\u2014"];
2905
+ })
2906
+ });
2907
+ const size = measure(base, props, context);
2908
+ if ("deferred" in size) return size.deferred;
2909
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
2910
+ const plot = card.area;
2911
+ const strokes = [...card.strokes];
2912
+ const labels = [...card.labels];
2913
+ const hitAreas = [];
2914
+ if (sectors.length === 0) return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
2915
+ const withLegend = plot.width >= LEGEND_WIDTH * 2;
2916
+ const drawing = withLegend ? { ...plot, width: plot.width - LEGEND_WIDTH - LEGEND_GAP2 } : plot;
2917
+ const frame = family.frameIn(drawing);
2918
+ const hole = frame.radius * HOLE3;
2919
+ const band = (frame.radius - hole) / sectors.length;
2920
+ let saturated = 0;
2921
+ sectors.forEach((s, k) => {
2922
+ const outer = frame.radius - k * band;
2923
+ const inner = outer - band * BAND_FILL;
2924
+ const middle = (outer + inner) / 2;
2925
+ strokes.push({ d: arcPath(frame, middle, family.start, family.start + family.span), role: "ornament", part: "grid" });
2926
+ const raw = family.shareOf(s, sectors);
2927
+ const share = Math.min(Math.max(raw, 0), 1);
2928
+ if (share !== raw) saturated += 1;
2929
+ const end = family.start + share * family.span;
2930
+ const d = sectorPath(frame, { inner, outer, start: family.start, end });
2931
+ if (d) strokes.push({ d, role: "encoding", part: "ink", tone: 2 });
2932
+ const tip = pointAt(frame, end, middle);
2933
+ hitAreas.push({ seriesKey: valueName, index: s.index, datum: s.datum, value: s.value, x: tip.x, y: tip.y });
2934
+ });
2935
+ if (saturated > 0) warnValue(chart, valueName, `${saturated} values fall outside the track's range; they are drawn at its end.`);
2936
+ if (withLegend) {
2937
+ const x = drawing.x + drawing.width + LEGEND_GAP2;
2938
+ const key = sectorLegend(
2939
+ { x, y: plot.y, width: plot.x + plot.width - x, height: plot.height },
2940
+ sectors.map((s) => ({ name: s.name, share: family.printed(s, locale, numberFormat), tone: 2 }))
2941
+ );
2942
+ strokes.push(...key.strokes);
2943
+ labels.push(...key.labels);
2944
+ }
2945
+ const description = props.description ?? `${base.name}. ${sectors.length} concentric tracks, outermost first: ${sectors.map((s) => `${s.name} ${family.printed(s, locale, numberFormat)}`).join(", ")}.`;
2946
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
2947
+ }
2948
+ function halfCircleFrame(area5, bottomRoom) {
2949
+ const radius = Math.max(Math.min(area5.width / 2, area5.height - bottomRoom) - PLOT_INSET, 1);
2950
+ return { cx: area5.x + area5.width / 2, cy: area5.y + area5.height - bottomRoom, radius };
2951
+ }
2952
+
2953
+ // src/charts/radial-arc-group/demo.ts
2954
+ var RADIAL_ARC_GROUP_DEMO = /* @__PURE__ */ Object.freeze(
2955
+ /* @__PURE__ */ [
2956
+ { name: "Direct", value: 84 },
2957
+ { name: "Partners", value: 62 },
2958
+ { name: "Online", value: 47 },
2959
+ { name: "Events", value: 23 }
2960
+ ].map((row) => Object.freeze(row))
2961
+ );
2962
+
2963
+ // src/charts/radial-arc-group/radial-arc-group.ts
2964
+ var FAMILY = {
2965
+ chart: "RadialArcGroup",
2966
+ fallbackName: "Radial arc group",
2967
+ // From 9 o'clock over the top to 3: a half turn.
2968
+ start: -Math.PI / 2,
2969
+ span: Math.PI,
2970
+ frameIn: (area5) => halfCircleFrame(area5, 8),
2971
+ // The largest value sweeps the whole half turn.
2972
+ shareOf: (s, all) => s.value / Math.max(...all.map((x) => x.value), Number.MIN_VALUE),
2973
+ printed: (s, locale, numberFormat) => formatNumber(s.value, locale, numberFormat),
2974
+ demo: RADIAL_ARC_GROUP_DEMO
2975
+ };
2976
+ var radialArcGroup = /* @__PURE__ */ Object.freeze({
2977
+ name: "RadialArcGroup",
2978
+ build: (props, context) => buildTracks(FAMILY, props, context)
2979
+ });
2980
+
2981
+ // src/charts/radial-rings/demo.ts
2982
+ var RADIAL_RINGS_DEMO = /* @__PURE__ */ Object.freeze(
2983
+ /* @__PURE__ */ [
2984
+ { name: "Move", value: 82 },
2985
+ { name: "Exercise", value: 64 },
2986
+ { name: "Stand", value: 100 }
2987
+ ].map((row) => Object.freeze(row))
2988
+ );
2989
+
2990
+ // src/charts/radial-rings/radial-rings.ts
2991
+ var FAMILY2 = {
2992
+ chart: "RadialRings",
2993
+ fallbackName: "Radial rings",
2994
+ // A full turn from 12 o'clock: a ring is progress towards 100.
2995
+ start: 0,
2996
+ span: 2 * Math.PI,
2997
+ frameIn: (area5) => polarFrame(area5),
2998
+ shareOf: (s) => s.value / 100,
2999
+ printed: (s, locale) => `${formatNumber(s.value, locale, { maximumFractionDigits: 1 })}%`,
3000
+ demo: RADIAL_RINGS_DEMO
3001
+ };
3002
+ var radialRings = /* @__PURE__ */ Object.freeze({
3003
+ name: "RadialRings",
3004
+ build: (props, context) => buildTracks(FAMILY2, props, context)
3005
+ });
3006
+
3007
+ // src/charts/shared/scalar.ts
3008
+ var SCALAR_DEMO = 72;
3009
+ function buildScalar(family, props, context) {
3010
+ const { chart } = family;
3011
+ const { locale } = context;
3012
+ const raw = props.percent === void 0 ? SCALAR_DEMO : props.percent;
3013
+ let value;
3014
+ if (typeof raw !== "number" || !Number.isFinite(raw)) {
3015
+ warnValue(chart, "percent", `The percent is ${String(raw)}; a meter needs a finite number, so none is drawn.`);
3016
+ } else {
3017
+ value = Math.min(Math.max(raw, 0), 100);
3018
+ if (value !== raw) warnValue(chart, "percent", `The percent is ${raw}; it is drawn at ${value}, the end of the 0-100 range.`);
3019
+ }
3020
+ const printed = value === void 0 ? "\u2014" : props.readout ?? `${formatNumber(value, locale, { maximumFractionDigits: 1 })}%`;
3021
+ const caption = props.caption && props.caption !== "" ? props.caption : void 0;
3022
+ const base = modelBase(chart, props, context, family.fallbackName, { columns: ["measure", "value"], rows: [[caption ?? "value", printed]] });
3023
+ const size = measure(base, props, context);
3024
+ if ("deferred" in size) return size.deferred;
3025
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
3026
+ const plot = card.area;
3027
+ const frame = family.frameIn(plot);
3028
+ const drawing = family.draw(frame, value);
3029
+ const strokes = [...card.strokes, ...drawing.strokes];
3030
+ const labels = [...card.labels, ...drawing.labels];
3031
+ const at2 = family.readoutAt(frame);
3032
+ labels.push({ x: frame.cx, y: at2.value, text: printed, kind: "value", part: "text", anchor: "middle" });
3033
+ if (caption) labels.push({ x: frame.cx, y: at2.caption, text: caption, kind: "tick", part: "axis", anchor: "middle" });
3034
+ const hitAreas = [];
3035
+ if (value !== void 0 && drawing.tip) {
3036
+ hitAreas.push({ seriesKey: "value", index: 0, datum: { value }, value, x: drawing.tip.x, y: drawing.tip.y });
3037
+ }
3038
+ const description = props.description ?? `${base.name}. ${caption ?? "The value"} at ${printed}${props.readout && value !== void 0 ? ` (${formatNumber(value, locale, void 0)}% of the range)` : " of the range"}.`;
3039
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
3040
+ }
3041
+
3042
+ // src/charts/gauge-arc/gauge-arc.ts
3043
+ var START = -2 * Math.PI / 3;
3044
+ var SPAN = 4 * Math.PI / 3;
3045
+ var BAND = 0.8;
3046
+ var FAMILY3 = {
3047
+ chart: "GaugeArc",
3048
+ fallbackName: "Gauge",
3049
+ // A 240° arc reaches half a radius below the centre: it needs 1.5 radii of height.
3050
+ frameIn: (area5) => {
3051
+ const radius = Math.max(Math.min(area5.width / 2, area5.height / 1.5) - PLOT_INSET, 1);
3052
+ return { cx: area5.x + area5.width / 2, cy: area5.y + (area5.height - 1.5 * radius) / 2 + radius, radius };
3053
+ },
3054
+ // Inside the ring, which the value never crosses.
3055
+ readoutAt: (frame) => ({ value: frame.cy + 2, caption: frame.cy + 16 }),
3056
+ draw: (frame, value) => {
3057
+ const inner = frame.radius * BAND;
3058
+ const middle = (inner + frame.radius) / 2;
3059
+ const strokes = [{ d: arcPath(frame, middle, START, START + SPAN), role: "ornament", part: "grid" }];
3060
+ if (value === void 0) return { strokes, labels: [] };
3061
+ const end = START + value / 100 * SPAN;
3062
+ const d = sectorPath(frame, { inner, outer: frame.radius, start: START, end });
3063
+ return { strokes: d ? [...strokes, { d, role: "encoding", part: "ink", tone: 2 }] : strokes, labels: [], tip: pointAt(frame, end, middle) };
3064
+ }
3065
+ };
3066
+ var gaugeArc = /* @__PURE__ */ Object.freeze({
3067
+ name: "GaugeArc",
3068
+ build: (props, context) => buildScalar(FAMILY3, props, context)
3069
+ });
3070
+
3071
+ // src/charts/meter-chart/meter-chart.ts
3072
+ var START2 = -Math.PI / 2;
3073
+ var SPAN2 = Math.PI;
3074
+ var TICKS = [0, 25, 50, 75, 100];
3075
+ var NEEDLE = 0.86;
3076
+ var FAMILY4 = {
3077
+ chart: "MeterChart",
3078
+ fallbackName: "Meter",
3079
+ // Under the pivot, clear of the needle, which sweeps only the upper half: the 30 px readout
3080
+ // stands up to 22 px above its baseline, and the caption goes under it.
3081
+ frameIn: (area5) => halfCircleFrame(area5, 46),
3082
+ readoutAt: (frame) => ({ value: frame.cy + 29, caption: frame.cy + 42 }),
3083
+ draw: (frame, value) => {
3084
+ const strokes = [{ d: arcPath(frame, frame.radius, START2, START2 + SPAN2), role: "ornament", part: "grid" }];
3085
+ const labels = [];
3086
+ for (const tick of TICKS) {
3087
+ const angle = START2 + tick / 100 * SPAN2;
3088
+ const a = pointAt(frame, angle, frame.radius * 0.9);
3089
+ const b = pointAt(frame, angle, frame.radius);
3090
+ strokes.push({ d: `M${a.x},${a.y}L${b.x},${b.y}`, role: "ornament", part: "rule" });
3091
+ }
3092
+ labels.push({ x: frame.cx - frame.radius, y: frame.cy + 11, text: "0", kind: "tick", part: "axis", anchor: "middle" });
3093
+ labels.push({ x: frame.cx + frame.radius, y: frame.cy + 11, text: "100", kind: "tick", part: "axis", anchor: "middle" });
3094
+ if (value === void 0) return { strokes, labels };
3095
+ const tip = pointAt(frame, START2 + value / 100 * SPAN2, frame.radius * NEEDLE);
3096
+ strokes.push({ d: `M${frame.cx},${frame.cy}L${tip.x},${tip.y}`, role: "encoding", part: "ink" });
3097
+ return { strokes, labels, tip };
3098
+ }
3099
+ };
3100
+ var meterChart = /* @__PURE__ */ Object.freeze({
3101
+ name: "MeterChart",
3102
+ build: (props, context) => buildScalar(FAMILY4, props, context)
3103
+ });
3104
+
3105
+ // src/charts/wind-rose/demo.ts
3106
+ var WIND_ROSE_DEMO = /* @__PURE__ */ Object.freeze(
3107
+ /* @__PURE__ */ [
3108
+ [180, 11],
3109
+ [170, 11],
3110
+ [180, 9],
3111
+ [180, 10],
3112
+ [170, 7],
3113
+ [180, 11],
3114
+ [180, 10],
3115
+ [180, 9],
3116
+ [160, 7],
3117
+ [150, 6],
3118
+ [140, 9],
3119
+ [150, 8],
3120
+ [160, 8],
3121
+ [180, 9],
3122
+ [190, 8],
3123
+ [200, 12],
3124
+ [190, 16],
3125
+ [210, 16],
3126
+ [170, 17],
3127
+ [180, 16],
3128
+ [190, 15],
3129
+ [190, 17],
3130
+ [190, 13],
3131
+ [180, 14],
3132
+ [170, 9],
3133
+ [170, 11],
3134
+ [160, 9],
3135
+ [150, 6],
3136
+ [150, 7],
3137
+ [150, 8],
3138
+ [160, 9],
3139
+ [160, 9],
3140
+ [140, 7],
3141
+ [150, 9],
3142
+ [150, 7],
3143
+ [140, 7],
3144
+ [140, 8],
3145
+ [150, 9],
3146
+ [160, 12],
3147
+ [170, 10],
3148
+ [170, 11],
3149
+ [140, 14],
3150
+ [150, 15],
3151
+ [150, 17],
3152
+ [140, 16],
3153
+ [140, 18],
3154
+ [150, 21],
3155
+ [140, 14],
3156
+ [140, 13],
3157
+ [130, 12],
3158
+ [140, 16],
3159
+ [150, 13],
3160
+ [160, 15],
3161
+ [160, 14],
3162
+ [160, 16],
3163
+ [160, 18],
3164
+ [170, 15],
3165
+ [170, 17],
3166
+ [170, 19],
3167
+ [170, 20],
3168
+ [170, 20],
3169
+ [180, 20],
3170
+ [180, 22],
3171
+ [190, 21],
3172
+ [180, 26],
3173
+ [190, 20],
3174
+ [200, 22],
3175
+ [210, 29],
3176
+ [210, 27],
3177
+ [250, 22],
3178
+ [270, 17],
3179
+ [300, 16],
3180
+ [320, 13],
3181
+ [330, 8],
3182
+ [350, 8],
3183
+ [30, 7],
3184
+ [20, 10],
3185
+ [110, 7],
3186
+ [100, 9],
3187
+ [80, 7],
3188
+ [90, 7],
3189
+ [70, 9],
3190
+ [40, 10],
3191
+ [20, 12],
3192
+ [50, 17],
3193
+ [30, 9],
3194
+ [50, 15],
3195
+ [50, 12],
3196
+ [30, 15],
3197
+ [30, 9],
3198
+ [30, 13],
3199
+ [40, 11],
3200
+ [20, 12],
3201
+ [10, 15],
3202
+ [20, 10],
3203
+ [20, 16],
3204
+ [40, 14],
3205
+ [30, 12],
3206
+ [10, 10],
3207
+ [360, 9],
3208
+ [10, 11],
3209
+ [360, 14],
3210
+ [10, 10],
3211
+ [10, 10],
3212
+ [20, 4],
3213
+ [20, 6],
3214
+ [350, 6],
3215
+ [350, 4],
3216
+ [360, 6],
3217
+ [30, 6],
3218
+ [40, 8],
3219
+ [10, 5],
3220
+ [0, 0],
3221
+ [320, 3],
3222
+ [0, 0],
3223
+ [0, 0],
3224
+ [310, 7],
3225
+ [310, 11],
3226
+ [330, 6],
3227
+ [350, 5],
3228
+ [350, 5],
3229
+ [20, 3],
3230
+ [10, 3],
3231
+ [0, 0],
3232
+ [80, 5],
3233
+ [0, 0],
3234
+ [0, 0],
3235
+ [0, 0],
3236
+ [0, 0],
3237
+ [280, 3],
3238
+ [0, 0],
3239
+ [0, 0],
3240
+ [70, 6],
3241
+ [110, 8],
3242
+ [130, 5],
3243
+ [90, 9],
3244
+ [110, 10],
3245
+ [130, 12],
3246
+ [120, 10],
3247
+ [110, 11],
3248
+ [110, 8],
3249
+ [110, 11],
3250
+ [100, 10],
3251
+ [110, 7],
3252
+ [100, 10],
3253
+ [140, 9],
3254
+ [70, 12],
3255
+ [120, 10],
3256
+ [100, 9],
3257
+ [100, 7],
3258
+ [100, 6],
3259
+ [100, 9],
3260
+ [100, 6],
3261
+ [80, 7],
3262
+ [80, 8],
3263
+ [70, 8],
3264
+ [80, 9],
3265
+ [80, 10],
3266
+ [100, 8],
3267
+ [80, 7],
3268
+ [80, 8],
3269
+ [60, 9],
3270
+ [90, 10],
3271
+ [90, 8],
3272
+ [90, 7],
3273
+ [90, 12],
3274
+ [80, 10],
3275
+ [80, 14],
3276
+ [80, 12],
3277
+ [50, 9],
3278
+ [30, 10],
3279
+ [40, 11],
3280
+ [40, 7],
3281
+ [30, 9],
3282
+ [30, 8],
3283
+ [30, 10],
3284
+ [20, 11],
3285
+ [360, 13],
3286
+ [360, 13],
3287
+ [360, 12],
3288
+ [350, 12],
3289
+ [350, 11],
3290
+ [340, 13],
3291
+ [350, 11],
3292
+ [330, 13],
3293
+ [330, 10],
3294
+ [330, 11],
3295
+ [330, 10],
3296
+ [330, 7],
3297
+ [310, 7],
3298
+ [340, 10],
3299
+ [340, 14],
3300
+ [340, 9],
3301
+ [340, 9],
3302
+ [340, 10],
3303
+ [330, 13],
3304
+ [340, 10],
3305
+ [320, 8],
3306
+ [310, 8],
3307
+ [310, 6],
3308
+ [310, 6],
3309
+ [310, 7],
3310
+ [310, 8],
3311
+ [290, 5],
3312
+ [310, 8],
3313
+ [320, 10],
3314
+ [330, 13],
3315
+ [320, 15],
3316
+ [320, 11],
3317
+ [290, 15],
3318
+ [310, 17],
3319
+ [310, 18],
3320
+ [300, 18],
3321
+ [320, 16],
3322
+ [330, 8],
3323
+ [320, 7],
3324
+ [330, 7],
3325
+ [350, 3],
3326
+ [350, 3],
3327
+ [310, 4],
3328
+ [300, 3],
3329
+ [300, 3],
3330
+ [280, 3],
3331
+ [270, 3],
3332
+ [240, 3],
3333
+ [300, 3],
3334
+ [270, 3],
3335
+ [0, 0],
3336
+ [0, 0],
3337
+ [0, 0],
3338
+ [150, 4],
3339
+ [170, 5],
3340
+ [220, 6],
3341
+ [230, 7],
3342
+ [180, 10],
3343
+ [190, 8],
3344
+ [200, 6],
3345
+ [200, 9],
3346
+ [170, 7],
3347
+ [150, 8],
3348
+ [150, 10],
3349
+ [150, 9],
3350
+ [160, 9],
3351
+ [150, 8],
3352
+ [160, 8],
3353
+ [180, 9],
3354
+ [190, 8],
3355
+ [200, 12],
3356
+ [190, 11],
3357
+ [200, 13],
3358
+ [200, 12],
3359
+ [210, 13],
3360
+ [220, 12],
3361
+ [220, 16],
3362
+ [230, 15],
3363
+ [210, 14],
3364
+ [220, 16],
3365
+ [210, 15],
3366
+ [220, 18],
3367
+ [200, 21],
3368
+ [220, 20],
3369
+ [220, 16],
3370
+ [210, 13],
3371
+ [200, 13],
3372
+ [200, 11],
3373
+ [190, 12],
3374
+ [200, 11],
3375
+ [200, 10],
3376
+ [200, 11],
3377
+ [200, 11],
3378
+ [200, 9],
3379
+ [200, 10],
3380
+ [210, 12],
3381
+ [220, 9],
3382
+ [210, 10],
3383
+ [210, 10],
3384
+ [210, 9],
3385
+ [220, 15],
3386
+ [240, 12],
3387
+ [270, 8],
3388
+ [240, 5],
3389
+ [280, 6],
3390
+ [200, 5],
3391
+ [180, 7],
3392
+ [220, 5],
3393
+ [0, 0],
3394
+ [0, 0],
3395
+ [0, 0],
3396
+ [20, 3],
3397
+ [30, 3],
3398
+ [10, 3],
3399
+ [70, 4],
3400
+ [0, 0],
3401
+ [150, 4],
3402
+ [0, 0],
3403
+ [0, 0],
3404
+ [80, 5],
3405
+ [60, 4],
3406
+ [70, 4],
3407
+ [50, 3],
3408
+ [90, 7],
3409
+ [100, 8],
3410
+ [150, 4],
3411
+ [170, 8],
3412
+ [140, 6],
3413
+ [90, 9],
3414
+ [100, 9],
3415
+ [80, 9],
3416
+ [100, 11],
3417
+ [90, 10],
3418
+ [100, 8],
3419
+ [90, 7],
3420
+ [90, 5],
3421
+ [80, 6],
3422
+ [40, 6],
3423
+ [70, 6],
3424
+ [100, 7],
3425
+ [100, 5],
3426
+ [100, 9],
3427
+ [180, 5],
3428
+ [80, 6],
3429
+ [40, 6],
3430
+ [50, 23],
3431
+ [70, 9],
3432
+ [360, 5],
3433
+ [350, 3],
3434
+ [360, 7],
3435
+ [20, 12],
3436
+ [20, 10],
3437
+ [20, 12],
3438
+ [40, 12],
3439
+ [30, 11],
3440
+ [40, 13],
3441
+ [30, 12],
3442
+ [20, 11],
3443
+ [40, 13],
3444
+ [10, 7],
3445
+ [360, 5],
3446
+ [20, 4],
3447
+ [30, 5],
3448
+ [330, 5],
3449
+ [320, 5],
3450
+ [330, 6],
3451
+ [340, 7],
3452
+ [350, 5],
3453
+ [320, 10],
3454
+ [320, 11],
3455
+ [330, 9],
3456
+ [340, 9],
3457
+ [320, 8],
3458
+ [300, 6],
3459
+ [300, 9],
3460
+ [320, 10],
3461
+ [310, 14],
3462
+ [280, 11],
3463
+ [320, 10],
3464
+ [290, 11],
3465
+ [290, 12],
3466
+ [300, 9],
3467
+ [290, 7],
3468
+ [280, 5],
3469
+ [210, 8],
3470
+ [220, 5],
3471
+ [260, 3],
3472
+ [230, 8],
3473
+ [240, 7],
3474
+ [230, 10],
3475
+ [230, 11],
3476
+ [250, 13],
3477
+ [250, 15],
3478
+ [250, 18],
3479
+ [260, 11],
3480
+ [270, 14],
3481
+ [300, 16],
3482
+ [310, 21],
3483
+ [310, 21],
3484
+ [310, 20],
3485
+ [310, 25],
3486
+ [290, 23],
3487
+ [300, 20],
3488
+ [300, 18],
3489
+ [310, 20],
3490
+ [300, 16],
3491
+ [300, 14],
3492
+ [310, 15],
3493
+ [320, 12],
3494
+ [320, 10],
3495
+ [320, 10],
3496
+ [310, 10],
3497
+ [310, 9],
3498
+ [310, 10],
3499
+ [310, 8],
3500
+ [330, 7],
3501
+ [330, 8],
3502
+ [300, 7],
3503
+ [320, 7],
3504
+ [360, 7],
3505
+ [320, 18],
3506
+ [330, 16],
3507
+ [310, 21],
3508
+ [340, 16],
3509
+ [320, 20],
3510
+ [330, 17],
3511
+ [320, 22],
3512
+ [320, 17],
3513
+ [330, 16],
3514
+ [320, 18],
3515
+ [340, 10],
3516
+ [330, 11],
3517
+ [320, 14],
3518
+ [330, 9],
3519
+ [320, 11],
3520
+ [320, 9],
3521
+ [320, 13],
3522
+ [320, 10],
3523
+ [320, 13],
3524
+ [320, 12],
3525
+ [320, 11],
3526
+ [320, 12],
3527
+ [310, 14],
3528
+ [320, 12],
3529
+ [310, 15],
3530
+ [320, 12],
3531
+ [320, 12],
3532
+ [320, 15],
3533
+ [280, 11],
3534
+ [300, 12],
3535
+ [270, 12],
3536
+ [280, 11],
3537
+ [260, 11],
3538
+ [250, 6],
3539
+ [240, 6],
3540
+ [220, 6],
3541
+ [210, 7],
3542
+ [210, 10],
3543
+ [210, 10],
3544
+ [210, 10],
3545
+ [220, 10],
3546
+ [210, 12],
3547
+ [220, 14],
3548
+ [220, 11],
3549
+ [230, 9],
3550
+ [250, 10],
3551
+ [260, 9],
3552
+ [270, 11],
3553
+ [310, 16],
3554
+ [330, 11],
3555
+ [310, 9],
3556
+ [340, 13],
3557
+ [320, 12],
3558
+ [320, 12],
3559
+ [310, 18],
3560
+ [340, 11],
3561
+ [320, 17],
3562
+ [300, 12],
3563
+ [310, 8],
3564
+ [300, 5],
3565
+ [360, 11],
3566
+ [10, 9],
3567
+ [350, 6],
3568
+ [10, 6],
3569
+ [10, 11],
3570
+ [350, 9],
3571
+ [330, 13],
3572
+ [350, 9],
3573
+ [330, 14],
3574
+ [340, 8],
3575
+ [350, 10],
3576
+ [360, 13],
3577
+ [360, 13],
3578
+ [20, 10],
3579
+ [350, 11],
3580
+ [350, 9],
3581
+ [360, 11],
3582
+ [350, 8],
3583
+ [40, 10],
3584
+ [30, 7],
3585
+ [10, 11],
3586
+ [340, 7],
3587
+ [10, 4],
3588
+ [10, 4],
3589
+ [30, 9],
3590
+ [40, 8],
3591
+ [50, 8],
3592
+ [60, 6],
3593
+ [60, 4],
3594
+ [60, 7],
3595
+ [70, 8],
3596
+ [70, 11],
3597
+ [60, 8],
3598
+ [80, 6],
3599
+ [70, 8],
3600
+ [100, 10],
3601
+ [120, 9],
3602
+ [70, 8],
3603
+ [120, 8],
3604
+ [80, 11],
3605
+ [120, 8],
3606
+ [120, 11],
3607
+ [90, 6],
3608
+ [120, 11],
3609
+ [100, 14],
3610
+ [100, 11],
3611
+ [110, 7],
3612
+ [110, 9],
3613
+ [100, 10],
3614
+ [110, 10],
3615
+ [120, 10],
3616
+ [130, 10],
3617
+ [90, 6],
3618
+ [20, 6],
3619
+ [60, 10],
3620
+ [50, 16],
3621
+ [20, 13],
3622
+ [50, 11],
3623
+ [90, 11],
3624
+ [50, 6],
3625
+ [30, 8],
3626
+ [360, 9],
3627
+ [360, 8],
3628
+ [350, 8],
3629
+ [350, 12],
3630
+ [360, 12],
3631
+ [360, 12],
3632
+ [10, 10],
3633
+ [360, 12],
3634
+ [360, 9],
3635
+ [10, 11],
3636
+ [360, 10],
3637
+ [360, 9],
3638
+ [350, 6],
3639
+ [360, 5],
3640
+ [360, 5],
3641
+ [10, 5],
3642
+ [360, 4],
3643
+ [360, 3],
3644
+ [360, 6],
3645
+ [360, 6],
3646
+ [20, 5],
3647
+ [20, 7],
3648
+ [30, 5],
3649
+ [70, 7],
3650
+ [70, 8],
3651
+ [80, 12],
3652
+ [90, 5],
3653
+ [120, 9],
3654
+ [120, 5],
3655
+ [110, 7],
3656
+ [100, 10],
3657
+ [110, 13],
3658
+ [110, 13],
3659
+ [120, 13],
3660
+ [100, 13],
3661
+ [110, 14],
3662
+ [100, 11],
3663
+ [110, 11],
3664
+ [110, 11],
3665
+ [100, 8],
3666
+ [90, 10],
3667
+ [90, 10],
3668
+ [90, 11],
3669
+ [90, 13],
3670
+ [100, 15],
3671
+ [110, 13],
3672
+ [100, 13],
3673
+ [100, 10],
3674
+ [90, 8],
3675
+ [90, 11],
3676
+ [100, 13],
3677
+ [100, 11],
3678
+ [110, 10],
3679
+ [120, 10],
3680
+ [130, 12],
3681
+ [140, 11],
3682
+ [110, 5],
3683
+ [120, 5],
3684
+ [80, 11],
3685
+ [100, 9],
3686
+ [90, 8],
3687
+ [120, 11],
3688
+ [120, 11],
3689
+ [130, 12],
3690
+ [140, 11],
3691
+ [130, 11],
3692
+ [130, 14],
3693
+ [130, 15],
3694
+ [130, 11],
3695
+ [130, 12],
3696
+ [160, 5],
3697
+ [160, 10],
3698
+ [150, 6],
3699
+ [160, 15],
3700
+ [180, 15],
3701
+ [180, 10],
3702
+ [150, 10],
3703
+ [160, 16],
3704
+ [150, 14],
3705
+ [130, 12],
3706
+ [130, 12],
3707
+ [150, 15],
3708
+ [150, 13],
3709
+ [150, 8],
3710
+ [200, 7],
3711
+ [200, 5],
3712
+ [320, 7],
3713
+ [310, 9],
3714
+ [300, 13],
3715
+ [340, 6],
3716
+ [310, 19],
3717
+ [310, 17],
3718
+ [310, 15],
3719
+ [300, 16],
3720
+ [310, 18],
3721
+ [300, 22],
3722
+ [320, 19],
3723
+ [300, 22],
3724
+ [300, 19],
3725
+ [300, 22],
3726
+ [290, 20],
3727
+ [290, 17],
3728
+ [300, 22],
3729
+ [280, 20],
3730
+ [300, 20],
3731
+ [310, 18],
3732
+ [310, 19],
3733
+ [290, 19],
3734
+ [300, 14],
3735
+ [300, 19],
3736
+ [300, 12],
3737
+ [280, 17],
3738
+ [300, 12],
3739
+ [300, 11],
3740
+ [300, 11],
3741
+ [280, 8],
3742
+ [290, 11],
3743
+ [290, 9],
3744
+ [290, 10],
3745
+ [270, 13],
3746
+ [290, 12],
3747
+ [280, 12],
3748
+ [310, 12],
3749
+ [290, 14],
3750
+ [290, 12],
3751
+ [270, 9],
3752
+ [240, 13],
3753
+ [210, 14],
3754
+ [240, 11],
3755
+ [240, 7],
3756
+ [230, 5],
3757
+ [220, 5],
3758
+ [180, 5],
3759
+ [180, 4],
3760
+ [170, 7],
3761
+ [180, 5],
3762
+ [170, 4],
3763
+ [70, 4],
3764
+ [140, 6],
3765
+ [30, 3],
3766
+ [60, 3],
3767
+ [90, 4],
3768
+ [0, 0],
3769
+ [100, 5],
3770
+ [110, 8],
3771
+ [90, 5],
3772
+ [50, 6],
3773
+ [60, 6],
3774
+ [0, 0],
3775
+ [0, 0],
3776
+ [20, 7],
3777
+ [40, 7],
3778
+ [60, 9],
3779
+ [60, 8],
3780
+ [70, 8],
3781
+ [80, 9],
3782
+ [80, 10],
3783
+ [80, 9],
3784
+ [90, 11],
3785
+ [100, 11],
3786
+ [90, 7],
3787
+ [90, 8],
3788
+ [100, 10],
3789
+ [100, 9],
3790
+ [100, 8],
3791
+ [110, 11],
3792
+ [100, 11],
3793
+ [110, 11],
3794
+ [100, 13],
3795
+ [100, 11],
3796
+ [190, 18],
3797
+ [160, 13],
3798
+ [180, 13],
3799
+ [160, 15],
3800
+ [180, 11],
3801
+ [120, 6],
3802
+ [70, 7],
3803
+ [70, 8],
3804
+ [80, 8],
3805
+ [70, 5],
3806
+ [330, 5],
3807
+ [10, 10],
3808
+ [350, 6],
3809
+ [280, 9],
3810
+ [300, 13],
3811
+ [320, 15],
3812
+ [320, 13],
3813
+ [310, 12],
3814
+ [310, 14],
3815
+ [330, 8],
3816
+ [330, 13],
3817
+ [340, 12],
3818
+ [330, 12],
3819
+ [350, 8],
3820
+ [340, 9],
3821
+ [360, 5],
3822
+ [30, 4],
3823
+ [10, 10],
3824
+ [360, 9],
3825
+ [350, 6],
3826
+ [360, 4],
3827
+ [10, 8],
3828
+ [10, 8],
3829
+ [30, 8],
3830
+ [20, 8],
3831
+ [30, 6],
3832
+ [20, 7],
3833
+ [30, 11],
3834
+ [20, 8],
3835
+ [30, 9],
3836
+ [40, 8],
3837
+ [30, 5],
3838
+ [30, 6],
3839
+ [50, 8],
3840
+ [60, 11],
3841
+ [50, 8],
3842
+ [90, 9],
3843
+ [50, 12],
3844
+ [80, 8],
3845
+ [70, 15],
3846
+ [80, 12],
3847
+ [90, 10],
3848
+ [80, 11],
3849
+ [70, 12]
3850
+ ].map(([bearing, speed]) => Object.freeze({ bearing, speed }))
3851
+ );
3852
+
3853
+ // src/charts/wind-rose/wind-rose.ts
3854
+ var CHART20 = "WindRose";
3855
+ var COUNTS = [4, 8, 16, 32];
3856
+ var DEFAULT_SECTORS = 16;
3857
+ var DEFAULT_BINS = [5, 10, 15, 20];
3858
+ var POINTS_16 = ["N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW"];
3859
+ var BIN_TONES = [1, 2, 3, 4];
3860
+ var SECTOR_GAP = 0.08;
3861
+ var LEGEND_ASPECT2 = 1.5;
3862
+ var LEGEND_GAP3 = 14;
3863
+ function compass(k, count, locale) {
3864
+ if (count <= 16) return POINTS_16[k * 16 / count];
3865
+ return `${formatNumber(k * 360 / count, locale, { maximumFractionDigits: 2 })}\xB0`;
3866
+ }
3867
+ var validBins = (bins) => !!bins && bins.length > 0 && bins.every((b, i) => Number.isFinite(b) && b > 0 && (i === 0 || b > bins[i - 1]));
3868
+ function buildWindRose(props, context) {
3869
+ const usesDemo = props.data === void 0;
3870
+ const data = props.data ?? WIND_ROSE_DEMO;
3871
+ const bearingKey = usesDemo ? "bearing" : props.bearingKey ?? "bearing";
3872
+ const valueKey = usesDemo ? "speed" : props.valueKey ?? "speed";
3873
+ const { locale } = context;
3874
+ const bearingName = accessorName(bearingKey, "bearing");
3875
+ const speedName = accessorName(valueKey, "speed");
3876
+ const count = props.sectors === void 0 || COUNTS.includes(props.sectors) ? props.sectors ?? DEFAULT_SECTORS : DEFAULT_SECTORS;
3877
+ if (count !== (props.sectors ?? DEFAULT_SECTORS)) warnValue(CHART20, "sectors", `${String(props.sectors)} sectors cannot divide a compass; ${DEFAULT_SECTORS} are drawn.`);
3878
+ const bins = props.bins === void 0 ? DEFAULT_BINS : validBins(props.bins) ? props.bins : DEFAULT_BINS;
3879
+ if (bins !== (props.bins ?? DEFAULT_BINS)) warnValue(CHART20, "bins", "The speed bins must be positive and ascending; the defaults are used.");
3880
+ const binLabels = [
3881
+ `< ${formatNumber(bins[0], locale, void 0)}`,
3882
+ ...bins.slice(1).map((b, i) => `${formatNumber(bins[i], locale, void 0)}\u2013${formatNumber(b, locale, void 0)}`),
3883
+ `\u2265 ${formatNumber(bins.at(-1), locale, void 0)}`
3884
+ ];
3885
+ const width = 2 * Math.PI / count;
3886
+ const tally = Array.from({ length: count }, () => new Array(binLabels.length).fill(0));
3887
+ let calm = 0;
3888
+ let observed = 0;
3889
+ data.forEach((datum, index) => {
3890
+ const bearing = finite(read(bearingKey, datum, index));
3891
+ const speed = finite(read(valueKey, datum, index));
3892
+ if (bearing === void 0 || speed === void 0 || speed < 0) {
3893
+ warnValue(CHART20, bearing === void 0 ? bearingName : speedName, `Row ${index} lacks a finite bearing or a speed \u2265 0; it is omitted.`);
3894
+ return;
3895
+ }
3896
+ observed += 1;
3897
+ if (speed === 0) {
3898
+ calm += 1;
3899
+ return;
3900
+ }
3901
+ const k = Math.round((bearing % 360 + 360) % 360 / (360 / count)) % count;
3902
+ const bin = bins.filter((b) => speed >= b).length;
3903
+ tally[k][bin] = tally[k][bin] + 1;
3904
+ });
3905
+ const pct = (n) => observed > 0 ? n / observed * 100 : 0;
3906
+ const printed = (n) => `${formatNumber(pct(n), locale, { maximumFractionDigits: 0 })}%`;
3907
+ const totals = tally.map((row) => row.reduce((a, b) => a + b, 0));
3908
+ const base = modelBase(CHART20, props, context, "Wind rose", {
3909
+ columns: ["direction", "share", ...binLabels],
3910
+ rows: tally.map((row, k) => [compass(k, count, locale), printed(totals[k]), ...row.map(printed)])
3911
+ });
3912
+ const size = measure(base, props, context);
3913
+ if ("deferred" in size) return size.deferred;
3914
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
3915
+ const plot = card.area;
3916
+ const strokes = [...card.strokes];
3917
+ const labels = [...card.labels];
3918
+ const hitAreas = [];
3919
+ if (observed === 0) return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
3920
+ const withLegend = plot.width >= plot.height * LEGEND_ASPECT2;
3921
+ const roseArea = withLegend ? { x: plot.x, y: plot.y, width: plot.height, height: plot.height } : plot;
3922
+ const frame = polarFrame(roseArea, RIM_LABELS);
3923
+ const largest = Math.max(...totals.map(pct));
3924
+ const radius = linearScale([0, Math.max(largest, Number.MIN_VALUE)], [0, frame.radius], { chart: CHART20, property: "share", padding: 0, nice: true });
3925
+ for (const tick of radius.ticks(3)) {
3926
+ const r = radius(tick);
3927
+ if (r <= 0 || r > frame.radius + 0.01) continue;
3928
+ strokes.push({ d: arcPath(frame, r, 0, 2 * Math.PI), role: "ornament", part: "grid" });
3929
+ if (r > frame.radius - RIM_LABELS) continue;
3930
+ const at2 = pointAt(frame, width / 2, r);
3931
+ labels.push({ x: at2.x + 2, y: at2.y - 2, text: `${formatNumber(tick, locale, { maximumFractionDigits: 0 })}%`, kind: "tick", part: "axis", anchor: "start" });
3932
+ }
3933
+ tally.forEach((row, k) => {
3934
+ const start = k * width - width / 2 + width * SECTOR_GAP;
3935
+ const end = (k + 1) * width - width / 2 - width * SECTOR_GAP;
3936
+ let running = 0;
3937
+ row.forEach((n, bin) => {
3938
+ if (n === 0) return;
3939
+ const inner = radius(pct(running));
3940
+ running += n;
3941
+ const d = sectorPath(frame, { inner, outer: radius(pct(running)), start, end });
3942
+ if (d) strokes.push({ d, role: "encoding", part: "ink", tone: BIN_TONES[bin % BIN_TONES.length] });
3943
+ });
3944
+ const tip = pointAt(frame, k * width, radius(pct(running)));
3945
+ const direction = compass(k, count, locale);
3946
+ hitAreas.push({ seriesKey: "share", index: k, datum: { direction, share: pct(running) }, value: pct(running), x: tip.x, y: tip.y });
3947
+ });
3948
+ labels.push(...rimLabels(frame, tally.map((_, k) => ({ angle: k * width, text: compass(k, count, locale) })), 16));
3949
+ const calmText = `calm ${printed(calm)}`;
3950
+ if (withLegend) {
3951
+ const x = roseArea.x + roseArea.width + LEGEND_GAP3;
3952
+ const area5 = { x, y: plot.y, width: Math.max(plot.x + plot.width - x, 1), height: plot.height - 16 };
3953
+ const binTotals = binLabels.map((_, bin) => tally.reduce((sum, row) => sum + row[bin], 0));
3954
+ const key = sectorLegend(area5, binLabels.map((name, bin) => ({ name, share: printed(binTotals[bin]), tone: BIN_TONES[bin % BIN_TONES.length] })));
3955
+ strokes.push(...key.strokes);
3956
+ labels.push(...key.labels);
3957
+ labels.push({ x: x + 14, y: plot.y + plot.height - 4, text: calmText, kind: "tick", part: "axis", anchor: "start" });
3958
+ } else {
3959
+ labels.push({ x: plot.x, y: plot.y + plot.height - 4, text: calmText, kind: "tick", part: "axis", anchor: "start" });
3960
+ }
3961
+ const prevailing = totals.indexOf(Math.max(...totals));
3962
+ const description = props.description ?? `${base.name}. Wind rose of ${observed} observations in ${count} sectors; ${totals[prevailing] > 0 ? `most often from ${compass(prevailing, count, locale)} (${printed(totals[prevailing])})` : "no wind from any direction"}; calm ${printed(calm)}.`;
3963
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
3964
+ }
3965
+ var windRose = /* @__PURE__ */ Object.freeze({ name: "WindRose", build: buildWindRose });
3966
+
3967
+ // src/charts/chord-ring/demo.ts
3968
+ var CHORD_RING_DEMO = /* @__PURE__ */ Object.freeze(
3969
+ /* @__PURE__ */ [
3970
+ { source: "Home", target: "Shop", value: 42 },
3971
+ { source: "Home", target: "Blog", value: 28 },
3972
+ { source: "Blog", target: "Shop", value: 16 },
3973
+ { source: "Shop", target: "Cart", value: 35 },
3974
+ { source: "Cart", target: "Shop", value: 12 },
3975
+ { source: "Blog", target: "Home", value: 9 },
3976
+ { source: "Help", target: "Cart", value: 7 },
3977
+ { source: "Shop", target: "Help", value: 11 }
3978
+ ].map((row) => Object.freeze(row))
3979
+ );
3980
+
3981
+ // src/charts/chord-ring/chord-ring.ts
3982
+ var CHART21 = "ChordRing";
3983
+ var CEILING = 12;
3984
+ var OTHER = "Other";
3985
+ var BAND2 = 0.08;
3986
+ var PAD = 0.04;
3987
+ function along(frame, r, from, to) {
3988
+ const p = pointAt(frame, to, r);
3989
+ return `A${r},${r},0,${to - from > Math.PI ? 1 : 0},1,${p.x},${p.y}`;
3990
+ }
3991
+ function ribbonPath(frame, r, s0, s1, t0, t1) {
3992
+ const start = pointAt(frame, s0, r);
3993
+ const target = pointAt(frame, t0, r);
3994
+ const c = `${frame.cx},${frame.cy}`;
3995
+ return `M${start.x},${start.y}${along(frame, r, s0, s1)}Q${c},${target.x},${target.y}${along(frame, r, t0, t1)}Q${c},${start.x},${start.y}Z`;
3996
+ }
3997
+ function buildChordRing(props, context) {
3998
+ const usesDemo = props.data === void 0;
3999
+ const data = props.data ?? CHORD_RING_DEMO;
4000
+ const sourceKey = usesDemo ? "source" : props.sourceKey ?? "source";
4001
+ const targetKey = usesDemo ? "target" : props.targetKey ?? "target";
4002
+ const valueKey = usesDemo ? "value" : props.valueKey ?? "value";
4003
+ const { locale } = context;
4004
+ const numberFormat = props.numberFormat;
4005
+ const valueName = accessorName(valueKey, "value");
4006
+ const most = props.maxCategories !== void 0 && Number.isInteger(props.maxCategories) && props.maxCategories >= 2 ? props.maxCategories : CEILING;
4007
+ const flows = [];
4008
+ data.forEach((datum, index) => {
4009
+ const rawSource = read(sourceKey, datum, index);
4010
+ const rawTarget = read(targetKey, datum, index);
4011
+ const value = finite(read(valueKey, datum, index));
4012
+ if (rawSource === void 0 || rawSource === null || rawSource === "" || rawTarget === void 0 || rawTarget === null || rawTarget === "" || value === void 0 || value <= 0) {
4013
+ warnValue(CHART21, valueName, `Row ${index} lacks a source, a target or a ${valueName} > 0; it is omitted.`);
4014
+ return;
4015
+ }
4016
+ flows.push({ index, source: formatCategory(rawSource, locale), target: formatCategory(rawTarget, locale), value });
4017
+ });
4018
+ const throughput = /* @__PURE__ */ new Map();
4019
+ for (const f of flows) {
4020
+ throughput.set(f.source, (throughput.get(f.source) ?? 0) + f.value);
4021
+ throughput.set(f.target, (throughput.get(f.target) ?? 0) + f.value);
4022
+ }
4023
+ const all = [...throughput.keys()];
4024
+ let other = OTHER;
4025
+ while (all.includes(other)) other = `${other} (merged)`;
4026
+ if (process.env.NODE_ENV !== "production" && (all.length > CEILING || all.length > most)) {
4027
+ diagnose("SP010", CHART21, {
4028
+ property: "maxCategories",
4029
+ message: `${all.length} categories${all.length > most ? `; the ${all.length - most + 1} smallest are merged into "${other}"` : ""}.`
4030
+ });
4031
+ }
4032
+ const kept = new Set(all.length > most ? [...all].sort((a, b) => (throughput.get(b) ?? 0) - (throughput.get(a) ?? 0)).slice(0, most - 1) : all);
4033
+ const categories = [...all.filter((c) => kept.has(c)), ...all.length > most ? [other] : []];
4034
+ const slot = (name) => categories.indexOf(kept.has(name) ? name : other);
4035
+ const pairs = /* @__PURE__ */ new Map();
4036
+ const seenPairs = /* @__PURE__ */ new Map();
4037
+ for (const f of flows) {
4038
+ const raw = `${f.source}\0${f.target}`;
4039
+ const earlier = seenPairs.get(raw);
4040
+ if (earlier === void 0) seenPairs.set(raw, f.index);
4041
+ else warnValue(CHART21, valueName, `Rows ${earlier} and ${f.index} both run ${f.source} \u2192 ${f.target}; their values are summed into one ribbon.`);
4042
+ const [i, j] = [slot(f.source), slot(f.target)];
4043
+ const pair = pairs.get(`${i}>${j}`);
4044
+ if (pair) pair.value += f.value;
4045
+ else pairs.set(`${i}>${j}`, { i, j, index: f.index, value: f.value });
4046
+ }
4047
+ const ribbonsInOrder = [...pairs.values()];
4048
+ const base = modelBase(CHART21, props, context, "Chord ring", {
4049
+ columns: ["flow", valueName],
4050
+ rows: ribbonsInOrder.map((p) => [`${categories[p.i]} \u2192 ${categories[p.j]}`, formatValue(p.value, locale, numberFormat)])
4051
+ });
4052
+ const size = measure(base, props, context);
4053
+ if ("deferred" in size) return size.deferred;
4054
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
4055
+ const plot = card.area;
4056
+ const strokes = [...card.strokes];
4057
+ const labels = [...card.labels];
4058
+ const hitAreas = [];
4059
+ if (flows.length === 0) return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
4060
+ const n = categories.length;
4061
+ const matrix = Array.from({ length: n }, () => new Array(n).fill(0));
4062
+ for (const p of ribbonsInOrder) matrix[p.i][p.j] = p.value;
4063
+ const chords = chordDirected().padAngle(Math.min(PAD, Math.PI / 2 / n))(matrix);
4064
+ const frame = polarFrame(plot, RIM_LABELS);
4065
+ const inner = frame.radius * (1 - BAND2);
4066
+ chords.groups.forEach((g) => {
4067
+ const d = sectorPath(frame, { inner, outer: frame.radius, start: g.startAngle, end: g.endAngle });
4068
+ if (d) strokes.push({ d, role: "encoding", part: "ink", tone: ringTone(g.index, n) });
4069
+ });
4070
+ const order = (c) => {
4071
+ const position = ribbonsInOrder.indexOf(pairs.get(`${c.source.index}>${c.target.index}`));
4072
+ return position < 0 ? Number.MAX_SAFE_INTEGER : position;
4073
+ };
4074
+ for (const c of [...chords].sort((x, y) => order(x) - order(y))) {
4075
+ strokes.push({ d: ribbonPath(frame, inner, c.source.startAngle, c.source.endAngle, c.target.startAngle, c.target.endAngle), role: "encoding", part: "ink", tone: ringTone(c.source.index, n) });
4076
+ const pair = pairs.get(`${c.source.index}>${c.target.index}`);
4077
+ if (!pair) continue;
4078
+ const a = pointAt(frame, (c.source.startAngle + c.source.endAngle) / 2, inner);
4079
+ const b = pointAt(frame, (c.target.startAngle + c.target.endAngle) / 2, inner);
4080
+ const datum = data[pair.index] ?? {};
4081
+ hitAreas.push({ seriesKey: valueName, index: ribbonsInOrder.indexOf(pair), datum, value: c.source.value, x: 0.25 * a.x + 0.5 * frame.cx + 0.25 * b.x, y: 0.25 * a.y + 0.5 * frame.cy + 0.25 * b.y });
4082
+ }
4083
+ labels.push(...rimLabels(frame, chords.groups.map((g) => ({ angle: (g.startAngle + g.endAngle) / 2, text: categories[g.index] }))));
4084
+ const busiest = chords.groups.reduce((x, y) => y.value > x.value ? y : x);
4085
+ const description = props.description ?? `${base.name}. Chord ring of ${flows.length} flows between ${n} categories; the busiest is ${categories[busiest.index]}, sending ${formatNumber(
4086
+ matrix[busiest.index].reduce((s, v) => s + v, 0),
4087
+ locale,
4088
+ numberFormat
4089
+ )}.`;
4090
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
4091
+ }
4092
+ var chordRing = /* @__PURE__ */ Object.freeze({ name: "ChordRing", build: buildChordRing });
4093
+
4094
+ // src/charts/orbit-chart/demo.ts
4095
+ var ORBIT_CHART_DEMO = /* @__PURE__ */ Object.freeze(
4096
+ /* @__PURE__ */ [
4097
+ { label: "2023", markers: Object.freeze([Object.freeze({ period: 0.08, value: 12 }), Object.freeze({ period: 0.33, value: 30 }), Object.freeze({ period: 0.61, value: 18 }), Object.freeze({ period: 0.9, value: 24 })]) },
4098
+ { label: "2024", markers: Object.freeze([Object.freeze({ period: 0.12, value: 20 }), Object.freeze({ period: 0.4, value: 42 }), Object.freeze({ period: 0.66, value: 15 }), Object.freeze({ period: 0.87, value: 33 })]) },
4099
+ { label: "2025", markers: Object.freeze([Object.freeze({ period: 0.05, value: 26 }), Object.freeze({ period: 0.29, value: 38 }), Object.freeze({ period: 0.55, value: 50 }), Object.freeze({ period: 0.81, value: 21 })]) }
4100
+ ].map((row) => Object.freeze(row))
4101
+ );
4102
+
4103
+ // src/charts/orbit-chart/orbit-chart.ts
4104
+ var CHART22 = "OrbitChart";
4105
+ var TILT = 0.42;
4106
+ var HOLE4 = 0.3;
4107
+ var MARKER = 6;
4108
+ function ellipsePath(cx, cy, a, b) {
4109
+ return `M${cx},${cy - b}A${a},${b},0,1,1,${cx},${cy + b}A${a},${b},0,1,1,${cx},${cy - b}Z`;
4110
+ }
4111
+ function buildOrbitChart(props, context) {
4112
+ const usesDemo = props.data === void 0;
4113
+ const all = props.data ?? ORBIT_CHART_DEMO;
4114
+ const markerKey = usesDemo ? "markers" : props.markerKey ?? "markers";
4115
+ const periodKey = usesDemo ? "period" : props.periodKey ?? "period";
4116
+ const { locale } = context;
4117
+ const numberFormat = props.numberFormat;
4118
+ const shown = props.orbits !== void 0 && Number.isInteger(props.orbits) && props.orbits > 0 ? Math.min(props.orbits, all.length) : all.length;
4119
+ const data = all.slice(0, shown);
4120
+ const periodName = accessorName(periodKey, "period");
4121
+ const rows = [];
4122
+ const markers = [];
4123
+ data.forEach((datum, orbit) => {
4124
+ const label = formatCategory(datum.label, locale);
4125
+ const list = read(markerKey, datum, orbit);
4126
+ let kept = 0;
4127
+ (Array.isArray(list) ? list : []).forEach((raw, column) => {
4128
+ const m = typeof raw === "object" && raw !== null ? raw : {};
4129
+ const period = finite(read(periodKey, m, column));
4130
+ const value = finite(m.value);
4131
+ rows.push([label, formatValue(period, locale, void 0), formatValue(value, locale, numberFormat)]);
4132
+ const flat = rows.length - 1;
4133
+ if (period === void 0 || period < 0 || period > 1 || value === void 0 || value < 0) {
4134
+ warnValue(CHART22, periodName, `Marker ${column} of orbit "${label}" needs a period in 0-1 and a value \u2265 0; it is omitted.`);
4135
+ return;
4136
+ }
4137
+ markers.push({ flat, orbit, column: kept++, datum: { orbit: label, ...m }, period, value });
4138
+ });
4139
+ });
4140
+ const base = modelBase(CHART22, props, context, "Orbit chart", { columns: ["orbit", periodName, "value"], rows });
4141
+ const size = measure(base, props, context);
4142
+ if ("deferred" in size) return size.deferred;
4143
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
4144
+ const plot = card.area;
4145
+ const strokes = [...card.strokes];
4146
+ const labels = [...card.labels];
4147
+ const hitAreas = [];
4148
+ if (data.length === 0) return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, all.length === 0);
4149
+ const cx = plot.x + plot.width / 2;
4150
+ const cy = plot.y + plot.height / 2;
4151
+ const outer = Math.max(Math.min(plot.width / 2, plot.height / 2 / TILT) - PLOT_INSET - MARKER, 1);
4152
+ const hole = outer * HOLE4;
4153
+ const widthOf = (orbit) => data.length === 1 ? outer : hole + orbit / (data.length - 1) * (outer - hole);
4154
+ data.forEach((datum, orbit) => {
4155
+ const a = widthOf(orbit);
4156
+ strokes.push({ d: ellipsePath(cx, cy, a, a * TILT), role: "ornament", part: "grid" });
4157
+ labels.push({ x: cx - a + 3, y: cy + 3, text: formatCategory(datum.label, locale), kind: "tick", part: "axis", anchor: "start" });
4158
+ });
4159
+ const largest = Math.max(0, ...markers.map((m) => m.value));
4160
+ for (const m of markers) {
4161
+ const a = widthOf(m.orbit);
4162
+ const angle = m.period * 2 * Math.PI;
4163
+ const x = cx + a * Math.sin(angle);
4164
+ const y = cy - a * TILT * Math.cos(angle);
4165
+ const r = largest > 0 ? MARKER * Math.sqrt(m.value / largest) : 0;
4166
+ if (r > 0) strokes.push({ d: circlePath(x, y, r), role: "encoding", part: "ink", paint: "fill" });
4167
+ hitAreas.push({ seriesKey: "value", index: m.flat, datum: m.datum, value: m.value, x, y, box: { x: x - r, y: y - r, width: 2 * r, height: 2 * r }, cell: { column: m.column, row: m.orbit } });
4168
+ }
4169
+ const description = props.description ?? `${base.name}. ${data.length} orbits from the inside out (${data.map((d) => formatCategory(d.label, locale)).join(", ")}) with ${markers.length} markers placed by ${periodName} over the cycle; the largest is ${formatNumber(largest, locale, numberFormat)}.`;
4170
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
4171
+ }
4172
+ var orbitChart = /* @__PURE__ */ Object.freeze({ name: "OrbitChart", build: buildOrbitChart });
4173
+
4174
+ // src/charts/volvelle-chart/demo.ts
4175
+ var VOLVELLE_CHART_DEMO = /* @__PURE__ */ Object.freeze(
4176
+ /* @__PURE__ */ [
4177
+ { label: "Day", segments: Object.freeze(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]) },
4178
+ { label: "Shift", segments: Object.freeze(["Early", "Late", "Night"]) },
4179
+ { label: "Team", segments: Object.freeze(["Atlas", "Borealis", "Cygnus", "Draco", "Eridanus"]) }
4180
+ ].map((row) => Object.freeze(row))
4181
+ );
4182
+
4183
+ // src/charts/volvelle-chart/volvelle-chart.ts
4184
+ var CHART23 = "VolvelleChart";
4185
+ var HUB = 0.22;
4186
+ var POINTER = 7;
4187
+ var CHAR2 = 5.2;
4188
+ var CAP = 7;
4189
+ var AIR = 1;
4190
+ function fits(frame, name, at2, inner, outer, start, end) {
4191
+ const width = name.length * CHAR2;
4192
+ const corners = [
4193
+ [at2.x - width / 2, at2.y + 3 - CAP],
4194
+ [at2.x + width / 2, at2.y + 3 - CAP],
4195
+ [at2.x - width / 2, at2.y + 3],
4196
+ [at2.x + width / 2, at2.y + 3]
4197
+ ];
4198
+ const middle = (start + end) / 2;
4199
+ return corners.every(([x, y]) => {
4200
+ const r = Math.hypot(x - frame.cx, y - frame.cy);
4201
+ let a = Math.atan2(x - frame.cx, frame.cy - y) - middle;
4202
+ a = Math.atan2(Math.sin(a), Math.cos(a));
4203
+ return r >= inner + AIR && r <= outer - AIR && Math.abs(a) <= (end - start) / 2 - AIR / Math.max(r, 1);
4204
+ });
4205
+ }
4206
+ function buildVolvelleChart(props, context) {
4207
+ const usesDemo = props.data === void 0;
4208
+ const all = props.data ?? VOLVELLE_CHART_DEMO;
4209
+ const { locale } = context;
4210
+ const cap = props.rings !== void 0 && Number.isInteger(props.rings) && props.rings > 0 ? props.rings : all.length;
4211
+ const rings = [];
4212
+ all.slice(0, cap).forEach((datum, index) => {
4213
+ const segments = datum.segments;
4214
+ if (!Array.isArray(segments) || segments.length === 0) {
4215
+ warnValue(CHART23, "segments", `Ring ${index} has no list of segments; it is omitted.`);
4216
+ return;
4217
+ }
4218
+ checkSectors(CHART23, "segments", segments.length);
4219
+ rings.push({ label: formatCategory(datum.label, locale), segments: segments.map((s) => formatCategory(s, locale)), given: index });
4220
+ });
4221
+ if (usesDemo && (props.indexRing !== void 0 || props.indexValue !== void 0)) {
4222
+ warnValue(CHART23, props.indexValue !== void 0 ? "indexValue" : "indexRing", "Without `data` the demo keeps its own index; `indexRing` and `indexValue` apply to your rings.");
4223
+ }
4224
+ let ringIndex = usesDemo || props.indexRing === void 0 ? 0 : rings.findIndex((r) => r.given === props.indexRing);
4225
+ if (ringIndex < 0) {
4226
+ if (rings.length > 0) warnValue(CHART23, "indexRing", `Ring ${String(props.indexRing)} does not exist or has no segments; the index reads the first ring.`);
4227
+ ringIndex = 0;
4228
+ }
4229
+ const turned = rings[ringIndex];
4230
+ let segment = turned && !usesDemo && props.indexValue !== void 0 ? turned.segments.indexOf(props.indexValue) : 0;
4231
+ if (segment < 0) {
4232
+ warnValue(CHART23, "indexValue", `"${String(props.indexValue)}" is not on ring "${turned?.label}"; the index reads its first segment.`);
4233
+ segment = 0;
4234
+ }
4235
+ const indexAngle = turned ? (segment + 0.5) * 2 * Math.PI / turned.segments.length : 0;
4236
+ const n = turned?.segments.length ?? 1;
4237
+ const underIndex = rings.map((r) => Math.floor((2 * segment + 1) * r.segments.length / (2 * n)));
4238
+ const combined = rings.map((r, k) => `${r.label} ${r.segments[underIndex[k]]}`).join(" \xB7 ");
4239
+ const base = modelBase(CHART23, props, context, "Volvelle", {
4240
+ columns: ["ring", "segment", "under the index"],
4241
+ rows: rings.flatMap((r, k) => r.segments.map((s, j) => [r.label, s, j === underIndex[k] ? "yes" : "no"]))
4242
+ });
4243
+ const size = measure(base, props, context);
4244
+ if ("deferred" in size) return size.deferred;
4245
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
4246
+ const plot = card.area;
4247
+ const strokes = [...card.strokes];
4248
+ const labels = [...card.labels];
4249
+ const hitAreas = [];
4250
+ if (rings.length === 0) return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, all.length === 0);
4251
+ const frame = polarFrame(plot, Math.max(POINTER, RIM_LABELS));
4252
+ const hub = frame.radius * HUB;
4253
+ const band = (frame.radius - hub) / rings.length;
4254
+ const turn = -indexAngle;
4255
+ let flat = 0;
4256
+ rings.forEach((r, k) => {
4257
+ const inner = hub + k * band;
4258
+ const outer = inner + band;
4259
+ const width = 2 * Math.PI / r.segments.length;
4260
+ r.segments.forEach((name, j) => {
4261
+ const start = j * width + turn;
4262
+ const aligned = j === underIndex[k];
4263
+ const d = sectorPath(frame, { inner, outer, start, end: start + width });
4264
+ if (d) strokes.push({ d, role: "encoding", part: "ink", ...aligned ? { tone: 3 } : {} });
4265
+ const middle = pointAt(frame, start + width / 2, (inner + outer) / 2);
4266
+ if (fits(frame, name, middle, inner, outer, start, start + width)) {
4267
+ labels.push({ x: middle.x, y: middle.y + 3, text: name, kind: "tick", part: "text", anchor: "middle" });
4268
+ } else if (k === rings.length - 1 && !aligned) {
4269
+ labels.push(polarLabel(frame, start + width / 2, outer + 8, name));
4270
+ }
4271
+ hitAreas.push({ seriesKey: "segment", index: flat, datum: { ring: r.label, segment: name }, value: j, x: middle.x, y: middle.y, cell: { column: j, row: k } });
4272
+ flat += 1;
4273
+ });
4274
+ });
4275
+ const tip = frame.cy - frame.radius - 1;
4276
+ strokes.push({ d: `M${frame.cx - 4},${tip - POINTER + 1}L${frame.cx + 4},${tip - POINTER + 1}L${frame.cx},${tip}Z`, role: "ornament", part: "ink", paint: "fill" });
4277
+ labels.push({ x: plot.x, y: plot.y + plot.height - 4, text: combined, kind: "tick", part: "text", anchor: "start" });
4278
+ const description = props.description ?? `${base.name}. Volvelle of ${rings.length} rings from the inside out; under the index: ${combined}.`;
4279
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
4280
+ }
4281
+ var volvelleChart = /* @__PURE__ */ Object.freeze({ name: "VolvelleChart", build: buildVolvelleChart });
4282
+
4283
+ // src/charts/candlestick-chart/demo.ts
4284
+ var CANDLESTICK_CHART_DEMO = /* @__PURE__ */ Object.freeze(
4285
+ /* @__PURE__ */ [
4286
+ { time: "Mon", open: 102, high: 108, low: 99, close: 106 },
4287
+ { time: "Tue", open: 106, high: 109, low: 101, close: 103 },
4288
+ { time: "Wed", open: 103, high: 105, low: 97, close: 99 },
4289
+ { time: "Thu", open: 99, high: 104, low: 96, close: 103 },
4290
+ { time: "Fri", open: 103, high: 111, low: 102, close: 110 },
4291
+ { time: "Mon", open: 110, high: 114, low: 107, close: 108 },
4292
+ { time: "Tue", open: 108, high: 110, low: 104, close: 109 },
4293
+ { time: "Wed", open: 109, high: 116, low: 108, close: 115 }
4294
+ ].map((row) => Object.freeze(row))
4295
+ );
4296
+
4297
+ // src/charts/candlestick-chart/candlestick-chart.ts
4298
+ var CHART24 = "CandlestickChart";
4299
+ var BODY_FILL = 0.6;
4300
+ function buildCandlestickChart(props, context) {
4301
+ const usesDemo = props.data === void 0;
4302
+ const data = props.data ?? CANDLESTICK_CHART_DEMO;
4303
+ const timeKey = usesDemo ? "time" : props.timeKey ?? "time";
4304
+ const keys = {
4305
+ open: usesDemo ? "open" : props.openKey ?? "open",
4306
+ high: usesDemo ? "high" : props.highKey ?? "high",
4307
+ low: usesDemo ? "low" : props.lowKey ?? "low",
4308
+ close: usesDemo ? "close" : props.closeKey ?? "close"
4309
+ };
4310
+ const names = { open: accessorName(keys.open, "open"), high: accessorName(keys.high, "high"), low: accessorName(keys.low, "low"), close: accessorName(keys.close, "close") };
4311
+ const { locale } = context;
4312
+ const numberFormat = props.numberFormat;
4313
+ checkCount(CHART24, names.close, data.length);
4314
+ const candles = [];
4315
+ data.forEach((datum, index) => {
4316
+ const [open, high, low, close] = ["open", "high", "low", "close"].map((k) => finite(read(keys[k], datum, index)));
4317
+ if (open === void 0 || high === void 0 || low === void 0 || close === void 0) {
4318
+ warnValue(CHART24, names.close, `Row ${index} lacks a finite open, high, low or close; it is omitted.`);
4319
+ return;
4320
+ }
4321
+ if (!(low <= Math.min(open, close) && Math.max(open, close) <= high)) {
4322
+ warnValue(CHART24, names.high, `Row ${index} breaks low \u2264 min(open, close) \u2264 max(open, close) \u2264 high; it is omitted.`);
4323
+ return;
4324
+ }
4325
+ candles.push({ index, datum, open, high, low, close });
4326
+ });
4327
+ const timeName = accessorName(timeKey, "time");
4328
+ const timeLabels = data.map((datum, index) => formatCategory(read(timeKey, datum, index), locale));
4329
+ const base = modelBase(CHART24, props, context, "Candlestick chart", {
4330
+ columns: [timeName, names.open, names.high, names.low, names.close],
4331
+ rows: data.map((datum, index) => [timeLabels[index] ?? "", ...["open", "high", "low", "close"].map((k) => formatValue(finite(read(keys[k], datum, index)), locale, numberFormat))])
4332
+ });
4333
+ const size = measure(base, props, context);
4334
+ if ("deferred" in size) return size.deferred;
4335
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
4336
+ const plot = cartesianPlot(card.area);
4337
+ const strokes = [...card.strokes];
4338
+ const labels = [...card.labels];
4339
+ const hitAreas = [];
4340
+ const pinned = props.bounds && Number.isFinite(props.bounds[0]) && Number.isFinite(props.bounds[1]) && props.bounds[0] < props.bounds[1] ? props.bounds : void 0;
4341
+ if (props.bounds !== void 0 && !pinned) warnValue(CHART24, "bounds", `[${String(props.bounds)}] is not two finite prices, lowest first; the bounds are derived from the rows.`);
4342
+ if (data.length === 0) return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, true);
4343
+ if (candles.length === 0) {
4344
+ if (!pinned) diagnose("SP009", CHART24, { property: "bounds", message: `None of the ${data.length} rows is a valid candle: open, high, low and close must be finite numbers with low \u2264 min(open, close) \u2264 max(open, close) \u2264 high.` });
4345
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, false);
4346
+ }
4347
+ const bounds = pinned ?? [Math.min(...candles.map((c) => c.low)), Math.max(...candles.map((c) => c.high))];
4348
+ const axis = valueAxis(plot, bounds, { chart: CHART24, property: "bounds", padding: context.domainPadding, locale, numberFormat, zero: false, nice: !pinned });
4349
+ const [floor, ceiling] = bounds;
4350
+ const outside = candles.filter((c) => c.low < floor || c.high > ceiling).length;
4351
+ if (outside > 0) warnValue(CHART24, "bounds", `${outside} candles reach outside the pinned bounds; they are drawn clamped to them.`);
4352
+ const y = (price) => axis.scale(Math.min(Math.max(price, floor), ceiling));
4353
+ strokes.push(...axis.strokes);
4354
+ labels.push(...axis.labels);
4355
+ const slots = categoryAxis(timeLabels, plot, { chart: CHART24, property: timeName, padding: context.domainPadding, numeric: false, paddingInner: 0.25, paddingOuter: 0.1 });
4356
+ const width = slots.bandwidth * BODY_FILL;
4357
+ for (const c of candles) {
4358
+ const x = slots.at(c.index);
4359
+ strokes.push({ d: `M${x},${y(c.high)}V${y(c.low)}`, role: "encoding", part: "ink" });
4360
+ const top = y(Math.max(c.open, c.close));
4361
+ const body = { x: x - width / 2, y: top, width, height: y(Math.min(c.open, c.close)) - top };
4362
+ if (body.height > 0) {
4363
+ strokes.push(c.close >= c.open ? { d: rectPath(body), role: "encoding", part: "ink" } : { d: rectPath(body), role: "encoding", part: "ink", paint: "fill" });
4364
+ } else {
4365
+ strokes.push({ d: `M${body.x},${top}H${body.x + width}`, role: "encoding", part: "ink" });
4366
+ }
4367
+ hitAreas.push({
4368
+ seriesKey: names.close,
4369
+ index: c.index,
4370
+ datum: c.datum,
4371
+ value: c.close,
4372
+ x,
4373
+ y: y(c.close),
4374
+ box: { x: x - slots.bandwidth / 2, y: y(c.high), width: slots.bandwidth, height: y(c.low) - y(c.high) }
4375
+ });
4376
+ }
4377
+ labels.push(...categoryLabels(timeLabels, slots.at, card.area, plot));
4378
+ const rising = candles.filter((c) => c.close >= c.open).length;
4379
+ const description = props.description ?? `${base.name}. Candlestick chart of ${candles.length} sessions, prices from ${formatNumber(bounds[0], locale, numberFormat)} to ${formatNumber(bounds[1], locale, numberFormat)}${pinned ? " (pinned bounds)" : ", from the lowest low to the highest high"}; ${rising} rise and ${candles.length - rising} fall.`;
4380
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
4381
+ }
4382
+ var candlestickChart = /* @__PURE__ */ Object.freeze({ name: CHART24, build: buildCandlestickChart });
4383
+
4384
+ // src/charts/area-chart/demo.ts
4385
+ var AREA_CHART_DEMO = /* @__PURE__ */ Object.freeze(
4386
+ /* @__PURE__ */ [
4387
+ { x: "Mon", value: 32 },
4388
+ { x: "Tue", value: 45 },
4389
+ { x: "Wed", value: 41 },
4390
+ { x: "Thu", value: 58 },
4391
+ { x: "Fri", value: 66 },
4392
+ { x: "Sat", value: 38 },
4393
+ { x: "Sun", value: 29 }
4394
+ ].map((row) => Object.freeze(row))
4395
+ );
4396
+
4397
+ // src/charts/area-chart/area-chart.ts
4398
+ var CHART25 = "AreaChart";
4399
+ var HEIGHTEN_RADIUS3 = 3.5;
4400
+ function buildAreaChart(props, context) {
4401
+ const usesDemo = props.data === void 0;
4402
+ const data = props.data ?? AREA_CHART_DEMO;
4403
+ const xKey = usesDemo ? "x" : props.xKey ?? "x";
4404
+ const valueKey = usesDemo ? "value" : props.valueKey ?? "value";
4405
+ const { locale } = context;
4406
+ const numberFormat = props.numberFormat;
4407
+ const series = readSeries(data, valueKey, "value");
4408
+ checkVolume(CHART25, [series]);
4409
+ const xName = accessorName(xKey, "x");
4410
+ const xValues = data.map((datum, index) => read(xKey, datum, index));
4411
+ const xLabels = xValues.map((value) => formatCategory(value, locale));
4412
+ const base = modelBase(CHART25, props, context, "Area chart", {
4413
+ columns: [xName, series.key],
4414
+ rows: data.map((_, i) => [xLabels[i] ?? "", formatValue(series.points[i]?.value, locale, numberFormat)])
4415
+ });
4416
+ const size = measure(base, props, context);
4417
+ if ("deferred" in size) return size.deferred;
4418
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
4419
+ const plot = cartesianPlot(card.area);
4420
+ const strokes = [...card.strokes];
4421
+ const labels = [...card.labels];
4422
+ const hitAreas = [];
4423
+ if (series.missing > 0 && data.length > 0) warnValue(CHART25, series.key, `${series.missing} of ${data.length} values are not finite; the area breaks there.`);
4424
+ const range = extent(seriesValues([series]));
4425
+ if (data.length === 0 || range === void 0) {
4426
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
4427
+ }
4428
+ const axis = valueAxis(plot, range, { chart: CHART25, property: series.key, padding: context.domainPadding, locale, numberFormat });
4429
+ const y = axis.scale;
4430
+ strokes.push(...axis.strokes);
4431
+ labels.push(...axis.labels);
4432
+ const xAt = categoryAxis(xValues, plot, { chart: CHART25, property: xName, padding: context.domainPadding }).at;
4433
+ const curve = CURVES2[props.curve ?? "monotone"];
4434
+ const defined = (p) => p.value !== void 0;
4435
+ const shape = area().defined(defined).x((p) => xAt(p.index)).y0(y(0)).y1((p) => y(p.value ?? 0)).curve(curve)(series.points);
4436
+ if (shape) strokes.push({ d: shape, role: "encoding", part: "ink", paint: "stroke", tone: 2 });
4437
+ const top = line().defined(defined).x((p) => xAt(p.index)).y((p) => y(p.value ?? 0)).curve(curve)(series.points);
4438
+ if (top) strokes.push({ d: top, role: "encoding", part: "ink" });
4439
+ for (const p of series.points) {
4440
+ if (p.value === void 0) continue;
4441
+ hitAreas.push({ seriesKey: series.key, index: p.index, datum: p.datum, value: p.value, x: xAt(p.index), y: y(p.value) });
4442
+ }
4443
+ const live = hitAreas.at(-1);
4444
+ if (live) {
4445
+ const mark = circlePath(live.x, live.y, HEIGHTEN_RADIUS3);
4446
+ strokes.push({ d: mark, role: "encoding", part: "heighten", paint: "fill" });
4447
+ strokes.push({ d: mark, role: "encoding", part: "ink", paint: "stroke" });
4448
+ }
4449
+ labels.push(...categoryLabels(xLabels, xAt, card.area, plot));
4450
+ const description = props.description ?? `${base.name}. Area chart of ${data.length} ${xName} values; ${series.key} ranges from ${formatNumber(range[0], locale, numberFormat)} to ${formatNumber(range[1], locale, numberFormat)}.`;
4451
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
4452
+ }
4453
+ var areaChart = /* @__PURE__ */ Object.freeze({ name: CHART25, build: buildAreaChart });
4454
+
4455
+ // src/charts/range-band-chart/demo.ts
4456
+ var RANGE_BAND_CHART_DEMO = /* @__PURE__ */ Object.freeze(
4457
+ /* @__PURE__ */ [
4458
+ { x: "Mon", low: 11, high: 19 },
4459
+ { x: "Tue", low: 12, high: 22 },
4460
+ { x: "Wed", low: 14, high: 24 },
4461
+ { x: "Thu", low: 13, high: 21 },
4462
+ { x: "Fri", low: 10, high: 18 },
4463
+ { x: "Sat", low: 9, high: 20 },
4464
+ { x: "Sun", low: 12, high: 23 }
4465
+ ].map((row) => Object.freeze(row))
4466
+ );
4467
+
4468
+ // src/charts/range-band-chart/range-band-chart.ts
4469
+ var CHART26 = "RangeBandChart";
4470
+ function buildRangeBandChart(props, context) {
4471
+ const usesDemo = props.data === void 0;
4472
+ const data = props.data ?? RANGE_BAND_CHART_DEMO;
4473
+ const xKey = usesDemo ? "x" : props.xKey ?? "x";
4474
+ const lowKey = usesDemo ? "low" : props.lowKey ?? "low";
4475
+ const highKey = usesDemo ? "high" : props.highKey ?? "high";
4476
+ const { locale } = context;
4477
+ const numberFormat = props.numberFormat;
4478
+ const lowName = accessorName(lowKey, "low");
4479
+ const highName = accessorName(highKey, "high");
4480
+ let swapped = 0;
4481
+ let missing = 0;
4482
+ const spans = data.map((datum, index) => {
4483
+ let low2 = finite(read(lowKey, datum, index));
4484
+ let high2 = finite(read(highKey, datum, index));
4485
+ if (low2 === void 0 || high2 === void 0) {
4486
+ missing += 1;
4487
+ return { index, datum, low: void 0, high: void 0 };
4488
+ }
4489
+ if (low2 > high2) {
4490
+ [low2, high2] = [high2, low2];
4491
+ swapped += 1;
4492
+ }
4493
+ return { index, datum, low: low2, high: high2 };
4494
+ });
4495
+ const asSeries = (key, pick) => ({
4496
+ key,
4497
+ points: spans.map((s) => ({ index: s.index, datum: s.datum, value: pick(s) })),
4498
+ missing
4499
+ });
4500
+ checkVolume(CHART26, [asSeries(lowName, (s) => s.low)]);
4501
+ const xName = accessorName(xKey, "x");
4502
+ const xValues = data.map((datum, index) => read(xKey, datum, index));
4503
+ const xLabels = xValues.map((value) => formatCategory(value, locale));
4504
+ const base = modelBase(CHART26, props, context, "Range band", {
4505
+ columns: [xName, lowName, highName],
4506
+ rows: spans.map((s, i) => [xLabels[i] ?? "", formatValue(s.low, locale, numberFormat), formatValue(s.high, locale, numberFormat)])
4507
+ });
4508
+ const size = measure(base, props, context);
4509
+ if ("deferred" in size) return size.deferred;
4510
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
4511
+ const plot = cartesianPlot(card.area);
4512
+ const strokes = [...card.strokes];
4513
+ const labels = [...card.labels];
4514
+ const hitAreas = [];
4515
+ if (swapped > 0) warnValue(CHART26, lowName, `${swapped} rows had ${lowName} above ${highName}; they are swapped.`);
4516
+ if (missing > 0 && data.length > 0) warnValue(CHART26, lowName, `${missing} of ${data.length} rows lack a finite ${lowName} or ${highName}; the band breaks there.`);
4517
+ const range = extent(spans.flatMap((s) => s.low === void 0 || s.high === void 0 ? [] : [s.low, s.high]));
4518
+ if (data.length === 0 || range === void 0) {
4519
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
4520
+ }
4521
+ const axis = valueAxis(plot, range, { chart: CHART26, property: highName, padding: context.domainPadding, locale, numberFormat, zero: false });
4522
+ const y = axis.scale;
4523
+ strokes.push(...axis.strokes);
4524
+ labels.push(...axis.labels);
4525
+ const xAt = categoryAxis(xValues, plot, { chart: CHART26, property: xName, padding: context.domainPadding }).at;
4526
+ const curve = CURVES2.monotone;
4527
+ const defined = (s) => s.low !== void 0;
4528
+ const band = area().defined(defined).x((s) => xAt(s.index)).y0((s) => y(s.low ?? 0)).y1((s) => y(s.high ?? 0)).curve(curve)(spans);
4529
+ if (band) strokes.push({ d: band, role: "encoding", part: "ink-secondary", paint: "none", tone: 1 });
4530
+ const edge = (pick) => line().defined(defined).x((s) => xAt(s.index)).y((s) => y(pick(s))).curve(curve)(spans);
4531
+ const high = edge((s) => s.high ?? 0);
4532
+ const low = edge((s) => s.low ?? 0);
4533
+ if (high) strokes.push({ d: high, role: "encoding", part: "ink" });
4534
+ if (low) strokes.push({ d: low, role: "encoding", part: "ink", dash: "dotted" });
4535
+ for (const [row, key, pick] of [[0, lowName, (s) => s.low], [1, highName, (s) => s.high]]) {
4536
+ for (const s of spans) {
4537
+ const value = pick(s);
4538
+ if (value === void 0) continue;
4539
+ hitAreas.push({ seriesKey: key, index: s.index, datum: s.datum, value, x: xAt(s.index), y: y(value), cell: { column: s.index, row } });
4540
+ }
4541
+ }
4542
+ labels.push(...categoryLabels(xLabels, xAt, card.area, plot));
4543
+ const description = props.description ?? `${base.name}. Range band over ${data.length} ${xName} values, from a lowest ${lowName} of ${formatNumber(range[0], locale, numberFormat)} to a highest ${highName} of ${formatNumber(range[1], locale, numberFormat)}.`;
4544
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
4545
+ }
4546
+ var rangeBandChart = /* @__PURE__ */ Object.freeze({ name: CHART26, build: buildRangeBandChart });
4547
+
4548
+ // src/charts/stream-chart/demo.ts
4549
+ var STREAM_CHART_DEMO = /* @__PURE__ */ Object.freeze(
4550
+ /* @__PURE__ */ [
4551
+ { x: "Mon", organic: 22, paid: 14 },
4552
+ { x: "Tue", organic: 28, paid: 18 },
4553
+ { x: "Wed", organic: 35, paid: 16 },
4554
+ { x: "Thu", organic: 31, paid: 24 },
4555
+ { x: "Fri", organic: 38, paid: 29 },
4556
+ { x: "Sat", organic: 26, paid: 21 },
4557
+ { x: "Sun", organic: 19, paid: 12 }
4558
+ ].map((row) => Object.freeze(row))
4559
+ );
4560
+ var STREAM_CHART_DEMO_KEYS = /* @__PURE__ */ Object.freeze(["organic", "paid"]);
4561
+
4562
+ // src/charts/stream-chart/stream-chart.ts
4563
+ var CHART27 = "StreamChart";
4564
+ var WAVES = 2;
4565
+ function buildStreamChart(props, context) {
4566
+ const usesDemo = props.data === void 0;
4567
+ const data = props.data ?? STREAM_CHART_DEMO;
4568
+ const xKey = usesDemo ? "x" : props.xKey ?? "x";
4569
+ const requested = usesDemo ? STREAM_CHART_DEMO_KEYS : props.keys ?? STREAM_CHART_DEMO_KEYS;
4570
+ const keys = requested.slice(0, WAVES);
4571
+ const stacked = props.stacked ?? false;
4572
+ const { locale } = context;
4573
+ const numberFormat = props.numberFormat;
4574
+ const series = keys.map((key2) => readSeries(data, key2, key2));
4575
+ checkVolume(CHART27, series);
4576
+ const xName = accessorName(xKey, "x");
4577
+ const xValues = data.map((datum, index) => read(xKey, datum, index));
4578
+ const xLabels = xValues.map((value) => formatCategory(value, locale));
4579
+ const base = modelBase(CHART27, props, context, "Stream chart", {
4580
+ columns: [xName, ...keys],
4581
+ rows: data.map((_, i) => [xLabels[i] ?? "", ...series.map((s) => formatValue(s.points[i]?.value, locale, numberFormat))])
4582
+ });
4583
+ const size = measure(base, props, context);
4584
+ if ("deferred" in size) return size.deferred;
4585
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
4586
+ const key = legend(card.area, keys.map((name, i) => i === 0 ? { name, part: "ink", tone: 1 } : { name, part: "ink-secondary", tone: 2, dash: "dotted" }));
4587
+ const plot = cartesianPlot(key.area);
4588
+ const strokes = [...card.strokes, ...key.strokes];
4589
+ const labels = [...card.labels, ...key.labels];
4590
+ const hitAreas = [];
4591
+ if (requested.length > WAVES) warnValue(CHART27, "keys", `${requested.length} keys were given; a stream draws the first ${WAVES}.`);
4592
+ for (const s of series) if (s.missing > 0 && data.length > 0) warnValue(CHART27, s.key, `${s.missing} of ${data.length} values are not finite; the wave breaks there.`);
4593
+ let clamped = 0;
4594
+ let unsupported = 0;
4595
+ const waves = series.map((s, w) => ({
4596
+ key: s.key,
4597
+ spans: s.points.map((p, i) => {
4598
+ if (p.value === void 0) return void 0;
4599
+ if (stacked && w > 0 && series[0]?.points[i]?.value === void 0) {
4600
+ unsupported += 1;
4601
+ return void 0;
4602
+ }
4603
+ let value = p.value;
4604
+ if (stacked && value < 0) {
4605
+ clamped += 1;
4606
+ value = 0;
4607
+ }
4608
+ const below = stacked && w > 0 ? Math.max(series[0]?.points[i]?.value ?? 0, 0) : 0;
4609
+ return { y0: below, y1: below + value, value: p.value };
4610
+ })
4611
+ }));
4612
+ if (clamped > 0) warnValue(CHART27, "stacked", `${clamped} negative values cannot stack; they are drawn at zero.`);
4613
+ if (unsupported > 0) warnValue(CHART27, "stacked", `${unsupported} values have nothing below them to stack on; the upper wave breaks there.`);
4614
+ const range = extent(waves.flatMap((w) => w.spans.flatMap((s) => s ? [s.y0, s.y1] : [])));
4615
+ if (data.length === 0 || range === void 0) {
4616
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
4617
+ }
4618
+ const axis = valueAxis(plot, range, { chart: CHART27, property: keys[0] ?? "value", padding: context.domainPadding, locale, numberFormat });
4619
+ const y = axis.scale;
4620
+ strokes.push(...axis.strokes);
4621
+ labels.push(...axis.labels);
4622
+ const xAt = categoryAxis(xValues, plot, { chart: CHART27, property: xName, padding: context.domainPadding }).at;
4623
+ const curve = CURVES2.monotone;
4624
+ waves.forEach((wave, w) => {
4625
+ const indexed = wave.spans.map((span, index) => ({ span, index }));
4626
+ const defined = (e) => e.span !== void 0;
4627
+ const shape = area().defined(defined).x((e) => xAt(e.index)).y0((e) => y(e.span?.y0 ?? 0)).y1((e) => y(e.span?.y1 ?? 0)).curve(curve)(indexed);
4628
+ const top = line().defined(defined).x((e) => xAt(e.index)).y((e) => y(e.span?.y1 ?? 0)).curve(curve)(indexed);
4629
+ const part = w === 0 ? "ink" : "ink-secondary";
4630
+ if (shape) strokes.push({ d: shape, role: "encoding", part, paint: "none", tone: w === 0 ? 1 : 2 });
4631
+ if (top) strokes.push({ d: top, role: "encoding", part, ...w === 0 ? {} : { dash: "dotted" } });
4632
+ wave.spans.forEach((span, index) => {
4633
+ if (!span) return;
4634
+ const datum = data[index] ?? {};
4635
+ hitAreas.push({ seriesKey: wave.key, index, datum, value: span.value, x: xAt(index), y: y(span.y1), cell: { column: index, row: w } });
4636
+ });
4637
+ });
4638
+ labels.push(...categoryLabels(xLabels, xAt, key.area, plot));
4639
+ const totals = series.map((s) => `${s.key} totals ${formatNumber(s.points.reduce((sum, p) => sum + (p.value ?? 0), 0), locale, numberFormat)}`);
4640
+ const description = props.description ?? `${base.name}. ${stacked ? "Stacked" : "Overlaid"} stream of ${keys.length} waves over ${data.length} ${xName} values; ${totals.join("; ")}.`;
4641
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
4642
+ }
4643
+ var streamChart = /* @__PURE__ */ Object.freeze({ name: CHART27, build: buildStreamChart });
4644
+
4645
+ // src/charts/shared/points.ts
4646
+ var validRange = (range) => !!range && Number.isFinite(range[0]) && Number.isFinite(range[1]) && range[0] > 0 && range[0] <= range[1];
4647
+ function buildPoints(family, props, context, demo) {
4648
+ const { chart } = family;
4649
+ const usesDemo = props.data === void 0;
4650
+ const data = props.data ?? demo;
4651
+ const xKey = usesDemo ? "x" : props.xKey ?? "x";
4652
+ const yKey = usesDemo ? "y" : props.yKey ?? "y";
4653
+ const sizeKey = usesDemo ? "size" : props.sizeKey ?? (family.sizeByDefault ? "size" : void 0);
4654
+ const sizeRange = validRange(props.sizeRange) ? props.sizeRange : family.defaultSizeRange;
4655
+ if (props.sizeRange !== void 0 && !validRange(props.sizeRange)) {
4656
+ warnValue(chart, "sizeRange", `[${String(props.sizeRange)}] is not two finite areas with 0 < smallest \u2264 largest; the default [${String(family.defaultSizeRange)}] is used.`);
4657
+ }
4658
+ const { locale } = context;
4659
+ const numberFormat = props.numberFormat;
4660
+ const xName = accessorName(xKey, "x");
4661
+ const yName = accessorName(yKey, "y");
4662
+ const sizeName = sizeKey === void 0 ? void 0 : accessorName(sizeKey, "size");
4663
+ checkCount(chart, yName, data.length);
4664
+ const points = [];
4665
+ data.forEach((datum, index) => {
4666
+ const x2 = finite(read(xKey, datum, index));
4667
+ const y2 = finite(read(yKey, datum, index));
4668
+ const size2 = sizeKey === void 0 ? void 0 : finite(read(sizeKey, datum, index));
4669
+ if (x2 === void 0 || y2 === void 0) {
4670
+ warnValue(chart, x2 === void 0 ? xName : yName, `Row ${index} has no finite ${x2 === void 0 ? xName : yName}; it is omitted.`);
4671
+ return;
4672
+ }
4673
+ if (sizeKey !== void 0 && (size2 === void 0 || size2 < 0)) {
4674
+ warnValue(chart, sizeName ?? "size", `Row ${index} has a size of ${String(size2 ?? "no number")}; sizes must be zero or more, so it is omitted.`);
4675
+ return;
4676
+ }
4677
+ points.push({ index, datum, x: x2, y: y2, size: size2 });
4678
+ });
4679
+ const base = modelBase(chart, props, context, family.fallbackName, {
4680
+ columns: [xName, yName, ...sizeName ? [sizeName] : []],
4681
+ rows: data.map((datum, index) => [
4682
+ formatValue(read(xKey, datum, index), locale, numberFormat),
4683
+ formatValue(read(yKey, datum, index), locale, numberFormat),
4684
+ ...sizeKey ? [formatValue(read(sizeKey, datum, index), locale, numberFormat)] : []
4685
+ ])
4686
+ });
4687
+ const size = measure(base, props, context);
4688
+ if ("deferred" in size) return size.deferred;
4689
+ const card = cardLayout(props, { width: size.width, areaHeight: size.height, chrome: props.chrome ?? "card", locale });
4690
+ const plot = cartesianPlot(card.area);
4691
+ const strokes = [...card.strokes];
4692
+ const labels = [...card.labels];
4693
+ const hitAreas = [];
4694
+ if (points.length === 0) {
4695
+ return emptyModel(base, props, context, { viewBox: card.viewBox, plot, strokes, labels }, data.length === 0);
4696
+ }
4697
+ const sizes = extent(points.flatMap((p) => p.size === void 0 ? [] : [p.size]));
4698
+ const areaOf = (s) => {
4699
+ if (s === void 0 || !sizes) return sizeRange[0];
4700
+ if (sizes[0] === sizes[1]) return sizeRange[1];
4701
+ return sizeRange[0] + (s - sizes[0]) / (sizes[1] - sizes[0]) * (sizeRange[1] - sizeRange[0]);
4702
+ };
4703
+ const radius = (s) => Math.sqrt(areaOf(s) / Math.PI);
4704
+ const largest = Math.max(...points.map((p) => radius(p.size)));
4705
+ const inner = inset(plot, largest);
4706
+ const axis = valueAxis(inner, extent(points.map((p) => p.y)) ?? [0, 0], { chart, property: yName, padding: context.domainPadding, locale, numberFormat, zero: false });
4707
+ const y = axis.scale;
4708
+ strokes.push(...axis.strokes);
4709
+ labels.push(...axis.labels);
4710
+ const gutter = Math.min(Math.max(0, ...axis.labels.map((l) => l.text.length * TICK_CHAR)) + 4 + largest, inner.width / 3);
4711
+ const x = linearScale(extent(points.map((p) => p.x)) ?? [0, 0], [inner.x + gutter, inner.x + inner.width], { chart, property: xName, padding: context.domainPadding, nice: true });
4712
+ for (const tick of x.ticks(4)) {
4713
+ labels.push({ x: x(tick), y: card.area.y + card.area.height - 5, text: formatNumber(tick, locale, numberFormat), kind: "tick", part: "axis", anchor: "middle" });
4714
+ }
4715
+ for (const p of [...points].sort((a, b) => radius(b.size) - radius(a.size) || a.index - b.index)) {
4716
+ strokes.push({ d: circlePath(x(p.x), y(p.y), radius(p.size)), role: "encoding", part: "ink", ...family.toned ? { tone: 1 } : {} });
4717
+ }
4718
+ for (const p of points) {
4719
+ const r = radius(p.size);
4720
+ hitAreas.push({ seriesKey: yName, index: p.index, datum: p.datum, value: p.y, x: x(p.x), y: y(p.y), box: { x: x(p.x) - r, y: y(p.y) - r, width: 2 * r, height: 2 * r } });
4721
+ }
4722
+ const xs = extent(points.map((p) => p.x)) ?? [0, 0];
4723
+ const ys = extent(points.map((p) => p.y)) ?? [0, 0];
4724
+ const description = props.description ?? `${base.name}. ${points.length} points; ${xName} from ${formatNumber(xs[0], locale, numberFormat)} to ${formatNumber(xs[1], locale, numberFormat)}, ${yName} from ${formatNumber(ys[0], locale, numberFormat)} to ${formatNumber(ys[1], locale, numberFormat)}${sizeName && sizes ? `, ${sizeName} from ${formatNumber(sizes[0], locale, numberFormat)} to ${formatNumber(sizes[1], locale, numberFormat)} as marker area` : ""}.`;
4725
+ return readyModel(base, description, { viewBox: card.viewBox, plot, strokes, labels, hitAreas });
4726
+ }
4727
+
4728
+ // src/charts/scatter-chart/demo.ts
4729
+ var SCATTER_CHART_DEMO = /* @__PURE__ */ Object.freeze(
4730
+ /* @__PURE__ */ [
4731
+ { x: 152, y: 51, size: 2 },
4732
+ { x: 158, y: 56, size: 4 },
4733
+ { x: 161, y: 54, size: 3 },
4734
+ { x: 165, y: 62, size: 6 },
4735
+ { x: 168, y: 60, size: 5 },
4736
+ { x: 171, y: 68, size: 8 },
4737
+ { x: 175, y: 71, size: 7 },
4738
+ { x: 178, y: 69, size: 9 },
4739
+ { x: 182, y: 77, size: 10 }
4740
+ ].map((row) => Object.freeze(row))
4741
+ );
4742
+
4743
+ // src/charts/scatter-chart/scatter-chart.ts
4744
+ var FAMILY5 = { chart: "ScatterChart", fallbackName: "Scatter chart", defaultSizeRange: [60, 240], sizeByDefault: false, toned: false };
4745
+ var scatterChart = /* @__PURE__ */ Object.freeze({
4746
+ name: "ScatterChart",
4747
+ build: (props, context) => buildPoints(FAMILY5, props, context, SCATTER_CHART_DEMO)
4748
+ });
4749
+
4750
+ // src/charts/bubble-chart/demo.ts
4751
+ var BUBBLE_CHART_DEMO = /* @__PURE__ */ Object.freeze(
4752
+ /* @__PURE__ */ [
4753
+ { x: 2, y: 18, size: 40 },
4754
+ { x: 5, y: 24, size: 120 },
4755
+ { x: 8, y: 12, size: 65 },
4756
+ { x: 11, y: 30, size: 210 },
4757
+ { x: 14, y: 21, size: 90 }
4758
+ ].map((row) => Object.freeze(row))
4759
+ );
4760
+
4761
+ // src/charts/bubble-chart/bubble-chart.ts
4762
+ var FAMILY6 = { chart: "BubbleChart", fallbackName: "Bubble chart", defaultSizeRange: [100, 500], sizeByDefault: true, toned: true };
4763
+ var bubbleChart = /* @__PURE__ */ Object.freeze({
4764
+ name: "BubbleChart",
4765
+ build: (props, context) => buildPoints(FAMILY6, props, context, BUBBLE_CHART_DEMO)
4766
+ });
4767
+
4768
+ export { DEFAULTS, DISPLAY_FACE_PROBE, FORCED_PRECISION_QUERY, LINE_CHART_DEMO, LINE_CHART_DEMO_KEYS, MIN_TOUCH_TARGET, NAVIGATION_KEYS, NullInker, PATH_BYTE_BUDGET, SilverpointError, __setDiagnosticSink, activityGrid, addDays, areaChart, bandScale, barChart, bubbleChart, bulletChart, candlestickChart, checkTypeface, chordRing, circlePath, composedChart, coxcombChart, createPrng, deriveSeed, diagnose, donutChart, enforceHeightening, expandDomain, extent, fnv1a32, funnelChart, gaugeArc, heatmapChart, instanceId, isoDate, kpiCard, lineChart, linearScale, max, meterChart, min, orbitChart, parseGeometry, parseIsoDate, pathBytes, polarBarChart, pyramidChart, radarChart, radialArcGroup, radialRings, rangeBandChart, readout, reduceInteraction, registerInker, resolveActive, resolveConfig, resolveInker, resolveSeed, round2, roundGeometry, roundPathData, sameActive, sankeyChart, scatterChart, serializeGeometry, sparklineRows, stackedBarChart, stepActive, stepChart, streamChart, svgString, toSvgPoint, toSvgView, treemapChart, volvelleChart, waterfallChart, weekday, windRose };
4769
+ //# sourceMappingURL=index.js.map
4770
+ //# sourceMappingURL=index.js.map