@classic-homes/charts-react 0.1.38 → 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
@@ -153,6 +153,8 @@ var ChartContainer = React4.forwardRef(
153
153
  onRetry,
154
154
  className,
155
155
  wrapperClassName,
156
+ legend,
157
+ legendPosition = "bottom",
156
158
  children,
157
159
  ...props
158
160
  }, ref) => {
@@ -175,7 +177,7 @@ var ChartContainer = React4.forwardRef(
175
177
  if (empty) {
176
178
  return /* @__PURE__ */ jsx4(ChartEmpty, { message: emptyMessage, height, className: wrapperClassName });
177
179
  }
178
- return /* @__PURE__ */ jsxs4(
180
+ const chart = /* @__PURE__ */ jsxs4(
179
181
  "div",
180
182
  {
181
183
  ref,
@@ -183,7 +185,7 @@ var ChartContainer = React4.forwardRef(
183
185
  "aria-label": title,
184
186
  "aria-describedby": description ? descriptionId : void 0,
185
187
  "data-testid": "chart-container",
186
- className: cn("rounded-lg border border-border bg-card", wrapperClassName),
188
+ className: legend ? "min-h-0 min-w-0 flex-1" : cn("rounded-lg border border-border bg-card", wrapperClassName),
187
189
  ...props,
188
190
  children: [
189
191
  description && /* @__PURE__ */ jsx4("span", { id: descriptionId, className: "sr-only", children: description }),
@@ -191,12 +193,83 @@ var ChartContainer = React4.forwardRef(
191
193
  ]
192
194
  }
193
195
  );
196
+ if (!legend) return chart;
197
+ const side = legendPosition === "left" || legendPosition === "right";
198
+ const leading = legendPosition === "top" || legendPosition === "left";
199
+ const legendNode = /* @__PURE__ */ jsx4(
200
+ "div",
201
+ {
202
+ "data-testid": "chart-legend",
203
+ className: cn("flex shrink-0 p-2", side ? "items-center" : "justify-center"),
204
+ children: legend
205
+ }
206
+ );
207
+ return /* @__PURE__ */ jsxs4(
208
+ "div",
209
+ {
210
+ className: cn(
211
+ "flex rounded-lg border border-border bg-card",
212
+ side ? "flex-row" : "flex-col",
213
+ wrapperClassName
214
+ ),
215
+ children: [
216
+ leading && legendNode,
217
+ chart,
218
+ !leading && legendNode
219
+ ]
220
+ }
221
+ );
194
222
  }
195
223
  );
196
224
  ChartContainer.displayName = "ChartContainer";
197
225
 
198
- // src/components/core/LineChart.tsx
226
+ // src/components/base/ChartLegend.tsx
199
227
  import * as React5 from "react";
228
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
229
+ var ChartLegend = React5.forwardRef(
230
+ ({ items, toggle, position = "bottom", chartTitle, orientation, className }, ref) => {
231
+ const vertical = (orientation ?? (position === "left" || position === "right" ? "vertical" : "horizontal")) === "vertical";
232
+ return /* @__PURE__ */ jsx5(
233
+ "ul",
234
+ {
235
+ ref,
236
+ "aria-label": chartTitle ? `${chartTitle} legend` : "Chart legend",
237
+ className: cn(
238
+ "flex gap-1 text-sm",
239
+ vertical ? "flex-col" : "flex-wrap justify-center",
240
+ className
241
+ ),
242
+ children: items.map((item) => /* @__PURE__ */ jsx5("li", { children: /* @__PURE__ */ jsxs5(
243
+ "button",
244
+ {
245
+ type: "button",
246
+ "aria-pressed": item.visible,
247
+ onClick: () => toggle(item.name),
248
+ className: cn(
249
+ "inline-flex items-center gap-2 rounded-md px-2 py-1 text-left hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
250
+ item.visible ? "text-foreground" : "text-muted-foreground line-through"
251
+ ),
252
+ children: [
253
+ /* @__PURE__ */ jsx5(
254
+ "span",
255
+ {
256
+ "aria-hidden": "true",
257
+ className: "h-3 w-3 shrink-0 rounded-sm",
258
+ style: { backgroundColor: item.color, opacity: item.visible ? 1 : 0.35 }
259
+ }
260
+ ),
261
+ item.name
262
+ ]
263
+ }
264
+ ) }, item.name))
265
+ }
266
+ );
267
+ }
268
+ );
269
+ ChartLegend.displayName = "ChartLegend";
270
+
271
+ // src/components/core/LineChart.tsx
272
+ import * as React6 from "react";
200
273
  import * as echarts3 from "echarts/core";
201
274
  import { LineChart as EChartsLineChart } from "echarts/charts";
202
275
  import {
@@ -207,9 +280,18 @@ import {
207
280
  } from "echarts/components";
208
281
  import { CanvasRenderer } from "echarts/renderers";
209
282
  import {
283
+ buildAxis,
284
+ buildDataLabel,
285
+ buildLegend,
286
+ buildTooltip,
210
287
  generateLineChartDescription,
211
288
  getChartColors,
212
- THEME_DARK as THEME_DARK2
289
+ hasPointLabels,
290
+ gridInset,
291
+ mergeChartOption,
292
+ seriesLabelLookup,
293
+ THEME_DARK as THEME_DARK2,
294
+ withPointLabels
213
295
  } from "@classic-homes/charts-core";
214
296
 
215
297
  // src/hooks/useChart.ts
@@ -366,6 +448,12 @@ function useChart({
366
448
  };
367
449
  }
368
450
 
451
+ // src/hooks/useChartLegend.ts
452
+ import { useCallback, useRef as useRef3, useState as useState4 } from "react";
453
+ import {
454
+ getLegendItems
455
+ } from "@classic-homes/charts-core";
456
+
369
457
  // src/hooks/useLatestRef.ts
370
458
  import { useRef as useRef2 } from "react";
371
459
  function useLatestRef(value) {
@@ -374,8 +462,62 @@ function useLatestRef(value) {
374
462
  return ref;
375
463
  }
376
464
 
465
+ // src/hooks/useChartLegend.ts
466
+ var LEGEND_EVENTS = [
467
+ "legendselectchanged",
468
+ "legendselected",
469
+ "legendunselected",
470
+ "legendselectall",
471
+ "legendinverseselect"
472
+ ];
473
+ function useChartLegend({
474
+ names,
475
+ colors,
476
+ palette,
477
+ config,
478
+ title,
479
+ onLegendChange
480
+ }) {
481
+ const chartRef = useRef3(null);
482
+ const [visible, setVisible] = useState4({});
483
+ const latest = useLatestRef({ onLegendChange });
484
+ const attach = useCallback(
485
+ (chart) => {
486
+ chartRef.current = chart;
487
+ setVisible({});
488
+ const handler = (params) => {
489
+ const selected = { ...params.selected };
490
+ setVisible(selected);
491
+ latest.current.onLegendChange?.(selected);
492
+ };
493
+ for (const event of LEGEND_EVENTS) chart.on(event, handler);
494
+ },
495
+ [latest]
496
+ );
497
+ const dispatch = (action) => {
498
+ const chart = chartRef.current;
499
+ if (chart && !chart.isDisposed()) chart.dispatchAction(action);
500
+ };
501
+ return {
502
+ attach,
503
+ renderProps: {
504
+ items: getLegendItems(names, palette, colors, visible),
505
+ position: config?.position ?? "bottom",
506
+ chartTitle: title,
507
+ toggle: (name) => dispatch({ type: "legendToggleSelect", name }),
508
+ setVisible: (name, show) => dispatch({ type: show ? "legendSelect" : "legendUnSelect", name }),
509
+ showOnly: (name) => {
510
+ for (const other of names) {
511
+ dispatch({ type: other === name ? "legendSelect" : "legendUnSelect", name: other });
512
+ }
513
+ },
514
+ showAll: () => dispatch({ type: "legendAllSelect" })
515
+ }
516
+ };
517
+ }
518
+
377
519
  // src/components/core/LineChart.tsx
378
- import { jsx as jsx5 } from "react/jsx-runtime";
520
+ import { jsx as jsx6 } from "react/jsx-runtime";
379
521
  echarts3.use([
380
522
  EChartsLineChart,
381
523
  GridComponent,
@@ -384,7 +526,7 @@ echarts3.use([
384
526
  TitleComponent,
385
527
  CanvasRenderer
386
528
  ]);
387
- var LineChart = React5.forwardRef(
529
+ var LineChart = React6.forwardRef(
388
530
  ({
389
531
  title,
390
532
  description,
@@ -402,6 +544,16 @@ var LineChart = React5.forwardRef(
402
544
  areaFilled = false,
403
545
  showDataPoints = false,
404
546
  stacked = false,
547
+ showLabels = false,
548
+ labelFormatter,
549
+ xAxisConfig,
550
+ yAxisConfig,
551
+ tooltipFormatter,
552
+ tooltipValueFormatter,
553
+ legend: legendConfig,
554
+ renderLegend,
555
+ onLegendChange,
556
+ option: optionOverride,
405
557
  onClick,
406
558
  onHover,
407
559
  class: className,
@@ -410,57 +562,92 @@ var LineChart = React5.forwardRef(
410
562
  const isEmpty = !data?.categories?.length || !data?.series?.length || data.series.every((s) => !s.data?.length);
411
563
  const currentTheme = useChartTheme(theme);
412
564
  const chartColors = getChartColors(currentTheme);
413
- const option = React5.useMemo(() => {
565
+ const legend = useChartLegend({
566
+ names: data?.series?.map((s) => s.name) ?? [],
567
+ colors: data?.series?.map((s) => s.color) ?? [],
568
+ palette: chartColors.series,
569
+ config: legendConfig,
570
+ title,
571
+ onLegendChange
572
+ });
573
+ const customLegend = renderLegend !== void 0;
574
+ const option = React6.useMemo(() => {
414
575
  if (isEmpty) return {};
415
- return {
416
- grid: {
417
- left: "3%",
418
- right: "4%",
419
- bottom: "3%",
420
- containLabel: true
421
- },
422
- tooltip: showTooltip ? {
423
- trigger: "axis",
424
- axisPointer: {
425
- type: "cross",
426
- label: {
427
- backgroundColor: chartColors.axisPointerLabel
576
+ const getLabel = seriesLabelLookup(data.series);
577
+ const showSymbol = showDataPoints || showLabels || hasPointLabels(data.series);
578
+ return mergeChartOption(
579
+ {
580
+ grid: {
581
+ left: "3%",
582
+ right: "4%",
583
+ bottom: "3%",
584
+ containLabel: true,
585
+ ...gridInset(legendConfig, showLegend && !customLegend, xAxisConfig, yAxisConfig)
586
+ },
587
+ tooltip: buildTooltip(
588
+ {
589
+ trigger: "axis",
590
+ axisPointer: {
591
+ type: "cross",
592
+ label: {
593
+ backgroundColor: chartColors.axisPointerLabel
594
+ }
595
+ }
596
+ },
597
+ {
598
+ show: showTooltip,
599
+ formatter: tooltipFormatter,
600
+ valueFormatter: tooltipValueFormatter,
601
+ getLabel
428
602
  }
429
- }
430
- } : void 0,
431
- legend: showLegend ? {
432
- data: data.series.map((s) => s.name),
433
- bottom: 0
434
- } : void 0,
435
- xAxis: {
436
- type: "category",
437
- boundaryGap: false,
438
- data: data.categories,
439
- splitLine: {
440
- show: showGrid
441
- }
442
- },
443
- yAxis: {
444
- type: "value",
445
- splitLine: {
446
- show: showGrid
447
- }
603
+ ),
604
+ legend: buildLegend(
605
+ data.series.map((s) => s.name),
606
+ legendConfig,
607
+ { show: showLegend, custom: customLegend }
608
+ ),
609
+ xAxis: buildAxis(
610
+ {
611
+ type: "category",
612
+ boundaryGap: false,
613
+ data: data.categories,
614
+ splitLine: {
615
+ show: showGrid
616
+ }
617
+ },
618
+ xAxisConfig
619
+ ),
620
+ yAxis: buildAxis(
621
+ {
622
+ type: "value",
623
+ splitLine: {
624
+ show: showGrid
625
+ }
626
+ },
627
+ yAxisConfig
628
+ ),
629
+ series: data.series.map((series) => ({
630
+ name: series.name,
631
+ type: "line",
632
+ data: withPointLabels(series.data, series.labels, labelFormatter, { position: "top" }),
633
+ smooth,
634
+ showSymbol,
635
+ symbolSize: showDataPoints ? 6 : 0,
636
+ stack: stacked ? "Total" : void 0,
637
+ areaStyle: areaFilled ? { opacity: 0.3 } : void 0,
638
+ itemStyle: series.color ? { color: series.color } : void 0,
639
+ label: buildDataLabel({ show: true, position: "top" }, void 0, {
640
+ show: showLabels,
641
+ formatter: labelFormatter,
642
+ getLabel
643
+ }),
644
+ emphasis: {
645
+ focus: "series"
646
+ }
647
+ }))
448
648
  },
449
- series: data.series.map((series) => ({
450
- name: series.name,
451
- type: "line",
452
- data: series.data,
453
- smooth,
454
- showSymbol: showDataPoints,
455
- symbolSize: showDataPoints ? 6 : 0,
456
- stack: stacked ? "Total" : void 0,
457
- areaStyle: areaFilled ? { opacity: 0.3 } : void 0,
458
- itemStyle: series.color ? { color: series.color } : void 0,
459
- emphasis: {
460
- focus: "series"
461
- }
462
- }))
463
- };
649
+ optionOverride
650
+ );
464
651
  }, [
465
652
  data,
466
653
  isEmpty,
@@ -471,7 +658,16 @@ var LineChart = React5.forwardRef(
471
658
  areaFilled,
472
659
  showDataPoints,
473
660
  stacked,
474
- chartColors
661
+ chartColors,
662
+ showLabels,
663
+ labelFormatter,
664
+ xAxisConfig,
665
+ yAxisConfig,
666
+ tooltipFormatter,
667
+ tooltipValueFormatter,
668
+ legendConfig,
669
+ customLegend,
670
+ optionOverride
475
671
  ]);
476
672
  const latest = useLatestRef({ onClick, onHover });
477
673
  const { containerRef } = useChart({
@@ -479,6 +675,7 @@ var LineChart = React5.forwardRef(
479
675
  theme: currentTheme === THEME_DARK2 ? "dark" : "light",
480
676
  animation,
481
677
  onReady: (chart) => {
678
+ legend.attach(chart);
482
679
  chart.on("click", (params) => {
483
680
  const { onClick: onClick2 } = latest.current;
484
681
  if (!onClick2) return;
@@ -510,7 +707,7 @@ var LineChart = React5.forwardRef(
510
707
  }
511
708
  });
512
709
  const accessibilityDescription = description || (!isEmpty ? generateLineChartDescription(title, data) : void 0);
513
- return /* @__PURE__ */ jsx5(
710
+ return /* @__PURE__ */ jsx6(
514
711
  ChartContainer,
515
712
  {
516
713
  ref,
@@ -522,8 +719,10 @@ var LineChart = React5.forwardRef(
522
719
  empty: isEmpty,
523
720
  emptyMessage,
524
721
  className: cn(className),
722
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
723
+ legendPosition: legendConfig?.position,
525
724
  ...props,
526
- children: /* @__PURE__ */ jsx5("div", { ref: containerRef, className: "h-full w-full" })
725
+ children: /* @__PURE__ */ jsx6("div", { ref: containerRef, className: "h-full w-full" })
527
726
  }
528
727
  );
529
728
  }
@@ -531,7 +730,7 @@ var LineChart = React5.forwardRef(
531
730
  LineChart.displayName = "LineChart";
532
731
 
533
732
  // src/components/core/BarChart.tsx
534
- import * as React6 from "react";
733
+ import * as React7 from "react";
535
734
  import * as echarts4 from "echarts/core";
536
735
  import { BarChart as EChartsBarChart } from "echarts/charts";
537
736
  import {
@@ -541,8 +740,19 @@ import {
541
740
  TitleComponent as TitleComponent2
542
741
  } from "echarts/components";
543
742
  import { CanvasRenderer as CanvasRenderer2 } from "echarts/renderers";
544
- import { generateBarChartDescription } from "@classic-homes/charts-core";
545
- import { jsx as jsx6 } from "react/jsx-runtime";
743
+ import {
744
+ buildAxis as buildAxis2,
745
+ buildDataLabel as buildDataLabel2,
746
+ buildLegend as buildLegend2,
747
+ buildTooltip as buildTooltip2,
748
+ generateBarChartDescription,
749
+ getChartColors as getChartColors2,
750
+ gridInset as gridInset2,
751
+ mergeChartOption as mergeChartOption2,
752
+ seriesLabelLookup as seriesLabelLookup2,
753
+ withPointLabels as withPointLabels2
754
+ } from "@classic-homes/charts-core";
755
+ import { jsx as jsx7 } from "react/jsx-runtime";
546
756
  echarts4.use([
547
757
  EChartsBarChart,
548
758
  GridComponent2,
@@ -551,7 +761,7 @@ echarts4.use([
551
761
  TitleComponent2,
552
762
  CanvasRenderer2
553
763
  ]);
554
- var BarChart = React6.forwardRef(
764
+ var BarChart = React7.forwardRef(
555
765
  ({
556
766
  title,
557
767
  description,
@@ -568,13 +778,32 @@ var BarChart = React6.forwardRef(
568
778
  orientation = "vertical",
569
779
  stacked = false,
570
780
  showValues = false,
781
+ labelFormatter,
782
+ xAxisConfig,
783
+ yAxisConfig,
784
+ tooltipFormatter,
785
+ tooltipValueFormatter,
786
+ legend: legendConfig,
787
+ renderLegend,
788
+ onLegendChange,
789
+ option: optionOverride,
571
790
  onClick,
572
791
  class: className,
573
792
  ...props
574
793
  }, ref) => {
575
794
  const isEmpty = !data?.categories?.length || !data?.series?.length || data.series.every((s) => !s.data?.length);
576
795
  const isHorizontal = orientation === "horizontal";
577
- const option = React6.useMemo(() => {
796
+ const currentTheme = useChartTheme(theme);
797
+ const legend = useChartLegend({
798
+ names: data?.series?.map((s) => s.name) ?? [],
799
+ colors: data?.series?.map((s) => s.color) ?? [],
800
+ palette: getChartColors2(currentTheme).series,
801
+ config: legendConfig,
802
+ title,
803
+ onLegendChange
804
+ });
805
+ const customLegend = renderLegend !== void 0;
806
+ const option = React7.useMemo(() => {
578
807
  if (isEmpty) return {};
579
808
  const categoryAxis = {
580
809
  type: "category",
@@ -589,47 +818,85 @@ var BarChart = React6.forwardRef(
589
818
  show: showGrid
590
819
  }
591
820
  };
592
- return {
593
- grid: {
594
- left: "3%",
595
- right: "4%",
596
- bottom: "3%",
597
- containLabel: true
821
+ const getLabel = seriesLabelLookup2(data.series);
822
+ return mergeChartOption2(
823
+ {
824
+ grid: {
825
+ left: "3%",
826
+ right: "4%",
827
+ bottom: "3%",
828
+ containLabel: true,
829
+ ...gridInset2(legendConfig, showLegend && !customLegend, xAxisConfig, yAxisConfig)
830
+ },
831
+ tooltip: buildTooltip2(
832
+ {
833
+ trigger: "axis",
834
+ axisPointer: {
835
+ type: "shadow"
836
+ }
837
+ },
838
+ {
839
+ show: showTooltip,
840
+ formatter: tooltipFormatter,
841
+ valueFormatter: tooltipValueFormatter,
842
+ getLabel
843
+ }
844
+ ),
845
+ legend: buildLegend2(
846
+ data.series.map((s) => s.name),
847
+ legendConfig,
848
+ { show: showLegend, custom: customLegend }
849
+ ),
850
+ xAxis: buildAxis2(isHorizontal ? valueAxis : categoryAxis, xAxisConfig),
851
+ yAxis: buildAxis2(isHorizontal ? categoryAxis : valueAxis, yAxisConfig),
852
+ series: data.series.map((series) => ({
853
+ name: series.name,
854
+ type: "bar",
855
+ data: withPointLabels2(series.data, series.labels, labelFormatter, {
856
+ position: isHorizontal ? "right" : "top"
857
+ }),
858
+ stack: stacked ? "Total" : void 0,
859
+ itemStyle: series.color ? { color: series.color } : void 0,
860
+ label: buildDataLabel2(
861
+ {
862
+ show: true,
863
+ position: isHorizontal ? "right" : "top"
864
+ },
865
+ void 0,
866
+ { show: showValues, formatter: labelFormatter, getLabel }
867
+ ),
868
+ emphasis: {
869
+ focus: "series"
870
+ }
871
+ }))
598
872
  },
599
- tooltip: showTooltip ? {
600
- trigger: "axis",
601
- axisPointer: {
602
- type: "shadow"
603
- }
604
- } : void 0,
605
- legend: showLegend ? {
606
- data: data.series.map((s) => s.name),
607
- bottom: 0
608
- } : void 0,
609
- xAxis: isHorizontal ? valueAxis : categoryAxis,
610
- yAxis: isHorizontal ? categoryAxis : valueAxis,
611
- series: data.series.map((series) => ({
612
- name: series.name,
613
- type: "bar",
614
- data: series.data,
615
- stack: stacked ? "Total" : void 0,
616
- itemStyle: series.color ? { color: series.color } : void 0,
617
- label: showValues ? {
618
- show: true,
619
- position: isHorizontal ? "right" : "top"
620
- } : void 0,
621
- emphasis: {
622
- focus: "series"
623
- }
624
- }))
625
- };
626
- }, [data, isEmpty, showTooltip, showLegend, showGrid, isHorizontal, stacked, showValues]);
873
+ optionOverride
874
+ );
875
+ }, [
876
+ data,
877
+ isEmpty,
878
+ showTooltip,
879
+ showLegend,
880
+ showGrid,
881
+ isHorizontal,
882
+ stacked,
883
+ showValues,
884
+ labelFormatter,
885
+ xAxisConfig,
886
+ yAxisConfig,
887
+ tooltipFormatter,
888
+ tooltipValueFormatter,
889
+ legendConfig,
890
+ customLegend,
891
+ optionOverride
892
+ ]);
627
893
  const latest = useLatestRef({ onClick });
628
894
  const { containerRef } = useChart({
629
895
  option,
630
896
  theme,
631
897
  animation,
632
898
  onReady: (chart) => {
899
+ legend.attach(chart);
633
900
  chart.on("click", (params) => {
634
901
  const { onClick: onClick2 } = latest.current;
635
902
  if (!onClick2) return;
@@ -647,7 +914,7 @@ var BarChart = React6.forwardRef(
647
914
  }
648
915
  });
649
916
  const accessibilityDescription = description || (!isEmpty ? generateBarChartDescription(title, data) : void 0);
650
- return /* @__PURE__ */ jsx6(
917
+ return /* @__PURE__ */ jsx7(
651
918
  ChartContainer,
652
919
  {
653
920
  ref,
@@ -659,8 +926,10 @@ var BarChart = React6.forwardRef(
659
926
  empty: isEmpty,
660
927
  emptyMessage,
661
928
  className: cn(className),
929
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
930
+ legendPosition: legendConfig?.position,
662
931
  ...props,
663
- children: /* @__PURE__ */ jsx6("div", { ref: containerRef, className: "h-full w-full" })
932
+ children: /* @__PURE__ */ jsx7("div", { ref: containerRef, className: "h-full w-full" })
664
933
  }
665
934
  );
666
935
  }
@@ -668,15 +937,27 @@ var BarChart = React6.forwardRef(
668
937
  BarChart.displayName = "BarChart";
669
938
 
670
939
  // src/components/core/PieChart.tsx
671
- import * as React7 from "react";
940
+ import * as React8 from "react";
672
941
  import * as echarts5 from "echarts/core";
673
942
  import { PieChart as EChartsPieChart } from "echarts/charts";
674
943
  import { TooltipComponent as TooltipComponent3, LegendComponent as LegendComponent3, TitleComponent as TitleComponent3 } from "echarts/components";
675
944
  import { CanvasRenderer as CanvasRenderer3 } from "echarts/renderers";
676
- import { generatePieChartDescription } from "@classic-homes/charts-core";
677
- import { jsx as jsx7 } from "react/jsx-runtime";
945
+ import {
946
+ buildDataLabel as buildDataLabel3,
947
+ buildLegend as buildLegend3,
948
+ buildTooltip as buildTooltip3,
949
+ escapeHtml,
950
+ formatTooltipValue,
951
+ generatePieChartDescription,
952
+ getChartColors as getChartColors3,
953
+ itemLabelLookup,
954
+ legendCenter,
955
+ mergeChartOption as mergeChartOption3,
956
+ pointLabel
957
+ } from "@classic-homes/charts-core";
958
+ import { jsx as jsx8 } from "react/jsx-runtime";
678
959
  echarts5.use([EChartsPieChart, TooltipComponent3, LegendComponent3, TitleComponent3, CanvasRenderer3]);
679
- var PieChart = React7.forwardRef(
960
+ var PieChart = React8.forwardRef(
680
961
  ({
681
962
  title,
682
963
  description,
@@ -692,58 +973,107 @@ var PieChart = React7.forwardRef(
692
973
  showLabels = true,
693
974
  labelPosition = "outside",
694
975
  innerRadius = 0,
976
+ labelFormatter,
977
+ tooltipFormatter,
978
+ tooltipValueFormatter,
979
+ legend: legendConfig,
980
+ renderLegend,
981
+ onLegendChange,
982
+ option: optionOverride,
695
983
  onClick,
696
984
  class: className,
697
985
  ...props
698
986
  }, ref) => {
699
987
  const isEmpty = !data?.length;
700
- const option = React7.useMemo(() => {
988
+ const currentTheme = useChartTheme(theme);
989
+ const legend = useChartLegend({
990
+ names: data?.map((d) => d.name) ?? [],
991
+ colors: data?.map((d) => d.color) ?? [],
992
+ palette: getChartColors3(currentTheme).series,
993
+ config: legendConfig,
994
+ title,
995
+ onLegendChange
996
+ });
997
+ const customLegend = renderLegend !== void 0;
998
+ const option = React8.useMemo(() => {
701
999
  if (isEmpty) return {};
702
- return {
703
- tooltip: showTooltip ? {
704
- trigger: "item",
705
- formatter: "{a} <br/>{b}: {c} ({d}%)"
706
- } : void 0,
707
- legend: showLegend ? {
708
- orient: "horizontal",
709
- bottom: 0,
710
- data: data.map((d) => d.name)
711
- } : void 0,
712
- series: [
713
- {
714
- name: title,
715
- type: "pie",
716
- radius: innerRadius ? [innerRadius, "70%"] : "70%",
717
- center: ["50%", "45%"],
718
- data: data.map((item) => ({
719
- name: item.name,
720
- value: item.value,
721
- itemStyle: item.color ? { color: item.color } : void 0
722
- })),
723
- label: showLabels ? {
724
- show: true,
725
- position: labelPosition,
726
- formatter: labelPosition === "inside" ? "{d}%" : "{b}: {d}%"
727
- } : {
728
- show: false
729
- },
730
- emphasis: {
731
- itemStyle: {
732
- shadowBlur: 10,
733
- shadowOffsetX: 0,
734
- shadowColor: "rgba(0, 0, 0, 0.5)"
1000
+ const valueFormatter = tooltipFormatter === void 0 ? tooltipValueFormatter : void 0;
1001
+ const tooltipBase = {
1002
+ trigger: "item",
1003
+ formatter: valueFormatter ? (params) => {
1004
+ const p = params;
1005
+ return `${escapeHtml(p.seriesName)} <br/>${escapeHtml(p.name)}: ${escapeHtml(formatTooltipValue(p.value, valueFormatter))} (${p.percent}%)`;
1006
+ } : "{a} <br/>{b}: {c} ({d}%)"
1007
+ };
1008
+ return mergeChartOption3(
1009
+ {
1010
+ tooltip: buildTooltip3(tooltipBase, {
1011
+ show: showTooltip,
1012
+ formatter: tooltipFormatter,
1013
+ valueFormatter: tooltipValueFormatter,
1014
+ getLabel: itemLabelLookup(data)
1015
+ }),
1016
+ legend: buildLegend3(
1017
+ data.map((d) => d.name),
1018
+ legendConfig,
1019
+ { show: showLegend, custom: customLegend, defaults: { orient: "horizontal" } }
1020
+ ),
1021
+ series: [
1022
+ {
1023
+ name: title,
1024
+ type: "pie",
1025
+ radius: innerRadius ? [innerRadius, "70%"] : "70%",
1026
+ center: legendCenter(legendConfig, showLegend && !customLegend, ["50%", "45%"]),
1027
+ data: data.map((item) => ({
1028
+ name: item.name,
1029
+ value: item.value,
1030
+ itemStyle: item.color ? { color: item.color } : void 0,
1031
+ label: pointLabel(item.label, labelFormatter, { position: labelPosition })
1032
+ })),
1033
+ label: buildDataLabel3(
1034
+ {
1035
+ show: true,
1036
+ position: labelPosition,
1037
+ formatter: labelPosition === "inside" ? "{d}%" : "{b}: {d}%"
1038
+ },
1039
+ { show: false },
1040
+ { show: showLabels, formatter: labelFormatter, getLabel: itemLabelLookup(data) }
1041
+ ),
1042
+ emphasis: {
1043
+ itemStyle: {
1044
+ shadowBlur: 10,
1045
+ shadowOffsetX: 0,
1046
+ shadowColor: "rgba(0, 0, 0, 0.5)"
1047
+ }
735
1048
  }
736
1049
  }
737
- }
738
- ]
739
- };
740
- }, [data, isEmpty, showTooltip, showLegend, showLabels, labelPosition, innerRadius, title]);
1050
+ ]
1051
+ },
1052
+ optionOverride
1053
+ );
1054
+ }, [
1055
+ data,
1056
+ isEmpty,
1057
+ showTooltip,
1058
+ showLegend,
1059
+ showLabels,
1060
+ labelPosition,
1061
+ innerRadius,
1062
+ title,
1063
+ labelFormatter,
1064
+ tooltipFormatter,
1065
+ tooltipValueFormatter,
1066
+ legendConfig,
1067
+ customLegend,
1068
+ optionOverride
1069
+ ]);
741
1070
  const latest = useLatestRef({ onClick });
742
1071
  const { containerRef } = useChart({
743
1072
  option,
744
1073
  theme,
745
1074
  animation,
746
1075
  onReady: (chart) => {
1076
+ legend.attach(chart);
747
1077
  chart.on("click", (params) => {
748
1078
  const { onClick: onClick2 } = latest.current;
749
1079
  if (!onClick2) return;
@@ -759,7 +1089,7 @@ var PieChart = React7.forwardRef(
759
1089
  }
760
1090
  });
761
1091
  const accessibilityDescription = description || (!isEmpty ? generatePieChartDescription(title, data, false) : void 0);
762
- return /* @__PURE__ */ jsx7(
1092
+ return /* @__PURE__ */ jsx8(
763
1093
  ChartContainer,
764
1094
  {
765
1095
  ref,
@@ -771,8 +1101,10 @@ var PieChart = React7.forwardRef(
771
1101
  empty: isEmpty,
772
1102
  emptyMessage,
773
1103
  className: cn(className),
1104
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
1105
+ legendPosition: legendConfig?.position,
774
1106
  ...props,
775
- children: /* @__PURE__ */ jsx7("div", { ref: containerRef, className: "h-full w-full" })
1107
+ children: /* @__PURE__ */ jsx8("div", { ref: containerRef, className: "h-full w-full" })
776
1108
  }
777
1109
  );
778
1110
  }
@@ -780,7 +1112,7 @@ var PieChart = React7.forwardRef(
780
1112
  PieChart.displayName = "PieChart";
781
1113
 
782
1114
  // src/components/core/AreaChart.tsx
783
- import * as React8 from "react";
1115
+ import * as React9 from "react";
784
1116
  import * as echarts6 from "echarts/core";
785
1117
  import { LineChart as EChartsLineChart2 } from "echarts/charts";
786
1118
  import {
@@ -790,8 +1122,21 @@ import {
790
1122
  TitleComponent as TitleComponent4
791
1123
  } from "echarts/components";
792
1124
  import { CanvasRenderer as CanvasRenderer4 } from "echarts/renderers";
793
- import { colorWithAlpha, getChartColors as getChartColors2, THEME_DARK as THEME_DARK3 } from "@classic-homes/charts-core";
794
- import { jsx as jsx8 } from "react/jsx-runtime";
1125
+ import {
1126
+ buildAxis as buildAxis3,
1127
+ buildDataLabel as buildDataLabel4,
1128
+ buildLegend as buildLegend4,
1129
+ buildTooltip as buildTooltip4,
1130
+ colorWithAlpha,
1131
+ getChartColors as getChartColors4,
1132
+ hasPointLabels as hasPointLabels2,
1133
+ gridInset as gridInset3,
1134
+ mergeChartOption as mergeChartOption4,
1135
+ seriesLabelLookup as seriesLabelLookup3,
1136
+ THEME_DARK as THEME_DARK3,
1137
+ withPointLabels as withPointLabels3
1138
+ } from "@classic-homes/charts-core";
1139
+ import { jsx as jsx9 } from "react/jsx-runtime";
795
1140
  echarts6.use([
796
1141
  EChartsLineChart2,
797
1142
  GridComponent3,
@@ -800,7 +1145,7 @@ echarts6.use([
800
1145
  TitleComponent4,
801
1146
  CanvasRenderer4
802
1147
  ]);
803
- var AreaChart = React8.forwardRef(
1148
+ var AreaChart = React9.forwardRef(
804
1149
  ({
805
1150
  title,
806
1151
  description,
@@ -817,76 +1162,143 @@ var AreaChart = React8.forwardRef(
817
1162
  smooth = false,
818
1163
  stacked = false,
819
1164
  gradient = true,
1165
+ showLabels = false,
1166
+ labelFormatter,
1167
+ xAxisConfig,
1168
+ yAxisConfig,
1169
+ tooltipFormatter,
1170
+ tooltipValueFormatter,
1171
+ legend: legendConfig,
1172
+ renderLegend,
1173
+ onLegendChange,
1174
+ option: optionOverride,
820
1175
  onClick,
821
1176
  class: className,
822
1177
  ...props
823
1178
  }, ref) => {
824
1179
  const isEmpty = !data?.categories?.length || !data?.series?.length || data.series.every((s) => !s.data?.length);
825
1180
  const currentTheme = useChartTheme(theme);
826
- const chartColors = getChartColors2(currentTheme);
827
- const option = React8.useMemo(() => {
1181
+ const chartColors = getChartColors4(currentTheme);
1182
+ const legend = useChartLegend({
1183
+ names: data?.series?.map((s) => s.name) ?? [],
1184
+ colors: data?.series?.map((s) => s.color) ?? [],
1185
+ palette: chartColors.series,
1186
+ config: legendConfig,
1187
+ title,
1188
+ onLegendChange
1189
+ });
1190
+ const customLegend = renderLegend !== void 0;
1191
+ const option = React9.useMemo(() => {
828
1192
  if (isEmpty) return {};
829
- return {
830
- grid: {
831
- left: "3%",
832
- right: "4%",
833
- bottom: "3%",
834
- containLabel: true
835
- },
836
- tooltip: showTooltip ? {
837
- trigger: "axis",
838
- axisPointer: {
839
- type: "cross",
840
- label: {
841
- backgroundColor: chartColors.axisPointerLabel
1193
+ const getLabel = seriesLabelLookup3(data.series);
1194
+ const showSymbol = showLabels || hasPointLabels2(data.series);
1195
+ return mergeChartOption4(
1196
+ {
1197
+ grid: {
1198
+ left: "3%",
1199
+ right: "4%",
1200
+ bottom: "3%",
1201
+ containLabel: true,
1202
+ ...gridInset3(legendConfig, showLegend && !customLegend, xAxisConfig, yAxisConfig)
1203
+ },
1204
+ tooltip: buildTooltip4(
1205
+ {
1206
+ trigger: "axis",
1207
+ axisPointer: {
1208
+ type: "cross",
1209
+ label: {
1210
+ backgroundColor: chartColors.axisPointerLabel
1211
+ }
1212
+ }
1213
+ },
1214
+ {
1215
+ show: showTooltip,
1216
+ formatter: tooltipFormatter,
1217
+ valueFormatter: tooltipValueFormatter,
1218
+ getLabel
842
1219
  }
843
- }
844
- } : void 0,
845
- legend: showLegend ? {
846
- data: data.series.map((s) => s.name),
847
- bottom: 0
848
- } : void 0,
849
- xAxis: {
850
- type: "category",
851
- boundaryGap: false,
852
- data: data.categories
853
- },
854
- yAxis: {
855
- type: "value",
856
- splitLine: {
857
- show: showGrid
858
- }
859
- },
860
- series: data.series.map((series, index) => {
861
- const base = series.color || chartColors.series[index % chartColors.series.length];
862
- return {
863
- name: series.name,
864
- type: "line",
865
- data: series.data,
866
- smooth,
867
- stack: stacked ? "Total" : void 0,
868
- areaStyle: gradient ? {
869
- opacity: 0.8,
870
- color: new echarts6.graphic.LinearGradient(0, 0, 0, 1, [
871
- { offset: 0, color: colorWithAlpha(base, 0.8) },
872
- { offset: 1, color: colorWithAlpha(base, 0.1) }
873
- ])
874
- } : { opacity: 0.5 },
875
- itemStyle: series.color ? { color: series.color } : void 0,
876
- emphasis: {
877
- focus: "series"
1220
+ ),
1221
+ legend: buildLegend4(
1222
+ data.series.map((s) => s.name),
1223
+ legendConfig,
1224
+ { show: showLegend, custom: customLegend }
1225
+ ),
1226
+ xAxis: buildAxis3(
1227
+ {
1228
+ type: "category",
1229
+ boundaryGap: false,
1230
+ data: data.categories
878
1231
  },
879
- showSymbol: false
880
- };
881
- })
882
- };
883
- }, [data, isEmpty, showTooltip, showLegend, showGrid, smooth, stacked, gradient, chartColors]);
1232
+ xAxisConfig
1233
+ ),
1234
+ yAxis: buildAxis3(
1235
+ {
1236
+ type: "value",
1237
+ splitLine: {
1238
+ show: showGrid
1239
+ }
1240
+ },
1241
+ yAxisConfig
1242
+ ),
1243
+ series: data.series.map((series, index) => {
1244
+ const base = series.color || chartColors.series[index % chartColors.series.length];
1245
+ return {
1246
+ name: series.name,
1247
+ type: "line",
1248
+ data: withPointLabels3(series.data, series.labels, labelFormatter, {
1249
+ position: "top"
1250
+ }),
1251
+ smooth,
1252
+ stack: stacked ? "Total" : void 0,
1253
+ areaStyle: gradient ? {
1254
+ opacity: 0.8,
1255
+ color: new echarts6.graphic.LinearGradient(0, 0, 0, 1, [
1256
+ { offset: 0, color: colorWithAlpha(base, 0.8) },
1257
+ { offset: 1, color: colorWithAlpha(base, 0.1) }
1258
+ ])
1259
+ } : { opacity: 0.5 },
1260
+ itemStyle: series.color ? { color: series.color } : void 0,
1261
+ label: buildDataLabel4({ show: true, position: "top" }, void 0, {
1262
+ show: showLabels,
1263
+ formatter: labelFormatter,
1264
+ getLabel
1265
+ }),
1266
+ emphasis: {
1267
+ focus: "series"
1268
+ },
1269
+ showSymbol
1270
+ };
1271
+ })
1272
+ },
1273
+ optionOverride
1274
+ );
1275
+ }, [
1276
+ data,
1277
+ isEmpty,
1278
+ showTooltip,
1279
+ showLegend,
1280
+ showGrid,
1281
+ smooth,
1282
+ stacked,
1283
+ gradient,
1284
+ chartColors,
1285
+ showLabels,
1286
+ labelFormatter,
1287
+ xAxisConfig,
1288
+ yAxisConfig,
1289
+ tooltipFormatter,
1290
+ tooltipValueFormatter,
1291
+ legendConfig,
1292
+ customLegend,
1293
+ optionOverride
1294
+ ]);
884
1295
  const latest = useLatestRef({ onClick });
885
1296
  const { containerRef } = useChart({
886
1297
  option,
887
1298
  theme: currentTheme === THEME_DARK3 ? "dark" : "light",
888
1299
  animation,
889
1300
  onReady: (chart) => {
1301
+ legend.attach(chart);
890
1302
  chart.on("click", (params) => {
891
1303
  const { onClick: onClick2 } = latest.current;
892
1304
  if (!onClick2) return;
@@ -904,7 +1316,7 @@ var AreaChart = React8.forwardRef(
904
1316
  }
905
1317
  });
906
1318
  const accessibilityDescription = description || (!isEmpty ? `${title}. Area chart with ${data.categories.length} data points across ${data.series.length} series.` : void 0);
907
- return /* @__PURE__ */ jsx8(
1319
+ return /* @__PURE__ */ jsx9(
908
1320
  ChartContainer,
909
1321
  {
910
1322
  ref,
@@ -916,8 +1328,10 @@ var AreaChart = React8.forwardRef(
916
1328
  empty: isEmpty,
917
1329
  emptyMessage,
918
1330
  className: cn(className),
1331
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
1332
+ legendPosition: legendConfig?.position,
919
1333
  ...props,
920
- children: /* @__PURE__ */ jsx8("div", { ref: containerRef, className: "h-full w-full" })
1334
+ children: /* @__PURE__ */ jsx9("div", { ref: containerRef, className: "h-full w-full" })
921
1335
  }
922
1336
  );
923
1337
  }
@@ -925,7 +1339,7 @@ var AreaChart = React8.forwardRef(
925
1339
  AreaChart.displayName = "AreaChart";
926
1340
 
927
1341
  // src/components/core/ScatterChart.tsx
928
- import * as React9 from "react";
1342
+ import * as React10 from "react";
929
1343
  import * as echarts7 from "echarts/core";
930
1344
  import { ScatterChart as EChartsScatterChart, LineChart as EChartsLineChart3 } from "echarts/charts";
931
1345
  import {
@@ -935,8 +1349,21 @@ import {
935
1349
  TitleComponent as TitleComponent5
936
1350
  } from "echarts/components";
937
1351
  import { CanvasRenderer as CanvasRenderer5 } from "echarts/renderers";
938
- import { escapeHtml, generateScatterChartDescription } from "@classic-homes/charts-core";
939
- import { jsx as jsx9 } from "react/jsx-runtime";
1352
+ import {
1353
+ buildAxis as buildAxis4,
1354
+ buildDataLabel as buildDataLabel5,
1355
+ buildLegend as buildLegend5,
1356
+ buildTooltip as buildTooltip5,
1357
+ escapeHtml as escapeHtml2,
1358
+ formatTooltipValue as formatTooltipValue2,
1359
+ generateScatterChartDescription,
1360
+ getChartColors as getChartColors5,
1361
+ gridInset as gridInset4,
1362
+ mergeChartOption as mergeChartOption5,
1363
+ seriesLabelLookup as seriesLabelLookup4,
1364
+ withPointLabels as withPointLabels4
1365
+ } from "@classic-homes/charts-core";
1366
+ import { jsx as jsx10 } from "react/jsx-runtime";
940
1367
  echarts7.use([
941
1368
  EChartsScatterChart,
942
1369
  // Trend lines are line series
@@ -947,7 +1374,7 @@ echarts7.use([
947
1374
  TitleComponent5,
948
1375
  CanvasRenderer5
949
1376
  ]);
950
- var ScatterChart = React9.forwardRef(
1377
+ var ScatterChart = React10.forwardRef(
951
1378
  ({
952
1379
  title,
953
1380
  description,
@@ -963,19 +1390,45 @@ var ScatterChart = React9.forwardRef(
963
1390
  showGrid = true,
964
1391
  showTrendLine = false,
965
1392
  symbolSize = 10,
1393
+ showLabels = false,
1394
+ labelFormatter,
1395
+ xAxisConfig,
1396
+ yAxisConfig,
1397
+ tooltipFormatter,
1398
+ tooltipValueFormatter,
1399
+ legend: legendConfig,
1400
+ renderLegend,
1401
+ onLegendChange,
1402
+ option: optionOverride,
966
1403
  onClick,
967
1404
  class: className,
968
1405
  ...props
969
1406
  }, ref) => {
970
1407
  const isEmpty = !data?.series?.length || data.series.every((s) => !s.data?.length);
971
- const option = React9.useMemo(() => {
1408
+ const currentTheme = useChartTheme(theme);
1409
+ const legend = useChartLegend({
1410
+ names: data?.series?.map((s) => s.name) ?? [],
1411
+ colors: data?.series?.map((s) => s.color) ?? [],
1412
+ palette: getChartColors5(currentTheme).series,
1413
+ config: legendConfig,
1414
+ title,
1415
+ onLegendChange
1416
+ });
1417
+ const customLegend = renderLegend !== void 0;
1418
+ const option = React10.useMemo(() => {
972
1419
  if (isEmpty) return {};
1420
+ const getLabel = seriesLabelLookup4(data.series);
973
1421
  const series = data.series.map((s) => ({
974
1422
  name: s.name,
975
1423
  type: "scatter",
976
- data: s.data,
1424
+ data: withPointLabels4(s.data, s.labels, labelFormatter, { position: "top" }),
977
1425
  symbolSize: typeof symbolSize === "function" ? symbolSize : symbolSize,
978
1426
  itemStyle: s.color ? { color: s.color } : void 0,
1427
+ label: buildDataLabel5({ show: true, position: "top" }, void 0, {
1428
+ show: showLabels,
1429
+ formatter: labelFormatter,
1430
+ getLabel
1431
+ }),
979
1432
  emphasis: {
980
1433
  focus: "series",
981
1434
  itemStyle: {
@@ -1023,46 +1476,85 @@ var ScatterChart = React9.forwardRef(
1023
1476
  });
1024
1477
  });
1025
1478
  }
1026
- return {
1027
- grid: {
1028
- left: "3%",
1029
- right: "4%",
1030
- bottom: "3%",
1031
- containLabel: true
1032
- },
1033
- tooltip: showTooltip ? {
1034
- trigger: "item",
1035
- formatter: (params) => {
1036
- const p = params;
1037
- if (!p.value) return "";
1038
- return `${escapeHtml(p.seriesName)}<br/>X: ${escapeHtml(p.value[0])}<br/>Y: ${escapeHtml(p.value[1])}`;
1039
- }
1040
- } : void 0,
1041
- legend: showLegend ? {
1042
- data: data.series.map((s) => s.name),
1043
- bottom: 0
1044
- } : void 0,
1045
- xAxis: {
1046
- type: "value",
1047
- splitLine: {
1048
- show: showGrid
1049
- }
1050
- },
1051
- yAxis: {
1052
- type: "value",
1053
- splitLine: {
1054
- show: showGrid
1055
- }
1056
- },
1057
- series
1058
- };
1059
- }, [data, isEmpty, showTooltip, showLegend, showGrid, showTrendLine, symbolSize]);
1479
+ return mergeChartOption5(
1480
+ {
1481
+ grid: {
1482
+ left: "3%",
1483
+ right: "4%",
1484
+ bottom: "3%",
1485
+ containLabel: true,
1486
+ ...gridInset4(legendConfig, showLegend && !customLegend, xAxisConfig, yAxisConfig)
1487
+ },
1488
+ tooltip: buildTooltip5(
1489
+ {
1490
+ trigger: "item",
1491
+ formatter: (params) => {
1492
+ const p = params;
1493
+ if (!p.value) return "";
1494
+ const x = formatTooltipValue2(p.value[0], tooltipValueFormatter);
1495
+ const y = formatTooltipValue2(p.value[1], tooltipValueFormatter);
1496
+ return `${escapeHtml2(p.seriesName)}<br/>X: ${escapeHtml2(x)}<br/>Y: ${escapeHtml2(y)}`;
1497
+ }
1498
+ },
1499
+ {
1500
+ show: showTooltip,
1501
+ formatter: tooltipFormatter,
1502
+ valueFormatter: tooltipValueFormatter,
1503
+ getLabel
1504
+ }
1505
+ ),
1506
+ legend: buildLegend5(
1507
+ data.series.map((s) => s.name),
1508
+ legendConfig,
1509
+ { show: showLegend, custom: customLegend }
1510
+ ),
1511
+ xAxis: buildAxis4(
1512
+ {
1513
+ type: "value",
1514
+ splitLine: {
1515
+ show: showGrid
1516
+ }
1517
+ },
1518
+ xAxisConfig
1519
+ ),
1520
+ yAxis: buildAxis4(
1521
+ {
1522
+ type: "value",
1523
+ splitLine: {
1524
+ show: showGrid
1525
+ }
1526
+ },
1527
+ yAxisConfig
1528
+ ),
1529
+ series
1530
+ },
1531
+ optionOverride
1532
+ );
1533
+ }, [
1534
+ data,
1535
+ isEmpty,
1536
+ showTooltip,
1537
+ showLegend,
1538
+ showGrid,
1539
+ showTrendLine,
1540
+ symbolSize,
1541
+ showLabels,
1542
+ labelFormatter,
1543
+ xAxisConfig,
1544
+ yAxisConfig,
1545
+ tooltipFormatter,
1546
+ tooltipValueFormatter,
1547
+ legendConfig,
1548
+ customLegend,
1549
+ optionOverride
1550
+ ]);
1060
1551
  const latest = useLatestRef({ onClick });
1061
1552
  const { containerRef } = useChart({
1062
1553
  option,
1063
1554
  theme,
1064
1555
  animation,
1065
1556
  onReady: (chart) => {
1557
+ legend.attach(chart);
1066
1558
  chart.on("click", (params) => {
1067
1559
  const { onClick: onClick2 } = latest.current;
1068
1560
  if (!onClick2) return;
@@ -1080,7 +1572,7 @@ var ScatterChart = React9.forwardRef(
1080
1572
  }
1081
1573
  });
1082
1574
  const accessibilityDescription = description || (!isEmpty ? generateScatterChartDescription(title, data) : void 0);
1083
- return /* @__PURE__ */ jsx9(
1575
+ return /* @__PURE__ */ jsx10(
1084
1576
  ChartContainer,
1085
1577
  {
1086
1578
  ref,
@@ -1092,8 +1584,10 @@ var ScatterChart = React9.forwardRef(
1092
1584
  empty: isEmpty,
1093
1585
  emptyMessage,
1094
1586
  className: cn(className),
1587
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
1588
+ legendPosition: legendConfig?.position,
1095
1589
  ...props,
1096
- children: /* @__PURE__ */ jsx9("div", { ref: containerRef, className: "h-full w-full" })
1590
+ children: /* @__PURE__ */ jsx10("div", { ref: containerRef, className: "h-full w-full" })
1097
1591
  }
1098
1592
  );
1099
1593
  }
@@ -1101,19 +1595,29 @@ var ScatterChart = React9.forwardRef(
1101
1595
  ScatterChart.displayName = "ScatterChart";
1102
1596
 
1103
1597
  // src/components/core/DonutChart.tsx
1104
- import * as React10 from "react";
1598
+ import * as React11 from "react";
1105
1599
  import * as echarts8 from "echarts/core";
1106
1600
  import { PieChart as EChartsPieChart2 } from "echarts/charts";
1107
1601
  import { TooltipComponent as TooltipComponent6, LegendComponent as LegendComponent6, TitleComponent as TitleComponent6 } from "echarts/components";
1108
1602
  import { CanvasRenderer as CanvasRenderer6 } from "echarts/renderers";
1109
1603
  import {
1604
+ buildDataLabel as buildDataLabel6,
1605
+ buildLegend as buildLegend6,
1606
+ buildTooltip as buildTooltip6,
1607
+ escapeHtml as escapeHtml3,
1608
+ formatTooltipValue as formatTooltipValue3,
1110
1609
  generatePieChartDescription as generatePieChartDescription2,
1111
- getChartColors as getChartColors3,
1610
+ getChartColors as getChartColors6,
1611
+ itemLabelLookup as itemLabelLookup2,
1612
+ anchorCenterGraphic,
1613
+ legendCenter as legendCenter2,
1614
+ mergeChartOption as mergeChartOption6,
1615
+ pointLabel as pointLabel2,
1112
1616
  THEME_DARK as THEME_DARK4
1113
1617
  } from "@classic-homes/charts-core";
1114
- import { jsx as jsx10 } from "react/jsx-runtime";
1618
+ import { jsx as jsx11 } from "react/jsx-runtime";
1115
1619
  echarts8.use([EChartsPieChart2, TooltipComponent6, LegendComponent6, TitleComponent6, CanvasRenderer6]);
1116
- var DonutChart = React10.forwardRef(
1620
+ var DonutChart = React11.forwardRef(
1117
1621
  ({
1118
1622
  title,
1119
1623
  description,
@@ -1131,95 +1635,134 @@ var DonutChart = React10.forwardRef(
1131
1635
  innerRadius = "50%",
1132
1636
  centerLabel,
1133
1637
  centerValue,
1638
+ labelFormatter,
1639
+ tooltipFormatter,
1640
+ tooltipValueFormatter,
1641
+ legend: legendConfig,
1642
+ renderLegend,
1643
+ onLegendChange,
1644
+ option: optionOverride,
1134
1645
  onClick,
1135
1646
  class: className,
1136
1647
  ...props
1137
1648
  }, ref) => {
1138
1649
  const isEmpty = !data?.length;
1139
1650
  const currentTheme = useChartTheme(theme);
1140
- const chartColors = getChartColors3(currentTheme);
1141
- const option = React10.useMemo(() => {
1651
+ const chartColors = getChartColors6(currentTheme);
1652
+ const legend = useChartLegend({
1653
+ names: data?.map((d) => d.name) ?? [],
1654
+ colors: data?.map((d) => d.color) ?? [],
1655
+ palette: chartColors.series,
1656
+ config: legendConfig,
1657
+ title,
1658
+ onLegendChange
1659
+ });
1660
+ const customLegend = renderLegend !== void 0;
1661
+ const option = React11.useMemo(() => {
1142
1662
  if (isEmpty) return {};
1663
+ const pieCenter = legendCenter2(legendConfig, showLegend && !customLegend, ["50%", "45%"]);
1143
1664
  const graphicElements = [];
1144
1665
  if (centerLabel || centerValue) {
1145
- graphicElements.push({
1146
- type: "group",
1147
- left: "center",
1148
- top: "40%",
1149
- children: [
1150
- ...centerValue !== void 0 ? [
1151
- {
1152
- type: "text",
1153
- style: {
1154
- text: String(centerValue),
1155
- fontSize: 28,
1156
- fontWeight: "bold",
1157
- fill: chartColors.foreground,
1158
- textAlign: "center"
1159
- },
1160
- top: 0,
1161
- left: "center"
1162
- }
1163
- ] : [],
1164
- ...centerLabel ? [
1165
- {
1166
- type: "text",
1167
- style: {
1168
- text: centerLabel,
1169
- fontSize: 14,
1170
- fill: chartColors.mutedForeground,
1171
- textAlign: "center"
1172
- },
1173
- top: centerValue !== void 0 ? 35 : 0,
1174
- left: "center"
1175
- }
1176
- ] : []
1177
- ]
1178
- });
1179
- }
1180
- return {
1181
- tooltip: showTooltip ? {
1182
- trigger: "item",
1183
- formatter: "{a} <br/>{b}: {c} ({d}%)"
1184
- } : void 0,
1185
- legend: showLegend ? {
1186
- orient: "horizontal",
1187
- bottom: 0,
1188
- data: data.map((d) => d.name)
1189
- } : void 0,
1190
- graphic: graphicElements.length > 0 ? graphicElements : void 0,
1191
- series: [
1192
- {
1193
- name: title,
1194
- type: "pie",
1195
- radius: [innerRadius, "70%"],
1196
- center: ["50%", "45%"],
1197
- avoidLabelOverlap: true,
1198
- data: data.map((item) => ({
1199
- name: item.name,
1200
- value: item.value,
1201
- itemStyle: item.color ? { color: item.color } : void 0
1202
- })),
1203
- label: showLabels ? {
1204
- show: true,
1205
- position: labelPosition,
1206
- formatter: labelPosition === "inside" ? "{d}%" : "{b}: {d}%"
1207
- } : {
1208
- show: false
1209
- },
1210
- labelLine: {
1211
- show: showLabels && labelPosition === "outside"
1666
+ graphicElements.push(
1667
+ anchorCenterGraphic(
1668
+ {
1669
+ type: "group",
1670
+ left: "center",
1671
+ top: "40%",
1672
+ children: [
1673
+ ...centerValue !== void 0 ? [
1674
+ {
1675
+ type: "text",
1676
+ style: {
1677
+ text: String(centerValue),
1678
+ fontSize: 28,
1679
+ fontWeight: "bold",
1680
+ fill: chartColors.foreground,
1681
+ textAlign: "center"
1682
+ },
1683
+ top: 0,
1684
+ left: "center"
1685
+ }
1686
+ ] : [],
1687
+ ...centerLabel ? [
1688
+ {
1689
+ type: "text",
1690
+ style: {
1691
+ text: centerLabel,
1692
+ fontSize: 14,
1693
+ fill: chartColors.mutedForeground,
1694
+ textAlign: "center"
1695
+ },
1696
+ top: centerValue !== void 0 ? 35 : 0,
1697
+ left: "center"
1698
+ }
1699
+ ] : []
1700
+ ]
1212
1701
  },
1213
- emphasis: {
1214
- itemStyle: {
1215
- shadowBlur: 10,
1216
- shadowOffsetX: 0,
1217
- shadowColor: "rgba(0, 0, 0, 0.5)"
1702
+ pieCenter,
1703
+ ["50%", "45%"]
1704
+ )
1705
+ );
1706
+ }
1707
+ const valueFormatter = tooltipFormatter === void 0 ? tooltipValueFormatter : void 0;
1708
+ const tooltipBase = {
1709
+ trigger: "item",
1710
+ formatter: valueFormatter ? (params) => {
1711
+ const p = params;
1712
+ return `${escapeHtml3(p.seriesName)} <br/>${escapeHtml3(p.name)}: ${escapeHtml3(formatTooltipValue3(p.value, valueFormatter))} (${p.percent}%)`;
1713
+ } : "{a} <br/>{b}: {c} ({d}%)"
1714
+ };
1715
+ return mergeChartOption6(
1716
+ {
1717
+ tooltip: buildTooltip6(tooltipBase, {
1718
+ show: showTooltip,
1719
+ formatter: tooltipFormatter,
1720
+ valueFormatter: tooltipValueFormatter,
1721
+ getLabel: itemLabelLookup2(data)
1722
+ }),
1723
+ legend: buildLegend6(
1724
+ data.map((d) => d.name),
1725
+ legendConfig,
1726
+ { show: showLegend, custom: customLegend, defaults: { orient: "horizontal" } }
1727
+ ),
1728
+ graphic: graphicElements.length > 0 ? graphicElements : void 0,
1729
+ series: [
1730
+ {
1731
+ name: title,
1732
+ type: "pie",
1733
+ radius: [innerRadius, "70%"],
1734
+ center: pieCenter,
1735
+ avoidLabelOverlap: true,
1736
+ data: data.map((item) => ({
1737
+ name: item.name,
1738
+ value: item.value,
1739
+ itemStyle: item.color ? { color: item.color } : void 0,
1740
+ label: pointLabel2(item.label, labelFormatter, { position: labelPosition })
1741
+ })),
1742
+ label: buildDataLabel6(
1743
+ {
1744
+ show: true,
1745
+ position: labelPosition,
1746
+ formatter: labelPosition === "inside" ? "{d}%" : "{b}: {d}%"
1747
+ },
1748
+ { show: false },
1749
+ { show: showLabels, formatter: labelFormatter, getLabel: itemLabelLookup2(data) }
1750
+ ),
1751
+ labelLine: {
1752
+ show: showLabels && labelPosition === "outside"
1753
+ },
1754
+ emphasis: {
1755
+ itemStyle: {
1756
+ shadowBlur: 10,
1757
+ shadowOffsetX: 0,
1758
+ shadowColor: "rgba(0, 0, 0, 0.5)"
1759
+ }
1218
1760
  }
1219
1761
  }
1220
- }
1221
- ]
1222
- };
1762
+ ]
1763
+ },
1764
+ optionOverride
1765
+ );
1223
1766
  }, [
1224
1767
  data,
1225
1768
  isEmpty,
@@ -1231,7 +1774,13 @@ var DonutChart = React10.forwardRef(
1231
1774
  centerLabel,
1232
1775
  centerValue,
1233
1776
  title,
1234
- chartColors
1777
+ chartColors,
1778
+ labelFormatter,
1779
+ tooltipFormatter,
1780
+ tooltipValueFormatter,
1781
+ legendConfig,
1782
+ customLegend,
1783
+ optionOverride
1235
1784
  ]);
1236
1785
  const latest = useLatestRef({ onClick });
1237
1786
  const { containerRef } = useChart({
@@ -1239,6 +1788,7 @@ var DonutChart = React10.forwardRef(
1239
1788
  theme: currentTheme === THEME_DARK4 ? "dark" : "light",
1240
1789
  animation,
1241
1790
  onReady: (chart) => {
1791
+ legend.attach(chart);
1242
1792
  chart.on("click", (params) => {
1243
1793
  const { onClick: onClick2 } = latest.current;
1244
1794
  if (!onClick2) return;
@@ -1254,7 +1804,7 @@ var DonutChart = React10.forwardRef(
1254
1804
  }
1255
1805
  });
1256
1806
  const accessibilityDescription = description || (!isEmpty ? generatePieChartDescription2(title, data, true) : void 0);
1257
- return /* @__PURE__ */ jsx10(
1807
+ return /* @__PURE__ */ jsx11(
1258
1808
  ChartContainer,
1259
1809
  {
1260
1810
  ref,
@@ -1266,8 +1816,10 @@ var DonutChart = React10.forwardRef(
1266
1816
  empty: isEmpty,
1267
1817
  emptyMessage,
1268
1818
  className: cn(className),
1819
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
1820
+ legendPosition: legendConfig?.position,
1269
1821
  ...props,
1270
- children: /* @__PURE__ */ jsx10("div", { ref: containerRef, className: "h-full w-full" })
1822
+ children: /* @__PURE__ */ jsx11("div", { ref: containerRef, className: "h-full w-full" })
1271
1823
  }
1272
1824
  );
1273
1825
  }
@@ -1275,7 +1827,7 @@ var DonutChart = React10.forwardRef(
1275
1827
  DonutChart.displayName = "DonutChart";
1276
1828
 
1277
1829
  // src/components/extended/RadarChart.tsx
1278
- import * as React11 from "react";
1830
+ import * as React12 from "react";
1279
1831
  import * as echarts9 from "echarts/core";
1280
1832
  import { RadarChart as EChartsRadarChart } from "echarts/charts";
1281
1833
  import {
@@ -1285,7 +1837,15 @@ import {
1285
1837
  RadarComponent
1286
1838
  } from "echarts/components";
1287
1839
  import { CanvasRenderer as CanvasRenderer7 } from "echarts/renderers";
1288
- import { jsx as jsx11 } from "react/jsx-runtime";
1840
+ import {
1841
+ buildDataLabel as buildDataLabel7,
1842
+ buildLegend as buildLegend7,
1843
+ buildTooltip as buildTooltip7,
1844
+ getChartColors as getChartColors7,
1845
+ legendCenter as legendCenter3,
1846
+ mergeChartOption as mergeChartOption7
1847
+ } from "@classic-homes/charts-core";
1848
+ import { jsx as jsx12 } from "react/jsx-runtime";
1289
1849
  echarts9.use([
1290
1850
  EChartsRadarChart,
1291
1851
  TooltipComponent7,
@@ -1294,7 +1854,7 @@ echarts9.use([
1294
1854
  RadarComponent,
1295
1855
  CanvasRenderer7
1296
1856
  ]);
1297
- var RadarChart = React11.forwardRef(
1857
+ var RadarChart = React12.forwardRef(
1298
1858
  ({
1299
1859
  title,
1300
1860
  description,
@@ -1309,50 +1869,101 @@ var RadarChart = React11.forwardRef(
1309
1869
  showLegend = true,
1310
1870
  showTooltip = true,
1311
1871
  shape = "polygon",
1872
+ showLabels = false,
1873
+ labelFormatter,
1874
+ tooltipFormatter,
1875
+ tooltipValueFormatter,
1876
+ legend: legendConfig,
1877
+ renderLegend,
1878
+ onLegendChange,
1879
+ option: optionOverride,
1312
1880
  class: className,
1313
1881
  ...props
1314
1882
  }, ref) => {
1315
1883
  const isEmpty = !data?.series?.length || !indicators?.length || data.series.every((s) => !s.data?.length);
1316
- const option = React11.useMemo(() => {
1884
+ const currentTheme = useChartTheme(theme);
1885
+ const legend = useChartLegend({
1886
+ names: data?.series?.map((s) => s.name) ?? [],
1887
+ colors: data?.series?.map((s) => s.color) ?? [],
1888
+ palette: getChartColors7(currentTheme).series,
1889
+ config: legendConfig,
1890
+ title,
1891
+ onLegendChange
1892
+ });
1893
+ const customLegend = renderLegend !== void 0;
1894
+ const option = React12.useMemo(() => {
1317
1895
  if (isEmpty) return {};
1318
- return {
1319
- tooltip: showTooltip ? {
1320
- trigger: "item"
1321
- } : void 0,
1322
- legend: showLegend ? {
1323
- data: data.series.map((s) => s.name),
1324
- bottom: 0
1325
- } : void 0,
1326
- radar: {
1327
- indicator: indicators.map((ind) => ({
1328
- name: ind.name,
1329
- max: ind.max,
1330
- min: ind.min
1331
- })),
1332
- shape
1896
+ return mergeChartOption7(
1897
+ {
1898
+ tooltip: buildTooltip7(
1899
+ {
1900
+ trigger: "item"
1901
+ },
1902
+ {
1903
+ show: showTooltip,
1904
+ formatter: tooltipFormatter,
1905
+ valueFormatter: tooltipValueFormatter
1906
+ }
1907
+ ),
1908
+ legend: buildLegend7(
1909
+ data.series.map((s) => s.name),
1910
+ legendConfig,
1911
+ { show: showLegend, custom: customLegend }
1912
+ ),
1913
+ radar: {
1914
+ indicator: indicators.map((ind) => ({
1915
+ name: ind.name,
1916
+ max: ind.max,
1917
+ min: ind.min
1918
+ })),
1919
+ shape,
1920
+ center: legendCenter3(legendConfig, showLegend && !customLegend, ["50%", "50%"])
1921
+ },
1922
+ series: [
1923
+ {
1924
+ type: "radar",
1925
+ label: buildDataLabel7({ show: true }, void 0, {
1926
+ show: showLabels,
1927
+ formatter: labelFormatter
1928
+ }),
1929
+ data: data.series.map((series) => ({
1930
+ name: series.name,
1931
+ value: series.data,
1932
+ itemStyle: series.color ? { color: series.color } : void 0,
1933
+ areaStyle: {
1934
+ opacity: 0.2
1935
+ }
1936
+ }))
1937
+ }
1938
+ ]
1333
1939
  },
1334
- series: [
1335
- {
1336
- type: "radar",
1337
- data: data.series.map((series) => ({
1338
- name: series.name,
1339
- value: series.data,
1340
- itemStyle: series.color ? { color: series.color } : void 0,
1341
- areaStyle: {
1342
- opacity: 0.2
1343
- }
1344
- }))
1345
- }
1346
- ]
1347
- };
1348
- }, [data, indicators, isEmpty, showTooltip, showLegend, shape]);
1940
+ optionOverride
1941
+ );
1942
+ }, [
1943
+ data,
1944
+ indicators,
1945
+ isEmpty,
1946
+ showTooltip,
1947
+ showLegend,
1948
+ shape,
1949
+ showLabels,
1950
+ labelFormatter,
1951
+ tooltipFormatter,
1952
+ tooltipValueFormatter,
1953
+ legendConfig,
1954
+ customLegend,
1955
+ optionOverride
1956
+ ]);
1349
1957
  const { containerRef } = useChart({
1350
1958
  option,
1351
1959
  theme,
1352
- animation
1960
+ animation,
1961
+ onReady: (chart) => {
1962
+ legend.attach(chart);
1963
+ }
1353
1964
  });
1354
1965
  const accessibilityDescription = description || (!isEmpty ? `${title}. Radar chart with ${indicators.length} dimensions comparing ${data.series.length} series.` : void 0);
1355
- return /* @__PURE__ */ jsx11(
1966
+ return /* @__PURE__ */ jsx12(
1356
1967
  ChartContainer,
1357
1968
  {
1358
1969
  ref,
@@ -1364,8 +1975,10 @@ var RadarChart = React11.forwardRef(
1364
1975
  empty: isEmpty,
1365
1976
  emptyMessage,
1366
1977
  className: cn(className),
1978
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
1979
+ legendPosition: legendConfig?.position,
1367
1980
  ...props,
1368
- children: /* @__PURE__ */ jsx11("div", { ref: containerRef, className: "h-full w-full" })
1981
+ children: /* @__PURE__ */ jsx12("div", { ref: containerRef, className: "h-full w-full" })
1369
1982
  }
1370
1983
  );
1371
1984
  }
@@ -1373,12 +1986,23 @@ var RadarChart = React11.forwardRef(
1373
1986
  RadarChart.displayName = "RadarChart";
1374
1987
 
1375
1988
  // src/components/extended/FunnelChart.tsx
1376
- import * as React12 from "react";
1989
+ import * as React13 from "react";
1377
1990
  import * as echarts10 from "echarts/core";
1378
1991
  import { FunnelChart as EChartsFunnelChart } from "echarts/charts";
1379
1992
  import { TooltipComponent as TooltipComponent8, LegendComponent as LegendComponent8, TitleComponent as TitleComponent8 } from "echarts/components";
1380
1993
  import { CanvasRenderer as CanvasRenderer8 } from "echarts/renderers";
1381
- import { jsx as jsx12 } from "react/jsx-runtime";
1994
+ import {
1995
+ buildDataLabel as buildDataLabel8,
1996
+ buildLegend as buildLegend8,
1997
+ buildTooltip as buildTooltip8,
1998
+ escapeHtml as escapeHtml4,
1999
+ formatTooltipValue as formatTooltipValue4,
2000
+ getChartColors as getChartColors8,
2001
+ itemLabelLookup as itemLabelLookup3,
2002
+ mergeChartOption as mergeChartOption8,
2003
+ pointLabel as pointLabel3
2004
+ } from "@classic-homes/charts-core";
2005
+ import { jsx as jsx13 } from "react/jsx-runtime";
1382
2006
  echarts10.use([
1383
2007
  EChartsFunnelChart,
1384
2008
  TooltipComponent8,
@@ -1386,7 +2010,7 @@ echarts10.use([
1386
2010
  TitleComponent8,
1387
2011
  CanvasRenderer8
1388
2012
  ]);
1389
- var FunnelChart = React12.forwardRef(
2013
+ var FunnelChart = React13.forwardRef(
1390
2014
  ({
1391
2015
  title,
1392
2016
  description,
@@ -1402,68 +2026,119 @@ var FunnelChart = React12.forwardRef(
1402
2026
  sort = "descending",
1403
2027
  orientation = "vertical",
1404
2028
  showLabels = true,
2029
+ labelFormatter,
2030
+ tooltipFormatter,
2031
+ tooltipValueFormatter,
2032
+ legend: legendConfig,
2033
+ renderLegend,
2034
+ onLegendChange,
2035
+ option: optionOverride,
1405
2036
  onClick,
1406
2037
  class: className,
1407
2038
  ...props
1408
2039
  }, ref) => {
1409
2040
  const isEmpty = !data?.length;
1410
- const option = React12.useMemo(() => {
2041
+ const currentTheme = useChartTheme(theme);
2042
+ const legend = useChartLegend({
2043
+ names: data?.map((d) => d.name) ?? [],
2044
+ colors: data?.map((d) => d.color) ?? [],
2045
+ palette: getChartColors8(currentTheme).series,
2046
+ config: legendConfig,
2047
+ title,
2048
+ onLegendChange
2049
+ });
2050
+ const customLegend = renderLegend !== void 0;
2051
+ const option = React13.useMemo(() => {
1411
2052
  if (isEmpty) return {};
1412
- return {
1413
- tooltip: showTooltip ? {
1414
- trigger: "item",
1415
- formatter: "{a} <br/>{b}: {c}"
1416
- } : void 0,
1417
- legend: showLegend ? {
1418
- data: data.map((d) => d.name),
1419
- bottom: 0
1420
- } : void 0,
1421
- series: [
1422
- {
1423
- name: title,
1424
- type: "funnel",
1425
- left: "10%",
1426
- top: 60,
1427
- bottom: 60,
1428
- width: "80%",
1429
- min: 0,
1430
- // reduce, not Math.max(...): spreading large arrays overflows the stack
1431
- max: data.reduce((m, d) => d.value > m ? d.value : m, -Infinity),
1432
- minSize: "0%",
1433
- maxSize: "100%",
1434
- sort,
1435
- orient: orientation,
1436
- gap: 2,
1437
- label: showLabels ? {
1438
- show: true,
1439
- position: "inside",
1440
- formatter: "{b}: {c}"
1441
- } : {
1442
- show: false
1443
- },
1444
- labelLine: {
1445
- show: false
1446
- },
1447
- emphasis: {
1448
- label: {
1449
- fontSize: 16
1450
- }
1451
- },
1452
- data: data.map((item) => ({
1453
- name: item.name,
1454
- value: item.value,
1455
- itemStyle: item.color ? { color: item.color } : void 0
1456
- }))
1457
- }
1458
- ]
2053
+ const getLabel = itemLabelLookup3(data);
2054
+ const valueFormatter = tooltipFormatter === void 0 ? tooltipValueFormatter : void 0;
2055
+ const tooltipBase = {
2056
+ trigger: "item",
2057
+ formatter: valueFormatter ? (params) => {
2058
+ const p = params;
2059
+ return `${escapeHtml4(p.seriesName)} <br/>${escapeHtml4(p.name)}: ${escapeHtml4(formatTooltipValue4(p.value, valueFormatter))}`;
2060
+ } : "{a} <br/>{b}: {c}"
1459
2061
  };
1460
- }, [data, isEmpty, showTooltip, showLegend, sort, orientation, showLabels, title]);
2062
+ return mergeChartOption8(
2063
+ {
2064
+ tooltip: buildTooltip8(tooltipBase, {
2065
+ show: showTooltip,
2066
+ formatter: tooltipFormatter,
2067
+ valueFormatter: tooltipValueFormatter,
2068
+ getLabel
2069
+ }),
2070
+ legend: buildLegend8(
2071
+ data.map((d) => d.name),
2072
+ legendConfig,
2073
+ { show: showLegend, custom: customLegend }
2074
+ ),
2075
+ series: [
2076
+ {
2077
+ name: title,
2078
+ type: "funnel",
2079
+ left: "10%",
2080
+ top: 60,
2081
+ bottom: 60,
2082
+ width: "80%",
2083
+ min: 0,
2084
+ // reduce, not Math.max(...): spreading large arrays overflows the stack
2085
+ max: data.reduce((m, d) => d.value > m ? d.value : m, -Infinity),
2086
+ minSize: "0%",
2087
+ maxSize: "100%",
2088
+ sort,
2089
+ orient: orientation,
2090
+ gap: 2,
2091
+ label: buildDataLabel8(
2092
+ {
2093
+ show: true,
2094
+ position: "inside",
2095
+ formatter: "{b}: {c}"
2096
+ },
2097
+ { show: false },
2098
+ { show: showLabels, formatter: labelFormatter, getLabel }
2099
+ ),
2100
+ labelLine: {
2101
+ show: false
2102
+ },
2103
+ emphasis: {
2104
+ label: {
2105
+ fontSize: 16
2106
+ }
2107
+ },
2108
+ data: data.map((item) => ({
2109
+ name: item.name,
2110
+ value: item.value,
2111
+ itemStyle: item.color ? { color: item.color } : void 0,
2112
+ label: pointLabel3(item.label, labelFormatter, { position: "inside" })
2113
+ }))
2114
+ }
2115
+ ]
2116
+ },
2117
+ optionOverride
2118
+ );
2119
+ }, [
2120
+ data,
2121
+ isEmpty,
2122
+ showTooltip,
2123
+ showLegend,
2124
+ sort,
2125
+ orientation,
2126
+ showLabels,
2127
+ title,
2128
+ labelFormatter,
2129
+ tooltipFormatter,
2130
+ tooltipValueFormatter,
2131
+ legendConfig,
2132
+ customLegend,
2133
+ optionOverride
2134
+ ]);
1461
2135
  const latest = useLatestRef({ onClick });
1462
2136
  const { containerRef } = useChart({
1463
2137
  option,
1464
2138
  theme,
1465
2139
  animation,
1466
2140
  onReady: (chart) => {
2141
+ legend.attach(chart);
1467
2142
  chart.on("click", (params) => {
1468
2143
  const { onClick: onClick2 } = latest.current;
1469
2144
  if (!onClick2) return;
@@ -1478,7 +2153,7 @@ var FunnelChart = React12.forwardRef(
1478
2153
  }
1479
2154
  });
1480
2155
  const accessibilityDescription = description || (!isEmpty ? `${title}. Funnel chart with ${data.length} stages.` : void 0);
1481
- return /* @__PURE__ */ jsx12(
2156
+ return /* @__PURE__ */ jsx13(
1482
2157
  ChartContainer,
1483
2158
  {
1484
2159
  ref,
@@ -1490,8 +2165,10 @@ var FunnelChart = React12.forwardRef(
1490
2165
  empty: isEmpty,
1491
2166
  emptyMessage,
1492
2167
  className: cn(className),
2168
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
2169
+ legendPosition: legendConfig?.position,
1493
2170
  ...props,
1494
- children: /* @__PURE__ */ jsx12("div", { ref: containerRef, className: "h-full w-full" })
2171
+ children: /* @__PURE__ */ jsx13("div", { ref: containerRef, className: "h-full w-full" })
1495
2172
  }
1496
2173
  );
1497
2174
  }
@@ -1499,15 +2176,20 @@ var FunnelChart = React12.forwardRef(
1499
2176
  FunnelChart.displayName = "FunnelChart";
1500
2177
 
1501
2178
  // src/components/extended/GaugeChart.tsx
1502
- import * as React13 from "react";
2179
+ import * as React14 from "react";
1503
2180
  import * as echarts11 from "echarts/core";
1504
2181
  import { GaugeChart as EChartsGaugeChart } from "echarts/charts";
1505
- import { TitleComponent as TitleComponent9 } from "echarts/components";
2182
+ import { TitleComponent as TitleComponent9, TooltipComponent as TooltipComponent9 } from "echarts/components";
1506
2183
  import { CanvasRenderer as CanvasRenderer9 } from "echarts/renderers";
1507
- import { getChartColors as getChartColors4, THEME_DARK as THEME_DARK5 } from "@classic-homes/charts-core";
1508
- import { jsx as jsx13 } from "react/jsx-runtime";
1509
- echarts11.use([EChartsGaugeChart, TitleComponent9, CanvasRenderer9]);
1510
- var GaugeChart = React13.forwardRef(
2184
+ import {
2185
+ buildTooltip as buildTooltip9,
2186
+ getChartColors as getChartColors9,
2187
+ mergeChartOption as mergeChartOption9,
2188
+ THEME_DARK as THEME_DARK5
2189
+ } from "@classic-homes/charts-core";
2190
+ import { jsx as jsx14 } from "react/jsx-runtime";
2191
+ echarts11.use([EChartsGaugeChart, TitleComponent9, TooltipComponent9, CanvasRenderer9]);
2192
+ var GaugeChart = React14.forwardRef(
1511
2193
  ({
1512
2194
  title,
1513
2195
  description,
@@ -1523,84 +2205,100 @@ var GaugeChart = React13.forwardRef(
1523
2205
  splitNumber = 10,
1524
2206
  showProgress = true,
1525
2207
  formatValue,
2208
+ showTooltip = true,
2209
+ tooltipFormatter,
2210
+ tooltipValueFormatter,
2211
+ option: optionOverride,
1526
2212
  class: className,
1527
2213
  ...props
1528
2214
  }, ref) => {
1529
2215
  const currentTheme = useChartTheme(theme);
1530
- const chartColors = getChartColors4(currentTheme);
2216
+ const chartColors = getChartColors9(currentTheme);
1531
2217
  const reducedMotion = useReducedMotion();
1532
- const option = React13.useMemo(() => {
1533
- return {
1534
- series: [
1535
- {
1536
- type: "gauge",
1537
- center: ["50%", "60%"],
1538
- radius: "80%",
1539
- min,
1540
- max,
1541
- splitNumber,
1542
- progress: showProgress ? {
1543
- show: true,
1544
- width: 18
1545
- } : void 0,
1546
- axisLine: {
1547
- lineStyle: {
2218
+ const option = React14.useMemo(() => {
2219
+ return mergeChartOption9(
2220
+ {
2221
+ // No tooltip by default; one appears once its content is customised
2222
+ tooltip: tooltipFormatter || tooltipValueFormatter ? buildTooltip9(
2223
+ { trigger: "item" },
2224
+ {
2225
+ show: showTooltip,
2226
+ formatter: tooltipFormatter,
2227
+ valueFormatter: tooltipValueFormatter
2228
+ }
2229
+ ) : void 0,
2230
+ series: [
2231
+ {
2232
+ type: "gauge",
2233
+ center: ["50%", "60%"],
2234
+ radius: "80%",
2235
+ min,
2236
+ max,
2237
+ splitNumber,
2238
+ progress: showProgress ? {
2239
+ show: true,
1548
2240
  width: 18
1549
- }
1550
- },
1551
- axisTick: {
1552
- distance: -30,
1553
- splitNumber: 5,
1554
- lineStyle: {
1555
- width: 2,
1556
- color: chartColors.mutedForeground
1557
- }
1558
- },
1559
- splitLine: {
1560
- distance: -32,
1561
- length: 10,
1562
- lineStyle: {
1563
- width: 3,
1564
- color: chartColors.mutedForeground
1565
- }
1566
- },
1567
- axisLabel: {
1568
- distance: -20,
1569
- color: chartColors.mutedForeground,
1570
- fontSize: 12
1571
- },
1572
- anchor: {
1573
- show: true,
1574
- size: 20,
1575
- itemStyle: {
1576
- borderWidth: 2
1577
- }
1578
- },
1579
- pointer: {
1580
- width: 6,
1581
- length: "60%"
1582
- },
1583
- title: {
1584
- show: true,
1585
- offsetCenter: [0, "70%"],
1586
- fontSize: 14
1587
- },
1588
- detail: {
1589
- valueAnimation: animation && !reducedMotion,
1590
- fontSize: 28,
1591
- fontWeight: "bold",
1592
- offsetCenter: [0, "40%"],
1593
- formatter: formatValue || "{value}"
1594
- },
1595
- data: [
1596
- {
1597
- value,
1598
- name: title
1599
- }
1600
- ]
1601
- }
1602
- ]
1603
- };
2241
+ } : void 0,
2242
+ axisLine: {
2243
+ lineStyle: {
2244
+ width: 18
2245
+ }
2246
+ },
2247
+ axisTick: {
2248
+ distance: -30,
2249
+ splitNumber: 5,
2250
+ lineStyle: {
2251
+ width: 2,
2252
+ color: chartColors.mutedForeground
2253
+ }
2254
+ },
2255
+ splitLine: {
2256
+ distance: -32,
2257
+ length: 10,
2258
+ lineStyle: {
2259
+ width: 3,
2260
+ color: chartColors.mutedForeground
2261
+ }
2262
+ },
2263
+ axisLabel: {
2264
+ distance: -20,
2265
+ color: chartColors.mutedForeground,
2266
+ fontSize: 12
2267
+ },
2268
+ anchor: {
2269
+ show: true,
2270
+ size: 20,
2271
+ itemStyle: {
2272
+ borderWidth: 2
2273
+ }
2274
+ },
2275
+ pointer: {
2276
+ width: 6,
2277
+ length: "60%"
2278
+ },
2279
+ title: {
2280
+ show: true,
2281
+ offsetCenter: [0, "70%"],
2282
+ fontSize: 14
2283
+ },
2284
+ detail: {
2285
+ valueAnimation: animation && !reducedMotion,
2286
+ fontSize: 28,
2287
+ fontWeight: "bold",
2288
+ offsetCenter: [0, "40%"],
2289
+ formatter: formatValue || "{value}"
2290
+ },
2291
+ data: [
2292
+ {
2293
+ value,
2294
+ name: title
2295
+ }
2296
+ ]
2297
+ }
2298
+ ]
2299
+ },
2300
+ optionOverride
2301
+ );
1604
2302
  }, [
1605
2303
  value,
1606
2304
  min,
@@ -1611,7 +2309,11 @@ var GaugeChart = React13.forwardRef(
1611
2309
  title,
1612
2310
  animation,
1613
2311
  reducedMotion,
1614
- chartColors
2312
+ chartColors,
2313
+ showTooltip,
2314
+ tooltipFormatter,
2315
+ tooltipValueFormatter,
2316
+ optionOverride
1615
2317
  ]);
1616
2318
  const { containerRef } = useChart({
1617
2319
  option,
@@ -1619,7 +2321,7 @@ var GaugeChart = React13.forwardRef(
1619
2321
  animation
1620
2322
  });
1621
2323
  const accessibilityDescription = description || `${title}. Gauge showing value ${value} on scale from ${min} to ${max}.`;
1622
- return /* @__PURE__ */ jsx13(
2324
+ return /* @__PURE__ */ jsx14(
1623
2325
  ChartContainer,
1624
2326
  {
1625
2327
  ref,
@@ -1631,7 +2333,7 @@ var GaugeChart = React13.forwardRef(
1631
2333
  emptyMessage,
1632
2334
  className: cn(className),
1633
2335
  ...props,
1634
- children: /* @__PURE__ */ jsx13("div", { ref: containerRef, className: "h-full w-full" })
2336
+ children: /* @__PURE__ */ jsx14("div", { ref: containerRef, className: "h-full w-full" })
1635
2337
  }
1636
2338
  );
1637
2339
  }
@@ -1639,27 +2341,34 @@ var GaugeChart = React13.forwardRef(
1639
2341
  GaugeChart.displayName = "GaugeChart";
1640
2342
 
1641
2343
  // src/components/extended/HeatmapChart.tsx
1642
- import * as React14 from "react";
2344
+ import * as React15 from "react";
1643
2345
  import * as echarts12 from "echarts/core";
1644
2346
  import { HeatmapChart as EChartsHeatmapChart } from "echarts/charts";
1645
2347
  import {
1646
2348
  GridComponent as GridComponent5,
1647
- TooltipComponent as TooltipComponent9,
2349
+ TooltipComponent as TooltipComponent10,
1648
2350
  TitleComponent as TitleComponent10,
1649
2351
  VisualMapComponent
1650
2352
  } from "echarts/components";
1651
2353
  import { CanvasRenderer as CanvasRenderer10 } from "echarts/renderers";
1652
- import { escapeHtml as escapeHtml2 } from "@classic-homes/charts-core";
1653
- import { jsx as jsx14 } from "react/jsx-runtime";
2354
+ import {
2355
+ buildAxis as buildAxis5,
2356
+ buildDataLabel as buildDataLabel9,
2357
+ buildTooltip as buildTooltip10,
2358
+ escapeHtml as escapeHtml5,
2359
+ formatTooltipValue as formatTooltipValue5,
2360
+ mergeChartOption as mergeChartOption10
2361
+ } from "@classic-homes/charts-core";
2362
+ import { jsx as jsx15 } from "react/jsx-runtime";
1654
2363
  echarts12.use([
1655
2364
  EChartsHeatmapChart,
1656
2365
  GridComponent5,
1657
- TooltipComponent9,
2366
+ TooltipComponent10,
1658
2367
  TitleComponent10,
1659
2368
  VisualMapComponent,
1660
2369
  CanvasRenderer10
1661
2370
  ]);
1662
- var HeatmapChart = React14.forwardRef(
2371
+ var HeatmapChart = React15.forwardRef(
1663
2372
  ({
1664
2373
  title,
1665
2374
  description,
@@ -1674,6 +2383,12 @@ var HeatmapChart = React14.forwardRef(
1674
2383
  animation = true,
1675
2384
  showTooltip = true,
1676
2385
  showValues = false,
2386
+ labelFormatter,
2387
+ xAxisConfig,
2388
+ yAxisConfig,
2389
+ tooltipFormatter,
2390
+ tooltipValueFormatter,
2391
+ option: optionOverride,
1677
2392
  onClick,
1678
2393
  class: className,
1679
2394
  ...props
@@ -1687,64 +2402,97 @@ var HeatmapChart = React14.forwardRef(
1687
2402
  }
1688
2403
  const minValue = data?.min ?? dataMin;
1689
2404
  const maxValue = data?.max ?? dataMax;
1690
- const option = React14.useMemo(() => {
2405
+ const option = React15.useMemo(() => {
1691
2406
  if (isEmpty) return {};
1692
- return {
1693
- tooltip: showTooltip ? {
1694
- position: "top",
1695
- formatter: (params) => {
1696
- const p = params;
1697
- if (!p.value) return "";
1698
- const [xIdx, yIdx, value] = p.value;
1699
- return `${escapeHtml2(xAxis[xIdx])} / ${escapeHtml2(yAxis[yIdx])}: ${escapeHtml2(value)}`;
1700
- }
1701
- } : void 0,
1702
- grid: {
1703
- left: "3%",
1704
- right: "10%",
1705
- bottom: "3%",
1706
- containLabel: true
1707
- },
1708
- xAxis: {
1709
- type: "category",
1710
- data: xAxis,
1711
- splitArea: {
1712
- show: true
1713
- }
1714
- },
1715
- yAxis: {
1716
- type: "category",
1717
- data: yAxis,
1718
- splitArea: {
1719
- show: true
1720
- }
1721
- },
1722
- visualMap: {
1723
- min: minValue,
1724
- max: maxValue,
1725
- calculable: true,
1726
- orient: "vertical",
1727
- right: "0%",
1728
- top: "center"
1729
- },
1730
- series: [
1731
- {
1732
- name: title,
1733
- type: "heatmap",
1734
- data: data.data,
1735
- label: showValues ? {
1736
- show: true
1737
- } : void 0,
1738
- emphasis: {
1739
- itemStyle: {
1740
- shadowBlur: 10,
1741
- shadowColor: "rgba(0, 0, 0, 0.5)"
2407
+ return mergeChartOption10(
2408
+ {
2409
+ tooltip: buildTooltip10(
2410
+ {
2411
+ position: "top",
2412
+ formatter: (params) => {
2413
+ const p = params;
2414
+ if (!p.value) return "";
2415
+ const [xIdx, yIdx, value] = p.value;
2416
+ return `${escapeHtml5(xAxis[xIdx])} / ${escapeHtml5(yAxis[yIdx])}: ${escapeHtml5(formatTooltipValue5(value, tooltipValueFormatter))}`;
1742
2417
  }
2418
+ },
2419
+ {
2420
+ show: showTooltip,
2421
+ formatter: tooltipFormatter,
2422
+ valueFormatter: tooltipValueFormatter
1743
2423
  }
1744
- }
1745
- ]
1746
- };
1747
- }, [data, xAxis, yAxis, isEmpty, showTooltip, showValues, minValue, maxValue, title]);
2424
+ ),
2425
+ grid: {
2426
+ left: "3%",
2427
+ right: "10%",
2428
+ bottom: "3%",
2429
+ containLabel: true
2430
+ },
2431
+ xAxis: buildAxis5(
2432
+ {
2433
+ type: "category",
2434
+ data: xAxis,
2435
+ splitArea: {
2436
+ show: true
2437
+ }
2438
+ },
2439
+ xAxisConfig
2440
+ ),
2441
+ yAxis: buildAxis5(
2442
+ {
2443
+ type: "category",
2444
+ data: yAxis,
2445
+ splitArea: {
2446
+ show: true
2447
+ }
2448
+ },
2449
+ yAxisConfig
2450
+ ),
2451
+ visualMap: {
2452
+ min: minValue,
2453
+ max: maxValue,
2454
+ calculable: true,
2455
+ orient: "vertical",
2456
+ right: "0%",
2457
+ top: "center"
2458
+ },
2459
+ series: [
2460
+ {
2461
+ name: title,
2462
+ type: "heatmap",
2463
+ data: data.data,
2464
+ label: buildDataLabel9({ show: true }, void 0, {
2465
+ show: showValues,
2466
+ formatter: labelFormatter
2467
+ }),
2468
+ emphasis: {
2469
+ itemStyle: {
2470
+ shadowBlur: 10,
2471
+ shadowColor: "rgba(0, 0, 0, 0.5)"
2472
+ }
2473
+ }
2474
+ }
2475
+ ]
2476
+ },
2477
+ optionOverride
2478
+ );
2479
+ }, [
2480
+ data,
2481
+ xAxis,
2482
+ yAxis,
2483
+ isEmpty,
2484
+ showTooltip,
2485
+ showValues,
2486
+ minValue,
2487
+ maxValue,
2488
+ title,
2489
+ labelFormatter,
2490
+ xAxisConfig,
2491
+ yAxisConfig,
2492
+ tooltipFormatter,
2493
+ tooltipValueFormatter,
2494
+ optionOverride
2495
+ ]);
1748
2496
  const latest = useLatestRef({ onClick, xAxis, yAxis });
1749
2497
  const { containerRef } = useChart({
1750
2498
  option,
@@ -1768,7 +2516,7 @@ var HeatmapChart = React14.forwardRef(
1768
2516
  }
1769
2517
  });
1770
2518
  const accessibilityDescription = description || (!isEmpty ? `${title}. Heatmap with ${xAxis.length} columns and ${yAxis.length} rows.` : void 0);
1771
- return /* @__PURE__ */ jsx14(
2519
+ return /* @__PURE__ */ jsx15(
1772
2520
  ChartContainer,
1773
2521
  {
1774
2522
  ref,
@@ -1781,7 +2529,7 @@ var HeatmapChart = React14.forwardRef(
1781
2529
  emptyMessage,
1782
2530
  className: cn(className),
1783
2531
  ...props,
1784
- children: /* @__PURE__ */ jsx14("div", { ref: containerRef, className: "h-full w-full" })
2532
+ children: /* @__PURE__ */ jsx15("div", { ref: containerRef, className: "h-full w-full" })
1785
2533
  }
1786
2534
  );
1787
2535
  }
@@ -1789,15 +2537,25 @@ var HeatmapChart = React14.forwardRef(
1789
2537
  HeatmapChart.displayName = "HeatmapChart";
1790
2538
 
1791
2539
  // src/components/extended/TreemapChart.tsx
1792
- import * as React15 from "react";
2540
+ import * as React16 from "react";
1793
2541
  import * as echarts13 from "echarts/core";
1794
2542
  import { TreemapChart as EChartsTreemapChart } from "echarts/charts";
1795
- import { TooltipComponent as TooltipComponent10, TitleComponent as TitleComponent11 } from "echarts/components";
2543
+ import { TooltipComponent as TooltipComponent11, TitleComponent as TitleComponent11 } from "echarts/components";
1796
2544
  import { CanvasRenderer as CanvasRenderer11 } from "echarts/renderers";
1797
- import { escapeHtml as escapeHtml3, getChartColors as getChartColors5, THEME_DARK as THEME_DARK6 } from "@classic-homes/charts-core";
1798
- import { jsx as jsx15 } from "react/jsx-runtime";
1799
- echarts13.use([EChartsTreemapChart, TooltipComponent10, TitleComponent11, CanvasRenderer11]);
1800
- var TreemapChart = React15.forwardRef(
2545
+ import {
2546
+ buildDataLabel as buildDataLabel10,
2547
+ buildTooltip as buildTooltip11,
2548
+ escapeHtml as escapeHtml6,
2549
+ formatTooltipValue as formatTooltipValue6,
2550
+ getChartColors as getChartColors10,
2551
+ mergeChartOption as mergeChartOption11,
2552
+ nameLabelLookup,
2553
+ pointLabel as pointLabel4,
2554
+ THEME_DARK as THEME_DARK6
2555
+ } from "@classic-homes/charts-core";
2556
+ import { jsx as jsx16 } from "react/jsx-runtime";
2557
+ echarts13.use([EChartsTreemapChart, TooltipComponent11, TitleComponent11, CanvasRenderer11]);
2558
+ var TreemapChart = React16.forwardRef(
1801
2559
  ({
1802
2560
  title,
1803
2561
  description,
@@ -1810,69 +2568,99 @@ var TreemapChart = React15.forwardRef(
1810
2568
  theme = "auto",
1811
2569
  animation = true,
1812
2570
  showTooltip = true,
2571
+ labelFormatter,
2572
+ tooltipFormatter,
2573
+ tooltipValueFormatter,
2574
+ option: optionOverride,
1813
2575
  onClick,
1814
2576
  class: className,
1815
2577
  ...props
1816
2578
  }, ref) => {
1817
2579
  const isEmpty = !data?.length;
1818
2580
  const currentTheme = useChartTheme(theme);
1819
- const chartColors = getChartColors5(currentTheme);
1820
- const option = React15.useMemo(() => {
2581
+ const chartColors = getChartColors10(currentTheme);
2582
+ const option = React16.useMemo(() => {
1821
2583
  if (isEmpty) return {};
1822
- return {
1823
- tooltip: showTooltip ? {
1824
- formatter: (params) => {
1825
- const p = params;
1826
- return `${escapeHtml3(p.name)}: ${escapeHtml3(p.value)}`;
1827
- }
1828
- } : void 0,
1829
- series: [
1830
- {
1831
- type: "treemap",
1832
- data,
1833
- roam: false,
1834
- levels: levels || [
1835
- {
1836
- itemStyle: {
1837
- borderColor: chartColors.divider,
1838
- borderWidth: 4,
1839
- gapWidth: 4
1840
- }
1841
- },
1842
- {
1843
- colorSaturation: [0.3, 0.6],
1844
- itemStyle: {
1845
- borderColorSaturation: 0.7,
1846
- gapWidth: 2,
1847
- borderWidth: 2
1848
- }
1849
- },
1850
- {
1851
- colorSaturation: [0.3, 0.5],
1852
- itemStyle: {
1853
- borderColorSaturation: 0.6,
1854
- gapWidth: 1
2584
+ const toNode = (node) => ({
2585
+ ...node,
2586
+ children: node.children?.map(toNode),
2587
+ label: pointLabel4(node.label, labelFormatter)
2588
+ });
2589
+ return mergeChartOption11(
2590
+ {
2591
+ tooltip: buildTooltip11(
2592
+ {
2593
+ formatter: (params) => {
2594
+ const p = params;
2595
+ return `${escapeHtml6(p.name)}: ${escapeHtml6(formatTooltipValue6(p.value, tooltipValueFormatter))}`;
2596
+ }
2597
+ },
2598
+ {
2599
+ show: showTooltip,
2600
+ formatter: tooltipFormatter,
2601
+ valueFormatter: tooltipValueFormatter
2602
+ }
2603
+ ),
2604
+ series: [
2605
+ {
2606
+ type: "treemap",
2607
+ data: data.map(toNode),
2608
+ roam: false,
2609
+ levels: levels || [
2610
+ {
2611
+ itemStyle: {
2612
+ borderColor: chartColors.divider,
2613
+ borderWidth: 4,
2614
+ gapWidth: 4
2615
+ }
2616
+ },
2617
+ {
2618
+ colorSaturation: [0.3, 0.6],
2619
+ itemStyle: {
2620
+ borderColorSaturation: 0.7,
2621
+ gapWidth: 2,
2622
+ borderWidth: 2
2623
+ }
2624
+ },
2625
+ {
2626
+ colorSaturation: [0.3, 0.5],
2627
+ itemStyle: {
2628
+ borderColorSaturation: 0.6,
2629
+ gapWidth: 1
2630
+ }
2631
+ },
2632
+ {
2633
+ colorSaturation: [0.3, 0.5]
1855
2634
  }
2635
+ ],
2636
+ label: buildDataLabel10({ show: true, formatter: "{b}" }, void 0, {
2637
+ show: true,
2638
+ formatter: labelFormatter,
2639
+ getLabel: nameLabelLookup(data)
2640
+ }),
2641
+ upperLabel: {
2642
+ show: true,
2643
+ height: 30
1856
2644
  },
1857
- {
1858
- colorSaturation: [0.3, 0.5]
2645
+ breadcrumb: {
2646
+ show: true
1859
2647
  }
1860
- ],
1861
- label: {
1862
- show: true,
1863
- formatter: "{b}"
1864
- },
1865
- upperLabel: {
1866
- show: true,
1867
- height: 30
1868
- },
1869
- breadcrumb: {
1870
- show: true
1871
2648
  }
1872
- }
1873
- ]
1874
- };
1875
- }, [data, levels, isEmpty, showTooltip, chartColors]);
2649
+ ]
2650
+ },
2651
+ optionOverride
2652
+ );
2653
+ }, [
2654
+ data,
2655
+ levels,
2656
+ isEmpty,
2657
+ showTooltip,
2658
+ chartColors,
2659
+ labelFormatter,
2660
+ tooltipFormatter,
2661
+ tooltipValueFormatter,
2662
+ optionOverride
2663
+ ]);
1876
2664
  const latest = useLatestRef({ onClick });
1877
2665
  const { containerRef } = useChart({
1878
2666
  option,
@@ -1895,7 +2683,7 @@ var TreemapChart = React15.forwardRef(
1895
2683
  }
1896
2684
  });
1897
2685
  const accessibilityDescription = description || (!isEmpty ? `${title}. Treemap chart showing hierarchical data.` : void 0);
1898
- return /* @__PURE__ */ jsx15(
2686
+ return /* @__PURE__ */ jsx16(
1899
2687
  ChartContainer,
1900
2688
  {
1901
2689
  ref,
@@ -1908,7 +2696,7 @@ var TreemapChart = React15.forwardRef(
1908
2696
  emptyMessage,
1909
2697
  className: cn(className),
1910
2698
  ...props,
1911
- children: /* @__PURE__ */ jsx15("div", { ref: containerRef, className: "h-full w-full" })
2699
+ children: /* @__PURE__ */ jsx16("div", { ref: containerRef, className: "h-full w-full" })
1912
2700
  }
1913
2701
  );
1914
2702
  }
@@ -1916,7 +2704,7 @@ var TreemapChart = React15.forwardRef(
1916
2704
  TreemapChart.displayName = "TreemapChart";
1917
2705
 
1918
2706
  // src/components/extended/CandlestickChart.tsx
1919
- import * as React16 from "react";
2707
+ import * as React17 from "react";
1920
2708
  import * as echarts14 from "echarts/core";
1921
2709
  import {
1922
2710
  CandlestickChart as EChartsCandlestickChart,
@@ -1924,23 +2712,29 @@ import {
1924
2712
  } from "echarts/charts";
1925
2713
  import {
1926
2714
  GridComponent as GridComponent6,
1927
- TooltipComponent as TooltipComponent11,
2715
+ TooltipComponent as TooltipComponent12,
1928
2716
  TitleComponent as TitleComponent12,
1929
2717
  DataZoomComponent
1930
2718
  } from "echarts/components";
1931
2719
  import { CanvasRenderer as CanvasRenderer12 } from "echarts/renderers";
1932
- import { getChartColors as getChartColors6, THEME_DARK as THEME_DARK7 } from "@classic-homes/charts-core";
1933
- import { jsx as jsx16 } from "react/jsx-runtime";
2720
+ import {
2721
+ buildAxis as buildAxis6,
2722
+ buildTooltip as buildTooltip12,
2723
+ getChartColors as getChartColors11,
2724
+ mergeChartOption as mergeChartOption12,
2725
+ THEME_DARK as THEME_DARK7
2726
+ } from "@classic-homes/charts-core";
2727
+ import { jsx as jsx17 } from "react/jsx-runtime";
1934
2728
  echarts14.use([
1935
2729
  EChartsCandlestickChart,
1936
2730
  EChartsBarChart2,
1937
2731
  GridComponent6,
1938
- TooltipComponent11,
2732
+ TooltipComponent12,
1939
2733
  TitleComponent12,
1940
2734
  DataZoomComponent,
1941
2735
  CanvasRenderer12
1942
2736
  ]);
1943
- var CandlestickChart = React16.forwardRef(
2737
+ var CandlestickChart = React17.forwardRef(
1944
2738
  ({
1945
2739
  title,
1946
2740
  description,
@@ -1953,14 +2747,19 @@ var CandlestickChart = React16.forwardRef(
1953
2747
  animation = true,
1954
2748
  showTooltip = true,
1955
2749
  showVolume = false,
2750
+ xAxisConfig,
2751
+ yAxisConfig,
2752
+ tooltipFormatter,
2753
+ tooltipValueFormatter,
2754
+ option: optionOverride,
1956
2755
  onClick,
1957
2756
  class: className,
1958
2757
  ...props
1959
2758
  }, ref) => {
1960
2759
  const isEmpty = !data?.dates?.length || !data?.data?.length;
1961
2760
  const currentTheme = useChartTheme(theme);
1962
- const chartColors = getChartColors6(currentTheme);
1963
- const option = React16.useMemo(() => {
2761
+ const chartColors = getChartColors11(currentTheme);
2762
+ const option = React17.useMemo(() => {
1964
2763
  if (isEmpty) return {};
1965
2764
  const candlestickData = data.data.map((item) => [item.open, item.close, item.low, item.high]);
1966
2765
  const seriesArr = [
@@ -1987,23 +2786,29 @@ var CandlestickChart = React16.forwardRef(
1987
2786
  }
1988
2787
  ];
1989
2788
  const xAxisArr = [
1990
- {
1991
- type: "category",
1992
- data: data.dates,
1993
- boundaryGap: false,
1994
- axisLine: { onZero: false },
1995
- splitLine: { show: false },
1996
- min: "dataMin",
1997
- max: "dataMax"
1998
- }
2789
+ buildAxis6(
2790
+ {
2791
+ type: "category",
2792
+ data: data.dates,
2793
+ boundaryGap: false,
2794
+ axisLine: { onZero: false },
2795
+ splitLine: { show: false },
2796
+ min: "dataMin",
2797
+ max: "dataMax"
2798
+ },
2799
+ xAxisConfig
2800
+ )
1999
2801
  ];
2000
2802
  const yAxisArr = [
2001
- {
2002
- scale: true,
2003
- splitArea: {
2004
- show: true
2005
- }
2006
- }
2803
+ buildAxis6(
2804
+ {
2805
+ scale: true,
2806
+ splitArea: {
2807
+ show: true
2808
+ }
2809
+ },
2810
+ yAxisConfig
2811
+ )
2007
2812
  ];
2008
2813
  if (showVolume && data.volume) {
2009
2814
  gridArr.push({
@@ -2044,33 +2849,55 @@ var CandlestickChart = React16.forwardRef(
2044
2849
  }
2045
2850
  });
2046
2851
  }
2047
- return {
2048
- tooltip: showTooltip ? {
2049
- trigger: "axis",
2050
- axisPointer: {
2051
- type: "cross"
2052
- }
2053
- } : void 0,
2054
- grid: gridArr,
2055
- xAxis: xAxisArr,
2056
- yAxis: yAxisArr,
2057
- dataZoom: [
2058
- {
2059
- type: "inside",
2060
- start: 80,
2061
- end: 100
2062
- },
2063
- {
2064
- show: true,
2065
- type: "slider",
2066
- top: "90%",
2067
- start: 80,
2068
- end: 100
2069
- }
2070
- ],
2071
- series: seriesArr
2072
- };
2073
- }, [data, isEmpty, showTooltip, showVolume, title, chartColors]);
2852
+ return mergeChartOption12(
2853
+ {
2854
+ tooltip: buildTooltip12(
2855
+ {
2856
+ trigger: "axis",
2857
+ axisPointer: {
2858
+ type: "cross"
2859
+ }
2860
+ },
2861
+ {
2862
+ show: showTooltip,
2863
+ formatter: tooltipFormatter,
2864
+ valueFormatter: tooltipValueFormatter
2865
+ }
2866
+ ),
2867
+ grid: gridArr,
2868
+ xAxis: xAxisArr,
2869
+ yAxis: yAxisArr,
2870
+ dataZoom: [
2871
+ {
2872
+ type: "inside",
2873
+ start: 80,
2874
+ end: 100
2875
+ },
2876
+ {
2877
+ show: true,
2878
+ type: "slider",
2879
+ top: "90%",
2880
+ start: 80,
2881
+ end: 100
2882
+ }
2883
+ ],
2884
+ series: seriesArr
2885
+ },
2886
+ optionOverride
2887
+ );
2888
+ }, [
2889
+ data,
2890
+ isEmpty,
2891
+ showTooltip,
2892
+ showVolume,
2893
+ title,
2894
+ chartColors,
2895
+ xAxisConfig,
2896
+ yAxisConfig,
2897
+ tooltipFormatter,
2898
+ tooltipValueFormatter,
2899
+ optionOverride
2900
+ ]);
2074
2901
  const latest = useLatestRef({ onClick, data });
2075
2902
  const { containerRef } = useChart({
2076
2903
  option,
@@ -2097,7 +2924,7 @@ var CandlestickChart = React16.forwardRef(
2097
2924
  }
2098
2925
  });
2099
2926
  const accessibilityDescription = description || (!isEmpty ? `${title}. Candlestick chart with ${data.dates.length} data points.` : void 0);
2100
- return /* @__PURE__ */ jsx16(
2927
+ return /* @__PURE__ */ jsx17(
2101
2928
  ChartContainer,
2102
2929
  {
2103
2930
  ref,
@@ -2110,7 +2937,7 @@ var CandlestickChart = React16.forwardRef(
2110
2937
  emptyMessage,
2111
2938
  className: cn(className),
2112
2939
  ...props,
2113
- children: /* @__PURE__ */ jsx16("div", { ref: containerRef, className: "h-full w-full" })
2940
+ children: /* @__PURE__ */ jsx17("div", { ref: containerRef, className: "h-full w-full" })
2114
2941
  }
2115
2942
  );
2116
2943
  }
@@ -2118,14 +2945,21 @@ var CandlestickChart = React16.forwardRef(
2118
2945
  CandlestickChart.displayName = "CandlestickChart";
2119
2946
 
2120
2947
  // src/components/extended/SankeyChart.tsx
2121
- import * as React17 from "react";
2948
+ import * as React18 from "react";
2122
2949
  import * as echarts15 from "echarts/core";
2123
2950
  import { SankeyChart as EChartsSankeyChart } from "echarts/charts";
2124
- import { TooltipComponent as TooltipComponent12, TitleComponent as TitleComponent13 } from "echarts/components";
2951
+ import { TooltipComponent as TooltipComponent13, TitleComponent as TitleComponent13 } from "echarts/components";
2125
2952
  import { CanvasRenderer as CanvasRenderer13 } from "echarts/renderers";
2126
- import { jsx as jsx17 } from "react/jsx-runtime";
2127
- echarts15.use([EChartsSankeyChart, TooltipComponent12, TitleComponent13, CanvasRenderer13]);
2128
- var SankeyChart = React17.forwardRef(
2953
+ import {
2954
+ buildDataLabel as buildDataLabel11,
2955
+ buildTooltip as buildTooltip13,
2956
+ mergeChartOption as mergeChartOption13,
2957
+ nameLabelLookup as nameLabelLookup2,
2958
+ pointLabel as pointLabel5
2959
+ } from "@classic-homes/charts-core";
2960
+ import { jsx as jsx18 } from "react/jsx-runtime";
2961
+ echarts15.use([EChartsSankeyChart, TooltipComponent13, TitleComponent13, CanvasRenderer13]);
2962
+ var SankeyChart = React18.forwardRef(
2129
2963
  ({
2130
2964
  title,
2131
2965
  description,
@@ -2138,45 +2972,73 @@ var SankeyChart = React17.forwardRef(
2138
2972
  animation = true,
2139
2973
  showTooltip = true,
2140
2974
  orient = "horizontal",
2975
+ labelFormatter,
2976
+ tooltipFormatter,
2977
+ tooltipValueFormatter,
2978
+ option: optionOverride,
2141
2979
  onClick,
2142
2980
  class: className,
2143
2981
  ...props
2144
2982
  }, ref) => {
2145
2983
  const isEmpty = !data?.nodes?.length || !data?.links?.length;
2146
- const option = React17.useMemo(() => {
2984
+ const option = React18.useMemo(() => {
2147
2985
  if (isEmpty) return {};
2148
- return {
2149
- tooltip: showTooltip ? {
2150
- trigger: "item",
2151
- triggerOn: "mousemove"
2152
- } : void 0,
2153
- series: [
2154
- {
2155
- type: "sankey",
2156
- orient,
2157
- emphasis: {
2158
- focus: "adjacency"
2159
- },
2160
- data: data.nodes.map((node) => ({
2161
- name: node.name,
2162
- itemStyle: node.color ? { color: node.color } : void 0
2163
- })),
2164
- links: data.links.map((link) => ({
2165
- source: link.source,
2166
- target: link.target,
2167
- value: link.value
2168
- })),
2169
- lineStyle: {
2170
- color: "gradient",
2171
- curveness: 0.5
2986
+ return mergeChartOption13(
2987
+ {
2988
+ tooltip: buildTooltip13(
2989
+ {
2990
+ trigger: "item",
2991
+ triggerOn: "mousemove"
2172
2992
  },
2173
- label: {
2174
- position: orient === "horizontal" ? "right" : "bottom"
2993
+ {
2994
+ show: showTooltip,
2995
+ formatter: tooltipFormatter,
2996
+ valueFormatter: tooltipValueFormatter
2175
2997
  }
2176
- }
2177
- ]
2178
- };
2179
- }, [data, isEmpty, showTooltip, orient]);
2998
+ ),
2999
+ series: [
3000
+ {
3001
+ type: "sankey",
3002
+ orient,
3003
+ emphasis: {
3004
+ focus: "adjacency"
3005
+ },
3006
+ data: data.nodes.map((node) => ({
3007
+ name: node.name,
3008
+ itemStyle: node.color ? { color: node.color } : void 0,
3009
+ label: pointLabel5(node.label, labelFormatter)
3010
+ })),
3011
+ links: data.links.map((link) => ({
3012
+ source: link.source,
3013
+ target: link.target,
3014
+ value: link.value
3015
+ })),
3016
+ lineStyle: {
3017
+ color: "gradient",
3018
+ curveness: 0.5
3019
+ },
3020
+ label: buildDataLabel11(
3021
+ {
3022
+ position: orient === "horizontal" ? "right" : "bottom"
3023
+ },
3024
+ void 0,
3025
+ { show: true, formatter: labelFormatter, getLabel: nameLabelLookup2(data.nodes) }
3026
+ )
3027
+ }
3028
+ ]
3029
+ },
3030
+ optionOverride
3031
+ );
3032
+ }, [
3033
+ data,
3034
+ isEmpty,
3035
+ showTooltip,
3036
+ orient,
3037
+ labelFormatter,
3038
+ tooltipFormatter,
3039
+ tooltipValueFormatter,
3040
+ optionOverride
3041
+ ]);
2180
3042
  const latest = useLatestRef({ onClick });
2181
3043
  const { containerRef } = useChart({
2182
3044
  option,
@@ -2200,7 +3062,7 @@ var SankeyChart = React17.forwardRef(
2200
3062
  }
2201
3063
  });
2202
3064
  const accessibilityDescription = description || (!isEmpty ? `${title}. Sankey diagram with ${data.nodes.length} nodes and ${data.links.length} links.` : void 0);
2203
- return /* @__PURE__ */ jsx17(
3065
+ return /* @__PURE__ */ jsx18(
2204
3066
  ChartContainer,
2205
3067
  {
2206
3068
  ref,
@@ -2213,7 +3075,7 @@ var SankeyChart = React17.forwardRef(
2213
3075
  emptyMessage,
2214
3076
  className: cn(className),
2215
3077
  ...props,
2216
- children: /* @__PURE__ */ jsx17("div", { ref: containerRef, className: "h-full w-full" })
3078
+ children: /* @__PURE__ */ jsx18("div", { ref: containerRef, className: "h-full w-full" })
2217
3079
  }
2218
3080
  );
2219
3081
  }
@@ -2226,6 +3088,7 @@ export {
2226
3088
  ChartContainer,
2227
3089
  ChartEmpty,
2228
3090
  ChartError,
3091
+ ChartLegend,
2229
3092
  ChartSkeleton,
2230
3093
  DonutChart,
2231
3094
  FunnelChart,
@@ -2240,6 +3103,7 @@ export {
2240
3103
  cn,
2241
3104
  registerClassicThemes,
2242
3105
  useChart,
3106
+ useChartLegend,
2243
3107
  useChartResize,
2244
3108
  useChartTheme,
2245
3109
  useReducedMotion