@sckyzo/cosmos-charts 0.0.2 → 0.2.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  var react = require('react');
4
4
  require('apexcharts/line');
5
- var ReactApexChart = require('react-apexcharts/core');
5
+ var ReactApexChart13 = require('react-apexcharts/core');
6
6
  var jsxRuntime = require('react/jsx-runtime');
7
7
  require('apexcharts/features/annotations');
8
8
  require('apexcharts/radialBar');
@@ -10,45 +10,105 @@ require('apexcharts/donut');
10
10
  require('apexcharts/features/legend');
11
11
  require('apexcharts/treemap');
12
12
  require('apexcharts/bar');
13
+ require('apexcharts/heatmap');
14
+ require('apexcharts/radar');
15
+ require('apexcharts/boxPlot');
16
+ require('apexcharts/rangeBar');
13
17
 
14
18
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
15
19
 
16
- var ReactApexChart__default = /*#__PURE__*/_interopDefault(ReactApexChart);
20
+ var ReactApexChart13__default = /*#__PURE__*/_interopDefault(ReactApexChart13);
17
21
 
18
22
  var COSMOS_BRAND = "#465fff";
19
23
  var COSMOS_PALETTE = ["#465fff", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6"];
24
+ var DARK_THEME_CLASSES = ["dark", "deep-blue"];
25
+ var BRAND_SCALE_LIGHT = [100, 300, 400, 500, 700];
26
+ var BRAND_SCALE_DARK = [900, 700, 500, 400, 300];
27
+ var BLUE_STEPS = {
28
+ 100: "#dde9ff",
29
+ 300: "#9cb9ff",
30
+ 400: "#7592ff",
31
+ 500: COSMOS_BRAND,
32
+ 700: "#2a31d8",
33
+ 900: "#262e89"
34
+ };
20
35
  var detectDark = () => {
21
36
  if (typeof document === "undefined") return false;
22
- return document.documentElement.classList.contains("dark");
37
+ const { classList } = document.documentElement;
38
+ return DARK_THEME_CLASSES.some((c) => classList.contains(c));
39
+ };
40
+ var readCssVar = (name, fallback) => {
41
+ if (typeof document === "undefined") return fallback;
42
+ return getComputedStyle(document.documentElement).getPropertyValue(name).trim() || fallback;
23
43
  };
24
- var useDarkMode = () => {
25
- const [dark, setDark] = react.useState(detectDark);
44
+ var readChartColors = () => {
45
+ const dark = detectDark();
46
+ return {
47
+ dark,
48
+ brand: readCssVar("--color-brand-500", COSMOS_BRAND),
49
+ brandScale: (dark ? BRAND_SCALE_DARK : BRAND_SCALE_LIGHT).map(
50
+ (step) => readCssVar(`--color-brand-${step}`, BLUE_STEPS[step])
51
+ ),
52
+ // The card background (`bg-white dark:bg-gray-900`).
53
+ surface: dark ? readCssVar("--color-gray-900", "#111827") : readCssVar("--color-white", "#fff"),
54
+ grid: dark ? readCssVar("--color-gray-800", "#1f2937") : readCssVar("--color-gray-200", "#e5e7eb"),
55
+ axis: dark ? readCssVar("--color-gray-400", "#9ca3af") : readCssVar("--color-gray-500", "#6b7280")
56
+ };
57
+ };
58
+ var useHtmlAppearance = (read) => {
59
+ const [value, setValue] = react.useState(read);
26
60
  react.useEffect(() => {
27
61
  if (typeof document === "undefined") return;
28
- const observer = new MutationObserver(() => setDark(detectDark()));
62
+ const observer = new MutationObserver(() => setValue(read));
29
63
  observer.observe(document.documentElement, {
30
64
  attributes: true,
31
- attributeFilter: ["class"]
65
+ attributeFilter: ["class", "data-accent"]
32
66
  });
33
67
  return () => observer.disconnect();
34
- }, []);
35
- return dark;
68
+ }, [read]);
69
+ return value;
36
70
  };
71
+ var useDarkMode = () => useHtmlAppearance(detectDark);
37
72
  var useChartTheme = () => {
38
- const dark = useDarkMode();
73
+ const {
74
+ dark,
75
+ brand,
76
+ brandScale,
77
+ surface,
78
+ grid: gridColor,
79
+ axis
80
+ } = useHtmlAppearance(readChartColors);
39
81
  const grid = {
40
- borderColor: dark ? "#1f2937" : "#e5e7eb",
82
+ borderColor: gridColor,
41
83
  strokeDashArray: 4
42
84
  };
43
85
  const axisLabels = {
44
86
  style: {
45
- colors: dark ? "#9ca3af" : "#6b7280",
87
+ colors: axis,
46
88
  fontSize: "11px",
47
89
  fontFamily: "inherit"
48
90
  }
49
91
  };
50
- return { dark, grid, axisLabels, brand: COSMOS_BRAND, palette: COSMOS_PALETTE };
92
+ return {
93
+ dark,
94
+ grid,
95
+ axisLabels,
96
+ brand,
97
+ brandScale,
98
+ surface,
99
+ palette: [brand, ...COSMOS_PALETTE.slice(1)]
100
+ };
101
+ };
102
+
103
+ // src/escape.ts
104
+ var ENTITIES = {
105
+ "&": "&",
106
+ "<": "&lt;",
107
+ ">": "&gt;",
108
+ '"': "&quot;",
109
+ "'": "&#39;"
51
110
  };
111
+ var escapeHtml = (value) => String(value).replace(/[&<>"']/g, (char) => ENTITIES[char]);
52
112
  var LineAreaChart = ({
53
113
  series,
54
114
  categories,
@@ -68,17 +128,28 @@ var LineAreaChart = ({
68
128
  stroke: { curve: "smooth", width: 2 },
69
129
  fill: { type: "gradient", gradient: { opacityFrom: 0.45, opacityTo: 0.05 } },
70
130
  grid,
131
+ // One labels object per axis: ApexCharts writes the x-axis category
132
+ // formatter onto it, which blanked the y labels on the next update.
71
133
  xaxis: {
72
134
  categories,
73
- labels: axisLabels,
135
+ labels: { ...axisLabels },
74
136
  axisBorder: { show: false },
75
137
  axisTicks: { show: false }
76
138
  },
77
- yaxis: { labels: axisLabels },
139
+ yaxis: { labels: { ...axisLabels } },
78
140
  legend: { show: false },
79
- tooltip: { theme: "dark" }
141
+ // The tooltip is HTML: escape the category and the series name. ApexCharts
142
+ // hands the x formatter the point's position on the axis (1, 2, …), not
143
+ // its category, so read the category back by index.
144
+ tooltip: {
145
+ theme: "dark",
146
+ x: {
147
+ formatter: (v, opts) => escapeHtml(opts === void 0 ? v : categories[opts.dataPointIndex] ?? v)
148
+ },
149
+ y: { title: { formatter: (name) => `${escapeHtml(name)}: ` } }
150
+ }
80
151
  };
81
- return /* @__PURE__ */ jsxRuntime.jsx(ReactApexChart__default.default, { type: "area", options, series, height, width });
152
+ return /* @__PURE__ */ jsxRuntime.jsx(ReactApexChart13__default.default, { type: "area", options, series, height, width });
82
153
  };
83
154
  var defaultGenerator = /* @__PURE__ */ (() => {
84
155
  let last = 50;
@@ -141,7 +212,7 @@ var RealtimeChart = ({
141
212
  tooltip: { theme: "dark" }
142
213
  };
143
214
  return /* @__PURE__ */ jsxRuntime.jsx(
144
- ReactApexChart__default.default,
215
+ ReactApexChart13__default.default,
145
216
  {
146
217
  type: "line",
147
218
  options,
@@ -178,7 +249,7 @@ var RadialBarChart = ({
178
249
  }
179
250
  };
180
251
  return /* @__PURE__ */ jsxRuntime.jsx(
181
- ReactApexChart__default.default,
252
+ ReactApexChart13__default.default,
182
253
  {
183
254
  type: "radialBar",
184
255
  options,
@@ -223,7 +294,7 @@ var GradientRadial = ({
223
294
  labels: [label]
224
295
  };
225
296
  return /* @__PURE__ */ jsxRuntime.jsx(
226
- ReactApexChart__default.default,
297
+ ReactApexChart13__default.default,
227
298
  {
228
299
  type: "radialBar",
229
300
  options,
@@ -258,7 +329,7 @@ var SemiCircleGauge = ({
258
329
  labels: [label]
259
330
  };
260
331
  return /* @__PURE__ */ jsxRuntime.jsx(
261
- ReactApexChart__default.default,
332
+ ReactApexChart13__default.default,
262
333
  {
263
334
  type: "radialBar",
264
335
  options,
@@ -292,7 +363,7 @@ var StrokedGauge = ({
292
363
  labels: [label]
293
364
  };
294
365
  return /* @__PURE__ */ jsxRuntime.jsx(
295
- ReactApexChart__default.default,
366
+ ReactApexChart13__default.default,
296
367
  {
297
368
  type: "radialBar",
298
369
  options,
@@ -314,7 +385,9 @@ var DonutChart = ({
314
385
  chart: { type: "donut", fontFamily: "inherit" },
315
386
  colors: palette,
316
387
  labels,
317
- legend: { position: rightLegend ? "right" : "bottom" },
388
+ // The legend and the tooltip are HTML: escape the labels.
389
+ legend: { position: rightLegend ? "right" : "bottom", formatter: (name) => escapeHtml(name) },
390
+ tooltip: { y: { title: { formatter: (name) => `${escapeHtml(name)}: ` } } },
318
391
  dataLabels: { enabled: false },
319
392
  plotOptions: {
320
393
  pie: {
@@ -322,7 +395,7 @@ var DonutChart = ({
322
395
  }
323
396
  }
324
397
  };
325
- return /* @__PURE__ */ jsxRuntime.jsx(ReactApexChart__default.default, { type: "donut", options, series, height, width });
398
+ return /* @__PURE__ */ jsxRuntime.jsx(ReactApexChart13__default.default, { type: "donut", options, series, height, width });
326
399
  };
327
400
  var DonutRightLegend = (props) => /* @__PURE__ */ jsxRuntime.jsx(DonutChart, { ...props, rightLegend: true });
328
401
  var Sparkline = ({
@@ -341,7 +414,7 @@ var Sparkline = ({
341
414
  tooltip: { fixed: { enabled: false }, x: { show: false }, marker: { show: false } }
342
415
  };
343
416
  return /* @__PURE__ */ jsxRuntime.jsx(
344
- ReactApexChart__default.default,
417
+ ReactApexChart13__default.default,
345
418
  {
346
419
  type: variant,
347
420
  options,
@@ -387,7 +460,7 @@ var TreemapChart = ({ series, height = 320, width = "100%" }) => {
387
460
  plotOptions: { treemap: { distributed: true, enableShades: false } }
388
461
  };
389
462
  return /* @__PURE__ */ jsxRuntime.jsx(
390
- ReactApexChart__default.default,
463
+ ReactApexChart13__default.default,
391
464
  {
392
465
  type: "treemap",
393
466
  options,
@@ -452,27 +525,644 @@ var BarChart = ({
452
525
  position: "top",
453
526
  horizontalAlign: "right",
454
527
  labels: { colors: axisLabels.style.colors },
455
- markers: { size: 5 }
528
+ markers: { size: 5 },
529
+ formatter: (name) => escapeHtml(name)
456
530
  },
531
+ // Tooltips and the legend are HTML: escape names, categories and values.
457
532
  tooltip: {
458
533
  theme: "dark",
459
- y: { formatter: (v) => valueFormatter(Number(v)) }
534
+ x: { formatter: (v) => escapeHtml(v) },
535
+ y: {
536
+ formatter: (v) => escapeHtml(valueFormatter(Number(v))),
537
+ title: { formatter: (name) => `${escapeHtml(name)}: ` }
538
+ }
460
539
  },
461
540
  fill: { opacity: 1 },
462
541
  stroke: { show: false }
463
542
  };
464
- return /* @__PURE__ */ jsxRuntime.jsx(ReactApexChart__default.default, { type: "bar", options, series, height, width });
543
+ return /* @__PURE__ */ jsxRuntime.jsx(ReactApexChart13__default.default, { type: "bar", options, series, height, width });
544
+ };
545
+ var NICE_STEPS = [1, 2, 2.5, 3, 4, 5, 6, 8, 10];
546
+ var niceStep = (value) => {
547
+ const magnitude = 10 ** Math.floor(Math.log10(value));
548
+ const step = NICE_STEPS.find((m) => m * magnitude >= value) ?? 10;
549
+ return step * magnitude;
550
+ };
551
+ var round = (value) => Number(value.toPrecision(12));
552
+ var evenRanges = (values, colors) => {
553
+ const finite = values.filter(Number.isFinite);
554
+ if (finite.length === 0 || colors.length === 0) return [];
555
+ const min = finite.reduce((a, b) => Math.min(a, b));
556
+ const max = finite.reduce((a, b) => Math.max(a, b));
557
+ let step = niceStep((max - min) / colors.length || Math.abs(max) || 1);
558
+ let start = Math.floor(min / step) * step;
559
+ while (start + step * colors.length < max) {
560
+ step = niceStep(step * 1.001);
561
+ start = Math.floor(min / step) * step;
562
+ }
563
+ return colors.map((color, i) => ({
564
+ from: round(start + step * i),
565
+ to: round(start + step * (i + 1)),
566
+ color
567
+ }));
568
+ };
569
+ var textColorOn = (color) => {
570
+ const hex = /^#([0-9a-f]{6})$/i.exec(color)?.[1];
571
+ if (!hex) return void 0;
572
+ const [r, g, b] = [0, 2, 4].map((i) => {
573
+ const c = parseInt(hex.slice(i, i + 2), 16) / 255;
574
+ return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
575
+ });
576
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b > 0.2 ? "#101828" : "#ffffff";
577
+ };
578
+ var HeatmapChart = ({
579
+ series,
580
+ categories,
581
+ colors,
582
+ ranges,
583
+ formatValue,
584
+ showValues = false,
585
+ showLegend = true,
586
+ height = 320,
587
+ width = "100%"
588
+ }) => {
589
+ const { axisLabels, brandScale, surface } = useChartTheme();
590
+ const valueFormatter = formatValue ?? ((v) => String(v));
591
+ const colorRanges = (ranges ?? evenRanges(
592
+ series.flatMap((s) => s.data),
593
+ colors ?? brandScale
594
+ )).map((range) => ({
595
+ ...range,
596
+ name: range.name ?? `${valueFormatter(range.from)} \u2013 ${valueFormatter(range.to)}`,
597
+ foreColor: textColorOn(range.color)
598
+ }));
599
+ const options = {
600
+ chart: {
601
+ type: "heatmap",
602
+ toolbar: { show: false },
603
+ fontFamily: "inherit"
604
+ },
605
+ dataLabels: {
606
+ enabled: showValues,
607
+ formatter: (v) => valueFormatter(Number(v)),
608
+ style: { fontSize: "11px", fontWeight: 500 }
609
+ },
610
+ plotOptions: {
611
+ heatmap: {
612
+ radius: 3,
613
+ // The ranges carry the exact colors: no shading on top of them.
614
+ enableShades: false,
615
+ colorScale: { ranges: colorRanges }
616
+ }
617
+ },
618
+ // Gaps between the cells, in the card's color.
619
+ stroke: { width: 2, colors: [surface] },
620
+ grid: { show: false },
621
+ // One labels object per axis: ApexCharts mutates them. Narrow columns
622
+ // skip the labels that would overlap instead of rotating them into a
623
+ // pile; the tooltip still names every column.
624
+ xaxis: {
625
+ categories,
626
+ labels: { ...axisLabels, rotate: 0 },
627
+ axisBorder: { show: false },
628
+ axisTicks: { show: false },
629
+ tooltip: { enabled: false }
630
+ },
631
+ // ApexCharts draws the first series at the bottom; read top-down instead.
632
+ yaxis: { reversed: true, labels: { ...axisLabels } },
633
+ legend: {
634
+ show: showLegend,
635
+ position: "top",
636
+ horizontalAlign: "right",
637
+ labels: { colors: axisLabels.style.colors },
638
+ // The default stroke outlines the swatches in white, glaring in dark themes.
639
+ markers: { size: 5, strokeWidth: 0 },
640
+ formatter: (name) => escapeHtml(name)
641
+ },
642
+ // The tooltip and the legend are HTML: escape row names, categories,
643
+ // range names and values. The title is the hovered column's category.
644
+ tooltip: {
645
+ theme: "dark",
646
+ x: {
647
+ show: true,
648
+ formatter: (v, opts) => escapeHtml(opts === void 0 ? v : categories[opts.dataPointIndex] ?? v)
649
+ },
650
+ y: {
651
+ formatter: (v) => escapeHtml(valueFormatter(Number(v))),
652
+ title: { formatter: (name) => `${escapeHtml(name)}: ` }
653
+ }
654
+ }
655
+ };
656
+ return /* @__PURE__ */ jsxRuntime.jsx(
657
+ ReactApexChart13__default.default,
658
+ {
659
+ type: "heatmap",
660
+ options,
661
+ series,
662
+ height,
663
+ width
664
+ }
665
+ );
666
+ };
667
+ var RadarChart = ({
668
+ series,
669
+ categories,
670
+ colors,
671
+ formatValue,
672
+ showLegend,
673
+ max,
674
+ height = 340,
675
+ width = "100%"
676
+ }) => {
677
+ const { grid, axisLabels, palette } = useChartTheme();
678
+ const legendVisible = showLegend ?? series.length > 1;
679
+ const valueFormatter = formatValue ?? ((v) => String(v));
680
+ const options = {
681
+ chart: {
682
+ type: "radar",
683
+ toolbar: { show: false },
684
+ fontFamily: "inherit"
685
+ },
686
+ colors: colors ?? palette,
687
+ dataLabels: { enabled: false },
688
+ stroke: { width: 2 },
689
+ fill: { opacity: 0.15 },
690
+ // The tooltip is hit-tested on the markers: keep them.
691
+ markers: { size: 3, strokeWidth: 0 },
692
+ // ApexCharts reserves part of an axis name's width only, so on a narrow
693
+ // chart the names at the side vertices ran off the edge. No effect on a
694
+ // wide chart: its size follows the height.
695
+ grid: { padding: { left: 24, right: 24 } },
696
+ plotOptions: {
697
+ radar: {
698
+ polygons: {
699
+ strokeColors: grid?.borderColor,
700
+ connectorColors: grid?.borderColor,
701
+ fill: { colors: ["transparent"] }
702
+ }
703
+ }
704
+ },
705
+ // One labels object per axis: ApexCharts mutates them.
706
+ xaxis: { categories, labels: { ...axisLabels } },
707
+ yaxis: {
708
+ max,
709
+ labels: { ...axisLabels, formatter: (val) => valueFormatter(Number(val)) }
710
+ },
711
+ legend: {
712
+ show: legendVisible,
713
+ position: "top",
714
+ horizontalAlign: "right",
715
+ labels: { colors: axisLabels.style.colors },
716
+ // The default stroke outlines the swatches in white, glaring in dark themes.
717
+ markers: { size: 5, strokeWidth: 0 },
718
+ formatter: (name) => escapeHtml(name)
719
+ },
720
+ // The tooltip and the legend are HTML: escape names, categories and
721
+ // values. The title is the hovered axis.
722
+ tooltip: {
723
+ theme: "dark",
724
+ x: {
725
+ formatter: (v, opts) => escapeHtml(opts === void 0 ? v : categories[opts.dataPointIndex] ?? v)
726
+ },
727
+ y: {
728
+ formatter: (v) => escapeHtml(valueFormatter(Number(v))),
729
+ title: { formatter: (name) => `${escapeHtml(name)}: ` }
730
+ }
731
+ }
732
+ };
733
+ return /* @__PURE__ */ jsxRuntime.jsx(ReactApexChart13__default.default, { type: "radar", options, series, height, width });
734
+ };
735
+
736
+ // src/tooltip.ts
737
+ var line = (content) => `<div class="apexcharts-tooltip-series-group apexcharts-active" style="display:flex">${content}</div>`;
738
+ var tooltipHtml = ({ title, series, rows }) => (title === void 0 ? "" : `<div class="apexcharts-tooltip-title">${escapeHtml(title)}</div>`) + (series === void 0 ? "" : line(
739
+ `<span class="apexcharts-tooltip-marker" style="background-color:${escapeHtml(series.color)};border-radius:50%"></span><span class="apexcharts-tooltip-text-y-label">${escapeHtml(series.name)}</span>`
740
+ )) + rows.map(
741
+ ([label, value]) => line(
742
+ `<span class="apexcharts-tooltip-text-y-label">${escapeHtml(label)}:</span><span class="apexcharts-tooltip-text-y-value">${escapeHtml(value)}</span>`
743
+ )
744
+ ).join("");
745
+ var formatNumber = (value) => String(Number(value.toFixed(2)));
746
+ var niceAxis = (values, pad = 0, ticks = 5) => {
747
+ const low = Math.min(...values);
748
+ const high = Math.max(...values);
749
+ const margin = (high - low) * pad;
750
+ const min = low >= 0 ? Math.max(low - margin, 0) : low - margin;
751
+ const max = high <= 0 ? Math.min(high + margin, 0) : high + margin;
752
+ if (!(max > min)) return {};
753
+ const rough = (max - min) / ticks;
754
+ const magnitude = 10 ** Math.floor(Math.log10(rough));
755
+ const step = [1, 2, 2.5, 5, 10].map((f) => f * magnitude).find((s) => s >= rough) ?? rough;
756
+ const round2 = (value) => Number(value.toPrecision(12));
757
+ const niceMin = round2(Math.floor(min / step) * step);
758
+ const niceMax = round2(Math.ceil(max / step) * step);
759
+ return { min: niceMin, max: niceMax, tickAmount: Math.round((niceMax - niceMin) / step) };
760
+ };
761
+ var xyOptions = ({ grid, axisLabels, palette }, {
762
+ series,
763
+ xTitle,
764
+ yTitle,
765
+ colors,
766
+ formatX = formatNumber,
767
+ formatY = formatNumber,
768
+ showLegend
769
+ }, { pad = 0, extraRows = () => [] } = {}) => {
770
+ const seriesColors = colors ?? palette;
771
+ const points = series.flatMap(({ data }) => data);
772
+ const axisTitle = (text) => text === void 0 ? void 0 : {
773
+ text,
774
+ style: {
775
+ color: axisLabels.style.colors,
776
+ fontSize: "12px",
777
+ fontFamily: "inherit",
778
+ fontWeight: 500
779
+ }
780
+ };
781
+ return {
782
+ chart: { toolbar: { show: false }, zoom: { enabled: false }, fontFamily: "inherit" },
783
+ colors: seriesColors,
784
+ dataLabels: { enabled: false },
785
+ grid,
786
+ // One labels object per axis: ApexCharts writes formatters onto them.
787
+ // Axis labels and titles are SVG text and stay unescaped.
788
+ xaxis: {
789
+ type: "numeric",
790
+ ...niceAxis(
791
+ points.map(({ x }) => x),
792
+ pad
793
+ ),
794
+ title: axisTitle(xTitle),
795
+ labels: { ...axisLabels, formatter: (value) => formatX(Number(value)) },
796
+ axisBorder: { show: false },
797
+ axisTicks: { show: false },
798
+ // The x-axis tooltip is HTML and repeats the x value of the tooltip.
799
+ tooltip: { enabled: false }
800
+ },
801
+ yaxis: {
802
+ ...niceAxis(
803
+ points.map(({ y }) => y),
804
+ pad
805
+ ),
806
+ title: axisTitle(yTitle),
807
+ labels: { ...axisLabels, formatter: (value) => formatY(value) }
808
+ },
809
+ legend: {
810
+ show: showLegend ?? series.length > 1,
811
+ position: "top",
812
+ horizontalAlign: "right",
813
+ labels: { colors: axisLabels.style.colors },
814
+ markers: { size: 5 },
815
+ formatter: (name) => escapeHtml(name)
816
+ },
817
+ // The tooltip is HTML: tooltipHtml escapes the label, the series name and
818
+ // the values.
819
+ tooltip: {
820
+ theme: "dark",
821
+ custom: ({ seriesIndex, dataPointIndex }) => {
822
+ const { name, data } = series[seriesIndex];
823
+ const point = data[dataPointIndex];
824
+ return tooltipHtml({
825
+ title: point.label,
826
+ series: { name, color: seriesColors[seriesIndex % seriesColors.length] },
827
+ rows: [
828
+ [xTitle ?? "x", formatX(point.x)],
829
+ [yTitle ?? "y", formatY(point.y)],
830
+ ...extraRows(point)
831
+ ]
832
+ });
833
+ }
834
+ }
835
+ };
836
+ };
837
+ var ScatterChart = ({ height = 320, width = "100%", ...props }) => {
838
+ const base = xyOptions(useChartTheme(), props);
839
+ const options = {
840
+ ...base,
841
+ chart: { ...base.chart, type: "scatter" },
842
+ markers: { size: 6, strokeWidth: 0, hover: { sizeOffset: 2 } }
843
+ };
844
+ return /* @__PURE__ */ jsxRuntime.jsx(
845
+ ReactApexChart13__default.default,
846
+ {
847
+ type: "scatter",
848
+ options,
849
+ series: props.series,
850
+ height,
851
+ width
852
+ }
853
+ );
854
+ };
855
+ var BubbleChart = ({
856
+ sizeTitle = "Size",
857
+ formatSize = formatNumber,
858
+ height = 320,
859
+ width = "100%",
860
+ ...props
861
+ }) => {
862
+ const theme = useChartTheme();
863
+ const base = xyOptions(theme, props, {
864
+ // Room for the bubbles at the edges of the plot.
865
+ pad: 0.08,
866
+ extraRows: (point) => [[sizeTitle, formatSize(point.z)]]
867
+ });
868
+ const options = {
869
+ ...base,
870
+ chart: { ...base.chart, type: "bubble" },
871
+ plotOptions: { bubble: { minBubbleRadius: 4 } },
872
+ // Translucent bubbles, outlined in their own color (ApexCharts' white
873
+ // outline glares on the dark themes), keep the overlapping ones readable.
874
+ fill: { opacity: 0.55 },
875
+ markers: { strokeWidth: 1, strokeColors: props.colors ?? theme.palette }
876
+ };
877
+ return /* @__PURE__ */ jsxRuntime.jsx(
878
+ ReactApexChart13__default.default,
879
+ {
880
+ type: "bubble",
881
+ options,
882
+ series: props.series,
883
+ height,
884
+ width
885
+ }
886
+ );
887
+ };
888
+ var ROWS = [
889
+ ["Max", 4],
890
+ ["Q3", 3],
891
+ ["Median", 2],
892
+ ["Q1", 1],
893
+ ["Min", 0]
894
+ ];
895
+ var BoxPlotChart = ({
896
+ series,
897
+ categories,
898
+ orientation = "vertical",
899
+ colors,
900
+ formatValue,
901
+ showLegend,
902
+ height = 320,
903
+ width = "100%"
904
+ }) => {
905
+ const { grid, axisLabels, palette } = useChartTheme();
906
+ const horizontal = orientation === "horizontal";
907
+ const seriesColors = colors ?? palette;
908
+ const valueFormatter = formatValue ?? ((v) => String(v));
909
+ const categoryLabels = () => ({ ...axisLabels });
910
+ const valueLabels = () => ({
911
+ ...axisLabels,
912
+ formatter: (value) => valueFormatter(Number(value))
913
+ });
914
+ const options = {
915
+ chart: {
916
+ type: "boxPlot",
917
+ toolbar: { show: false },
918
+ zoom: { enabled: false },
919
+ fontFamily: "inherit"
920
+ },
921
+ colors: seriesColors,
922
+ plotOptions: {
923
+ bar: { horizontal, columnWidth: "50%", barHeight: "50%" },
924
+ // Both halves take the series color: a translucent fill, and an outline,
925
+ // median and whiskers drawn in the full color (ApexCharts' defaults are
926
+ // fixed colors, with a near-black stroke lost on the dark themes).
927
+ boxPlot: { colors: { upper: seriesColors, lower: seriesColors } }
928
+ },
929
+ fill: { opacity: 0.35 },
930
+ stroke: { width: 1.5, colors: seriesColors },
931
+ grid,
932
+ xaxis: {
933
+ labels: horizontal ? valueLabels() : categoryLabels(),
934
+ axisBorder: { show: false },
935
+ axisTicks: { show: false },
936
+ // The x-axis tooltip is HTML and repeats the tooltip title.
937
+ tooltip: { enabled: false }
938
+ },
939
+ yaxis: { labels: horizontal ? categoryLabels() : valueLabels() },
940
+ // Horizontal, the value labels sit side by side and run into each other
941
+ // on a phone: keep three of them.
942
+ responsive: horizontal ? [{ breakpoint: 640, options: { xaxis: { tickAmount: 2 } } }] : [],
943
+ legend: {
944
+ show: showLegend ?? series.length > 1,
945
+ position: "top",
946
+ horizontalAlign: "right",
947
+ labels: { colors: axisLabels.style.colors },
948
+ markers: { size: 5 },
949
+ formatter: (name) => escapeHtml(name)
950
+ },
951
+ // ApexCharts' own box plot tooltip prints the values raw. This one is
952
+ // HTML too: tooltipHtml escapes the category, the series name and values.
953
+ tooltip: {
954
+ theme: "dark",
955
+ custom: ({ seriesIndex, dataPointIndex }) => {
956
+ const { name, data } = series[seriesIndex];
957
+ const summary = data[dataPointIndex];
958
+ return tooltipHtml({
959
+ title: categories[dataPointIndex],
960
+ series: series.length > 1 ? { name, color: seriesColors[seriesIndex % seriesColors.length] } : void 0,
961
+ rows: ROWS.map(([label, index]) => [label, valueFormatter(summary[index])])
962
+ });
963
+ }
964
+ }
965
+ };
966
+ const chartSeries = series.map(({ name, data }) => ({
967
+ name,
968
+ data: data.map((y, index) => ({ x: categories[index], y }))
969
+ }));
970
+ return /* @__PURE__ */ jsxRuntime.jsx(
971
+ ReactApexChart13__default.default,
972
+ {
973
+ type: "boxPlot",
974
+ options,
975
+ series: chartSeries,
976
+ height,
977
+ width
978
+ }
979
+ );
980
+ };
981
+ var UTC_DATE_TIME = new Intl.DateTimeFormat("en-GB", {
982
+ day: "2-digit",
983
+ month: "short",
984
+ hour: "2-digit",
985
+ minute: "2-digit",
986
+ hourCycle: "h23",
987
+ timeZone: "UTC"
988
+ });
989
+ var defaultFormatDate = (timestamp) => UTC_DATE_TIME.format(timestamp);
990
+ var RangeBarChart = ({
991
+ series,
992
+ groupRows = false,
993
+ colors,
994
+ formatDate = defaultFormatDate,
995
+ showLegend,
996
+ height = 320,
997
+ width = "100%"
998
+ }) => {
999
+ const { grid, axisLabels, palette } = useChartTheme();
1000
+ const seriesColors = colors ?? palette;
1001
+ const legendVisible = showLegend ?? series.length > 1;
1002
+ const lanes = groupRows ? 1 : series.length;
1003
+ const tooltip = ({
1004
+ seriesIndex,
1005
+ dataPointIndex
1006
+ }) => {
1007
+ const s = series[seriesIndex];
1008
+ const point = s?.data[dataPointIndex];
1009
+ if (!point) return "";
1010
+ const [start, end] = point.y;
1011
+ return tooltipHtml({
1012
+ title: point.x,
1013
+ series: { name: s.name, color: seriesColors[seriesIndex % seriesColors.length] },
1014
+ rows: [
1015
+ ["Start", formatDate(start)],
1016
+ ["End", formatDate(end)]
1017
+ ]
1018
+ });
1019
+ };
1020
+ const options = {
1021
+ chart: {
1022
+ type: "rangeBar",
1023
+ toolbar: { show: false },
1024
+ zoom: { enabled: false },
1025
+ fontFamily: "inherit"
1026
+ },
1027
+ colors: seriesColors,
1028
+ dataLabels: { enabled: false },
1029
+ plotOptions: {
1030
+ bar: {
1031
+ horizontal: true,
1032
+ // Without groupRows each series gets its own lane in the row: give
1033
+ // the bars more of their (thinner) lane.
1034
+ barHeight: lanes > 1 ? "80%" : "60%",
1035
+ borderRadius: 3,
1036
+ borderRadiusApplication: "around",
1037
+ rangeBarGroupRows: groupRows
1038
+ }
1039
+ },
1040
+ // Time runs left to right: vertical grid lines, plus a line between rows.
1041
+ grid: { ...grid, xaxis: { lines: { show: true } }, yaxis: { lines: { show: true } } },
1042
+ // One labels object per axis: ApexCharts mutates them.
1043
+ xaxis: {
1044
+ type: "datetime",
1045
+ labels: { ...axisLabels, datetimeUTC: true },
1046
+ axisBorder: { show: false },
1047
+ axisTicks: { show: false }
1048
+ },
1049
+ yaxis: { labels: { ...axisLabels } },
1050
+ legend: {
1051
+ show: legendVisible,
1052
+ position: "top",
1053
+ horizontalAlign: "right",
1054
+ labels: { colors: axisLabels.style.colors },
1055
+ markers: { size: 5 },
1056
+ formatter: (name) => escapeHtml(name)
1057
+ },
1058
+ tooltip: { theme: "dark", custom: tooltip },
1059
+ fill: { opacity: 1 }
1060
+ };
1061
+ return /* @__PURE__ */ jsxRuntime.jsx(
1062
+ ReactApexChart13__default.default,
1063
+ {
1064
+ type: "rangeBar",
1065
+ options,
1066
+ series,
1067
+ height,
1068
+ width
1069
+ }
1070
+ );
1071
+ };
1072
+ var SIDES = ["left", "right"];
1073
+ var axisOf = (s) => s.axis ?? "left";
1074
+ var MixedChart = ({
1075
+ series,
1076
+ categories,
1077
+ colors,
1078
+ formatValue,
1079
+ formatRightValue,
1080
+ showLegend,
1081
+ height = 320,
1082
+ width = "100%"
1083
+ }) => {
1084
+ const { grid, axisLabels, palette } = useChartTheme();
1085
+ const legendVisible = showLegend ?? series.length > 1;
1086
+ const formatters = { left: formatValue, right: formatRightValue };
1087
+ const usedSides = SIDES.filter((side) => series.some((s) => axisOf(s) === side));
1088
+ const sides = usedSides.length > 0 ? usedSides : SIDES.slice(0, 1);
1089
+ const yaxis = sides.map((side) => ({
1090
+ seriesName: sides.length > 1 ? series.filter((s) => axisOf(s) === side).map((s) => s.name) : void 0,
1091
+ opposite: side === "right",
1092
+ labels: { ...axisLabels, formatter: formatters[side] }
1093
+ }));
1094
+ const options = {
1095
+ chart: {
1096
+ type: "line",
1097
+ toolbar: { show: false },
1098
+ zoom: { enabled: false },
1099
+ fontFamily: "inherit"
1100
+ },
1101
+ colors: colors ?? palette,
1102
+ dataLabels: { enabled: false },
1103
+ // Lines only: columns have no stroke. monotoneCubic stays smooth without
1104
+ // overshooting the samples, so a latency line never dips below its data.
1105
+ stroke: { width: series.map((s) => s.type === "line" ? 3 : 0), curve: "monotoneCubic" },
1106
+ plotOptions: {
1107
+ bar: { columnWidth: "55%", borderRadius: 4, borderRadiusApplication: "end" }
1108
+ },
1109
+ grid,
1110
+ xaxis: {
1111
+ categories,
1112
+ labels: { ...axisLabels },
1113
+ axisBorder: { show: false },
1114
+ axisTicks: { show: false }
1115
+ },
1116
+ yaxis,
1117
+ legend: {
1118
+ show: legendVisible,
1119
+ position: "top",
1120
+ horizontalAlign: "right",
1121
+ labels: { colors: axisLabels.style.colors },
1122
+ markers: { size: 5 },
1123
+ // ApexCharts stacks the series of each y axis in a column of their own:
1124
+ // keep one row, like the other charts.
1125
+ clusterGroupedSeries: false,
1126
+ formatter: (name) => escapeHtml(name)
1127
+ },
1128
+ // The tooltip is HTML: escape the category, the series names and the
1129
+ // values. ApexCharts hands the x formatter the point's position on a line
1130
+ // chart's axis, not its category, so read the category back by index.
1131
+ tooltip: {
1132
+ theme: "dark",
1133
+ x: {
1134
+ formatter: (v, opts) => escapeHtml(opts === void 0 ? v : categories[opts.dataPointIndex] ?? v)
1135
+ },
1136
+ y: {
1137
+ formatter: (v, opts) => {
1138
+ const s = opts && series[opts.seriesIndex];
1139
+ const format = (s && formatters[axisOf(s)]) ?? String;
1140
+ return escapeHtml(format(Number(v)));
1141
+ },
1142
+ title: { formatter: (name) => `${escapeHtml(name)}: ` }
1143
+ }
1144
+ },
1145
+ fill: { opacity: 1 }
1146
+ };
1147
+ return /* @__PURE__ */ jsxRuntime.jsx(ReactApexChart13__default.default, { type: "line", options, series, height, width });
465
1148
  };
466
1149
 
467
1150
  exports.BarChart = BarChart;
1151
+ exports.BoxPlotChart = BoxPlotChart;
1152
+ exports.BubbleChart = BubbleChart;
468
1153
  exports.COSMOS_BRAND = COSMOS_BRAND;
469
1154
  exports.COSMOS_PALETTE = COSMOS_PALETTE;
470
1155
  exports.DonutChart = DonutChart;
471
1156
  exports.DonutRightLegend = DonutRightLegend;
472
1157
  exports.GradientRadial = GradientRadial;
1158
+ exports.HeatmapChart = HeatmapChart;
473
1159
  exports.LineAreaChart = LineAreaChart;
1160
+ exports.MixedChart = MixedChart;
1161
+ exports.RadarChart = RadarChart;
474
1162
  exports.RadialBarChart = RadialBarChart;
1163
+ exports.RangeBarChart = RangeBarChart;
475
1164
  exports.RealtimeChart = RealtimeChart;
1165
+ exports.ScatterChart = ScatterChart;
476
1166
  exports.SemiCircleGauge = SemiCircleGauge;
477
1167
  exports.Sparkline = Sparkline;
478
1168
  exports.SparklineGroup = SparklineGroup;