@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.
- package/README.md +1 -1
- package/dist/add-AU3EDPNW.js +6 -0
- package/dist/add-AU3EDPNW.js.map +1 -0
- package/dist/{chunk-AABMKHEU.js → chunk-35B3YIBF.js} +16 -7
- package/dist/chunk-35B3YIBF.js.map +1 -0
- package/dist/{chunk-QNOX7WLZ.js → chunk-5HBUBVQ3.js} +94 -8
- package/dist/chunk-5HBUBVQ3.js.map +1 -0
- package/dist/{chunk-TRMHNMMZ.js → chunk-LYUGDHSF.js} +3 -3
- package/dist/{chunk-TRMHNMMZ.js.map → chunk-LYUGDHSF.js.map} +1 -1
- package/dist/{chunk-2IAPIOPS.js → chunk-P2KIHF65.js} +2 -2
- package/dist/chunk-P2KIHF65.js.map +1 -0
- package/dist/{chunk-77CISBMG.js → chunk-SC7K2RD6.js} +3598 -337
- package/dist/chunk-SC7K2RD6.js.map +1 -0
- package/dist/{chunk-3RJEOP3J.js → chunk-XRKSIC5Z.js} +5 -3
- package/dist/chunk-XRKSIC5Z.js.map +1 -0
- package/dist/cli.js +2 -2
- package/dist/components/index.d.ts +644 -9
- package/dist/components/index.js +4 -4
- package/dist/{delta-text-CbrOaW83.d.ts → delta-text-tav2Lh5d.d.ts} +1 -1
- package/dist/doc/index.d.ts +2 -2
- package/dist/doc/index.js +4 -4
- package/dist/{format-DihQkL1N.d.ts → format-D34piCei.d.ts} +2 -1
- package/dist/geo-japan-7BMSOJRX.js +8 -0
- package/dist/geo-japan-7BMSOJRX.js.map +1 -0
- package/dist/geo-us-GVOG7VXH.js +8 -0
- package/dist/geo-us-GVOG7VXH.js.map +1 -0
- package/dist/geo-world-35HL56UU.js +8 -0
- package/dist/geo-world-35HL56UU.js.map +1 -0
- package/dist/slide/index.d.ts +2 -2
- package/dist/slide/index.js +3 -3
- package/dist/table/index.js +4 -5
- package/dist/table/index.js.map +1 -1
- package/dist/ui/index.d.ts +7 -3
- package/dist/ui/index.js +4 -4
- package/dist/ui/index.js.map +1 -1
- package/dist/{upgrade-6DT7ADRP.js → upgrade-MCHMELWC.js} +3 -3
- package/dist/{upgrade-6DT7ADRP.js.map → upgrade-MCHMELWC.js.map} +1 -1
- package/docs/en/agent-skills.md +13 -14
- package/docs/en/changelog.md +8 -0
- package/docs/en/parts/draft-chart.md +42 -0
- package/docs/en/parts/page-shell.md +1 -1
- package/docs/index.json +70 -50
- package/docs/ja/agent-skills.md +14 -14
- package/docs/ja/changelog.md +8 -0
- package/docs/ja/parts/draft-chart.md +40 -0
- package/docs/ja/parts/page-shell.md +1 -1
- package/package.json +2 -2
- package/registry/parts.json +5240 -0
- package/registry/ui/chart-frame.tsx +8 -0
- package/registry/ui/combo-chart.tsx +1852 -0
- package/registry/ui/dashboard-card.tsx +3 -1
- package/registry/ui/data-table.tsx +3 -1
- package/registry/ui/delta.tsx +3 -1
- package/registry/ui/filter-bar.tsx +3 -1
- package/registry/ui/forecast-chart.tsx +4734 -0
- package/registry/ui/funnel-steps.tsx +1 -1
- package/registry/ui/kpi-card.tsx +3 -1
- package/registry/ui/metric-value.tsx +2 -2
- package/registry/ui/page-shell.tsx +25 -4
- package/registry/ui/paired-chart.tsx +1 -1
- package/registry/ui/pivot-table.tsx +1260 -0
- package/registry/ui/progress-circle.tsx +2 -2
- package/registry/ui/sparkline.tsx +1 -1
- package/registry/ui/treemap.tsx +1917 -0
- package/templates/AGENTS.md +3 -3
- package/templates/agents/page.md +5 -4
- package/dist/add-4GHF2TX2.js +0 -6
- package/dist/add-4GHF2TX2.js.map +0 -1
- package/dist/chunk-2IAPIOPS.js.map +0 -1
- package/dist/chunk-3RJEOP3J.js.map +0 -1
- package/dist/chunk-77CISBMG.js.map +0 -1
- package/dist/chunk-AABMKHEU.js.map +0 -1
- package/dist/chunk-NZ5QIIG4.js +0 -74
- package/dist/chunk-NZ5QIIG4.js.map +0 -1
- package/dist/chunk-QNOX7WLZ.js.map +0 -1
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { ChartFrameContext, useChartSurface, resolveChartColors,
|
|
2
|
-
import { Sparkline } from './chunk-
|
|
3
|
-
import { formatDateRange, EMPTY_VALUE,
|
|
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-
|
|
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
|
|
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
|
|
662
|
-
|
|
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
|
-
(
|
|
665
|
-
|
|
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
|
-
(
|
|
669
|
-
|
|
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
|
-
(
|
|
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,
|
|
804
|
-
return `color-mix(in oklab, ${cssColor} ${
|
|
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,
|
|
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" &&
|
|
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,
|
|
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,
|
|
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
|
|
1761
|
+
const percent2 = kind !== "line" && type === "percent";
|
|
1744
1762
|
const horizontalBars = kind === "bar" && layout === "vertical";
|
|
1745
|
-
const comparison = kind !== "bar" && !
|
|
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
|
|
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 (
|
|
1781
|
+
if (forecast2) {
|
|
1763
1782
|
const position = new Map(labels.map((label2, i) => [label2, i]));
|
|
1764
|
-
for (const row of
|
|
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 && !
|
|
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 =
|
|
1804
|
-
const picked = highlight !== "auto" || mainIdx.length === 0 ||
|
|
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" && !
|
|
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 && !
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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 =
|
|
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 (
|
|
2013
|
-
const category =
|
|
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
|
-
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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 ||
|
|
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 (
|
|
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:
|
|
2318
|
-
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:
|
|
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
|
-
...
|
|
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 } :
|
|
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
|
-
(
|
|
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 =
|
|
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
|
-
|
|
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 ??
|
|
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 ??
|
|
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
|
-
|
|
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 ??
|
|
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 ??
|
|
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
|
|
4382
|
-
...headingWidth > 0 ? { left: headingWidth } :
|
|
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) =>
|
|
4639
|
-
|
|
4640
|
-
/* @__PURE__ */
|
|
4641
|
-
|
|
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
|
|
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 <=
|
|
5089
|
-
return text * 10 / 12 + HEATMAP_CELL_PADDING <=
|
|
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 = (
|
|
5118
|
-
const tooltip = (
|
|
5119
|
-
const head = `${escapeHtml2(label2(model.ys[
|
|
5120
|
-
const main =
|
|
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 =
|
|
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 =
|
|
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((
|
|
5176
|
+
model.cells.flatMap((cell2) => cell2.value === null ? [] : [valueFormatter(cell2.value)])
|
|
5133
5177
|
) : 12;
|
|
5134
|
-
const point = (
|
|
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((
|
|
5183
|
-
...point(
|
|
5184
|
-
itemStyle: flagged(
|
|
5226
|
+
data: present.map((cell2) => ({
|
|
5227
|
+
...point(cell2),
|
|
5228
|
+
itemStyle: flagged(cell2),
|
|
5185
5229
|
label: {
|
|
5186
|
-
color: heatmapRatio(
|
|
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((
|
|
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
|
|
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
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
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
|
-
|
|
5369
|
-
|
|
5370
|
-
|
|
5371
|
-
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
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
|
|
5385
|
-
function
|
|
5386
|
-
|
|
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
|
|
5389
|
-
|
|
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
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
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
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
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
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
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
|
-
|
|
5427
|
-
|
|
5428
|
-
const
|
|
5429
|
-
const
|
|
5430
|
-
const
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
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
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
}
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
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
|
|
5448
|
-
function
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
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) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[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) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[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) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[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) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[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 =
|
|
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((
|
|
5762
|
-
const meta =
|
|
5763
|
-
const comparisonColumn = comparisonOf.get(
|
|
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(
|
|
5770
|
-
isHost: comparisonOf.has(
|
|
8871
|
+
isComparison: comparisonIds.has(cell2.column.id),
|
|
8872
|
+
isHost: comparisonOf.has(cell2.column.id)
|
|
5771
8873
|
}),
|
|
5772
8874
|
children: [
|
|
5773
|
-
flexRender(
|
|
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
|
-
|
|
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 = (
|
|
5924
|
-
let text =
|
|
5925
|
-
if (guard && typeof
|
|
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((
|
|
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 ??
|
|
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
|
|
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 = () =>
|
|
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
|
|
6341
|
-
ja: {
|
|
6342
|
-
|
|
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
|
|
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:
|
|
9667
|
+
{ label: t.thisQuarter, getValue: toDate3("quarter") },
|
|
6409
9668
|
{ label: t.lastQuarter, getValue: previous("quarter") },
|
|
6410
|
-
{ label: t.thisFiscalYear, getValue:
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
10013
|
+
var NO_VALUES2 = [];
|
|
6749
10014
|
var MultiSelect = forwardRef(function MultiSelect2({
|
|
6750
10015
|
options = NO_OPTIONS3,
|
|
6751
|
-
value =
|
|
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 =
|
|
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
|
|
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)) /
|
|
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
|
|
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: "
|
|
6950
|
-
{ variant: "badge", sentiment: "negative", className: "
|
|
6951
|
-
{ variant: "badge", sentiment: "neutral", className: "
|
|
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__ */
|
|
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
|
|
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 =
|
|
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-
|
|
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__ */
|
|
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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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 =
|
|
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-
|
|
7581
|
-
//# sourceMappingURL=chunk-
|
|
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
|