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