@optilogic/charts 1.3.10 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +353 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -1
- package/dist/index.d.ts +49 -1
- package/dist/index.js +355 -31
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/cartesian/area-chart.tsx +11 -0
- package/src/cartesian/bar-chart.tsx +109 -12
- package/src/cartesian/composed-chart.tsx +11 -0
- package/src/cartesian/line-chart.tsx +12 -2
- package/src/cartesian/scatter-chart.tsx +22 -0
- package/src/cartesian/waterfall-chart.tsx +11 -0
- package/src/dashboard/chart-builder.tsx +47 -5
- package/src/dashboard/chart-renderer.tsx +1 -1
- package/src/index.ts +5 -1
- package/src/radial/donut-chart.tsx +6 -0
- package/src/radial/pie-chart.tsx +6 -0
- package/src/radial/radar-chart.tsx +11 -0
- package/src/radial/radial-bar-chart.tsx +6 -0
- package/src/shared/chart-container.tsx +133 -6
- package/src/shared/formatters.ts +20 -0
- package/src/specialized/funnel-chart.tsx +6 -0
- package/src/specialized/gantt-chart.tsx +25 -0
- package/src/specialized/heatmap-chart.tsx +40 -5
- package/src/specialized/sankey-chart.tsx +17 -0
- package/src/specialized/treemap-chart.tsx +31 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as React17 from 'react';
|
|
2
2
|
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
3
3
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
-
import { ResponsiveContainer, LineChart as LineChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Line, BarChart as BarChart$1, Bar, AreaChart as AreaChart$1, Area, ScatterChart as ScatterChart$1, ZAxis, Scatter, ComposedChart as ComposedChart$1, ReferenceLine,
|
|
5
|
-
import { cn } from '@optilogic/core';
|
|
4
|
+
import { ResponsiveContainer, LineChart as LineChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Line, BarChart as BarChart$1, Bar, Cell, AreaChart as AreaChart$1, Area, ScatterChart as ScatterChart$1, ZAxis, Scatter, ComposedChart as ComposedChart$1, ReferenceLine, PieChart as PieChart$1, Pie, RadarChart as RadarChart$1, PolarGrid, PolarAngleAxis, PolarRadiusAxis, Radar, RadialBarChart as RadialBarChart$1, RadialBar, Sankey, Treemap, FunnelChart as FunnelChart$1, Funnel, LabelList, Layer, Rectangle } from 'recharts';
|
|
5
|
+
import { cn, VisuallyHidden, computeScaleDomain, resolveCellFormat, resolveFormatColor } from '@optilogic/core';
|
|
6
6
|
|
|
7
7
|
// src/shared/colors.ts
|
|
8
8
|
var CHART_COLORS = [
|
|
@@ -211,6 +211,13 @@ function resolveLegendConfig(legend) {
|
|
|
211
211
|
if (legend === true) return { position: "top", align: "right" };
|
|
212
212
|
return legend;
|
|
213
213
|
}
|
|
214
|
+
function formatCell(value) {
|
|
215
|
+
if (value == null) return "";
|
|
216
|
+
if (typeof value === "number") return value.toLocaleString();
|
|
217
|
+
if (value instanceof Date) return value.toLocaleString();
|
|
218
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
219
|
+
return String(value);
|
|
220
|
+
}
|
|
214
221
|
var ChartContainer = React17.forwardRef(function ChartContainer2({
|
|
215
222
|
children,
|
|
216
223
|
className,
|
|
@@ -219,8 +226,18 @@ var ChartContainer = React17.forwardRef(function ChartContainer2({
|
|
|
219
226
|
empty,
|
|
220
227
|
emptyMessage = "No data available",
|
|
221
228
|
title,
|
|
222
|
-
description
|
|
229
|
+
description,
|
|
230
|
+
ariaLabel,
|
|
231
|
+
ariaDescription,
|
|
232
|
+
tableData,
|
|
233
|
+
tableColumns,
|
|
234
|
+
onRowActivate,
|
|
235
|
+
rowActionLabel
|
|
223
236
|
}, ref) {
|
|
237
|
+
const descriptionId = React17.useId();
|
|
238
|
+
const accessibleName = ariaLabel ?? title;
|
|
239
|
+
const accessibleDescription = ariaDescription ?? description;
|
|
240
|
+
const hasTable = !loading && !empty && !!tableData && tableData.length > 0 && !!tableColumns && tableColumns.length > 0;
|
|
224
241
|
return /* @__PURE__ */ jsxs("div", { ref, className: cn("w-full", className), style: { height }, children: [
|
|
225
242
|
(title || description) && /* @__PURE__ */ jsxs("div", { style: { marginBottom: 8 }, children: [
|
|
226
243
|
title && /* @__PURE__ */ jsx(
|
|
@@ -283,12 +300,51 @@ var ChartContainer = React17.forwardRef(function ChartContainer2({
|
|
|
283
300
|
},
|
|
284
301
|
children: emptyMessage
|
|
285
302
|
}
|
|
286
|
-
) : /* @__PURE__ */
|
|
287
|
-
|
|
303
|
+
) : /* @__PURE__ */ jsxs(
|
|
304
|
+
"div",
|
|
288
305
|
{
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
306
|
+
role: "img",
|
|
307
|
+
"aria-label": accessibleName,
|
|
308
|
+
"aria-describedby": accessibleDescription ? descriptionId : void 0,
|
|
309
|
+
className: "relative",
|
|
310
|
+
style: {
|
|
311
|
+
width: "100%",
|
|
312
|
+
height: title || description ? "calc(100% - 40px)" : "100%"
|
|
313
|
+
},
|
|
314
|
+
children: [
|
|
315
|
+
accessibleDescription && /* @__PURE__ */ jsx(VisuallyHidden, { as: "span", id: descriptionId, children: accessibleDescription }),
|
|
316
|
+
/* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: "100%", children }),
|
|
317
|
+
hasTable && /* @__PURE__ */ jsx(VisuallyHidden, { as: "div", children: /* @__PURE__ */ jsxs("table", { children: [
|
|
318
|
+
accessibleName && /* @__PURE__ */ jsx("caption", { children: accessibleName }),
|
|
319
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { children: tableColumns.map((col) => /* @__PURE__ */ jsx("th", { scope: "col", children: col.label }, col.key)) }) }),
|
|
320
|
+
/* @__PURE__ */ jsx("tbody", { children: tableData.map((row, rowIndex) => {
|
|
321
|
+
const record = row;
|
|
322
|
+
return /* @__PURE__ */ jsx("tr", { children: tableColumns.map((col) => /* @__PURE__ */ jsx("td", { children: formatCell(record[col.key]) }, col.key)) }, rowIndex);
|
|
323
|
+
}) })
|
|
324
|
+
] }) }),
|
|
325
|
+
hasTable && onRowActivate && /* @__PURE__ */ jsx(
|
|
326
|
+
"nav",
|
|
327
|
+
{
|
|
328
|
+
"aria-label": `${accessibleName ?? "Chart"} data point links`,
|
|
329
|
+
className: "sr-only focus-within:not-sr-only focus-within:absolute focus-within:z-20 focus-within:top-1 focus-within:left-1 focus-within:flex focus-within:max-h-[calc(100%-0.5rem)] focus-within:flex-col focus-within:gap-1 focus-within:overflow-auto focus-within:rounded-md focus-within:border focus-within:border-border focus-within:bg-popover focus-within:p-2 focus-within:shadow-md",
|
|
330
|
+
children: tableData.map((row, rowIndex) => {
|
|
331
|
+
const record = row;
|
|
332
|
+
const firstKey = tableColumns[0]?.key;
|
|
333
|
+
const label = rowActionLabel?.(row, rowIndex) ?? (firstKey ? formatCell(record[firstKey]) : `Row ${rowIndex + 1}`);
|
|
334
|
+
return /* @__PURE__ */ jsx(
|
|
335
|
+
"button",
|
|
336
|
+
{
|
|
337
|
+
type: "button",
|
|
338
|
+
onClick: () => onRowActivate(row, rowIndex),
|
|
339
|
+
className: "rounded px-1 py-0.5 text-left text-sm text-foreground underline focus:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
340
|
+
children: label
|
|
341
|
+
},
|
|
342
|
+
rowIndex
|
|
343
|
+
);
|
|
344
|
+
})
|
|
345
|
+
}
|
|
346
|
+
)
|
|
347
|
+
]
|
|
292
348
|
}
|
|
293
349
|
)
|
|
294
350
|
] });
|
|
@@ -341,6 +397,14 @@ function useLiveData(config = {}) {
|
|
|
341
397
|
}
|
|
342
398
|
|
|
343
399
|
// src/shared/formatters.ts
|
|
400
|
+
function toCellValue(value) {
|
|
401
|
+
if (value == null) return null;
|
|
402
|
+
if (typeof value === "number" || typeof value === "string" || typeof value === "boolean") {
|
|
403
|
+
return value;
|
|
404
|
+
}
|
|
405
|
+
if (value instanceof Date) return value;
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
344
408
|
var compactThresholds = [
|
|
345
409
|
[1e9, "B"],
|
|
346
410
|
[1e6, "M"],
|
|
@@ -407,6 +471,13 @@ var LineChart = React17.forwardRef(
|
|
|
407
471
|
const legendConfig = resolveLegendConfig(legend);
|
|
408
472
|
const tooltipProps = resolveTooltipProps(tooltip);
|
|
409
473
|
const isAnimated = live ? false : animate;
|
|
474
|
+
const tableColumns = React17.useMemo(
|
|
475
|
+
() => [
|
|
476
|
+
{ key: xAxis.dataKey, label: xAxis.label ?? xAxis.dataKey },
|
|
477
|
+
...series.map((s) => ({ key: s.dataKey, label: s.name }))
|
|
478
|
+
],
|
|
479
|
+
[xAxis.dataKey, xAxis.label, series]
|
|
480
|
+
);
|
|
410
481
|
return /* @__PURE__ */ jsx(
|
|
411
482
|
ChartContainer,
|
|
412
483
|
{
|
|
@@ -415,6 +486,9 @@ var LineChart = React17.forwardRef(
|
|
|
415
486
|
height,
|
|
416
487
|
loading,
|
|
417
488
|
empty: !data?.length,
|
|
489
|
+
ariaLabel: "Line chart",
|
|
490
|
+
tableData: data,
|
|
491
|
+
tableColumns,
|
|
418
492
|
children: /* @__PURE__ */ jsxs(LineChart$1, { data, margin, children: [
|
|
419
493
|
gridConfig && /* @__PURE__ */ jsx(
|
|
420
494
|
CartesianGrid,
|
|
@@ -509,7 +583,8 @@ var BarChart = React17.forwardRef(
|
|
|
509
583
|
className,
|
|
510
584
|
height = "100%",
|
|
511
585
|
margin = { top: 8, right: 12, left: 0, bottom: 4 },
|
|
512
|
-
loading
|
|
586
|
+
loading,
|
|
587
|
+
onDataPointClick
|
|
513
588
|
}, ref) => {
|
|
514
589
|
const gridConfig = React17.useMemo(() => {
|
|
515
590
|
if (grid === false) return null;
|
|
@@ -520,6 +595,36 @@ var BarChart = React17.forwardRef(
|
|
|
520
595
|
const tooltipProps = resolveTooltipProps(tooltip);
|
|
521
596
|
const isAnimated = live ? false : animate;
|
|
522
597
|
const isHorizontal = layout === "horizontal";
|
|
598
|
+
const tableColumns = React17.useMemo(
|
|
599
|
+
() => [
|
|
600
|
+
{ key: xAxis.dataKey, label: xAxis.label ?? xAxis.dataKey },
|
|
601
|
+
...series.map((s) => ({ key: s.dataKey, label: s.name }))
|
|
602
|
+
],
|
|
603
|
+
[xAxis.dataKey, xAxis.label, series]
|
|
604
|
+
);
|
|
605
|
+
const seriesScaleDomains = React17.useMemo(() => {
|
|
606
|
+
const domains = {};
|
|
607
|
+
for (const s of series) {
|
|
608
|
+
const cf = s.conditionalFormat;
|
|
609
|
+
if (cf?.type === "colorScale" && !cf.domain) {
|
|
610
|
+
domains[s.dataKey] = computeScaleDomain(
|
|
611
|
+
data.map((d) => toCellValue(d[s.dataKey]))
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return domains;
|
|
616
|
+
}, [series, data]);
|
|
617
|
+
const resolveBarFill = (s, datum, fallback) => {
|
|
618
|
+
if (!s.conditionalFormat) return fallback;
|
|
619
|
+
const fmt = resolveCellFormat(
|
|
620
|
+
s.conditionalFormat,
|
|
621
|
+
toCellValue(datum[s.dataKey]),
|
|
622
|
+
datum,
|
|
623
|
+
seriesScaleDomains[s.dataKey]
|
|
624
|
+
);
|
|
625
|
+
const color = fmt?.background ?? fmt?.color;
|
|
626
|
+
return color ? resolveFormatColor(color) : fallback;
|
|
627
|
+
};
|
|
523
628
|
return /* @__PURE__ */ jsx(
|
|
524
629
|
ChartContainer,
|
|
525
630
|
{
|
|
@@ -528,6 +633,13 @@ var BarChart = React17.forwardRef(
|
|
|
528
633
|
height,
|
|
529
634
|
loading,
|
|
530
635
|
empty: !data?.length,
|
|
636
|
+
ariaLabel: "Bar chart",
|
|
637
|
+
tableData: data,
|
|
638
|
+
tableColumns,
|
|
639
|
+
onRowActivate: onDataPointClick ? (_row, index) => {
|
|
640
|
+
const datum = data[index];
|
|
641
|
+
if (datum) onDataPointClick(datum, index);
|
|
642
|
+
} : void 0,
|
|
531
643
|
children: /* @__PURE__ */ jsxs(
|
|
532
644
|
BarChart$1,
|
|
533
645
|
{
|
|
@@ -626,19 +738,36 @@ var BarChart = React17.forwardRef(
|
|
|
626
738
|
content: /* @__PURE__ */ jsx(ChartLegendContent, {})
|
|
627
739
|
}
|
|
628
740
|
),
|
|
629
|
-
series.map((s, index) =>
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
741
|
+
series.map((s, index) => {
|
|
742
|
+
const baseFill = getChartColor(index, s.color);
|
|
743
|
+
const interactive = !!onDataPointClick;
|
|
744
|
+
const perDatum = !!s.conditionalFormat || interactive;
|
|
745
|
+
return /* @__PURE__ */ jsx(
|
|
746
|
+
Bar,
|
|
747
|
+
{
|
|
748
|
+
dataKey: s.dataKey,
|
|
749
|
+
name: s.name,
|
|
750
|
+
fill: baseFill,
|
|
751
|
+
stackId: s.stackId,
|
|
752
|
+
radius: s.radius ?? 0,
|
|
753
|
+
isAnimationActive: isAnimated,
|
|
754
|
+
animationDuration: isAnimated ? 300 : 0,
|
|
755
|
+
onClick: interactive ? (_data, i) => {
|
|
756
|
+
const datum = data[i];
|
|
757
|
+
if (datum) onDataPointClick?.(datum, i);
|
|
758
|
+
} : void 0,
|
|
759
|
+
children: perDatum ? data.map((d, i) => /* @__PURE__ */ jsx(
|
|
760
|
+
Cell,
|
|
761
|
+
{
|
|
762
|
+
fill: resolveBarFill(s, d, baseFill),
|
|
763
|
+
cursor: interactive ? "pointer" : void 0
|
|
764
|
+
},
|
|
765
|
+
`${s.dataKey}-${i}`
|
|
766
|
+
)) : null
|
|
767
|
+
},
|
|
768
|
+
s.dataKey
|
|
769
|
+
);
|
|
770
|
+
})
|
|
642
771
|
]
|
|
643
772
|
}
|
|
644
773
|
)
|
|
@@ -671,6 +800,13 @@ var AreaChart = React17.forwardRef(
|
|
|
671
800
|
const legendConfig = resolveLegendConfig(legend);
|
|
672
801
|
const tooltipProps = resolveTooltipProps(tooltip);
|
|
673
802
|
const isAnimated = live ? false : animate;
|
|
803
|
+
const tableColumns = React17.useMemo(
|
|
804
|
+
() => [
|
|
805
|
+
{ key: xAxis.dataKey, label: xAxis.label ?? xAxis.dataKey },
|
|
806
|
+
...series.map((s) => ({ key: s.dataKey, label: s.name }))
|
|
807
|
+
],
|
|
808
|
+
[xAxis.dataKey, xAxis.label, series]
|
|
809
|
+
);
|
|
674
810
|
return /* @__PURE__ */ jsx(
|
|
675
811
|
ChartContainer,
|
|
676
812
|
{
|
|
@@ -679,6 +815,9 @@ var AreaChart = React17.forwardRef(
|
|
|
679
815
|
height,
|
|
680
816
|
loading,
|
|
681
817
|
empty: !data?.length,
|
|
818
|
+
ariaLabel: "Area chart",
|
|
819
|
+
tableData: data,
|
|
820
|
+
tableColumns,
|
|
682
821
|
children: /* @__PURE__ */ jsxs(AreaChart$1, { data, margin, children: [
|
|
683
822
|
/* @__PURE__ */ jsx("defs", { children: series.map((s, index) => {
|
|
684
823
|
const color = getChartColor(index, s.color);
|
|
@@ -799,6 +938,22 @@ var ScatterChart = React17.forwardRef(
|
|
|
799
938
|
const tooltipProps = resolveTooltipProps(tooltip);
|
|
800
939
|
const isAnimated = live ? false : animate;
|
|
801
940
|
const hasZ = series.some((s) => s.zDataKey);
|
|
941
|
+
const zDataKey = series.find((s) => s.zDataKey)?.zDataKey;
|
|
942
|
+
const tableColumns = React17.useMemo(() => {
|
|
943
|
+
const cols = [
|
|
944
|
+
{ key: "__series", label: "Series" },
|
|
945
|
+
{ key: xAxis.dataKey, label: xAxis.name ?? xAxis.label ?? xAxis.dataKey },
|
|
946
|
+
{ key: "y", label: yAxis?.name ?? yAxis?.label ?? "y" }
|
|
947
|
+
];
|
|
948
|
+
if (zDataKey) cols.push({ key: zDataKey, label: zDataKey });
|
|
949
|
+
return cols;
|
|
950
|
+
}, [xAxis.dataKey, xAxis.name, xAxis.label, yAxis?.name, yAxis?.label, zDataKey]);
|
|
951
|
+
const tableData = React17.useMemo(
|
|
952
|
+
() => series.flatMap(
|
|
953
|
+
(s) => (s.data ?? []).map((point) => ({ __series: s.name, ...point }))
|
|
954
|
+
),
|
|
955
|
+
[series]
|
|
956
|
+
);
|
|
802
957
|
return /* @__PURE__ */ jsx(
|
|
803
958
|
ChartContainer,
|
|
804
959
|
{
|
|
@@ -807,6 +962,9 @@ var ScatterChart = React17.forwardRef(
|
|
|
807
962
|
height,
|
|
808
963
|
loading,
|
|
809
964
|
empty: !series.some((s) => s.data?.length),
|
|
965
|
+
ariaLabel: "Scatter chart",
|
|
966
|
+
tableData,
|
|
967
|
+
tableColumns,
|
|
810
968
|
children: /* @__PURE__ */ jsxs(ScatterChart$1, { margin, children: [
|
|
811
969
|
gridConfig && /* @__PURE__ */ jsx(
|
|
812
970
|
CartesianGrid,
|
|
@@ -908,6 +1066,13 @@ var ComposedChart = React17.forwardRef(
|
|
|
908
1066
|
const legendConfig = resolveLegendConfig(legend);
|
|
909
1067
|
const tooltipProps = resolveTooltipProps(tooltip);
|
|
910
1068
|
const isAnimated = live ? false : animate;
|
|
1069
|
+
const tableColumns = React17.useMemo(
|
|
1070
|
+
() => [
|
|
1071
|
+
{ key: xAxis.dataKey, label: xAxis.label ?? xAxis.dataKey },
|
|
1072
|
+
...series.map((s) => ({ key: s.dataKey, label: s.name }))
|
|
1073
|
+
],
|
|
1074
|
+
[xAxis.dataKey, xAxis.label, series]
|
|
1075
|
+
);
|
|
911
1076
|
return /* @__PURE__ */ jsx(
|
|
912
1077
|
ChartContainer,
|
|
913
1078
|
{
|
|
@@ -916,6 +1081,9 @@ var ComposedChart = React17.forwardRef(
|
|
|
916
1081
|
height,
|
|
917
1082
|
loading,
|
|
918
1083
|
empty: !data?.length,
|
|
1084
|
+
ariaLabel: "Composed chart",
|
|
1085
|
+
tableData: data,
|
|
1086
|
+
tableColumns,
|
|
919
1087
|
children: /* @__PURE__ */ jsxs(ComposedChart$1, { data, margin, children: [
|
|
920
1088
|
gridConfig && /* @__PURE__ */ jsx(
|
|
921
1089
|
CartesianGrid,
|
|
@@ -1094,6 +1262,13 @@ var WaterfallChart = React17.forwardRef(
|
|
|
1094
1262
|
const posColor = positiveColor ?? getSemanticColor("positive");
|
|
1095
1263
|
const negColor = negativeColor ?? getSemanticColor("negative");
|
|
1096
1264
|
const totColor = totalColor ?? "hsl(var(--chart-3))";
|
|
1265
|
+
const tableColumns = React17.useMemo(
|
|
1266
|
+
() => [
|
|
1267
|
+
{ key: "name", label: xAxis?.label ?? "Name" },
|
|
1268
|
+
{ key: "value", label: yAxis?.label ?? "Value" }
|
|
1269
|
+
],
|
|
1270
|
+
[xAxis?.label, yAxis?.label]
|
|
1271
|
+
);
|
|
1097
1272
|
return /* @__PURE__ */ jsx(
|
|
1098
1273
|
ChartContainer,
|
|
1099
1274
|
{
|
|
@@ -1102,6 +1277,9 @@ var WaterfallChart = React17.forwardRef(
|
|
|
1102
1277
|
height,
|
|
1103
1278
|
loading,
|
|
1104
1279
|
empty: !data?.length,
|
|
1280
|
+
ariaLabel: "Waterfall chart",
|
|
1281
|
+
tableData: data,
|
|
1282
|
+
tableColumns,
|
|
1105
1283
|
children: /* @__PURE__ */ jsxs(BarChart$1, { data: rows, margin, children: [
|
|
1106
1284
|
/* @__PURE__ */ jsx(
|
|
1107
1285
|
CartesianGrid,
|
|
@@ -1230,6 +1408,12 @@ var PieChart = React17.forwardRef(
|
|
|
1230
1408
|
height,
|
|
1231
1409
|
loading,
|
|
1232
1410
|
empty: !data?.length,
|
|
1411
|
+
ariaLabel: "Pie chart",
|
|
1412
|
+
tableData: data,
|
|
1413
|
+
tableColumns: [
|
|
1414
|
+
{ key: "name", label: "Name" },
|
|
1415
|
+
{ key: "value", label: "Value" }
|
|
1416
|
+
],
|
|
1233
1417
|
children: /* @__PURE__ */ jsxs(PieChart$1, { margin, children: [
|
|
1234
1418
|
/* @__PURE__ */ jsx(
|
|
1235
1419
|
Pie,
|
|
@@ -1302,6 +1486,12 @@ var DonutChart = React17.forwardRef(
|
|
|
1302
1486
|
height,
|
|
1303
1487
|
loading,
|
|
1304
1488
|
empty: !data?.length,
|
|
1489
|
+
ariaLabel: centerLabel ?? "Donut chart",
|
|
1490
|
+
tableData: data,
|
|
1491
|
+
tableColumns: [
|
|
1492
|
+
{ key: "name", label: "Name" },
|
|
1493
|
+
{ key: "value", label: "Value" }
|
|
1494
|
+
],
|
|
1305
1495
|
children: /* @__PURE__ */ jsxs(PieChart$1, { margin, children: [
|
|
1306
1496
|
/* @__PURE__ */ jsx(
|
|
1307
1497
|
Pie,
|
|
@@ -1394,6 +1584,13 @@ var RadarChart = React17.forwardRef(
|
|
|
1394
1584
|
const legendConfig = resolveLegendConfig(legend);
|
|
1395
1585
|
const tooltipProps = resolveTooltipProps(tooltip);
|
|
1396
1586
|
const isAnimated = live ? false : animate;
|
|
1587
|
+
const tableColumns = React17.useMemo(
|
|
1588
|
+
() => [
|
|
1589
|
+
{ key: categoryKey, label: categoryKey },
|
|
1590
|
+
...series.map((s) => ({ key: s.dataKey, label: s.name }))
|
|
1591
|
+
],
|
|
1592
|
+
[categoryKey, series]
|
|
1593
|
+
);
|
|
1397
1594
|
return /* @__PURE__ */ jsx(
|
|
1398
1595
|
ChartContainer,
|
|
1399
1596
|
{
|
|
@@ -1402,6 +1599,9 @@ var RadarChart = React17.forwardRef(
|
|
|
1402
1599
|
height,
|
|
1403
1600
|
loading,
|
|
1404
1601
|
empty: !data?.length,
|
|
1602
|
+
ariaLabel: "Radar chart",
|
|
1603
|
+
tableData: data,
|
|
1604
|
+
tableColumns,
|
|
1405
1605
|
children: /* @__PURE__ */ jsxs(RadarChart$1, { data, margin, cx: "50%", cy: "50%", children: [
|
|
1406
1606
|
/* @__PURE__ */ jsx(PolarGrid, { stroke: "hsl(var(--divider))" }),
|
|
1407
1607
|
/* @__PURE__ */ jsx(
|
|
@@ -1487,6 +1687,12 @@ var RadialBarChart = React17.forwardRef(
|
|
|
1487
1687
|
height,
|
|
1488
1688
|
loading,
|
|
1489
1689
|
empty: !data?.length,
|
|
1690
|
+
ariaLabel: "Radial bar chart",
|
|
1691
|
+
tableData: data,
|
|
1692
|
+
tableColumns: [
|
|
1693
|
+
{ key: "name", label: "Name" },
|
|
1694
|
+
{ key: "value", label: "Value" }
|
|
1695
|
+
],
|
|
1490
1696
|
children: /* @__PURE__ */ jsxs(
|
|
1491
1697
|
RadialBarChart$1,
|
|
1492
1698
|
{
|
|
@@ -1604,6 +1810,14 @@ var SankeyChart = React17.forwardRef(
|
|
|
1604
1810
|
loading
|
|
1605
1811
|
}, ref) => {
|
|
1606
1812
|
const tooltipProps = resolveTooltipProps(tooltip);
|
|
1813
|
+
const tableData = React17.useMemo(
|
|
1814
|
+
() => (data?.links ?? []).map((link) => ({
|
|
1815
|
+
source: data.nodes[link.source]?.name ?? String(link.source),
|
|
1816
|
+
target: data.nodes[link.target]?.name ?? String(link.target),
|
|
1817
|
+
value: link.value
|
|
1818
|
+
})),
|
|
1819
|
+
[data]
|
|
1820
|
+
);
|
|
1607
1821
|
return /* @__PURE__ */ jsx(
|
|
1608
1822
|
ChartContainer,
|
|
1609
1823
|
{
|
|
@@ -1612,6 +1826,13 @@ var SankeyChart = React17.forwardRef(
|
|
|
1612
1826
|
height,
|
|
1613
1827
|
loading,
|
|
1614
1828
|
empty: !data?.nodes?.length,
|
|
1829
|
+
ariaLabel: "Sankey diagram",
|
|
1830
|
+
tableData,
|
|
1831
|
+
tableColumns: [
|
|
1832
|
+
{ key: "source", label: "Source" },
|
|
1833
|
+
{ key: "target", label: "Target" },
|
|
1834
|
+
{ key: "value", label: "Value" }
|
|
1835
|
+
],
|
|
1615
1836
|
children: /* @__PURE__ */ jsx(
|
|
1616
1837
|
Sankey,
|
|
1617
1838
|
{
|
|
@@ -1629,6 +1850,20 @@ var SankeyChart = React17.forwardRef(
|
|
|
1629
1850
|
}
|
|
1630
1851
|
);
|
|
1631
1852
|
SankeyChart.displayName = "SankeyChart";
|
|
1853
|
+
function flattenTreemap(nodes) {
|
|
1854
|
+
const rows = [];
|
|
1855
|
+
const walk = (items) => {
|
|
1856
|
+
for (const item of items) {
|
|
1857
|
+
if (item.children && item.children.length > 0) {
|
|
1858
|
+
walk(item.children);
|
|
1859
|
+
} else {
|
|
1860
|
+
rows.push({ name: item.name, value: item.value ?? null });
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
};
|
|
1864
|
+
walk(nodes);
|
|
1865
|
+
return rows;
|
|
1866
|
+
}
|
|
1632
1867
|
function TreemapContent({
|
|
1633
1868
|
x,
|
|
1634
1869
|
y,
|
|
@@ -1642,6 +1877,7 @@ function TreemapContent({
|
|
|
1642
1877
|
const fill = color ?? getChartColor(index);
|
|
1643
1878
|
const showLabel = width > 40 && height > 20;
|
|
1644
1879
|
return /* @__PURE__ */ jsxs("g", { children: [
|
|
1880
|
+
name && /* @__PURE__ */ jsx("title", { children: name }),
|
|
1645
1881
|
/* @__PURE__ */ jsx(
|
|
1646
1882
|
"rect",
|
|
1647
1883
|
{
|
|
@@ -1682,11 +1918,11 @@ var TreemapChart = React17.forwardRef(
|
|
|
1682
1918
|
live = false,
|
|
1683
1919
|
className,
|
|
1684
1920
|
height = "100%",
|
|
1685
|
-
margin,
|
|
1686
1921
|
loading
|
|
1687
1922
|
}, ref) => {
|
|
1688
1923
|
const tooltipProps = resolveTooltipProps(tooltip);
|
|
1689
1924
|
const isAnimated = live ? false : animate;
|
|
1925
|
+
const tableData = React17.useMemo(() => flattenTreemap(data), [data]);
|
|
1690
1926
|
return /* @__PURE__ */ jsx(
|
|
1691
1927
|
ChartContainer,
|
|
1692
1928
|
{
|
|
@@ -1695,6 +1931,12 @@ var TreemapChart = React17.forwardRef(
|
|
|
1695
1931
|
height,
|
|
1696
1932
|
loading,
|
|
1697
1933
|
empty: !data?.length,
|
|
1934
|
+
ariaLabel: "Treemap chart",
|
|
1935
|
+
tableData,
|
|
1936
|
+
tableColumns: [
|
|
1937
|
+
{ key: "name", label: "Name" },
|
|
1938
|
+
{ key: "value", label: "Value" }
|
|
1939
|
+
],
|
|
1698
1940
|
children: /* @__PURE__ */ jsx(
|
|
1699
1941
|
Treemap,
|
|
1700
1942
|
{
|
|
@@ -1734,6 +1976,12 @@ var FunnelChart = React17.forwardRef(
|
|
|
1734
1976
|
height,
|
|
1735
1977
|
loading,
|
|
1736
1978
|
empty: !data?.length,
|
|
1979
|
+
ariaLabel: "Funnel chart",
|
|
1980
|
+
tableData: data,
|
|
1981
|
+
tableColumns: [
|
|
1982
|
+
{ key: "name", label: "Stage" },
|
|
1983
|
+
{ key: "value", label: "Value" }
|
|
1984
|
+
],
|
|
1737
1985
|
children: /* @__PURE__ */ jsxs(FunnelChart$1, { margin, children: [
|
|
1738
1986
|
/* @__PURE__ */ jsxs(
|
|
1739
1987
|
Funnel,
|
|
@@ -1974,9 +2222,13 @@ var GanttChart = React17.forwardRef(
|
|
|
1974
2222
|
const color = task.color ?? getChartColor(i);
|
|
1975
2223
|
const isHovered = hoveredId === task.id;
|
|
1976
2224
|
const progress = task.progress ?? 0;
|
|
2225
|
+
const taskLabel = `${task.name}, ${task.start.toLocaleDateString()} to ${task.end.toLocaleDateString()}${task.progress != null ? `, ${Math.round((task.progress ?? 0) * 100)}% complete` : ""}`;
|
|
1977
2226
|
return /* @__PURE__ */ jsxs(
|
|
1978
2227
|
"g",
|
|
1979
2228
|
{
|
|
2229
|
+
tabIndex: 0,
|
|
2230
|
+
role: onTaskClick ? "button" : "img",
|
|
2231
|
+
"aria-label": taskLabel,
|
|
1980
2232
|
onMouseEnter: (e) => {
|
|
1981
2233
|
setHoveredId(task.id);
|
|
1982
2234
|
const rect = containerRef.current?.getBoundingClientRect();
|
|
@@ -1990,7 +2242,22 @@ var GanttChart = React17.forwardRef(
|
|
|
1990
2242
|
setHoveredId(null);
|
|
1991
2243
|
setTooltipInfo(null);
|
|
1992
2244
|
},
|
|
2245
|
+
onFocus: () => {
|
|
2246
|
+
setHoveredId(task.id);
|
|
2247
|
+
setTooltipInfo({ task, x: barX, y: barY });
|
|
2248
|
+
},
|
|
2249
|
+
onBlur: () => {
|
|
2250
|
+
setHoveredId(null);
|
|
2251
|
+
setTooltipInfo(null);
|
|
2252
|
+
},
|
|
2253
|
+
onKeyDown: (e) => {
|
|
2254
|
+
if (onTaskClick && (e.key === "Enter" || e.key === " ")) {
|
|
2255
|
+
e.preventDefault();
|
|
2256
|
+
onTaskClick(task);
|
|
2257
|
+
}
|
|
2258
|
+
},
|
|
1993
2259
|
onClick: () => onTaskClick?.(task),
|
|
2260
|
+
className: "focus:outline-none focus-visible:[outline:2px_solid_hsl(var(--ring))]",
|
|
1994
2261
|
style: { cursor: onTaskClick ? "pointer" : "default" },
|
|
1995
2262
|
children: [
|
|
1996
2263
|
i % 2 === 0 && /* @__PURE__ */ jsx(
|
|
@@ -2199,6 +2466,7 @@ var HeatmapChart = React17.forwardRef(
|
|
|
2199
2466
|
}, ref) {
|
|
2200
2467
|
const containerRef = React17.useRef(null);
|
|
2201
2468
|
const [hoveredCell, setHoveredCell] = React17.useState(null);
|
|
2469
|
+
const [focusedKey, setFocusedKey] = React17.useState(null);
|
|
2202
2470
|
const [tooltipPos, setTooltipPos] = React17.useState({ x: 0, y: 0 });
|
|
2203
2471
|
const { valueMap, minVal, maxVal } = React17.useMemo(() => {
|
|
2204
2472
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -2281,9 +2549,15 @@ var HeatmapChart = React17.forwardRef(
|
|
|
2281
2549
|
const cx = labelColWidth + xi * cellSize;
|
|
2282
2550
|
const cy = 28 + yi * cellSize;
|
|
2283
2551
|
const cellData = { x: xLabel, y: yLabel, value: val ?? 0 };
|
|
2552
|
+
const isInteractive = !!onCellClick;
|
|
2553
|
+
const valueText = val != null ? valueFormatter ? valueFormatter(val) : val.toLocaleString() : "no value";
|
|
2554
|
+
const cellLabel = `${yLabel}, ${xLabel}: ${valueText}`;
|
|
2284
2555
|
return /* @__PURE__ */ jsxs(
|
|
2285
2556
|
"g",
|
|
2286
2557
|
{
|
|
2558
|
+
role: isInteractive ? "button" : "img",
|
|
2559
|
+
tabIndex: 0,
|
|
2560
|
+
"aria-label": cellLabel,
|
|
2287
2561
|
onMouseEnter: (e) => {
|
|
2288
2562
|
setHoveredCell(cellData);
|
|
2289
2563
|
const rect = containerRef.current?.getBoundingClientRect();
|
|
@@ -2293,8 +2567,26 @@ var HeatmapChart = React17.forwardRef(
|
|
|
2293
2567
|
});
|
|
2294
2568
|
},
|
|
2295
2569
|
onMouseLeave: () => setHoveredCell(null),
|
|
2570
|
+
onFocus: () => {
|
|
2571
|
+
setHoveredCell(cellData);
|
|
2572
|
+
setFocusedKey(key);
|
|
2573
|
+
setTooltipPos({
|
|
2574
|
+
x: cx + cellSize / 2,
|
|
2575
|
+
y: cy
|
|
2576
|
+
});
|
|
2577
|
+
},
|
|
2578
|
+
onBlur: () => {
|
|
2579
|
+
setHoveredCell(null);
|
|
2580
|
+
setFocusedKey(null);
|
|
2581
|
+
},
|
|
2296
2582
|
onClick: () => onCellClick?.(cellData),
|
|
2297
|
-
|
|
2583
|
+
onKeyDown: (e) => {
|
|
2584
|
+
if (isInteractive && (e.key === "Enter" || e.key === " ")) {
|
|
2585
|
+
e.preventDefault();
|
|
2586
|
+
onCellClick?.(cellData);
|
|
2587
|
+
}
|
|
2588
|
+
},
|
|
2589
|
+
style: { cursor: onCellClick ? "pointer" : "default", outline: "none" },
|
|
2298
2590
|
children: [
|
|
2299
2591
|
/* @__PURE__ */ jsx(
|
|
2300
2592
|
"rect",
|
|
@@ -2306,8 +2598,8 @@ var HeatmapChart = React17.forwardRef(
|
|
|
2306
2598
|
rx: 3,
|
|
2307
2599
|
fill: val != null ? interpolateColor(t, colorScale) : "hsl(var(--muted))",
|
|
2308
2600
|
fillOpacity: val != null ? 0.85 : 0.3,
|
|
2309
|
-
stroke: hoveredCell?.x === xLabel && hoveredCell?.y === yLabel ? "hsl(var(--foreground))" : "none",
|
|
2310
|
-
strokeWidth: 1.5
|
|
2601
|
+
stroke: focusedKey === key ? "hsl(var(--ring))" : hoveredCell?.x === xLabel && hoveredCell?.y === yLabel ? "hsl(var(--foreground))" : "none",
|
|
2602
|
+
strokeWidth: focusedKey === key ? 2.5 : 1.5
|
|
2311
2603
|
}
|
|
2312
2604
|
),
|
|
2313
2605
|
showValues && val != null && cellSize >= 32 && /* @__PURE__ */ jsx(
|
|
@@ -2937,6 +3229,18 @@ var labelStyle = {
|
|
|
2937
3229
|
marginBottom: 3,
|
|
2938
3230
|
display: "block"
|
|
2939
3231
|
};
|
|
3232
|
+
var FOCUS_STYLE_ID = "opti-chart-builder-focus";
|
|
3233
|
+
var focusStyles = `
|
|
3234
|
+
.opti-chart-builder-control:focus-visible {
|
|
3235
|
+
outline: 2px solid hsl(var(--ring));
|
|
3236
|
+
outline-offset: 1px;
|
|
3237
|
+
border-radius: 4px;
|
|
3238
|
+
}
|
|
3239
|
+
`;
|
|
3240
|
+
function FocusStyles() {
|
|
3241
|
+
return /* @__PURE__ */ jsx("style", { id: FOCUS_STYLE_ID, children: focusStyles });
|
|
3242
|
+
}
|
|
3243
|
+
var FOCUS_CLASS = "opti-chart-builder-control";
|
|
2940
3244
|
var ChartBuilder = React17.memo(function ChartBuilder2({
|
|
2941
3245
|
columns,
|
|
2942
3246
|
data,
|
|
@@ -2979,6 +3283,7 @@ var ChartBuilder = React17.memo(function ChartBuilder2({
|
|
|
2979
3283
|
className: cn("flex gap-4", className),
|
|
2980
3284
|
style: { minHeight: 400 },
|
|
2981
3285
|
children: [
|
|
3286
|
+
/* @__PURE__ */ jsx(FocusStyles, {}),
|
|
2982
3287
|
/* @__PURE__ */ jsxs(
|
|
2983
3288
|
"div",
|
|
2984
3289
|
{
|
|
@@ -2993,10 +3298,12 @@ var ChartBuilder = React17.memo(function ChartBuilder2({
|
|
|
2993
3298
|
children: [
|
|
2994
3299
|
/* @__PURE__ */ jsx("div", { style: { fontSize: 13, fontWeight: 600, color: "hsl(var(--foreground))" }, children: "Chart Builder" }),
|
|
2995
3300
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
2996
|
-
/* @__PURE__ */ jsx("label", { style: labelStyle, children: "Chart Type" }),
|
|
3301
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "cb-chart-type", style: labelStyle, children: "Chart Type" }),
|
|
2997
3302
|
/* @__PURE__ */ jsx(
|
|
2998
3303
|
"select",
|
|
2999
3304
|
{
|
|
3305
|
+
id: "cb-chart-type",
|
|
3306
|
+
className: FOCUS_CLASS,
|
|
3000
3307
|
style: selectStyle,
|
|
3001
3308
|
value: config.type,
|
|
3002
3309
|
onChange: (e) => setConfig((prev) => ({ ...prev, type: e.target.value })),
|
|
@@ -3005,10 +3312,12 @@ var ChartBuilder = React17.memo(function ChartBuilder2({
|
|
|
3005
3312
|
)
|
|
3006
3313
|
] }),
|
|
3007
3314
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
3008
|
-
/* @__PURE__ */ jsx("label", { style: labelStyle, children: "Category Axis" }),
|
|
3315
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "cb-category-axis", style: labelStyle, children: "Category Axis" }),
|
|
3009
3316
|
/* @__PURE__ */ jsx(
|
|
3010
3317
|
"select",
|
|
3011
3318
|
{
|
|
3319
|
+
id: "cb-category-axis",
|
|
3320
|
+
className: FOCUS_CLASS,
|
|
3012
3321
|
style: selectStyle,
|
|
3013
3322
|
value: config.categoryKey,
|
|
3014
3323
|
onChange: (e) => setConfig((prev) => ({ ...prev, categoryKey: e.target.value })),
|
|
@@ -3016,8 +3325,8 @@ var ChartBuilder = React17.memo(function ChartBuilder2({
|
|
|
3016
3325
|
}
|
|
3017
3326
|
)
|
|
3018
3327
|
] }),
|
|
3019
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
3020
|
-
/* @__PURE__ */ jsx("
|
|
3328
|
+
/* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Series", children: [
|
|
3329
|
+
/* @__PURE__ */ jsx("div", { style: labelStyle, children: "Series" }),
|
|
3021
3330
|
config.series.map((s, i) => /* @__PURE__ */ jsxs(
|
|
3022
3331
|
"div",
|
|
3023
3332
|
{
|
|
@@ -3027,6 +3336,8 @@ var ChartBuilder = React17.memo(function ChartBuilder2({
|
|
|
3027
3336
|
/* @__PURE__ */ jsx(
|
|
3028
3337
|
"select",
|
|
3029
3338
|
{
|
|
3339
|
+
className: FOCUS_CLASS,
|
|
3340
|
+
"aria-label": `Series ${i + 1} data column`,
|
|
3030
3341
|
style: { ...selectStyle, flex: 1 },
|
|
3031
3342
|
value: s.dataKey,
|
|
3032
3343
|
onChange: (e) => updateSeries(i, "dataKey", e.target.value),
|
|
@@ -3036,6 +3347,9 @@ var ChartBuilder = React17.memo(function ChartBuilder2({
|
|
|
3036
3347
|
/* @__PURE__ */ jsx(
|
|
3037
3348
|
"button",
|
|
3038
3349
|
{
|
|
3350
|
+
type: "button",
|
|
3351
|
+
"aria-label": `Remove series ${i + 1}`,
|
|
3352
|
+
className: FOCUS_CLASS,
|
|
3039
3353
|
onClick: () => removeSeries(i),
|
|
3040
3354
|
style: {
|
|
3041
3355
|
padding: "2px 6px",
|
|
@@ -3057,6 +3371,8 @@ var ChartBuilder = React17.memo(function ChartBuilder2({
|
|
|
3057
3371
|
/* @__PURE__ */ jsx(
|
|
3058
3372
|
"button",
|
|
3059
3373
|
{
|
|
3374
|
+
type: "button",
|
|
3375
|
+
className: FOCUS_CLASS,
|
|
3060
3376
|
onClick: addSeries,
|
|
3061
3377
|
style: {
|
|
3062
3378
|
width: "100%",
|
|
@@ -3074,10 +3390,12 @@ var ChartBuilder = React17.memo(function ChartBuilder2({
|
|
|
3074
3390
|
)
|
|
3075
3391
|
] }),
|
|
3076
3392
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
3077
|
-
/* @__PURE__ */ jsx("label", { style: labelStyle, children: "Legend" }),
|
|
3393
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "cb-legend", style: labelStyle, children: "Legend" }),
|
|
3078
3394
|
/* @__PURE__ */ jsx(
|
|
3079
3395
|
"select",
|
|
3080
3396
|
{
|
|
3397
|
+
id: "cb-legend",
|
|
3398
|
+
className: FOCUS_CLASS,
|
|
3081
3399
|
style: selectStyle,
|
|
3082
3400
|
value: config.legend,
|
|
3083
3401
|
onChange: (e) => setConfig((prev) => ({ ...prev, legend: e.target.value })),
|
|
@@ -3091,6 +3409,8 @@ var ChartBuilder = React17.memo(function ChartBuilder2({
|
|
|
3091
3409
|
{
|
|
3092
3410
|
type: "checkbox",
|
|
3093
3411
|
id: "grid-toggle",
|
|
3412
|
+
"aria-label": "Show Grid",
|
|
3413
|
+
className: FOCUS_CLASS,
|
|
3094
3414
|
checked: config.grid,
|
|
3095
3415
|
onChange: (e) => setConfig((prev) => ({ ...prev, grid: e.target.checked }))
|
|
3096
3416
|
}
|
|
@@ -3103,6 +3423,8 @@ var ChartBuilder = React17.memo(function ChartBuilder2({
|
|
|
3103
3423
|
{
|
|
3104
3424
|
type: "checkbox",
|
|
3105
3425
|
id: "animate-toggle",
|
|
3426
|
+
"aria-label": "Animate",
|
|
3427
|
+
className: FOCUS_CLASS,
|
|
3106
3428
|
checked: config.animate,
|
|
3107
3429
|
onChange: (e) => setConfig((prev) => ({ ...prev, animate: e.target.checked }))
|
|
3108
3430
|
}
|
|
@@ -3112,6 +3434,8 @@ var ChartBuilder = React17.memo(function ChartBuilder2({
|
|
|
3112
3434
|
onSave && /* @__PURE__ */ jsx(
|
|
3113
3435
|
"button",
|
|
3114
3436
|
{
|
|
3437
|
+
type: "button",
|
|
3438
|
+
className: FOCUS_CLASS,
|
|
3115
3439
|
onClick: () => onSave(config),
|
|
3116
3440
|
style: {
|
|
3117
3441
|
marginTop: 8,
|