@robosystems/report-components 0.2.10 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,109 @@
1
- import { formatDate, BLOCK_TITLES, numericKindOf, humanizeDuration, formatValue, formatPeriod, isExternalFactUrl, BLOCK_ORDER, humanize, qname, currencySymbolFor } from './chunk-P3GQDCL2.js';
2
- export { BLOCK_ORDER, BLOCK_TITLES, NS, currencySymbolFor, cypherAdapter, fetchSecReportShell, fetchSecSection, formatDate, formatMoney, formatPeriod, formatValue, humanize, humanizeDuration, isExternalFactUrl, jsonldFileAdapter, mergeSecSections, numericKindOf, parseJsonld, parseStore, parseStructureDefinition, parseStructureName, qname, stripRoleSuffix } from './chunk-P3GQDCL2.js';
1
+ import { formatMoney, formatDate, BLOCK_TITLES, numericKindOf, humanizeDuration, formatValue, formatPeriod, isExternalFactUrl, BLOCK_ORDER, humanize, qname, currencySymbolFor } from './chunk-ZTYXTMAT.js';
2
+ export { BLOCK_ORDER, BLOCK_TITLES, NS, currencySymbolFor, cypherAdapter, fetchSecReportShell, fetchSecSection, formatDate, formatMoney, formatPeriod, formatValue, humanize, humanizeDuration, isExternalFactUrl, jsonldFileAdapter, mergeSecSections, numericKindOf, parseJsonld, parseStore, parseStructureDefinition, parseStructureName, qname, stripRoleSuffix } from './chunk-ZTYXTMAT.js';
3
3
  import { marked } from 'marked';
4
- import { useState, useEffect, useMemo, useRef } from 'react';
4
+ import { useState, useEffect, useMemo, useRef, useId } from 'react';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
 
7
+ // src/chart/format.ts
8
+ var DASH = "\u2014";
9
+ function formatMetricValue(itemType, value) {
10
+ if (value === null || value === void 0 || Number.isNaN(value)) return DASH;
11
+ switch (itemType) {
12
+ case "monetary":
13
+ return formatMoney(value);
14
+ case "percent":
15
+ return `${(value * 100).toLocaleString("en-US", { maximumFractionDigits: 2 })}%`;
16
+ case "multiple":
17
+ return `${value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}\xD7`;
18
+ case "days":
19
+ return value.toLocaleString("en-US", { maximumFractionDigits: 0 });
20
+ case "ratio":
21
+ return value.toLocaleString("en-US", {
22
+ minimumFractionDigits: 2,
23
+ maximumFractionDigits: 2
24
+ });
25
+ default:
26
+ return value.toLocaleString("en-US", { maximumFractionDigits: 4 });
27
+ }
28
+ }
29
+ function formatMetricCompact(itemType, value) {
30
+ if (value === null || value === void 0 || Number.isNaN(value)) return "";
31
+ if (itemType === "monetary") {
32
+ const abs = Math.abs(value);
33
+ const sign = value < 0 ? "-" : "";
34
+ if (abs >= 1e9) return `${sign}$${(abs / 1e9).toFixed(abs >= 1e10 ? 0 : 1)}B`;
35
+ if (abs >= 1e6) return `${sign}$${(abs / 1e6).toFixed(abs >= 1e7 ? 0 : 1)}M`;
36
+ if (abs >= 1e3) return `${sign}$${(abs / 1e3).toFixed(abs >= 1e4 ? 0 : 1)}K`;
37
+ return `${sign}$${abs.toFixed(0)}`;
38
+ }
39
+ if (itemType === "percent") {
40
+ return `${(value * 100).toLocaleString("en-US", { maximumFractionDigits: 1 })}%`;
41
+ }
42
+ if (itemType === "multiple") {
43
+ return `${value.toLocaleString("en-US", { maximumFractionDigits: 1 })}\xD7`;
44
+ }
45
+ if (itemType === "days") {
46
+ return value.toLocaleString("en-US", { maximumFractionDigits: 0 });
47
+ }
48
+ return value.toLocaleString("en-US", { maximumFractionDigits: 2 });
49
+ }
50
+
51
+ // src/chart/join.ts
52
+ function joinChartSeries(panel, rendering) {
53
+ const rowByElement = new Map(rendering.rows.map((r) => [r.elementId, r]));
54
+ const periods = rendering.periods;
55
+ const out = [];
56
+ for (const series of panel.series) {
57
+ const row = rowByElement.get(series.elementId);
58
+ if (!row) continue;
59
+ const points = periods.map((p, i) => ({
60
+ x: p.end,
61
+ y: i < row.values.length ? row.values[i] : null
62
+ }));
63
+ out.push({
64
+ key: series.key,
65
+ label: series.label,
66
+ itemType: panel.itemType,
67
+ points
68
+ });
69
+ }
70
+ return out;
71
+ }
72
+
73
+ // src/chart/palette.ts
74
+ var SERIES_COLORS_LIGHT = [
75
+ "#2a78d6",
76
+ // 1 blue
77
+ "#eb6834",
78
+ // 2 orange
79
+ "#1baf7a",
80
+ // 3 aqua
81
+ "#eda100",
82
+ // 4 yellow
83
+ "#e87ba4",
84
+ // 5 magenta
85
+ "#008300",
86
+ // 6 green
87
+ "#4a3aa7",
88
+ // 7 violet
89
+ "#e34948"
90
+ // 8 red
91
+ ];
92
+ var SERIES_COLORS_DARK = [
93
+ "#3987e5",
94
+ "#d95926",
95
+ "#199e70",
96
+ "#c98500",
97
+ "#d55181",
98
+ "#008300",
99
+ "#9085e9",
100
+ "#e66767"
101
+ ];
102
+ function seriesColor(index) {
103
+ const slot = Math.min(index, SERIES_COLORS_LIGHT.length - 1);
104
+ return `var(--rs-chart-series-${slot + 1}, ${SERIES_COLORS_LIGHT[slot]})`;
105
+ }
106
+
7
107
  // src/project.ts
8
108
  function fallbackElement(id) {
9
109
  return {
@@ -102,8 +202,11 @@ function memberByAxis(fact) {
102
202
  for (const d of fact.dimensions ?? []) m.set(d.axis, d);
103
203
  return m;
104
204
  }
205
+ function memberKey(d) {
206
+ return d?.member ?? d?.typedValue ?? DOMAIN;
207
+ }
105
208
  function coordinate(byAxis, axes) {
106
- return axes.map((a) => `${a}=${byAxis.get(a)?.member ?? byAxis.get(a)?.typedValue ?? DOMAIN}`).join("|");
209
+ return axes.map((a) => `${a}=${memberKey(byAxis.get(a))}`).join("|");
107
210
  }
108
211
  function sectionFacts(model, ib) {
109
212
  const target = ib.factSet;
@@ -362,7 +465,7 @@ function resolveScale(facts, model, requested) {
362
465
  }
363
466
  return { factor, label, caption };
364
467
  }
365
- function buildSlicers(model, facts, config) {
468
+ function buildSlicers(model, config, diced) {
366
469
  const slicers = [];
367
470
  for (const key of config.slicers) {
368
471
  if (key === "entity") {
@@ -377,14 +480,7 @@ function buildSlicers(model, facts, config) {
377
480
  }
378
481
  const axis = axisOf(key);
379
482
  if (!axis) continue;
380
- let qual = null;
381
- for (const f of facts) {
382
- const d = (f.dimensions ?? []).find((x) => x.axis === axis);
383
- if (d) {
384
- qual = d;
385
- break;
386
- }
387
- }
483
+ const qual = diced.get(axis) ?? null;
388
484
  const axisLabel = qual?.axisLabel ?? humanize(axis);
389
485
  slicers.push({
390
486
  aspect: key,
@@ -404,14 +500,25 @@ function buildPivot(model, ib, config = defaultPivotConfig(model, ib)) {
404
500
  const facts = sectionFacts(model, ib).filter(
405
501
  (f) => (f.dimensions ?? []).every((d) => allowedAxes.has(d.axis) && dimensionInScope(scope, d))
406
502
  );
503
+ const dicedSlicers = /* @__PURE__ */ new Map();
504
+ for (const axis of dimAxes(config.slicers)) {
505
+ const qual = facts.flatMap((f) => f.dimensions ?? []).find((d) => d.axis === axis) ?? null;
506
+ dicedSlicers.set(axis, qual);
507
+ }
508
+ const shownAxes = /* @__PURE__ */ new Set([...rowDimAxes, ...colDimAxes]);
509
+ const tableFacts = facts.filter(
510
+ (f) => (f.dimensions ?? []).every(
511
+ (d) => shownAxes.has(d.axis) || dicedSlicers.has(d.axis) && memberKey(d) === memberKey(dicedSlicers.get(d.axis))
512
+ )
513
+ );
407
514
  const periodOnColumns = config.columns.includes("period");
408
- let periods = periodOnColumns ? derivePeriods(model, facts) : [];
515
+ let periods = periodOnColumns ? derivePeriods(model, tableFacts) : [];
409
516
  if (periodOnColumns && config.dropSparsePeriods !== false) {
410
- periods = pruneSparsePeriods(model, facts, periods);
517
+ periods = pruneSparsePeriods(model, tableFacts, periods);
411
518
  }
412
- const colCombos = memberCombos(facts, colDimAxes, tree.indexOf);
519
+ const colCombos = memberCombos(tableFacts, colDimAxes, tree.indexOf);
413
520
  const occupiedCols = /* @__PURE__ */ new Set();
414
- for (const f of facts) {
521
+ for (const f of tableFacts) {
415
522
  const end = model.periods[f.period]?.end ?? "";
416
523
  occupiedCols.add(`${end}#${coordinate(memberByAxis(f), colDimAxes)}`);
417
524
  }
@@ -433,18 +540,17 @@ function buildPivot(model, ib, config = defaultPivotConfig(model, ib)) {
433
540
  }
434
541
  }
435
542
  const columnHeaders = headerLevels(columnSegs);
436
- const shownAxes = /* @__PURE__ */ new Set([...rowDimAxes, ...colDimAxes]);
543
+ const slicerSpecificity = (f) => (f.dimensions ?? []).filter((d) => !shownAxes.has(d.axis)).length;
437
544
  const factIndex = /* @__PURE__ */ new Map();
438
- for (const f of facts) {
545
+ for (const f of [...tableFacts].sort((a, b) => slicerSpecificity(a) - slicerSpecificity(b))) {
439
546
  const byAxis = memberByAxis(f);
440
- if ([...byAxis.keys()].some((axis) => !shownAxes.has(axis))) continue;
441
547
  const end = model.periods[f.period]?.end ?? "";
442
548
  const rowSig = coordinate(byAxis, rowDimAxes);
443
549
  const colSig = coordinate(byAxis, colDimAxes);
444
550
  factIndex.set(`${f.element}\u241F${rowSig}\u241F${end}\u241F${colSig}`, f);
445
551
  }
446
- const factfulConcepts = new Set(facts.map((f) => f.element));
447
- const rowCombos = memberCombos(facts, rowDimAxes, tree.indexOf);
552
+ const factfulConcepts = new Set(tableFacts.map((f) => f.element));
553
+ const rowCombos = memberCombos(tableFacts, rowDimAxes, tree.indexOf);
448
554
  const combosForConcept = (element) => {
449
555
  if (rowDimAxes.length === 0) return rowCombos;
450
556
  return rowCombos.filter(
@@ -546,11 +652,11 @@ function buildPivot(model, ib, config = defaultPivotConfig(model, ib)) {
546
652
  structureName: ib.structureId ? null : structure?.structureName ?? ib.label ?? null,
547
653
  definition: structure?.definition ?? null,
548
654
  kind: structure?.kind ?? null,
549
- slicers: buildSlicers(model, facts, config),
655
+ slicers: buildSlicers(model, config, dicedSlicers),
550
656
  columnHeaders,
551
657
  columns,
552
658
  rows,
553
- scale: resolveScale(facts, model, config.scale),
659
+ scale: resolveScale(tableFacts, model, config.scale),
554
660
  config
555
661
  };
556
662
  }
@@ -1190,7 +1296,394 @@ function ReportView({ report, inspect = true, dimensionAxis = "rows" }) {
1190
1296
  )
1191
1297
  ] });
1192
1298
  }
1299
+ var MARGIN = { top: 16, right: 104, bottom: 30, left: 60 };
1300
+ var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
1301
+ function shortMonth(iso) {
1302
+ const m = /^(\d{4})-(\d{2})/.exec(iso);
1303
+ if (!m) return iso;
1304
+ return `${MONTHS[Number(m[2]) - 1] ?? m[2]} '${m[1].slice(2)}`;
1305
+ }
1306
+ function niceTicks(min, max, count = 4) {
1307
+ if (!(max > min)) return [min];
1308
+ const span = max - min;
1309
+ const step0 = span / count;
1310
+ const mag = Math.pow(10, Math.floor(Math.log10(step0)));
1311
+ const norm = step0 / mag;
1312
+ const step = (norm >= 5 ? 10 : norm >= 2 ? 5 : norm >= 1 ? 2 : 1) * mag;
1313
+ const start = Math.ceil(min / step) * step;
1314
+ const ticks = [];
1315
+ for (let v = start; v <= max + step * 1e-6; v += step) ticks.push(Number(v.toFixed(10)));
1316
+ return ticks;
1317
+ }
1318
+ var styles5 = {
1319
+ wrap: {
1320
+ fontFamily: "var(--rs-font-sans, ui-sans-serif, system-ui, sans-serif)",
1321
+ color: "var(--rs-text, #111827)"
1322
+ },
1323
+ title: {
1324
+ fontFamily: "var(--rs-font-heading, var(--rs-font-sans, sans-serif))",
1325
+ fontSize: "0.9rem",
1326
+ fontWeight: 600,
1327
+ margin: "0 0 0.5rem"
1328
+ },
1329
+ legend: {
1330
+ display: "flex",
1331
+ flexWrap: "wrap",
1332
+ gap: "0.25rem 1rem",
1333
+ marginBottom: "0.5rem",
1334
+ fontSize: "0.78rem",
1335
+ color: "var(--rs-muted, #6b7280)"
1336
+ },
1337
+ legendItem: { display: "inline-flex", alignItems: "center", gap: "0.4rem" },
1338
+ legendKey: { display: "inline-block", width: 16, height: 2, borderRadius: 1 },
1339
+ empty: {
1340
+ padding: "2rem",
1341
+ textAlign: "center",
1342
+ color: "var(--rs-muted, #6b7280)",
1343
+ fontSize: "0.85rem"
1344
+ }
1345
+ };
1346
+ function TimeSeriesChart({
1347
+ series,
1348
+ itemType,
1349
+ title,
1350
+ width = 720,
1351
+ height = 280
1352
+ }) {
1353
+ const svgRef = useRef(null);
1354
+ const [active, setActive] = useState(null);
1355
+ const clipId = useId();
1356
+ const family = itemType ?? series[0]?.itemType ?? null;
1357
+ const periodCount = series[0]?.points.length ?? 0;
1358
+ const model = useMemo(() => {
1359
+ const values = [];
1360
+ for (const s of series)
1361
+ for (const p of s.points) if (p.y !== null && !Number.isNaN(p.y)) values.push(p.y);
1362
+ if (!values.length) return null;
1363
+ let min2 = Math.min(...values);
1364
+ let max2 = Math.max(...values);
1365
+ if (min2 === max2) {
1366
+ const pad = Math.abs(min2) * 0.1 || 1;
1367
+ min2 -= pad;
1368
+ max2 += pad;
1369
+ } else {
1370
+ const pad = (max2 - min2) * 0.08;
1371
+ min2 -= pad;
1372
+ max2 += pad;
1373
+ }
1374
+ return { min: min2, max: max2 };
1375
+ }, [series]);
1376
+ if (!model || periodCount === 0) {
1377
+ return /* @__PURE__ */ jsxs("div", { style: styles5.wrap, children: [
1378
+ title && /* @__PURE__ */ jsx("p", { style: styles5.title, children: title }),
1379
+ /* @__PURE__ */ jsx("p", { style: styles5.empty, children: "No data to plot for this period range." })
1380
+ ] });
1381
+ }
1382
+ const innerW = width - MARGIN.left - MARGIN.right;
1383
+ const innerH = height - MARGIN.top - MARGIN.bottom;
1384
+ const { min, max } = model;
1385
+ const xAt = (i) => periodCount === 1 ? MARGIN.left + innerW / 2 : MARGIN.left + i / (periodCount - 1) * innerW;
1386
+ const yAt = (v) => MARGIN.top + innerH - (v - min) / (max - min) * innerH;
1387
+ const yTicks = niceTicks(min, max, 4);
1388
+ const xEvery = Math.max(1, Math.ceil(periodCount / 6));
1389
+ const periods = series[0].points;
1390
+ const showEndLabels = series.length <= 4;
1391
+ const placedLabelY = [];
1392
+ const showLegend = series.length >= 2;
1393
+ const ariaLabel = `${title ? `${title} \u2014 ` : ""}line chart of ${series.length} metric${series.length === 1 ? "" : "s"} over ${periodCount} periods`;
1394
+ function handleMove(e) {
1395
+ const svg = svgRef.current;
1396
+ const ctm = svg?.getScreenCTM?.();
1397
+ if (!svg || !ctm) return;
1398
+ const pt = new DOMPoint(e.clientX, e.clientY).matrixTransform(ctm.inverse());
1399
+ if (periodCount === 1) {
1400
+ setActive(0);
1401
+ return;
1402
+ }
1403
+ const frac = (pt.x - MARGIN.left) / innerW;
1404
+ const idx = Math.round(frac * (periodCount - 1));
1405
+ setActive(Math.max(0, Math.min(periodCount - 1, idx)));
1406
+ }
1407
+ function handleKey(e) {
1408
+ if (e.key === "ArrowRight") {
1409
+ e.preventDefault();
1410
+ setActive((a) => Math.min(periodCount - 1, (a ?? -1) + 1));
1411
+ } else if (e.key === "ArrowLeft") {
1412
+ e.preventDefault();
1413
+ setActive((a) => Math.max(0, (a ?? periodCount) - 1));
1414
+ } else if (e.key === "Escape") {
1415
+ setActive(null);
1416
+ }
1417
+ }
1418
+ return /* @__PURE__ */ jsxs("div", { style: styles5.wrap, children: [
1419
+ title && /* @__PURE__ */ jsx("p", { style: styles5.title, children: title }),
1420
+ showLegend && /* @__PURE__ */ jsx("div", { style: styles5.legend, children: series.map((s, i) => /* @__PURE__ */ jsxs("span", { style: styles5.legendItem, children: [
1421
+ /* @__PURE__ */ jsx("span", { style: { ...styles5.legendKey, background: seriesColor(i) } }),
1422
+ s.label
1423
+ ] }, s.key)) }),
1424
+ /* @__PURE__ */ jsxs(
1425
+ "svg",
1426
+ {
1427
+ ref: svgRef,
1428
+ viewBox: `0 0 ${width} ${height}`,
1429
+ width: "100%",
1430
+ role: "img",
1431
+ "aria-label": ariaLabel,
1432
+ tabIndex: 0,
1433
+ style: { display: "block", outline: "none", touchAction: "none" },
1434
+ onPointerMove: handleMove,
1435
+ onPointerLeave: () => setActive(null),
1436
+ onKeyDown: handleKey,
1437
+ children: [
1438
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsx("clipPath", { id: clipId, children: /* @__PURE__ */ jsx("rect", { x: MARGIN.left, y: MARGIN.top, width: innerW, height: innerH }) }) }),
1439
+ yTicks.map((t) => /* @__PURE__ */ jsxs("g", { children: [
1440
+ /* @__PURE__ */ jsx(
1441
+ "line",
1442
+ {
1443
+ x1: MARGIN.left,
1444
+ x2: MARGIN.left + innerW,
1445
+ y1: yAt(t),
1446
+ y2: yAt(t),
1447
+ stroke: "var(--rs-border, #e5e7eb)",
1448
+ strokeWidth: 1
1449
+ }
1450
+ ),
1451
+ /* @__PURE__ */ jsx(
1452
+ "text",
1453
+ {
1454
+ x: MARGIN.left - 8,
1455
+ y: yAt(t),
1456
+ textAnchor: "end",
1457
+ dominantBaseline: "middle",
1458
+ fontSize: 11,
1459
+ fill: "var(--rs-muted, #6b7280)",
1460
+ style: { fontVariantNumeric: "tabular-nums" },
1461
+ children: formatMetricCompact(family, t)
1462
+ }
1463
+ )
1464
+ ] }, `y${t}`)),
1465
+ periods.map(
1466
+ (p, i) => i % xEvery === 0 || i === periodCount - 1 ? /* @__PURE__ */ jsx(
1467
+ "text",
1468
+ {
1469
+ x: xAt(i),
1470
+ y: height - MARGIN.bottom + 18,
1471
+ textAnchor: "middle",
1472
+ fontSize: 11,
1473
+ fill: "var(--rs-muted, #6b7280)",
1474
+ children: shortMonth(p.x)
1475
+ },
1476
+ `x${p.x}`
1477
+ ) : null
1478
+ ),
1479
+ /* @__PURE__ */ jsx(
1480
+ "line",
1481
+ {
1482
+ x1: MARGIN.left,
1483
+ x2: MARGIN.left + innerW,
1484
+ y1: MARGIN.top + innerH,
1485
+ y2: MARGIN.top + innerH,
1486
+ stroke: "var(--rs-border-strong, #9ca3af)",
1487
+ strokeWidth: 1
1488
+ }
1489
+ ),
1490
+ active !== null && /* @__PURE__ */ jsx(
1491
+ "line",
1492
+ {
1493
+ x1: xAt(active),
1494
+ x2: xAt(active),
1495
+ y1: MARGIN.top,
1496
+ y2: MARGIN.top + innerH,
1497
+ stroke: "var(--rs-border-strong, #9ca3af)",
1498
+ strokeWidth: 1
1499
+ }
1500
+ ),
1501
+ /* @__PURE__ */ jsx("g", { clipPath: `url(#${clipId})`, children: series.map((s, si) => {
1502
+ const color = seriesColor(si);
1503
+ const segs = [];
1504
+ let pen = false;
1505
+ s.points.forEach((pt, i) => {
1506
+ if (pt.y === null || Number.isNaN(pt.y)) {
1507
+ pen = false;
1508
+ return;
1509
+ }
1510
+ segs.push(`${pen ? "L" : "M"}${xAt(i)},${yAt(pt.y)}`);
1511
+ pen = true;
1512
+ });
1513
+ return /* @__PURE__ */ jsx(
1514
+ "path",
1515
+ {
1516
+ d: segs.join(" "),
1517
+ fill: "none",
1518
+ stroke: color,
1519
+ strokeWidth: 2,
1520
+ strokeLinejoin: "round",
1521
+ strokeLinecap: "round"
1522
+ },
1523
+ s.key
1524
+ );
1525
+ }) }),
1526
+ series.map((s, si) => {
1527
+ const color = seriesColor(si);
1528
+ let endIdx = -1;
1529
+ for (let i = s.points.length - 1; i >= 0; i--) {
1530
+ const y = s.points[i].y;
1531
+ if (y !== null && !Number.isNaN(y)) {
1532
+ endIdx = i;
1533
+ break;
1534
+ }
1535
+ }
1536
+ if (endIdx < 0) return null;
1537
+ const ey = yAt(s.points[endIdx].y);
1538
+ const ex = xAt(endIdx);
1539
+ let label = null;
1540
+ if (showEndLabels && endIdx === periodCount - 1) {
1541
+ const collides = placedLabelY.some((py) => Math.abs(py - ey) < 13);
1542
+ if (!collides) {
1543
+ placedLabelY.push(ey);
1544
+ label = formatMetricValue(family, s.points[endIdx].y);
1545
+ }
1546
+ }
1547
+ return /* @__PURE__ */ jsxs("g", { children: [
1548
+ /* @__PURE__ */ jsx(
1549
+ "circle",
1550
+ {
1551
+ cx: ex,
1552
+ cy: ey,
1553
+ r: 4,
1554
+ fill: color,
1555
+ stroke: "var(--rs-surface, #ffffff)",
1556
+ strokeWidth: 2
1557
+ }
1558
+ ),
1559
+ label && /* @__PURE__ */ jsx(
1560
+ "text",
1561
+ {
1562
+ x: ex + 8,
1563
+ y: ey,
1564
+ dominantBaseline: "middle",
1565
+ fontSize: 11,
1566
+ fill: "var(--rs-text, #111827)",
1567
+ style: { fontVariantNumeric: "tabular-nums" },
1568
+ children: label
1569
+ }
1570
+ )
1571
+ ] }, `end${s.key}`);
1572
+ }),
1573
+ active !== null && /* @__PURE__ */ jsx(
1574
+ ActiveReadout,
1575
+ {
1576
+ active,
1577
+ series,
1578
+ family,
1579
+ xAt,
1580
+ yAt,
1581
+ width,
1582
+ periodLabel: periods[active] ? shortMonth(periods[active].x) : "",
1583
+ plotTop: MARGIN.top
1584
+ }
1585
+ )
1586
+ ]
1587
+ }
1588
+ )
1589
+ ] });
1590
+ }
1591
+ function ActiveReadout({
1592
+ active,
1593
+ series,
1594
+ family,
1595
+ xAt,
1596
+ yAt,
1597
+ width,
1598
+ periodLabel,
1599
+ plotTop
1600
+ }) {
1601
+ const rows = series.map((s, i) => ({
1602
+ label: s.label,
1603
+ color: seriesColor(i),
1604
+ value: s.points[active]?.y ?? null,
1605
+ y: s.points[active]?.y
1606
+ }));
1607
+ const rowH = 16;
1608
+ const boxW = 176;
1609
+ const boxH = 22 + rows.length * rowH;
1610
+ const cx = xAt(active);
1611
+ const bx = cx + 12 + boxW > width ? cx - 12 - boxW : cx + 12;
1612
+ const by = plotTop + 4;
1613
+ return /* @__PURE__ */ jsxs("g", { pointerEvents: "none", children: [
1614
+ rows.map(
1615
+ (r) => r.y !== null && r.y !== void 0 && !Number.isNaN(r.y) ? /* @__PURE__ */ jsx(
1616
+ "circle",
1617
+ {
1618
+ cx,
1619
+ cy: yAt(r.y),
1620
+ r: 4,
1621
+ fill: r.color,
1622
+ stroke: "var(--rs-surface, #ffffff)",
1623
+ strokeWidth: 2
1624
+ },
1625
+ `ap${r.label}`
1626
+ ) : null
1627
+ ),
1628
+ /* @__PURE__ */ jsx(
1629
+ "rect",
1630
+ {
1631
+ x: bx,
1632
+ y: by,
1633
+ width: boxW,
1634
+ height: boxH,
1635
+ rx: 6,
1636
+ fill: "var(--rs-surface, #ffffff)",
1637
+ stroke: "var(--rs-border, #e5e7eb)",
1638
+ strokeWidth: 1
1639
+ }
1640
+ ),
1641
+ /* @__PURE__ */ jsx("text", { x: bx + 10, y: by + 15, fontSize: 11, fontWeight: 600, fill: "var(--rs-muted, #6b7280)", children: periodLabel }),
1642
+ rows.map((r, i) => {
1643
+ const ry = by + 22 + i * rowH + 11;
1644
+ return /* @__PURE__ */ jsxs("g", { children: [
1645
+ /* @__PURE__ */ jsx(
1646
+ "line",
1647
+ {
1648
+ x1: bx + 10,
1649
+ x2: bx + 24,
1650
+ y1: ry,
1651
+ y2: ry,
1652
+ stroke: r.color,
1653
+ strokeWidth: 2,
1654
+ strokeLinecap: "round"
1655
+ }
1656
+ ),
1657
+ /* @__PURE__ */ jsx(
1658
+ "text",
1659
+ {
1660
+ x: bx + 30,
1661
+ y: ry,
1662
+ dominantBaseline: "middle",
1663
+ fontSize: 11,
1664
+ fill: "var(--rs-muted, #6b7280)",
1665
+ children: r.label
1666
+ }
1667
+ ),
1668
+ /* @__PURE__ */ jsx(
1669
+ "text",
1670
+ {
1671
+ x: bx + boxW - 10,
1672
+ y: ry,
1673
+ dominantBaseline: "middle",
1674
+ textAnchor: "end",
1675
+ fontSize: 11,
1676
+ fontWeight: 600,
1677
+ fill: "var(--rs-text, #111827)",
1678
+ style: { fontVariantNumeric: "tabular-nums" },
1679
+ children: formatMetricValue(family, r.value)
1680
+ }
1681
+ )
1682
+ ] }, `tr${r.label}`);
1683
+ })
1684
+ ] });
1685
+ }
1193
1686
 
1194
- export { ExternalTextBlock, FactInspector, ReportView, StatementTable, buildPivot, buildPivots, calcSubtotals, defaultPivotConfig, footCheck, pivotDimensionsOn, presentationOrder, reportSections, sliceReportSection };
1687
+ export { ExternalTextBlock, FactInspector, ReportView, SERIES_COLORS_DARK, SERIES_COLORS_LIGHT, StatementTable, TimeSeriesChart, buildPivot, buildPivots, calcSubtotals, defaultPivotConfig, footCheck, formatMetricCompact, formatMetricValue, joinChartSeries, pivotDimensionsOn, presentationOrder, reportSections, seriesColor, sliceReportSection };
1195
1688
  //# sourceMappingURL=index.js.map
1196
1689
  //# sourceMappingURL=index.js.map