@squadbase/vantage 0.7.0 → 0.7.1-dev1.215984c

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +1 -1
  2. package/dist/add-AU3EDPNW.js +6 -0
  3. package/dist/add-AU3EDPNW.js.map +1 -0
  4. package/dist/{chunk-AABMKHEU.js → chunk-35B3YIBF.js} +16 -7
  5. package/dist/chunk-35B3YIBF.js.map +1 -0
  6. package/dist/{chunk-QNOX7WLZ.js → chunk-5HBUBVQ3.js} +94 -8
  7. package/dist/chunk-5HBUBVQ3.js.map +1 -0
  8. package/dist/{chunk-TRMHNMMZ.js → chunk-LYUGDHSF.js} +3 -3
  9. package/dist/{chunk-TRMHNMMZ.js.map → chunk-LYUGDHSF.js.map} +1 -1
  10. package/dist/{chunk-2IAPIOPS.js → chunk-P2KIHF65.js} +2 -2
  11. package/dist/chunk-P2KIHF65.js.map +1 -0
  12. package/dist/{chunk-77CISBMG.js → chunk-SC7K2RD6.js} +3598 -337
  13. package/dist/chunk-SC7K2RD6.js.map +1 -0
  14. package/dist/{chunk-3RJEOP3J.js → chunk-XRKSIC5Z.js} +5 -3
  15. package/dist/chunk-XRKSIC5Z.js.map +1 -0
  16. package/dist/cli.js +2 -2
  17. package/dist/components/index.d.ts +644 -9
  18. package/dist/components/index.js +4 -4
  19. package/dist/{delta-text-CbrOaW83.d.ts → delta-text-tav2Lh5d.d.ts} +1 -1
  20. package/dist/doc/index.d.ts +2 -2
  21. package/dist/doc/index.js +4 -4
  22. package/dist/{format-DihQkL1N.d.ts → format-D34piCei.d.ts} +2 -1
  23. package/dist/geo-japan-7BMSOJRX.js +8 -0
  24. package/dist/geo-japan-7BMSOJRX.js.map +1 -0
  25. package/dist/geo-us-GVOG7VXH.js +8 -0
  26. package/dist/geo-us-GVOG7VXH.js.map +1 -0
  27. package/dist/geo-world-35HL56UU.js +8 -0
  28. package/dist/geo-world-35HL56UU.js.map +1 -0
  29. package/dist/slide/index.d.ts +2 -2
  30. package/dist/slide/index.js +3 -3
  31. package/dist/table/index.js +4 -5
  32. package/dist/table/index.js.map +1 -1
  33. package/dist/ui/index.d.ts +7 -3
  34. package/dist/ui/index.js +4 -4
  35. package/dist/ui/index.js.map +1 -1
  36. package/dist/{upgrade-6DT7ADRP.js → upgrade-MCHMELWC.js} +3 -3
  37. package/dist/{upgrade-6DT7ADRP.js.map → upgrade-MCHMELWC.js.map} +1 -1
  38. package/docs/en/agent-skills.md +13 -14
  39. package/docs/en/changelog.md +8 -0
  40. package/docs/en/parts/draft-chart.md +42 -0
  41. package/docs/en/parts/page-shell.md +1 -1
  42. package/docs/index.json +70 -50
  43. package/docs/ja/agent-skills.md +14 -14
  44. package/docs/ja/changelog.md +8 -0
  45. package/docs/ja/parts/draft-chart.md +40 -0
  46. package/docs/ja/parts/page-shell.md +1 -1
  47. package/package.json +2 -2
  48. package/registry/parts.json +5240 -0
  49. package/registry/ui/chart-frame.tsx +8 -0
  50. package/registry/ui/combo-chart.tsx +1852 -0
  51. package/registry/ui/dashboard-card.tsx +3 -1
  52. package/registry/ui/data-table.tsx +3 -1
  53. package/registry/ui/delta.tsx +3 -1
  54. package/registry/ui/filter-bar.tsx +3 -1
  55. package/registry/ui/forecast-chart.tsx +4734 -0
  56. package/registry/ui/funnel-steps.tsx +1 -1
  57. package/registry/ui/kpi-card.tsx +3 -1
  58. package/registry/ui/metric-value.tsx +2 -2
  59. package/registry/ui/page-shell.tsx +25 -4
  60. package/registry/ui/paired-chart.tsx +1 -1
  61. package/registry/ui/pivot-table.tsx +1260 -0
  62. package/registry/ui/progress-circle.tsx +2 -2
  63. package/registry/ui/sparkline.tsx +1 -1
  64. package/registry/ui/treemap.tsx +1917 -0
  65. package/templates/AGENTS.md +3 -3
  66. package/templates/agents/page.md +5 -4
  67. package/dist/add-4GHF2TX2.js +0 -6
  68. package/dist/add-4GHF2TX2.js.map +0 -1
  69. package/dist/chunk-2IAPIOPS.js.map +0 -1
  70. package/dist/chunk-3RJEOP3J.js.map +0 -1
  71. package/dist/chunk-77CISBMG.js.map +0 -1
  72. package/dist/chunk-AABMKHEU.js.map +0 -1
  73. package/dist/chunk-NZ5QIIG4.js +0 -74
  74. package/dist/chunk-NZ5QIIG4.js.map +0 -1
  75. package/dist/chunk-QNOX7WLZ.js.map +0 -1
@@ -1,19 +1,19 @@
1
- import { ChartFrameContext, useChartSurface, resolveChartColors, CHART_OTHER_COLOR, EChart, resolveFillColors } from './chunk-EG4PX726.js';
2
- import { Sparkline } from './chunk-2IAPIOPS.js';
3
- import { formatDateRange, EMPTY_VALUE, formatDate, toValueFormatter, formatValue, formatPoints, formatKind, formatDelta, MINUS_SIGN, formatDatesInText, DEFAULT_LOCALE } from './chunk-3RJEOP3J.js';
4
- import { Tooltip, TooltipTrigger, TooltipContent, Badge, SidebarProvider, Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarFooter, SidebarRail, SidebarInset, SidebarTrigger, Separator, Input, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Skeleton, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuCheckboxItem, useIsMobile, Popover, PopoverTrigger, PopoverContent, Calendar, ToggleGroup, ToggleGroupItem, SelectGroup, SelectLabel, Collapsible, CollapsibleTrigger, SidebarMenuButton, SidebarMenuBadge, CollapsibleContent, SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton, SidebarMenuItem, Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, Checkbox, Card, CardHeader, CardDescription, CardTitle, CardAction, CardFooter } from './chunk-QNOX7WLZ.js';
1
+ import { ChartFrameContext, useChartSurface, resolveChartColors, resolveFillColors, EChart, CHART_OTHER_COLOR } from './chunk-EG4PX726.js';
2
+ import { Sparkline } from './chunk-P2KIHF65.js';
3
+ import { formatDateRange, EMPTY_VALUE, toValueFormatter, formatDate, formatValue, formatPoints, formatKind, formatDelta, MINUS_SIGN, DEFAULT_LOCALE, formatDatesInText } from './chunk-XRKSIC5Z.js';
4
+ import { Tooltip, TooltipTrigger, TooltipContent, Badge, SidebarProvider, Sidebar, SidebarContent, SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarFooter, SidebarRail, SidebarInset, SidebarTrigger, Separator, Input, Table, TableHeader, TableRow, TableHead, TableBody, TableCell, Skeleton, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuCheckboxItem, useIsMobile, Popover, PopoverTrigger, PopoverContent, Calendar, ToggleGroup, ToggleGroupItem, SelectGroup, SelectLabel, Collapsible, CollapsibleTrigger, SidebarMenuButton, SidebarMenuBadge, CollapsibleContent, SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton, SidebarMenuItem, Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, Empty, Checkbox, Card, CardHeader, CardDescription, CardTitle, CardAction, CardFooter } from './chunk-5HBUBVQ3.js';
5
5
  import { useLocale, uiLanguage, Button, weekStartsOn, resolveLocale, defaultFiscalYearStartMonth, FALLBACK_LOCALE } from './chunk-ROHNXP7J.js';
6
6
  import { cn } from './chunk-U736PVHY.js';
7
- import { forwardRef, useState, useEffect, createContext, useId, useMemo, useRef, useCallback, Fragment as Fragment$1, useContext } from 'react';
7
+ import { forwardRef, useState, useEffect, createContext, useId, useMemo, useRef, useCallback, Fragment as Fragment$1, useContext, useInsertionEffect, useLayoutEffect } from 'react';
8
8
  import { cva } from 'class-variance-authority';
9
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
10
  import { Search, Columns3, CalendarIcon, ChevronDownIcon, ArrowUp, ArrowDown, ArrowRight, Minus, ChevronRight, ArrowUpDown, XIcon, LoaderIcon, CheckIcon, Download, ChevronDown, X, CircleX, TriangleAlert, CircleCheck } from 'lucide-react';
11
- import { use } from 'echarts/core';
12
- import { GraphicComponent, VisualMapComponent } from 'echarts/components';
13
- import { ScatterChart as ScatterChart$1, HeatmapChart } from 'echarts/charts';
11
+ import { use, registerMap } from 'echarts/core';
12
+ import { GraphicComponent, VisualMapComponent, GeoComponent } from 'echarts/components';
13
+ import { ScatterChart as ScatterChart$1, HeatmapChart, MapChart, TreemapChart } from 'echarts/charts';
14
14
  import 'echarts';
15
+ import { subMonths, subDays, endOfDay, startOfDay, startOfWeek, subWeeks, endOfWeek, startOfMonth, endOfMonth, isSameDay, subYears, differenceInCalendarDays, addMonths, parseISO } from 'date-fns';
15
16
  import { flexRender, useReactTable, getPaginationRowModel, getFilteredRowModel, getSortedRowModel, getCoreRowModel } from '@tanstack/react-table';
16
- import { subMonths, subDays, endOfDay, startOfDay, startOfWeek, subWeeks, endOfWeek, startOfMonth, endOfMonth, isSameDay, addMonths } from 'date-fns';
17
17
  import { ja } from 'date-fns/locale';
18
18
 
19
19
  // @squadbase/vantage — generated build. Do not edit.
@@ -127,7 +127,13 @@ var UpdatedAt = forwardRef(
127
127
  );
128
128
  UpdatedAt.displayName = "UpdatedAt";
129
129
  var PageShell = forwardRef(
130
- ({ className, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, "data-slot": "page-shell", className: cn("flex flex-col", className), ...props })
130
+ ({ className, title, description, children, ...props }, ref) => /* @__PURE__ */ jsx("div", { ref, "data-slot": "page-shell", className: cn("flex flex-col", className), ...props, children: title === void 0 ? children : /* @__PURE__ */ jsxs(Fragment, { children: [
131
+ /* @__PURE__ */ jsx(PageShellHeader, { children: /* @__PURE__ */ jsxs(PageShellHeading, { children: [
132
+ /* @__PURE__ */ jsx(PageShellTitle, { children: title }),
133
+ description ? /* @__PURE__ */ jsx(PageShellDescription, { children: description }) : null
134
+ ] }) }),
135
+ /* @__PURE__ */ jsx(PageShellContent, { children })
136
+ ] }) })
131
137
  );
132
138
  PageShell.displayName = "PageShell";
133
139
  var SOURCE_PREFIX = { ja: "\u51FA\u5178\uFF1A", en: "Source: " };
@@ -621,7 +627,7 @@ var DashboardCardFooter = forwardRef(
621
627
  {
622
628
  ref,
623
629
  "data-slot": "dashboard-card-footer",
624
- className: cn("flex items-center px-3 py-1.5 group-data-[size=sm]:px-2", className),
630
+ className: cn("-mx-1 -mb-1 flex min-w-0 items-center justify-between gap-3 border-t px-4 py-2.5 text-xs text-muted-foreground tabular-nums group-data-[size=sm]:px-3", className),
625
631
  ...props
626
632
  }
627
633
  )
@@ -647,6 +653,7 @@ function DashboardCardSkeleton({ className }) {
647
653
  function DashboardCardPreset({
648
654
  title,
649
655
  description,
656
+ headline,
650
657
  actions,
651
658
  footer,
652
659
  children,
@@ -658,15 +665,21 @@ function DashboardCardPreset({
658
665
  ...props
659
666
  }) {
660
667
  const sample = useSampleMark();
661
- const hasHeader = title || description || actions || sample;
662
- return /* @__PURE__ */ jsx(DashboardCard, { className, subgrid, ...props, children: /* @__PURE__ */ jsxs(SampleClaim, { children: [
668
+ const hasTitle = Boolean(title || description);
669
+ const floatingMark = sample && !hasTitle && !headline && !actions;
670
+ const hasHeader = hasTitle || headline || actions || sample && !floatingMark;
671
+ return /* @__PURE__ */ jsx(DashboardCard, { className: cn(floatingMark && "relative", className), subgrid, ...props, children: /* @__PURE__ */ jsxs(SampleClaim, { children: [
672
+ floatingMark && /* @__PURE__ */ jsx(SampleBadge, { label: sample.label, className: "absolute top-2 right-2 z-10" }),
663
673
  hasHeader && /* @__PURE__ */ jsxs(DashboardCardHeader, { className: cn(subgrid && "row-start-1", headerClassName), children: [
664
- (title || description) && /* @__PURE__ */ jsxs("div", { children: [
665
- title && /* @__PURE__ */ jsx(DashboardCardTitle, { children: title }),
674
+ (hasTitle || sample) && /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
675
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-1", children: [
676
+ title && /* @__PURE__ */ jsx(DashboardCardTitle, { children: title }),
677
+ sample && /* @__PURE__ */ jsx(SampleBadge, { label: sample.label })
678
+ ] }),
666
679
  description && /* @__PURE__ */ jsx(DashboardCardDescription, { children: description })
667
680
  ] }),
668
- (actions || sample) && /* @__PURE__ */ jsxs(DashboardCardAction, { className: cn(sample && actions && "flex items-center gap-2"), children: [
669
- sample && /* @__PURE__ */ jsx(SampleBadge, { label: sample.label }),
681
+ (headline || actions) && /* @__PURE__ */ jsxs(DashboardCardAction, { className: "flex items-center gap-2", children: [
682
+ headline && /* @__PURE__ */ jsx("div", { "data-slot": "dashboard-card-headline", className: "text-sm tabular-nums text-muted-foreground", children: headline }),
670
683
  actions
671
684
  ] })
672
685
  ] }),
@@ -690,6 +703,7 @@ var ChartFrame = forwardRef(function ChartFrame2({
690
703
  ariaLabel,
691
704
  plotRole = "img",
692
705
  dataTable,
706
+ footnote,
693
707
  className,
694
708
  children,
695
709
  ...props
@@ -720,6 +734,7 @@ var ChartFrame = forwardRef(function ChartFrame2({
720
734
  children: state === "loading" ? /* @__PURE__ */ jsx(ChartSkeleton, { variant, loadingText: t.loading }) : state === "empty" ? /* @__PURE__ */ jsx(ChartEmpty, { ...emptyState, defaultTitle: t.emptyTitle }) : /* @__PURE__ */ jsx(ChartFrameContext.Provider, { value: true, children })
721
735
  }
722
736
  ),
737
+ footnote && /* @__PURE__ */ jsx("p", { "data-slot": "chart-frame-footnote", className: "text-xs leading-snug text-muted-foreground", children: footnote }),
723
738
  state === "ready" && dataTable && dataTable.rows.length > 0 && /* @__PURE__ */ jsx(ChartDataTableView, { table: dataTable, fallbackCaption: title ?? ariaLabel })
724
739
  ] });
725
740
  });
@@ -743,7 +758,7 @@ function ChartDataTableView({ table, fallbackCaption }) {
743
758
  caption && /* @__PURE__ */ jsx("caption", { children: caption }),
744
759
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: table.columns.map((column, i) => /* @__PURE__ */ jsx("th", { scope: "col", children: column }, i)) }) }),
745
760
  /* @__PURE__ */ jsx("tbody", { children: table.rows.map((row, r) => /* @__PURE__ */ jsx("tr", { children: row.map(
746
- (cell, c) => c === 0 ? /* @__PURE__ */ jsx("th", { scope: "row", children: cell }, c) : /* @__PURE__ */ jsx("td", { children: cell }, c)
761
+ (cell2, c) => c === 0 ? /* @__PURE__ */ jsx("th", { scope: "row", children: cell2 }, c) : /* @__PURE__ */ jsx("td", { children: cell2 }, c)
747
762
  ) }, r)) })
748
763
  ] }) });
749
764
  }
@@ -800,8 +815,8 @@ function chartColorAt(colors, i) {
800
815
  const palette = colors && colors.length > 0 ? colors : DEFAULT_CHART_COLORS;
801
816
  return palette[i % palette.length];
802
817
  }
803
- function tintCss(cssColor, percent) {
804
- return `color-mix(in oklab, ${cssColor} ${percent}%, transparent)`;
818
+ function tintCss(cssColor, percent2) {
819
+ return `color-mix(in oklab, ${cssColor} ${percent2}%, transparent)`;
805
820
  }
806
821
  function resolveValueFormatter(valueFormatter, format, locale) {
807
822
  return valueFormatter ?? toValueFormatter(format, locale);
@@ -1217,20 +1232,20 @@ function baselineOfLabel(named, language) {
1217
1232
  }
1218
1233
  return named;
1219
1234
  }
1220
- function comparisonBaseline(vs, grain, label2, toDate2, locale) {
1235
+ function comparisonBaseline(vs, grain, label2, toDate3, locale) {
1221
1236
  const language = uiLanguage(resolveLocale(locale));
1222
1237
  const nouns = BASELINE_NOUNS[language];
1223
1238
  const named = label2?.trim();
1224
1239
  if (named) return baselineOfLabel(named, language);
1225
- if (vs === "custom") return nouns.custom;
1226
1240
  if (vs === "target") return nouns.target;
1227
- if (vs === "prior-year" && toDate2) return nouns["prior-year"].unknown;
1241
+ if (vs === "prior-year" && toDate3) return nouns["prior-year"].unknown;
1242
+ if (vs !== "prior-year" && vs !== "prior-period") return nouns.custom;
1228
1243
  return nouns[vs][grain ?? "unknown"];
1229
1244
  }
1230
- function comparisonLabel(vs, grain, label2, toDate2, locale) {
1245
+ function comparisonLabel(vs, grain, label2, toDate3, locale) {
1231
1246
  if (label2?.trim()) return label2;
1232
1247
  const resolved = resolveLocale(locale);
1233
- return LABEL_OF[uiLanguage(resolved)](comparisonBaseline(vs, grain, void 0, toDate2, resolved));
1248
+ return LABEL_OF[uiLanguage(resolved)](comparisonBaseline(vs, grain, void 0, toDate3, resolved));
1234
1249
  }
1235
1250
  var toNumber = (v) => v === null || v === void 0 || Number.isNaN(v) ? null : v;
1236
1251
  function computeComparison(current, comparison, options) {
@@ -1622,6 +1637,9 @@ function legendLines(items, plotWidth, reserve = 0) {
1622
1637
  return Math.min(3, Math.max(1, Math.ceil(width / budget)));
1623
1638
  }
1624
1639
  var ACTIONS_RESERVE = 80;
1640
+ var BAR_MAX_WIDTH = 40;
1641
+ var BAR_RADIUS = 3;
1642
+ var STACK_GAP = 1.5;
1625
1643
  var TIME_LABEL_WIDTH = 80;
1626
1644
  var HIGHLIGHT_LABEL_WIDTH = 96;
1627
1645
  var TARGET_NEAR_SHARE = 0.12;
@@ -1740,9 +1758,9 @@ function buildCartesianOption(cfg) {
1740
1758
  const text = chartText(locale);
1741
1759
  const provisionalLabel = cfg.provisionalLabel ?? text.provisional;
1742
1760
  const stacked = kind !== "line" && type !== "default";
1743
- const percent = kind !== "line" && type === "percent";
1761
+ const percent2 = kind !== "line" && type === "percent";
1744
1762
  const horizontalBars = kind === "bar" && layout === "vertical";
1745
- const comparison = kind !== "bar" && !percent ? cfg.comparison : void 0;
1763
+ const comparison = kind !== "bar" && !percent2 ? cfg.comparison : void 0;
1746
1764
  const compIdx = comparison ? categories.indexOf(comparison.category) : -1;
1747
1765
  const mainIdx = categories.map((_, i) => i).filter((i) => i !== compIdx);
1748
1766
  const seriesType = kind === "line" ? "default" : type;
@@ -1752,16 +1770,17 @@ function buildCartesianOption(cfg) {
1752
1770
  const compared = cartesianSeriesValues(data, [categories[compIdx]], "default")[0];
1753
1771
  return categories.map((_, i) => i === compIdx ? compared : main[mainIdx.indexOf(i)]);
1754
1772
  })();
1773
+ const diverging = kind === "bar" && values.some((s) => s.some((v) => typeof v === "number" && v < 0));
1755
1774
  const comparisonColor = guideColor ? withAlpha(guideColor, COMPARISON_OPACITY) : void 0;
1756
1775
  const labels = data.map((row) => String(row[index] ?? ""));
1757
1776
  const rawIndex = data.map((row) => row[index]);
1758
1777
  const lastActual = labels.length - 1;
1759
1778
  const provStart = categories.length > 0 ? provisionalStart(rawIndex, cfg.provisionalFrom) : -1;
1760
- const forecast = kind !== "bar" && !percent && cfg.forecast && cfg.forecast.data.length > 0 && data.length > 0 && categories.length > 0 ? cfg.forecast : void 0;
1779
+ const forecast2 = kind !== "bar" && !percent2 && cfg.forecast && cfg.forecast.data.length > 0 && data.length > 0 && categories.length > 0 ? cfg.forecast : void 0;
1761
1780
  const forecastAt = [];
1762
- if (forecast) {
1781
+ if (forecast2) {
1763
1782
  const position = new Map(labels.map((label2, i) => [label2, i]));
1764
- for (const row of forecast.data) {
1783
+ for (const row of forecast2.data) {
1765
1784
  const label2 = String(row[index] ?? "");
1766
1785
  let at = position.get(label2);
1767
1786
  if (at === void 0 || at <= lastActual) {
@@ -1798,14 +1817,14 @@ function buildCartesianOption(cfg) {
1798
1817
  (title, i) => nameLabels ? hasLabel(nameLabels, labels[i] ?? "") ? labelOf(labels[i] ?? "", nameLabels) : title : title
1799
1818
  );
1800
1819
  const highlight = cfg.highlight ?? defaultChartHighlight(kind, data, index);
1801
- const stackTotals = stacked && !percent ? stackTotalsOf(mainIdx.map((i) => values[i])) : null;
1820
+ const stackTotals = stacked && !percent2 ? stackTotalsOf(mainIdx.map((i) => values[i])) : null;
1802
1821
  const highlightSeries = (stackTotals ? mainIdx[mainIdx.length - 1] : mainIdx[0]) ?? 0;
1803
- const shareHighlight = percent && kind === "area";
1804
- const picked = highlight !== "auto" || mainIdx.length === 0 || percent && !shareHighlight ? [] : shareHighlight ? categories.length <= 2 ? pickShareHighlightPoints(values[0] ?? []) : [] : stackTotals ? pickHighlightPoints(stackTotals) : mainIdx.length <= 2 ? pickHighlightPoints(values[highlightSeries] ?? []) : [];
1822
+ const shareHighlight = percent2 && kind === "area";
1823
+ const picked = highlight !== "auto" || mainIdx.length === 0 || percent2 && !shareHighlight ? [] : shareHighlight ? categories.length <= 2 ? pickShareHighlightPoints(values[0] ?? []) : [] : stackTotals ? pickHighlightPoints(stackTotals) : mainIdx.length <= 2 ? pickHighlightPoints(values[highlightSeries] ?? []) : [];
1805
1824
  const pointGap = plotWidth && labels.length > 1 ? plotWidth / (kind === "bar" ? labels.length : labels.length - 1) : null;
1806
1825
  const latestOnly = cfg.highlight === void 0 && !shareHighlight && picked.length === 2;
1807
1826
  const highlightPoints = latestOnly ? [picked[1]] : picked.length === 2 && pointGap !== null && Math.abs(picked[1].index - picked[0].index) * pointGap < HIGHLIGHT_LABEL_WIDTH ? [picked[1]] : picked;
1808
- const boundaryIndex = kind !== "bar" && !percent && cfg.forecast && cfg.forecast.data.length > 0 ? lastActual : -1;
1827
+ const boundaryIndex = kind !== "bar" && !percent2 && cfg.forecast && cfg.forecast.data.length > 0 ? lastActual : -1;
1809
1828
  const edgeAlign = (i) => horizontalBars || labels.length < 2 ? void 0 : i === boundaryIndex && i !== last ? { align: "right", offset: [-6, 0] } : i === last ? { align: "right", ...kind === "bar" ? { offset: [8, 0] } : {} } : i === 0 ? { align: "left", ...kind === "bar" ? { offset: [-8, 0] } : {} } : void 0;
1810
1829
  const perBarTarget = kind === "bar" && Boolean(targetKey);
1811
1830
  const hasTarget = !perBarTarget && target !== void 0 && Number.isFinite(target);
@@ -1826,7 +1845,7 @@ function buildCartesianOption(cfg) {
1826
1845
  const hideTargetLabel = kind === "line" && targetLabelPosition === "legend" || !shareHighlight && plotWidth !== void 0 && plotWidth < TARGET_LABEL_NARROW_WIDTH && highlightPoints.some((p) => p.index === labels.length - 1 && (nearTargetLabel(p, "under") || nearTargetLabel(p, "over")));
1827
1846
  const below = (p) => !shareHighlight && !hideTargetLabel && nearTargetLabel(p, "under");
1828
1847
  const targetLabelBelow = !shareHighlight && !hideTargetLabel && highlightPoints.some((p) => nearTargetLabel(p, "over"));
1829
- const barTargets = perBarTarget && targetKey && !percent && (stacked || categories.length === 1) ? barTargetValues(data, targetKey) : null;
1848
+ const barTargets = perBarTarget && targetKey && !percent2 && (stacked || categories.length === 1) ? barTargetValues(data, targetKey) : null;
1830
1849
  const barTargetName = targetLabel ?? text.target;
1831
1850
  const barTargetColor = labelColor ?? guideColor;
1832
1851
  const markPointFor = (c, color) => c === highlightSeries && highlightPoints.length > 0 ? {
@@ -1867,14 +1886,16 @@ function buildCartesianOption(cfg) {
1867
1886
  const seriesData = pad(values[c]);
1868
1887
  if (kind === "bar") {
1869
1888
  const rounded = horizontalBars ? [0, 4, 4, 0] : [4, 4, 0, 0];
1870
- const radius = stacked && c !== categories.length - 1 ? 0 : rounded;
1889
+ const radius = stacked ? BAR_RADIUS : rounded;
1890
+ const gapStyle = stacked && labelBackground ? { borderColor: labelBackground, borderWidth: STACK_GAP } : {};
1871
1891
  return {
1872
1892
  id: SERIES_ID_PREFIX + category,
1873
1893
  type: "bar",
1874
1894
  name: category,
1875
1895
  stack: stacked ? "total" : void 0,
1876
1896
  barCategoryGap,
1877
- itemStyle: { color, borderRadius: radius },
1897
+ barMaxWidth: BAR_MAX_WIDTH,
1898
+ itemStyle: { color, borderRadius: radius, ...gapStyle },
1878
1899
  markPoint: markPointFor(c, solid),
1879
1900
  markLine: void 0,
1880
1901
  data: seriesData.map((value, i) => {
@@ -1951,7 +1972,7 @@ function buildCartesianOption(cfg) {
1951
1972
  symbolSize: 6,
1952
1973
  showSymbol: showDots || data.length === 1,
1953
1974
  // 100% stacked: the top band's upper edge is the 100% line, not a value — drawn, it reads as「this band = 100%」.
1954
- lineStyle: percent && c === categories.length - 1 ? { width: 0, color, opacity: 0 } : { width: 2, color, opacity: dimSeries ? DIMMED : 1 },
1975
+ lineStyle: percent2 && c === categories.length - 1 ? { width: 0, color, opacity: 0 } : { width: 2, color, opacity: dimSeries ? DIMMED : 1 },
1955
1976
  itemStyle: { color, opacity: dimSeries ? DIMMED : 1 },
1956
1977
  // Hovering a series fades the others (not for a stack, where the bands read together).
1957
1978
  emphasis: stacked ? void 0 : { focus: "series" },
@@ -1980,7 +2001,7 @@ function buildCartesianOption(cfg) {
1980
2001
  symbol: "circle",
1981
2002
  symbolSize: 6,
1982
2003
  showSymbol: showDots || data.length === 1,
1983
- lineStyle: percent && c === categories.length - 1 ? { width: 0, color, opacity: 0 } : { width: 2, type: "dashed", color, opacity },
2004
+ lineStyle: percent2 && c === categories.length - 1 ? { width: 0, color, opacity: 0 } : { width: 2, type: "dashed", color, opacity },
1984
2005
  itemStyle: { color, opacity },
1985
2006
  areaStyle: solid.areaStyle ? { ...solid.areaStyle, opacity: (solid.areaStyle.opacity ?? 1) * PROVISIONAL_OPACITY } : void 0,
1986
2007
  markPoint: void 0,
@@ -2006,11 +2027,11 @@ function buildCartesianOption(cfg) {
2006
2027
  data: []
2007
2028
  });
2008
2029
  }
2009
- const forecastLabel = forecast?.label ?? text.forecast;
2030
+ const forecastLabel = forecast2?.label ?? text.forecast;
2010
2031
  const forecastBandAlpha = isDarkSurface(labelBackground) ? FORECAST_BAND_ALPHA_DARK : FORECAST_BAND_ALPHA_LIGHT;
2011
2032
  const forecastValues = [];
2012
- if (forecast) {
2013
- const category = forecast.category !== void 0 && categories.includes(forecast.category) && forecast.category !== comparison?.category ? forecast.category : categories[mainIdx[0] ?? 0];
2033
+ if (forecast2) {
2034
+ const category = forecast2.category !== void 0 && categories.includes(forecast2.category) && forecast2.category !== comparison?.category ? forecast2.category : categories[mainIdx[0] ?? 0];
2014
2035
  const c = categories.indexOf(category);
2015
2036
  const color = palette[c] || void 0;
2016
2037
  const actual = values[c];
@@ -2030,7 +2051,7 @@ function buildCartesianOption(cfg) {
2030
2051
  range[start] = 0;
2031
2052
  }
2032
2053
  let hasBand = false;
2033
- forecast.data.forEach((row, k) => {
2054
+ forecast2.data.forEach((row, k) => {
2034
2055
  const at = forecastAt[k];
2035
2056
  line[at] = cellNumber(row.value);
2036
2057
  const lo = cellNumber(row.lower);
@@ -2096,7 +2117,7 @@ function buildCartesianOption(cfg) {
2096
2117
  const totals = labels.map(
2097
2118
  (_, i) => stacked ? values.reduce((sum, v) => sum + Math.max(0, v[i] ?? 0), 0) : Math.max(0, ...values.map((v) => v[i] ?? 0))
2098
2119
  );
2099
- const top = percent ? 100 : maxValue ?? Math.max(target, ...totals);
2120
+ const top = percent2 ? 100 : maxValue ?? Math.max(target, ...totals);
2100
2121
  if (!(top > 0)) return "center";
2101
2122
  const ratio = Math.min(1, Math.max(0, target / top));
2102
2123
  const half = (estimateTextWidth(targetText) + 8) / 2;
@@ -2129,7 +2150,15 @@ function buildCartesianOption(cfg) {
2129
2150
  }
2130
2151
  });
2131
2152
  }
2132
- if (forecast) {
2153
+ if (diverging) {
2154
+ markLines.push({
2155
+ name: "zero",
2156
+ [horizontalBars ? "xAxis" : "yAxis"]: 0,
2157
+ lineStyle: { type: "solid", width: 1, color: guideColor },
2158
+ label: { show: false }
2159
+ });
2160
+ }
2161
+ if (forecast2) {
2133
2162
  const forecastSpan = plotWidth && labels.length > 1 ? Math.max(1, plotWidth - (showYAxis ? yAxisWidth ?? Y_AXIS_ESTIMATE : 0) - LINE_RIGHT_MARGIN) / (labels.length - 1) * (labels.length - 1 - lastActual) + LINE_RIGHT_MARGIN : null;
2134
2163
  markLines.push({
2135
2164
  name: "forecast-boundary",
@@ -2260,6 +2289,8 @@ function buildCartesianOption(cfg) {
2260
2289
  show: horizontalBars ? showYAxis : showXAxis,
2261
2290
  boundaryGap: kind === "bar",
2262
2291
  inverse: horizontalBars,
2292
+ // Diverging: the axis stays at the plot's edge (not at 0, where labels would hit the negative bars); the zero line is a mark line.
2293
+ axisLine: diverging ? { show: false, onZero: false } : void 0,
2263
2294
  axisTick: { show: false },
2264
2295
  axisLabel: {
2265
2296
  hideOverlap: true,
@@ -2280,16 +2311,16 @@ function buildCartesianOption(cfg) {
2280
2311
  const fit = kind === "line" && (fitToData ?? autoMinValue);
2281
2312
  const valueSplitNumber = (() => {
2282
2313
  if (!horizontalBars || !plotWidth) return void 0;
2283
- const top = percent ? 100 : maxValue ?? Math.max(0, ...(stacked ? stackTotalsOf(values) : values.flat()).map((v) => v ?? 0));
2314
+ const top = percent2 ? 100 : maxValue ?? Math.max(0, ...(stacked ? stackTotalsOf(values) : values.flat()).map((v) => v ?? 0));
2284
2315
  const categoryColumn = showYAxis ? (yAxisWidth ?? Math.min(160, Math.max(0, ...labels.map(estimateTextWidth)))) + 8 : 0;
2285
2316
  const plot = plotWidth - categoryColumn - (highlightPoints.length > 0 ? 64 : 16) - 4;
2286
- const text2 = percent ? (v) => `${v}%` : (v) => axisFormatter(v);
2317
+ const text2 = percent2 ? (v) => `${v}%` : (v) => axisFormatter(v);
2287
2318
  return valueAxisSplitNumber(plot, [text2(0), text2(top)]);
2288
2319
  })();
2289
2320
  const limits = valueAxisLimits({ minValue, maxValue }, kind !== "line");
2290
2321
  const splitNumber = horizontalBars ? valueSplitNumber : valueAxisSplitCount(plotHeight);
2291
2322
  const tickFormatter = (() => {
2292
- if (!cfg.axisTicks || percent) return axisFormatter;
2323
+ if (!cfg.axisTicks || percent2) return axisFormatter;
2293
2324
  const extentValues = [];
2294
2325
  const addAll = (list) => {
2295
2326
  for (const v of list) if (v !== null && Number.isFinite(v)) extentValues.push(v);
@@ -2297,7 +2328,7 @@ function buildCartesianOption(cfg) {
2297
2328
  if (stackTotals) addAll(stackTotals);
2298
2329
  else values.forEach(addAll);
2299
2330
  addAll(forecastValues);
2300
- if (forecast) addAll(forecast.data.flatMap((row) => [cellNumber(row.lower), cellNumber(row.upper)]));
2331
+ if (forecast2) addAll(forecast2.data.flatMap((row) => [cellNumber(row.lower), cellNumber(row.upper)]));
2301
2332
  if (barTargets) addAll(barTargets);
2302
2333
  if (hasTarget && target !== void 0) extentValues.push(target);
2303
2334
  if (maxValue !== void 0 && Number.isFinite(maxValue)) extentValues.push(maxValue);
@@ -2314,8 +2345,8 @@ function buildCartesianOption(cfg) {
2314
2345
  show: horizontalBars ? showXAxis : showYAxis,
2315
2346
  // Bars and areas always start at 0 (a positive `minValue` can't lift them, a negative one makes room below);
2316
2347
  // lines do too unless asked to fit the data. A limit never cuts the data: the axis widens to it.
2317
- min: percent ? 0 : kind === "line" ? minValue === void 0 ? fit ? void 0 : zeroBasedMin : limits.min : limits.min,
2318
- max: percent ? 100 : limits.max,
2348
+ min: percent2 ? 0 : kind === "line" ? minValue === void 0 ? fit ? void 0 : zeroBasedMin : limits.min : limits.min,
2349
+ max: percent2 ? 100 : limits.max,
2319
2350
  scale: fit && minValue == null,
2320
2351
  splitNumber,
2321
2352
  minInterval: allowDecimals ? void 0 : 1,
@@ -2323,7 +2354,7 @@ function buildCartesianOption(cfg) {
2323
2354
  axisTick: { show: false },
2324
2355
  splitLine: { show: showGridLines },
2325
2356
  axisLabel: {
2326
- formatter: percent ? (v) => `${v}%` : (v) => tickFormatter(v),
2357
+ formatter: percent2 ? (v) => `${v}%` : (v) => tickFormatter(v),
2327
2358
  // Fitted to the data: the lowest tick would print over the first x label at the bottom left. The gridline stays.
2328
2359
  showMinLabel: fit && minValue == null && !horizontalBars ? false : void 0,
2329
2360
  // Horizontal: ticks that would still touch on a narrow plot are dropped.
@@ -2337,7 +2368,7 @@ function buildCartesianOption(cfg) {
2337
2368
  };
2338
2369
  const legendItems = [
2339
2370
  ...categories,
2340
- ...forecast ? [forecastLabel] : [],
2371
+ ...forecast2 ? [forecastLabel] : [],
2341
2372
  ...provStart !== -1 ? [provisionalLabel] : [],
2342
2373
  ...barTargets ? [barTargetName] : [],
2343
2374
  ...hasTarget ? [targetText] : []
@@ -2348,7 +2379,7 @@ function buildCartesianOption(cfg) {
2348
2379
  show: showLegend,
2349
2380
  top: 0,
2350
2381
  // Narrow: left-aligned and wrapping above the plot, so nothing is cut off at the right edge.
2351
- ...plotWidth !== void 0 && plotWidth < NARROW_PLOT_WIDTH ? { left: 0, right: 0 } : { right: reserveActions ? ACTIONS_RESERVE : 0 },
2382
+ ...plotWidth !== void 0 && plotWidth < NARROW_PLOT_WIDTH ? { left: 0, right: 0 } : { left: 0, right: reserveActions ? ACTIONS_RESERVE : 0 },
2352
2383
  // Plain wraps onto a second line at narrow widths instead of hiding items behind arrows.
2353
2384
  type: "plain",
2354
2385
  icon: "circle",
@@ -2358,7 +2389,7 @@ function buildCartesianOption(cfg) {
2358
2389
  // The target marker is a tick, not a dot.
2359
2390
  // The target is a row of dots and the forecast a row of dashes — their lines' patterns — and a per-bar target a tick.
2360
2391
  data: legendItems.map(
2361
- (name) => barTargets && name === barTargetName ? { name, icon: TARGET_LEGEND_ICON } : forecast && name === forecastLabel ? { name, icon: FORECAST_DASHED_ICON } : hasTarget && name === targetText ? { name, icon: TARGET_DOTTED_ICON } : name
2392
+ (name) => barTargets && name === barTargetName ? { name, icon: TARGET_LEGEND_ICON } : forecast2 && name === forecastLabel ? { name, icon: FORECAST_DASHED_ICON } : hasTarget && name === targetText ? { name, icon: TARGET_DOTTED_ICON } : name
2362
2393
  ),
2363
2394
  formatter: nameLabels ? (name) => labelOf(name, nameLabels) : void 0
2364
2395
  },
@@ -2366,7 +2397,7 @@ function buildCartesianOption(cfg) {
2366
2397
  top: (showLegend ? 12 + LEGEND_LINE_HEIGHT * legendLines(legendItems, plotWidth, reserveActions ? ACTIONS_RESERVE : 0) : 12) + // The flat target label of a horizontal bar chart sits above the plot.
2367
2398
  (horizontalBars && hasTarget ? 18 : 0) + // The label of a band over bars sits above the plot's top edge.
2368
2399
  (kind === "bar" && notes.some((n) => n.to !== void 0) ? BAND_LABEL_ROOM : 0) + // The boundary label of a forecast sits above the plot's top edge too.
2369
- (forecast ? FORECAST_LABEL_ROOM : 0),
2400
+ (forecast2 ? FORECAST_LABEL_ROOM : 0),
2370
2401
  // Lines start and end on the outermost ticks, so the last x label (and an edge
2371
2402
  // highlight) hangs past the plot; a horizontal bar's highlight sits past its end.
2372
2403
  right: horizontalBars ? highlightPoints.length > 0 ? 64 : 16 : kind === "bar" ? 16 : LINE_RIGHT_MARGIN,
@@ -2397,7 +2428,7 @@ function buildCartesianOption(cfg) {
2397
2428
  )
2398
2429
  };
2399
2430
  };
2400
- const shares = percent ? cartesianSeriesValues(i <= lastActual ? [row] : [], categories, "percent") : null;
2431
+ const shares = percent2 ? cartesianSeriesValues(i <= lastActual ? [row] : [], categories, "percent") : null;
2401
2432
  const legendRank = (p) => {
2402
2433
  const at = categories.indexOf(p.seriesName ?? "");
2403
2434
  return at === -1 ? categories.length : at;
@@ -2642,12 +2673,12 @@ function AreaChartImpl({
2642
2673
  provisionalFrom,
2643
2674
  provisionalLabel,
2644
2675
  isLoading,
2645
- loading,
2676
+ loading: loading2,
2646
2677
  emptyState,
2647
2678
  height,
2648
2679
  chartOptions,
2649
2680
  areaOptions,
2650
- forecast,
2681
+ forecast: forecast2,
2651
2682
  comparison,
2652
2683
  highlight,
2653
2684
  target,
@@ -2729,7 +2760,7 @@ function AreaChartImpl({
2729
2760
  target,
2730
2761
  targetLabel,
2731
2762
  annotations,
2732
- forecast,
2763
+ forecast: forecast2,
2733
2764
  comparison: comparisonConfig,
2734
2765
  comparisonFormat: format,
2735
2766
  provisionalFrom,
@@ -2775,7 +2806,7 @@ function AreaChartImpl({
2775
2806
  target,
2776
2807
  targetLabel,
2777
2808
  annotations,
2778
- forecast,
2809
+ forecast2,
2779
2810
  comparisonConfig,
2780
2811
  format,
2781
2812
  height,
@@ -2803,7 +2834,7 @@ function AreaChartImpl({
2803
2834
  ref: frameRef,
2804
2835
  title,
2805
2836
  summary: resolvedSummary,
2806
- isLoading: isLoading ?? loading ?? false,
2837
+ isLoading: isLoading ?? loading2 ?? false,
2807
2838
  isEmpty: data.length === 0,
2808
2839
  emptyState,
2809
2840
  height,
@@ -2837,7 +2868,7 @@ function BarChartImpl({
2837
2868
  provisionalFrom,
2838
2869
  provisionalLabel,
2839
2870
  isLoading,
2840
- loading,
2871
+ loading: loading2,
2841
2872
  emptyState,
2842
2873
  height,
2843
2874
  chartOptions,
@@ -2993,7 +3024,7 @@ function BarChartImpl({
2993
3024
  ref: frameRef,
2994
3025
  title,
2995
3026
  summary: resolvedSummary,
2996
- isLoading: isLoading ?? loading ?? false,
3027
+ isLoading: isLoading ?? loading2 ?? false,
2997
3028
  isEmpty: data.length === 0,
2998
3029
  emptyState,
2999
3030
  height,
@@ -3027,12 +3058,12 @@ function LineChartImpl({
3027
3058
  provisionalFrom,
3028
3059
  provisionalLabel,
3029
3060
  isLoading,
3030
- loading,
3061
+ loading: loading2,
3031
3062
  emptyState,
3032
3063
  height,
3033
3064
  chartOptions,
3034
3065
  lineOptions,
3035
- forecast,
3066
+ forecast: forecast2,
3036
3067
  comparison,
3037
3068
  highlight,
3038
3069
  target,
@@ -3115,7 +3146,7 @@ function LineChartImpl({
3115
3146
  targetLabel,
3116
3147
  targetLabelPosition,
3117
3148
  annotations,
3118
- forecast,
3149
+ forecast: forecast2,
3119
3150
  comparison: comparisonConfig,
3120
3151
  comparisonFormat: format,
3121
3152
  provisionalFrom,
@@ -3162,7 +3193,7 @@ function LineChartImpl({
3162
3193
  targetLabel,
3163
3194
  targetLabelPosition,
3164
3195
  annotations,
3165
- forecast,
3196
+ forecast2,
3166
3197
  comparisonConfig,
3167
3198
  format,
3168
3199
  height,
@@ -3190,7 +3221,7 @@ function LineChartImpl({
3190
3221
  ref: frameRef,
3191
3222
  title,
3192
3223
  summary: resolvedSummary,
3193
- isLoading: isLoading ?? loading ?? false,
3224
+ isLoading: isLoading ?? loading2 ?? false,
3194
3225
  isEmpty: data.length === 0,
3195
3226
  emptyState,
3196
3227
  height,
@@ -3234,7 +3265,7 @@ function DonutChartImpl({
3234
3265
  actions,
3235
3266
  fileName,
3236
3267
  isLoading,
3237
- loading,
3268
+ loading: loading2,
3238
3269
  emptyState,
3239
3270
  height,
3240
3271
  chartOptions,
@@ -3328,7 +3359,7 @@ function DonutChartImpl({
3328
3359
  ref: frameRef,
3329
3360
  title,
3330
3361
  summary: resolvedSummary,
3331
- isLoading: isLoading ?? loading ?? false,
3362
+ isLoading: isLoading ?? loading2 ?? false,
3332
3363
  isEmpty: data.length === 0,
3333
3364
  emptyState,
3334
3365
  height,
@@ -4378,8 +4409,8 @@ function buildScatterOption(config) {
4378
4409
  legend: {
4379
4410
  show: showLegend,
4380
4411
  ...legendBelow ? { bottom: 0 } : { top: 0 },
4381
- // With a heading the legend reads from the left, after it; otherwise it sits at the right edge.
4382
- ...headingWidth > 0 ? { left: headingWidth } : legendBelow ? { left: 0 } : { right: config.reserveActions ? 72 : 0 },
4412
+ // With a heading the legend reads from the left, after it; otherwise it sits at the left edge (as in every chart).
4413
+ ...headingWidth > 0 ? { left: headingWidth } : { left: 0, ...legendBelow ? {} : { right: config.reserveActions ? 72 : 0 } },
4383
4414
  type: "plain",
4384
4415
  icon: "circle",
4385
4416
  itemWidth: 8,
@@ -4623,7 +4654,7 @@ var TEXT11 = {
4623
4654
  ja: { legend: "\u51E1\u4F8B" },
4624
4655
  en: { legend: "Legend" }
4625
4656
  };
4626
- var Legend = forwardRef(function Legend2({ categories, colors, labels, layout = "horizontal", ariaLabel, className }, ref) {
4657
+ var Legend = forwardRef(function Legend2({ categories, colors, labels, layout = "horizontal", hidden = [], onHiddenChange, ariaLabel, className }, ref) {
4627
4658
  const t = TEXT11[uiLanguage(useLocale())];
4628
4659
  if (categories.length === 0) return null;
4629
4660
  const swatches = legendColors(categories, colors);
@@ -4635,10 +4666,23 @@ var Legend = forwardRef(function Legend2({ categories, colors, labels, layout =
4635
4666
  "data-layout": layout,
4636
4667
  "aria-label": ariaLabel ?? t.legend,
4637
4668
  className: cn("m-0 flex list-none gap-x-4 gap-y-1 p-0 text-xs", layout === "vertical" ? "flex-col" : "flex-row flex-wrap", className),
4638
- children: categories.map((category, i) => /* @__PURE__ */ jsxs("li", { "data-slot": "legend-item", className: "flex min-w-0 items-center gap-1.5", children: [
4639
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "size-2.5 shrink-0 rounded-[2px]", style: { background: swatches[i] } }),
4640
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-muted-foreground", children: labelOf(category, labels) })
4641
- ] }, category))
4669
+ children: categories.map((category, i) => {
4670
+ const off = hidden.includes(category);
4671
+ const chip = /* @__PURE__ */ jsxs(Fragment, { children: [
4672
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn("size-2.5 shrink-0 rounded-[2px]", off && "opacity-30"), style: { background: swatches[i] } }),
4673
+ /* @__PURE__ */ jsx("span", { className: cn("min-w-0 truncate text-muted-foreground", off && "line-through opacity-60"), children: labelOf(category, labels) })
4674
+ ] });
4675
+ return /* @__PURE__ */ jsx("li", { "data-slot": "legend-item", "data-hidden": off || void 0, className: "flex min-w-0 items-center gap-1.5", children: onHiddenChange ? /* @__PURE__ */ jsx(
4676
+ "button",
4677
+ {
4678
+ type: "button",
4679
+ "aria-pressed": !off,
4680
+ onClick: () => onHiddenChange(off ? hidden.filter((c) => c !== category) : [...hidden, category]),
4681
+ className: "flex min-w-0 cursor-pointer items-center gap-1.5 rounded-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
4682
+ children: chip
4683
+ }
4684
+ ) : chip }, category);
4685
+ })
4642
4686
  }
4643
4687
  );
4644
4688
  });
@@ -5083,10 +5127,10 @@ var HEATMAP_CELL_PADDING = 4;
5083
5127
  var HEATMAP_BUCKET_MID = 20;
5084
5128
  function heatmapCellFontSize(plotWidth, columns, rowNames, texts) {
5085
5129
  const widest = (items) => items.reduce((m, t) => Math.max(m, estimateTextWidth2(t)), 0);
5086
- const cell = (plotWidth + HEATMAP_BUCKET_MID - HEATMAP_GRID_X - (widest(rowNames) + 8)) / Math.max(1, columns);
5130
+ const cell2 = (plotWidth + HEATMAP_BUCKET_MID - HEATMAP_GRID_X - (widest(rowNames) + 8)) / Math.max(1, columns);
5087
5131
  const text = widest(texts);
5088
- if (text + HEATMAP_CELL_PADDING <= cell) return 12;
5089
- return text * 10 / 12 + HEATMAP_CELL_PADDING <= cell ? 10 : null;
5132
+ if (text + HEATMAP_CELL_PADDING <= cell2) return 12;
5133
+ return text * 10 / 12 + HEATMAP_CELL_PADDING <= cell2 ? 10 : null;
5090
5134
  }
5091
5135
  var TEXT13 = {
5092
5136
  ja: {
@@ -5114,24 +5158,24 @@ function buildHeatmapOption(config) {
5114
5158
  const base = config.color || "#6366f1";
5115
5159
  const range = [mixIntoSurface(base, config.surfaceColor, HEATMAP_FLOOR_SHARE), base];
5116
5160
  const gapColor = "rgba(0, 0, 0, 0)";
5117
- const flagged = (cell) => cell.flagged ? { borderColor: config.flagColor, borderWidth: 3, borderType: "solid" } : { borderColor: "transparent", borderWidth: 1 };
5118
- const tooltip = (cell) => {
5119
- const head = `${escapeHtml2(label2(model.ys[cell.yi]))} ${escapeHtml2(label2(model.xs[cell.xi]))}`;
5120
- const main = cell.value === null ? EMPTY_VALUE : escapeHtml2(valueFormatter(cell.value));
5161
+ const flagged = (cell2) => cell2.flagged ? { borderColor: config.flagColor, borderWidth: 3, borderType: "solid" } : { borderColor: "transparent", borderWidth: 1 };
5162
+ const tooltip = (cell2) => {
5163
+ const head = `${escapeHtml2(label2(model.ys[cell2.yi]))} ${escapeHtml2(label2(model.xs[cell2.xi]))}`;
5164
+ const main = cell2.value === null ? EMPTY_VALUE : escapeHtml2(valueFormatter(cell2.value));
5121
5165
  const extras = (config.details ?? []).map((key) => {
5122
- const n = cell.row ? toChartNumber(cell.row[key]) : null;
5166
+ const n = cell2.row ? toChartNumber(cell2.row[key]) : null;
5123
5167
  return `${escapeHtml2(label2(key))} ${n === null ? EMPTY_VALUE : escapeHtml2(valueFormatter(n))}`;
5124
5168
  }).join(t.join);
5125
- const flag = cell.flagged ? `${t.join}${t.flagged}` : "";
5169
+ const flag = cell2.flagged ? `${t.join}${t.flagged}` : "";
5126
5170
  return `${head} ${main}${extras ? `${t.join}${extras}` : ""}${flag}`;
5127
5171
  };
5128
5172
  const cellFont = config.plotWidth ? heatmapCellFontSize(
5129
5173
  config.plotWidth,
5130
5174
  model.xs.length,
5131
5175
  model.ys.map(label2),
5132
- model.cells.flatMap((cell) => cell.value === null ? [] : [valueFormatter(cell.value)])
5176
+ model.cells.flatMap((cell2) => cell2.value === null ? [] : [valueFormatter(cell2.value)])
5133
5177
  ) : 12;
5134
- const point = (cell) => ({ value: [cell.xi, cell.yi, cell.value], cell });
5178
+ const point = (cell2) => ({ value: [cell2.xi, cell2.yi, cell2.value], cell: cell2 });
5135
5179
  const option = {
5136
5180
  animation: false,
5137
5181
  tooltip: config.showTooltip ? { trigger: "item", formatter: (p) => tooltip(p.data.cell) } : { show: false },
@@ -5179,11 +5223,11 @@ function buildHeatmapOption(config) {
5179
5223
  {
5180
5224
  id: "values",
5181
5225
  type: "heatmap",
5182
- data: present.map((cell) => ({
5183
- ...point(cell),
5184
- itemStyle: flagged(cell),
5226
+ data: present.map((cell2) => ({
5227
+ ...point(cell2),
5228
+ itemStyle: flagged(cell2),
5185
5229
  label: {
5186
- color: heatmapRatio(cell.value, model.min, model.max) >= HEATMAP_LIGHT_TEXT_FROM ? config.darkCellText : config.lightCellText
5230
+ color: heatmapRatio(cell2.value, model.min, model.max) >= HEATMAP_LIGHT_TEXT_FROM ? config.darkCellText : config.lightCellText
5187
5231
  }
5188
5232
  })),
5189
5233
  label: {
@@ -5198,7 +5242,7 @@ function buildHeatmapOption(config) {
5198
5242
  {
5199
5243
  id: "gaps",
5200
5244
  type: "heatmap",
5201
- data: missing.map((cell) => ({ value: [cell.xi, cell.yi, 0], cell, itemStyle: flagged(cell) })),
5245
+ data: missing.map((cell2) => ({ value: [cell2.xi, cell2.yi, 0], cell: cell2, itemStyle: flagged(cell2) })),
5202
5246
  label: { show: false },
5203
5247
  itemStyle: {
5204
5248
  color: gapColor,
@@ -5234,7 +5278,7 @@ function heatmapDataTable(model, names, formatter, labels, locale) {
5234
5278
  }
5235
5279
  function buildHeatmapSummary(model, formatter, title, labels, locale) {
5236
5280
  let best;
5237
- for (const cell of model.cells) if (cell.value !== null && (!best || cell.value > best.value)) best = cell;
5281
+ for (const cell2 of model.cells) if (cell2.value !== null && (!best || cell2.value > best.value)) best = cell2;
5238
5282
  if (!best) return void 0;
5239
5283
  const label2 = (key) => labels?.[key] ?? key;
5240
5284
  const head = title ? `${title}: ` : "";
@@ -5348,122 +5392,3180 @@ function HeatmapLegend({
5348
5392
  var Heatmap = forwardRef(HeatmapImpl);
5349
5393
  Heatmap.displayName = "Heatmap";
5350
5394
  var TEXT14 = {
5395
+ ja: { item: "\u9805\u76EE", chart: (keys) => `${keys.join("\u30FB")}\u306E\u63A8\u79FB(\u4E0A\u6BB5 \u68D2\u3001\u4E0B\u6BB5 \u7DDA)` },
5396
+ en: { item: "Item", chart: (keys) => `Trend of ${keys.join(", ")} (bars on top, line below)` }
5397
+ };
5398
+ var NO_ROWS12 = [];
5399
+ var NO_KEYS = [];
5400
+ var PANEL_GAP = 20;
5401
+ var LEGEND_LINE = 24;
5402
+ var DEFAULT_HEIGHT = 400;
5403
+ var DEFAULT_Y_AXIS_WIDTH = 64;
5404
+ function pairedPalette(barKeys, lineKeys, barColors, lineColors) {
5405
+ const bars = seriesColorsFor(barKeys, barColors, CHART_OTHER_COLOR);
5406
+ const line = lineColors ? seriesColorsFor(lineKeys, lineColors, CHART_OTHER_COLOR) : lineKeys.map((_, i) => DEFAULT_CHART_COLORS[(barKeys.length + i) % DEFAULT_CHART_COLORS.length]);
5407
+ return { bars, line };
5408
+ }
5409
+ function buildPairedOption(cfg) {
5410
+ const { data, index, labels, barKeys, lineKeys, height, heightRatio, yAxisWidth, showLegend, showTooltip } = cfg;
5411
+ const legendNames = [...barKeys, ...lineKeys];
5412
+ const legendWidth = legendNames.reduce((sum, name) => sum + [...labels?.[name] ?? name].length * 12 + 29, 0);
5413
+ const budget = Math.max(80, Math.min(cfg.plotWidth ?? 720, 720) - (cfg.reserveActions ? 80 : 0));
5414
+ const legendRows = showLegend ? Math.min(3, Math.max(1, Math.ceil(legendWidth / budget))) : 0;
5415
+ const top = (showLegend ? 12 + LEGEND_LINE * legendRows : 12) + 4;
5416
+ const room = Math.max(120, height - top - PANEL_GAP);
5417
+ const [a, b] = heightRatio;
5418
+ const upper = Math.round(room * a / (a + b));
5419
+ const common = {
5420
+ data,
5421
+ index,
5422
+ labels,
5423
+ showLegend: false,
5424
+ showTooltip: false,
5425
+ highlight: "none",
5426
+ yAxisWidth,
5427
+ reducedMotion: false,
5428
+ locale: cfg.locale
5429
+ };
5430
+ const barOpt = buildCartesianOption({
5431
+ ...common,
5432
+ kind: "bar",
5433
+ categories: barKeys,
5434
+ palette: cfg.barPalette,
5435
+ valueFormatter: cfg.barFormatter,
5436
+ axisFormatter: cfg.barAxis,
5437
+ axisTicks: cfg.barTicks,
5438
+ type: cfg.type,
5439
+ showXAxis: false,
5440
+ yAxisLabel: cfg.barAxisLabel,
5441
+ maxValue: cfg.barMax,
5442
+ plotWidth: cfg.plotWidth,
5443
+ plotHeight: upper
5444
+ });
5445
+ const lineOpt = buildCartesianOption({
5446
+ ...common,
5447
+ kind: "line",
5448
+ categories: lineKeys,
5449
+ palette: cfg.linePalette,
5450
+ valueFormatter: cfg.lineFormatter,
5451
+ axisFormatter: cfg.lineAxis,
5452
+ axisTicks: cfg.lineTicks,
5453
+ fitToData: cfg.fitToData,
5454
+ showXAxis: true,
5455
+ yAxisLabel: cfg.lineAxisLabel,
5456
+ minValue: cfg.lineMin,
5457
+ maxValue: cfg.lineMax,
5458
+ guideColor: cfg.guideColor,
5459
+ plotWidth: cfg.plotWidth,
5460
+ plotHeight: room - upper
5461
+ });
5462
+ const times = timeTooltipLabels(data.map((row) => row[index]), cfg.locale);
5463
+ const titles = labelRows(data, index, labels).map((row, i) => String(times?.[i] ?? row[index] ?? ""));
5464
+ const cell2 = (row, key) => {
5465
+ const v = row[key];
5466
+ const n = typeof v === "number" ? v : typeof v === "string" && v.trim() !== "" ? Number(v) : NaN;
5467
+ return Number.isFinite(n) ? n : null;
5468
+ };
5469
+ const rowsOf = (row, keys, palette) => keys.map((key, i) => ({ name: labelOf(key, labels), color: palette[i] || "currentColor", value: cell2(row, key) }));
5470
+ const emptyTitle = tooltipHtml("", [], String, [], cfg.locale);
5471
+ const tooltipFormatter = (params) => {
5472
+ const list = Array.isArray(params) ? params : [params];
5473
+ const i = list[0]?.dataIndex ?? 0;
5474
+ const row = data[i] ?? {};
5475
+ const upperHtml = tooltipHtml(titles[i] ?? "", rowsOf(row, barKeys, cfg.barPalette), cfg.barFormatter, [], cfg.locale);
5476
+ const lowerHtml = tooltipHtml("", rowsOf(row, lineKeys, cfg.linePalette), cfg.lineFormatter, [], cfg.locale).slice(emptyTitle.length);
5477
+ return `${upperHtml}${lowerHtml}`;
5478
+ };
5479
+ const lineX = { ...lineOpt.xAxis, id: "x-line", gridIndex: 1, boundaryGap: true };
5480
+ const barX = { ...barOpt.xAxis, id: "x-bars", gridIndex: 0, show: true, axisLine: { show: false }, axisLabel: { show: false } };
5481
+ const gridLeft = barOpt.grid.left ?? 4;
5482
+ const right = 16;
5483
+ const option = {
5484
+ ...barOpt,
5485
+ legend: { ...barOpt.legend, show: showLegend, data: legendNames, left: 0, right: cfg.reserveActions ? 80 : 0 },
5486
+ grid: [
5487
+ { top, height: upper, left: gridLeft, right, containLabel: true },
5488
+ { top: top + upper + PANEL_GAP, bottom: 4, left: gridLeft, right, containLabel: true }
5489
+ ],
5490
+ xAxis: [barX, lineX],
5491
+ yAxis: [
5492
+ { ...barOpt.yAxis, id: "y-bars", gridIndex: 0 },
5493
+ { ...lineOpt.yAxis, id: "y-line", gridIndex: 1 }
5494
+ ],
5495
+ series: [
5496
+ ...barOpt.series.map((s) => ({ ...s, xAxisIndex: 0, yAxisIndex: 0 })),
5497
+ ...lineOpt.series.map((s) => ({ ...s, xAxisIndex: 1, yAxisIndex: 1 }))
5498
+ ],
5499
+ tooltip: {
5500
+ ...barOpt.tooltip,
5501
+ show: showTooltip,
5502
+ trigger: "axis",
5503
+ axisPointer: { type: "line", link: [{ xAxisIndex: "all" }] },
5504
+ formatter: tooltipFormatter
5505
+ },
5506
+ axisPointer: { link: [{ xAxisIndex: "all" }] }
5507
+ };
5508
+ return mergeChartOptions(option, cfg.chartOptions);
5509
+ }
5510
+ function PairedChartImpl({
5511
+ data = NO_ROWS12,
5512
+ index,
5513
+ labels,
5514
+ bars,
5515
+ line,
5516
+ title,
5517
+ summary,
5518
+ actions,
5519
+ fileName,
5520
+ isLoading,
5521
+ emptyState,
5522
+ height = DEFAULT_HEIGHT,
5523
+ heightRatio = [3, 2],
5524
+ yAxisWidth = DEFAULT_Y_AXIS_WIDTH,
5525
+ showLegend = true,
5526
+ showTooltip = true,
5527
+ chartOptions,
5528
+ ariaLabel,
5529
+ className,
5530
+ ...rest
5531
+ }, ref) {
5532
+ const { ref: frameRef, width: plotWidth, tokens } = useChartSurface(ref);
5533
+ const barKeys = bars?.categories ?? NO_KEYS;
5534
+ const lineKeys = line?.categories ?? NO_KEYS;
5535
+ const locale = useLocale();
5536
+ const t = TEXT14[uiLanguage(locale)];
5537
+ const barFormatter = useMemo(() => resolveValueFormatter(bars?.valueFormatter, bars?.format, locale), [bars?.valueFormatter, bars?.format, locale]);
5538
+ const lineFormatter = useMemo(() => resolveValueFormatter(line?.valueFormatter, line?.format, locale), [line?.valueFormatter, line?.format, locale]);
5539
+ const barAxis = useMemo(() => resolveAxisFormatter(bars?.valueFormatter, bars?.format, locale), [bars?.valueFormatter, bars?.format, locale]);
5540
+ const lineAxis = useMemo(() => resolveAxisFormatter(line?.valueFormatter, line?.format, locale), [line?.valueFormatter, line?.format, locale]);
5541
+ const barTicks = useMemo(() => resolveAxisTickFormatter(bars?.valueFormatter, bars?.format, locale), [bars?.valueFormatter, bars?.format, locale]);
5542
+ const lineTicks = useMemo(() => resolveAxisTickFormatter(line?.valueFormatter, line?.format, locale), [line?.valueFormatter, line?.format, locale]);
5543
+ const palette = useMemo(() => {
5544
+ const p = pairedPalette(barKeys, lineKeys, bars?.colors, line?.colors);
5545
+ return { bars: resolveChartColors(p.bars, tokens), line: resolveChartColors(p.line, tokens) };
5546
+ }, [barKeys, lineKeys, bars?.colors, line?.colors, tokens]);
5547
+ const option = useMemo(
5548
+ () => buildPairedOption({
5549
+ data,
5550
+ index,
5551
+ labels,
5552
+ barKeys,
5553
+ lineKeys,
5554
+ barPalette: palette.bars,
5555
+ linePalette: palette.line,
5556
+ barFormatter,
5557
+ lineFormatter,
5558
+ barAxis,
5559
+ lineAxis,
5560
+ barTicks,
5561
+ lineTicks,
5562
+ type: bars?.type,
5563
+ fitToData: line?.fitToData,
5564
+ barAxisLabel: bars?.yAxisLabel,
5565
+ lineAxisLabel: line?.yAxisLabel,
5566
+ barMax: bars?.maxValue,
5567
+ lineMax: line?.maxValue,
5568
+ lineMin: line?.minValue,
5569
+ height,
5570
+ heightRatio,
5571
+ yAxisWidth,
5572
+ showLegend,
5573
+ showTooltip,
5574
+ reserveActions: Boolean(actions),
5575
+ plotWidth,
5576
+ guideColor: tokens?.mutedForeground ?? void 0,
5577
+ chartOptions,
5578
+ locale
5579
+ }),
5580
+ [
5581
+ data,
5582
+ index,
5583
+ labels,
5584
+ barKeys,
5585
+ lineKeys,
5586
+ palette,
5587
+ barFormatter,
5588
+ lineFormatter,
5589
+ barAxis,
5590
+ lineAxis,
5591
+ barTicks,
5592
+ lineTicks,
5593
+ bars?.type,
5594
+ bars?.yAxisLabel,
5595
+ bars?.maxValue,
5596
+ line?.fitToData,
5597
+ line?.yAxisLabel,
5598
+ line?.maxValue,
5599
+ line?.minValue,
5600
+ height,
5601
+ heightRatio,
5602
+ yAxisWidth,
5603
+ showLegend,
5604
+ showTooltip,
5605
+ actions,
5606
+ plotWidth,
5607
+ tokens,
5608
+ chartOptions,
5609
+ locale
5610
+ ]
5611
+ );
5612
+ const dataTable = useMemo(() => {
5613
+ const upper = chartDataTable(labelRows(data, index, labels), index, barKeys, barFormatter, labels, t.item);
5614
+ const lower = chartDataTable(labelRows(data, index, labels), index, lineKeys, lineFormatter, labels, t.item);
5615
+ const times = timeTooltipLabels(data.map((row) => row[index]), locale);
5616
+ return {
5617
+ ...upper,
5618
+ columns: [...upper.columns, ...lower.columns.slice(1)],
5619
+ rows: upper.rows.map((r, i) => [times?.[i] ?? r[0] ?? "", ...r.slice(1), ...(lower.rows[i] ?? []).slice(1)])
5620
+ };
5621
+ }, [data, index, labels, barKeys, lineKeys, barFormatter, lineFormatter, t, locale]);
5622
+ const label2 = ariaLabel ?? t.chart([...barKeys, ...lineKeys]);
5623
+ return /* @__PURE__ */ jsx(
5624
+ ChartFrame,
5625
+ {
5626
+ ...chartRootProps(rest),
5627
+ ref: frameRef,
5628
+ title,
5629
+ summary: summary === "auto" ? void 0 : summary,
5630
+ isLoading: isLoading ?? false,
5631
+ isEmpty: data.length === 0,
5632
+ emptyState,
5633
+ height,
5634
+ variant: "bar",
5635
+ ariaLabel: label2,
5636
+ className,
5637
+ dataTable,
5638
+ children: /* @__PURE__ */ jsx(EChart, { option, height: "100%", ariaLabel: label2, actions, fileName, exportHeader: { title, summary: summary === "auto" ? void 0 : summary } })
5639
+ }
5640
+ );
5641
+ }
5642
+ var PairedChart = forwardRef(PairedChartImpl);
5643
+ PairedChart.displayName = "PairedChart";
5644
+
5645
+ // ../squadkit/registry/lib/distribution.ts
5646
+ function toFinite(value) {
5647
+ const n = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : NaN;
5648
+ return Number.isFinite(n) ? n : null;
5649
+ }
5650
+ function sortedNumbers(values) {
5651
+ const out = [];
5652
+ for (const v of values) {
5653
+ const n = toFinite(v);
5654
+ if (n !== null) out.push(n);
5655
+ }
5656
+ return out.sort((a, b) => a - b);
5657
+ }
5658
+ function quantile(sorted, p) {
5659
+ if (sorted.length === 0) return null;
5660
+ const pos = (sorted.length - 1) * Math.min(1, Math.max(0, p));
5661
+ const lo = Math.floor(pos);
5662
+ const hi = Math.ceil(pos);
5663
+ return sorted[lo] + (sorted[hi] - sorted[lo]) * (pos - lo);
5664
+ }
5665
+ function boxStats(values, whisker = 1.5) {
5666
+ const sorted = sortedNumbers(values);
5667
+ if (sorted.length === 0) return null;
5668
+ const q1 = quantile(sorted, 0.25);
5669
+ const median = quantile(sorted, 0.5);
5670
+ const q3 = quantile(sorted, 0.75);
5671
+ const reach = Math.max(0, whisker) * (q3 - q1);
5672
+ const low = q1 - reach;
5673
+ const high = q3 + reach;
5674
+ const inside = sorted.filter((v) => v >= low && v <= high);
5675
+ return {
5676
+ count: sorted.length,
5677
+ min: sorted[0],
5678
+ max: sorted[sorted.length - 1],
5679
+ q1,
5680
+ median,
5681
+ q3,
5682
+ whiskerLow: inside[0] ?? q1,
5683
+ whiskerHigh: inside[inside.length - 1] ?? q3,
5684
+ outliers: sorted.filter((v) => v < low || v > high)
5685
+ };
5686
+ }
5687
+ function niceBinWidth(span, target = 10) {
5688
+ if (!(span > 0) || !Number.isFinite(span)) return 1;
5689
+ const raw = span / target;
5690
+ const pow = 10 ** Math.floor(Math.log10(raw));
5691
+ const f = raw / pow;
5692
+ return (f <= 1 ? 1 : f <= 2 ? 2 : f <= 2.5 ? 2.5 : f <= 5 ? 5 : 10) * pow;
5693
+ }
5694
+ var MAX_BINS = 200;
5695
+ function tidy(n, step) {
5696
+ const digits = Math.min(100, Math.max(0, Math.ceil(-Math.log10(step)) + 6));
5697
+ const rounded = Number(n.toFixed(digits));
5698
+ return Number.isFinite(rounded) ? rounded : n;
5699
+ }
5700
+ function histogramBins(values, config = {}) {
5701
+ const sorted = sortedNumbers(values);
5702
+ if (sorted.length === 0) return [];
5703
+ const width = config.binWidth !== void 0 && Number.isFinite(config.binWidth) && config.binWidth > 0 ? config.binWidth : null;
5704
+ const lo = sorted[0];
5705
+ const hi = sorted[sorted.length - 1];
5706
+ const cap = config.max !== void 0 && Number.isFinite(config.max) ? config.max : null;
5707
+ const end = cap !== null ? cap : hi;
5708
+ const first = config.start !== void 0 && Number.isFinite(config.start) ? config.start : null;
5709
+ let step = width ?? niceBinWidth((cap !== null ? Math.min(hi, cap) : hi) - (first ?? lo));
5710
+ const start = first ?? tidy(Math.floor(lo / step) * step, step);
5711
+ const countFor = (w) => Math.ceil((end - start) / w - 1e-9);
5712
+ if (countFor(step) > MAX_BINS) step = niceBinWidth(end - start, MAX_BINS);
5713
+ const n = Math.min(MAX_BINS, Math.max(cap === null ? 1 : 0, countFor(step)));
5714
+ const bins = [];
5715
+ for (let i = 0; i < n; i++) {
5716
+ const from = i === 0 ? start : bins[i - 1].to;
5717
+ let to = tidy(start + (i + 1) * step, step);
5718
+ if (!(to > from)) to = start + (i + 1) * step;
5719
+ if (cap !== null) to = Math.min(to, cap);
5720
+ bins.push({ from, to, count: 0, overflow: false });
5721
+ }
5722
+ const inRange = cap === null ? sorted : sorted.filter((v) => v < cap);
5723
+ const over = cap === null ? [] : sorted.filter((v) => v >= cap);
5724
+ for (const v of inRange) {
5725
+ if (v < start || bins.length === 0) continue;
5726
+ const i = Math.min(bins.length - 1, Math.floor((v - start) / step + 1e-9));
5727
+ bins[i].count += 1;
5728
+ }
5729
+ if (over.length > 0) bins.push({ from: cap, to: Math.max(hi, cap), count: over.length, overflow: true });
5730
+ return bins;
5731
+ }
5732
+ var TEXT15 = {
5351
5733
  ja: {
5352
- tones: { positive: "\u826F\u597D", warning: "\u6CE8\u610F", negative: "\u8981\u5BFE\u5FDC", neutral: "\u4E2D\u7ACB" },
5353
- toneNote: (label2) => `\uFF08${label2}\uFF09`,
5354
- selectAll: "\u5168\u3066\u9078\u629E",
5355
- selectRow: "\u884C\u3092\u9078\u629E",
5356
- trend: (points, first, last, min, max) => `${points}\u70B9\u306E\u63A8\u79FB\u3001${first}\u304B\u3089${last}\u3001\u6700\u5C0F${min}\u3001\u6700\u5927${max}`,
5357
- noData: "\u30C7\u30FC\u30BF\u304C\u3042\u308A\u307E\u305B\u3093",
5358
- pageSize: (size) => `${size}\u4EF6`,
5359
- range: (total, start, end) => `${total}\u4EF6\u4E2D ${start} - ${end} \u3092\u8868\u793A`,
5360
- first: "\u6700\u521D",
5361
- previous: "\u524D\u3078",
5362
- next: "\u6B21\u3078",
5363
- last: "\u6700\u5F8C",
5364
- columns: "\u8868\u793A\u3059\u308B\u5217",
5365
- exportCsv: "CSV \u3092\u66F8\u304D\u51FA\u3059"
5734
+ countUnit: "\u4EF6",
5735
+ atOrAbove: (from) => `${from}\u4EE5\u4E0A`,
5736
+ range: (from, to) => `${from}\u301C${to}`,
5737
+ count: (n, unit) => `${n}${unit}`,
5738
+ countHeader: (unit) => `${unit}\u6570`,
5739
+ share: "\u69CB\u6210\u6BD4",
5740
+ value: "\u5024",
5741
+ valueInSentence: "\u5024",
5742
+ threshold: "\u3057\u304D\u3044\u5024",
5743
+ peak: (total, bin, count, unit) => `${total}${unit}\u306E\u3046\u3061\u6700\u3082\u591A\u3044\u306E\u306F ${bin}\uFF08${count}${unit}\uFF09`,
5744
+ overThreshold: (name, value, count, share) => `${name}\uFF08${value}\uFF09\u4EE5\u4E0A\u306F ${count}\uFF08${share}\uFF09`,
5745
+ sentenceJoin: "\u3002",
5746
+ chart: (label2) => `${label2}\u306E\u30D2\u30B9\u30C8\u30B0\u30E9\u30E0`
5366
5747
  },
5367
5748
  en: {
5368
- tones: { positive: "Good", warning: "Caution", negative: "Needs action", neutral: "Neutral" },
5369
- toneNote: (label2) => ` (${label2})`,
5370
- selectAll: "Select all",
5371
- selectRow: "Select row",
5372
- trend: (points, first, last, min, max) => `Trend over ${points} points: ${first} to ${last}, min ${min}, max ${max}`,
5373
- noData: "No results.",
5374
- pageSize: (size) => `${size} / page`,
5375
- range: (total, start, end) => `Showing ${start}\u2013${end} of ${total}`,
5376
- first: "First",
5377
- previous: "Previous",
5378
- next: "Next",
5379
- last: "Last",
5380
- columns: "Columns",
5381
- exportCsv: "Export CSV"
5749
+ countUnit: "",
5750
+ atOrAbove: (from) => `${from}+`,
5751
+ range: (from, to) => `${from}\u2013${to}`,
5752
+ count: (n, unit) => unit ? `${n} ${unit}` : `${n}`,
5753
+ countHeader: () => "Count",
5754
+ share: "Share",
5755
+ value: "Value",
5756
+ valueInSentence: "values",
5757
+ threshold: "Threshold",
5758
+ peak: (total, bin, count, unit) => `Most common: ${bin} (${unit ? `${count} ${unit}` : count} of ${total})`,
5759
+ overThreshold: (name, value, count, share) => `At or above ${name} (${value}): ${count} (${share})`,
5760
+ sentenceJoin: ". ",
5761
+ chart: (label2) => `Histogram of ${label2}`
5382
5762
  }
5383
5763
  };
5384
- var useText = () => TEXT14[uiLanguage(useLocale())];
5385
- function resolveFormat(format, row) {
5386
- return typeof format === "function" ? format(row) : format;
5764
+ var textOf4 = (locale) => TEXT15[uiLanguage(resolveLocale(locale))];
5765
+ function binLabel(bin, formatter, locale) {
5766
+ const t = textOf4(locale);
5767
+ return bin.overflow ? t.atOrAbove(formatter(bin.from)) : t.range(formatter(bin.from), formatter(bin.to));
5768
+ }
5769
+ function thresholdPosition(bins, value) {
5770
+ const lastIndex = bins.length - 1;
5771
+ const i = bins.findIndex((b, k) => k === lastIndex ? value >= b.from && (b.overflow || value <= b.to) : value >= b.from && value < b.to);
5772
+ const bin = bins[i];
5773
+ if (!bin) return null;
5774
+ return bin.overflow ? i - 0.5 : i - 0.5 + (value - bin.from) / (bin.to - bin.from);
5775
+ }
5776
+ function countAtOrAbove(values, value, from = -Infinity) {
5777
+ return sortedNumbers(values).filter((v) => v >= value && v >= from).length;
5778
+ }
5779
+ var percent = (n, total) => total === 0 ? "0%" : `${Math.round(n / total * 100)}%`;
5780
+ function buildHistogramSummary(bins, formatter, config) {
5781
+ if (bins.length === 0 || config.total === 0) return void 0;
5782
+ const t = textOf4(config.locale);
5783
+ const count = (n) => t.count(n, config.countUnit);
5784
+ const peak = bins.reduce((a, b) => b.count > a.count ? b : a);
5785
+ const parts = [t.peak(config.total, binLabel(peak, formatter, config.locale), peak.count, config.countUnit)];
5786
+ if (config.threshold && config.atOrAbove !== void 0) {
5787
+ const name = config.threshold.label ?? t.threshold;
5788
+ parts.push(t.overThreshold(name, formatter(config.threshold.value), count(config.atOrAbove), percent(config.atOrAbove, config.total)));
5789
+ }
5790
+ return parts.join(t.sentenceJoin);
5387
5791
  }
5388
- function isMissingCell(value) {
5389
- return value === null || value === void 0 || value === "" || typeof value === "number" && Number.isNaN(value);
5792
+ function histogramDataTable(bins, formatter, config) {
5793
+ const t = textOf4(config.locale);
5794
+ return {
5795
+ columns: [config.valueLabel ?? t.value, t.countHeader(config.countUnit), t.share],
5796
+ rows: bins.map((b) => [binLabel(b, formatter, config.locale), `${b.count}`, percent(b.count, config.total)])
5797
+ };
5390
5798
  }
5391
- function formatCellText(value, row, meta, locale) {
5392
- if (isMissingCell(value)) return meta.nullLabel ?? EMPTY_VALUE;
5393
- if (meta.dateFormat && (value instanceof Date || typeof value === "string" || typeof value === "number")) {
5394
- const text = formatDate(value, meta.dateFormat, locale, { timeZone: meta.timeZone });
5395
- return text === EMPTY_VALUE ? String(value) : text;
5396
- }
5397
- if (typeof value !== "number") return String(value);
5398
- return formatValue(value, resolveFormat(meta.format, row), locale);
5799
+ function thresholdPoint(bins, value) {
5800
+ const pos = thresholdPosition(bins, value) ?? 0;
5801
+ const index = Math.min(bins.length - 1, Math.max(0, Math.round(pos)));
5802
+ return [index, 0, pos - index];
5399
5803
  }
5400
- var THRESHOLD_TONE_STYLES = {
5401
- positive: { Icon: CircleCheck, text: "text-positive" },
5402
- warning: { Icon: TriangleAlert, text: "text-warning" },
5403
- negative: { Icon: CircleX, text: "text-negative" },
5404
- neutral: { Icon: Minus, text: "text-neutral" }
5405
- };
5406
- function ThresholdCell({ value, text, thresholds }) {
5407
- const t = useText();
5408
- const tone = typeof value === "number" ? toneForValue(value, thresholds) : void 0;
5409
- if (!tone) return /* @__PURE__ */ jsx("div", { className: "text-right tabular-nums", children: text });
5410
- const { Icon, text: toneClass } = THRESHOLD_TONE_STYLES[tone];
5411
- const label2 = t.tones[tone];
5412
- return /* @__PURE__ */ jsxs(
5413
- "div",
5414
- {
5415
- "data-slot": "data-table-threshold-cell",
5416
- "data-tone": tone,
5417
- className: cn("flex items-center justify-end gap-1.5 tabular-nums", toneClass),
5804
+ function thresholdRenderer(style) {
5805
+ return (params, api) => {
5806
+ const { x: gx, y: gy, width, height } = params.coordSys;
5807
+ const band = Math.abs(api.size([1, 0])[0] ?? 0);
5808
+ const x = (api.coord([api.value(0), 0])[0] ?? 0) + api.value(2) * band;
5809
+ const nearRight = x > gx + width * 0.7;
5810
+ return {
5811
+ type: "group",
5418
5812
  children: [
5419
- /* @__PURE__ */ jsx(Icon, { className: "size-3.5 shrink-0", "aria-hidden": "true" }),
5420
- /* @__PURE__ */ jsx("span", { children: text }),
5421
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: t.toneNote(label2) })
5813
+ {
5814
+ type: "line",
5815
+ shape: { x1: x, y1: gy, x2: x, y2: gy + height },
5816
+ style: { stroke: style.lineColor || "#888", lineWidth: 1.5, lineDash: [4, 4], fill: "none" }
5817
+ },
5818
+ {
5819
+ type: "text",
5820
+ style: {
5821
+ text: style.text,
5822
+ x,
5823
+ y: gy - 4,
5824
+ fill: style.labelColor || "#888",
5825
+ textAlign: nearRight ? "right" : "left",
5826
+ textVerticalAlign: "bottom"
5827
+ }
5828
+ }
5422
5829
  ]
5423
- }
5424
- );
5830
+ };
5831
+ };
5425
5832
  }
5426
- function useControlledState(controlledValue, defaultValue, onChange) {
5427
- const [internalValue, setInternalValue] = useState(defaultValue);
5428
- const isControlled = controlledValue !== void 0;
5429
- const value = isControlled ? controlledValue : internalValue;
5430
- const setValue = useCallback(
5431
- (updaterOrValue) => {
5432
- onChange?.(updaterOrValue);
5433
- if (!isControlled) {
5434
- setInternalValue(updaterOrValue);
5833
+ var ROW_STYLE3 = "display:flex;align-items:center;gap:8px;line-height:20px";
5834
+ function buildHistogramOption(config) {
5835
+ const { locale } = config;
5836
+ const t = textOf4(locale);
5837
+ const { bins, total, colors, valueFormatter, threshold, showTooltip = true, countUnit = t.countUnit } = config;
5838
+ const pos = threshold ? thresholdPosition(bins, threshold.value) : null;
5839
+ const option = {
5840
+ ...chartMotion(false),
5841
+ legend: { show: false },
5842
+ grid: { top: pos === null ? 16 : 28, right: 16, bottom: 4, left: 4, containLabel: true },
5843
+ tooltip: {
5844
+ show: showTooltip,
5845
+ trigger: "axis",
5846
+ axisPointer: { type: "shadow" },
5847
+ formatter: (params) => {
5848
+ const list = Array.isArray(params) ? params : [params];
5849
+ const bin = bins[(list.find((p) => p.seriesId === "count") ?? list[0])?.dataIndex ?? 0];
5850
+ if (!bin) return "";
5851
+ const color = escapeHtml((bin.overflow ? colors.overflow : colors.bar) || "currentColor");
5852
+ const line = (name, text) => `<div style="${ROW_STYLE3}"><span style="display:inline-block;width:8px;height:8px;border-radius:9999px;background:${color}"></span><span style="flex:1;opacity:.8">${escapeHtml(name)}</span><span style="font-weight:500;font-variant-numeric:tabular-nums;margin-left:12px">${escapeHtml(text)}</span></div>`;
5853
+ return `<div style="font-weight:500;margin-bottom:4px">${escapeHtml(binLabel(bin, valueFormatter, locale))}</div>` + line(t.countHeader(countUnit), t.count(bin.count, countUnit)) + line(t.share, percent(bin.count, total));
5435
5854
  }
5436
5855
  },
5437
- [onChange, isControlled]
5438
- );
5439
- return [value, setValue];
5440
- }
5441
- var DataTableContext = createContext(null);
5442
- function useDataTable() {
5443
- const ctx = useContext(DataTableContext);
5444
- if (!ctx) throw new Error("useDataTable must be used inside <DataTable>");
5445
- return ctx;
5856
+ xAxis: {
5857
+ type: "category",
5858
+ data: bins.map((b) => binLabel(b, valueFormatter, locale)),
5859
+ axisTick: { show: false },
5860
+ axisLabel: { hideOverlap: true }
5861
+ },
5862
+ yAxis: {
5863
+ type: "value",
5864
+ minInterval: 1,
5865
+ axisLine: { show: false },
5866
+ axisTick: { show: false },
5867
+ splitLine: { show: true }
5868
+ },
5869
+ series: [
5870
+ {
5871
+ id: "count",
5872
+ type: "bar",
5873
+ barCategoryGap: "6%",
5874
+ data: bins.map((b) => ({
5875
+ value: b.count,
5876
+ itemStyle: { color: (b.overflow ? colors.overflow : colors.bar) || void 0, borderRadius: [2, 2, 0, 0] }
5877
+ })),
5878
+ label: { show: false, color: config.labelColor, fontVariantNumeric: "tabular-nums" }
5879
+ },
5880
+ ...threshold && pos !== null ? [
5881
+ {
5882
+ id: "threshold",
5883
+ type: "custom",
5884
+ silent: true,
5885
+ z: 10,
5886
+ animation: false,
5887
+ tooltip: { show: false },
5888
+ encode: { x: 0, y: 1 },
5889
+ // [階級の添字, 0(y の位置は使わない), 階級の中での位置(左端 -0.5 〜 右端 0.5)]
5890
+ data: [thresholdPoint(bins, threshold.value)],
5891
+ renderItem: thresholdRenderer({
5892
+ text: `${threshold.label ?? t.threshold} ${valueFormatter(threshold.value)}`,
5893
+ lineColor: config.lineColor,
5894
+ labelColor: config.labelColor
5895
+ })
5896
+ }
5897
+ ] : []
5898
+ ]
5899
+ };
5900
+ return mergeChartOptions(option, config.chartOptions);
5446
5901
  }
5447
- var NO_ROWS12 = [];
5448
- function DataTable({
5449
- columns = NO_ROWS12,
5450
- data = NO_ROWS12,
5451
- enableSorting = true,
5452
- sorting: sortingProp,
5453
- onSortingChange,
5454
- enableFiltering = false,
5455
- columnFilters: columnFiltersProp,
5456
- onColumnFiltersChange,
5457
- globalFilter: globalFilterProp,
5458
- onGlobalFilterChange,
5459
- enablePagination = false,
5460
- pagination: paginationProp,
5461
- onPaginationChange,
5462
- pageCount,
5463
- rowCount,
5464
- manualFiltering,
5465
- manualSorting,
5466
- isLoading = false,
5902
+ var NO_VALUES = [];
5903
+ var HistogramChart = forwardRef(function HistogramChart2({
5904
+ values = NO_VALUES,
5905
+ binWidth,
5906
+ start,
5907
+ max,
5908
+ threshold,
5909
+ countUnit: countUnitProp,
5910
+ valueLabel,
5911
+ format,
5912
+ valueFormatter: valueFormatterProp,
5913
+ showTooltip = true,
5914
+ title,
5915
+ summary,
5916
+ actions,
5917
+ fileName,
5918
+ isLoading,
5919
+ emptyState,
5920
+ height,
5921
+ chartOptions,
5922
+ ariaLabel,
5923
+ className,
5924
+ ...rest
5925
+ }, ref) {
5926
+ const { ref: frameRef, tokens } = useChartSurface(ref);
5927
+ const locale = useLocale();
5928
+ const t = TEXT15[uiLanguage(locale)];
5929
+ const countUnit = countUnitProp ?? t.countUnit;
5930
+ const valueFormatter = useMemo(
5931
+ () => valueFormatterProp ?? (format === void 0 ? toValueFormatter("number", locale) : resolveValueFormatter(void 0, format, locale)),
5932
+ [valueFormatterProp, format, locale]
5933
+ );
5934
+ const bins = useMemo(() => histogramBins(values, { binWidth, start, max }), [values, binWidth, start, max]);
5935
+ const total = useMemo(() => bins.reduce((sum, b) => sum + b.count, 0), [bins]);
5936
+ const colors = useMemo(() => {
5937
+ const [bar = ""] = resolveChartColors(["chart-1"], tokens);
5938
+ return { bar, overflow: resolveFillColors(tokens).neutral };
5939
+ }, [tokens]);
5940
+ const option = useMemo(
5941
+ () => buildHistogramOption({
5942
+ bins,
5943
+ total,
5944
+ colors,
5945
+ valueFormatter,
5946
+ countUnit,
5947
+ threshold,
5948
+ showTooltip,
5949
+ labelColor: tokens?.foreground ?? void 0,
5950
+ lineColor: tokens?.mutedForeground ?? void 0,
5951
+ chartOptions,
5952
+ locale
5953
+ }),
5954
+ [bins, total, colors, valueFormatter, countUnit, threshold, showTooltip, tokens, chartOptions, locale]
5955
+ );
5956
+ const dataTable = useMemo(
5957
+ () => histogramDataTable(bins, valueFormatter, { total, countUnit, valueLabel, locale }),
5958
+ [bins, valueFormatter, total, countUnit, valueLabel, locale]
5959
+ );
5960
+ const resolvedSummary = useMemo(
5961
+ () => summary === "auto" ? buildHistogramSummary(bins, valueFormatter, {
5962
+ total,
5963
+ countUnit,
5964
+ threshold,
5965
+ atOrAbove: threshold ? countAtOrAbove(values, threshold.value, bins[0]?.from) : void 0,
5966
+ locale
5967
+ }) : summary,
5968
+ [summary, bins, valueFormatter, total, countUnit, threshold, values, locale]
5969
+ );
5970
+ const label2 = ariaLabel ?? t.chart(valueLabel ?? t.valueInSentence);
5971
+ return /* @__PURE__ */ jsx(
5972
+ ChartFrame,
5973
+ {
5974
+ ...chartRootProps(rest),
5975
+ ref: frameRef,
5976
+ title,
5977
+ summary: resolvedSummary,
5978
+ isLoading: isLoading ?? false,
5979
+ isEmpty: bins.length === 0,
5980
+ emptyState,
5981
+ height,
5982
+ variant: "bar",
5983
+ ariaLabel: label2,
5984
+ className,
5985
+ dataTable,
5986
+ children: /* @__PURE__ */ jsx(EChart, { option, height: "100%", ariaLabel: label2, actions, fileName, exportHeader: { title, summary: resolvedSummary } })
5987
+ }
5988
+ );
5989
+ });
5990
+ HistogramChart.displayName = "HistogramChart";
5991
+ var TEXT16 = {
5992
+ ja: {
5993
+ value: "\u5024",
5994
+ valueInSentence: "\u5024",
5995
+ medianOf: (name, label2, median) => `${name}\u306E${label2}\u306E\u4E2D\u592E\u5024\u306F ${median}`,
5996
+ medianRange: (label2, top, topMedian, low, lowMedian) => `${label2}\u306E\u4E2D\u592E\u5024\u304C\u6700\u3082\u9AD8\u3044\u306E\u306F ${top}\uFF08${topMedian}\uFF09\u3001\u6700\u3082\u4F4E\u3044\u306E\u306F ${low}\uFF08${lowMedian}\uFF09`,
5997
+ outliers: (n) => `\u5916\u308C\u5024\u306F ${n} \u4EF6`,
5998
+ sentenceJoin: "\u3002",
5999
+ columns: ["\u7FA4", "\u4EF6\u6570", "\u6700\u5C0F\uFF08\u3072\u3052\uFF09", "\u7B2C1\u56DB\u5206\u4F4D", "\u4E2D\u592E\u5024", "\u7B2C3\u56DB\u5206\u4F4D", "\u6700\u5927\uFF08\u3072\u3052\uFF09", "\u5916\u308C\u5024"],
6000
+ listJoin: "\u3001",
6001
+ none: "\u306A\u3057",
6002
+ tooltipTitle: (name, label2, count) => `${name}\uFF08${label2}\u30FB${count}\u4EF6\uFF09`,
6003
+ max: "\u6700\u5927\uFF08\u3072\u3052\uFF09",
6004
+ q3: "\u7B2C3\u56DB\u5206\u4F4D",
6005
+ median: "\u4E2D\u592E\u5024",
6006
+ q1: "\u7B2C1\u56DB\u5206\u4F4D",
6007
+ min: "\u6700\u5C0F\uFF08\u3072\u3052\uFF09",
6008
+ outlierRow: "\u5916\u308C\u5024",
6009
+ chart: (label2) => `${label2}\u306E\u7BB1\u3072\u3052\u56F3`
6010
+ },
6011
+ en: {
6012
+ value: "Value",
6013
+ valueInSentence: "value",
6014
+ medianOf: (name, label2, median) => `Median ${label2} for ${name}: ${median}`,
6015
+ medianRange: (label2, top, topMedian, low, lowMedian) => `Highest median ${label2}: ${top} (${topMedian}); lowest: ${low} (${lowMedian})`,
6016
+ outliers: (n) => n === 1 ? "1 outlier" : `${n} outliers`,
6017
+ sentenceJoin: ". ",
6018
+ columns: ["Group", "Count", "Min (whisker)", "Q1", "Median", "Q3", "Max (whisker)", "Outliers"],
6019
+ listJoin: ", ",
6020
+ none: "None",
6021
+ tooltipTitle: (name, label2, count) => `${name} (${label2}, n=${count})`,
6022
+ max: "Max (whisker)",
6023
+ q3: "Q3",
6024
+ median: "Median",
6025
+ q1: "Q1",
6026
+ min: "Min (whisker)",
6027
+ outlierRow: "Outliers",
6028
+ chart: (label2) => `Box plot of ${label2}`
6029
+ }
6030
+ };
6031
+ var textOf5 = (locale) => TEXT16[uiLanguage(resolveLocale(locale))];
6032
+ function boxPlotEntries(groups, whisker) {
6033
+ return groups.map((g) => ({ name: g.name, stats: boxStats(g.values, whisker) }));
6034
+ }
6035
+ function buildBoxPlotSummary(entries, formatter, valueLabel, locale) {
6036
+ const t = textOf5(locale);
6037
+ const label2 = valueLabel ?? t.valueInSentence;
6038
+ const real = entries.filter((e) => e.stats !== null);
6039
+ if (real.length === 0) return void 0;
6040
+ const top = real.reduce((a, b) => b.stats.median > a.stats.median ? b : a);
6041
+ const low = real.reduce((a, b) => b.stats.median < a.stats.median ? b : a);
6042
+ const parts = [
6043
+ real.length === 1 ? t.medianOf(top.name, label2, formatter(top.stats.median)) : t.medianRange(label2, top.name, formatter(top.stats.median), low.name, formatter(low.stats.median))
6044
+ ];
6045
+ const outliers = real.reduce((sum, e) => sum + e.stats.outliers.length, 0);
6046
+ if (outliers > 0) parts.push(t.outliers(outliers));
6047
+ return parts.join(t.sentenceJoin);
6048
+ }
6049
+ function boxPlotDataTable(entries, formatter, locale) {
6050
+ const t = textOf5(locale);
6051
+ const f = (n) => n === void 0 ? EMPTY_VALUE : formatter(n);
6052
+ return {
6053
+ columns: [...t.columns],
6054
+ rows: entries.map(({ name, stats: s }) => [
6055
+ name,
6056
+ s ? `${s.count}` : EMPTY_VALUE,
6057
+ f(s?.whiskerLow),
6058
+ f(s?.q1),
6059
+ f(s?.median),
6060
+ f(s?.q3),
6061
+ f(s?.whiskerHigh),
6062
+ s ? s.outliers.length > 0 ? s.outliers.map(formatter).join(t.listJoin) : t.none : EMPTY_VALUE
6063
+ ])
6064
+ };
6065
+ }
6066
+ var ROW_STYLE4 = "display:flex;align-items:center;gap:8px;line-height:20px";
6067
+ var BOX_SHARE = 0.4;
6068
+ function boxPlotRenderer(color, outliers) {
6069
+ return (_params, api) => {
6070
+ const x = api.coord([api.value(0), api.value(3)])[0] ?? 0;
6071
+ const y = (dim) => api.coord([api.value(0), api.value(dim)])[1] ?? 0;
6072
+ const band = Math.abs(api.size([1, 0])[0] ?? 0);
6073
+ const half = band * BOX_SHARE / 2;
6074
+ const cap = half / 2;
6075
+ const [yLow, yQ1, yMed, yQ3, yHigh] = [y(1), y(2), y(3), y(4), y(5)];
6076
+ const stroke = { stroke: color, lineWidth: 1.5, fill: "none" };
6077
+ const line = (x1, y1, x2, y2) => ({ type: "line", shape: { x1, y1, x2, y2 }, style: stroke });
6078
+ return {
6079
+ type: "group",
6080
+ children: [
6081
+ line(x, yHigh, x, yQ3),
6082
+ line(x, yQ1, x, yLow),
6083
+ line(x - cap, yHigh, x + cap, yHigh),
6084
+ line(x - cap, yLow, x + cap, yLow),
6085
+ {
6086
+ type: "rect",
6087
+ shape: { x: x - half, y: Math.min(yQ3, yQ1), width: half * 2, height: Math.abs(yQ1 - yQ3), r: 2 },
6088
+ style: { fill: withAlpha(color, 0.25), stroke: color, lineWidth: 1.5 }
6089
+ },
6090
+ line(x - half, yMed, x + half, yMed),
6091
+ ...(outliers[api.value(0)] ?? []).map((v) => ({
6092
+ type: "circle",
6093
+ shape: { cx: x, cy: api.coord([api.value(0), v])[1] ?? 0, r: 3 },
6094
+ style: { fill: "transparent", stroke: color, lineWidth: 1.5 }
6095
+ }))
6096
+ ]
6097
+ };
6098
+ };
6099
+ }
6100
+ function buildBoxPlotOption(config) {
6101
+ const t = textOf5(config.locale);
6102
+ const { entries, valueFormatter, showTooltip = true, valueLabel = t.value } = config;
6103
+ const axisFormatter = config.axisFormatter ?? valueFormatter;
6104
+ const color = config.color || "#888";
6105
+ const data = entries.flatMap((e, i) => e.stats ? [[i, e.stats.whiskerLow, e.stats.q1, e.stats.median, e.stats.q3, e.stats.whiskerHigh, e.stats.min, e.stats.max]] : []);
6106
+ const entryAt = (i) => entries[i];
6107
+ const option = {
6108
+ ...chartMotion(false),
6109
+ legend: { show: false },
6110
+ grid: { top: 16, right: 16, bottom: 4, left: 4, containLabel: true },
6111
+ tooltip: {
6112
+ show: showTooltip,
6113
+ trigger: "item",
6114
+ formatter: (params) => {
6115
+ const entry = entryAt(params.value?.[0] ?? -1);
6116
+ const s = entry?.stats;
6117
+ if (!entry || !s) return "";
6118
+ const line = (name, text) => `<div style="${ROW_STYLE4}"><span style="flex:1;opacity:.8">${escapeHtml(name)}</span><span style="font-weight:500;font-variant-numeric:tabular-nums;margin-left:12px">${escapeHtml(text)}</span></div>`;
6119
+ return `<div style="font-weight:500;margin-bottom:4px">${escapeHtml(t.tooltipTitle(entry.name, valueLabel, s.count))}</div>` + line(t.max, valueFormatter(s.whiskerHigh)) + line(t.q3, valueFormatter(s.q3)) + line(t.median, valueFormatter(s.median)) + line(t.q1, valueFormatter(s.q1)) + line(t.min, valueFormatter(s.whiskerLow)) + (s.outliers.length > 0 ? line(t.outlierRow, s.outliers.map(valueFormatter).join(t.listJoin)) : "");
6120
+ }
6121
+ },
6122
+ xAxis: { type: "category", data: entries.map((e) => e.name), axisTick: { show: false }, axisLabel: { interval: 0, hideOverlap: false } },
6123
+ yAxis: {
6124
+ type: "value",
6125
+ axisLine: { show: false },
6126
+ axisTick: { show: false },
6127
+ splitLine: { show: true },
6128
+ axisLabel: { formatter: (v) => axisFormatter(v) }
6129
+ },
6130
+ series: [
6131
+ {
6132
+ id: "box",
6133
+ type: "custom",
6134
+ encode: { x: 0, y: [1, 2, 3, 4, 5, 6, 7] },
6135
+ data,
6136
+ renderItem: boxPlotRenderer(color, entries.map((e) => e.stats?.outliers ?? []))
6137
+ }
6138
+ ]
6139
+ };
6140
+ return mergeChartOptions(option, config.chartOptions);
6141
+ }
6142
+ var NO_GROUPS = [];
6143
+ var BoxPlotChart = forwardRef(function BoxPlotChart2({
6144
+ groups = NO_GROUPS,
6145
+ whisker,
6146
+ valueLabel,
6147
+ format,
6148
+ valueFormatter: valueFormatterProp,
6149
+ showTooltip = true,
6150
+ title,
6151
+ summary,
6152
+ actions,
6153
+ fileName,
6154
+ isLoading,
6155
+ emptyState,
6156
+ height,
6157
+ chartOptions,
6158
+ ariaLabel,
6159
+ className,
6160
+ ...rest
6161
+ }, ref) {
6162
+ const { ref: frameRef, tokens } = useChartSurface(ref);
6163
+ const locale = useLocale();
6164
+ const t = TEXT16[uiLanguage(locale)];
6165
+ const valueFormatter = useMemo(() => resolveValueFormatter(valueFormatterProp, format, locale), [valueFormatterProp, format, locale]);
6166
+ const entries = useMemo(() => boxPlotEntries(groups, whisker), [groups, whisker]);
6167
+ const [color = ""] = useMemo(() => resolveChartColors(["chart-1"], tokens), [tokens]);
6168
+ const option = useMemo(
6169
+ () => buildBoxPlotOption({ entries, color, valueFormatter, valueLabel, showTooltip, labelColor: tokens?.foreground ?? void 0, chartOptions, locale }),
6170
+ [entries, color, valueFormatter, valueLabel, showTooltip, tokens, chartOptions, locale]
6171
+ );
6172
+ const dataTable = useMemo(() => boxPlotDataTable(entries, valueFormatter, locale), [entries, valueFormatter, locale]);
6173
+ const resolvedSummary = useMemo(
6174
+ () => summary === "auto" ? buildBoxPlotSummary(entries, valueFormatter, valueLabel, locale) : summary,
6175
+ [summary, entries, valueFormatter, valueLabel, locale]
6176
+ );
6177
+ const label2 = ariaLabel ?? t.chart(valueLabel ?? t.valueInSentence);
6178
+ return /* @__PURE__ */ jsx(
6179
+ ChartFrame,
6180
+ {
6181
+ ...chartRootProps(rest),
6182
+ ref: frameRef,
6183
+ title,
6184
+ summary: resolvedSummary,
6185
+ isLoading: isLoading ?? false,
6186
+ isEmpty: entries.length === 0,
6187
+ emptyState,
6188
+ height,
6189
+ variant: "bar",
6190
+ ariaLabel: label2,
6191
+ className,
6192
+ dataTable,
6193
+ children: /* @__PURE__ */ jsx(EChart, { option, height: "100%", ariaLabel: label2, actions, fileName, exportHeader: { title, summary: resolvedSummary } })
6194
+ }
6195
+ );
6196
+ });
6197
+ BoxPlotChart.displayName = "BoxPlotChart";
6198
+ var STATUS_LABELS = {
6199
+ ja: {
6200
+ success: "\u6B63\u5E38",
6201
+ partial: "\u4E00\u90E8\u5931\u6557",
6202
+ failed: "\u5931\u6557",
6203
+ delayed: "\u9045\u5EF6",
6204
+ running: "\u5B9F\u884C\u4E2D",
6205
+ stopped: "\u505C\u6B62\u4E2D",
6206
+ active: "\u6709\u52B9",
6207
+ inactive: "\u7121\u52B9",
6208
+ warning: "\u8B66\u544A",
6209
+ error: "\u30A8\u30E9\u30FC",
6210
+ pending: "\u4FDD\u7559\u4E2D"
6211
+ },
6212
+ en: {
6213
+ success: "Healthy",
6214
+ partial: "Partially failed",
6215
+ failed: "Failed",
6216
+ delayed: "Delayed",
6217
+ running: "Running",
6218
+ stopped: "Stopped",
6219
+ active: "Active",
6220
+ inactive: "Inactive",
6221
+ warning: "Warning",
6222
+ error: "Error",
6223
+ pending: "Pending"
6224
+ }
6225
+ };
6226
+ var STATUS_VOCABULARY = {
6227
+ success: { label: STATUS_LABELS.en.success, tone: "positive" },
6228
+ partial: { label: STATUS_LABELS.en.partial, tone: "warning" },
6229
+ failed: { label: STATUS_LABELS.en.failed, tone: "negative" },
6230
+ delayed: { label: STATUS_LABELS.en.delayed, tone: "warning" },
6231
+ running: { label: STATUS_LABELS.en.running, tone: "info" },
6232
+ stopped: { label: STATUS_LABELS.en.stopped, tone: "neutral" },
6233
+ active: { label: STATUS_LABELS.en.active, tone: "positive" },
6234
+ inactive: { label: STATUS_LABELS.en.inactive, tone: "neutral" },
6235
+ warning: { label: STATUS_LABELS.en.warning, tone: "warning" },
6236
+ error: { label: STATUS_LABELS.en.error, tone: "negative" },
6237
+ pending: { label: STATUS_LABELS.en.pending, tone: "info" }
6238
+ };
6239
+ function statusLabel(status, locale) {
6240
+ return Object.hasOwn(STATUS_VOCABULARY, status) ? STATUS_LABELS[uiLanguage(resolveLocale(locale))][status] : status;
6241
+ }
6242
+ var defaultColorMap = Object.fromEntries(
6243
+ Object.entries(STATUS_VOCABULARY).map(([key, { tone }]) => [key, tone])
6244
+ );
6245
+ var legacyColorTones = {
6246
+ green: "positive",
6247
+ gray: "neutral",
6248
+ yellow: "warning",
6249
+ red: "negative",
6250
+ blue: "info"
6251
+ };
6252
+ var toneStyles = {
6253
+ positive: { dot: "bg-positive", bg: "bg-positive-subtle", text: "text-positive" },
6254
+ negative: { dot: "bg-negative", bg: "bg-negative-subtle", text: "text-negative" },
6255
+ warning: { dot: "bg-warning", bg: "bg-warning-subtle", text: "text-warning" },
6256
+ info: { dot: "bg-info", bg: "bg-info-subtle", text: "text-info" },
6257
+ neutral: { dot: "bg-neutral", bg: "bg-neutral-subtle", text: "text-neutral" }
6258
+ };
6259
+ var resolveTone = (color) => {
6260
+ if (color === void 0) return "neutral";
6261
+ if (Object.hasOwn(toneStyles, color)) return color;
6262
+ return legacyColorTones[color] ?? "neutral";
6263
+ };
6264
+ var StatusBadge = forwardRef(function StatusBadge2({ status, label: label2, colorMap, className }, ref) {
6265
+ const locale = useLocale();
6266
+ const mergedColorMap = { ...defaultColorMap, ...colorMap };
6267
+ const style = toneStyles[resolveTone(mergedColorMap[status])];
6268
+ const displayLabel = label2 ?? statusLabel(status, locale);
6269
+ return /* @__PURE__ */ jsxs(
6270
+ Badge,
6271
+ {
6272
+ ref,
6273
+ "data-slot": "status-badge",
6274
+ variant: "outline",
6275
+ className: cn("gap-1.5 rounded-md border-transparent", style.bg, style.text, className),
6276
+ "aria-label": displayLabel,
6277
+ children: [
6278
+ /* @__PURE__ */ jsx("span", { className: cn("inline-block size-1.5 rounded-full", style.dot), "aria-hidden": "true" }),
6279
+ displayLabel
6280
+ ]
6281
+ }
6282
+ );
6283
+ });
6284
+ StatusBadge.displayName = "StatusBadge";
6285
+ var KIND_ORDER = ["overdue", "risk", "dueSoon", "stale"];
6286
+ var KIND_TONES = {
6287
+ overdue: "negative",
6288
+ risk: "negative",
6289
+ dueSoon: "warning",
6290
+ stale: "info"
6291
+ };
6292
+ var daysEn = (n) => n === 1 ? "1 day" : `${n} days`;
6293
+ var TEXT17 = {
6294
+ ja: {
6295
+ labels: { overdue: "\u671F\u65E5\u8D85\u904E", risk: "\u30EA\u30B9\u30AF", dueSoon: "\u671F\u9650\u9593\u8FD1", stale: "\u6EDE\u7559" },
6296
+ overdue: (days) => `\u671F\u65E5\u3092 ${days} \u65E5\u904E\u304E\u3066\u3044\u307E\u3059`,
6297
+ dueToday: "\u671F\u65E5\u306F\u4ECA\u65E5\u3067\u3059",
6298
+ dueIn: (days) => `\u671F\u65E5\u307E\u3067\u3042\u3068 ${days} \u65E5\u3067\u3059`,
6299
+ stale: (days) => `${days} \u65E5\u9593\u52D5\u304D\u304C\u3042\u308A\u307E\u305B\u3093`,
6300
+ empty: "\u5BFE\u5FDC\u304C\u5FC5\u8981\u306A\u9805\u76EE\u306F\u3042\u308A\u307E\u305B\u3093",
6301
+ due: "\u671F\u65E5",
6302
+ nextAction: "\u6B21\u306E\u4E00\u624B: "
6303
+ },
6304
+ en: {
6305
+ labels: { overdue: "Overdue", risk: "At risk", dueSoon: "Due soon", stale: "Stalled" },
6306
+ overdue: (days) => `${daysEn(days)} overdue`,
6307
+ dueToday: "Due today",
6308
+ dueIn: (days) => `Due in ${daysEn(days)}`,
6309
+ stale: (days) => `No activity for ${daysEn(days)}`,
6310
+ empty: "Nothing needs attention",
6311
+ due: "Due",
6312
+ nextAction: "Next step: "
6313
+ }
6314
+ };
6315
+ TEXT17.en.labels;
6316
+ function toDate2(value) {
6317
+ if (value === null || value === void 0 || value === "") return null;
6318
+ const date = typeof value === "string" ? parseISO(value) : value;
6319
+ return Number.isFinite(date.getTime()) ? date : null;
6320
+ }
6321
+ function judgeAttention(input) {
6322
+ const asOf = toDate2(input.asOf);
6323
+ if (!asOf) return null;
6324
+ const dueSoonDays = input.dueSoonDays ?? 7;
6325
+ const due = toDate2(input.dueDate);
6326
+ if (due) {
6327
+ const untilDue = differenceInCalendarDays(due, asOf);
6328
+ if (untilDue < 0) return { kind: "overdue", days: -untilDue };
6329
+ }
6330
+ if (input.risk) return { kind: "risk", days: 0 };
6331
+ if (due) {
6332
+ const untilDue = differenceInCalendarDays(due, asOf);
6333
+ if (untilDue <= dueSoonDays) return { kind: "dueSoon", days: untilDue };
6334
+ }
6335
+ const last = toDate2(input.lastActivityAt);
6336
+ if (last && input.staleDays !== void 0) {
6337
+ const idle = differenceInCalendarDays(asOf, last);
6338
+ if (idle >= input.staleDays) return { kind: "stale", days: idle };
6339
+ }
6340
+ return null;
6341
+ }
6342
+ function compareAttention(a, b) {
6343
+ if (!a || !b) return a ? -1 : b ? 1 : 0;
6344
+ const byKind = KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind);
6345
+ if (byKind !== 0) return byKind;
6346
+ return a.kind === "dueSoon" ? a.days - b.days : b.days - a.days;
6347
+ }
6348
+ function defaultReason(j, labels, t) {
6349
+ switch (j.kind) {
6350
+ case "overdue":
6351
+ return t.overdue(j.days);
6352
+ case "dueSoon":
6353
+ return j.days === 0 ? t.dueToday : t.dueIn(j.days);
6354
+ case "stale":
6355
+ return t.stale(j.days);
6356
+ case "risk":
6357
+ return labels.risk;
6358
+ }
6359
+ }
6360
+ function cell(row, key) {
6361
+ if (!key || row === null || typeof row !== "object") return void 0;
6362
+ return row[key];
6363
+ }
6364
+ function asDateInput(value) {
6365
+ return typeof value === "string" || value instanceof Date ? value : void 0;
6366
+ }
6367
+ function asNode(value) {
6368
+ return typeof value === "string" || typeof value === "number" ? value : null;
6369
+ }
6370
+ function AttentionList({
6371
+ rows,
6372
+ asOf,
6373
+ dueSoonDays,
6374
+ staleDays,
6375
+ labels,
6376
+ getRowId,
6377
+ getTitle,
6378
+ getDueDate,
6379
+ getLastActivityAt,
6380
+ isRisk,
6381
+ idKey,
6382
+ titleKey,
6383
+ dueDateKey,
6384
+ lastActivityAtKey,
6385
+ riskKey,
6386
+ nextActionKey,
6387
+ getReason,
6388
+ getNextAction,
6389
+ getMeta,
6390
+ onRowClick,
6391
+ includeUnflagged = false,
6392
+ emptyTitle,
6393
+ emptyDescription,
6394
+ className,
6395
+ ...rest
6396
+ }) {
6397
+ const locale = useLocale();
6398
+ const t = TEXT17[uiLanguage(locale)];
6399
+ const mergedLabels = useMemo(() => ({ ...t.labels, ...labels }), [t, labels]);
6400
+ const colorMap = KIND_TONES;
6401
+ const dueOf = (row) => getDueDate ? getDueDate(row) : asDateInput(cell(row, dueDateKey));
6402
+ const nextActionOf = (row, j) => getNextAction ? getNextAction(row, j) : asNode(cell(row, nextActionKey));
6403
+ const items = (rows ?? []).map((row, index) => ({
6404
+ row,
6405
+ index,
6406
+ judgement: judgeAttention({
6407
+ dueDate: dueOf(row),
6408
+ lastActivityAt: getLastActivityAt ? getLastActivityAt(row) : asDateInput(cell(row, lastActivityAtKey)),
6409
+ risk: isRisk ? isRisk(row) : Boolean(cell(row, riskKey)),
6410
+ asOf,
6411
+ dueSoonDays,
6412
+ staleDays
6413
+ })
6414
+ })).filter((item) => includeUnflagged || item.judgement).sort((a, b) => compareAttention(a.judgement, b.judgement) || a.index - b.index);
6415
+ if (items.length === 0) {
6416
+ return /* @__PURE__ */ jsx(Empty, { title: emptyTitle ?? t.empty, description: emptyDescription, className });
6417
+ }
6418
+ return /* @__PURE__ */ jsx("ul", { "data-slot": "attention-list", className: cn("divide-y divide-border", className), ...rest, children: items.map(({ row, index, judgement }) => {
6419
+ const due = dueOf(row);
6420
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
6421
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1", children: [
6422
+ judgement && /* @__PURE__ */ jsx(StatusBadge, { status: judgement.kind, label: mergedLabels[judgement.kind], colorMap }),
6423
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 font-medium", children: getTitle ? getTitle(row) : asNode(cell(row, titleKey)) }),
6424
+ getMeta && /* @__PURE__ */ jsx("span", { className: "text-sm tabular-nums text-muted-foreground", children: getMeta(row) }),
6425
+ due ? /* @__PURE__ */ jsxs("span", { className: "ml-auto text-sm tabular-nums text-muted-foreground", children: [
6426
+ t.due,
6427
+ " ",
6428
+ formatDate(due, "date", locale)
6429
+ ] }) : null
6430
+ ] }),
6431
+ judgement && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: getReason ? getReason(row, judgement) : defaultReason(judgement, mergedLabels, t) }),
6432
+ judgement && (getNextAction || nextActionKey) && nextActionOf(row, judgement) ? /* @__PURE__ */ jsxs("p", { className: "mt-1 text-sm", children: [
6433
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t.nextAction }),
6434
+ nextActionOf(row, judgement)
6435
+ ] }) : null
6436
+ ] });
6437
+ return /* @__PURE__ */ jsx("li", { "data-slot": "attention-list-row", children: onRowClick ? /* @__PURE__ */ jsx(
6438
+ "button",
6439
+ {
6440
+ type: "button",
6441
+ onClick: () => onRowClick(row),
6442
+ className: "block w-full px-3 py-3 text-left transition-colors hover:bg-muted/50 focus-visible:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
6443
+ children: body
6444
+ }
6445
+ ) : /* @__PURE__ */ jsx("div", { className: "px-3 py-3", children: body }) }, getRowId ? getRowId(row) : String(cell(row, idKey) ?? index));
6446
+ }) });
6447
+ }
6448
+ AttentionList.displayName = "AttentionList";
6449
+ function niceStep2(raw) {
6450
+ if (!(raw > 0) || !Number.isFinite(raw)) return 1;
6451
+ const exp10 = 10 ** Math.floor(Math.log10(raw));
6452
+ const f = raw / exp10;
6453
+ return (f < 1.5 ? 1 : f < 2.5 ? 2 : f < 4 ? 3 : f < 7 ? 5 : 10) * exp10;
6454
+ }
6455
+ var round = (n) => Number(n.toPrecision(12));
6456
+ function comboAxisScale(lo, hi, splitNumber, ticks) {
6457
+ let low = Math.min(0, Number.isFinite(lo) ? lo : 0);
6458
+ let high = Math.max(0, Number.isFinite(hi) ? hi : 0);
6459
+ if (high <= low) high = low + 1;
6460
+ let step = niceStep2((high - low) / Math.max(1, splitNumber));
6461
+ for (let attempt = 0; ; attempt++) {
6462
+ const min = round(Math.floor(low / step) * step);
6463
+ const max = round(Math.ceil(high / step) * step);
6464
+ const tick = ticks(Math.max(Math.abs(min), Math.abs(max)));
6465
+ const count = Math.round((max - min) / step);
6466
+ const labels = Array.from({ length: count + 1 }, (_, i) => tick(round(min + i * step)));
6467
+ if (new Set(labels).size === labels.length || attempt >= 6) return { min, max, interval: step, tick };
6468
+ step = niceStep2(step * 2);
6469
+ }
6470
+ }
6471
+ var DEFAULT_CHART_HEIGHT2 = 288;
6472
+ var splitCountFor = (height) => height < 200 ? 3 : height < 300 ? 4 : 5;
6473
+ function escapeHtml3(text) {
6474
+ return text.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
6475
+ }
6476
+ var TEXT18 = {
6477
+ ja: { item: "\u9805\u76EE", chart: "\u8907\u5408\u30B0\u30E9\u30D5" },
6478
+ en: { item: "Item", chart: "Combination chart" }
6479
+ };
6480
+ var textOf6 = (locale) => TEXT18[uiLanguage(resolveLocale(locale))];
6481
+ var axisOf = (s) => s.axis === "right" ? "right" : "left";
6482
+ function comboSeriesList(series) {
6483
+ if (!Array.isArray(series)) return [];
6484
+ return series.filter(
6485
+ (s) => !!s && typeof s === "object" && typeof s.key === "string" && ["bar", "line", "area"].includes(s.type)
6486
+ );
6487
+ }
6488
+ function buildComboChartOption(config) {
6489
+ const { data, index, labels, formatters } = config;
6490
+ const series = comboSeriesList(config.series);
6491
+ const label2 = (key) => labels?.[key] ?? key;
6492
+ const seriesName = (s) => s.label ?? label2(s.key);
6493
+ const vertical = config.layout === "vertical";
6494
+ const categories = data.map((row) => String(row?.[index] ?? ""));
6495
+ const values = series.map((s) => data.map((row) => toChartNumber(row?.[s.key])));
6496
+ const hasRight = series.some((s) => axisOf(s) === "right");
6497
+ const axes = hasRight ? ["left", "right"] : ["left"];
6498
+ const extent = (axis) => {
6499
+ let [lo, hi] = [0, 0];
6500
+ const add = (v) => {
6501
+ if (v !== null && Number.isFinite(v)) [lo, hi] = [Math.min(lo, v), Math.max(hi, v)];
6502
+ };
6503
+ const stacks = /* @__PURE__ */ new Map();
6504
+ series.forEach((s, i) => {
6505
+ if (axisOf(s) !== axis) return;
6506
+ if (config.stacked && s.type !== "line") {
6507
+ const stack = stacks.get(s.type) ?? { pos: data.map(() => 0), neg: data.map(() => 0) };
6508
+ values[i].forEach((v, j) => {
6509
+ if (v === null) return;
6510
+ if (v >= 0) stack.pos[j] += v;
6511
+ else stack.neg[j] += v;
6512
+ });
6513
+ stacks.set(s.type, stack);
6514
+ } else values[i].forEach(add);
6515
+ });
6516
+ for (const stack of stacks.values()) [...stack.pos, ...stack.neg].forEach(add);
6517
+ for (const ref of config.referenceLines ?? []) if (axisOf(ref) === axis) add(toChartNumber(ref.value));
6518
+ return [lo, hi];
6519
+ };
6520
+ const split = splitCountFor(config.plotHeight ?? DEFAULT_CHART_HEIGHT2);
6521
+ const scales = Object.fromEntries(axes.map((axis) => [axis, comboAxisScale(...extent(axis), split, config.ticks[axis])]));
6522
+ const valueAxes = axes.map((axis) => ({
6523
+ type: "value",
6524
+ position: vertical ? axis === "left" ? "bottom" : "top" : axis,
6525
+ min: scales[axis].min,
6526
+ max: scales[axis].max,
6527
+ interval: scales[axis].interval,
6528
+ axisLine: { show: false },
6529
+ axisTick: { show: false },
6530
+ splitLine: { show: axis === "left" },
6531
+ axisLabel: { formatter: (v) => scales[axis].tick(v), hideOverlap: vertical ? true : void 0 }
6532
+ }));
6533
+ const categoryAxis = {
6534
+ type: "category",
6535
+ data: categories,
6536
+ inverse: vertical,
6537
+ axisTick: { show: false },
6538
+ axisLabel: { formatter: (v) => label2(v), hideOverlap: true }
6539
+ };
6540
+ const axisIndex = (s) => axisOf(s) === "right" ? 1 : 0;
6541
+ const drawn = series.map((s, i) => {
6542
+ const color = config.palette[i] || void 0;
6543
+ const common = {
6544
+ id: `series-${i}`,
6545
+ name: seriesName(s),
6546
+ data: values[i],
6547
+ [vertical ? "xAxisIndex" : "yAxisIndex"]: axisIndex(s),
6548
+ emphasis: { focus: "none" }
6549
+ };
6550
+ if (s.type === "bar")
6551
+ return {
6552
+ ...common,
6553
+ type: "bar",
6554
+ stack: config.stacked ? `bar-${axisOf(s)}` : void 0,
6555
+ barMaxWidth: 40,
6556
+ itemStyle: { color, borderRadius: vertical ? [0, 2, 2, 0] : [2, 2, 0, 0] }
6557
+ };
6558
+ return {
6559
+ ...common,
6560
+ type: "line",
6561
+ stack: config.stacked && s.type === "area" ? `area-${axisOf(s)}` : void 0,
6562
+ symbol: "circle",
6563
+ symbolSize: 6,
6564
+ showSymbol: data.length <= 24,
6565
+ connectNulls: false,
6566
+ lineStyle: { width: 2, color },
6567
+ itemStyle: { color },
6568
+ areaStyle: s.type === "area" ? { color, opacity: 0.18 } : void 0,
6569
+ z: 3
6570
+ };
6571
+ });
6572
+ const references = axes.flatMap((axis) => {
6573
+ const lines = (config.referenceLines ?? []).filter((r) => axisOf(r) === axis && toChartNumber(r.value) !== null);
6574
+ if (lines.length === 0) return [];
6575
+ return [
6576
+ {
6577
+ id: `reference-${axis}`,
6578
+ type: "line",
6579
+ data: data.map(() => null),
6580
+ [vertical ? "xAxisIndex" : "yAxisIndex"]: axis === "right" ? 1 : 0,
6581
+ silent: true,
6582
+ symbol: "none",
6583
+ tooltip: { show: false },
6584
+ markLine: {
6585
+ silent: true,
6586
+ symbol: "none",
6587
+ lineStyle: { type: "dashed", color: config.referenceColor || void 0, width: 1 },
6588
+ label: { position: "insideEndTop", color: config.referenceColor || void 0 },
6589
+ data: lines.map((r) => ({
6590
+ [vertical ? "xAxis" : "yAxis"]: toChartNumber(r.value),
6591
+ label: { formatter: r.label ?? config.formatters[axis](toChartNumber(r.value)) }
6592
+ }))
6593
+ }
6594
+ }
6595
+ ];
6596
+ });
6597
+ const showLegend = (config.showLegend ?? true) && series.length > 1;
6598
+ const tooltip = (params) => {
6599
+ const items = Array.isArray(params) ? params : [params];
6600
+ const first = items[0];
6601
+ if (!first) return "";
6602
+ const head = escapeHtml3(label2(categories[first.dataIndex] ?? ""));
6603
+ const lines = items.filter((p) => p.seriesIndex < series.length).map((p) => {
6604
+ const s = series[p.seriesIndex];
6605
+ const v = values[p.seriesIndex][p.dataIndex];
6606
+ return `${p.marker ?? ""}${escapeHtml3(seriesName(s))} ${v === null || v === void 0 ? EMPTY_VALUE : escapeHtml3(formatters[axisOf(s)](v))}`;
6607
+ });
6608
+ return [head, ...lines].join("<br/>");
6609
+ };
6610
+ const option = {
6611
+ animation: false,
6612
+ legend: { show: showLegend, top: 0, left: 0, data: series.map(seriesName) },
6613
+ tooltip: config.showTooltip === false ? { show: false } : { trigger: "axis", axisPointer: { type: "shadow" }, formatter: tooltip },
6614
+ grid: { left: 8, right: 8, top: showLegend ? 36 : 12, bottom: 8, containLabel: true },
6615
+ xAxis: vertical ? valueAxes : categoryAxis,
6616
+ yAxis: vertical ? categoryAxis : valueAxes,
6617
+ series: [...drawn, ...references]
6618
+ };
6619
+ return config.chartOptions ? mergeDeep2(option, config.chartOptions) : option;
6620
+ }
6621
+ function mergeDeep2(a, b) {
6622
+ const out = { ...a };
6623
+ for (const [k, v] of Object.entries(b)) {
6624
+ const cur = out[k];
6625
+ out[k] = v && cur && typeof v === "object" && typeof cur === "object" && !Array.isArray(v) && !Array.isArray(cur) ? mergeDeep2(cur, v) : v;
6626
+ }
6627
+ return out;
6628
+ }
6629
+ function comboChartDataTable(data, index, series, formatters, labels, locale) {
6630
+ const list = comboSeriesList(series);
6631
+ const label2 = (key) => labels?.[key] ?? key;
6632
+ return {
6633
+ columns: [labels?.[index] ?? textOf6(locale).item, ...list.map((s) => s.label ?? label2(s.key))],
6634
+ rows: data.map((row) => [
6635
+ label2(String(row?.[index] ?? "")),
6636
+ ...list.map((s) => {
6637
+ const n = toChartNumber(row?.[s.key]);
6638
+ return n === null ? EMPTY_VALUE : formatters[axisOf(s)](n);
6639
+ })
6640
+ ])
6641
+ };
6642
+ }
6643
+ var NO_ROWS13 = [];
6644
+ function ComboChartImpl({
6645
+ data = NO_ROWS13,
6646
+ index,
6647
+ series = NO_ROWS13,
6648
+ format,
6649
+ rightFormat,
6650
+ labels,
6651
+ referenceLines,
6652
+ stacked = false,
6653
+ layout = "horizontal",
6654
+ showLegend = true,
6655
+ showTooltip = true,
6656
+ onSelect,
6657
+ title,
6658
+ summary,
6659
+ isLoading,
6660
+ emptyState,
6661
+ height,
6662
+ chartOptions,
6663
+ actions,
6664
+ fileName,
6665
+ className,
6666
+ ariaLabel,
6667
+ ...rest
6668
+ }, ref) {
6669
+ const rows = Array.isArray(data) ? data : NO_ROWS13;
6670
+ const list = useMemo(() => comboSeriesList(series), [series]);
6671
+ const { ref: frameRef, tokens } = useChartSurface(ref);
6672
+ const locale = useLocale();
6673
+ const formatters = useMemo(
6674
+ () => ({ left: resolveValueFormatter(void 0, format, locale), right: resolveValueFormatter(void 0, rightFormat ?? format, locale) }),
6675
+ [format, rightFormat, locale]
6676
+ );
6677
+ const ticks = useMemo(
6678
+ () => ({ left: resolveAxisTickFormatter(void 0, format, locale), right: resolveAxisTickFormatter(void 0, rightFormat ?? format, locale) }),
6679
+ [format, rightFormat, locale]
6680
+ );
6681
+ const palette = useMemo(() => resolveColors(tokens, list), [tokens, list]);
6682
+ const option = useMemo(
6683
+ () => buildComboChartOption({
6684
+ data: rows,
6685
+ index,
6686
+ series: list,
6687
+ palette: palette.series,
6688
+ referenceColor: palette.reference,
6689
+ labels,
6690
+ formatters,
6691
+ ticks,
6692
+ referenceLines,
6693
+ stacked,
6694
+ layout,
6695
+ showLegend,
6696
+ showTooltip,
6697
+ plotHeight: height,
6698
+ chartOptions
6699
+ }),
6700
+ [rows, index, list, palette, labels, formatters, ticks, referenceLines, stacked, layout, showLegend, showTooltip, height, chartOptions]
6701
+ );
6702
+ const onEvents = useMemo(
6703
+ () => onSelect ? {
6704
+ click: (params) => {
6705
+ const p = params;
6706
+ if (p.componentType !== "series" || (p.seriesIndex ?? Infinity) >= list.length) return;
6707
+ const row = rows[p.dataIndex ?? -1];
6708
+ if (row) onSelect(row);
6709
+ }
6710
+ } : void 0,
6711
+ [onSelect, rows, list.length]
6712
+ );
6713
+ const dataTable = useMemo(() => comboChartDataTable(rows, index, list, formatters, labels, locale), [rows, index, list, formatters, labels, locale]);
6714
+ const label2 = ariaLabel ?? textOf6(locale).chart;
6715
+ return /* @__PURE__ */ jsx(
6716
+ ChartFrame,
6717
+ {
6718
+ ...chartRootProps(rest),
6719
+ ref: frameRef,
6720
+ title,
6721
+ summary,
6722
+ isLoading: isLoading ?? false,
6723
+ isEmpty: rows.length === 0,
6724
+ emptyState,
6725
+ height,
6726
+ variant: "bar",
6727
+ ariaLabel: label2,
6728
+ className,
6729
+ dataTable,
6730
+ children: /* @__PURE__ */ jsx(EChart, { option, onEvents, height: "100%", ariaLabel: label2, actions, fileName, exportHeader: { title, summary } })
6731
+ }
6732
+ );
6733
+ }
6734
+ function resolveColors(tokens, series) {
6735
+ const resolved = resolveChartColors(
6736
+ ["var(--muted-foreground)", ...series.map((s, i) => s.color ?? DEFAULT_CHART_COLORS[i % DEFAULT_CHART_COLORS.length])],
6737
+ tokens
6738
+ );
6739
+ return { reference: resolved[0] ?? "", series: resolved.slice(1) };
6740
+ }
6741
+ var ComboChart = forwardRef(ComboChartImpl);
6742
+ ComboChart.displayName = "ComboChart";
6743
+
6744
+ // ../squadkit/registry/lib/forecast.ts
6745
+ var FORECAST_MIN_POINTS = 5;
6746
+ var FORECAST_MAX_FIT_POINTS = 1e3;
6747
+ function inverseNormal(p) {
6748
+ if (!(p > 0 && p < 1)) return p <= 0 ? -Infinity : Infinity;
6749
+ const a = [-39.69683028665376, 220.9460984245205, -275.9285104469687, 138.357751867269, -30.66479806614716, 2.506628277459239];
6750
+ const b = [-54.47609879822406, 161.5858368580409, -155.6989798598866, 66.80131188771972, -13.28068155288572];
6751
+ const c = [-0.007784894002430293, -0.3223964580411365, -2.400758277161838, -2.549732539343734, 4.374664141464968, 2.938163982698783];
6752
+ const d = [0.007784695709041462, 0.3224671290700398, 2.445134137142996, 3.754408661907416];
6753
+ const low = 0.02425;
6754
+ if (p < low) {
6755
+ const q2 = Math.sqrt(-2 * Math.log(p));
6756
+ return (((((c[0] * q2 + c[1]) * q2 + c[2]) * q2 + c[3]) * q2 + c[4]) * q2 + c[5]) / ((((d[0] * q2 + d[1]) * q2 + d[2]) * q2 + d[3]) * q2 + 1);
6757
+ }
6758
+ if (p > 1 - low) {
6759
+ const q2 = Math.sqrt(-2 * Math.log(1 - p));
6760
+ return -(((((c[0] * q2 + c[1]) * q2 + c[2]) * q2 + c[3]) * q2 + c[4]) * q2 + c[5]) / ((((d[0] * q2 + d[1]) * q2 + d[2]) * q2 + d[3]) * q2 + 1);
6761
+ }
6762
+ const q = p - 0.5;
6763
+ const r = q * q;
6764
+ return (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q / (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1);
6765
+ }
6766
+ function nelderMead(f, start, step, maxEvals) {
6767
+ const dim = start.length;
6768
+ const simplex = [{ x: start.slice(), fx: f(start) }];
6769
+ for (let i = 0; i < dim; i++) {
6770
+ const x = start.slice();
6771
+ x[i] = x[i] + step;
6772
+ simplex.push({ x, fx: f(x) });
6773
+ }
6774
+ let evals = dim + 1;
6775
+ const combine = (a, b, t) => a.map((v, i) => v + t * (b[i] - v));
6776
+ while (evals < maxEvals) {
6777
+ simplex.sort((p, q) => p.fx - q.fx);
6778
+ const best = simplex[0];
6779
+ const worst = simplex[dim];
6780
+ const spread = Math.abs(worst.fx - best.fx);
6781
+ if (spread <= 1e-10 * (Math.abs(best.fx) + 1e-20)) break;
6782
+ const centroid = new Array(dim).fill(0);
6783
+ for (let i = 0; i < dim; i++) for (let j = 0; j < dim; j++) centroid[j] += simplex[i].x[j] / dim;
6784
+ const reflected = combine(centroid, worst.x, -1);
6785
+ const fr = f(reflected);
6786
+ evals++;
6787
+ if (fr < best.fx) {
6788
+ const expanded = combine(centroid, worst.x, -2);
6789
+ const fe = f(expanded);
6790
+ evals++;
6791
+ simplex[dim] = fe < fr ? { x: expanded, fx: fe } : { x: reflected, fx: fr };
6792
+ continue;
6793
+ }
6794
+ if (fr < simplex[dim - 1].fx) {
6795
+ simplex[dim] = { x: reflected, fx: fr };
6796
+ continue;
6797
+ }
6798
+ const outside = fr < worst.fx;
6799
+ const contracted = outside ? combine(centroid, reflected, 0.5) : combine(centroid, worst.x, 0.5);
6800
+ const fc = f(contracted);
6801
+ evals++;
6802
+ if (fc < (outside ? fr : worst.fx)) {
6803
+ simplex[dim] = { x: contracted, fx: fc };
6804
+ continue;
6805
+ }
6806
+ for (let i = 1; i <= dim; i++) {
6807
+ const x = combine(best.x, simplex[i].x, 0.5);
6808
+ simplex[i] = { x, fx: f(x) };
6809
+ evals++;
6810
+ }
6811
+ }
6812
+ simplex.sort((p, q) => p.fx - q.fx);
6813
+ return simplex[0];
6814
+ }
6815
+ function run(y, spec, p) {
6816
+ let l = p.l0;
6817
+ let b = spec.trend === "additive" ? p.b0 : 0;
6818
+ const s = spec.season === "none" ? [] : p.s0.slice();
6819
+ const mult = spec.season === "multiplicative";
6820
+ let sse = 0;
6821
+ for (let t = 0; t < y.length; t++) {
6822
+ const j = t % spec.m;
6823
+ const base = l + b;
6824
+ const sj = spec.season === "none" ? mult ? 1 : 0 : s[j];
6825
+ if (mult && (sj <= 0 || base <= 0)) return null;
6826
+ const yhat = mult ? base * sj : base + sj;
6827
+ const e = y[t] - yhat;
6828
+ if (!Number.isFinite(e)) return null;
6829
+ sse += e * e;
6830
+ if (mult) {
6831
+ l = base + p.alpha * e / sj;
6832
+ if (spec.trend === "additive") b = b + p.beta * e / sj;
6833
+ s[j] = sj + p.gamma * e / base;
6834
+ } else {
6835
+ l = base + p.alpha * e;
6836
+ if (spec.trend === "additive") b = b + p.beta * e;
6837
+ if (spec.season === "additive") s[j] = sj + p.gamma * e;
6838
+ }
6839
+ }
6840
+ return Number.isFinite(sse) ? { sse, level: l, slope: b, season: s } : null;
6841
+ }
6842
+ function initialSeason(y, m, mult) {
6843
+ const len = Math.min(Math.floor(y.length / m), 3) * m;
6844
+ const half = Math.floor(m / 2);
6845
+ const sums = new Array(m).fill(0);
6846
+ const counts = new Array(m).fill(0);
6847
+ for (let t = half; t < len - half; t++) {
6848
+ let trend;
6849
+ if (m % 2 === 0) {
6850
+ let sum = 0.5 * y[t - half] + 0.5 * y[t + half];
6851
+ for (let k = t - half + 1; k < t + half; k++) sum += y[k];
6852
+ trend = sum / m;
6853
+ } else {
6854
+ let sum = 0;
6855
+ for (let k = t - half; k <= t + half; k++) sum += y[k];
6856
+ trend = sum / m;
6857
+ }
6858
+ if (mult && trend <= 0) continue;
6859
+ sums[t % m] += mult ? y[t] / trend : y[t] - trend;
6860
+ counts[t % m]++;
6861
+ }
6862
+ const raw = sums.map((v, j) => counts[j] > 0 ? v / counts[j] : mult ? 1 : 0);
6863
+ const mean = raw.reduce((a, v) => a + v, 0) / m;
6864
+ return mult ? raw.map((v) => mean > 0 ? v / mean : 1) : raw.map((v) => v - mean);
6865
+ }
6866
+ function linearFit(z) {
6867
+ const n = z.length;
6868
+ if (n < 2) return { intercept: z[0] ?? 0, slope: 0 };
6869
+ const mt = (n - 1) / 2;
6870
+ const mz = z.reduce((a, v) => a + v, 0) / n;
6871
+ let num = 0;
6872
+ let den = 0;
6873
+ z.forEach((v, t) => {
6874
+ num += (t - mt) * (v - mz);
6875
+ den += (t - mt) * (t - mt);
6876
+ });
6877
+ const slope = den === 0 ? 0 : num / den;
6878
+ return { intercept: mz - slope * mt, slope };
6879
+ }
6880
+ var logistic = (x) => 1 / (1 + Math.exp(-x));
6881
+ var logit = (p) => Math.log(p / (1 - p));
6882
+ var EDGE = 1e-4;
6883
+ function fitModel(y, spec) {
6884
+ const n = y.length;
6885
+ const { m } = spec;
6886
+ const seasonal = spec.season !== "none";
6887
+ const mult = spec.season === "multiplicative";
6888
+ const s0 = seasonal ? initialSeason(y, m, mult) : [];
6889
+ const adjusted = y.map((v, t) => !seasonal ? v : mult ? v / s0[t % m] : v - s0[t % m]);
6890
+ const head = adjusted.slice(0, seasonal ? Math.min(n, Math.max(2 * m, 10)) : Math.min(n, 10));
6891
+ const line = linearFit(head);
6892
+ const trend = spec.trend === "additive";
6893
+ const l0Init = trend ? line.intercept - line.slope : head.slice(0, Math.min(head.length, 4)).reduce((a, v) => a + v, 0) / Math.min(head.length, 4);
6894
+ const b0Init = trend ? line.slope : 0;
6895
+ const mean = y.reduce((a, v) => a + v, 0) / n;
6896
+ const sd = Math.sqrt(y.reduce((a, v) => a + (v - mean) * (v - mean), 0) / n);
6897
+ const scale = Math.max(sd, Math.abs(mean) * 1e-3, 1e-9);
6898
+ const decode = (x) => {
6899
+ let i = 0;
6900
+ const alpha = EDGE + (1 - 2 * EDGE) * logistic(x[i++]);
6901
+ const beta = trend ? alpha * (EDGE + (1 - 2 * EDGE) * logistic(x[i++])) : 0;
6902
+ const gamma = seasonal ? (1 - alpha) * (EDGE + (1 - 2 * EDGE) * logistic(x[i++])) : 0;
6903
+ const l0 = l0Init + x[i++] * scale;
6904
+ const b0 = trend ? b0Init + x[i++] * scale * 0.1 : 0;
6905
+ return { alpha, beta, gamma, l0, b0, s0 };
6906
+ };
6907
+ const objective = (x) => run(y, spec, decode(x))?.sse ?? Infinity;
6908
+ const starts = [
6909
+ [0.2, 0.1, 0.1],
6910
+ [0.5, 0.3, 0.3],
6911
+ [0.05, 0.5, 0.05]
6912
+ ];
6913
+ let best = null;
6914
+ for (const [a, b, g] of starts) {
6915
+ const x0 = [logit(a), ...trend ? [logit(b)] : [], ...seasonal ? [logit(g)] : [], 0, ...trend ? [0] : []];
6916
+ let found = nelderMead(objective, x0, 1, 300 * x0.length);
6917
+ found = nelderMead(objective, found.x, 0.25, 200 * x0.length);
6918
+ if (best === null || found.fx < best.fx) best = found;
6919
+ }
6920
+ if (best === null || !Number.isFinite(best.fx)) return null;
6921
+ const params = decode(best.x);
6922
+ const state = run(y, spec, params);
6923
+ if (!state) return null;
6924
+ const k = 2 + (trend ? 2 : 0) + (seasonal ? 1 + (m - 1) : 0);
6925
+ const sse = Math.max(state.sse, 1e-12 * Math.max(1, mean * mean) * n);
6926
+ const aic = n * Math.log(sse / n) + 2 * k;
6927
+ const aicc = n - k - 1 > 0 ? aic + 2 * k * (k + 1) / (n - k - 1) : Infinity;
6928
+ return { spec, params, state, k, aicc };
6929
+ }
6930
+ function varianceMultiplier(fit, h, seasonIndex) {
6931
+ const { alpha: a, beta: b, gamma: g } = fit.params;
6932
+ const { m } = fit.spec;
6933
+ const trend = fit.spec.trend === "additive";
6934
+ const seasonal = fit.spec.season !== "none";
6935
+ const k = Math.floor((h - 1) / m);
6936
+ let extra = trend ? (h - 1) * (a * a + a * b * h + b * b * h * (2 * h - 1) / 6) : (h - 1) * a * a;
6937
+ if (seasonal) extra += trend ? g * k * (2 * a + g + b * m * (k + 1)) : g * k * (2 * a + g);
6938
+ if (fit.spec.season === "multiplicative") extra *= seasonIndex * seasonIndex;
6939
+ return 1 + extra;
6940
+ }
6941
+ function forecast(values, options = {}) {
6942
+ const finite = (v) => typeof v === "number" && Number.isFinite(v);
6943
+ let first = 0;
6944
+ while (first < values.length && !finite(values[first])) first++;
6945
+ let last = values.length - 1;
6946
+ while (last >= first && !finite(values[last])) last--;
6947
+ if (last < first) return null;
6948
+ const ignore = Math.max(0, Math.floor(options.ignoreLast ?? 0));
6949
+ const end = last + 1 - ignore;
6950
+ let start = first;
6951
+ if (end - start > FORECAST_MAX_FIT_POINTS) start = end - FORECAST_MAX_FIT_POINTS;
6952
+ if (end - start < FORECAST_MIN_POINTS) return null;
6953
+ const y = [];
6954
+ for (let t = start; t < end; t++) {
6955
+ const v = values[t];
6956
+ if (finite(v)) {
6957
+ y.push(v);
6958
+ continue;
6959
+ }
6960
+ let lo = t - 1;
6961
+ while (lo >= start && !finite(values[lo])) lo--;
6962
+ let hi = t + 1;
6963
+ while (hi < end && !finite(values[hi])) hi++;
6964
+ const a = lo >= start ? values[lo] : void 0;
6965
+ const b = hi < end ? values[hi] : void 0;
6966
+ y.push(a !== void 0 && b !== void 0 ? a + (b - a) * (t - lo) / (hi - lo) : a ?? b ?? 0);
6967
+ }
6968
+ const n = y.length;
6969
+ const m = Math.max(1, Math.floor(options.seasonLength ?? 1));
6970
+ const seasonOption = options.season ?? "auto";
6971
+ const trendOption = options.trend ?? "auto";
6972
+ const canSeason = m > 1 && n >= 2 * m && seasonOption !== "none";
6973
+ const positive = y.every((v) => v > 0);
6974
+ const seasons = !canSeason ? ["none"] : seasonOption === "additive" ? ["additive"] : seasonOption === "multiplicative" ? [positive ? "multiplicative" : "additive"] : positive ? ["none", "additive", "multiplicative"] : ["none", "additive"];
6975
+ const trends = trendOption === "none" ? ["none"] : trendOption === "auto" ? ["none", "additive"] : ["additive"];
6976
+ let chosen = null;
6977
+ for (const trend of trends) {
6978
+ for (const season of seasons) {
6979
+ const fit = fitModel(y, { trend, season, m: season === "none" ? 1 : m });
6980
+ if (!fit) continue;
6981
+ if (chosen === null || fit.aicc < chosen.aicc || !Number.isFinite(chosen.aicc) && fit.k < chosen.k) chosen = fit;
6982
+ }
6983
+ }
6984
+ if (!chosen) return null;
6985
+ const dof = n - (chosen.k - 1);
6986
+ const sigma = Math.sqrt(chosen.state.sse / (dof >= 1 ? dof : n));
6987
+ const confidence = options.confidence !== void 0 && options.confidence > 0 && options.confidence < 1 ? options.confidence : 0.95;
6988
+ const z = inverseNormal(1 - (1 - confidence) / 2);
6989
+ const horizon = options.horizon !== void 0 && Number.isFinite(options.horizon) ? Math.max(0, Math.floor(options.horizon)) : m > 1 ? m : Math.min(12, Math.max(1, Math.round(n / 4)));
6990
+ const steps = values.length - end + horizon;
6991
+ const { spec, state } = chosen;
6992
+ const points = [];
6993
+ for (let h = 1; h <= steps; h++) {
6994
+ const base = state.level + h * state.slope;
6995
+ const s = spec.season === "none" ? void 0 : state.season[(n - 1 + h) % spec.m];
6996
+ const value = s === void 0 ? base : spec.season === "multiplicative" ? base * s : base + s;
6997
+ const half = z * sigma * Math.sqrt(varianceMultiplier(chosen, h, s ?? 1));
6998
+ points.push({ value, lower: value - half, upper: value + half });
6999
+ }
7000
+ const { alpha, beta, gamma } = chosen.params;
7001
+ return {
7002
+ model: {
7003
+ trend: spec.trend,
7004
+ season: spec.season,
7005
+ alpha,
7006
+ ...spec.trend === "additive" ? { beta } : {},
7007
+ ...spec.season !== "none" ? { gamma, seasonLength: spec.m } : {}
7008
+ },
7009
+ fitted: n,
7010
+ end,
7011
+ sigma,
7012
+ points
7013
+ };
7014
+ }
7015
+ var TEXT19 = {
7016
+ ja: {
7017
+ forecast: "\u4E88\u6E2C",
7018
+ note: (source) => source ? `\u4E88\u6E2C\u306F\u6307\u6570\u5E73\u6ED1\u6CD5\u306B\u3088\u308B\u8FD1\u4F3C\u5024\u3067\u3059\u3002${source} \u306E\u4E88\u6E2C\u5024\u3068\u306F\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002` : "\u4E88\u6E2C\u306F\u6307\u6570\u5E73\u6ED1\u6CD5\u306B\u3088\u308B\u8FD1\u4F3C\u5024\u3067\u3059\u3002\u5143\u306E\u30C4\u30FC\u30EB\u306E\u4E88\u6E2C\u5024\u3068\u306F\u4E00\u81F4\u3057\u307E\u305B\u3093\u3002",
7019
+ interval: (pct, lo, hi) => `${pct} \u4E88\u6E2C\u533A\u9593 ${lo}\u301C${hi}`,
7020
+ chart: (names) => `${names.join("\u30FB")}\u306E\u5B9F\u7E3E\u3068\u4E88\u6E2C\u306E\u6298\u308C\u7DDA\u30B0\u30E9\u30D5`,
7021
+ item: "\u671F\u9593"
7022
+ },
7023
+ en: {
7024
+ forecast: "Forecast",
7025
+ note: (source) => `Forecast is an approximation (exponential smoothing); values do not match ${source ? `${source}'s` : "the source tool's"} forecast.`,
7026
+ interval: (pct, lo, hi) => `${pct} interval ${lo} \u2013 ${hi}`,
7027
+ chart: (names) => `Line chart of actuals and forecast for ${names.join(", ")}`,
7028
+ item: "Period"
7029
+ }
7030
+ };
7031
+ var LEGEND_DASHED_ICON = "path://M0,0 L42,0 L42,24 L0,24 Z M58,0 L100,0 L100,24 L58,24 Z";
7032
+ var BOUNDARY_LABEL_ROOM = 18;
7033
+ var MAX_PERIODS = 5e3;
7034
+ var DAY_MS = 864e5;
7035
+ var NO_ROWS14 = [];
7036
+ var DATE_RE = /^(\d{4})(?:[-/](\d{1,2})(?:[-/](\d{1,2}))?)?(?:[T ].*)?$/;
7037
+ var QUARTER_RE = /^(\d{4})[-\s]?Q([1-4])$/i;
7038
+ function parseDate(value) {
7039
+ if (value instanceof Date) {
7040
+ return Number.isNaN(value.getTime()) ? null : { y: value.getFullYear(), m: value.getMonth() + 1, d: value.getDate(), precision: "day" };
7041
+ }
7042
+ if (typeof value === "number" && Number.isFinite(value)) {
7043
+ if (Number.isInteger(value) && value >= 1e3 && value <= 9999) return { y: value, m: 1, d: 1, precision: "year" };
7044
+ return parseDate(new Date(value));
7045
+ }
7046
+ if (typeof value !== "string") return null;
7047
+ const text = value.trim();
7048
+ const q = QUARTER_RE.exec(text);
7049
+ if (q) return { y: Number(q[1]), m: (Number(q[2]) - 1) * 3 + 1, d: 1, precision: "month" };
7050
+ const m = DATE_RE.exec(text);
7051
+ if (m) {
7052
+ const month = m[2] === void 0 ? 1 : Number(m[2]);
7053
+ const day = m[3] === void 0 ? 1 : Number(m[3]);
7054
+ if (month < 1 || month > 12 || day < 1 || day > 31) return null;
7055
+ return { y: Number(m[1]), m: month, d: day, precision: m[3] !== void 0 ? "day" : m[2] !== void 0 ? "month" : "year" };
7056
+ }
7057
+ if (text === "") return null;
7058
+ const parsed = new Date(text);
7059
+ return Number.isNaN(parsed.getTime()) ? null : parseDate(parsed);
7060
+ }
7061
+ var dayOrdinal = (p) => Math.round(Date.UTC(p.y, p.m - 1, p.d) / DAY_MS);
7062
+ function ordinal(p, period) {
7063
+ switch (period) {
7064
+ case "year":
7065
+ return p.y;
7066
+ case "quarter":
7067
+ return p.y * 4 + Math.floor((p.m - 1) / 3);
7068
+ case "month":
7069
+ return p.y * 12 + (p.m - 1);
7070
+ default:
7071
+ return dayOrdinal(p);
7072
+ }
7073
+ }
7074
+ var pad2 = (n) => String(n).padStart(2, "0");
7075
+ function ordinalLabel(o, period) {
7076
+ if (period === "year") return String(o);
7077
+ if (period === "quarter") return `${Math.floor(o / 4)}-${pad2(o % 4 * 3 + 1)}`;
7078
+ if (period === "month") return `${Math.floor(o / 12)}-${pad2(o % 12 + 1)}`;
7079
+ const d = new Date(o * DAY_MS);
7080
+ return `${d.getUTCFullYear()}-${pad2(d.getUTCMonth() + 1)}-${pad2(d.getUTCDate())}`;
7081
+ }
7082
+ function inferPeriod(dates) {
7083
+ if (dates.every((d) => d.precision === "year")) return "year";
7084
+ const coarse = dates.every((d) => d.precision !== "day");
7085
+ const keys = [...new Set(dates.map((d) => coarse ? ordinal(d, "month") : dayOrdinal(d)))].sort((a, b) => a - b);
7086
+ const gaps = keys.slice(1).map((k, i) => k - keys[i]).sort((a, b) => a - b);
7087
+ const gap = gaps.length === 0 ? coarse ? 1 : 1 : gaps[Math.floor(gaps.length / 2)];
7088
+ if (coarse) return gap >= 12 ? "year" : gap >= 3 ? "quarter" : "month";
7089
+ return gap >= 360 ? "year" : gap >= 85 ? "quarter" : gap >= 28 ? "month" : gap >= 7 ? "week" : "day";
7090
+ }
7091
+ var SEASON_LENGTH = { day: 7, week: 52, month: 12, quarter: 4, year: 1 };
7092
+ function buildForecastModel(data, x, y, seriesKey, period, options, singleName, otherLabel) {
7093
+ if (!x || !y || !Array.isArray(data)) return null;
7094
+ const points = [];
7095
+ for (const row of data) {
7096
+ if (!row || typeof row !== "object") continue;
7097
+ const date = parseDate(row[x]);
7098
+ const value = toChartNumber(row[y]);
7099
+ if (!date || value === null) continue;
7100
+ const raw = seriesKey ? row[seriesKey] : singleName;
7101
+ const name = raw === null || raw === void 0 || raw === "" ? "\u2014" : String(raw);
7102
+ points.push({ date, name, value });
7103
+ }
7104
+ if (points.length === 0) return null;
7105
+ const grain = period ?? inferPeriod(points.map((p) => p.date));
7106
+ const step = grain === "week" ? 7 : 1;
7107
+ const keyed = points.map((p) => ({ ...p, key: ordinal(p.date, grain) }));
7108
+ let min = Math.min(...keyed.map((p) => p.key));
7109
+ const max = Math.max(...keyed.map((p) => p.key));
7110
+ if ((max - min) / step + 1 > MAX_PERIODS) min = max - (MAX_PERIODS - 1) * step;
7111
+ const count = Math.floor((max - min) / step) + 1;
7112
+ const slot = (key) => Math.floor((key - min) / step);
7113
+ const sums = /* @__PURE__ */ new Map();
7114
+ for (const p of keyed) {
7115
+ const at = slot(p.key);
7116
+ if (at < 0) continue;
7117
+ let list = sums.get(p.name);
7118
+ if (!list) {
7119
+ list = Array(count).fill(null);
7120
+ sums.set(p.name, list);
7121
+ }
7122
+ list[at] = (list[at] ?? 0) + p.value;
7123
+ }
7124
+ const all = [...sums.keys()];
7125
+ const totals = all.map((name) => ({ name, value: sums.get(name).reduce((a, v) => a + (v ?? 0), 0) }));
7126
+ const { top, rest, other } = aggregateTopN(totals, void 0, otherLabel);
7127
+ const names = top.map((t) => t.name);
7128
+ const actual = names.map((n) => sums.get(n));
7129
+ if (other) {
7130
+ const folded = Array(count).fill(null);
7131
+ for (const r of rest) sums.get(r.name).forEach((v, i) => v === null ? void 0 : folded[i] = (folded[i] ?? 0) + v);
7132
+ names.push(other.name);
7133
+ actual.push(folded);
7134
+ }
7135
+ const resolved = { ...options, seasonLength: options?.seasonLength ?? SEASON_LENGTH[grain] };
7136
+ const results = actual.map((values) => {
7137
+ try {
7138
+ return forecast(values.map((v) => v === null ? NaN : v), resolved);
7139
+ } catch {
7140
+ return null;
7141
+ }
7142
+ });
7143
+ const length = Math.max(count, ...results.map((r) => r ? r.end + r.points.length : 0));
7144
+ const labels = Array.from({ length }, (_, i) => ordinalLabel(min + i * step, grain));
7145
+ const predicted = results.map((r) => {
7146
+ const out = Array(length).fill(null);
7147
+ r?.points.forEach((p, k) => out[r.end + k] = p);
7148
+ return out;
7149
+ });
7150
+ return {
7151
+ labels,
7152
+ actualCount: count,
7153
+ names,
7154
+ otherName: other ? other.name : null,
7155
+ actual: actual.map((list) => [...list, ...Array(length - count).fill(null)]),
7156
+ predicted,
7157
+ fittedEnd: results.map((r) => r ? r.end - 1 : -1),
7158
+ results
7159
+ };
7160
+ }
7161
+ var FORECAST_ID = "__forecast:";
7162
+ var BAND_BASE_ID = "__forecast-band-base:";
7163
+ var BAND_RANGE_ID = "__forecast-band-range:";
7164
+ function ForecastChartImpl({
7165
+ data = NO_ROWS14,
7166
+ x,
7167
+ y,
7168
+ series: seriesKey,
7169
+ period,
7170
+ forecast: forecast2,
7171
+ forecastLabel,
7172
+ forecastSource,
7173
+ forecastNote,
7174
+ colors,
7175
+ labels,
7176
+ valueFormatter,
7177
+ format,
7178
+ title,
7179
+ summary,
7180
+ actions,
7181
+ fileName,
7182
+ isLoading,
7183
+ emptyState,
7184
+ height,
7185
+ chartOptions,
7186
+ showLegend = true,
7187
+ showTooltip = true,
7188
+ showGridLines = true,
7189
+ showXAxis = true,
7190
+ showYAxis = true,
7191
+ yAxisWidth,
7192
+ minValue,
7193
+ maxValue,
7194
+ fitToData = false,
7195
+ xAxisLabel,
7196
+ yAxisLabel,
7197
+ ariaLabel,
7198
+ className,
7199
+ ...rest
7200
+ }, ref) {
7201
+ const { ref: frameRef, surface, width: plotWidth, tokens } = useChartSurface(ref);
7202
+ const locale = useLocale();
7203
+ const t = TEXT19[uiLanguage(locale)];
7204
+ const fcLabel = forecastLabel ?? t.forecast;
7205
+ const tooltipFormatter = useMemo(() => resolveValueFormatter(valueFormatter, format, locale), [valueFormatter, format, locale]);
7206
+ const axisFormatter = useMemo(() => resolveAxisFormatter(valueFormatter, format, locale), [valueFormatter, format, locale]);
7207
+ const axisTicks = useMemo(() => resolveAxisTickFormatter(valueFormatter, format, locale), [valueFormatter, format, locale]);
7208
+ const singleName = labels?.[y] ?? y ?? "";
7209
+ const model = useMemo(
7210
+ () => buildForecastModel(data, x, y, seriesKey, period, forecast2, singleName, otherSeriesLabel(locale)),
7211
+ // forecast はオブジェクトリテラルで渡されることが多いので中身で比べる
7212
+ [data, x, y, seriesKey, period, JSON.stringify(forecast2 ?? null), singleName, locale]
7213
+ );
7214
+ const palette = useMemo(
7215
+ () => model ? resolveChartColors(seriesColorsFor(model.names, colors, CHART_OTHER_COLOR, model.otherName), tokens) : [],
7216
+ [model, colors, tokens]
7217
+ );
7218
+ const option = useMemo(() => {
7219
+ if (!model) return null;
7220
+ const index = "__period";
7221
+ const rows = model.labels.map((label3, i) => {
7222
+ const row = { [index]: label3 };
7223
+ model.names.forEach((name, c) => row[name] = model.actual[c][i]);
7224
+ return row;
7225
+ });
7226
+ const base = buildCartesianOption({
7227
+ kind: "line",
7228
+ data: rows,
7229
+ index,
7230
+ categories: model.names,
7231
+ palette,
7232
+ valueFormatter: tooltipFormatter,
7233
+ axisFormatter,
7234
+ axisTicks,
7235
+ curveType: "linear",
7236
+ connectNulls: false,
7237
+ showLegend,
7238
+ showTooltip,
7239
+ showGridLines,
7240
+ showXAxis,
7241
+ showYAxis,
7242
+ startEndOnly: false,
7243
+ yAxisWidth,
7244
+ minValue,
7245
+ maxValue,
7246
+ fitToData,
7247
+ allowDecimals: true,
7248
+ xAxisLabel,
7249
+ yAxisLabel,
7250
+ showDots: false,
7251
+ active: null,
7252
+ highlight: "none",
7253
+ reserveActions: Boolean(actions),
7254
+ guideColor: tokens?.mutedForeground ?? void 0,
7255
+ labelBackground: surface,
7256
+ labelColor: tokens?.foreground ?? void 0,
7257
+ plotWidth,
7258
+ plotHeight: typeof height === "number" ? height : void 0,
7259
+ locale
7260
+ });
7261
+ const bandAlpha = isDarkSurface(surface) ? FORECAST_BAND_ALPHA_DARK : FORECAST_BAND_ALPHA_LIGHT;
7262
+ const helper = {
7263
+ type: "line",
7264
+ symbol: "none",
7265
+ showSymbol: false,
7266
+ connectNulls: true,
7267
+ legendHoverLink: false,
7268
+ emphasis: { disabled: true }
7269
+ };
7270
+ const lines = [];
7271
+ const bands = [];
7272
+ model.names.forEach((name, c) => {
7273
+ const start = model.fittedEnd[c];
7274
+ if (start < 0) return;
7275
+ const color = palette[c] || void 0;
7276
+ const predicted = model.predicted[c];
7277
+ const startValue = model.actual[c][start] ?? null;
7278
+ const line = predicted.map((p, i) => i === start ? startValue : p?.value ?? null);
7279
+ const lower = predicted.map((p, i) => i === start ? startValue : p ? Math.min(p.lower, p.upper) : null);
7280
+ const range = predicted.map((p, i) => i === start ? startValue === null ? null : 0 : p ? Math.abs(p.upper - p.lower) : null);
7281
+ lines.push({
7282
+ ...helper,
7283
+ id: FORECAST_ID + name,
7284
+ name: fcLabel,
7285
+ z: 3,
7286
+ lineStyle: { width: 2, type: FORECAST_LINE_DASH, color, opacity: 0.7 },
7287
+ itemStyle: { color, opacity: 0.7 },
7288
+ data: line
7289
+ });
7290
+ bands.push(
7291
+ { ...helper, id: BAND_BASE_ID + name, name: fcLabel, silent: true, z: 1, stack: BAND_BASE_ID + name, tooltip: { show: false }, lineStyle: { opacity: 0 }, itemStyle: { color }, data: lower },
7292
+ {
7293
+ ...helper,
7294
+ id: BAND_RANGE_ID + name,
7295
+ name: fcLabel,
7296
+ silent: true,
7297
+ z: 1,
7298
+ stack: BAND_BASE_ID + name,
7299
+ tooltip: { show: false },
7300
+ lineStyle: { opacity: 0 },
7301
+ itemStyle: { color },
7302
+ areaStyle: color ? { color: withAlpha(color, bandAlpha), opacity: 1 } : { opacity: bandAlpha },
7303
+ data: range
7304
+ }
7305
+ );
7306
+ });
7307
+ if (lines.length === 0) return mergeChartOptions(base, chartOptions);
7308
+ const boundary = Math.max(...model.fittedEnd);
7309
+ const total = model.labels.length;
7310
+ const span = plotWidth && total > 1 ? (plotWidth - 64) / (total - 1) * (total - 1 - boundary) : null;
7311
+ const boundaryText = forecastBoundaryText(fcLabel);
7312
+ lines[0] = {
7313
+ ...lines[0],
7314
+ markLine: {
7315
+ silent: true,
7316
+ symbol: "none",
7317
+ animation: false,
7318
+ data: [
7319
+ {
7320
+ name: "forecast-boundary",
7321
+ xAxis: boundary,
7322
+ lineStyle: { type: "dashed", width: 1, color: tokens?.mutedForeground ?? void 0 },
7323
+ label: {
7324
+ show: true,
7325
+ position: "end",
7326
+ rotate: 0,
7327
+ align: forecastLabelAlign(span, boundaryText),
7328
+ verticalAlign: "bottom",
7329
+ distance: 4,
7330
+ fontSize: 12,
7331
+ color: tokens?.mutedForeground ?? void 0,
7332
+ formatter: boundaryText
7333
+ }
7334
+ }
7335
+ ]
7336
+ }
7337
+ };
7338
+ const legend = base.legend;
7339
+ const grid = base.grid;
7340
+ const times = timeTooltipLabels(model.labels, locale);
7341
+ const pct = `${Math.round((forecast2?.confidence && forecast2.confidence > 0 && forecast2.confidence < 1 ? forecast2.confidence : 0.95) * 100)}%`;
7342
+ const option2 = {
7343
+ ...base,
7344
+ legend: legend ? { ...legend, data: [...legend.data ?? [], { name: fcLabel, icon: LEGEND_DASHED_ICON }] } : legend,
7345
+ grid: grid ? { ...grid, top: (grid.top ?? 12) + BOUNDARY_LABEL_ROOM } : grid,
7346
+ tooltip: {
7347
+ ...base.tooltip,
7348
+ formatter: (params) => {
7349
+ const list = Array.isArray(params) ? params : [params];
7350
+ const i = list[0]?.dataIndex ?? 0;
7351
+ const shown = new Set(list.map((p) => p.seriesId ?? ""));
7352
+ const visible = (id) => list.length === 0 || shown.has(id);
7353
+ const rows2 = [];
7354
+ model.names.forEach((name, c) => {
7355
+ const color = palette[c] || "currentColor";
7356
+ const value = model.actual[c][i] ?? null;
7357
+ if (value !== null && visible(`series:${name}`)) rows2.push({ name, color, value });
7358
+ const p = model.predicted[c][i];
7359
+ if (p && visible(FORECAST_ID + name)) {
7360
+ rows2.push({
7361
+ name: model.names.length > 1 ? `${name} \xB7 ${fcLabel}` : fcLabel,
7362
+ color,
7363
+ value: p.value,
7364
+ note: t.interval(pct, tooltipFormatter(Math.min(p.lower, p.upper)), tooltipFormatter(Math.max(p.lower, p.upper)))
7365
+ });
7366
+ }
7367
+ });
7368
+ return tooltipHtml(times?.[i] ?? model.labels[i] ?? "", rows2, tooltipFormatter, [], locale);
7369
+ }
7370
+ },
7371
+ series: [...base.series ?? [], ...lines, ...bands]
7372
+ };
7373
+ return mergeChartOptions(option2, chartOptions);
7374
+ }, [
7375
+ model,
7376
+ palette,
7377
+ tooltipFormatter,
7378
+ axisFormatter,
7379
+ axisTicks,
7380
+ showLegend,
7381
+ showTooltip,
7382
+ showGridLines,
7383
+ showXAxis,
7384
+ showYAxis,
7385
+ yAxisWidth,
7386
+ minValue,
7387
+ maxValue,
7388
+ fitToData,
7389
+ xAxisLabel,
7390
+ yAxisLabel,
7391
+ actions,
7392
+ tokens,
7393
+ surface,
7394
+ plotWidth,
7395
+ height,
7396
+ locale,
7397
+ fcLabel,
7398
+ forecast2?.confidence,
7399
+ t,
7400
+ chartOptions
7401
+ ]);
7402
+ const dataTable = useMemo(() => {
7403
+ if (!model) return void 0;
7404
+ const forecastColumns = model.names.map((name) => `${name} (${fcLabel})`);
7405
+ const rows = model.labels.map((label3, i) => {
7406
+ const row = { period: label3 };
7407
+ model.names.forEach((name, c) => {
7408
+ row[name] = model.actual[c][i];
7409
+ row[forecastColumns[c]] = model.predicted[c][i]?.value ?? null;
7410
+ });
7411
+ return row;
7412
+ });
7413
+ const columns = model.names.flatMap((name, c) => model.fittedEnd[c] >= 0 ? [name, forecastColumns[c]] : [name]);
7414
+ const table = chartDataTable(rows, "period", columns, tooltipFormatter, void 0, t.item);
7415
+ const times = timeTooltipLabels(model.labels, locale);
7416
+ return times ? { ...table, rows: table.rows.map((row, i) => [times[i] ?? row[0] ?? "", ...row.slice(1)]) } : table;
7417
+ }, [model, fcLabel, tooltipFormatter, t, locale]);
7418
+ const label2 = ariaLabel ?? t.chart(model?.names ?? [singleName]);
7419
+ return /* @__PURE__ */ jsx(
7420
+ ChartFrame,
7421
+ {
7422
+ ...chartRootProps(rest),
7423
+ ref: frameRef,
7424
+ title,
7425
+ summary,
7426
+ isLoading: isLoading ?? false,
7427
+ isEmpty: !model || !option,
7428
+ emptyState,
7429
+ height,
7430
+ variant: "line",
7431
+ ariaLabel: label2,
7432
+ className,
7433
+ dataTable,
7434
+ footnote: [t.note(forecastSource), forecastNote].filter(Boolean).join(" "),
7435
+ children: option && /* @__PURE__ */ jsx(EChart, { option, height: "100%", ariaLabel: label2, actions, fileName, exportHeader: { title, summary } })
7436
+ }
7437
+ );
7438
+ }
7439
+ var ForecastChart = forwardRef(ForecastChartImpl);
7440
+ ForecastChart.displayName = "ForecastChart";
7441
+
7442
+ // ../squadkit/registry/lib/color-ramp.ts
7443
+ var RAMP_FLOOR_SHARE = 0.2;
7444
+ var RAMP_LIGHT_TEXT_FROM = 0.55;
7445
+ function parseRgb2(color) {
7446
+ const hex = /^#([0-9a-f]{6})$/i.exec(color.trim());
7447
+ if (hex) {
7448
+ const n = parseInt(hex[1], 16);
7449
+ return [n >> 16 & 255, n >> 8 & 255, n & 255];
7450
+ }
7451
+ const rgb = /^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/i.exec(color.trim());
7452
+ return rgb ? [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])] : null;
7453
+ }
7454
+ var toLinear2 = (c) => (c /= 255) <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
7455
+ var fromLinear2 = (c) => Math.round(255 * Math.min(1, Math.max(0, c <= 31308e-7 ? c * 12.92 : 1.055 * c ** (1 / 2.4) - 0.055)));
7456
+ function toOklab2([r, g, b]) {
7457
+ const [lr, lg, lb] = [toLinear2(r), toLinear2(g), toLinear2(b)];
7458
+ const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
7459
+ const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
7460
+ const t = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
7461
+ return [0.2104542553 * l + 0.793617785 * m - 0.0040720468 * t, 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * t, 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * t];
7462
+ }
7463
+ function fromOklab2([L, a, b]) {
7464
+ const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
7465
+ const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
7466
+ const t = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
7467
+ const rgb = [4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * t, -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * t, -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * t];
7468
+ return `rgb(${rgb.map(fromLinear2).join(", ")})`;
7469
+ }
7470
+ function withAlpha3(color, alpha) {
7471
+ const c = parseRgb2(color);
7472
+ return c ? `rgba(${c[0]}, ${c[1]}, ${c[2]}, ${alpha})` : color;
7473
+ }
7474
+ function mixIntoSurface2(color, surface, share) {
7475
+ const c = parseRgb2(color);
7476
+ const s = surface ? parseRgb2(surface) : null;
7477
+ if (!c || !s) return withAlpha3(color, share);
7478
+ const [c1, c2] = [toOklab2(c), toOklab2(s)];
7479
+ return fromOklab2([0, 1, 2].map((i) => c2[i] + (c1[i] - c2[i]) * share));
7480
+ }
7481
+ function rampRatio(value, min, max) {
7482
+ return max > min ? Math.min(1, Math.max(0, (value - min) / (max - min))) : 1;
7483
+ }
7484
+ function rampColor(color, surface, ratio) {
7485
+ return mixIntoSurface2(color, surface, RAMP_FLOOR_SHARE + (1 - RAMP_FLOOR_SHARE) * Math.min(1, Math.max(0, ratio)));
7486
+ }
7487
+ function rampGradientCss(tone) {
7488
+ return `linear-gradient(to right, color-mix(in oklch, var(--${tone}) ${RAMP_FLOOR_SHARE * 100}%, var(--card)), var(--${tone}))`;
7489
+ }
7490
+ var GEO_MAPS = ["japan", "us", "world"];
7491
+ var geoMapKey = (name) => `squadkit-${name}`;
7492
+ var GEO_ASPECT_SCALE = { japan: 0.8, us: 0.8, world: 0.75 };
7493
+ function fold(text) {
7494
+ return text.normalize("NFKC").normalize("NFD").replace(/[̀-ͯ]/g, "").normalize("NFC").toLowerCase().trim();
7495
+ }
7496
+ var SEPARATORS = /[\s.,'’`"()_\-‐・・]/g;
7497
+ var squash = (text) => text.replace(SEPARATORS, "");
7498
+ function regionKeys(raw) {
7499
+ if (raw === null || raw === void 0) return [];
7500
+ const text = fold(String(raw));
7501
+ if (!text) return [];
7502
+ const keys = [squash(text)];
7503
+ const stripped = text.replace(/[\s-]+(prefecture|pref|ken|fu|to|state)$/, "").replace(/^(state of|the)\s+/, "").replace(/(?<=..)(都|府|県|州)$/, "");
7504
+ if (stripped !== text) keys.push(squash(stripped));
7505
+ if (/^\d{1,2}$/.test(text)) keys.push(`jp${text.padStart(2, "0")}`);
7506
+ return [...new Set(keys.filter(Boolean))];
7507
+ }
7508
+ var ALIASES = {
7509
+ "US-DC": ["District of Columbia", "Washington DC", "Washington, D.C.", "\u30EF\u30B7\u30F3\u30C8\u30F3DC", "\u30B3\u30ED\u30F3\u30D3\u30A2\u7279\u5225\u533A"],
7510
+ USA: ["United States", "US", "USA", "America", "\u7C73\u56FD", "\u30A2\u30E1\u30EA\u30AB"],
7511
+ GBR: ["UK", "Great Britain", "Britain", "\u82F1\u56FD"],
7512
+ KOR: ["Korea", "Republic of Korea", "Korea, Republic of", "\u97D3\u56FD"],
7513
+ PRK: ["DPRK", "\u5317\u671D\u9BAE"],
7514
+ CHN: ["China", "\u4E2D\u56FD"],
7515
+ TWN: ["TW", "\u53F0\u6E7E"],
7516
+ RUS: ["Russian Federation"],
7517
+ CZE: ["Czechia"],
7518
+ VNM: ["Viet Nam"],
7519
+ CIV: ["C\xF4te d'Ivoire"],
7520
+ SWZ: ["Swaziland"],
7521
+ MKD: ["Macedonia"],
7522
+ COD: ["DR Congo", "Congo-Kinshasa"],
7523
+ COG: ["Congo", "Congo-Brazzaville"]
7524
+ };
7525
+ var SKIP_NAME = /* @__PURE__ */ new Set(["US-DC"]);
7526
+ var indexCache = /* @__PURE__ */ new WeakMap();
7527
+ function buildGeoIndex(map, collection) {
7528
+ const cached = indexCache.get(collection);
7529
+ if (cached) return cached;
7530
+ const byKey = /* @__PURE__ */ new Map();
7531
+ const byCode = /* @__PURE__ */ new Map();
7532
+ const add = (key, code) => {
7533
+ for (const k of regionKeys(key)) if (!byKey.has(k)) byKey.set(k, code);
7534
+ };
7535
+ for (const { properties: p } of collection.features) {
7536
+ byCode.set(p.code, p);
7537
+ add(p.code, p.code);
7538
+ const short = p.code.split("-")[1];
7539
+ if (short) add(short, p.code);
7540
+ if (p.alpha2) add(p.alpha2, p.code);
7541
+ if (map === "japan" && short) add(String(Number(short)), p.code);
7542
+ }
7543
+ for (const { properties: p } of collection.features) {
7544
+ if (!SKIP_NAME.has(p.code)) add(p.name, p.code);
7545
+ add(p.name_ja, p.code);
7546
+ for (const alias of ALIASES[p.code] ?? []) add(alias, p.code);
7547
+ }
7548
+ const index = { map, byKey, byCode };
7549
+ indexCache.set(collection, index);
7550
+ return index;
7551
+ }
7552
+ function matchRegion(index, raw) {
7553
+ for (const key of regionKeys(raw)) {
7554
+ const code = index.byKey.get(key);
7555
+ if (code) return code;
7556
+ }
7557
+ return null;
7558
+ }
7559
+ function regionName(properties, language) {
7560
+ return language === "ja" && properties.name_ja ? properties.name_ja : properties.name.replace(/ Prefecture$/, "");
7561
+ }
7562
+ function detectGeoMap(values, loaded2) {
7563
+ const distinct = [...new Set(values.filter((v) => v !== null && v !== void 0 && String(v).trim() !== "").map(String))];
7564
+ let best = null;
7565
+ let bestCount = 0;
7566
+ for (const map of GEO_MAPS) {
7567
+ const collection = loaded2[map];
7568
+ if (!collection) continue;
7569
+ const index = buildGeoIndex(map, collection);
7570
+ const count = distinct.filter((v) => matchRegion(index, v) !== null).length;
7571
+ if (count > bestCount) [best, bestCount] = [map, count];
7572
+ }
7573
+ return best;
7574
+ }
7575
+ var BOUNDS = {
7576
+ japan: [122, 20, 154, 46.5],
7577
+ us: [-180, 18, -65, 72]
7578
+ };
7579
+ function detectBasemap(points) {
7580
+ if (points.length === 0) return "world";
7581
+ for (const map of ["japan", "us"]) {
7582
+ const [w, s, e, n] = BOUNDS[map];
7583
+ if (points.every(([lon, lat]) => lon >= w && lon <= e && lat >= s && lat <= n)) return map;
7584
+ }
7585
+ return "world";
7586
+ }
7587
+ var US_INSETS = [
7588
+ // Alaska (the Aleutians stop short of the antimeridian in this data).
7589
+ { box: [-180, 51, -129.5, 72], center: [-149, 63], to: [-117, 25.5], scale: 0.35 },
7590
+ // Hawaii.
7591
+ { box: [-161, 18.5, -154.5, 22.5], center: [-157.5, 20.5], to: [-106, 25.5], scale: 1 }
7592
+ ];
7593
+ function insetUsPoint(lon, lat) {
7594
+ for (const inset of US_INSETS) {
7595
+ const [w, s, e, n] = inset.box;
7596
+ if (lon >= w && lon <= e && lat >= s && lat <= n) {
7597
+ return [inset.to[0] + (lon - inset.center[0]) * inset.scale, inset.to[1] + (lat - inset.center[1]) * inset.scale];
7598
+ }
7599
+ }
7600
+ return [lon, lat];
7601
+ }
7602
+ function insetUs(collection) {
7603
+ const moveRing = (ring) => ring.map(([lon, lat]) => insetUsPoint(lon, lat));
7604
+ return {
7605
+ type: "FeatureCollection",
7606
+ features: collection.features.map((f) => {
7607
+ if (f.properties.code !== "US-AK" && f.properties.code !== "US-HI") return f;
7608
+ const g = f.geometry;
7609
+ const geometry = g.type === "Polygon" ? { type: "Polygon", coordinates: g.coordinates.map(moveRing) } : { type: "MultiPolygon", coordinates: g.coordinates.map((poly) => poly.map(moveRing)) };
7610
+ return { ...f, geometry };
7611
+ })
7612
+ };
7613
+ }
7614
+ var loading = /* @__PURE__ */ new Map();
7615
+ var loaded = /* @__PURE__ */ new Map();
7616
+ function loadedGeoMap(name) {
7617
+ return loaded.get(name);
7618
+ }
7619
+ function loadGeoMap(name) {
7620
+ const pending = loading.get(name);
7621
+ if (pending) return pending;
7622
+ const promise = (name === "japan" ? import('./geo-japan-7BMSOJRX.js').then((m) => m.JAPAN_PREFECTURES) : name === "us" ? import('./geo-us-GVOG7VXH.js').then((m) => insetUs(m.US_STATES)) : import('./geo-world-35HL56UU.js').then((m) => m.WORLD_COUNTRIES)).then((collection) => {
7623
+ registerMap(geoMapKey(name), collection);
7624
+ loaded.set(name, collection);
7625
+ return collection;
7626
+ });
7627
+ promise.catch(() => loading.delete(name));
7628
+ loading.set(name, promise);
7629
+ return promise;
7630
+ }
7631
+ async function loadGeoMapFor(values, map) {
7632
+ if (map !== "auto") return { name: map, collection: await loadGeoMap(map) };
7633
+ const distinct = [...new Set(values.filter((v) => v !== null && v !== void 0).map(String))];
7634
+ const [japan, us] = await Promise.all([loadGeoMap("japan"), loadGeoMap("us")]);
7635
+ const local = { japan, us };
7636
+ const pick = detectGeoMap(distinct, local);
7637
+ if (pick) {
7638
+ const index = buildGeoIndex(pick, local[pick]);
7639
+ const matched = distinct.filter((v) => matchRegion(index, v) !== null).length;
7640
+ if (matched * 2 >= distinct.length) return { name: pick, collection: local[pick] };
7641
+ }
7642
+ const world = await loadGeoMap("world");
7643
+ const name = detectGeoMap(distinct, { ...local, world }) ?? "world";
7644
+ return { name, collection: name === "world" ? world : local[name] };
7645
+ }
7646
+ function useGeoMap(values, map) {
7647
+ const key = useMemo(() => [...new Set(values.map((v) => v === null || v === void 0 ? "" : String(v)))].join("\0"), [values]);
7648
+ const [state, setState] = useState(null);
7649
+ const wanted = `${map}${key}`;
7650
+ useEffect(() => {
7651
+ let alive = true;
7652
+ loadGeoMapFor(key ? key.split("\0") : [], map).then(
7653
+ (result) => alive && setState({ key: wanted, result }),
7654
+ () => {
7655
+ }
7656
+ );
7657
+ return () => {
7658
+ alive = false;
7659
+ };
7660
+ }, [wanted]);
7661
+ if (state?.key === wanted) return state.result;
7662
+ const ready = map !== "auto" ? loadedGeoMap(map) : void 0;
7663
+ return ready && map !== "auto" ? { name: map, collection: ready } : null;
7664
+ }
7665
+ use([MapChart]);
7666
+ function buildFilledMapModel(config) {
7667
+ const { data, region, value, map, collection } = config;
7668
+ const language = uiLanguage(resolveLocale(config.locale));
7669
+ const index = buildGeoIndex(map, collection);
7670
+ const byCode = /* @__PURE__ */ new Map();
7671
+ const unmatched = [];
7672
+ for (const row of data) {
7673
+ if (!row || typeof row !== "object") continue;
7674
+ const code = matchRegion(index, row[region]);
7675
+ if (code) byCode.set(code, row);
7676
+ else unmatched.push(row);
7677
+ }
7678
+ let lo = Infinity;
7679
+ let hi = -Infinity;
7680
+ const regions = collection.features.map(({ properties }) => {
7681
+ const row = byCode.get(properties.code) ?? null;
7682
+ const n = row ? toChartNumber(row[value]) : null;
7683
+ if (n !== null) [lo, hi] = [Math.min(lo, n), Math.max(hi, n)];
7684
+ const key = row ? String(row[region]) : null;
7685
+ return { code: properties.code, key, name: (key !== null ? config.labels?.[key] : void 0) ?? regionName(properties, language), value: n, row };
7686
+ });
7687
+ const min = config.scale?.min ?? (Number.isFinite(lo) ? lo : 0);
7688
+ let max = config.scale?.max ?? (Number.isFinite(hi) ? hi : 1);
7689
+ if (max <= min) max = min + 1;
7690
+ const selectedCodes = config.selected && config.selected.length > 0 ? new Set(config.selected.map((v) => matchRegion(index, v)).filter((c) => c !== null)) : null;
7691
+ return { map, regions, unmatched, selectedCodes, min, max };
7692
+ }
7693
+ function escapeHtml4(text) {
7694
+ return text.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
7695
+ }
7696
+ var TEXT20 = {
7697
+ ja: {
7698
+ join: "\u30FB",
7699
+ region: "\u5730\u57DF",
7700
+ notOnMap: "(\u5730\u56F3\u306B\u7121\u3044)",
7701
+ top: (name, value) => `\u6700\u5927\u306F ${name} \u306E ${value}`,
7702
+ chart: (value) => `${value}\u306E\u5730\u56F3`
7703
+ },
7704
+ en: {
7705
+ join: ", ",
7706
+ region: "Region",
7707
+ notOnMap: " (not on the map)",
7708
+ top: (name, value) => `Highest: ${name} (${value})`,
7709
+ chart: (value) => `Map of ${value}`
7710
+ }
7711
+ };
7712
+ var textOf7 = (locale) => TEXT20[uiLanguage(resolveLocale(locale))];
7713
+ function detailText(raw, formatter) {
7714
+ if (raw === null || raw === void 0 || raw === "") return EMPTY_VALUE;
7715
+ const n = typeof raw === "string" ? null : toChartNumber(raw);
7716
+ return n === null ? String(raw) : formatter(n);
7717
+ }
7718
+ function buildFilledMapOption(config) {
7719
+ const { model, labels, valueFormatter } = config;
7720
+ const t = textOf7(config.locale);
7721
+ const label2 = (key) => labels?.[key] ?? key;
7722
+ const base = config.color || "#6366f1";
7723
+ const border = config.surfaceColor ?? "#ffffff";
7724
+ const dimmed = (r) => model.selectedCodes !== null && !model.selectedCodes.has(r.code);
7725
+ const tooltip = (r) => {
7726
+ const head = `${escapeHtml4(r.name)} ${r.value === null ? EMPTY_VALUE : escapeHtml4(valueFormatter(r.value))}`;
7727
+ const extras = (config.details ?? []).map((key) => `${escapeHtml4(label2(key))} ${escapeHtml4(detailText(r.row?.[key], valueFormatter))}`).join(t.join);
7728
+ return extras ? `${head}${t.join}${extras}` : head;
7729
+ };
7730
+ const option = {
7731
+ animation: false,
7732
+ tooltip: config.showTooltip ? { trigger: "item", formatter: (p) => tooltip(p.data.region) } : { show: false },
7733
+ series: [
7734
+ {
7735
+ id: "regions",
7736
+ type: "map",
7737
+ map: geoMapKey(model.map),
7738
+ nameProperty: "code",
7739
+ aspectScale: GEO_ASPECT_SCALE[model.map],
7740
+ roam: false,
7741
+ selectedMode: false,
7742
+ left: 8,
7743
+ right: 8,
7744
+ top: 8,
7745
+ bottom: 8,
7746
+ label: { show: false },
7747
+ itemStyle: { areaColor: config.emptyColor, borderColor: border, borderWidth: 0.6 },
7748
+ emphasis: { label: { show: false }, itemStyle: { borderColor: config.hoverColor, borderWidth: 1.2 } },
7749
+ data: model.regions.map((r) => {
7750
+ const fill = r.value === null ? config.emptyColor : rampColor(base, config.surfaceColor, rampRatio(r.value, model.min, model.max));
7751
+ const areaColor = dimmed(r) ? mixIntoSurface2(fill, config.surfaceColor, 0.3) : fill;
7752
+ return {
7753
+ name: r.code,
7754
+ value: r.value ?? void 0,
7755
+ region: r,
7756
+ itemStyle: { areaColor },
7757
+ emphasis: { itemStyle: { areaColor } }
7758
+ };
7759
+ })
7760
+ }
7761
+ ]
7762
+ };
7763
+ return config.chartOptions ? mergeDeep3(option, config.chartOptions) : option;
7764
+ }
7765
+ function mergeDeep3(a, b) {
7766
+ const out = { ...a };
7767
+ for (const [k, v] of Object.entries(b)) {
7768
+ const cur = out[k];
7769
+ out[k] = v && cur && typeof v === "object" && typeof cur === "object" && !Array.isArray(v) && !Array.isArray(cur) ? mergeDeep3(cur, v) : v;
7770
+ }
7771
+ return out;
7772
+ }
7773
+ function filledMapDataTable(model, names, formatter, details = [], labels, locale) {
7774
+ const t = textOf7(locale);
7775
+ const label2 = (key) => labels?.[key] ?? key;
7776
+ const cells = (row, value) => [
7777
+ value === null ? EMPTY_VALUE : formatter(value),
7778
+ ...details.map((key) => detailText(row?.[key], formatter))
7779
+ ];
7780
+ const rows = [
7781
+ ...model.regions.filter((r) => r.row !== null).map((r) => [r.name, ...cells(r.row, r.value)]),
7782
+ ...model.unmatched.map((row) => [`${label2(String(row[names.region] ?? EMPTY_VALUE))}${t.notOnMap}`, ...cells(row, toChartNumber(row[names.value]))])
7783
+ ];
7784
+ return { columns: [labels?.[names.region] ?? t.region, label2(names.value), ...details.map(label2)], rows };
7785
+ }
7786
+ function buildFilledMapSummary(model, formatter, title, locale) {
7787
+ let best;
7788
+ for (const r of model.regions) if (r.value !== null && (!best || r.value > best.value)) best = r;
7789
+ if (!best) return void 0;
7790
+ return `${title ? `${title}: ` : ""}${textOf7(locale).top(best.name, formatter(best.value))}`;
7791
+ }
7792
+ function colorsFor2(tokens, tone) {
7793
+ const [color = "", empty = "", hover = ""] = resolveChartColors([tone, "var(--muted)", "var(--foreground)"], tokens);
7794
+ return { color, empty, hover };
7795
+ }
7796
+ var NO_ROWS15 = [];
7797
+ function FilledMapImpl({
7798
+ data = NO_ROWS15,
7799
+ region,
7800
+ value,
7801
+ map = "auto",
7802
+ labels,
7803
+ format,
7804
+ valueFormatter,
7805
+ tone = "chart-1",
7806
+ scale,
7807
+ showLegend = true,
7808
+ showTooltip = true,
7809
+ details,
7810
+ selected,
7811
+ onSelect,
7812
+ title,
7813
+ summary,
7814
+ isLoading,
7815
+ emptyState,
7816
+ height,
7817
+ chartOptions,
7818
+ actions,
7819
+ fileName,
7820
+ className,
7821
+ ariaLabel,
7822
+ ...rest
7823
+ }, ref) {
7824
+ const rows = Array.isArray(data) ? data : NO_ROWS15;
7825
+ const { ref: frameRef, surface, tokens } = useChartSurface(ref);
7826
+ const locale = useLocale();
7827
+ const formatter = useMemo(() => resolveValueFormatter(valueFormatter, format ?? "number", locale), [valueFormatter, format, locale]);
7828
+ const regionValues = useMemo(() => rows.map((row) => row && typeof row === "object" ? row[region] : void 0), [rows, region]);
7829
+ const geo = useGeoMap(regionValues, map);
7830
+ const model = useMemo(
7831
+ () => geo ? buildFilledMapModel({ data: rows, region, value, map: geo.name, collection: geo.collection, scale, labels, selected, locale }) : null,
7832
+ [geo, rows, region, value, scale?.min, scale?.max, labels, selected, locale]
7833
+ );
7834
+ const colors = useMemo(() => colorsFor2(tokens, tone), [tokens, tone]);
7835
+ const option = useMemo(
7836
+ () => model ? buildFilledMapOption({
7837
+ model,
7838
+ color: colors.color,
7839
+ surfaceColor: surface ?? void 0,
7840
+ emptyColor: colors.empty || "rgba(128, 128, 128, 0.15)",
7841
+ hoverColor: colors.hover || "#000000",
7842
+ labels,
7843
+ valueFormatter: formatter,
7844
+ details,
7845
+ showTooltip,
7846
+ chartOptions,
7847
+ locale
7848
+ }) : null,
7849
+ [model, value, colors, surface, labels, formatter, details, showTooltip, chartOptions, locale]
7850
+ );
7851
+ const onEvents = useMemo(
7852
+ () => onSelect ? {
7853
+ click: (params) => {
7854
+ const r = params.data?.region;
7855
+ if (r?.row) onSelect(String(r.row[region]), r.row);
7856
+ }
7857
+ } : void 0,
7858
+ [onSelect, region]
7859
+ );
7860
+ const dataTable = useMemo(
7861
+ () => model ? filledMapDataTable(model, { region, value }, formatter, details, labels, locale) : void 0,
7862
+ [model, region, value, formatter, details, labels, locale]
7863
+ );
7864
+ const resolvedSummary = useMemo(
7865
+ () => summary === "auto" ? model ? buildFilledMapSummary(model, formatter, title, locale) : void 0 : summary,
7866
+ [summary, model, formatter, title, locale]
7867
+ );
7868
+ const label2 = ariaLabel ?? textOf7(locale).chart(labels?.[value] ?? value ?? "");
7869
+ const isEmpty = rows.length === 0;
7870
+ return /* @__PURE__ */ jsx(
7871
+ ChartFrame,
7872
+ {
7873
+ ...chartRootProps(rest),
7874
+ ref: frameRef,
7875
+ title,
7876
+ summary: resolvedSummary,
7877
+ isLoading: (isLoading ?? false) || !isEmpty && !option,
7878
+ isEmpty,
7879
+ emptyState,
7880
+ height,
7881
+ variant: "bar",
7882
+ ariaLabel: label2,
7883
+ className,
7884
+ dataTable,
7885
+ children: /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full flex-col gap-2", children: [
7886
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: option && /* @__PURE__ */ jsx(EChart, { option, onEvents, height: "100%", ariaLabel: label2, actions, fileName, exportHeader: { title, summary: resolvedSummary } }) }),
7887
+ showLegend && model ? /* @__PURE__ */ jsx(RampLegend, { min: model.min, max: model.max, formatter, tone }) : null
7888
+ ] })
7889
+ }
7890
+ );
7891
+ }
7892
+ function RampLegend({ min, max, formatter, tone }) {
7893
+ return /* @__PURE__ */ jsx("div", { "data-slot": "filled-map-legend", className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground tabular-nums", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
7894
+ /* @__PURE__ */ jsx("span", { children: formatter(min) }),
7895
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "h-2 w-24 rounded-sm", style: { backgroundImage: rampGradientCss(tone) } }),
7896
+ /* @__PURE__ */ jsx("span", { children: formatter(max) })
7897
+ ] }) });
7898
+ }
7899
+ var FilledMap = forwardRef(FilledMapImpl);
7900
+ FilledMap.displayName = "FilledMap";
7901
+ use([ScatterChart$1, GeoComponent]);
7902
+ var SYMBOL_MAP_MIN_SIZE = 6;
7903
+ var SYMBOL_MAP_MAX_SIZE = 36;
7904
+ var SYMBOL_MAP_DEFAULT_SIZE = 10;
7905
+ var MAX_CATEGORIES = 5;
7906
+ var isBlank = (v) => v === null || v === void 0 || typeof v === "string" && v.trim() === "";
7907
+ function buildSymbolMapModel(config) {
7908
+ const { data, latitude, longitude, name, size, color } = config;
7909
+ const rows = data.filter((row) => row && typeof row === "object");
7910
+ const colorValues = color ? rows.map((row) => row[color]).filter((v) => !isBlank(v)) : [];
7911
+ const colorMode = !color || colorValues.length === 0 ? "none" : colorValues.every((v) => toChartNumber(v) !== null) ? "numeric" : "category";
7912
+ const points = [];
7913
+ const unplotted = [];
7914
+ const categories = [];
7915
+ let [lo, hi, sizeMax] = [Infinity, -Infinity, 0];
7916
+ for (const row of rows) {
7917
+ const lat = toChartNumber(row[latitude]);
7918
+ const lon = toChartNumber(row[longitude]);
7919
+ if (lat === null || lon === null || Math.abs(lat) > 90 || Math.abs(lon) > 180) {
7920
+ unplotted.push(row);
7921
+ continue;
7922
+ }
7923
+ const s = size ? toChartNumber(row[size]) : null;
7924
+ if (s !== null && s > sizeMax) sizeMax = s;
7925
+ const shade = colorMode === "numeric" ? toChartNumber(row[color]) : null;
7926
+ if (shade !== null) [lo, hi] = [Math.min(lo, shade), Math.max(hi, shade)];
7927
+ const category = colorMode === "category" && !isBlank(row[color]) ? String(row[color]) : null;
7928
+ if (category !== null && !categories.includes(category)) categories.push(category);
7929
+ points.push({ lon, lat, name: name && !isBlank(row[name]) ? String(row[name]) : null, size: s, shade, category, row });
7930
+ }
7931
+ const shadeMin = Number.isFinite(lo) ? lo : 0;
7932
+ let shadeMax = Number.isFinite(hi) ? hi : 1;
7933
+ if (shadeMax <= shadeMin) shadeMax = shadeMin + 1;
7934
+ return { points, unplotted, colorMode, categories, shadeMin, shadeMax, sizeMax };
7935
+ }
7936
+ function symbolMapSize(value, max, hasSize) {
7937
+ if (!hasSize) return SYMBOL_MAP_DEFAULT_SIZE;
7938
+ if (value === null || value <= 0 || !(max > 0)) return SYMBOL_MAP_MIN_SIZE;
7939
+ return SYMBOL_MAP_MIN_SIZE + (SYMBOL_MAP_MAX_SIZE - SYMBOL_MAP_MIN_SIZE) * Math.sqrt(Math.min(1, value / max));
7940
+ }
7941
+ function escapeHtml5(text) {
7942
+ return text.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
7943
+ }
7944
+ var TEXT21 = {
7945
+ ja: {
7946
+ join: "\u30FB",
7947
+ point: "\u5730\u70B9",
7948
+ latitude: "\u7DEF\u5EA6",
7949
+ longitude: "\u7D4C\u5EA6",
7950
+ notPlotted: "(\u5EA7\u6A19\u306A\u3057)",
7951
+ chart: "\u5730\u70B9\u306E\u5730\u56F3"
7952
+ },
7953
+ en: {
7954
+ join: ", ",
7955
+ point: "Point",
7956
+ latitude: "Latitude",
7957
+ longitude: "Longitude",
7958
+ notPlotted: " (no coordinates)",
7959
+ chart: "Map of points"
7960
+ }
7961
+ };
7962
+ var textOf8 = (locale) => TEXT21[uiLanguage(resolveLocale(locale))];
7963
+ var coordText = (n) => n === null ? EMPTY_VALUE : n.toFixed(4).replace(/\.?0+$/, "");
7964
+ function buildSymbolMapOption(config) {
7965
+ const { model, labels, valueFormatter, sizeFormatter } = config;
7966
+ const label2 = (key) => labels?.[key] ?? key;
7967
+ const base = config.toneColor || "#6366f1";
7968
+ const selected = config.selected && config.selected.length > 0 ? new Set(config.selected.map(String)) : null;
7969
+ const fillOf = (p) => {
7970
+ if (model.colorMode === "numeric") return p.shade === null ? config.landColor : rampColor(base, config.surfaceColor, rampRatio(p.shade, model.shadeMin, model.shadeMax));
7971
+ if (model.colorMode === "category") {
7972
+ const at = p.category === null ? -1 : model.categories.indexOf(p.category);
7973
+ return at === -1 ? config.categoryColors[MAX_CATEGORIES] ?? base : config.categoryColors[Math.min(at, MAX_CATEGORIES)] ?? base;
7974
+ }
7975
+ return base;
7976
+ };
7977
+ const tooltip = (p) => {
7978
+ const lines = [escapeHtml5(p.name ?? `${coordText(p.lat)}, ${coordText(p.lon)}`)];
7979
+ if (config.size) lines.push(`${escapeHtml5(label2(config.size))} ${p.size === null ? EMPTY_VALUE : escapeHtml5(sizeFormatter(p.size))}`);
7980
+ if (config.color && config.color !== config.size)
7981
+ lines.push(`${escapeHtml5(label2(config.color))} ${model.colorMode === "numeric" ? p.shade === null ? EMPTY_VALUE : escapeHtml5(valueFormatter(p.shade)) : escapeHtml5(label2(p.category ?? EMPTY_VALUE))}`);
7982
+ for (const key of config.details ?? []) lines.push(`${escapeHtml5(label2(key))} ${escapeHtml5(cellText(p.row[key], valueFormatter))}`);
7983
+ return lines.join("<br/>");
7984
+ };
7985
+ const order = [...model.points].sort((a, b) => (b.size ?? 0) - (a.size ?? 0));
7986
+ const option = {
7987
+ animation: false,
7988
+ tooltip: config.showTooltip ? { trigger: "item", formatter: (p) => tooltip(p.data.point) } : { show: false },
7989
+ geo: {
7990
+ map: geoMapKey(config.basemap),
7991
+ silent: true,
7992
+ roam: false,
7993
+ aspectScale: GEO_ASPECT_SCALE[config.basemap],
7994
+ left: 8,
7995
+ right: 8,
7996
+ top: 8,
7997
+ bottom: 8,
7998
+ label: { show: false },
7999
+ itemStyle: { areaColor: config.landColor, borderColor: config.borderColor, borderWidth: 0.6 },
8000
+ emphasis: { disabled: true }
8001
+ },
8002
+ series: [
8003
+ {
8004
+ id: "points",
8005
+ type: "scatter",
8006
+ coordinateSystem: "geo",
8007
+ data: order.map((p) => {
8008
+ const [x, y] = config.basemap === "us" ? insetUsPoint(p.lon, p.lat) : [p.lon, p.lat];
8009
+ const fill = fillOf(p);
8010
+ const dim = selected !== null && !(p.name !== null && selected.has(p.name));
8011
+ return {
8012
+ value: [x, y],
8013
+ point: p,
8014
+ symbolSize: symbolMapSize(p.size, model.sizeMax, !!config.size),
8015
+ itemStyle: { color: dim ? mixIntoSurface2(fill, config.surfaceColor, 0.3) : fill, opacity: dim ? 1 : 0.85 }
8016
+ };
8017
+ }),
8018
+ itemStyle: { borderColor: config.surfaceColor ?? "#ffffff", borderWidth: 1 },
8019
+ emphasis: { scale: false, itemStyle: { opacity: 1, borderColor: config.borderColor, borderWidth: 1.5 } }
8020
+ }
8021
+ ]
8022
+ };
8023
+ return config.chartOptions ? mergeDeep4(option, config.chartOptions) : option;
8024
+ }
8025
+ function cellText(raw, formatter) {
8026
+ if (isBlank(raw)) return EMPTY_VALUE;
8027
+ const n = typeof raw === "string" ? null : toChartNumber(raw);
8028
+ return n === null ? String(raw) : formatter(n);
8029
+ }
8030
+ function mergeDeep4(a, b) {
8031
+ const out = { ...a };
8032
+ for (const [k, v] of Object.entries(b)) {
8033
+ const cur = out[k];
8034
+ out[k] = v && cur && typeof v === "object" && typeof cur === "object" && !Array.isArray(v) && !Array.isArray(cur) ? mergeDeep4(cur, v) : v;
8035
+ }
8036
+ return out;
8037
+ }
8038
+ function symbolMapDataTable(model, names, formatters, details = [], labels, locale) {
8039
+ const t = textOf8(locale);
8040
+ const label2 = (key) => labels?.[key] ?? key;
8041
+ const extra = [names.size, names.color].filter((k) => !!k && k !== names.latitude && k !== names.longitude);
8042
+ const keys = [.../* @__PURE__ */ new Set([...extra, ...details])];
8043
+ const rowCells = (row) => keys.map((key) => key === names.size ? cellText(row[key], formatters.size) : key === names.color && model.colorMode === "category" ? isBlank(row[key]) ? EMPTY_VALUE : label2(String(row[key])) : cellText(row[key], formatters.value));
8044
+ const nameOf = (row) => names.name && !isBlank(row[names.name]) ? String(row[names.name]) : EMPTY_VALUE;
8045
+ const rows = [
8046
+ ...model.points.map((p) => [nameOf(p.row), coordText(p.lat), coordText(p.lon), ...rowCells(p.row)]),
8047
+ ...model.unplotted.map((row) => [
8048
+ `${nameOf(row)}${t.notPlotted}`,
8049
+ coordText(toChartNumber(row[names.latitude])),
8050
+ coordText(toChartNumber(row[names.longitude])),
8051
+ ...rowCells(row)
8052
+ ])
8053
+ ];
8054
+ return {
8055
+ columns: [names.name ? label2(names.name) : t.point, labels?.[names.latitude] ?? t.latitude, labels?.[names.longitude] ?? t.longitude, ...keys.map(label2)],
8056
+ rows
8057
+ };
8058
+ }
8059
+ function colorsFor3(tokens, tone) {
8060
+ const resolved = resolveChartColors([tone, "var(--muted)", "var(--border)", ...DEFAULT_CHART_COLORS, CHART_OTHER_COLOR], tokens);
8061
+ const [toneColor = "", land = "", border = ""] = resolved;
8062
+ return { toneColor, land, border, categories: resolved.slice(3) };
8063
+ }
8064
+ var NO_ROWS16 = [];
8065
+ function SymbolMapImpl({
8066
+ data = NO_ROWS16,
8067
+ latitude,
8068
+ longitude,
8069
+ name,
8070
+ size,
8071
+ color,
8072
+ basemap = "auto",
8073
+ labels,
8074
+ format,
8075
+ sizeFormat,
8076
+ tone = "chart-1",
8077
+ showLegend = true,
8078
+ showTooltip = true,
8079
+ details,
8080
+ selected,
8081
+ onSelect,
8082
+ title,
8083
+ summary,
8084
+ isLoading,
8085
+ emptyState,
8086
+ height,
8087
+ chartOptions,
8088
+ actions,
8089
+ fileName,
8090
+ className,
8091
+ ariaLabel,
8092
+ ...rest
8093
+ }, ref) {
8094
+ const rows = Array.isArray(data) ? data : NO_ROWS16;
8095
+ const { ref: frameRef, surface, tokens } = useChartSurface(ref);
8096
+ const locale = useLocale();
8097
+ const formatter = useMemo(() => resolveValueFormatter(void 0, format ?? "number", locale), [format, locale]);
8098
+ const sizeFormatter = useMemo(() => resolveValueFormatter(void 0, sizeFormat ?? format ?? "number", locale), [sizeFormat, format, locale]);
8099
+ const model = useMemo(() => buildSymbolMapModel({ data: rows, latitude, longitude, name, size, color }), [rows, latitude, longitude, name, size, color]);
8100
+ const resolvedBasemap = useMemo(
8101
+ () => basemap === "auto" ? detectBasemap(model.points.map((p) => [p.lon, p.lat])) : basemap,
8102
+ [basemap, model]
8103
+ );
8104
+ const geo = useGeoMap(NO_ROWS16, resolvedBasemap);
8105
+ const colors = useMemo(() => colorsFor3(tokens, tone), [tokens, tone]);
8106
+ const option = useMemo(
8107
+ () => geo ? buildSymbolMapOption({
8108
+ model,
8109
+ basemap: geo.name,
8110
+ size,
8111
+ color,
8112
+ toneColor: colors.toneColor,
8113
+ categoryColors: colors.categories,
8114
+ surfaceColor: surface ?? void 0,
8115
+ landColor: colors.land || "rgba(128, 128, 128, 0.15)",
8116
+ borderColor: colors.border || "rgba(128, 128, 128, 0.4)",
8117
+ labels,
8118
+ valueFormatter: formatter,
8119
+ sizeFormatter,
8120
+ details,
8121
+ selected,
8122
+ showTooltip,
8123
+ chartOptions}) : null,
8124
+ [geo, model, size, color, colors, surface, labels, formatter, sizeFormatter, details, selected, showTooltip, chartOptions, locale]
8125
+ );
8126
+ const onEvents = useMemo(
8127
+ () => onSelect ? {
8128
+ click: (params) => {
8129
+ const p = params.data?.point;
8130
+ if (p) onSelect(p.row);
8131
+ }
8132
+ } : void 0,
8133
+ [onSelect]
8134
+ );
8135
+ const dataTable = useMemo(
8136
+ () => symbolMapDataTable(model, { latitude, longitude, name, size, color }, { value: formatter, size: sizeFormatter }, details, labels, locale),
8137
+ [model, latitude, longitude, name, size, color, formatter, sizeFormatter, details, labels, locale]
8138
+ );
8139
+ const label2 = ariaLabel ?? textOf8(locale).chart;
8140
+ const isEmpty = rows.length === 0;
8141
+ const legendCategories = model.categories.length > MAX_CATEGORIES ? [...model.categories.slice(0, MAX_CATEGORIES), otherSeriesLabel(locale)] : model.categories;
8142
+ const legend = !showLegend ? null : model.colorMode === "numeric" ? /* @__PURE__ */ jsx("div", { "data-slot": "symbol-map-legend", className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground tabular-nums", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
8143
+ color && /* @__PURE__ */ jsx("span", { children: labels?.[color] ?? color }),
8144
+ /* @__PURE__ */ jsx("span", { children: formatter(model.shadeMin) }),
8145
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "h-2 w-24 rounded-sm", style: { backgroundImage: rampGradientCss(tone) } }),
8146
+ /* @__PURE__ */ jsx("span", { children: formatter(model.shadeMax) })
8147
+ ] }) }) : model.colorMode === "category" ? /* @__PURE__ */ jsx(Legend, { categories: legendCategories, labels }) : null;
8148
+ return /* @__PURE__ */ jsx(
8149
+ ChartFrame,
8150
+ {
8151
+ ...chartRootProps(rest),
8152
+ ref: frameRef,
8153
+ title,
8154
+ summary,
8155
+ isLoading: (isLoading ?? false) || !isEmpty && !option,
8156
+ isEmpty,
8157
+ emptyState,
8158
+ height,
8159
+ variant: "bar",
8160
+ ariaLabel: label2,
8161
+ className,
8162
+ dataTable,
8163
+ children: /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full flex-col gap-2", children: [
8164
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: option && /* @__PURE__ */ jsx(EChart, { option, onEvents, height: "100%", ariaLabel: label2, actions, fileName, exportHeader: { title, summary } }) }),
8165
+ legend
8166
+ ] })
8167
+ }
8168
+ );
8169
+ }
8170
+ var SymbolMap = forwardRef(SymbolMapImpl);
8171
+ SymbolMap.displayName = "SymbolMap";
8172
+ use([TreemapChart]);
8173
+ var MAX_CATEGORIES2 = 5;
8174
+ function buildTreemapModel(config) {
8175
+ const { data, index, group, value, color } = config;
8176
+ const leaves = [];
8177
+ let [lo, hi] = [Infinity, -Infinity];
8178
+ for (const row of data) {
8179
+ if (!row || typeof row !== "object") continue;
8180
+ const shade = color ? toChartNumber(row[color]) : null;
8181
+ if (shade !== null) [lo, hi] = [Math.min(lo, shade), Math.max(hi, shade)];
8182
+ leaves.push({
8183
+ name: String(row[index] ?? EMPTY_VALUE),
8184
+ group: group ? String(row[group] ?? EMPTY_VALUE) : null,
8185
+ value: toChartNumber(row[value]),
8186
+ shade,
8187
+ row
8188
+ });
8189
+ }
8190
+ const byGroup = /* @__PURE__ */ new Map();
8191
+ for (const leaf of leaves) {
8192
+ if (leaf.value === null || leaf.value <= 0) continue;
8193
+ const list = byGroup.get(leaf.group) ?? [];
8194
+ list.push(leaf);
8195
+ byGroup.set(leaf.group, list);
8196
+ }
8197
+ const groups = [...byGroup.entries()].map(([name, list]) => ({ name, total: list.reduce((s, l) => s + l.value, 0), leaves: [...list].sort((a, b) => b.value - a.value) })).sort((a, b) => b.total - a.total);
8198
+ const total = groups.reduce((s, g) => s + g.total, 0);
8199
+ const categories = group ? groups.map((g) => g.name) : [...new Set(groups.flatMap((g) => g.leaves.map((l) => l.name)))];
8200
+ const shadeMin = Number.isFinite(lo) ? lo : 0;
8201
+ let shadeMax = Number.isFinite(hi) ? hi : 1;
8202
+ if (shadeMax <= shadeMin) shadeMax = shadeMin + 1;
8203
+ return { leaves, groups, categories, total, shadeMin, shadeMax };
8204
+ }
8205
+ function escapeHtml6(text) {
8206
+ return text.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" })[c]);
8207
+ }
8208
+ var TEXT22 = {
8209
+ ja: {
8210
+ join: "\u30FB",
8211
+ item: "\u9805\u76EE",
8212
+ group: "\u30B0\u30EB\u30FC\u30D7",
8213
+ share: "\u69CB\u6210\u6BD4",
8214
+ top: (name, share) => `\u6700\u5927\u306F ${name}(\u5168\u4F53\u306E ${share})`,
8215
+ chart: (value) => `${value}\u306E\u30C4\u30EA\u30FC\u30DE\u30C3\u30D7`
8216
+ },
8217
+ en: {
8218
+ join: ", ",
8219
+ item: "Item",
8220
+ group: "Group",
8221
+ share: "Share",
8222
+ top: (name, share) => `Largest: ${name} (${share} of the total)`,
8223
+ chart: (value) => `Treemap of ${value}`
8224
+ }
8225
+ };
8226
+ var textOf9 = (locale) => TEXT22[uiLanguage(resolveLocale(locale))];
8227
+ function buildTreemapOption(config) {
8228
+ const { model, labels, valueFormatter } = config;
8229
+ const t = textOf9(config.locale);
8230
+ const label2 = (key) => labels?.[key] ?? key;
8231
+ const surface = config.surfaceColor ?? "#ffffff";
8232
+ const base = config.toneColor || "#6366f1";
8233
+ const categoryColor = (name) => {
8234
+ const at = model.categories.indexOf(name);
8235
+ return config.categoryColors[at === -1 ? MAX_CATEGORIES2 : Math.min(at, MAX_CATEGORIES2)] ?? base;
8236
+ };
8237
+ const share = (v) => formatValue(model.total > 0 ? v / model.total : 0, "percent", config.locale);
8238
+ const leafNode = (leaf) => {
8239
+ let fill;
8240
+ let dark = true;
8241
+ if (config.colorMode === "numeric") {
8242
+ const ratio = leaf.shade === null ? 0 : rampRatio(leaf.shade, model.shadeMin, model.shadeMax);
8243
+ fill = rampColor(base, config.surfaceColor, ratio);
8244
+ dark = ratio >= RAMP_LIGHT_TEXT_FROM;
8245
+ } else fill = categoryColor(config.hasGroup ? leaf.group : leaf.name);
8246
+ return {
8247
+ name: label2(leaf.name),
8248
+ value: leaf.value,
8249
+ leaf,
8250
+ itemStyle: { color: fill },
8251
+ label: { color: dark ? surface : config.foregroundColor }
8252
+ };
8253
+ };
8254
+ const tooltip = (leaf, name, value) => {
8255
+ const head = leaf?.group != null ? `${escapeHtml6(label2(leaf.group))} / ${escapeHtml6(name)}` : escapeHtml6(name);
8256
+ const parts = [`${head} ${escapeHtml6(valueFormatter(value))}`, `${t.share} ${escapeHtml6(share(value))}`];
8257
+ if (leaf && config.colorKey && config.colorMode === "numeric")
8258
+ parts.push(`${escapeHtml6(label2(config.colorKey))} ${leaf.shade === null ? EMPTY_VALUE : escapeHtml6(config.colorFormatter(leaf.shade))}`);
8259
+ return parts.join(t.join);
8260
+ };
8261
+ const data = config.hasGroup ? model.groups.map((g) => ({
8262
+ name: label2(g.name),
8263
+ value: g.total,
8264
+ itemStyle: { color: config.colorMode === "numeric" ? config.foregroundColor : categoryColor(g.name) },
8265
+ children: g.leaves.map(leafNode)
8266
+ })) : model.groups.flatMap((g) => g.leaves.map(leafNode));
8267
+ const option = {
8268
+ animation: false,
8269
+ tooltip: config.showTooltip ? {
8270
+ trigger: "item",
8271
+ formatter: (p) => {
8272
+ const d = p;
8273
+ return tooltip(d.data?.leaf, d.name, Number(d.value));
8274
+ }
8275
+ } : { show: false },
8276
+ series: [
8277
+ {
8278
+ id: "tiles",
8279
+ type: "treemap",
8280
+ left: 0,
8281
+ right: 0,
8282
+ top: 0,
8283
+ bottom: 0,
8284
+ roam: false,
8285
+ nodeClick: false,
8286
+ breadcrumb: { show: false },
8287
+ squareRatio: 0.5 * (1 + Math.sqrt(5)),
8288
+ label: {
8289
+ show: true,
8290
+ position: "insideTopLeft",
8291
+ overflow: "truncate",
8292
+ lineHeight: 16,
8293
+ formatter: (p) => {
8294
+ const d = p;
8295
+ return `${d.name}
8296
+ ${valueFormatter(Number(d.value))}`;
8297
+ }
8298
+ },
8299
+ upperLabel: { show: config.hasGroup, height: 20, color: surface, fontWeight: 600 },
8300
+ itemStyle: { borderColor: surface, borderWidth: 1, gapWidth: 1 },
8301
+ emphasis: { disabled: true },
8302
+ levels: config.hasGroup ? [{ itemStyle: { borderColor: surface, borderWidth: 2, gapWidth: 2 } }, { itemStyle: { borderColor: surface, borderWidth: 0, gapWidth: 1 } }] : [{ itemStyle: { borderColor: surface, borderWidth: 0, gapWidth: 1 } }],
8303
+ data
8304
+ }
8305
+ ]
8306
+ };
8307
+ return config.chartOptions ? mergeDeep5(option, config.chartOptions) : option;
8308
+ }
8309
+ function mergeDeep5(a, b) {
8310
+ const out = { ...a };
8311
+ for (const [k, v] of Object.entries(b)) {
8312
+ const cur = out[k];
8313
+ out[k] = v && cur && typeof v === "object" && typeof cur === "object" && !Array.isArray(v) && !Array.isArray(cur) ? mergeDeep5(cur, v) : v;
8314
+ }
8315
+ return out;
8316
+ }
8317
+ function treemapDataTable(model, names, formatters, labels, locale) {
8318
+ const t = textOf9(locale);
8319
+ const label2 = (key) => labels?.[key] ?? key;
8320
+ const share = (v) => v === null || v <= 0 || model.total <= 0 ? EMPTY_VALUE : formatValue(v / model.total, "percent", locale);
8321
+ return {
8322
+ columns: [
8323
+ ...names.group ? [labels?.[names.group] ?? t.group] : [],
8324
+ labels?.[names.index] ?? t.item,
8325
+ label2(names.value),
8326
+ t.share,
8327
+ ...names.color && names.color !== names.value ? [label2(names.color)] : []
8328
+ ],
8329
+ rows: model.leaves.map((leaf) => [
8330
+ ...names.group ? [label2(leaf.group ?? EMPTY_VALUE)] : [],
8331
+ label2(leaf.name),
8332
+ leaf.value === null ? EMPTY_VALUE : formatters.value(leaf.value),
8333
+ share(leaf.value),
8334
+ ...names.color && names.color !== names.value ? [leaf.shade === null ? EMPTY_VALUE : formatters.color(leaf.shade)] : []
8335
+ ])
8336
+ };
8337
+ }
8338
+ function buildTreemapSummary(model, title, labels, locale) {
8339
+ let best;
8340
+ for (const g of model.groups) for (const l of g.leaves) if (!best || l.value > best.value) best = l;
8341
+ if (!best || model.total <= 0) return void 0;
8342
+ return `${title ? `${title}: ` : ""}${textOf9(locale).top(labels?.[best.name] ?? best.name, formatValue(best.value / model.total, "percent", locale))}`;
8343
+ }
8344
+ function colorsFor4(tokens, tone) {
8345
+ const resolved = resolveChartColors([tone, "var(--foreground)", ...DEFAULT_CHART_COLORS, CHART_OTHER_COLOR], tokens);
8346
+ const [toneColor = "", foreground = ""] = resolved;
8347
+ return { toneColor, foreground, categories: resolved.slice(2) };
8348
+ }
8349
+ var NO_ROWS17 = [];
8350
+ function TreemapImpl({
8351
+ data = NO_ROWS17,
8352
+ index,
8353
+ group,
8354
+ value,
8355
+ color,
8356
+ labels,
8357
+ format,
8358
+ colorFormat,
8359
+ valueFormatter,
8360
+ tone = "chart-1",
8361
+ showLegend = true,
8362
+ showTooltip = true,
8363
+ onSelect,
8364
+ title,
8365
+ summary,
8366
+ isLoading,
8367
+ emptyState,
8368
+ height,
8369
+ chartOptions,
8370
+ actions,
8371
+ fileName,
8372
+ className,
8373
+ ariaLabel,
8374
+ ...rest
8375
+ }, ref) {
8376
+ const rows = Array.isArray(data) ? data : NO_ROWS17;
8377
+ const { ref: frameRef, surface, tokens } = useChartSurface(ref);
8378
+ const locale = useLocale();
8379
+ const formatter = useMemo(() => resolveValueFormatter(valueFormatter, format ?? "number", locale), [valueFormatter, format, locale]);
8380
+ const colorFormatter = useMemo(() => resolveValueFormatter(void 0, colorFormat ?? format ?? "number", locale), [colorFormat, format, locale]);
8381
+ const model = useMemo(() => buildTreemapModel({ data: rows, index, group, value, color }), [rows, index, group, value, color]);
8382
+ const colorMode = color ? "numeric" : "category";
8383
+ const colors = useMemo(() => colorsFor4(tokens, tone), [tokens, tone]);
8384
+ const option = useMemo(
8385
+ () => buildTreemapOption({
8386
+ model,
8387
+ hasGroup: !!group,
8388
+ colorMode,
8389
+ toneColor: colors.toneColor,
8390
+ categoryColors: colors.categories,
8391
+ surfaceColor: surface ?? void 0,
8392
+ foregroundColor: colors.foreground || tokens?.foreground || "#111111",
8393
+ labels,
8394
+ valueFormatter: formatter,
8395
+ colorFormatter,
8396
+ colorKey: color,
8397
+ showTooltip,
8398
+ chartOptions,
8399
+ locale
8400
+ }),
8401
+ [model, group, colorMode, colors, surface, tokens, labels, formatter, colorFormatter, color, showTooltip, chartOptions, locale]
8402
+ );
8403
+ const onEvents = useMemo(
8404
+ () => onSelect ? {
8405
+ click: (params) => {
8406
+ const leaf = params.data?.leaf;
8407
+ if (leaf) onSelect(leaf.row);
8408
+ }
8409
+ } : void 0,
8410
+ [onSelect]
8411
+ );
8412
+ const dataTable = useMemo(
8413
+ () => treemapDataTable(model, { index, group, value, color }, { value: formatter, color: colorFormatter }, labels, locale),
8414
+ [model, index, group, value, color, formatter, colorFormatter, labels, locale]
8415
+ );
8416
+ const resolvedSummary = useMemo(
8417
+ () => summary === "auto" ? buildTreemapSummary(model, title, labels, locale) : summary,
8418
+ [summary, model, title, labels, locale]
8419
+ );
8420
+ const label2 = ariaLabel ?? textOf9(locale).chart(labels?.[value] ?? value ?? "");
8421
+ const legendCategories = model.categories.length > MAX_CATEGORIES2 ? [...model.categories.slice(0, MAX_CATEGORIES2), otherSeriesLabel(locale)] : model.categories;
8422
+ const legend = !showLegend ? null : colorMode === "numeric" ? /* @__PURE__ */ jsx("div", { "data-slot": "treemap-legend", className: "flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground tabular-nums", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
8423
+ color && /* @__PURE__ */ jsx("span", { children: labels?.[color] ?? color }),
8424
+ /* @__PURE__ */ jsx("span", { children: colorFormatter(model.shadeMin) }),
8425
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "h-2 w-24 rounded-sm", style: { backgroundImage: rampGradientCss(tone) } }),
8426
+ /* @__PURE__ */ jsx("span", { children: colorFormatter(model.shadeMax) })
8427
+ ] }) }) : group ? /* @__PURE__ */ jsx(Legend, { categories: legendCategories, labels }) : null;
8428
+ return /* @__PURE__ */ jsx(
8429
+ ChartFrame,
8430
+ {
8431
+ ...chartRootProps(rest),
8432
+ ref: frameRef,
8433
+ title,
8434
+ summary: resolvedSummary,
8435
+ isLoading: isLoading ?? false,
8436
+ isEmpty: rows.length === 0,
8437
+ emptyState,
8438
+ height,
8439
+ variant: "bar",
8440
+ ariaLabel: label2,
8441
+ className,
8442
+ dataTable,
8443
+ children: /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full flex-col gap-2", children: [
8444
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(EChart, { option, onEvents, height: "100%", ariaLabel: label2, actions, fileName, exportHeader: { title, summary: resolvedSummary } }) }),
8445
+ legend
8446
+ ] })
8447
+ }
8448
+ );
8449
+ }
8450
+ var Treemap = forwardRef(TreemapImpl);
8451
+ Treemap.displayName = "Treemap";
8452
+ var TEXT23 = {
8453
+ ja: {
8454
+ tones: { positive: "\u826F\u597D", warning: "\u6CE8\u610F", negative: "\u8981\u5BFE\u5FDC", neutral: "\u4E2D\u7ACB" },
8455
+ toneNote: (label2) => `\uFF08${label2}\uFF09`,
8456
+ selectAll: "\u5168\u3066\u9078\u629E",
8457
+ selectRow: "\u884C\u3092\u9078\u629E",
8458
+ trend: (points, first, last, min, max) => `${points}\u70B9\u306E\u63A8\u79FB\u3001${first}\u304B\u3089${last}\u3001\u6700\u5C0F${min}\u3001\u6700\u5927${max}`,
8459
+ noData: "\u30C7\u30FC\u30BF\u304C\u3042\u308A\u307E\u305B\u3093",
8460
+ pageSize: (size) => `${size}\u4EF6`,
8461
+ range: (total, start, end) => `${total}\u4EF6\u4E2D ${start} - ${end} \u3092\u8868\u793A`,
8462
+ first: "\u6700\u521D",
8463
+ previous: "\u524D\u3078",
8464
+ next: "\u6B21\u3078",
8465
+ last: "\u6700\u5F8C",
8466
+ columns: "\u8868\u793A\u3059\u308B\u5217",
8467
+ exportCsv: "CSV \u3092\u66F8\u304D\u51FA\u3059"
8468
+ },
8469
+ en: {
8470
+ tones: { positive: "Good", warning: "Caution", negative: "Needs action", neutral: "Neutral" },
8471
+ toneNote: (label2) => ` (${label2})`,
8472
+ selectAll: "Select all",
8473
+ selectRow: "Select row",
8474
+ trend: (points, first, last, min, max) => `Trend over ${points} points: ${first} to ${last}, min ${min}, max ${max}`,
8475
+ noData: "No results.",
8476
+ pageSize: (size) => `${size} / page`,
8477
+ range: (total, start, end) => `Showing ${start}\u2013${end} of ${total}`,
8478
+ first: "First",
8479
+ previous: "Previous",
8480
+ next: "Next",
8481
+ last: "Last",
8482
+ columns: "Columns",
8483
+ exportCsv: "Export CSV"
8484
+ }
8485
+ };
8486
+ var useText = () => TEXT23[uiLanguage(useLocale())];
8487
+ function resolveFormat(format, row) {
8488
+ return typeof format === "function" ? format(row) : format;
8489
+ }
8490
+ function isMissingCell(value) {
8491
+ return value === null || value === void 0 || value === "" || typeof value === "number" && Number.isNaN(value);
8492
+ }
8493
+ function formatCellText(value, row, meta, locale) {
8494
+ if (isMissingCell(value)) return meta.nullLabel ?? EMPTY_VALUE;
8495
+ if (meta.dateFormat && (value instanceof Date || typeof value === "string" || typeof value === "number")) {
8496
+ const text = formatDate(value, meta.dateFormat, locale, { timeZone: meta.timeZone });
8497
+ return text === EMPTY_VALUE ? String(value) : text;
8498
+ }
8499
+ if (typeof value !== "number") return String(value);
8500
+ return formatValue(value, resolveFormat(meta.format, row), locale);
8501
+ }
8502
+ var THRESHOLD_TONE_STYLES = {
8503
+ positive: { Icon: CircleCheck, text: "text-positive" },
8504
+ warning: { Icon: TriangleAlert, text: "text-warning" },
8505
+ negative: { Icon: CircleX, text: "text-negative" },
8506
+ neutral: { Icon: Minus, text: "text-neutral" }
8507
+ };
8508
+ function ThresholdCell({ value, text, thresholds }) {
8509
+ const t = useText();
8510
+ const tone = typeof value === "number" ? toneForValue(value, thresholds) : void 0;
8511
+ if (!tone) return /* @__PURE__ */ jsx("div", { className: "text-right tabular-nums", children: text });
8512
+ const { Icon, text: toneClass } = THRESHOLD_TONE_STYLES[tone];
8513
+ const label2 = t.tones[tone];
8514
+ return /* @__PURE__ */ jsxs(
8515
+ "div",
8516
+ {
8517
+ "data-slot": "data-table-threshold-cell",
8518
+ "data-tone": tone,
8519
+ className: cn("flex items-center justify-end gap-1.5 tabular-nums", toneClass),
8520
+ children: [
8521
+ /* @__PURE__ */ jsx(Icon, { className: "size-3.5 shrink-0", "aria-hidden": "true" }),
8522
+ /* @__PURE__ */ jsx("span", { children: text }),
8523
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: t.toneNote(label2) })
8524
+ ]
8525
+ }
8526
+ );
8527
+ }
8528
+ function useControlledState(controlledValue, defaultValue, onChange) {
8529
+ const [internalValue, setInternalValue] = useState(defaultValue);
8530
+ const isControlled = controlledValue !== void 0;
8531
+ const value = isControlled ? controlledValue : internalValue;
8532
+ const setValue = useCallback(
8533
+ (updaterOrValue) => {
8534
+ onChange?.(updaterOrValue);
8535
+ if (!isControlled) {
8536
+ setInternalValue(updaterOrValue);
8537
+ }
8538
+ },
8539
+ [onChange, isControlled]
8540
+ );
8541
+ return [value, setValue];
8542
+ }
8543
+ var DataTableContext = createContext(null);
8544
+ function useDataTable() {
8545
+ const ctx = useContext(DataTableContext);
8546
+ if (!ctx) throw new Error("useDataTable must be used inside <DataTable>");
8547
+ return ctx;
8548
+ }
8549
+ var NO_ROWS18 = [];
8550
+ function DataTable({
8551
+ columns = NO_ROWS18,
8552
+ data = NO_ROWS18,
8553
+ enableSorting = true,
8554
+ sorting: sortingProp,
8555
+ onSortingChange,
8556
+ enableFiltering = false,
8557
+ columnFilters: columnFiltersProp,
8558
+ onColumnFiltersChange,
8559
+ globalFilter: globalFilterProp,
8560
+ onGlobalFilterChange,
8561
+ enablePagination = false,
8562
+ pagination: paginationProp,
8563
+ onPaginationChange,
8564
+ pageCount,
8565
+ rowCount,
8566
+ manualFiltering,
8567
+ manualSorting,
8568
+ isLoading = false,
5467
8569
  enableRowSelection = false,
5468
8570
  rowSelection: rowSelectionProp,
5469
8571
  onRowSelectionChange,
@@ -5474,7 +8576,7 @@ function DataTable({
5474
8576
  children
5475
8577
  }) {
5476
8578
  const locale = useLocale();
5477
- const t = TEXT14[uiLanguage(locale)];
8579
+ const t = TEXT23[uiLanguage(locale)];
5478
8580
  const [sorting, setSorting] = useControlledState(sortingProp, [], onSortingChange);
5479
8581
  const [columnFilters, setColumnFilters] = useControlledState(
5480
8582
  columnFiltersProp,
@@ -5758,19 +8860,19 @@ var DataTableContent = forwardRef(
5758
8860
  }) }, headerGroup.id)) }),
5759
8861
  /* @__PURE__ */ jsx(TableBody, { children: isLoading ? Array.from({ length: enablePagination ? table.getState().pagination.pageSize : 5 }).map(
5760
8862
  (_, i) => /* @__PURE__ */ jsx(TableRow, { "data-slot": "data-table-skeleton-row", children: Array.from({ length: visibleColumnCount }).map((__, j) => /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-full" }) }, j)) }, i)
5761
- ) : table.getRowModel().rows.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx(TableRow, { "data-state": row.getIsSelected() ? "selected" : void 0, children: row.getVisibleCells().map((cell) => {
5762
- const meta = cell.column.columnDef.meta;
5763
- const comparisonColumn = comparisonOf.get(cell.column.id);
8863
+ ) : table.getRowModel().rows.length ? table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx(TableRow, { "data-state": row.getIsSelected() ? "selected" : void 0, children: row.getVisibleCells().map((cell2) => {
8864
+ const meta = cell2.column.columnDef.meta;
8865
+ const comparisonColumn = comparisonOf.get(cell2.column.id);
5764
8866
  const comparisonCell = comparisonColumn ? row.getVisibleCells().find((c) => c.column.id === comparisonColumn.id) : void 0;
5765
8867
  return /* @__PURE__ */ jsxs(
5766
8868
  TableCell,
5767
8869
  {
5768
8870
  className: columnClassName(meta, {
5769
- isComparison: comparisonIds.has(cell.column.id),
5770
- isHost: comparisonOf.has(cell.column.id)
8871
+ isComparison: comparisonIds.has(cell2.column.id),
8872
+ isHost: comparisonOf.has(cell2.column.id)
5771
8873
  }),
5772
8874
  children: [
5773
- flexRender(cell.column.columnDef.cell, cell.getContext()),
8875
+ flexRender(cell2.column.columnDef.cell, cell2.getContext()),
5774
8876
  comparisonCell && /* @__PURE__ */ jsx(
5775
8877
  "div",
5776
8878
  {
@@ -5785,7 +8887,7 @@ var DataTableContent = forwardRef(
5785
8887
  )
5786
8888
  ]
5787
8889
  },
5788
- cell.id
8890
+ cell2.id
5789
8891
  );
5790
8892
  }) }, row.id)) : /* @__PURE__ */ jsx(TableRow, { children: /* @__PURE__ */ jsx(
5791
8893
  TableCell,
@@ -5920,16 +9022,16 @@ DataTableColumnVisibility.displayName = "DataTableColumnVisibility";
5920
9022
  var SIGNED_NUMBER = /^[+-]\s?[^\d\s=+\-@]{0,3}\d[\d,.]*[^\d\s=+\-@]{0,4}$/;
5921
9023
  function toCsv(header, rows, options = {}) {
5922
9024
  const { bom = true, guardFormulas = false } = options;
5923
- const field = (cell, guard) => {
5924
- let text = cell === null || cell === void 0 ? "" : String(cell);
5925
- if (guard && typeof cell === "string" && /^[=+\-@]/.test(text) && !SIGNED_NUMBER.test(text)) {
9025
+ const field = (cell2, guard) => {
9026
+ let text = cell2 === null || cell2 === void 0 ? "" : String(cell2);
9027
+ if (guard && typeof cell2 === "string" && /^[=+\-@]/.test(text) && !SIGNED_NUMBER.test(text)) {
5926
9028
  text = `'${text}`;
5927
9029
  }
5928
9030
  return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
5929
9031
  };
5930
9032
  const lines = [
5931
9033
  header.map((h) => field(h, false)).join(","),
5932
- ...rows.map((row) => row.map((cell) => field(cell, guardFormulas)).join(","))
9034
+ ...rows.map((row) => row.map((cell2) => field(cell2, guardFormulas)).join(","))
5933
9035
  ];
5934
9036
  return (bom ? "\uFEFF" : "") + lines.join("\r\n");
5935
9037
  }
@@ -5983,7 +9085,7 @@ function DataTableExport({
5983
9085
  disabled: isLoading,
5984
9086
  children: [
5985
9087
  /* @__PURE__ */ jsx(Download, { className: "size-4", "aria-hidden": "true" }),
5986
- children ?? TEXT14[uiLanguage(locale)].exportCsv
9088
+ children ?? TEXT23[uiLanguage(locale)].exportCsv
5987
9089
  ]
5988
9090
  }
5989
9091
  );
@@ -6017,10 +9119,153 @@ function DataTablePreset({
6017
9119
  ] });
6018
9120
  }
6019
9121
  DataTablePreset.displayName = "DataTablePreset";
9122
+ var FLOOR_SHARE = 0.2;
9123
+ var DEFAULT_CHART_HEIGHT3 = 288;
9124
+ var LIGHT_TEXT_FROM = 0.55;
9125
+ var dimText = (v) => v === null || v === void 0 || v === "" ? EMPTY_VALUE : String(v);
9126
+ var pivotCellKey = (rowKey, colKey) => `${rowKey.join("")}\0${colKey.join("")}`;
9127
+ function buildPivotModel(config) {
9128
+ const { data, rows, columns, values } = config;
9129
+ const rowKeys = /* @__PURE__ */ new Map();
9130
+ const colKeys = /* @__PURE__ */ new Map();
9131
+ const cells = /* @__PURE__ */ new Map();
9132
+ for (const row of data) {
9133
+ if (!row || typeof row !== "object") continue;
9134
+ const rk = rows.map((d) => dimText(row[d]));
9135
+ const ck = columns.map((d) => dimText(row[d]));
9136
+ const rId = rk.join("");
9137
+ const cId = ck.join("");
9138
+ if (!rowKeys.has(rId)) rowKeys.set(rId, rk);
9139
+ if (!colKeys.has(cId)) colKeys.set(cId, ck);
9140
+ cells.set(pivotCellKey(rk, ck), row);
9141
+ }
9142
+ const ranges = {};
9143
+ for (const value of values) {
9144
+ let [lo, hi] = [Infinity, -Infinity];
9145
+ for (const row of cells.values()) {
9146
+ const n = toChartNumber(row[value]);
9147
+ if (n !== null) [lo, hi] = [Math.min(lo, n), Math.max(hi, n)];
9148
+ }
9149
+ ranges[value] = { min: Number.isFinite(lo) ? lo : 0, max: Number.isFinite(hi) ? hi : 0 };
9150
+ }
9151
+ return {
9152
+ rowKeys: rowKeys.size > 0 ? [...rowKeys.values()] : [rows.map(() => EMPTY_VALUE)],
9153
+ colKeys: colKeys.size > 0 ? [...colKeys.values()] : [columns.map(() => EMPTY_VALUE)],
9154
+ cells,
9155
+ ranges
9156
+ };
9157
+ }
9158
+ function pivotSpans(keys, level) {
9159
+ const out = [];
9160
+ const prefix = (k) => k.slice(0, level + 1).join("");
9161
+ keys.forEach((key, i) => {
9162
+ const last = out[out.length - 1];
9163
+ if (last && prefix(keys[last.start]) === prefix(key)) last.span++;
9164
+ else out.push({ text: key[level] ?? "", start: i, span: 1 });
9165
+ });
9166
+ return out;
9167
+ }
9168
+ var TEXT24 = {
9169
+ ja: { table: "\u30D4\u30DC\u30C3\u30C8\u8868" },
9170
+ en: { table: "Pivot table" }
9171
+ };
9172
+ var NO_ROWS19 = [];
9173
+ var asList = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : [];
9174
+ function PivotTable({
9175
+ data = NO_ROWS19,
9176
+ rows: rowDims,
9177
+ columns: colDims,
9178
+ values: valueKeys,
9179
+ formats,
9180
+ labels,
9181
+ highlight = false,
9182
+ tone = "chart-1",
9183
+ onSelect,
9184
+ title,
9185
+ summary,
9186
+ isLoading,
9187
+ emptyState,
9188
+ maxHeight = 480,
9189
+ className,
9190
+ ariaLabel
9191
+ }) {
9192
+ const locale = useLocale();
9193
+ const t = TEXT24[uiLanguage(resolveLocale(locale))];
9194
+ const dataRows = Array.isArray(data) ? data : NO_ROWS19;
9195
+ const rows = useMemo(() => asList(rowDims), [rowDims]);
9196
+ const columns = useMemo(() => asList(colDims), [colDims]);
9197
+ const values = useMemo(() => asList(valueKeys), [valueKeys]);
9198
+ const model = useMemo(() => buildPivotModel({ data: dataRows, rows, columns, values }), [dataRows, rows, columns, values]);
9199
+ const label2 = (key) => labels?.[key] ?? key;
9200
+ const R = rows.length;
9201
+ const C = columns.length;
9202
+ const V = values.length;
9203
+ const valueRow = V > 1 || C === 0;
9204
+ const H = C + (valueRow ? 1 : 0);
9205
+ const shaded = (value) => highlight === true || highlight === value;
9206
+ const isEmpty = dataRows.length === 0;
9207
+ const ready = !isLoading && !isEmpty;
9208
+ const cellStyle = (value, n) => {
9209
+ if (n === null || !shaded(value)) return void 0;
9210
+ const { min, max } = model.ranges[value] ?? { min: 0, max: 0 };
9211
+ const ratio = max > min ? Math.min(1, Math.max(0, (n - min) / (max - min))) : 1;
9212
+ const share = Math.round((FLOOR_SHARE + (1 - FLOOR_SHARE) * ratio) * 100);
9213
+ return {
9214
+ backgroundColor: `color-mix(in oklch, var(--${tone}) ${share}%, var(--card))`,
9215
+ color: ratio >= LIGHT_TEXT_FROM ? "var(--card)" : void 0
9216
+ };
9217
+ };
9218
+ const headerRows = Array.from({ length: H }, (_, h) => {
9219
+ const lead = h === H - 1 ? rows.map((d, level) => /* @__PURE__ */ jsx(TableHead, { scope: "col", className: cn(level === 0 && "pl-0"), children: label2(d) }, `dim-${d}`)) : h === 0 && R > 0 ? [/* @__PURE__ */ jsx(TableHead, { colSpan: R, rowSpan: H - 1, "aria-hidden": "true" }, "corner")] : [];
9220
+ const cells = h < C ? pivotSpans(model.colKeys, h).map((s) => /* @__PURE__ */ jsx(TableHead, { scope: "colgroup", colSpan: s.span * Math.max(1, V), className: "text-right", children: label2(s.text) }, `c-${h}-${s.start}`)) : model.colKeys.flatMap(
9221
+ (_2, ci) => values.map((v) => /* @__PURE__ */ jsx(TableHead, { scope: "col", className: "text-right", children: label2(v) }, `v-${ci}-${v}`))
9222
+ );
9223
+ return /* @__PURE__ */ jsxs(TableRow, { className: "hover:bg-transparent", children: [
9224
+ lead,
9225
+ cells
9226
+ ] }, `h-${h}`);
9227
+ });
9228
+ const rowSpans = rows.map((_, level) => new Map(pivotSpans(model.rowKeys, level).map((s) => [s.start, s.span])));
9229
+ const body = model.rowKeys.map((rk, ri) => /* @__PURE__ */ jsxs(TableRow, { children: [
9230
+ rows.map((d, level) => {
9231
+ const span = rowSpans[level].get(ri);
9232
+ if (span === void 0) return null;
9233
+ return /* @__PURE__ */ jsx(TableHead, { scope: span > 1 ? "rowgroup" : "row", rowSpan: span, className: cn("align-top font-medium text-foreground", level === 0 && "pl-0"), children: label2(rk[level] ?? "") }, `rh-${d}`);
9234
+ }),
9235
+ model.colKeys.flatMap(
9236
+ (ck, ci) => values.map((v) => {
9237
+ const row = model.cells.get(pivotCellKey(rk, ck));
9238
+ const n = row ? toChartNumber(row[v]) : null;
9239
+ const text = formatValue(n, formats?.[v] ?? "number", locale);
9240
+ return /* @__PURE__ */ jsx(TableCell, { className: "text-right tabular-nums", style: cellStyle(v, n), children: onSelect && row ? /* @__PURE__ */ jsx("button", { type: "button", className: "w-full cursor-pointer text-right tabular-nums", onClick: () => onSelect(row), children: text }) : text }, `c-${ci}-${v}`);
9241
+ })
9242
+ )
9243
+ ] }, `r-${ri}`));
9244
+ return /* @__PURE__ */ jsx(
9245
+ ChartFrame,
9246
+ {
9247
+ title,
9248
+ summary,
9249
+ isLoading: isLoading ?? false,
9250
+ isEmpty,
9251
+ emptyState,
9252
+ height: ready ? "auto" : DEFAULT_CHART_HEIGHT3,
9253
+ variant: "list",
9254
+ plotRole: "group",
9255
+ ariaLabel: ariaLabel ?? title ?? t.table,
9256
+ className,
9257
+ children: /* @__PURE__ */ jsx("div", { "data-slot": "pivot-table", className: "relative w-full overflow-auto", style: { maxHeight }, children: /* @__PURE__ */ jsxs("table", { "data-slot": "table", "aria-label": ariaLabel ?? title ?? t.table, className: "w-full caption-bottom text-sm", children: [
9258
+ /* @__PURE__ */ jsx(TableHeader, { className: "sticky top-0 z-10 bg-card", children: headerRows }),
9259
+ /* @__PURE__ */ jsx(TableBody, { children: body })
9260
+ ] }) })
9261
+ }
9262
+ );
9263
+ }
6020
9264
  var FilterBarContext = createContext(null);
6021
- var TEXT15 = {
9265
+ var TEXT25 = {
6022
9266
  ja: {
6023
9267
  filters: "\u30D5\u30A3\u30EB\u30BF\u30FC",
9268
+ addFilter: "\u30D5\u30A3\u30EB\u30BF\u30FC\u3092\u8FFD\u52A0",
6024
9269
  selected: (count) => ` (${count}\u4EF6\u9078\u629E\u4E2D)`,
6025
9270
  remove: (filter, option) => `${filter}: ${option} \u3092\u524A\u9664`,
6026
9271
  clear: "\u30AF\u30EA\u30A2",
@@ -6028,13 +9273,14 @@ var TEXT15 = {
6028
9273
  },
6029
9274
  en: {
6030
9275
  filters: "Filters",
9276
+ addFilter: "Add filter",
6031
9277
  selected: (count) => ` (${count} selected)`,
6032
9278
  remove: (filter, option) => `Remove ${filter}: ${option}`,
6033
9279
  clear: "Clear",
6034
9280
  clearAll: "Clear all filters"
6035
9281
  }
6036
9282
  };
6037
- var useText2 = () => TEXT15[uiLanguage(useLocale())];
9283
+ var useText2 = () => TEXT25[uiLanguage(useLocale())];
6038
9284
  function useFilterBar() {
6039
9285
  const ctx = useContext(FilterBarContext);
6040
9286
  if (!ctx) throw new Error("useFilterBar must be used within <FilterBar>");
@@ -6301,6 +9547,11 @@ function fiscalPeriodRange(date, {
6301
9547
  const periodStart = addMonths(startOfMonth(subMonths(date, into)), offset * length);
6302
9548
  return { from: periodStart, to: endOfMonth(addMonths(periodStart, length - 1)) };
6303
9549
  }
9550
+ function comparisonRange({ from, to }, kind) {
9551
+ if (kind === "previous-year") return { from: subYears(from, 1), to: subYears(to, 1) };
9552
+ const days = differenceInCalendarDays(to, from) + 1;
9553
+ return { from: subDays(from, days), to: subDays(from, 1) };
9554
+ }
6304
9555
  var PRESET_LABELS = {
6305
9556
  ja: {
6306
9557
  today: "\u4ECA\u65E5",
@@ -6337,9 +9588,17 @@ var PRESET_LABELS = {
6337
9588
  lastFiscalYear: "Last fiscal year"
6338
9589
  }
6339
9590
  };
6340
- var TEXT16 = {
6341
- ja: { placeholder: "\u671F\u9593\u3092\u9078\u629E", rangeSeparator: " - " },
6342
- en: { placeholder: "Pick a date range", rangeSeparator: " \u2013 " }
9591
+ var TEXT26 = {
9592
+ ja: {
9593
+ placeholder: "\u671F\u9593\u3092\u9078\u629E",
9594
+ rangeSeparator: " - ",
9595
+ comparison: { "previous-period": "\u524D\u671F\u9593", "previous-year": "\u524D\u5E74\u540C\u671F" }
9596
+ },
9597
+ en: {
9598
+ placeholder: "Pick a date range",
9599
+ rangeSeparator: " \u2013 ",
9600
+ comparison: { "previous-period": "vs. previous period", "previous-year": "vs. same period last year" }
9601
+ }
6343
9602
  };
6344
9603
  var calendarLocale = (locale) => uiLanguage(locale) === "ja" ? ja : void 0;
6345
9604
  function createDatePresets({
@@ -6355,7 +9614,7 @@ function createDatePresets({
6355
9614
  const today = now();
6356
9615
  return { from: startOfDay(subDays(today, days - 1)), to: endOfDay(today) };
6357
9616
  };
6358
- const toDate2 = (unit) => () => {
9617
+ const toDate3 = (unit) => () => {
6359
9618
  const today = now();
6360
9619
  return {
6361
9620
  from: fiscalPeriodRange(today, { unit, fiscalYearStartMonth: startMonth }).from,
@@ -6405,9 +9664,9 @@ function createDatePresets({
6405
9664
  return { from: startOfMonth(lastMonth), to: endOfMonth(lastMonth) };
6406
9665
  }
6407
9666
  },
6408
- { label: t.thisQuarter, getValue: toDate2("quarter") },
9667
+ { label: t.thisQuarter, getValue: toDate3("quarter") },
6409
9668
  { label: t.lastQuarter, getValue: previous("quarter") },
6410
- { label: t.thisFiscalYear, getValue: toDate2("year") },
9669
+ { label: t.thisFiscalYear, getValue: toDate3("year") },
6411
9670
  { label: t.lastFiscalYear, getValue: previous("year") }
6412
9671
  ];
6413
9672
  }
@@ -6428,6 +9687,7 @@ var DateRangePicker = forwardRef(
6428
9687
  showPresets = true,
6429
9688
  locale: localeProp,
6430
9689
  fiscalYearStartMonth,
9690
+ comparison,
6431
9691
  maxDate,
6432
9692
  minDate,
6433
9693
  placeholder,
@@ -6438,7 +9698,7 @@ var DateRangePicker = forwardRef(
6438
9698
  const [open, setOpen] = useState(false);
6439
9699
  const isMobile = useIsMobile();
6440
9700
  const locale = useLocale(localeProp);
6441
- const t = TEXT16[uiLanguage(locale)];
9701
+ const t = TEXT26[uiLanguage(locale)];
6442
9702
  const presets = useMemo(
6443
9703
  () => presetsProp ?? createDatePresets({ locale, fiscalYearStartMonth }),
6444
9704
  [presetsProp, locale, fiscalYearStartMonth]
@@ -6463,6 +9723,10 @@ var DateRangePicker = forwardRef(
6463
9723
  };
6464
9724
  const selectedPreset = findMatchingPreset(presets, value);
6465
9725
  const presetLabel = selectedPreset?.label;
9726
+ const comparisonText = comparison && value?.from && value.to ? (() => {
9727
+ const c = comparisonRange({ from: value.from, to: value.to }, comparison);
9728
+ return `${t.comparison[comparison]} ${formatDate(c.from, "date", locale)}${t.rangeSeparator}${formatDate(c.to, "date", locale)}`;
9729
+ })() : void 0;
6466
9730
  const endMonth = value?.to ?? value?.from;
6467
9731
  const defaultMonth = endMonth ? isMobile ? endMonth : subMonths(endMonth, 1) : void 0;
6468
9732
  const calendarSelected = value?.from ? { from: value.from, to: value.to ?? void 0 } : void 0;
@@ -6489,7 +9753,8 @@ var DateRangePicker = forwardRef(
6489
9753
  presetLabel ? /* @__PURE__ */ jsxs(Fragment, { children: [
6490
9754
  /* @__PURE__ */ jsx("span", { children: presetLabel }),
6491
9755
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: formatDateRange2() })
6492
- ] }) : /* @__PURE__ */ jsx("span", { children: formatDateRange2() })
9756
+ ] }) : /* @__PURE__ */ jsx("span", { children: formatDateRange2() }),
9757
+ comparisonText && /* @__PURE__ */ jsx("span", { "data-slot": "date-range-picker-comparison", className: "text-muted-foreground", children: comparisonText })
6493
9758
  ]
6494
9759
  }
6495
9760
  ),
@@ -6655,7 +9920,7 @@ function RemoveButton({ label: label2, onRemove, iconClassName }) {
6655
9920
  }
6656
9921
  );
6657
9922
  }
6658
- var TEXT17 = {
9923
+ var TEXT27 = {
6659
9924
  ja: { placeholder: "\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044", search: "\u691C\u7D22...", empty: "\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3067\u3057\u305F", clear: "\u30AF\u30EA\u30A2" },
6660
9925
  en: { placeholder: "Select an option", search: "Search...", empty: "No results found.", clear: "Clear" }
6661
9926
  };
@@ -6675,7 +9940,7 @@ var SearchableSelect = forwardRef(
6675
9940
  className
6676
9941
  }, ref) {
6677
9942
  const [open, setOpen] = useState(false);
6678
- const t = TEXT17[uiLanguage(useLocale())];
9943
+ const t = TEXT27[uiLanguage(useLocale())];
6679
9944
  const allOptions = useMemo(() => flattenOptions(options), [options]);
6680
9945
  const selectedOption = useMemo(
6681
9946
  () => allOptions.find((opt) => opt.value === value),
@@ -6728,7 +9993,7 @@ var SearchableSelect = forwardRef(
6728
9993
  }
6729
9994
  );
6730
9995
  SearchableSelect.displayName = "SearchableSelect";
6731
- var TEXT18 = {
9996
+ var TEXT28 = {
6732
9997
  ja: {
6733
9998
  placeholder: "\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044",
6734
9999
  search: "\u691C\u7D22...",
@@ -6745,10 +10010,10 @@ var TEXT18 = {
6745
10010
  }
6746
10011
  };
6747
10012
  var NO_OPTIONS3 = [];
6748
- var NO_VALUES = [];
10013
+ var NO_VALUES2 = [];
6749
10014
  var MultiSelect = forwardRef(function MultiSelect2({
6750
10015
  options = NO_OPTIONS3,
6751
- value = NO_VALUES,
10016
+ value = NO_VALUES2,
6752
10017
  onChange,
6753
10018
  onSearch,
6754
10019
  maxCount = 3,
@@ -6761,7 +10026,7 @@ var MultiSelect = forwardRef(function MultiSelect2({
6761
10026
  }, ref) {
6762
10027
  const [open, setOpen] = useState(false);
6763
10028
  const listId = useId();
6764
- const t = TEXT18[uiLanguage(useLocale())];
10029
+ const t = TEXT28[uiLanguage(useLocale())];
6765
10030
  const allOptions = useMemo(() => flattenOptions(options), [options]);
6766
10031
  const selectedOptions = useMemo(
6767
10032
  () => allOptions.filter((opt) => value.includes(opt.value)),
@@ -6891,10 +10156,10 @@ function computeTargetProgress(current, target, options) {
6891
10156
  elapsed
6892
10157
  };
6893
10158
  }
6894
- var DAY_MS = 24 * 60 * 60 * 1e3;
10159
+ var DAY_MS2 = 24 * 60 * 60 * 1e3;
6895
10160
  function inferPeriodGrain(period) {
6896
10161
  if (!period) return void 0;
6897
- const days = (toTime(period.end) - toTime(period.start)) / DAY_MS;
10162
+ const days = (toTime(period.end) - toTime(period.start)) / DAY_MS2;
6898
10163
  if (!Number.isFinite(days)) return void 0;
6899
10164
  const near = (min, max) => days >= min - 1 / 24 && days <= max + 1 / 24;
6900
10165
  if (near(1, 1)) return "day";
@@ -6936,7 +10201,7 @@ var deltaVariants = cva(
6936
10201
  {
6937
10202
  variants: {
6938
10203
  variant: {
6939
- badge: "h-5 rounded-md border px-2 py-0.5 text-xs font-medium [&>svg]:size-3",
10204
+ badge: "h-5 rounded-md px-2 py-0.5 text-xs font-medium [&>svg]:size-3",
6940
10205
  inline: "text-sm [&>svg]:size-3.5"
6941
10206
  },
6942
10207
  sentiment: {
@@ -6946,9 +10211,9 @@ var deltaVariants = cva(
6946
10211
  }
6947
10212
  },
6948
10213
  compoundVariants: [
6949
- { variant: "badge", sentiment: "positive", className: "border-positive/20 bg-positive-subtle dark:border-positive/25" },
6950
- { variant: "badge", sentiment: "negative", className: "border-negative/20 bg-negative-subtle dark:border-negative/25" },
6951
- { variant: "badge", sentiment: "neutral", className: "border-neutral/20 bg-neutral-subtle dark:border-neutral/25" }
10214
+ { variant: "badge", sentiment: "positive", className: "bg-positive-subtle" },
10215
+ { variant: "badge", sentiment: "negative", className: "bg-negative-subtle" },
10216
+ { variant: "badge", sentiment: "neutral", className: "bg-neutral-subtle" }
6952
10217
  ],
6953
10218
  defaultVariants: {
6954
10219
  variant: "badge",
@@ -6981,6 +10246,7 @@ function deltaAriaLabel(result, locale, change = "relative", format, digits) {
6981
10246
  if (amount === null) return t.none(label2);
6982
10247
  return t.change(label2, change === "points" ? amount.replace(/pt$/, t.points) : amount, direction === "up");
6983
10248
  }
10249
+ var SENTIMENT_TEXT = { positive: "text-positive", negative: "text-negative", neutral: "text-neutral" };
6984
10250
  var Delta = forwardRef(
6985
10251
  ({
6986
10252
  value,
@@ -7010,7 +10276,7 @@ var Delta = forwardRef(
7010
10276
  const comparable = result.direction !== "none";
7011
10277
  const Icon = result.direction === "up" ? ArrowUp : result.direction === "down" ? ArrowDown : ArrowRight;
7012
10278
  const text = formatChange(result, display, locale, format, changeDigits);
7013
- return /* @__PURE__ */ jsxs(
10279
+ return /* @__PURE__ */ jsx(
7014
10280
  "span",
7015
10281
  {
7016
10282
  ref,
@@ -7020,16 +10286,31 @@ var Delta = forwardRef(
7020
10286
  "data-sentiment": result.sentiment,
7021
10287
  "aria-label": deltaAriaLabel(result, locale, display, format, changeDigits),
7022
10288
  className: cn(
7023
- deltaVariants({ variant, sentiment: result.sentiment }),
10289
+ variant === "badge" ? "inline-flex w-fit shrink-0 items-center gap-1.5 font-medium whitespace-nowrap tabular-nums" : deltaVariants({ variant, sentiment: result.sentiment }),
10290
+ // The chip carries the colour of the change; the root repeats it for the "—" case and for `className` consumers.
10291
+ variant === "badge" && SENTIMENT_TEXT[result.sentiment],
7024
10292
  !comparable && "text-muted-foreground",
7025
10293
  className
7026
10294
  ),
7027
10295
  ...props,
7028
- children: [
10296
+ children: variant === "badge" ? /* @__PURE__ */ jsxs(Fragment, { children: [
10297
+ /* @__PURE__ */ jsxs(
10298
+ "span",
10299
+ {
10300
+ "data-slot": "delta-chip",
10301
+ className: cn(deltaVariants({ variant, sentiment: result.sentiment }), !comparable && "text-muted-foreground"),
10302
+ children: [
10303
+ comparable && /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true" }),
10304
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: text ?? DELTA_NOT_COMPARABLE })
10305
+ ]
10306
+ }
10307
+ ),
10308
+ showLabel && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-xs font-normal text-muted-foreground", children: result.label })
10309
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
7029
10310
  comparable && /* @__PURE__ */ jsx(Icon, { "aria-hidden": "true" }),
7030
10311
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: text ?? DELTA_NOT_COMPARABLE }),
7031
10312
  showLabel && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "font-normal text-muted-foreground", children: result.label })
7032
- ]
10313
+ ] })
7033
10314
  }
7034
10315
  );
7035
10316
  }
@@ -7043,7 +10324,7 @@ var LONG_VALUE_CHARS = 10;
7043
10324
  var DELTA_UNDER_PLACEMENT = "col-start-1 row-span-1 row-start-3 justify-self-start";
7044
10325
  var KPI_FOOTER_CLASS = "flex-col items-start gap-2 rounded-none border-t-0 bg-transparent pt-0 text-sm";
7045
10326
  var PLACEHOLDER = /\{(\w+)\}/g;
7046
- var TEXT19 = {
10327
+ var TEXT29 = {
7047
10328
  ja: {
7048
10329
  target: "\u76EE\u6A19",
7049
10330
  provisional: "\u901F\u5831",
@@ -7175,7 +10456,7 @@ function KpiCard({
7175
10456
  ...props
7176
10457
  }) {
7177
10458
  const locale = useLocale(localeProp);
7178
- const t = TEXT19[uiLanguage(locale)];
10459
+ const t = TEXT29[uiLanguage(locale)];
7179
10460
  const allComparisons = [...comparisonProp ? [comparisonProp] : [], ...comparisons ?? []];
7180
10461
  const comparison = allComparisons[0];
7181
10462
  const isProvisional = provisional !== void 0 && provisional !== false;
@@ -7205,7 +10486,7 @@ function KpiCard({
7205
10486
  const targetBar = hasTarget && !neutral && !byPoints && !lower;
7206
10487
  if (summary !== void 0) warnShallowSummary(summary, t);
7207
10488
  const cardClass = cn("row-span-2 grid content-start gap-y-0 grid-rows-subgrid", className);
7208
- const valueSize = small ? "text-xl @[218px]/card:text-2xl" : "text-2xl @[218px]/card:text-4xl";
10489
+ const valueSize = small ? "text-xl @[218px]/card:text-2xl" : "text-2xl @[218px]/card:text-[26px]";
7209
10490
  if (isLoading) {
7210
10491
  return /* @__PURE__ */ jsxs(
7211
10492
  Card,
@@ -7310,7 +10591,8 @@ function KpiCard({
7310
10591
  format,
7311
10592
  change: display,
7312
10593
  digits,
7313
- locale
10594
+ locale,
10595
+ className: "min-w-0 shrink whitespace-normal"
7314
10596
  },
7315
10597
  i
7316
10598
  ));
@@ -7318,16 +10600,20 @@ function KpiCard({
7318
10600
  const longValue = valueText.length > LONG_VALUE_CHARS;
7319
10601
  return /* @__PURE__ */ jsxs(Card, { "data-slot": "kpi-card", size, className: cardClass, ...props, children: [
7320
10602
  /* @__PURE__ */ jsxs(CardHeader, { className: HEADER_CLASS, children: [
7321
- /* @__PURE__ */ jsx(CardDescription, { "data-slot": "kpi-card-label", title: label2, className: "col-start-1 min-w-0 truncate text-[13px] leading-snug font-medium text-muted-foreground", children: label2 }),
10603
+ /* @__PURE__ */ jsxs(CardDescription, { "data-slot": "kpi-card-label", title: label2, className: "col-start-1 flex min-w-0 items-center gap-2 text-[13px] leading-snug font-medium text-muted-foreground", children: [
10604
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: label2 }),
10605
+ sample && /* @__PURE__ */ jsx(SampleBadge, { label: sample.label, className: "shrink-0" })
10606
+ ] }),
7322
10607
  /* @__PURE__ */ jsxs(
7323
10608
  CardTitle,
7324
10609
  {
7325
10610
  "data-slot": "kpi-card-value",
7326
10611
  title: valueText,
7327
10612
  className: cn(
7328
- "col-start-1 flex min-w-0 flex-wrap items-center gap-x-2 gap-y-1 leading-none font-semibold tracking-tight tabular-nums",
10613
+ "col-start-1 flex min-w-0 items-center gap-x-2 gap-y-1 leading-none font-medium tracking-tight tabular-nums",
7329
10614
  // Fixed size: the step follows the card width only, never the length of the value.
7330
- valueSize
10615
+ valueSize,
10616
+ (longValue || isProvisional) && "flex-wrap"
7331
10617
  ),
7332
10618
  children: [
7333
10619
  /* @__PURE__ */ jsx("span", { "data-slot": "kpi-card-value-text", className: longValue ? "min-w-0 break-words" : "whitespace-nowrap", children: longValue ? digitGroups(valueText).map((group, i) => /* @__PURE__ */ jsxs("span", { className: "whitespace-nowrap", children: [
@@ -7335,11 +10621,11 @@ function KpiCard({
7335
10621
  /* @__PURE__ */ jsx("wbr", {})
7336
10622
  ] }, i)) : valueText }),
7337
10623
  isProvisional && /* @__PURE__ */ jsx(Badge, { "data-slot": "kpi-card-provisional", variant: "outline", className: "align-middle", children: provisionalLabel }),
7338
- sample && /* @__PURE__ */ jsx(SampleBadge, { label: sample.label })
10624
+ comparison && !stacked && !longValue && deltas
7339
10625
  ]
7340
10626
  }
7341
10627
  ),
7342
- comparison && (stacked ? /* @__PURE__ */ jsx("div", { "data-slot": "kpi-card-comparisons", className: "flex flex-wrap items-center gap-1", children: deltas }) : /* @__PURE__ */ jsx(CardAction, { className: longValue ? DELTA_UNDER_PLACEMENT : DELTA_PLACEMENT, children: deltas })),
10628
+ comparison && (stacked ? /* @__PURE__ */ jsx("div", { "data-slot": "kpi-card-comparisons", className: "flex flex-wrap items-center gap-1", children: deltas }) : longValue && /* @__PURE__ */ jsx(CardAction, { className: DELTA_UNDER_PLACEMENT, children: deltas })),
7343
10629
  hasPrevious && comparison && /* @__PURE__ */ jsx("div", { "data-slot": "kpi-card-previous", className: "col-span-full text-sm text-muted-foreground tabular-nums", children: result?.previous != null ? t.previous(baseline ?? "", fmt(result.previous)) : t.noPrevious(baseline ?? "") }),
7344
10630
  bar && progress && /* @__PURE__ */ jsxs("div", { "data-slot": "kpi-card-target-progress", className: "col-span-full mt-1 flex min-w-0 flex-col gap-1.5", children: [
7345
10631
  /* @__PURE__ */ jsxs(
@@ -7426,7 +10712,7 @@ function updatedAtText(updatedAt, display, locale, t, now, timeZone) {
7426
10712
  const absolute2 = formatDate(updatedAt, "datetime", locale, { timeZone });
7427
10713
  return display === "absolute" ? t.updatedAbsolute(absolute2) : t.updatedBoth(absolute2, relative2);
7428
10714
  }
7429
- var TEXT20 = {
10715
+ var TEXT30 = {
7430
10716
  ja: {
7431
10717
  flat: (value) => `\u5909\u5316\u306A\u3057 ${value}%`,
7432
10718
  up: (value) => `${value}% \u5897\u52A0`,
@@ -7455,7 +10741,7 @@ var TrendIndicator = forwardRef(
7455
10741
  const isGood = direction === "neutral" ? null : direction === "up" === positiveIsGood;
7456
10742
  const sentiment = isGood === null ? "neutral" : isGood ? "positive" : "negative";
7457
10743
  const Icon = direction === "up" ? ArrowUp : direction === "down" ? ArrowDown : Minus;
7458
- const t = TEXT20[uiLanguage(useLocale())];
10744
+ const t = TEXT30[uiLanguage(useLocale())];
7459
10745
  const ariaLabel = direction === "neutral" ? t.flat(value) : direction === "up" ? t.up(value) : t.down(value);
7460
10746
  return /* @__PURE__ */ jsxs(
7461
10747
  "span",
@@ -7476,93 +10762,6 @@ var TrendIndicator = forwardRef(
7476
10762
  }
7477
10763
  );
7478
10764
  TrendIndicator.displayName = "TrendIndicator";
7479
- var STATUS_LABELS = {
7480
- ja: {
7481
- success: "\u6B63\u5E38",
7482
- partial: "\u4E00\u90E8\u5931\u6557",
7483
- failed: "\u5931\u6557",
7484
- delayed: "\u9045\u5EF6",
7485
- running: "\u5B9F\u884C\u4E2D",
7486
- stopped: "\u505C\u6B62\u4E2D",
7487
- active: "\u6709\u52B9",
7488
- inactive: "\u7121\u52B9",
7489
- warning: "\u8B66\u544A",
7490
- error: "\u30A8\u30E9\u30FC",
7491
- pending: "\u4FDD\u7559\u4E2D"
7492
- },
7493
- en: {
7494
- success: "Healthy",
7495
- partial: "Partially failed",
7496
- failed: "Failed",
7497
- delayed: "Delayed",
7498
- running: "Running",
7499
- stopped: "Stopped",
7500
- active: "Active",
7501
- inactive: "Inactive",
7502
- warning: "Warning",
7503
- error: "Error",
7504
- pending: "Pending"
7505
- }
7506
- };
7507
- var STATUS_VOCABULARY = {
7508
- success: { label: STATUS_LABELS.en.success, tone: "positive" },
7509
- partial: { label: STATUS_LABELS.en.partial, tone: "warning" },
7510
- failed: { label: STATUS_LABELS.en.failed, tone: "negative" },
7511
- delayed: { label: STATUS_LABELS.en.delayed, tone: "warning" },
7512
- running: { label: STATUS_LABELS.en.running, tone: "info" },
7513
- stopped: { label: STATUS_LABELS.en.stopped, tone: "neutral" },
7514
- active: { label: STATUS_LABELS.en.active, tone: "positive" },
7515
- inactive: { label: STATUS_LABELS.en.inactive, tone: "neutral" },
7516
- warning: { label: STATUS_LABELS.en.warning, tone: "warning" },
7517
- error: { label: STATUS_LABELS.en.error, tone: "negative" },
7518
- pending: { label: STATUS_LABELS.en.pending, tone: "info" }
7519
- };
7520
- function statusLabel(status, locale) {
7521
- return Object.hasOwn(STATUS_VOCABULARY, status) ? STATUS_LABELS[uiLanguage(resolveLocale(locale))][status] : status;
7522
- }
7523
- var defaultColorMap = Object.fromEntries(
7524
- Object.entries(STATUS_VOCABULARY).map(([key, { tone }]) => [key, tone])
7525
- );
7526
- var legacyColorTones = {
7527
- green: "positive",
7528
- gray: "neutral",
7529
- yellow: "warning",
7530
- red: "negative",
7531
- blue: "info"
7532
- };
7533
- var toneStyles = {
7534
- positive: { dot: "bg-positive", bg: "bg-positive-subtle", text: "text-positive", border: "border-positive/20 dark:border-positive/25" },
7535
- negative: { dot: "bg-negative", bg: "bg-negative-subtle", text: "text-negative", border: "border-negative/20 dark:border-negative/25" },
7536
- warning: { dot: "bg-warning", bg: "bg-warning-subtle", text: "text-warning", border: "border-warning/20 dark:border-warning/25" },
7537
- info: { dot: "bg-info", bg: "bg-info-subtle", text: "text-info", border: "border-info/20 dark:border-info/25" },
7538
- neutral: { dot: "bg-neutral", bg: "bg-neutral-subtle", text: "text-neutral", border: "border-neutral/20 dark:border-neutral/25" }
7539
- };
7540
- var resolveTone = (color) => {
7541
- if (color === void 0) return "neutral";
7542
- if (Object.hasOwn(toneStyles, color)) return color;
7543
- return legacyColorTones[color] ?? "neutral";
7544
- };
7545
- var StatusBadge = forwardRef(function StatusBadge2({ status, label: label2, colorMap, className }, ref) {
7546
- const locale = useLocale();
7547
- const mergedColorMap = { ...defaultColorMap, ...colorMap };
7548
- const style = toneStyles[resolveTone(mergedColorMap[status])];
7549
- const displayLabel = label2 ?? statusLabel(status, locale);
7550
- return /* @__PURE__ */ jsxs(
7551
- Badge,
7552
- {
7553
- ref,
7554
- "data-slot": "status-badge",
7555
- variant: "outline",
7556
- className: cn("gap-1.5 rounded-md", style.border, style.bg, style.text, className),
7557
- "aria-label": displayLabel,
7558
- children: [
7559
- /* @__PURE__ */ jsx("span", { className: cn("inline-block size-1.5 rounded-full", style.dot), "aria-hidden": "true" }),
7560
- displayLabel
7561
- ]
7562
- }
7563
- );
7564
- });
7565
- StatusBadge.displayName = "StatusBadge";
7566
10765
  var Placeholder = forwardRef(function Placeholder2({ className, ...props }, ref) {
7567
10766
  return /* @__PURE__ */ jsx(
7568
10767
  "span",
@@ -7575,7 +10774,69 @@ var Placeholder = forwardRef(function Placeholder2({ className, ...props }, ref)
7575
10774
  );
7576
10775
  });
7577
10776
  Placeholder.displayName = "Placeholder";
10777
+ function DraftChart({ children }) {
10778
+ if (!import.meta.env.DEV) return null;
10779
+ return /* @__PURE__ */ jsx(DraftChartPreview, { children });
10780
+ }
10781
+ var STYLE_ID = "vantage-draft-chart";
10782
+ var DRAFT_CSS = `@layer components {
10783
+ [data-sq-draft] {
10784
+ position: relative;
10785
+ isolation: isolate;
10786
+ }
10787
+ [data-sq-draft]::after {
10788
+ content: "";
10789
+ position: absolute;
10790
+ inset: -1px;
10791
+ z-index: 2147483647;
10792
+ border-radius: inherit;
10793
+ background-color: var(--background);
10794
+ opacity: 0.75;
10795
+ pointer-events: none;
10796
+ transition: opacity 200ms ease;
10797
+ }
10798
+ [data-sq-draft]:hover::after {
10799
+ opacity: 0;
10800
+ }
10801
+ @media (prefers-reduced-motion: reduce) {
10802
+ [data-sq-draft]::after {
10803
+ transition: none;
10804
+ }
10805
+ }
10806
+ }`;
10807
+ function DraftChartPreview({ children }) {
10808
+ const ref = useRef(null);
10809
+ useInsertionEffect(() => {
10810
+ let style = document.getElementById(STYLE_ID);
10811
+ if (!style) {
10812
+ style = document.createElement("style");
10813
+ style.id = STYLE_ID;
10814
+ document.head.appendChild(style);
10815
+ }
10816
+ if (style.textContent !== DRAFT_CSS) style.textContent = DRAFT_CSS;
10817
+ }, []);
10818
+ useLayoutEffect(() => {
10819
+ const wrapper = ref.current;
10820
+ if (!wrapper) return;
10821
+ let marked = null;
10822
+ const mark = () => {
10823
+ const root = wrapper.firstElementChild;
10824
+ if (root === marked) return;
10825
+ marked?.removeAttribute("data-sq-draft");
10826
+ root?.setAttribute("data-sq-draft", "");
10827
+ marked = root;
10828
+ };
10829
+ mark();
10830
+ const observer = new MutationObserver(mark);
10831
+ observer.observe(wrapper, { childList: true });
10832
+ return () => {
10833
+ observer.disconnect();
10834
+ marked?.removeAttribute("data-sq-draft");
10835
+ };
10836
+ }, []);
10837
+ return /* @__PURE__ */ jsx("div", { ref, "data-slot": "draft-chart", style: { display: "contents" }, children });
10838
+ }
7578
10839
 
7579
- export { AppShell, AreaChart, BarChart, BarList, COMPARISON_LABELS, CategoryBar, ChartFrame, DEFAULT_SAMPLE_LABEL, DELTA_NOT_COMPARABLE, DashboardCard, DashboardCardAction, DashboardCardContent, DashboardCardDescription, DashboardCardFooter, DashboardCardHeader, DashboardCardPreset, DashboardCardSkeleton, DashboardCardTitle, DataTable, DataTableColumnVisibility, DataTableContent, DataTableExport, DataTablePagination, DataTablePreset, DataTableToolbar, DateRangePicker, Delta, DonutChart, FilterBar, FilterBarActiveChips, FilterBarClearButton, FilterBarMultiSelect, FilterBarPreset, FilterBarSelect, FunnelSteps, Heatmap, KpiCard, Legend, LineChart, MetricUnit, MetricValue, MultiSelect, PageShell, PageShellActions, PageShellContent, PageShellDescription, PageShellHeader, PageShellHeaderEnd, PageShellHeading, PageShellMeta, PageShellSummary, PageShellSummaryCard, PageShellTitle, Placeholder, ProgressCircle, RECORD_MIN_POINTS, SampleBadge, SampleClaim, SampleData, ScatterChart, SearchableSelect, SectionHeader, SegmentedControl, StatusBadge, Tracker, TrendIndicator, UpdatedAt, WaterfallChart, comparisonBaseline, comparisonLabel, computeComparison, computeTargetProgress, defaultChangeDisplay, defaultPresets, deltaVariants, detectRecord, fillKpiTemplate, flattenOptions, formatChange, formatChangeAmount, formatRelativeTime, funnelStepsVariants, inferPeriodGrain, isGroupedOptions, metricValueVariants, pageShellSummaryCardVariants, tableToCsv, toCsv, toneForValue, useDataTable, useSampleMark };
7580
- //# sourceMappingURL=chunk-77CISBMG.js.map
7581
- //# sourceMappingURL=chunk-77CISBMG.js.map
10840
+ export { AppShell, AreaChart, AttentionList, BarChart, BarList, BoxPlotChart, COMPARISON_LABELS, CategoryBar, ChartFrame, ComboChart, DEFAULT_SAMPLE_LABEL, DELTA_NOT_COMPARABLE, DashboardCard, DashboardCardAction, DashboardCardContent, DashboardCardDescription, DashboardCardFooter, DashboardCardHeader, DashboardCardPreset, DashboardCardSkeleton, DashboardCardTitle, DataTable, DataTableColumnVisibility, DataTableContent, DataTableExport, DataTablePagination, DataTablePreset, DataTableToolbar, DateRangePicker, Delta, DonutChart, DraftChart, FilledMap, FilterBar, FilterBarActiveChips, FilterBarClearButton, FilterBarMultiSelect, FilterBarPreset, FilterBarSelect, ForecastChart, FunnelSteps, Heatmap, HistogramChart, KpiCard, Legend, LineChart, MetricUnit, MetricValue, MultiSelect, PageShell, PageShellActions, PageShellContent, PageShellDescription, PageShellHeader, PageShellHeaderEnd, PageShellHeading, PageShellMeta, PageShellSummary, PageShellSummaryCard, PageShellTitle, PairedChart, PivotTable, Placeholder, ProgressCircle, RECORD_MIN_POINTS, SampleBadge, SampleClaim, SampleData, ScatterChart, SearchableSelect, SectionHeader, SegmentedControl, StatusBadge, SymbolMap, Tracker, Treemap, TrendIndicator, UpdatedAt, WaterfallChart, comparisonBaseline, comparisonLabel, computeComparison, computeTargetProgress, defaultChangeDisplay, defaultPresets, deltaVariants, detectRecord, fillKpiTemplate, flattenOptions, formatChange, formatChangeAmount, formatRelativeTime, funnelStepsVariants, inferPeriodGrain, isGroupedOptions, metricValueVariants, pageShellSummaryCardVariants, tableToCsv, toCsv, toneForValue, useDataTable, useSampleMark };
10841
+ //# sourceMappingURL=chunk-SC7K2RD6.js.map
10842
+ //# sourceMappingURL=chunk-SC7K2RD6.js.map