@classic-homes/charts-core 0.1.37 → 0.3.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/{chunk-JM72YHZD.js → chunk-WAHMKXRF.js} +13 -3
- package/dist/index.cjs +1126 -34
- package/dist/index.d.cts +542 -6
- package/dist/index.d.ts +542 -6
- package/dist/index.js +1053 -34
- package/dist/theme/index.cjs +12 -2
- package/dist/theme/index.d.cts +10 -0
- package/dist/theme/index.d.ts +10 -0
- package/dist/theme/index.js +1 -1
- package/dist/types/index.d.cts +734 -29
- package/dist/types/index.d.ts +734 -29
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -284,7 +284,12 @@ var lightChartColors = {
|
|
|
284
284
|
axisPointerLabel: themeTokens.semanticColors.light.mutedForeground,
|
|
285
285
|
divider: themeTokens.colors.gray[600],
|
|
286
286
|
success: themeTokens.semanticColors.light.success,
|
|
287
|
-
destructive: themeTokens.semanticColors.light.destructive
|
|
287
|
+
destructive: themeTokens.semanticColors.light.destructive,
|
|
288
|
+
warning: themeTokens.semanticColors.light.warning,
|
|
289
|
+
reference: themeTokens.semanticColors.light.mutedForeground,
|
|
290
|
+
referenceBand: "rgba(102, 102, 102, 0.08)",
|
|
291
|
+
surface: themeTokens.semanticColors.light.card,
|
|
292
|
+
ranges: [themeTokens.colors.gray[300], themeTokens.colors.gray[200], themeTokens.colors.gray[100]]
|
|
288
293
|
};
|
|
289
294
|
var darkChartColors = {
|
|
290
295
|
series: darkTheme.color,
|
|
@@ -294,7 +299,12 @@ var darkChartColors = {
|
|
|
294
299
|
axisPointerLabel: themeTokens.colors.gray[600],
|
|
295
300
|
divider: themeTokens.colors.gray[700],
|
|
296
301
|
success: themeTokens.semanticColors.dark.success,
|
|
297
|
-
destructive: themeTokens.semanticColors.dark.destructive
|
|
302
|
+
destructive: themeTokens.semanticColors.dark.destructive,
|
|
303
|
+
warning: themeTokens.semanticColors.dark.warning,
|
|
304
|
+
reference: themeTokens.semanticColors.dark.mutedForeground,
|
|
305
|
+
referenceBand: "rgba(255, 255, 255, 0.06)",
|
|
306
|
+
surface: themeTokens.semanticColors.dark.card,
|
|
307
|
+
ranges: [themeTokens.colors.gray[600], themeTokens.colors.gray[700], themeTokens.colors.gray[800]]
|
|
298
308
|
};
|
|
299
309
|
function getChartColors(themeName) {
|
|
300
310
|
return themeName === THEME_DARK ? darkChartColors : lightChartColors;
|
|
@@ -312,17 +322,58 @@ function colorWithAlpha(color, alpha) {
|
|
|
312
322
|
}
|
|
313
323
|
|
|
314
324
|
// src/utils/format.ts
|
|
315
|
-
|
|
316
|
-
|
|
325
|
+
var numberFormats = /* @__PURE__ */ new Map();
|
|
326
|
+
function numberFormat(locale, options) {
|
|
327
|
+
const key = `${locale}|${options ? JSON.stringify(options) : ""}`;
|
|
328
|
+
let format = numberFormats.get(key);
|
|
329
|
+
if (!format) {
|
|
330
|
+
format = new Intl.NumberFormat(locale, options);
|
|
331
|
+
numberFormats.set(key, format);
|
|
332
|
+
}
|
|
333
|
+
return format;
|
|
334
|
+
}
|
|
335
|
+
function formatNumber(value, options, locale = "en-US") {
|
|
336
|
+
return numberFormat(locale, options).format(value);
|
|
337
|
+
}
|
|
338
|
+
var PRESETS = {
|
|
339
|
+
number: { maximumFractionDigits: 2 },
|
|
340
|
+
integer: { maximumFractionDigits: 0 },
|
|
341
|
+
compact: { notation: "compact", maximumFractionDigits: 1 },
|
|
342
|
+
currency: { style: "currency", currency: "USD", maximumFractionDigits: 0 },
|
|
343
|
+
currencyCents: { style: "currency", currency: "USD" },
|
|
344
|
+
percent: { style: "percent", maximumFractionDigits: 1 },
|
|
345
|
+
percentValue: { style: "percent", maximumFractionDigits: 1 }
|
|
346
|
+
};
|
|
347
|
+
function resolveValueFormatter(format, locale = "en-US") {
|
|
348
|
+
if (format === void 0) return void 0;
|
|
349
|
+
if (typeof format === "function") return format;
|
|
350
|
+
if (format === "days") {
|
|
351
|
+
return (value) => {
|
|
352
|
+
const days = Math.round(value);
|
|
353
|
+
return `${numberFormat(locale).format(days)} ${Math.abs(days) === 1 ? "day" : "days"}`;
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
if (format === "percentValue") {
|
|
357
|
+
const intl2 = numberFormat(locale, PRESETS.percentValue);
|
|
358
|
+
return (value) => intl2.format(value / 100);
|
|
359
|
+
}
|
|
360
|
+
const intl = numberFormat(locale, typeof format === "string" ? PRESETS[format] : format);
|
|
361
|
+
return (value) => intl.format(value);
|
|
362
|
+
}
|
|
363
|
+
function formatValue(value, formatter, empty = "") {
|
|
364
|
+
if (value == null) return empty;
|
|
365
|
+
if (typeof value === "string") return value;
|
|
366
|
+
if (!Number.isFinite(value)) return empty;
|
|
367
|
+
return formatter ? formatter(value) : formatNumber(value, { maximumFractionDigits: 2 });
|
|
317
368
|
}
|
|
318
369
|
function formatCurrency(value, currency = "USD", locale = "en-US") {
|
|
319
|
-
return
|
|
370
|
+
return numberFormat(locale, {
|
|
320
371
|
style: "currency",
|
|
321
372
|
currency
|
|
322
373
|
}).format(value);
|
|
323
374
|
}
|
|
324
375
|
function formatPercent(value, decimals = 1, locale = "en-US") {
|
|
325
|
-
return
|
|
376
|
+
return numberFormat(locale, {
|
|
326
377
|
style: "percent",
|
|
327
378
|
minimumFractionDigits: decimals,
|
|
328
379
|
maximumFractionDigits: decimals
|
|
@@ -374,52 +425,80 @@ function createTooltipFormatter(options = {}) {
|
|
|
374
425
|
}
|
|
375
426
|
|
|
376
427
|
// src/utils/accessibility.ts
|
|
377
|
-
|
|
428
|
+
var isNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
429
|
+
function seriesList(names) {
|
|
430
|
+
return names.join(", ");
|
|
431
|
+
}
|
|
432
|
+
function describeTrends(title, kind, data, format) {
|
|
378
433
|
const seriesCount = data.series.length;
|
|
379
|
-
|
|
380
|
-
const seriesNames = data.series.map((s) => s.name).join(", ");
|
|
381
|
-
let description = `${title}. Line chart with ${categoryCount} data points`;
|
|
434
|
+
let description = `${title}. ${kind} chart with ${data.categories.length} data points`;
|
|
382
435
|
if (seriesCount > 1) {
|
|
383
|
-
description += ` across ${seriesCount} series: ${
|
|
436
|
+
description += ` across ${seriesCount} series: ${seriesList(data.series.map((s) => s.name))}`;
|
|
384
437
|
}
|
|
385
438
|
data.series.forEach((series) => {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
const
|
|
439
|
+
const present = series.data.filter(isNumber);
|
|
440
|
+
if (present.length >= 2) {
|
|
441
|
+
const first = present[0];
|
|
442
|
+
const last = present[present.length - 1];
|
|
389
443
|
const trend = last > first ? "increasing" : last < first ? "decreasing" : "stable";
|
|
390
|
-
description += `. ${series.name} shows ${trend} trend from ${first} to ${last}`;
|
|
444
|
+
description += `. ${series.name} shows ${trend} trend from ${formatValue(first, format)} to ${formatValue(last, format)}`;
|
|
391
445
|
}
|
|
392
446
|
});
|
|
447
|
+
const missing = data.series.reduce(
|
|
448
|
+
(sum, series) => sum + series.data.filter((value) => !isNumber(value)).length,
|
|
449
|
+
0
|
|
450
|
+
);
|
|
451
|
+
if (missing > 0) description += `. ${missing} values are missing`;
|
|
393
452
|
return description;
|
|
394
453
|
}
|
|
395
|
-
function
|
|
454
|
+
function generateLineChartDescription(title, data, format) {
|
|
455
|
+
return describeTrends(title, "Line", data, format);
|
|
456
|
+
}
|
|
457
|
+
function generateAreaChartDescription(title, data, format) {
|
|
458
|
+
return describeTrends(title, "Area", data, format);
|
|
459
|
+
}
|
|
460
|
+
function generateComboChartDescription(title, data, format) {
|
|
461
|
+
return describeTrends(title, "Combined bar and line", data, format);
|
|
462
|
+
}
|
|
463
|
+
function extremes(data) {
|
|
464
|
+
let max;
|
|
465
|
+
let min;
|
|
466
|
+
data.series.forEach((series) => {
|
|
467
|
+
series.data.forEach((value, index) => {
|
|
468
|
+
if (!isNumber(value)) return;
|
|
469
|
+
const category = data.categories[index] ?? "";
|
|
470
|
+
if (!max || value > max.value) max = { value, category };
|
|
471
|
+
if (!min || value < min.value) min = { value, category };
|
|
472
|
+
});
|
|
473
|
+
});
|
|
474
|
+
return { max, min };
|
|
475
|
+
}
|
|
476
|
+
function generateBarChartDescription(title, data, format) {
|
|
396
477
|
const categoryCount = data.categories.length;
|
|
397
478
|
const seriesCount = data.series.length;
|
|
398
479
|
let description = `${title}. Bar chart with ${categoryCount} categories`;
|
|
399
480
|
if (seriesCount > 1) {
|
|
400
481
|
description += ` and ${seriesCount} series`;
|
|
401
482
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
});
|
|
418
|
-
description += `. Highest value is ${maxValue} for ${maxCategory}. Lowest value is ${minValue} for ${minCategory}`;
|
|
483
|
+
const { max, min } = extremes(data);
|
|
484
|
+
if (max && min) {
|
|
485
|
+
description += `. Highest value is ${formatValue(max.value, format)} for ${max.category}. Lowest value is ${formatValue(min.value, format)} for ${min.category}`;
|
|
486
|
+
}
|
|
487
|
+
return description;
|
|
488
|
+
}
|
|
489
|
+
function generateDotPlotDescription(title, data, format) {
|
|
490
|
+
let description = `${title}. Dot plot with ${data.categories.length} rows`;
|
|
491
|
+
if (data.series.length > 1) {
|
|
492
|
+
description += ` comparing ${seriesList(data.series.map((s) => s.name))}`;
|
|
493
|
+
}
|
|
494
|
+
const { max, min } = extremes(data);
|
|
495
|
+
if (max && min) {
|
|
496
|
+
description += `. Highest value is ${formatValue(max.value, format)} for ${max.category}. Lowest value is ${formatValue(min.value, format)} for ${min.category}`;
|
|
497
|
+
}
|
|
419
498
|
return description;
|
|
420
499
|
}
|
|
421
500
|
function generatePieChartDescription(title, data, isDonut = false) {
|
|
422
|
-
const total = data.reduce((sum, slice) => sum + slice.value, 0);
|
|
501
|
+
const total = data.reduce((sum, slice) => sum + (isNumber(slice.value) ? slice.value : 0), 0);
|
|
423
502
|
const chartType = isDonut ? "Donut" : "Pie";
|
|
424
503
|
let description = `${title}. ${chartType} chart with ${data.length} segments`;
|
|
425
504
|
if (total === 0) {
|
|
@@ -444,6 +523,44 @@ function generateScatterChartDescription(title, data) {
|
|
|
444
523
|
}
|
|
445
524
|
return description;
|
|
446
525
|
}
|
|
526
|
+
function generateHistogramDescription(title, buckets, total) {
|
|
527
|
+
let description = `${title}. Histogram of ${total} values in ${buckets.length} buckets`;
|
|
528
|
+
const peak = buckets.reduce(
|
|
529
|
+
(best, bucket) => !best || bucket.count > best.count ? bucket : best,
|
|
530
|
+
void 0
|
|
531
|
+
);
|
|
532
|
+
if (peak && peak.count > 0) {
|
|
533
|
+
description += `. The most common range is ${peak.label} with ${peak.count} values`;
|
|
534
|
+
}
|
|
535
|
+
return description;
|
|
536
|
+
}
|
|
537
|
+
function generateBoxPlotDescription(title, groups, format) {
|
|
538
|
+
let description = `${title}. Box plot of ${groups.length} groups`;
|
|
539
|
+
const parts = groups.filter((group) => group.stats).slice(0, 5).map(
|
|
540
|
+
({ name, stats }) => `${name}: median ${formatValue(stats.median, format)}, middle half ${formatValue(stats.q1, format)} to ${formatValue(stats.q3, format)}`
|
|
541
|
+
);
|
|
542
|
+
if (parts.length) description += `. ${parts.join("; ")}`;
|
|
543
|
+
return description;
|
|
544
|
+
}
|
|
545
|
+
function generateWaterfallDescription(title, steps, final, format) {
|
|
546
|
+
const changes = steps.filter((step) => !step.isTotal);
|
|
547
|
+
const increases = changes.filter((step) => step.value > 0).length;
|
|
548
|
+
const decreases = changes.filter((step) => step.value < 0).length;
|
|
549
|
+
return `${title}. Waterfall chart with ${steps.length} steps: ${increases} increases and ${decreases} decreases, ending at ${formatValue(final, format)}`;
|
|
550
|
+
}
|
|
551
|
+
function generateBulletDescription(title, items, format) {
|
|
552
|
+
const withTarget = items.filter((item) => isNumber(item.target));
|
|
553
|
+
let description = `${title}. Bullet chart with ${items.length} rows`;
|
|
554
|
+
if (withTarget.length) {
|
|
555
|
+
const met = withTarget.filter((item) => item.value >= item.target).length;
|
|
556
|
+
description += `. ${met} of ${withTarget.length} meet their target`;
|
|
557
|
+
}
|
|
558
|
+
const parts = items.slice(0, 5).map(
|
|
559
|
+
(item) => `${item.name} ${formatValue(item.value, format)}${isNumber(item.target) ? ` of ${formatValue(item.target, format)}` : ""}`
|
|
560
|
+
);
|
|
561
|
+
if (parts.length) description += `. ${parts.join(", ")}`;
|
|
562
|
+
return description;
|
|
563
|
+
}
|
|
447
564
|
function generateDataTable(headers, rows) {
|
|
448
565
|
const headerRow = headers.map((h) => `<th scope="col">${escapeHtml(h)}</th>`).join("");
|
|
449
566
|
const bodyRows = rows.map((row) => `<tr>${row.map((cell) => `<td>${escapeHtml(cell)}</td>`).join("")}</tr>`).join("");
|
|
@@ -475,27 +592,1002 @@ function getReplaceMerge(prev, next, isComponentType) {
|
|
|
475
592
|
}
|
|
476
593
|
return [...types].filter((type) => isComponentType(type));
|
|
477
594
|
}
|
|
595
|
+
function isPlainObject(value) {
|
|
596
|
+
if (value === null || typeof value !== "object") return false;
|
|
597
|
+
const proto = Object.getPrototypeOf(value);
|
|
598
|
+
return proto === Object.prototype || proto === null;
|
|
599
|
+
}
|
|
600
|
+
function mergeValue(base, override, topLevel = false) {
|
|
601
|
+
if (override === void 0) return base;
|
|
602
|
+
if (topLevel && (Array.isArray(base) || Array.isArray(override))) {
|
|
603
|
+
const from = Array.isArray(base) ? base : base === void 0 ? [] : [base];
|
|
604
|
+
const to = Array.isArray(override) ? override : [override];
|
|
605
|
+
const length = Math.max(from.length, to.length);
|
|
606
|
+
return Array.from({ length }, (_, index) => mergeValue(from[index], to[index]));
|
|
607
|
+
}
|
|
608
|
+
if (isPlainObject(base) && isPlainObject(override)) {
|
|
609
|
+
const merged = { ...base };
|
|
610
|
+
for (const key of Object.keys(override)) {
|
|
611
|
+
merged[key] = mergeValue(base[key], override[key]);
|
|
612
|
+
}
|
|
613
|
+
return merged;
|
|
614
|
+
}
|
|
615
|
+
return override;
|
|
616
|
+
}
|
|
617
|
+
function mergeChartOption(generated, override) {
|
|
618
|
+
if (!override) return generated;
|
|
619
|
+
const base = generated;
|
|
620
|
+
const merged = { ...base };
|
|
621
|
+
for (const [key, value] of Object.entries(override)) {
|
|
622
|
+
merged[key] = mergeValue(base[key], value, true);
|
|
623
|
+
}
|
|
624
|
+
return merged;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// src/utils/options.ts
|
|
628
|
+
function toParams(raw, getLabel) {
|
|
629
|
+
const seriesIndex = raw.seriesIndex ?? 0;
|
|
630
|
+
const dataIndex = raw.dataIndex ?? 0;
|
|
631
|
+
const name = raw.name ?? "";
|
|
632
|
+
const value = raw.value;
|
|
633
|
+
return {
|
|
634
|
+
name,
|
|
635
|
+
value: Array.isArray(value) ? value : value,
|
|
636
|
+
seriesName: raw.seriesName ?? "",
|
|
637
|
+
seriesIndex,
|
|
638
|
+
dataIndex,
|
|
639
|
+
percent: raw.percent,
|
|
640
|
+
color: typeof raw.color === "string" ? raw.color : void 0,
|
|
641
|
+
label: getLabel?.(seriesIndex, dataIndex, name)
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
function toTooltipParams(raw, getLabel) {
|
|
645
|
+
return {
|
|
646
|
+
...toParams(raw, getLabel),
|
|
647
|
+
marker: typeof raw.marker === "string" ? raw.marker : "",
|
|
648
|
+
axisValue: raw.axisValue
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
var EDGE_ALIGN = { start: "left", center: "center", end: "right" };
|
|
652
|
+
var SIDE_ALIGN = { start: "top", center: "middle", end: "bottom" };
|
|
653
|
+
function buildLegend(names, config, { show, custom = false, defaults = {} }) {
|
|
654
|
+
if (custom) return { show: false, data: names };
|
|
655
|
+
if (!show) return void 0;
|
|
656
|
+
const { position = "bottom", align = "center", orient, icon, formatter, scroll } = config ?? {};
|
|
657
|
+
const legend = { ...defaults, data: names };
|
|
658
|
+
if (position === "top" || position === "bottom") {
|
|
659
|
+
legend[position] = 0;
|
|
660
|
+
if (align !== "center") legend.left = EDGE_ALIGN[align];
|
|
661
|
+
} else {
|
|
662
|
+
legend[position] = 0;
|
|
663
|
+
legend.top = SIDE_ALIGN[align];
|
|
664
|
+
legend.orient = "vertical";
|
|
665
|
+
}
|
|
666
|
+
if (orient) legend.orient = orient;
|
|
667
|
+
if (icon) legend.icon = icon;
|
|
668
|
+
if (formatter) legend.formatter = formatter;
|
|
669
|
+
if (scroll) legend.type = "scroll";
|
|
670
|
+
return legend;
|
|
671
|
+
}
|
|
672
|
+
function gridInset(legend, showLegend, xAxis, yAxis) {
|
|
673
|
+
const inset = {};
|
|
674
|
+
if (showLegend) {
|
|
675
|
+
const position = legend?.position ?? "bottom";
|
|
676
|
+
inset[position] = position === "left" || position === "right" ? 140 : 36;
|
|
677
|
+
}
|
|
678
|
+
if (xAxis?.name !== void 0 && (xAxis.nameLocation ?? "middle") === "middle") {
|
|
679
|
+
inset.bottom = (inset.bottom ?? 12) + 24;
|
|
680
|
+
}
|
|
681
|
+
if (yAxis?.name !== void 0 && (yAxis.nameLocation ?? "middle") === "middle") {
|
|
682
|
+
inset.left = (inset.left ?? 12) + 24;
|
|
683
|
+
}
|
|
684
|
+
return Object.keys(inset).length ? inset : void 0;
|
|
685
|
+
}
|
|
686
|
+
function legendCenter(config, show, fallback) {
|
|
687
|
+
if (!show) return fallback;
|
|
688
|
+
switch (config?.position) {
|
|
689
|
+
case "left":
|
|
690
|
+
return ["60%", "50%"];
|
|
691
|
+
case "right":
|
|
692
|
+
return ["40%", "50%"];
|
|
693
|
+
case "top":
|
|
694
|
+
return ["50%", "55%"];
|
|
695
|
+
default:
|
|
696
|
+
return fallback;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
function anchorCenterGraphic(group, center, defaultCenter) {
|
|
700
|
+
if (center[0] === defaultCenter[0] && center[1] === defaultCenter[1]) return group;
|
|
701
|
+
const shiftY = parseFloat(center[1]) - parseFloat(defaultCenter[1]);
|
|
702
|
+
const top = typeof group.top === "string" ? `${parseFloat(group.top) + shiftY}%` : group.top;
|
|
703
|
+
return {
|
|
704
|
+
...group,
|
|
705
|
+
left: center[0],
|
|
706
|
+
top,
|
|
707
|
+
bounding: "raw",
|
|
708
|
+
children: (group.children ?? []).map(
|
|
709
|
+
({ left: _left, top: childTop, ...child }) => ({ ...child, x: 0, y: childTop ?? 0 })
|
|
710
|
+
)
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function getLegendItems(names, palette, overrides, visible) {
|
|
714
|
+
return names.map((name, index) => ({
|
|
715
|
+
name,
|
|
716
|
+
color: overrides[index] || palette[index % palette.length] || "",
|
|
717
|
+
visible: visible[name] !== false
|
|
718
|
+
}));
|
|
719
|
+
}
|
|
720
|
+
function buildAxis(base, config) {
|
|
721
|
+
if (!config) return base;
|
|
722
|
+
const axis = { ...base };
|
|
723
|
+
const { type, show, name, nameLocation, nameGap, formatter, rotate, min, max, interval } = config;
|
|
724
|
+
if (type !== void 0) {
|
|
725
|
+
axis.type = type;
|
|
726
|
+
if (type !== "category") {
|
|
727
|
+
delete axis.data;
|
|
728
|
+
delete axis.boundaryGap;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (show !== void 0) axis.show = show;
|
|
732
|
+
if (name !== void 0) {
|
|
733
|
+
axis.name = name;
|
|
734
|
+
axis.nameLocation = nameLocation ?? "middle";
|
|
735
|
+
axis.nameGap = nameGap ?? (axis.nameLocation === "middle" ? 32 : 15);
|
|
736
|
+
}
|
|
737
|
+
if (formatter !== void 0 || rotate !== void 0) {
|
|
738
|
+
const axisLabel = { ...base.axisLabel };
|
|
739
|
+
if (formatter !== void 0) axisLabel.formatter = formatter;
|
|
740
|
+
if (rotate !== void 0) axisLabel.rotate = rotate;
|
|
741
|
+
axis.axisLabel = axisLabel;
|
|
742
|
+
}
|
|
743
|
+
if (min !== void 0) axis.min = min;
|
|
744
|
+
if (max !== void 0) axis.max = max;
|
|
745
|
+
if (interval !== void 0) axis.interval = interval;
|
|
746
|
+
return axis;
|
|
747
|
+
}
|
|
748
|
+
function buildDataLabel(base, hidden, { show, formatter, getLabel }) {
|
|
749
|
+
if (formatter === void 0) return show ? base : hidden;
|
|
750
|
+
const wrapped = typeof formatter === "string" ? formatter : (raw) => formatter(toParams(raw, getLabel));
|
|
751
|
+
if (!show) return { ...hidden, show: false, formatter: wrapped };
|
|
752
|
+
return { ...base, show: true, formatter: wrapped };
|
|
753
|
+
}
|
|
754
|
+
function pointLabel(text, formatter, base = {}) {
|
|
755
|
+
if (text == null || text === "") return void 0;
|
|
756
|
+
return typeof formatter === "function" ? { ...base, show: true } : { ...base, show: true, formatter: text };
|
|
757
|
+
}
|
|
758
|
+
function withPointLabels(values, labels, formatter, base) {
|
|
759
|
+
if (!labels?.some((text) => text != null && text !== "")) return values;
|
|
760
|
+
return values.map((value, index) => {
|
|
761
|
+
const label = pointLabel(labels[index], formatter, base);
|
|
762
|
+
return label ? { value, label } : value;
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
function seriesLabelLookup(series) {
|
|
766
|
+
return (seriesIndex, dataIndex) => series[seriesIndex]?.labels?.[dataIndex] ?? void 0;
|
|
767
|
+
}
|
|
768
|
+
function itemLabelLookup(items) {
|
|
769
|
+
return (_seriesIndex, dataIndex) => items[dataIndex]?.label;
|
|
770
|
+
}
|
|
771
|
+
function nameLabelLookup(nodes) {
|
|
772
|
+
const labels = /* @__PURE__ */ new Map();
|
|
773
|
+
const visit = (list) => {
|
|
774
|
+
for (const node of list) {
|
|
775
|
+
if (node.label != null && !labels.has(node.name)) labels.set(node.name, node.label);
|
|
776
|
+
if (node.children) visit(node.children);
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
visit(nodes);
|
|
780
|
+
return (_seriesIndex, _dataIndex, name) => labels.get(name);
|
|
781
|
+
}
|
|
782
|
+
function hasPointLabels(series) {
|
|
783
|
+
return series.some((s) => s.labels?.some((text) => text != null && text !== ""));
|
|
784
|
+
}
|
|
785
|
+
function buildTooltip(base, { show, formatter, valueFormatter, getLabel }) {
|
|
786
|
+
if (!show) return void 0;
|
|
787
|
+
if (formatter === void 0 && valueFormatter === void 0) return base;
|
|
788
|
+
const tooltip = { ...base };
|
|
789
|
+
if (valueFormatter) {
|
|
790
|
+
tooltip.valueFormatter = (value) => typeof value === "number" ? valueFormatter(value) : String(value ?? "");
|
|
791
|
+
}
|
|
792
|
+
if (typeof formatter === "string") {
|
|
793
|
+
tooltip.formatter = formatter;
|
|
794
|
+
} else if (formatter) {
|
|
795
|
+
tooltip.formatter = (raw) => formatter((Array.isArray(raw) ? raw : [raw]).map((p) => toTooltipParams(p, getLabel)));
|
|
796
|
+
}
|
|
797
|
+
return tooltip;
|
|
798
|
+
}
|
|
799
|
+
function formatTooltipValue(value, valueFormatter) {
|
|
800
|
+
if (valueFormatter && typeof value === "number") return valueFormatter(value);
|
|
801
|
+
return String(value ?? "");
|
|
802
|
+
}
|
|
803
|
+
function valueLabelFormatter(format) {
|
|
804
|
+
return (params) => {
|
|
805
|
+
if (params.label != null && params.label !== "") return params.label;
|
|
806
|
+
const value = Array.isArray(params.value) ? params.value[params.value.length - 1] : params.value;
|
|
807
|
+
return formatValue(value, format);
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
function valueAxisConfig(config, format) {
|
|
811
|
+
if (!format || config?.formatter !== void 0) return config;
|
|
812
|
+
return { ...config, formatter: (value) => format(Number(value)) };
|
|
813
|
+
}
|
|
814
|
+
function chartFormatters({
|
|
815
|
+
valueFormat,
|
|
816
|
+
locale,
|
|
817
|
+
tooltipValueFormatter,
|
|
818
|
+
labelFormatter
|
|
819
|
+
}) {
|
|
820
|
+
const format = resolveValueFormatter(valueFormat, locale);
|
|
821
|
+
return {
|
|
822
|
+
format,
|
|
823
|
+
tooltip: tooltipValueFormatter ?? format,
|
|
824
|
+
label: labelFormatter ?? (format ? valueLabelFormatter(format) : void 0),
|
|
825
|
+
axis: (config) => valueAxisConfig(config, format)
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
var STATISTICS = ["average", "median", "min", "max"];
|
|
829
|
+
function buildMarkLine(lines, { valueAxis, format, color, inverse = false }) {
|
|
830
|
+
if (!lines?.length) return void 0;
|
|
831
|
+
return {
|
|
832
|
+
silent: true,
|
|
833
|
+
symbol: "none",
|
|
834
|
+
animation: false,
|
|
835
|
+
data: lines.map((line) => {
|
|
836
|
+
const statistic = STATISTICS.includes(line.value);
|
|
837
|
+
const lineColor = line.color ?? color;
|
|
838
|
+
const position = valueAxis === "y" ? "insideEndTop" : inverse ? "start" : "end";
|
|
839
|
+
return {
|
|
840
|
+
...statistic ? { type: line.value, valueDim: valueAxis } : { [valueAxis === "y" ? "yAxis" : "xAxis"]: line.value },
|
|
841
|
+
name: line.label,
|
|
842
|
+
lineStyle: { type: line.style ?? "dashed", color: lineColor, width: 1.5 },
|
|
843
|
+
label: {
|
|
844
|
+
show: true,
|
|
845
|
+
position,
|
|
846
|
+
color: lineColor,
|
|
847
|
+
formatter: (params) => line.label ?? formatValue(Number(params.value), format)
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
})
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
function buildMarkArea(bands, { valueAxis, bandColor }) {
|
|
854
|
+
if (!bands?.length) return void 0;
|
|
855
|
+
const key = valueAxis === "y" ? "yAxis" : "xAxis";
|
|
856
|
+
return {
|
|
857
|
+
silent: true,
|
|
858
|
+
animation: false,
|
|
859
|
+
data: bands.map((band) => [
|
|
860
|
+
{
|
|
861
|
+
[key]: band.from,
|
|
862
|
+
name: band.label,
|
|
863
|
+
itemStyle: { color: band.color ?? bandColor },
|
|
864
|
+
label: {
|
|
865
|
+
show: !!band.label,
|
|
866
|
+
position: "insideTopLeft"
|
|
867
|
+
}
|
|
868
|
+
},
|
|
869
|
+
{ [key]: band.to }
|
|
870
|
+
])
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
function withReferenceMarks(series, lines, bands, options) {
|
|
874
|
+
if (!lines?.length && !bands?.length) return series;
|
|
875
|
+
const indexOf = (name) => {
|
|
876
|
+
if (name === void 0) return 0;
|
|
877
|
+
const index = series.findIndex((s) => s.name === name);
|
|
878
|
+
return index === -1 ? 0 : index;
|
|
879
|
+
};
|
|
880
|
+
return series.map((s, index) => {
|
|
881
|
+
const markLine = buildMarkLine(
|
|
882
|
+
lines?.filter((line) => indexOf(line.series) === index),
|
|
883
|
+
options
|
|
884
|
+
);
|
|
885
|
+
const markArea = buildMarkArea(
|
|
886
|
+
bands?.filter((band) => indexOf(band.series) === index),
|
|
887
|
+
options
|
|
888
|
+
);
|
|
889
|
+
if (!markLine && !markArea) return s;
|
|
890
|
+
return { ...s, ...markLine && { markLine }, ...markArea && { markArea } };
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
function buildDataZoom(mode, axis, bottomOffset = 0) {
|
|
894
|
+
if (!mode) return void 0;
|
|
895
|
+
const target = axis === "x" ? { xAxisIndex: 0 } : { yAxisIndex: 0 };
|
|
896
|
+
const zooms = [];
|
|
897
|
+
if (mode === true || mode === "inside") zooms.push({ type: "inside", ...target });
|
|
898
|
+
if (mode === true || mode === "slider") {
|
|
899
|
+
zooms.push(
|
|
900
|
+
axis === "x" ? { type: "slider", ...target, bottom: 8 + bottomOffset, height: 20 } : { type: "slider", ...target, right: 8, width: 20 }
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
return zooms;
|
|
904
|
+
}
|
|
905
|
+
function withZoomInset(inset, mode, axis) {
|
|
906
|
+
if (mode !== true && mode !== "slider") return inset;
|
|
907
|
+
const edge = axis === "x" ? "bottom" : "right";
|
|
908
|
+
const current = typeof inset?.[edge] === "number" ? inset[edge] : 12;
|
|
909
|
+
return { ...inset, [edge]: current + 36 };
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// src/utils/stats.ts
|
|
913
|
+
var isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
|
|
914
|
+
var clean = (value) => Number(value.toPrecision(12));
|
|
915
|
+
function niceStep(rough) {
|
|
916
|
+
if (!(rough > 0) || !Number.isFinite(rough)) return 1;
|
|
917
|
+
const power = 10 ** Math.floor(Math.log10(rough));
|
|
918
|
+
const fraction = rough / power;
|
|
919
|
+
const nice = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 2.5 ? 2.5 : fraction <= 5 ? 5 : 10;
|
|
920
|
+
return clean(nice * power);
|
|
921
|
+
}
|
|
922
|
+
function bucketLabel(start, end, format) {
|
|
923
|
+
const text = (value) => formatValue(value, format);
|
|
924
|
+
if (start === -Infinity && end === Infinity) return "All";
|
|
925
|
+
if (start === -Infinity) return `< ${text(end)}`;
|
|
926
|
+
if (end === Infinity) return `${text(start)}+`;
|
|
927
|
+
return `${text(start)}\u2013${text(end)}`;
|
|
928
|
+
}
|
|
929
|
+
function binValues(values, options = {}) {
|
|
930
|
+
const { bins, binWidth, format } = options;
|
|
931
|
+
const finite = values.filter(isFiniteNumber);
|
|
932
|
+
let edges;
|
|
933
|
+
if (Array.isArray(bins)) {
|
|
934
|
+
edges = bins.map((bin) => ({
|
|
935
|
+
start: bin.start ?? -Infinity,
|
|
936
|
+
end: bin.end ?? Infinity,
|
|
937
|
+
label: bin.label
|
|
938
|
+
}));
|
|
939
|
+
} else {
|
|
940
|
+
if (!finite.length) return [];
|
|
941
|
+
let min = Infinity;
|
|
942
|
+
let max = -Infinity;
|
|
943
|
+
for (const value of finite) {
|
|
944
|
+
if (value < min) min = value;
|
|
945
|
+
if (value > max) max = value;
|
|
946
|
+
}
|
|
947
|
+
if (min === max) {
|
|
948
|
+
edges = [{ start: min, end: max }];
|
|
949
|
+
} else {
|
|
950
|
+
let step;
|
|
951
|
+
let start;
|
|
952
|
+
if (binWidth && binWidth > 0) {
|
|
953
|
+
step = binWidth;
|
|
954
|
+
start = Math.floor(min / step) * step;
|
|
955
|
+
} else if (typeof bins === "number" && bins > 0) {
|
|
956
|
+
step = (max - min) / Math.floor(bins);
|
|
957
|
+
start = min;
|
|
958
|
+
} else {
|
|
959
|
+
const sturges = Math.ceil(Math.log2(finite.length) + 1);
|
|
960
|
+
step = niceStep((max - min) / sturges);
|
|
961
|
+
start = Math.floor(min / step) * step;
|
|
962
|
+
}
|
|
963
|
+
const count = Math.max(1, Math.ceil(clean((max - start) / step)));
|
|
964
|
+
edges = Array.from({ length: count }, (_, index) => ({
|
|
965
|
+
start: clean(start + index * step),
|
|
966
|
+
end: clean(start + (index + 1) * step)
|
|
967
|
+
}));
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
const buckets = edges.map(({ start, end, label }) => ({
|
|
971
|
+
label: label ?? bucketLabel(start, end, format),
|
|
972
|
+
start,
|
|
973
|
+
end,
|
|
974
|
+
count: 0
|
|
975
|
+
}));
|
|
976
|
+
const last = buckets.length - 1;
|
|
977
|
+
for (const value of finite) {
|
|
978
|
+
const index = buckets.findIndex(
|
|
979
|
+
(bucket, i) => value >= bucket.start && (value < bucket.end || i === last && value <= bucket.end)
|
|
980
|
+
);
|
|
981
|
+
if (index !== -1) buckets[index].count++;
|
|
982
|
+
}
|
|
983
|
+
return buckets;
|
|
984
|
+
}
|
|
985
|
+
function quantile(sorted, q) {
|
|
986
|
+
if (!sorted.length) return NaN;
|
|
987
|
+
const position = (sorted.length - 1) * q;
|
|
988
|
+
const lower = Math.floor(position);
|
|
989
|
+
const upper = Math.ceil(position);
|
|
990
|
+
if (lower === upper) return sorted[lower];
|
|
991
|
+
return sorted[lower] + (sorted[upper] - sorted[lower]) * (position - lower);
|
|
992
|
+
}
|
|
993
|
+
function boxplotStats(values) {
|
|
994
|
+
const sorted = values.filter(isFiniteNumber).sort((a, b) => a - b);
|
|
995
|
+
if (!sorted.length) return null;
|
|
996
|
+
const q1 = quantile(sorted, 0.25);
|
|
997
|
+
const median = quantile(sorted, 0.5);
|
|
998
|
+
const q3 = quantile(sorted, 0.75);
|
|
999
|
+
const iqr = q3 - q1;
|
|
1000
|
+
const lowFence = q1 - 1.5 * iqr;
|
|
1001
|
+
const highFence = q3 + 1.5 * iqr;
|
|
1002
|
+
const inside = sorted.filter((value) => value >= lowFence && value <= highFence);
|
|
1003
|
+
const outliers = sorted.filter((value) => value < lowFence || value > highFence);
|
|
1004
|
+
return {
|
|
1005
|
+
low: inside[0] ?? q1,
|
|
1006
|
+
q1,
|
|
1007
|
+
median,
|
|
1008
|
+
q3,
|
|
1009
|
+
high: inside[inside.length - 1] ?? q3,
|
|
1010
|
+
outliers,
|
|
1011
|
+
count: sorted.length,
|
|
1012
|
+
mean: sorted.reduce((sum, value) => sum + value, 0) / sorted.length
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
function waterfallLayout(steps) {
|
|
1016
|
+
let running = 0;
|
|
1017
|
+
return steps.map((step) => {
|
|
1018
|
+
let start;
|
|
1019
|
+
let end;
|
|
1020
|
+
let kind;
|
|
1021
|
+
if (step.isTotal === "value") {
|
|
1022
|
+
start = 0;
|
|
1023
|
+
end = isFiniteNumber(step.value) ? step.value : running;
|
|
1024
|
+
kind = "total";
|
|
1025
|
+
} else if (step.isTotal) {
|
|
1026
|
+
start = 0;
|
|
1027
|
+
end = running;
|
|
1028
|
+
kind = "total";
|
|
1029
|
+
} else {
|
|
1030
|
+
const change = isFiniteNumber(step.value) ? step.value : 0;
|
|
1031
|
+
start = running;
|
|
1032
|
+
end = running + change;
|
|
1033
|
+
kind = change < 0 ? "decrease" : "increase";
|
|
1034
|
+
}
|
|
1035
|
+
running = end;
|
|
1036
|
+
const low = Math.min(start, end);
|
|
1037
|
+
const high = Math.max(start, end);
|
|
1038
|
+
let base = 0;
|
|
1039
|
+
let positive = 0;
|
|
1040
|
+
let negative = 0;
|
|
1041
|
+
if (low >= 0) {
|
|
1042
|
+
base = low;
|
|
1043
|
+
positive = high - low;
|
|
1044
|
+
} else if (high <= 0) {
|
|
1045
|
+
base = high;
|
|
1046
|
+
negative = low - high;
|
|
1047
|
+
} else {
|
|
1048
|
+
positive = high;
|
|
1049
|
+
negative = low;
|
|
1050
|
+
}
|
|
1051
|
+
return {
|
|
1052
|
+
start,
|
|
1053
|
+
end,
|
|
1054
|
+
value: kind === "total" ? end : end - start,
|
|
1055
|
+
kind,
|
|
1056
|
+
base: clean(base),
|
|
1057
|
+
positive: clean(positive),
|
|
1058
|
+
negative: clean(negative)
|
|
1059
|
+
};
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
function toPercentStack(series) {
|
|
1063
|
+
const length = Math.max(0, ...series.map((s) => s.data.length));
|
|
1064
|
+
const totals = Array.from(
|
|
1065
|
+
{ length },
|
|
1066
|
+
(_, index) => series.reduce((sum, s) => {
|
|
1067
|
+
const value = s.data[index];
|
|
1068
|
+
return sum + (isFiniteNumber(value) ? Math.abs(value) : 0);
|
|
1069
|
+
}, 0)
|
|
1070
|
+
);
|
|
1071
|
+
return series.map((s) => ({
|
|
1072
|
+
...s,
|
|
1073
|
+
data: s.data.map((value, index) => {
|
|
1074
|
+
if (!isFiniteNumber(value)) return null;
|
|
1075
|
+
return totals[index] === 0 ? 0 : clean(value / totals[index] * 100);
|
|
1076
|
+
})
|
|
1077
|
+
}));
|
|
1078
|
+
}
|
|
1079
|
+
function pairWithCategories(categories, values, axisType) {
|
|
1080
|
+
if (axisType !== "time" && axisType !== "value") return values;
|
|
1081
|
+
return values.map((value, index) => [categories[index], value]);
|
|
1082
|
+
}
|
|
1083
|
+
function resolveChartHeight(height, rows, { rowHeight = 28, chrome = 56, minHeight = 160, maxHeight } = {}) {
|
|
1084
|
+
if (height !== "auto") return height;
|
|
1085
|
+
let value = Math.max(minHeight, chrome + Math.max(rows, 1) * rowHeight);
|
|
1086
|
+
if (maxHeight !== void 0) value = Math.min(value, maxHeight);
|
|
1087
|
+
return value;
|
|
1088
|
+
}
|
|
1089
|
+
function allSeriesEmpty(series) {
|
|
1090
|
+
return !series?.length || series.every((s) => !s.data?.some((value) => value != null));
|
|
1091
|
+
}
|
|
1092
|
+
function trendLine(points) {
|
|
1093
|
+
const finite = points.filter(([x, y]) => isFiniteNumber(x) && isFiniteNumber(y));
|
|
1094
|
+
const n = finite.length;
|
|
1095
|
+
if (n < 2) return null;
|
|
1096
|
+
let sumX = 0;
|
|
1097
|
+
let sumY = 0;
|
|
1098
|
+
let sumXY = 0;
|
|
1099
|
+
let sumX2 = 0;
|
|
1100
|
+
let minX = Infinity;
|
|
1101
|
+
let maxX = -Infinity;
|
|
1102
|
+
for (const [x, y] of finite) {
|
|
1103
|
+
sumX += x;
|
|
1104
|
+
sumY += y;
|
|
1105
|
+
sumXY += x * y;
|
|
1106
|
+
sumX2 += x * x;
|
|
1107
|
+
if (x < minX) minX = x;
|
|
1108
|
+
if (x > maxX) maxX = x;
|
|
1109
|
+
}
|
|
1110
|
+
const denominator = n * sumX2 - sumX * sumX;
|
|
1111
|
+
if (minX === maxX || denominator === 0) return null;
|
|
1112
|
+
const slope = (n * sumXY - sumX * sumY) / denominator;
|
|
1113
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
1114
|
+
return [
|
|
1115
|
+
[minX, slope * minX + intercept],
|
|
1116
|
+
[maxX, slope * maxX + intercept]
|
|
1117
|
+
];
|
|
1118
|
+
}
|
|
1119
|
+
function computeDelta(current, previous) {
|
|
1120
|
+
if (!isFiniteNumber(current) || !isFiniteNumber(previous)) return null;
|
|
1121
|
+
const change = clean(current - previous);
|
|
1122
|
+
return {
|
|
1123
|
+
change,
|
|
1124
|
+
ratio: previous === 0 ? null : change / Math.abs(previous),
|
|
1125
|
+
direction: change > 0 ? "up" : change < 0 ? "down" : "flat"
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// src/utils/records.ts
|
|
1130
|
+
function read(row, accessor) {
|
|
1131
|
+
return typeof accessor === "function" ? accessor(row) : row[accessor];
|
|
1132
|
+
}
|
|
1133
|
+
function toNumber(value) {
|
|
1134
|
+
if (value == null || value === "") return null;
|
|
1135
|
+
const number = typeof value === "number" ? value : Number(value);
|
|
1136
|
+
return Number.isFinite(number) ? number : null;
|
|
1137
|
+
}
|
|
1138
|
+
function toLabel(value, nullLabel) {
|
|
1139
|
+
return value == null || value === "" ? nullLabel : String(value);
|
|
1140
|
+
}
|
|
1141
|
+
var Cell = class {
|
|
1142
|
+
constructor() {
|
|
1143
|
+
this.sum = 0;
|
|
1144
|
+
this.count = 0;
|
|
1145
|
+
this.min = Infinity;
|
|
1146
|
+
this.max = -Infinity;
|
|
1147
|
+
this.first = null;
|
|
1148
|
+
this.last = null;
|
|
1149
|
+
}
|
|
1150
|
+
add(value) {
|
|
1151
|
+
this.count++;
|
|
1152
|
+
if (value === null) return;
|
|
1153
|
+
this.sum += value;
|
|
1154
|
+
if (value < this.min) this.min = value;
|
|
1155
|
+
if (value > this.max) this.max = value;
|
|
1156
|
+
if (this.first === null) this.first = value;
|
|
1157
|
+
this.last = value;
|
|
1158
|
+
}
|
|
1159
|
+
result(aggregate, present) {
|
|
1160
|
+
switch (aggregate) {
|
|
1161
|
+
case "count":
|
|
1162
|
+
return this.count;
|
|
1163
|
+
case "avg":
|
|
1164
|
+
return present ? this.sum / present : null;
|
|
1165
|
+
case "min":
|
|
1166
|
+
return present ? this.min : null;
|
|
1167
|
+
case "max":
|
|
1168
|
+
return present ? this.max : null;
|
|
1169
|
+
case "first":
|
|
1170
|
+
return this.first;
|
|
1171
|
+
case "last":
|
|
1172
|
+
return this.last;
|
|
1173
|
+
default:
|
|
1174
|
+
return present ? this.sum : null;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
};
|
|
1178
|
+
var Accumulator = class {
|
|
1179
|
+
constructor() {
|
|
1180
|
+
this.cells = /* @__PURE__ */ new Map();
|
|
1181
|
+
}
|
|
1182
|
+
add(key, value) {
|
|
1183
|
+
let cell = this.cells.get(key);
|
|
1184
|
+
if (!cell) {
|
|
1185
|
+
cell = Object.assign(new Cell(), { present: 0 });
|
|
1186
|
+
this.cells.set(key, cell);
|
|
1187
|
+
}
|
|
1188
|
+
cell.add(value);
|
|
1189
|
+
if (value !== null) cell.present++;
|
|
1190
|
+
}
|
|
1191
|
+
get(key, aggregate) {
|
|
1192
|
+
const cell = this.cells.get(key);
|
|
1193
|
+
return cell ? cell.result(aggregate, cell.present) : null;
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
function ordered(seen, order) {
|
|
1197
|
+
if (!order?.length) return seen;
|
|
1198
|
+
const present = new Set(seen);
|
|
1199
|
+
const first = order.filter((name) => present.has(name));
|
|
1200
|
+
const listed = new Set(first);
|
|
1201
|
+
return [...first, ...seen.filter((name) => !listed.has(name))];
|
|
1202
|
+
}
|
|
1203
|
+
function fromRecords(rows, options) {
|
|
1204
|
+
const {
|
|
1205
|
+
category,
|
|
1206
|
+
value,
|
|
1207
|
+
series,
|
|
1208
|
+
measures,
|
|
1209
|
+
seriesName = "Value",
|
|
1210
|
+
aggregate = "sum",
|
|
1211
|
+
categoryOrder,
|
|
1212
|
+
topN,
|
|
1213
|
+
otherLabel,
|
|
1214
|
+
seriesOrder,
|
|
1215
|
+
nullLabel = "Unknown",
|
|
1216
|
+
fill = null,
|
|
1217
|
+
colors: colors3
|
|
1218
|
+
} = options;
|
|
1219
|
+
const sort = options.sort ?? (topN !== void 0 ? "total" : "none");
|
|
1220
|
+
const categories = [];
|
|
1221
|
+
const categorySet = /* @__PURE__ */ new Set();
|
|
1222
|
+
const seriesNames = [];
|
|
1223
|
+
const seriesSet = /* @__PURE__ */ new Set();
|
|
1224
|
+
const cells = new Accumulator();
|
|
1225
|
+
const key = (c, s) => `${c}\0${s}`;
|
|
1226
|
+
const note = (list, set, name) => {
|
|
1227
|
+
if (!set.has(name)) {
|
|
1228
|
+
set.add(name);
|
|
1229
|
+
list.push(name);
|
|
1230
|
+
}
|
|
1231
|
+
};
|
|
1232
|
+
for (const row of rows ?? []) {
|
|
1233
|
+
const c = toLabel(read(row, category), nullLabel);
|
|
1234
|
+
note(categories, categorySet, c);
|
|
1235
|
+
if (measures) {
|
|
1236
|
+
for (const [name, accessor] of Object.entries(measures)) {
|
|
1237
|
+
note(seriesNames, seriesSet, name);
|
|
1238
|
+
cells.add(key(c, name), toNumber(read(row, accessor)));
|
|
1239
|
+
}
|
|
1240
|
+
} else {
|
|
1241
|
+
const s = series ? toLabel(read(row, series), nullLabel) : seriesName;
|
|
1242
|
+
note(seriesNames, seriesSet, s);
|
|
1243
|
+
cells.add(key(c, s), value === void 0 ? 1 : toNumber(read(row, value)));
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
const names = ordered(seriesNames, seriesOrder);
|
|
1247
|
+
const cellValue = (c, s) => cells.get(key(c, s), aggregate);
|
|
1248
|
+
const total = (c) => names.reduce((sum, s) => sum + (cellValue(c, s) ?? 0), 0);
|
|
1249
|
+
let order = ordered(categories, categoryOrder);
|
|
1250
|
+
if (sort === "total" || sort === "total-asc") {
|
|
1251
|
+
const totals = new Map(order.map((c) => [c, total(c)]));
|
|
1252
|
+
const direction = sort === "total" ? -1 : 1;
|
|
1253
|
+
order = [...order].sort((a, b) => direction * (totals.get(a) - totals.get(b)));
|
|
1254
|
+
} else if (sort === "category") {
|
|
1255
|
+
order = [...order].sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
|
|
1256
|
+
} else if (typeof sort === "function") {
|
|
1257
|
+
order = [...order].sort(sort);
|
|
1258
|
+
}
|
|
1259
|
+
let rest = [];
|
|
1260
|
+
if (topN !== void 0 && order.length > topN) {
|
|
1261
|
+
rest = order.slice(topN);
|
|
1262
|
+
order = order.slice(0, topN);
|
|
1263
|
+
}
|
|
1264
|
+
const foldOther = otherLabel !== void 0 && rest.length > 0 && (aggregate === "sum" || aggregate === "count");
|
|
1265
|
+
const result = names.map((name) => {
|
|
1266
|
+
const data = order.map((c) => cellValue(c, name) ?? fill);
|
|
1267
|
+
if (foldOther) {
|
|
1268
|
+
const other = rest.reduce((sum, c) => {
|
|
1269
|
+
const v = cellValue(c, name);
|
|
1270
|
+
return v === null ? sum : (sum ?? 0) + v;
|
|
1271
|
+
}, null);
|
|
1272
|
+
data.push(other ?? fill);
|
|
1273
|
+
}
|
|
1274
|
+
return colors3?.[name] ? { name, data, color: colors3[name] } : { name, data };
|
|
1275
|
+
});
|
|
1276
|
+
return {
|
|
1277
|
+
categories: foldOther ? [...order, otherLabel] : order,
|
|
1278
|
+
series: result
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1281
|
+
function toSliceData(rows, options) {
|
|
1282
|
+
const {
|
|
1283
|
+
name,
|
|
1284
|
+
value,
|
|
1285
|
+
aggregate = "sum",
|
|
1286
|
+
dropZero = true,
|
|
1287
|
+
sort = "desc",
|
|
1288
|
+
topN,
|
|
1289
|
+
otherLabel,
|
|
1290
|
+
nullLabel = "Unknown",
|
|
1291
|
+
colors: colors3
|
|
1292
|
+
} = options;
|
|
1293
|
+
const { categories, series } = fromRecords(rows, {
|
|
1294
|
+
category: name,
|
|
1295
|
+
value,
|
|
1296
|
+
aggregate: value === void 0 ? "count" : aggregate,
|
|
1297
|
+
nullLabel
|
|
1298
|
+
});
|
|
1299
|
+
const values = series[0]?.data ?? [];
|
|
1300
|
+
let slices = categories.map((label, index) => ({ name: label, value: values[index] ?? 0 }));
|
|
1301
|
+
if (dropZero) slices = slices.filter((slice) => slice.value !== 0);
|
|
1302
|
+
if (sort !== "none") {
|
|
1303
|
+
const direction = sort === "desc" ? -1 : 1;
|
|
1304
|
+
slices = [...slices].sort((a, b) => direction * (a.value - b.value));
|
|
1305
|
+
}
|
|
1306
|
+
if (topN !== void 0 && slices.length > topN) {
|
|
1307
|
+
const rest = slices.slice(topN);
|
|
1308
|
+
slices = slices.slice(0, topN);
|
|
1309
|
+
if (otherLabel !== void 0) {
|
|
1310
|
+
slices.push({ name: otherLabel, value: rest.reduce((sum, slice) => sum + slice.value, 0) });
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
return slices.map(
|
|
1314
|
+
(slice) => colors3?.[slice.name] ? { ...slice, color: colors3[slice.name] } : slice
|
|
1315
|
+
);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
// src/utils/tables.ts
|
|
1319
|
+
function categoryTable(data, categoryHeader = "Category") {
|
|
1320
|
+
return {
|
|
1321
|
+
headers: [categoryHeader, ...data.series.map((s) => s.name)],
|
|
1322
|
+
rows: data.categories.map((category, index) => [
|
|
1323
|
+
category,
|
|
1324
|
+
...data.series.map((s) => s.data[index] ?? null)
|
|
1325
|
+
]),
|
|
1326
|
+
columns: ["text", ...data.series.map(() => "value")]
|
|
1327
|
+
};
|
|
1328
|
+
}
|
|
1329
|
+
function sliceTable(slices, nameHeader = "Name") {
|
|
1330
|
+
const total = slices.reduce(
|
|
1331
|
+
(sum, slice) => sum + (Number.isFinite(slice.value) ? slice.value : 0),
|
|
1332
|
+
0
|
|
1333
|
+
);
|
|
1334
|
+
return {
|
|
1335
|
+
headers: [nameHeader, "Value", "Share"],
|
|
1336
|
+
rows: slices.map((slice) => [
|
|
1337
|
+
slice.name,
|
|
1338
|
+
slice.value,
|
|
1339
|
+
total ? Number((slice.value / total * 100).toFixed(1)) : 0
|
|
1340
|
+
]),
|
|
1341
|
+
columns: ["text", "value", "percent"]
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
function scatterTable(data, xHeader = "X", yHeader = "Y") {
|
|
1345
|
+
return {
|
|
1346
|
+
headers: ["Series", xHeader, yHeader],
|
|
1347
|
+
rows: data.series.flatMap((s) => s.data.map(([x, y]) => [s.name, x, y])),
|
|
1348
|
+
columns: ["text", "number", "value"]
|
|
1349
|
+
};
|
|
1350
|
+
}
|
|
1351
|
+
function radarTable(data, indicators) {
|
|
1352
|
+
return {
|
|
1353
|
+
headers: ["Indicator", ...data.series.map((s) => s.name)],
|
|
1354
|
+
rows: indicators.map((indicator, index) => [
|
|
1355
|
+
indicator.name,
|
|
1356
|
+
...data.series.map((s) => s.data[index] ?? null)
|
|
1357
|
+
]),
|
|
1358
|
+
columns: ["text", ...data.series.map(() => "value")]
|
|
1359
|
+
};
|
|
1360
|
+
}
|
|
1361
|
+
function heatmapTable(data, xAxis, yAxis) {
|
|
1362
|
+
const cells = new Map(data.data.map(([x, y, value]) => [`${x},${y}`, value]));
|
|
1363
|
+
return {
|
|
1364
|
+
headers: ["", ...xAxis],
|
|
1365
|
+
rows: yAxis.map((label, y) => [label, ...xAxis.map((_, x) => cells.get(`${x},${y}`) ?? null)]),
|
|
1366
|
+
columns: ["text", ...xAxis.map(() => "value")]
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
function treemapTable(nodes) {
|
|
1370
|
+
const rows = [];
|
|
1371
|
+
const visit = (list, path) => {
|
|
1372
|
+
for (const node of list) {
|
|
1373
|
+
const nodePath = [...path, node.name];
|
|
1374
|
+
if (node.children?.length) visit(node.children, nodePath);
|
|
1375
|
+
else rows.push([nodePath.join(" / "), node.value ?? null]);
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
visit(nodes, []);
|
|
1379
|
+
return { headers: ["Item", "Value"], rows, columns: ["text", "value"] };
|
|
1380
|
+
}
|
|
1381
|
+
function sankeyTable(data) {
|
|
1382
|
+
return {
|
|
1383
|
+
headers: ["From", "To", "Value"],
|
|
1384
|
+
rows: data.links.map((link) => [link.source, link.target, link.value]),
|
|
1385
|
+
columns: ["text", "text", "value"]
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
function candlestickTable(data) {
|
|
1389
|
+
const withVolume = !!data.volume?.length;
|
|
1390
|
+
return {
|
|
1391
|
+
headers: ["Date", "Open", "High", "Low", "Close", ...withVolume ? ["Volume"] : []],
|
|
1392
|
+
rows: data.data.map((item, index) => [
|
|
1393
|
+
data.dates[index] ?? "",
|
|
1394
|
+
item.open,
|
|
1395
|
+
item.high,
|
|
1396
|
+
item.low,
|
|
1397
|
+
item.close,
|
|
1398
|
+
...withVolume ? [data.volume?.[index] ?? null] : []
|
|
1399
|
+
]),
|
|
1400
|
+
columns: [
|
|
1401
|
+
"text",
|
|
1402
|
+
"value",
|
|
1403
|
+
"value",
|
|
1404
|
+
"value",
|
|
1405
|
+
"value",
|
|
1406
|
+
...withVolume ? ["number"] : []
|
|
1407
|
+
]
|
|
1408
|
+
};
|
|
1409
|
+
}
|
|
1410
|
+
function histogramTable(buckets) {
|
|
1411
|
+
return {
|
|
1412
|
+
headers: ["Range", "Count"],
|
|
1413
|
+
rows: buckets.map((bucket) => [bucket.label, bucket.count]),
|
|
1414
|
+
columns: ["text", "number"]
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
function boxPlotTable(groups) {
|
|
1418
|
+
return {
|
|
1419
|
+
headers: ["Group", "Count", "Low", "Q1", "Median", "Q3", "High", "Outliers"],
|
|
1420
|
+
rows: groups.map(
|
|
1421
|
+
({ name, stats }) => stats ? [
|
|
1422
|
+
name,
|
|
1423
|
+
stats.count,
|
|
1424
|
+
stats.low,
|
|
1425
|
+
stats.q1,
|
|
1426
|
+
stats.median,
|
|
1427
|
+
stats.q3,
|
|
1428
|
+
stats.high,
|
|
1429
|
+
stats.outliers.length
|
|
1430
|
+
] : [name, 0, null, null, null, null, null, 0]
|
|
1431
|
+
),
|
|
1432
|
+
columns: ["text", "number", "value", "value", "value", "value", "value", "number"]
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
function waterfallTable(steps, bars) {
|
|
1436
|
+
return {
|
|
1437
|
+
headers: ["Step", "Change", "Total"],
|
|
1438
|
+
rows: steps.map((step, index) => [
|
|
1439
|
+
step.name,
|
|
1440
|
+
bars[index]?.kind === "total" ? null : bars[index]?.value ?? null,
|
|
1441
|
+
bars[index]?.end ?? null
|
|
1442
|
+
]),
|
|
1443
|
+
columns: ["text", "value", "value"]
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
function bulletTable(items) {
|
|
1447
|
+
const withTarget = items.some((item) => item.target !== void 0);
|
|
1448
|
+
return {
|
|
1449
|
+
headers: ["Name", "Value", ...withTarget ? ["Target"] : []],
|
|
1450
|
+
rows: items.map((item) => [
|
|
1451
|
+
item.name,
|
|
1452
|
+
item.value,
|
|
1453
|
+
...withTarget ? [item.target ?? null] : []
|
|
1454
|
+
]),
|
|
1455
|
+
columns: ["text", "value", ...withTarget ? ["value"] : []]
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
function formatTableCell(table, cell, column, format) {
|
|
1459
|
+
if (typeof cell !== "number") return cell ?? "";
|
|
1460
|
+
switch (table.columns?.[column] ?? "value") {
|
|
1461
|
+
case "number":
|
|
1462
|
+
return formatNumber(cell, { maximumFractionDigits: 2 });
|
|
1463
|
+
case "percent":
|
|
1464
|
+
return `${cell}%`;
|
|
1465
|
+
case "text":
|
|
1466
|
+
return String(cell);
|
|
1467
|
+
default:
|
|
1468
|
+
return formatValue(cell, format);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
function csvCell(cell) {
|
|
1472
|
+
if (cell == null) return "";
|
|
1473
|
+
const text = String(cell);
|
|
1474
|
+
return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
|
|
1475
|
+
}
|
|
1476
|
+
function tableToCsv(table, format) {
|
|
1477
|
+
const lines = [table.headers, ...table.rows].map(
|
|
1478
|
+
(row, rowIndex) => row.map(
|
|
1479
|
+
(cell, column) => csvCell(rowIndex > 0 && format ? formatTableCell(table, cell, column, format) : cell)
|
|
1480
|
+
).join(",")
|
|
1481
|
+
);
|
|
1482
|
+
return lines.join("\r\n");
|
|
1483
|
+
}
|
|
1484
|
+
function chartFilename(title) {
|
|
1485
|
+
return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "chart";
|
|
1486
|
+
}
|
|
1487
|
+
function downloadFile(content, filename, mimeType = "text/plain") {
|
|
1488
|
+
if (typeof document === "undefined") return;
|
|
1489
|
+
const isDataUrl = content.startsWith("data:");
|
|
1490
|
+
const url = isDataUrl ? content : URL.createObjectURL(new Blob([content], { type: `${mimeType};charset=utf-8` }));
|
|
1491
|
+
const link = document.createElement("a");
|
|
1492
|
+
link.href = url;
|
|
1493
|
+
link.download = filename;
|
|
1494
|
+
link.style.display = "none";
|
|
1495
|
+
document.body.appendChild(link);
|
|
1496
|
+
link.click();
|
|
1497
|
+
link.remove();
|
|
1498
|
+
if (!isDataUrl) setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
1499
|
+
}
|
|
1500
|
+
function downloadCsv(table, filename) {
|
|
1501
|
+
downloadFile(
|
|
1502
|
+
`\uFEFF${tableToCsv(table)}`,
|
|
1503
|
+
filename.endsWith(".csv") ? filename : `${filename}.csv`,
|
|
1504
|
+
"text/csv"
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
478
1507
|
|
|
479
1508
|
exports.THEME_DARK = THEME_DARK;
|
|
480
1509
|
exports.THEME_LIGHT = THEME_LIGHT;
|
|
481
1510
|
exports.abbreviateNumber = abbreviateNumber;
|
|
1511
|
+
exports.allSeriesEmpty = allSeriesEmpty;
|
|
1512
|
+
exports.anchorCenterGraphic = anchorCenterGraphic;
|
|
1513
|
+
exports.binValues = binValues;
|
|
1514
|
+
exports.boxPlotTable = boxPlotTable;
|
|
1515
|
+
exports.boxplotStats = boxplotStats;
|
|
1516
|
+
exports.buildAxis = buildAxis;
|
|
1517
|
+
exports.buildDataLabel = buildDataLabel;
|
|
1518
|
+
exports.buildDataZoom = buildDataZoom;
|
|
1519
|
+
exports.buildLegend = buildLegend;
|
|
1520
|
+
exports.buildMarkArea = buildMarkArea;
|
|
1521
|
+
exports.buildMarkLine = buildMarkLine;
|
|
1522
|
+
exports.buildTooltip = buildTooltip;
|
|
1523
|
+
exports.bulletTable = bulletTable;
|
|
1524
|
+
exports.candlestickTable = candlestickTable;
|
|
1525
|
+
exports.categoryTable = categoryTable;
|
|
1526
|
+
exports.chartFilename = chartFilename;
|
|
1527
|
+
exports.chartFormatters = chartFormatters;
|
|
482
1528
|
exports.colorWithAlpha = colorWithAlpha;
|
|
1529
|
+
exports.computeDelta = computeDelta;
|
|
483
1530
|
exports.createTooltipFormatter = createTooltipFormatter;
|
|
484
1531
|
exports.darkChartColors = darkChartColors;
|
|
485
1532
|
exports.darkTheme = darkTheme;
|
|
1533
|
+
exports.downloadCsv = downloadCsv;
|
|
1534
|
+
exports.downloadFile = downloadFile;
|
|
486
1535
|
exports.escapeHtml = escapeHtml;
|
|
487
1536
|
exports.formatCurrency = formatCurrency;
|
|
488
1537
|
exports.formatDate = formatDate;
|
|
489
1538
|
exports.formatNumber = formatNumber;
|
|
490
1539
|
exports.formatPercent = formatPercent;
|
|
1540
|
+
exports.formatTableCell = formatTableCell;
|
|
1541
|
+
exports.formatTooltipValue = formatTooltipValue;
|
|
1542
|
+
exports.formatValue = formatValue;
|
|
1543
|
+
exports.fromRecords = fromRecords;
|
|
1544
|
+
exports.generateAreaChartDescription = generateAreaChartDescription;
|
|
491
1545
|
exports.generateBarChartDescription = generateBarChartDescription;
|
|
1546
|
+
exports.generateBoxPlotDescription = generateBoxPlotDescription;
|
|
1547
|
+
exports.generateBulletDescription = generateBulletDescription;
|
|
1548
|
+
exports.generateComboChartDescription = generateComboChartDescription;
|
|
492
1549
|
exports.generateDarkTheme = generateDarkTheme;
|
|
493
1550
|
exports.generateDataTable = generateDataTable;
|
|
1551
|
+
exports.generateDotPlotDescription = generateDotPlotDescription;
|
|
1552
|
+
exports.generateHistogramDescription = generateHistogramDescription;
|
|
494
1553
|
exports.generateLightTheme = generateLightTheme;
|
|
495
1554
|
exports.generateLineChartDescription = generateLineChartDescription;
|
|
496
1555
|
exports.generatePieChartDescription = generatePieChartDescription;
|
|
497
1556
|
exports.generateScatterChartDescription = generateScatterChartDescription;
|
|
1557
|
+
exports.generateWaterfallDescription = generateWaterfallDescription;
|
|
498
1558
|
exports.getChartColors = getChartColors;
|
|
1559
|
+
exports.getLegendItems = getLegendItems;
|
|
499
1560
|
exports.getReplaceMerge = getReplaceMerge;
|
|
1561
|
+
exports.gridInset = gridInset;
|
|
1562
|
+
exports.hasPointLabels = hasPointLabels;
|
|
1563
|
+
exports.heatmapTable = heatmapTable;
|
|
1564
|
+
exports.histogramTable = histogramTable;
|
|
1565
|
+
exports.itemLabelLookup = itemLabelLookup;
|
|
1566
|
+
exports.legendCenter = legendCenter;
|
|
500
1567
|
exports.lightChartColors = lightChartColors;
|
|
501
1568
|
exports.lightTheme = lightTheme;
|
|
1569
|
+
exports.mergeChartOption = mergeChartOption;
|
|
1570
|
+
exports.nameLabelLookup = nameLabelLookup;
|
|
1571
|
+
exports.niceStep = niceStep;
|
|
1572
|
+
exports.pairWithCategories = pairWithCategories;
|
|
1573
|
+
exports.pointLabel = pointLabel;
|
|
1574
|
+
exports.quantile = quantile;
|
|
1575
|
+
exports.radarTable = radarTable;
|
|
1576
|
+
exports.resolveChartHeight = resolveChartHeight;
|
|
1577
|
+
exports.resolveValueFormatter = resolveValueFormatter;
|
|
1578
|
+
exports.sankeyTable = sankeyTable;
|
|
1579
|
+
exports.scatterTable = scatterTable;
|
|
1580
|
+
exports.seriesLabelLookup = seriesLabelLookup;
|
|
1581
|
+
exports.sliceTable = sliceTable;
|
|
1582
|
+
exports.tableToCsv = tableToCsv;
|
|
1583
|
+
exports.toPercentStack = toPercentStack;
|
|
1584
|
+
exports.toSliceData = toSliceData;
|
|
1585
|
+
exports.treemapTable = treemapTable;
|
|
1586
|
+
exports.trendLine = trendLine;
|
|
1587
|
+
exports.valueAxisConfig = valueAxisConfig;
|
|
1588
|
+
exports.valueLabelFormatter = valueLabelFormatter;
|
|
1589
|
+
exports.waterfallLayout = waterfallLayout;
|
|
1590
|
+
exports.waterfallTable = waterfallTable;
|
|
1591
|
+
exports.withPointLabels = withPointLabels;
|
|
1592
|
+
exports.withReferenceMarks = withReferenceMarks;
|
|
1593
|
+
exports.withZoomInset = withZoomInset;
|