@classic-homes/charts-react 0.1.37 → 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
@@ -77,10 +77,11 @@ var ChartError = React2.forwardRef(
77
77
  )
78
78
  }
79
79
  ),
80
- /* @__PURE__ */ jsx2("p", { className: "mb-2 text-sm font-medium text-destructive", children: message }),
80
+ /* @__PURE__ */ jsx2("p", { className: "mb-2 text-sm font-medium text-foreground", children: message }),
81
81
  onRetry && /* @__PURE__ */ jsx2(
82
82
  "button",
83
83
  {
84
+ type: "button",
84
85
  onClick: onRetry,
85
86
  className: "rounded-md bg-destructive px-3 py-1.5 text-xs font-medium text-destructive-foreground transition-colors hover:bg-destructive/90",
86
87
  children: "Retry"
@@ -152,10 +153,13 @@ var ChartContainer = React4.forwardRef(
152
153
  onRetry,
153
154
  className,
154
155
  wrapperClassName,
156
+ legend,
157
+ legendPosition = "bottom",
155
158
  children,
156
159
  ...props
157
160
  }, ref) => {
158
161
  const heightStyle = typeof height === "number" ? `${height}px` : height;
162
+ const descriptionId = `${React4.useId()}-desc`;
159
163
  if (loading) {
160
164
  return /* @__PURE__ */ jsx4(ChartSkeleton, { height, className: wrapperClassName });
161
165
  }
@@ -173,28 +177,99 @@ var ChartContainer = React4.forwardRef(
173
177
  if (empty) {
174
178
  return /* @__PURE__ */ jsx4(ChartEmpty, { message: emptyMessage, height, className: wrapperClassName });
175
179
  }
176
- return /* @__PURE__ */ jsxs4(
180
+ const chart = /* @__PURE__ */ jsxs4(
177
181
  "div",
178
182
  {
179
183
  ref,
180
184
  role: "img",
181
185
  "aria-label": title,
182
- "aria-describedby": description ? `${title}-desc` : void 0,
186
+ "aria-describedby": description ? descriptionId : void 0,
183
187
  "data-testid": "chart-container",
184
- 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),
185
189
  ...props,
186
190
  children: [
187
- description && /* @__PURE__ */ jsx4("span", { id: `${title}-desc`, className: "sr-only", children: description }),
191
+ description && /* @__PURE__ */ jsx4("span", { id: descriptionId, className: "sr-only", children: description }),
188
192
  /* @__PURE__ */ jsx4("div", { className: cn("h-full w-full", className), style: { height: heightStyle }, children })
189
193
  ]
190
194
  }
191
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
+ );
192
222
  }
193
223
  );
194
224
  ChartContainer.displayName = "ChartContainer";
195
225
 
196
- // src/components/core/LineChart.tsx
226
+ // src/components/base/ChartLegend.tsx
197
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";
198
273
  import * as echarts3 from "echarts/core";
199
274
  import { LineChart as EChartsLineChart } from "echarts/charts";
200
275
  import {
@@ -204,14 +279,28 @@ import {
204
279
  TitleComponent
205
280
  } from "echarts/components";
206
281
  import { CanvasRenderer } from "echarts/renderers";
207
- import { generateLineChartDescription } from "@classic-homes/charts-core";
282
+ import {
283
+ buildAxis,
284
+ buildDataLabel,
285
+ buildLegend,
286
+ buildTooltip,
287
+ generateLineChartDescription,
288
+ getChartColors,
289
+ hasPointLabels,
290
+ gridInset,
291
+ mergeChartOption,
292
+ seriesLabelLookup,
293
+ THEME_DARK as THEME_DARK2,
294
+ withPointLabels
295
+ } from "@classic-homes/charts-core";
208
296
 
209
297
  // src/hooks/useChart.ts
210
- import { useEffect as useEffect4, useRef as useRef2 } from "react";
298
+ import { useEffect as useEffect4, useRef } from "react";
211
299
  import * as echarts2 from "echarts/core";
300
+ import { getReplaceMerge } from "@classic-homes/charts-core";
212
301
 
213
302
  // src/hooks/useChartTheme.ts
214
- import { useEffect, useState, useCallback } from "react";
303
+ import { useEffect, useState } from "react";
215
304
  import * as echarts from "echarts/core";
216
305
  import { lightTheme, darkTheme, THEME_LIGHT, THEME_DARK } from "@classic-homes/charts-core";
217
306
  var themesRegistered = false;
@@ -221,18 +310,18 @@ function registerClassicThemes() {
221
310
  echarts.registerTheme(THEME_DARK, darkTheme);
222
311
  themesRegistered = true;
223
312
  }
313
+ function resolveTheme(preference) {
314
+ if (preference !== "auto") {
315
+ return preference === "dark" ? THEME_DARK : THEME_LIGHT;
316
+ }
317
+ if (typeof document === "undefined") return THEME_LIGHT;
318
+ return document.documentElement.classList.contains("dark") ? THEME_DARK : THEME_LIGHT;
319
+ }
224
320
  function useChartTheme(preference = "auto") {
225
- const [currentTheme, setCurrentTheme] = useState(THEME_LIGHT);
226
- const updateTheme = useCallback(() => {
227
- if (preference !== "auto") {
228
- setCurrentTheme(preference === "dark" ? THEME_DARK : THEME_LIGHT);
229
- return;
230
- }
231
- const isDark = document.documentElement.classList.contains("dark");
232
- setCurrentTheme(isDark ? THEME_DARK : THEME_LIGHT);
233
- }, [preference]);
321
+ const [currentTheme, setCurrentTheme] = useState(() => resolveTheme(preference));
234
322
  useEffect(() => {
235
323
  registerClassicThemes();
324
+ const updateTheme = () => setCurrentTheme(resolveTheme(preference));
236
325
  updateTheme();
237
326
  if (preference !== "auto") return;
238
327
  const observer = new MutationObserver((mutations) => {
@@ -246,26 +335,29 @@ function useChartTheme(preference = "auto") {
246
335
  attributes: true,
247
336
  attributeFilter: ["class"]
248
337
  });
249
- const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
250
- const handleMediaChange = () => updateTheme();
251
- mediaQuery.addEventListener("change", handleMediaChange);
252
338
  return () => {
253
339
  observer.disconnect();
254
- mediaQuery.removeEventListener("change", handleMediaChange);
255
340
  };
256
- }, [preference, updateTheme]);
341
+ }, [preference]);
257
342
  return currentTheme;
258
343
  }
259
344
 
260
345
  // src/hooks/useChartResize.ts
261
- import { useEffect as useEffect2, useRef } from "react";
346
+ import { useEffect as useEffect2, useLayoutEffect, useState as useState2 } from "react";
347
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? useEffect2 : useLayoutEffect;
348
+ function useRefElement(ref) {
349
+ const [element, setElement] = useState2(null);
350
+ useIsomorphicLayoutEffect(() => {
351
+ setElement(ref.current);
352
+ });
353
+ return element;
354
+ }
262
355
  function useChartResize(chartRef, containerRef) {
263
- const resizeObserverRef = useRef(null);
356
+ const container = useRefElement(containerRef);
264
357
  useEffect2(() => {
265
- const container = containerRef.current;
266
358
  if (!container) return;
267
359
  let resizeTimeout;
268
- resizeObserverRef.current = new ResizeObserver(() => {
360
+ const observer = new ResizeObserver(() => {
269
361
  clearTimeout(resizeTimeout);
270
362
  resizeTimeout = setTimeout(() => {
271
363
  const chart = chartRef.current;
@@ -274,18 +366,18 @@ function useChartResize(chartRef, containerRef) {
274
366
  }
275
367
  }, 100);
276
368
  });
277
- resizeObserverRef.current.observe(container);
369
+ observer.observe(container);
278
370
  return () => {
279
371
  clearTimeout(resizeTimeout);
280
- resizeObserverRef.current?.disconnect();
372
+ observer.disconnect();
281
373
  };
282
- }, [chartRef, containerRef]);
374
+ }, [chartRef, container]);
283
375
  }
284
376
 
285
377
  // src/hooks/useReducedMotion.ts
286
- import { useState as useState2, useEffect as useEffect3 } from "react";
378
+ import { useState as useState3, useEffect as useEffect3 } from "react";
287
379
  function useReducedMotion() {
288
- const [reducedMotion, setReducedMotion] = useState2(false);
380
+ const [reducedMotion, setReducedMotion] = useState3(false);
289
381
  useEffect3(() => {
290
382
  const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
291
383
  setReducedMotion(mediaQuery.matches);
@@ -301,29 +393,39 @@ function useReducedMotion() {
301
393
  }
302
394
 
303
395
  // src/hooks/useChart.ts
304
- var SET_OPTION_OPTS = { notMerge: false, lazyUpdate: true };
396
+ var isComponentType = (mainType) => echarts2.ComponentModel.hasClass(mainType);
397
+ function applyOption(chart, prev, next) {
398
+ chart.setOption(next, {
399
+ replaceMerge: getReplaceMerge(prev, next, isComponentType),
400
+ lazyUpdate: true
401
+ });
402
+ }
305
403
  function useChart({
306
404
  option,
307
405
  theme = "auto",
308
406
  animation,
309
407
  onReady
310
408
  }) {
311
- const containerRef = useRef2(null);
312
- const chartRef = useRef2(null);
409
+ const containerRef = useRef(null);
410
+ const container = useRefElement(containerRef);
411
+ const chartRef = useRef(null);
313
412
  const currentTheme = useChartTheme(theme);
314
413
  const reducedMotion = useReducedMotion();
315
- const onReadyRef = useRef2(onReady);
414
+ const onReadyRef = useRef(onReady);
316
415
  onReadyRef.current = onReady;
317
- const optionRef = useRef2(option);
416
+ const optionRef = useRef(option);
318
417
  optionRef.current = {
319
418
  ...option,
320
- animation: animation ?? !reducedMotion
419
+ // Reduced motion always wins over the animation prop
420
+ animation: (animation ?? true) && !reducedMotion
321
421
  };
422
+ const appliedRef = useRef(null);
322
423
  useEffect4(() => {
323
- if (!containerRef.current) return;
324
- const chart = echarts2.init(containerRef.current, currentTheme);
424
+ if (!container) return;
425
+ const chart = echarts2.init(container, currentTheme);
325
426
  chartRef.current = chart;
326
- chart.setOption(optionRef.current, SET_OPTION_OPTS);
427
+ applyOption(chart, null, optionRef.current);
428
+ appliedRef.current = optionRef.current;
327
429
  onReadyRef.current?.(chart);
328
430
  return () => {
329
431
  chart.dispose();
@@ -331,11 +433,12 @@ function useChart({
331
433
  chartRef.current = null;
332
434
  }
333
435
  };
334
- }, [currentTheme]);
436
+ }, [container, currentTheme]);
335
437
  useEffect4(() => {
336
438
  const chart = chartRef.current;
337
439
  if (!chart || chart.isDisposed()) return;
338
- chart.setOption(optionRef.current, SET_OPTION_OPTS);
440
+ applyOption(chart, appliedRef.current, optionRef.current);
441
+ appliedRef.current = optionRef.current;
339
442
  }, [option, animation, reducedMotion]);
340
443
  useChartResize(chartRef, containerRef);
341
444
  return {
@@ -345,8 +448,76 @@ function useChart({
345
448
  };
346
449
  }
347
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
+
457
+ // src/hooks/useLatestRef.ts
458
+ import { useRef as useRef2 } from "react";
459
+ function useLatestRef(value) {
460
+ const ref = useRef2(value);
461
+ ref.current = value;
462
+ return ref;
463
+ }
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
+
348
519
  // src/components/core/LineChart.tsx
349
- import { jsx as jsx5 } from "react/jsx-runtime";
520
+ import { jsx as jsx6 } from "react/jsx-runtime";
350
521
  echarts3.use([
351
522
  EChartsLineChart,
352
523
  GridComponent,
@@ -355,7 +526,7 @@ echarts3.use([
355
526
  TitleComponent,
356
527
  CanvasRenderer
357
528
  ]);
358
- var LineChart = React5.forwardRef(
529
+ var LineChart = React6.forwardRef(
359
530
  ({
360
531
  title,
361
532
  description,
@@ -373,63 +544,110 @@ var LineChart = React5.forwardRef(
373
544
  areaFilled = false,
374
545
  showDataPoints = false,
375
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,
376
557
  onClick,
377
558
  onHover,
378
559
  class: className,
379
560
  ...props
380
561
  }, ref) => {
381
562
  const isEmpty = !data?.categories?.length || !data?.series?.length || data.series.every((s) => !s.data?.length);
382
- const option = React5.useMemo(() => {
563
+ const currentTheme = useChartTheme(theme);
564
+ const chartColors = getChartColors(currentTheme);
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(() => {
383
575
  if (isEmpty) return {};
384
- return {
385
- grid: {
386
- left: "3%",
387
- right: "4%",
388
- bottom: "3%",
389
- containLabel: true
390
- },
391
- tooltip: showTooltip ? {
392
- trigger: "axis",
393
- axisPointer: {
394
- type: "cross",
395
- label: {
396
- backgroundColor: "#6a7985"
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
397
602
  }
398
- }
399
- } : void 0,
400
- legend: showLegend ? {
401
- data: data.series.map((s) => s.name),
402
- bottom: 0
403
- } : void 0,
404
- xAxis: {
405
- type: "category",
406
- boundaryGap: false,
407
- data: data.categories,
408
- splitLine: {
409
- show: showGrid
410
- }
411
- },
412
- yAxis: {
413
- type: "value",
414
- splitLine: {
415
- show: showGrid
416
- }
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
+ }))
417
648
  },
418
- series: data.series.map((series) => ({
419
- name: series.name,
420
- type: "line",
421
- data: series.data,
422
- smooth,
423
- showSymbol: showDataPoints,
424
- symbolSize: showDataPoints ? 6 : 0,
425
- stack: stacked ? "Total" : void 0,
426
- areaStyle: areaFilled ? { opacity: 0.3 } : void 0,
427
- itemStyle: series.color ? { color: series.color } : void 0,
428
- emphasis: {
429
- focus: "series"
430
- }
431
- }))
432
- };
649
+ optionOverride
650
+ );
433
651
  }, [
434
652
  data,
435
653
  isEmpty,
@@ -439,45 +657,57 @@ var LineChart = React5.forwardRef(
439
657
  smooth,
440
658
  areaFilled,
441
659
  showDataPoints,
442
- stacked
660
+ stacked,
661
+ chartColors,
662
+ showLabels,
663
+ labelFormatter,
664
+ xAxisConfig,
665
+ yAxisConfig,
666
+ tooltipFormatter,
667
+ tooltipValueFormatter,
668
+ legendConfig,
669
+ customLegend,
670
+ optionOverride
443
671
  ]);
672
+ const latest = useLatestRef({ onClick, onHover });
444
673
  const { containerRef } = useChart({
445
674
  option,
446
- theme,
675
+ theme: currentTheme === THEME_DARK2 ? "dark" : "light",
447
676
  animation,
448
677
  onReady: (chart) => {
449
- if (onClick) {
450
- chart.on("click", (params) => {
451
- onClick({
452
- type: params.type || "click",
453
- componentType: params.componentType || "series",
454
- seriesIndex: params.seriesIndex,
455
- seriesName: params.seriesName || "",
456
- dataIndex: params.dataIndex || 0,
457
- name: params.name || "",
458
- value: params.value,
459
- color: params.color
460
- });
678
+ legend.attach(chart);
679
+ chart.on("click", (params) => {
680
+ const { onClick: onClick2 } = latest.current;
681
+ if (!onClick2) return;
682
+ onClick2({
683
+ type: params.type || "click",
684
+ componentType: params.componentType || "series",
685
+ seriesIndex: params.seriesIndex,
686
+ seriesName: params.seriesName || "",
687
+ dataIndex: params.dataIndex || 0,
688
+ name: params.name || "",
689
+ value: params.value,
690
+ color: params.color
461
691
  });
462
- }
463
- if (onHover) {
464
- chart.on("mouseover", (params) => {
465
- onHover({
466
- type: params.type || "mouseover",
467
- componentType: params.componentType || "series",
468
- seriesIndex: params.seriesIndex,
469
- seriesName: params.seriesName || "",
470
- dataIndex: params.dataIndex || 0,
471
- name: params.name || "",
472
- value: params.value,
473
- color: params.color
474
- });
692
+ });
693
+ chart.on("mouseover", (params) => {
694
+ const { onHover: onHover2 } = latest.current;
695
+ if (!onHover2) return;
696
+ onHover2({
697
+ type: params.type || "mouseover",
698
+ componentType: params.componentType || "series",
699
+ seriesIndex: params.seriesIndex,
700
+ seriesName: params.seriesName || "",
701
+ dataIndex: params.dataIndex || 0,
702
+ name: params.name || "",
703
+ value: params.value,
704
+ color: params.color
475
705
  });
476
- }
706
+ });
477
707
  }
478
708
  });
479
709
  const accessibilityDescription = description || (!isEmpty ? generateLineChartDescription(title, data) : void 0);
480
- return /* @__PURE__ */ jsx5(
710
+ return /* @__PURE__ */ jsx6(
481
711
  ChartContainer,
482
712
  {
483
713
  ref,
@@ -489,8 +719,10 @@ var LineChart = React5.forwardRef(
489
719
  empty: isEmpty,
490
720
  emptyMessage,
491
721
  className: cn(className),
722
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
723
+ legendPosition: legendConfig?.position,
492
724
  ...props,
493
- children: /* @__PURE__ */ jsx5("div", { ref: containerRef, className: "h-full w-full" })
725
+ children: /* @__PURE__ */ jsx6("div", { ref: containerRef, className: "h-full w-full" })
494
726
  }
495
727
  );
496
728
  }
@@ -498,7 +730,7 @@ var LineChart = React5.forwardRef(
498
730
  LineChart.displayName = "LineChart";
499
731
 
500
732
  // src/components/core/BarChart.tsx
501
- import * as React6 from "react";
733
+ import * as React7 from "react";
502
734
  import * as echarts4 from "echarts/core";
503
735
  import { BarChart as EChartsBarChart } from "echarts/charts";
504
736
  import {
@@ -508,8 +740,19 @@ import {
508
740
  TitleComponent as TitleComponent2
509
741
  } from "echarts/components";
510
742
  import { CanvasRenderer as CanvasRenderer2 } from "echarts/renderers";
511
- import { generateBarChartDescription } from "@classic-homes/charts-core";
512
- 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";
513
756
  echarts4.use([
514
757
  EChartsBarChart,
515
758
  GridComponent2,
@@ -518,7 +761,7 @@ echarts4.use([
518
761
  TitleComponent2,
519
762
  CanvasRenderer2
520
763
  ]);
521
- var BarChart = React6.forwardRef(
764
+ var BarChart = React7.forwardRef(
522
765
  ({
523
766
  title,
524
767
  description,
@@ -535,13 +778,32 @@ var BarChart = React6.forwardRef(
535
778
  orientation = "vertical",
536
779
  stacked = false,
537
780
  showValues = false,
781
+ labelFormatter,
782
+ xAxisConfig,
783
+ yAxisConfig,
784
+ tooltipFormatter,
785
+ tooltipValueFormatter,
786
+ legend: legendConfig,
787
+ renderLegend,
788
+ onLegendChange,
789
+ option: optionOverride,
538
790
  onClick,
539
791
  class: className,
540
792
  ...props
541
793
  }, ref) => {
542
794
  const isEmpty = !data?.categories?.length || !data?.series?.length || data.series.every((s) => !s.data?.length);
543
795
  const isHorizontal = orientation === "horizontal";
544
- 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(() => {
545
807
  if (isEmpty) return {};
546
808
  const categoryAxis = {
547
809
  type: "category",
@@ -556,64 +818,103 @@ var BarChart = React6.forwardRef(
556
818
  show: showGrid
557
819
  }
558
820
  };
559
- return {
560
- grid: {
561
- left: "3%",
562
- right: "4%",
563
- bottom: "3%",
564
- 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
+ }))
565
872
  },
566
- tooltip: showTooltip ? {
567
- trigger: "axis",
568
- axisPointer: {
569
- type: "shadow"
570
- }
571
- } : void 0,
572
- legend: showLegend ? {
573
- data: data.series.map((s) => s.name),
574
- bottom: 0
575
- } : void 0,
576
- xAxis: isHorizontal ? valueAxis : categoryAxis,
577
- yAxis: isHorizontal ? categoryAxis : valueAxis,
578
- series: data.series.map((series) => ({
579
- name: series.name,
580
- type: "bar",
581
- data: series.data,
582
- stack: stacked ? "Total" : void 0,
583
- itemStyle: series.color ? { color: series.color } : void 0,
584
- label: showValues ? {
585
- show: true,
586
- position: isHorizontal ? "right" : "top"
587
- } : void 0,
588
- emphasis: {
589
- focus: "series"
590
- }
591
- }))
592
- };
593
- }, [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
+ ]);
893
+ const latest = useLatestRef({ onClick });
594
894
  const { containerRef } = useChart({
595
895
  option,
596
896
  theme,
597
897
  animation,
598
898
  onReady: (chart) => {
599
- if (onClick) {
600
- chart.on("click", (params) => {
601
- onClick({
602
- type: params.type || "click",
603
- componentType: params.componentType || "series",
604
- seriesIndex: params.seriesIndex,
605
- seriesName: params.seriesName || "",
606
- dataIndex: params.dataIndex || 0,
607
- name: params.name || "",
608
- value: params.value,
609
- color: params.color
610
- });
899
+ legend.attach(chart);
900
+ chart.on("click", (params) => {
901
+ const { onClick: onClick2 } = latest.current;
902
+ if (!onClick2) return;
903
+ onClick2({
904
+ type: params.type || "click",
905
+ componentType: params.componentType || "series",
906
+ seriesIndex: params.seriesIndex,
907
+ seriesName: params.seriesName || "",
908
+ dataIndex: params.dataIndex || 0,
909
+ name: params.name || "",
910
+ value: params.value,
911
+ color: params.color
611
912
  });
612
- }
913
+ });
613
914
  }
614
915
  });
615
916
  const accessibilityDescription = description || (!isEmpty ? generateBarChartDescription(title, data) : void 0);
616
- return /* @__PURE__ */ jsx6(
917
+ return /* @__PURE__ */ jsx7(
617
918
  ChartContainer,
618
919
  {
619
920
  ref,
@@ -625,8 +926,10 @@ var BarChart = React6.forwardRef(
625
926
  empty: isEmpty,
626
927
  emptyMessage,
627
928
  className: cn(className),
929
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
930
+ legendPosition: legendConfig?.position,
628
931
  ...props,
629
- children: /* @__PURE__ */ jsx6("div", { ref: containerRef, className: "h-full w-full" })
932
+ children: /* @__PURE__ */ jsx7("div", { ref: containerRef, className: "h-full w-full" })
630
933
  }
631
934
  );
632
935
  }
@@ -634,15 +937,27 @@ var BarChart = React6.forwardRef(
634
937
  BarChart.displayName = "BarChart";
635
938
 
636
939
  // src/components/core/PieChart.tsx
637
- import * as React7 from "react";
940
+ import * as React8 from "react";
638
941
  import * as echarts5 from "echarts/core";
639
942
  import { PieChart as EChartsPieChart } from "echarts/charts";
640
943
  import { TooltipComponent as TooltipComponent3, LegendComponent as LegendComponent3, TitleComponent as TitleComponent3 } from "echarts/components";
641
944
  import { CanvasRenderer as CanvasRenderer3 } from "echarts/renderers";
642
- import { generatePieChartDescription } from "@classic-homes/charts-core";
643
- 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";
644
959
  echarts5.use([EChartsPieChart, TooltipComponent3, LegendComponent3, TitleComponent3, CanvasRenderer3]);
645
- var PieChart = React7.forwardRef(
960
+ var PieChart = React8.forwardRef(
646
961
  ({
647
962
  title,
648
963
  description,
@@ -658,73 +973,123 @@ var PieChart = React7.forwardRef(
658
973
  showLabels = true,
659
974
  labelPosition = "outside",
660
975
  innerRadius = 0,
976
+ labelFormatter,
977
+ tooltipFormatter,
978
+ tooltipValueFormatter,
979
+ legend: legendConfig,
980
+ renderLegend,
981
+ onLegendChange,
982
+ option: optionOverride,
661
983
  onClick,
662
984
  class: className,
663
985
  ...props
664
986
  }, ref) => {
665
987
  const isEmpty = !data?.length;
666
- 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(() => {
667
999
  if (isEmpty) return {};
668
- return {
669
- tooltip: showTooltip ? {
670
- trigger: "item",
671
- formatter: "{a} <br/>{b}: {c} ({d}%)"
672
- } : void 0,
673
- legend: showLegend ? {
674
- orient: "horizontal",
675
- bottom: 0,
676
- data: data.map((d) => d.name)
677
- } : void 0,
678
- series: [
679
- {
680
- name: title,
681
- type: "pie",
682
- radius: innerRadius === 0 ? "70%" : ["50%", "70%"],
683
- center: ["50%", "45%"],
684
- data: data.map((item) => ({
685
- name: item.name,
686
- value: item.value,
687
- itemStyle: item.color ? { color: item.color } : void 0
688
- })),
689
- label: showLabels ? {
690
- show: true,
691
- position: labelPosition,
692
- formatter: labelPosition === "inside" ? "{d}%" : "{b}: {d}%"
693
- } : {
694
- show: false
695
- },
696
- emphasis: {
697
- itemStyle: {
698
- shadowBlur: 10,
699
- shadowOffsetX: 0,
700
- 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
+ }
701
1048
  }
702
1049
  }
703
- }
704
- ]
705
- };
706
- }, [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
+ ]);
1070
+ const latest = useLatestRef({ onClick });
707
1071
  const { containerRef } = useChart({
708
1072
  option,
709
1073
  theme,
710
1074
  animation,
711
1075
  onReady: (chart) => {
712
- if (onClick) {
713
- chart.on("click", (params) => {
714
- onClick({
715
- type: params.type || "click",
716
- componentType: params.componentType || "series",
717
- name: params.name || "",
718
- value: params.value,
719
- percent: params.percent,
720
- color: params.color
721
- });
1076
+ legend.attach(chart);
1077
+ chart.on("click", (params) => {
1078
+ const { onClick: onClick2 } = latest.current;
1079
+ if (!onClick2) return;
1080
+ onClick2({
1081
+ type: params.type || "click",
1082
+ componentType: params.componentType || "series",
1083
+ name: params.name || "",
1084
+ value: params.value,
1085
+ percent: params.percent,
1086
+ color: params.color
722
1087
  });
723
- }
1088
+ });
724
1089
  }
725
1090
  });
726
1091
  const accessibilityDescription = description || (!isEmpty ? generatePieChartDescription(title, data, false) : void 0);
727
- return /* @__PURE__ */ jsx7(
1092
+ return /* @__PURE__ */ jsx8(
728
1093
  ChartContainer,
729
1094
  {
730
1095
  ref,
@@ -736,8 +1101,10 @@ var PieChart = React7.forwardRef(
736
1101
  empty: isEmpty,
737
1102
  emptyMessage,
738
1103
  className: cn(className),
1104
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
1105
+ legendPosition: legendConfig?.position,
739
1106
  ...props,
740
- children: /* @__PURE__ */ jsx7("div", { ref: containerRef, className: "h-full w-full" })
1107
+ children: /* @__PURE__ */ jsx8("div", { ref: containerRef, className: "h-full w-full" })
741
1108
  }
742
1109
  );
743
1110
  }
@@ -745,7 +1112,7 @@ var PieChart = React7.forwardRef(
745
1112
  PieChart.displayName = "PieChart";
746
1113
 
747
1114
  // src/components/core/AreaChart.tsx
748
- import * as React8 from "react";
1115
+ import * as React9 from "react";
749
1116
  import * as echarts6 from "echarts/core";
750
1117
  import { LineChart as EChartsLineChart2 } from "echarts/charts";
751
1118
  import {
@@ -755,7 +1122,21 @@ import {
755
1122
  TitleComponent as TitleComponent4
756
1123
  } from "echarts/components";
757
1124
  import { CanvasRenderer as CanvasRenderer4 } from "echarts/renderers";
758
- 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";
759
1140
  echarts6.use([
760
1141
  EChartsLineChart2,
761
1142
  GridComponent3,
@@ -764,7 +1145,7 @@ echarts6.use([
764
1145
  TitleComponent4,
765
1146
  CanvasRenderer4
766
1147
  ]);
767
- var AreaChart = React8.forwardRef(
1148
+ var AreaChart = React9.forwardRef(
768
1149
  ({
769
1150
  title,
770
1151
  description,
@@ -781,94 +1162,161 @@ var AreaChart = React8.forwardRef(
781
1162
  smooth = false,
782
1163
  stacked = false,
783
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,
784
1175
  onClick,
785
1176
  class: className,
786
1177
  ...props
787
1178
  }, ref) => {
788
1179
  const isEmpty = !data?.categories?.length || !data?.series?.length || data.series.every((s) => !s.data?.length);
789
- const option = React8.useMemo(() => {
1180
+ const currentTheme = useChartTheme(theme);
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(() => {
790
1192
  if (isEmpty) return {};
791
- return {
792
- grid: {
793
- left: "3%",
794
- right: "4%",
795
- bottom: "3%",
796
- containLabel: true
797
- },
798
- tooltip: showTooltip ? {
799
- trigger: "axis",
800
- axisPointer: {
801
- type: "cross",
802
- label: {
803
- backgroundColor: "#6a7985"
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
804
1219
  }
805
- }
806
- } : void 0,
807
- legend: showLegend ? {
808
- data: data.series.map((s) => s.name),
809
- bottom: 0
810
- } : void 0,
811
- xAxis: {
812
- type: "category",
813
- boundaryGap: false,
814
- data: data.categories
815
- },
816
- yAxis: {
817
- type: "value",
818
- splitLine: {
819
- show: showGrid
820
- }
821
- },
822
- series: data.series.map((series, index) => ({
823
- name: series.name,
824
- type: "line",
825
- data: series.data,
826
- smooth,
827
- stack: stacked ? "Total" : void 0,
828
- areaStyle: gradient ? {
829
- opacity: 0.8,
830
- color: new echarts6.graphic.LinearGradient(0, 0, 0, 1, [
831
- {
832
- offset: 0,
833
- color: series.color || `rgba(${60 + index * 40}, ${160 - index * 20}, ${180 - index * 30}, 0.8)`
834
- },
835
- {
836
- offset: 1,
837
- color: series.color || `rgba(${60 + index * 40}, ${160 - index * 20}, ${180 - index * 30}, 0.1)`
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
1231
+ },
1232
+ xAxisConfig
1233
+ ),
1234
+ yAxis: buildAxis3(
1235
+ {
1236
+ type: "value",
1237
+ splitLine: {
1238
+ show: showGrid
838
1239
  }
839
- ])
840
- } : { opacity: 0.5 },
841
- itemStyle: series.color ? { color: series.color } : void 0,
842
- emphasis: {
843
- focus: "series"
844
- },
845
- showSymbol: false
846
- }))
847
- };
848
- }, [data, isEmpty, showTooltip, showLegend, showGrid, smooth, stacked, gradient]);
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
+ ]);
1295
+ const latest = useLatestRef({ onClick });
849
1296
  const { containerRef } = useChart({
850
1297
  option,
851
- theme,
1298
+ theme: currentTheme === THEME_DARK3 ? "dark" : "light",
852
1299
  animation,
853
1300
  onReady: (chart) => {
854
- if (onClick) {
855
- chart.on("click", (params) => {
856
- onClick({
857
- type: params.type || "click",
858
- componentType: params.componentType || "series",
859
- seriesIndex: params.seriesIndex,
860
- seriesName: params.seriesName || "",
861
- dataIndex: params.dataIndex || 0,
862
- name: params.name || "",
863
- value: params.value,
864
- color: params.color
865
- });
1301
+ legend.attach(chart);
1302
+ chart.on("click", (params) => {
1303
+ const { onClick: onClick2 } = latest.current;
1304
+ if (!onClick2) return;
1305
+ onClick2({
1306
+ type: params.type || "click",
1307
+ componentType: params.componentType || "series",
1308
+ seriesIndex: params.seriesIndex,
1309
+ seriesName: params.seriesName || "",
1310
+ dataIndex: params.dataIndex || 0,
1311
+ name: params.name || "",
1312
+ value: params.value,
1313
+ color: params.color
866
1314
  });
867
- }
1315
+ });
868
1316
  }
869
1317
  });
870
1318
  const accessibilityDescription = description || (!isEmpty ? `${title}. Area chart with ${data.categories.length} data points across ${data.series.length} series.` : void 0);
871
- return /* @__PURE__ */ jsx8(
1319
+ return /* @__PURE__ */ jsx9(
872
1320
  ChartContainer,
873
1321
  {
874
1322
  ref,
@@ -880,8 +1328,10 @@ var AreaChart = React8.forwardRef(
880
1328
  empty: isEmpty,
881
1329
  emptyMessage,
882
1330
  className: cn(className),
1331
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
1332
+ legendPosition: legendConfig?.position,
883
1333
  ...props,
884
- children: /* @__PURE__ */ jsx8("div", { ref: containerRef, className: "h-full w-full" })
1334
+ children: /* @__PURE__ */ jsx9("div", { ref: containerRef, className: "h-full w-full" })
885
1335
  }
886
1336
  );
887
1337
  }
@@ -889,9 +1339,9 @@ var AreaChart = React8.forwardRef(
889
1339
  AreaChart.displayName = "AreaChart";
890
1340
 
891
1341
  // src/components/core/ScatterChart.tsx
892
- import * as React9 from "react";
1342
+ import * as React10 from "react";
893
1343
  import * as echarts7 from "echarts/core";
894
- import { ScatterChart as EChartsScatterChart } from "echarts/charts";
1344
+ import { ScatterChart as EChartsScatterChart, LineChart as EChartsLineChart3 } from "echarts/charts";
895
1345
  import {
896
1346
  GridComponent as GridComponent4,
897
1347
  TooltipComponent as TooltipComponent5,
@@ -899,17 +1349,32 @@ import {
899
1349
  TitleComponent as TitleComponent5
900
1350
  } from "echarts/components";
901
1351
  import { CanvasRenderer as CanvasRenderer5 } from "echarts/renderers";
902
- import { generateScatterChartDescription } from "@classic-homes/charts-core";
903
- 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";
904
1367
  echarts7.use([
905
1368
  EChartsScatterChart,
1369
+ // Trend lines are line series
1370
+ EChartsLineChart3,
906
1371
  GridComponent4,
907
1372
  TooltipComponent5,
908
1373
  LegendComponent5,
909
1374
  TitleComponent5,
910
1375
  CanvasRenderer5
911
1376
  ]);
912
- var ScatterChart = React9.forwardRef(
1377
+ var ScatterChart = React10.forwardRef(
913
1378
  ({
914
1379
  title,
915
1380
  description,
@@ -925,19 +1390,45 @@ var ScatterChart = React9.forwardRef(
925
1390
  showGrid = true,
926
1391
  showTrendLine = false,
927
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,
928
1403
  onClick,
929
1404
  class: className,
930
1405
  ...props
931
1406
  }, ref) => {
932
1407
  const isEmpty = !data?.series?.length || data.series.every((s) => !s.data?.length);
933
- 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(() => {
934
1419
  if (isEmpty) return {};
1420
+ const getLabel = seriesLabelLookup4(data.series);
935
1421
  const series = data.series.map((s) => ({
936
1422
  name: s.name,
937
1423
  type: "scatter",
938
- data: s.data,
1424
+ data: withPointLabels4(s.data, s.labels, labelFormatter, { position: "top" }),
939
1425
  symbolSize: typeof symbolSize === "function" ? symbolSize : symbolSize,
940
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
+ }),
941
1432
  emphasis: {
942
1433
  focus: "series",
943
1434
  itemStyle: {
@@ -959,9 +1450,12 @@ var ScatterChart = React9.forwardRef(
959
1450
  });
960
1451
  const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
961
1452
  const intercept = (sumY - slope * sumX) / n;
962
- const xValues = s.data.map(([x]) => x);
963
- const minX = Math.min(...xValues);
964
- const maxX = Math.max(...xValues);
1453
+ let minX = Infinity;
1454
+ let maxX = -Infinity;
1455
+ for (const [x] of s.data) {
1456
+ if (x < minX) minX = x;
1457
+ if (x > maxX) maxX = x;
1458
+ }
965
1459
  series.push({
966
1460
  name: `${s.name} Trend`,
967
1461
  type: "line",
@@ -982,63 +1476,103 @@ var ScatterChart = React9.forwardRef(
982
1476
  });
983
1477
  });
984
1478
  }
985
- return {
986
- grid: {
987
- left: "3%",
988
- right: "4%",
989
- bottom: "3%",
990
- containLabel: true
991
- },
992
- tooltip: showTooltip ? {
993
- trigger: "item",
994
- formatter: (params) => {
995
- const p = params;
996
- if (!p.value) return "";
997
- return `${p.seriesName}<br/>X: ${p.value[0]}<br/>Y: ${p.value[1]}`;
998
- }
999
- } : void 0,
1000
- legend: showLegend ? {
1001
- data: data.series.map((s) => s.name),
1002
- bottom: 0
1003
- } : void 0,
1004
- xAxis: {
1005
- type: "value",
1006
- splitLine: {
1007
- show: showGrid
1008
- }
1009
- },
1010
- yAxis: {
1011
- type: "value",
1012
- splitLine: {
1013
- show: showGrid
1014
- }
1015
- },
1016
- series
1017
- };
1018
- }, [data, isEmpty, showTooltip, showLegend, showGrid, showTrendLine, symbolSize]);
1019
- const { containerRef } = useChart({
1020
- option,
1021
- theme,
1022
- animation,
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
+ ]);
1551
+ const latest = useLatestRef({ onClick });
1552
+ const { containerRef } = useChart({
1553
+ option,
1554
+ theme,
1555
+ animation,
1023
1556
  onReady: (chart) => {
1024
- if (onClick) {
1025
- chart.on("click", (params) => {
1026
- onClick({
1027
- type: params.type || "click",
1028
- componentType: params.componentType || "series",
1029
- seriesIndex: params.seriesIndex,
1030
- seriesName: params.seriesName || "",
1031
- dataIndex: params.dataIndex || 0,
1032
- name: params.name || "",
1033
- value: params.value,
1034
- color: params.color
1035
- });
1557
+ legend.attach(chart);
1558
+ chart.on("click", (params) => {
1559
+ const { onClick: onClick2 } = latest.current;
1560
+ if (!onClick2) return;
1561
+ onClick2({
1562
+ type: params.type || "click",
1563
+ componentType: params.componentType || "series",
1564
+ seriesIndex: params.seriesIndex,
1565
+ seriesName: params.seriesName || "",
1566
+ dataIndex: params.dataIndex || 0,
1567
+ name: params.name || "",
1568
+ value: params.value,
1569
+ color: params.color
1036
1570
  });
1037
- }
1571
+ });
1038
1572
  }
1039
1573
  });
1040
1574
  const accessibilityDescription = description || (!isEmpty ? generateScatterChartDescription(title, data) : void 0);
1041
- return /* @__PURE__ */ jsx9(
1575
+ return /* @__PURE__ */ jsx10(
1042
1576
  ChartContainer,
1043
1577
  {
1044
1578
  ref,
@@ -1050,8 +1584,10 @@ var ScatterChart = React9.forwardRef(
1050
1584
  empty: isEmpty,
1051
1585
  emptyMessage,
1052
1586
  className: cn(className),
1587
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
1588
+ legendPosition: legendConfig?.position,
1053
1589
  ...props,
1054
- children: /* @__PURE__ */ jsx9("div", { ref: containerRef, className: "h-full w-full" })
1590
+ children: /* @__PURE__ */ jsx10("div", { ref: containerRef, className: "h-full w-full" })
1055
1591
  }
1056
1592
  );
1057
1593
  }
@@ -1059,15 +1595,29 @@ var ScatterChart = React9.forwardRef(
1059
1595
  ScatterChart.displayName = "ScatterChart";
1060
1596
 
1061
1597
  // src/components/core/DonutChart.tsx
1062
- import * as React10 from "react";
1598
+ import * as React11 from "react";
1063
1599
  import * as echarts8 from "echarts/core";
1064
1600
  import { PieChart as EChartsPieChart2 } from "echarts/charts";
1065
1601
  import { TooltipComponent as TooltipComponent6, LegendComponent as LegendComponent6, TitleComponent as TitleComponent6 } from "echarts/components";
1066
1602
  import { CanvasRenderer as CanvasRenderer6 } from "echarts/renderers";
1067
- import { generatePieChartDescription as generatePieChartDescription2 } from "@classic-homes/charts-core";
1068
- import { jsx as jsx10 } from "react/jsx-runtime";
1603
+ import {
1604
+ buildDataLabel as buildDataLabel6,
1605
+ buildLegend as buildLegend6,
1606
+ buildTooltip as buildTooltip6,
1607
+ escapeHtml as escapeHtml3,
1608
+ formatTooltipValue as formatTooltipValue3,
1609
+ generatePieChartDescription as generatePieChartDescription2,
1610
+ getChartColors as getChartColors6,
1611
+ itemLabelLookup as itemLabelLookup2,
1612
+ anchorCenterGraphic,
1613
+ legendCenter as legendCenter2,
1614
+ mergeChartOption as mergeChartOption6,
1615
+ pointLabel as pointLabel2,
1616
+ THEME_DARK as THEME_DARK4
1617
+ } from "@classic-homes/charts-core";
1618
+ import { jsx as jsx11 } from "react/jsx-runtime";
1069
1619
  echarts8.use([EChartsPieChart2, TooltipComponent6, LegendComponent6, TitleComponent6, CanvasRenderer6]);
1070
- var DonutChart = React10.forwardRef(
1620
+ var DonutChart = React11.forwardRef(
1071
1621
  ({
1072
1622
  title,
1073
1623
  description,
@@ -1085,93 +1635,134 @@ var DonutChart = React10.forwardRef(
1085
1635
  innerRadius = "50%",
1086
1636
  centerLabel,
1087
1637
  centerValue,
1638
+ labelFormatter,
1639
+ tooltipFormatter,
1640
+ tooltipValueFormatter,
1641
+ legend: legendConfig,
1642
+ renderLegend,
1643
+ onLegendChange,
1644
+ option: optionOverride,
1088
1645
  onClick,
1089
1646
  class: className,
1090
1647
  ...props
1091
1648
  }, ref) => {
1092
1649
  const isEmpty = !data?.length;
1093
- const option = React10.useMemo(() => {
1650
+ const currentTheme = useChartTheme(theme);
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(() => {
1094
1662
  if (isEmpty) return {};
1663
+ const pieCenter = legendCenter2(legendConfig, showLegend && !customLegend, ["50%", "45%"]);
1095
1664
  const graphicElements = [];
1096
1665
  if (centerLabel || centerValue) {
1097
- graphicElements.push({
1098
- type: "group",
1099
- left: "center",
1100
- top: "40%",
1101
- children: [
1102
- ...centerValue !== void 0 ? [
1103
- {
1104
- type: "text",
1105
- style: {
1106
- text: String(centerValue),
1107
- fontSize: 28,
1108
- fontWeight: "bold",
1109
- fill: "currentColor",
1110
- textAlign: "center"
1111
- },
1112
- top: 0,
1113
- left: "center"
1114
- }
1115
- ] : [],
1116
- ...centerLabel ? [
1117
- {
1118
- type: "text",
1119
- style: {
1120
- text: centerLabel,
1121
- fontSize: 14,
1122
- fill: "#888",
1123
- textAlign: "center"
1124
- },
1125
- top: centerValue !== void 0 ? 35 : 0,
1126
- left: "center"
1127
- }
1128
- ] : []
1129
- ]
1130
- });
1131
- }
1132
- return {
1133
- tooltip: showTooltip ? {
1134
- trigger: "item",
1135
- formatter: "{a} <br/>{b}: {c} ({d}%)"
1136
- } : void 0,
1137
- legend: showLegend ? {
1138
- orient: "horizontal",
1139
- bottom: 0,
1140
- data: data.map((d) => d.name)
1141
- } : void 0,
1142
- graphic: graphicElements.length > 0 ? graphicElements : void 0,
1143
- series: [
1144
- {
1145
- name: title,
1146
- type: "pie",
1147
- radius: [innerRadius, "70%"],
1148
- center: ["50%", "45%"],
1149
- avoidLabelOverlap: true,
1150
- data: data.map((item) => ({
1151
- name: item.name,
1152
- value: item.value,
1153
- itemStyle: item.color ? { color: item.color } : void 0
1154
- })),
1155
- label: showLabels ? {
1156
- show: true,
1157
- position: labelPosition,
1158
- formatter: labelPosition === "inside" ? "{d}%" : "{b}: {d}%"
1159
- } : {
1160
- show: false
1161
- },
1162
- labelLine: {
1163
- 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
+ ]
1164
1701
  },
1165
- emphasis: {
1166
- itemStyle: {
1167
- shadowBlur: 10,
1168
- shadowOffsetX: 0,
1169
- 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
+ }
1170
1760
  }
1171
1761
  }
1172
- }
1173
- ]
1174
- };
1762
+ ]
1763
+ },
1764
+ optionOverride
1765
+ );
1175
1766
  }, [
1176
1767
  data,
1177
1768
  isEmpty,
@@ -1182,29 +1773,38 @@ var DonutChart = React10.forwardRef(
1182
1773
  innerRadius,
1183
1774
  centerLabel,
1184
1775
  centerValue,
1185
- title
1776
+ title,
1777
+ chartColors,
1778
+ labelFormatter,
1779
+ tooltipFormatter,
1780
+ tooltipValueFormatter,
1781
+ legendConfig,
1782
+ customLegend,
1783
+ optionOverride
1186
1784
  ]);
1785
+ const latest = useLatestRef({ onClick });
1187
1786
  const { containerRef } = useChart({
1188
1787
  option,
1189
- theme,
1788
+ theme: currentTheme === THEME_DARK4 ? "dark" : "light",
1190
1789
  animation,
1191
1790
  onReady: (chart) => {
1192
- if (onClick) {
1193
- chart.on("click", (params) => {
1194
- onClick({
1195
- type: params.type || "click",
1196
- componentType: params.componentType || "series",
1197
- name: params.name || "",
1198
- value: params.value,
1199
- percent: params.percent,
1200
- color: params.color
1201
- });
1791
+ legend.attach(chart);
1792
+ chart.on("click", (params) => {
1793
+ const { onClick: onClick2 } = latest.current;
1794
+ if (!onClick2) return;
1795
+ onClick2({
1796
+ type: params.type || "click",
1797
+ componentType: params.componentType || "series",
1798
+ name: params.name || "",
1799
+ value: params.value,
1800
+ percent: params.percent,
1801
+ color: params.color
1202
1802
  });
1203
- }
1803
+ });
1204
1804
  }
1205
1805
  });
1206
1806
  const accessibilityDescription = description || (!isEmpty ? generatePieChartDescription2(title, data, true) : void 0);
1207
- return /* @__PURE__ */ jsx10(
1807
+ return /* @__PURE__ */ jsx11(
1208
1808
  ChartContainer,
1209
1809
  {
1210
1810
  ref,
@@ -1216,8 +1816,10 @@ var DonutChart = React10.forwardRef(
1216
1816
  empty: isEmpty,
1217
1817
  emptyMessage,
1218
1818
  className: cn(className),
1819
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
1820
+ legendPosition: legendConfig?.position,
1219
1821
  ...props,
1220
- children: /* @__PURE__ */ jsx10("div", { ref: containerRef, className: "h-full w-full" })
1822
+ children: /* @__PURE__ */ jsx11("div", { ref: containerRef, className: "h-full w-full" })
1221
1823
  }
1222
1824
  );
1223
1825
  }
@@ -1225,7 +1827,7 @@ var DonutChart = React10.forwardRef(
1225
1827
  DonutChart.displayName = "DonutChart";
1226
1828
 
1227
1829
  // src/components/extended/RadarChart.tsx
1228
- import * as React11 from "react";
1830
+ import * as React12 from "react";
1229
1831
  import * as echarts9 from "echarts/core";
1230
1832
  import { RadarChart as EChartsRadarChart } from "echarts/charts";
1231
1833
  import {
@@ -1235,7 +1837,15 @@ import {
1235
1837
  RadarComponent
1236
1838
  } from "echarts/components";
1237
1839
  import { CanvasRenderer as CanvasRenderer7 } from "echarts/renderers";
1238
- 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";
1239
1849
  echarts9.use([
1240
1850
  EChartsRadarChart,
1241
1851
  TooltipComponent7,
@@ -1244,7 +1854,7 @@ echarts9.use([
1244
1854
  RadarComponent,
1245
1855
  CanvasRenderer7
1246
1856
  ]);
1247
- var RadarChart = React11.forwardRef(
1857
+ var RadarChart = React12.forwardRef(
1248
1858
  ({
1249
1859
  title,
1250
1860
  description,
@@ -1259,50 +1869,101 @@ var RadarChart = React11.forwardRef(
1259
1869
  showLegend = true,
1260
1870
  showTooltip = true,
1261
1871
  shape = "polygon",
1872
+ showLabels = false,
1873
+ labelFormatter,
1874
+ tooltipFormatter,
1875
+ tooltipValueFormatter,
1876
+ legend: legendConfig,
1877
+ renderLegend,
1878
+ onLegendChange,
1879
+ option: optionOverride,
1262
1880
  class: className,
1263
1881
  ...props
1264
1882
  }, ref) => {
1265
1883
  const isEmpty = !data?.series?.length || !indicators?.length || data.series.every((s) => !s.data?.length);
1266
- 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(() => {
1267
1895
  if (isEmpty) return {};
1268
- return {
1269
- tooltip: showTooltip ? {
1270
- trigger: "item"
1271
- } : void 0,
1272
- legend: showLegend ? {
1273
- data: data.series.map((s) => s.name),
1274
- bottom: 0
1275
- } : void 0,
1276
- radar: {
1277
- indicator: indicators.map((ind) => ({
1278
- name: ind.name,
1279
- max: ind.max,
1280
- min: ind.min
1281
- })),
1282
- 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
+ ]
1283
1939
  },
1284
- series: [
1285
- {
1286
- type: "radar",
1287
- data: data.series.map((series) => ({
1288
- name: series.name,
1289
- value: series.data,
1290
- itemStyle: series.color ? { color: series.color } : void 0,
1291
- areaStyle: {
1292
- opacity: 0.2
1293
- }
1294
- }))
1295
- }
1296
- ]
1297
- };
1298
- }, [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
+ ]);
1299
1957
  const { containerRef } = useChart({
1300
1958
  option,
1301
1959
  theme,
1302
- animation
1960
+ animation,
1961
+ onReady: (chart) => {
1962
+ legend.attach(chart);
1963
+ }
1303
1964
  });
1304
1965
  const accessibilityDescription = description || (!isEmpty ? `${title}. Radar chart with ${indicators.length} dimensions comparing ${data.series.length} series.` : void 0);
1305
- return /* @__PURE__ */ jsx11(
1966
+ return /* @__PURE__ */ jsx12(
1306
1967
  ChartContainer,
1307
1968
  {
1308
1969
  ref,
@@ -1314,8 +1975,10 @@ var RadarChart = React11.forwardRef(
1314
1975
  empty: isEmpty,
1315
1976
  emptyMessage,
1316
1977
  className: cn(className),
1978
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
1979
+ legendPosition: legendConfig?.position,
1317
1980
  ...props,
1318
- children: /* @__PURE__ */ jsx11("div", { ref: containerRef, className: "h-full w-full" })
1981
+ children: /* @__PURE__ */ jsx12("div", { ref: containerRef, className: "h-full w-full" })
1319
1982
  }
1320
1983
  );
1321
1984
  }
@@ -1323,12 +1986,23 @@ var RadarChart = React11.forwardRef(
1323
1986
  RadarChart.displayName = "RadarChart";
1324
1987
 
1325
1988
  // src/components/extended/FunnelChart.tsx
1326
- import * as React12 from "react";
1989
+ import * as React13 from "react";
1327
1990
  import * as echarts10 from "echarts/core";
1328
1991
  import { FunnelChart as EChartsFunnelChart } from "echarts/charts";
1329
1992
  import { TooltipComponent as TooltipComponent8, LegendComponent as LegendComponent8, TitleComponent as TitleComponent8 } from "echarts/components";
1330
1993
  import { CanvasRenderer as CanvasRenderer8 } from "echarts/renderers";
1331
- 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";
1332
2006
  echarts10.use([
1333
2007
  EChartsFunnelChart,
1334
2008
  TooltipComponent8,
@@ -1336,7 +2010,7 @@ echarts10.use([
1336
2010
  TitleComponent8,
1337
2011
  CanvasRenderer8
1338
2012
  ]);
1339
- var FunnelChart = React12.forwardRef(
2013
+ var FunnelChart = React13.forwardRef(
1340
2014
  ({
1341
2015
  title,
1342
2016
  description,
@@ -1352,81 +2026,134 @@ var FunnelChart = React12.forwardRef(
1352
2026
  sort = "descending",
1353
2027
  orientation = "vertical",
1354
2028
  showLabels = true,
2029
+ labelFormatter,
2030
+ tooltipFormatter,
2031
+ tooltipValueFormatter,
2032
+ legend: legendConfig,
2033
+ renderLegend,
2034
+ onLegendChange,
2035
+ option: optionOverride,
1355
2036
  onClick,
1356
2037
  class: className,
1357
2038
  ...props
1358
2039
  }, ref) => {
1359
2040
  const isEmpty = !data?.length;
1360
- 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(() => {
1361
2052
  if (isEmpty) return {};
1362
- return {
1363
- tooltip: showTooltip ? {
1364
- trigger: "item",
1365
- formatter: "{a} <br/>{b}: {c}"
1366
- } : void 0,
1367
- legend: showLegend ? {
1368
- data: data.map((d) => d.name),
1369
- bottom: 0
1370
- } : void 0,
1371
- series: [
1372
- {
1373
- name: title,
1374
- type: "funnel",
1375
- left: "10%",
1376
- top: 60,
1377
- bottom: 60,
1378
- width: "80%",
1379
- min: 0,
1380
- max: Math.max(...data.map((d) => d.value)),
1381
- minSize: "0%",
1382
- maxSize: "100%",
1383
- sort,
1384
- orient: orientation,
1385
- gap: 2,
1386
- label: showLabels ? {
1387
- show: true,
1388
- position: "inside",
1389
- formatter: "{b}: {c}"
1390
- } : {
1391
- show: false
1392
- },
1393
- labelLine: {
1394
- show: false
1395
- },
1396
- emphasis: {
1397
- label: {
1398
- fontSize: 16
1399
- }
1400
- },
1401
- data: data.map((item) => ({
1402
- name: item.name,
1403
- value: item.value,
1404
- itemStyle: item.color ? { color: item.color } : void 0
1405
- }))
1406
- }
1407
- ]
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}"
1408
2061
  };
1409
- }, [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
+ ]);
2135
+ const latest = useLatestRef({ onClick });
1410
2136
  const { containerRef } = useChart({
1411
2137
  option,
1412
2138
  theme,
1413
2139
  animation,
1414
2140
  onReady: (chart) => {
1415
- if (onClick) {
1416
- chart.on("click", (params) => {
1417
- onClick({
1418
- type: params.type || "click",
1419
- componentType: params.componentType || "series",
1420
- name: params.name || "",
1421
- value: params.value,
1422
- color: params.color
1423
- });
2141
+ legend.attach(chart);
2142
+ chart.on("click", (params) => {
2143
+ const { onClick: onClick2 } = latest.current;
2144
+ if (!onClick2) return;
2145
+ onClick2({
2146
+ type: params.type || "click",
2147
+ componentType: params.componentType || "series",
2148
+ name: params.name || "",
2149
+ value: params.value,
2150
+ color: params.color
1424
2151
  });
1425
- }
2152
+ });
1426
2153
  }
1427
2154
  });
1428
2155
  const accessibilityDescription = description || (!isEmpty ? `${title}. Funnel chart with ${data.length} stages.` : void 0);
1429
- return /* @__PURE__ */ jsx12(
2156
+ return /* @__PURE__ */ jsx13(
1430
2157
  ChartContainer,
1431
2158
  {
1432
2159
  ref,
@@ -1438,8 +2165,10 @@ var FunnelChart = React12.forwardRef(
1438
2165
  empty: isEmpty,
1439
2166
  emptyMessage,
1440
2167
  className: cn(className),
2168
+ legend: !isEmpty && renderLegend ? renderLegend(legend.renderProps) : void 0,
2169
+ legendPosition: legendConfig?.position,
1441
2170
  ...props,
1442
- children: /* @__PURE__ */ jsx12("div", { ref: containerRef, className: "h-full w-full" })
2171
+ children: /* @__PURE__ */ jsx13("div", { ref: containerRef, className: "h-full w-full" })
1443
2172
  }
1444
2173
  );
1445
2174
  }
@@ -1447,14 +2176,20 @@ var FunnelChart = React12.forwardRef(
1447
2176
  FunnelChart.displayName = "FunnelChart";
1448
2177
 
1449
2178
  // src/components/extended/GaugeChart.tsx
1450
- import * as React13 from "react";
2179
+ import * as React14 from "react";
1451
2180
  import * as echarts11 from "echarts/core";
1452
2181
  import { GaugeChart as EChartsGaugeChart } from "echarts/charts";
1453
- import { TitleComponent as TitleComponent9 } from "echarts/components";
2182
+ import { TitleComponent as TitleComponent9, TooltipComponent as TooltipComponent9 } from "echarts/components";
1454
2183
  import { CanvasRenderer as CanvasRenderer9 } from "echarts/renderers";
1455
- import { jsx as jsx13 } from "react/jsx-runtime";
1456
- echarts11.use([EChartsGaugeChart, TitleComponent9, CanvasRenderer9]);
1457
- 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(
1458
2193
  ({
1459
2194
  title,
1460
2195
  description,
@@ -1470,89 +2205,123 @@ var GaugeChart = React13.forwardRef(
1470
2205
  splitNumber = 10,
1471
2206
  showProgress = true,
1472
2207
  formatValue,
2208
+ showTooltip = true,
2209
+ tooltipFormatter,
2210
+ tooltipValueFormatter,
2211
+ option: optionOverride,
1473
2212
  class: className,
1474
2213
  ...props
1475
2214
  }, ref) => {
1476
- const option = React13.useMemo(() => {
1477
- return {
1478
- series: [
1479
- {
1480
- type: "gauge",
1481
- center: ["50%", "60%"],
1482
- radius: "80%",
1483
- min,
1484
- max,
1485
- splitNumber,
1486
- progress: showProgress ? {
1487
- show: true,
1488
- width: 18
1489
- } : void 0,
1490
- axisLine: {
1491
- lineStyle: {
2215
+ const currentTheme = useChartTheme(theme);
2216
+ const chartColors = getChartColors9(currentTheme);
2217
+ const reducedMotion = useReducedMotion();
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,
1492
2240
  width: 18
1493
- }
1494
- },
1495
- axisTick: {
1496
- distance: -30,
1497
- splitNumber: 5,
1498
- lineStyle: {
1499
- width: 2,
1500
- color: "#999"
1501
- }
1502
- },
1503
- splitLine: {
1504
- distance: -32,
1505
- length: 10,
1506
- lineStyle: {
1507
- width: 3,
1508
- color: "#999"
1509
- }
1510
- },
1511
- axisLabel: {
1512
- distance: -20,
1513
- color: "#999",
1514
- fontSize: 12
1515
- },
1516
- anchor: {
1517
- show: true,
1518
- size: 20,
1519
- itemStyle: {
1520
- borderWidth: 2
1521
- }
1522
- },
1523
- pointer: {
1524
- width: 6,
1525
- length: "60%"
1526
- },
1527
- title: {
1528
- show: true,
1529
- offsetCenter: [0, "70%"],
1530
- fontSize: 14
1531
- },
1532
- detail: {
1533
- valueAnimation: animation,
1534
- fontSize: 28,
1535
- fontWeight: "bold",
1536
- offsetCenter: [0, "40%"],
1537
- formatter: formatValue || "{value}"
1538
- },
1539
- data: [
1540
- {
1541
- value,
1542
- name: title
1543
- }
1544
- ]
1545
- }
1546
- ]
1547
- };
1548
- }, [value, min, max, splitNumber, showProgress, formatValue, title, animation]);
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
+ );
2302
+ }, [
2303
+ value,
2304
+ min,
2305
+ max,
2306
+ splitNumber,
2307
+ showProgress,
2308
+ formatValue,
2309
+ title,
2310
+ animation,
2311
+ reducedMotion,
2312
+ chartColors,
2313
+ showTooltip,
2314
+ tooltipFormatter,
2315
+ tooltipValueFormatter,
2316
+ optionOverride
2317
+ ]);
1549
2318
  const { containerRef } = useChart({
1550
2319
  option,
1551
- theme,
2320
+ theme: currentTheme === THEME_DARK5 ? "dark" : "light",
1552
2321
  animation
1553
2322
  });
1554
2323
  const accessibilityDescription = description || `${title}. Gauge showing value ${value} on scale from ${min} to ${max}.`;
1555
- return /* @__PURE__ */ jsx13(
2324
+ return /* @__PURE__ */ jsx14(
1556
2325
  ChartContainer,
1557
2326
  {
1558
2327
  ref,
@@ -1564,7 +2333,7 @@ var GaugeChart = React13.forwardRef(
1564
2333
  emptyMessage,
1565
2334
  className: cn(className),
1566
2335
  ...props,
1567
- children: /* @__PURE__ */ jsx13("div", { ref: containerRef, className: "h-full w-full" })
2336
+ children: /* @__PURE__ */ jsx14("div", { ref: containerRef, className: "h-full w-full" })
1568
2337
  }
1569
2338
  );
1570
2339
  }
@@ -1572,26 +2341,34 @@ var GaugeChart = React13.forwardRef(
1572
2341
  GaugeChart.displayName = "GaugeChart";
1573
2342
 
1574
2343
  // src/components/extended/HeatmapChart.tsx
1575
- import * as React14 from "react";
2344
+ import * as React15 from "react";
1576
2345
  import * as echarts12 from "echarts/core";
1577
2346
  import { HeatmapChart as EChartsHeatmapChart } from "echarts/charts";
1578
2347
  import {
1579
2348
  GridComponent as GridComponent5,
1580
- TooltipComponent as TooltipComponent9,
2349
+ TooltipComponent as TooltipComponent10,
1581
2350
  TitleComponent as TitleComponent10,
1582
2351
  VisualMapComponent
1583
2352
  } from "echarts/components";
1584
2353
  import { CanvasRenderer as CanvasRenderer10 } from "echarts/renderers";
1585
- 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";
1586
2363
  echarts12.use([
1587
2364
  EChartsHeatmapChart,
1588
2365
  GridComponent5,
1589
- TooltipComponent9,
2366
+ TooltipComponent10,
1590
2367
  TitleComponent10,
1591
2368
  VisualMapComponent,
1592
2369
  CanvasRenderer10
1593
2370
  ]);
1594
- var HeatmapChart = React14.forwardRef(
2371
+ var HeatmapChart = React15.forwardRef(
1595
2372
  ({
1596
2373
  title,
1597
2374
  description,
@@ -1606,95 +2383,140 @@ var HeatmapChart = React14.forwardRef(
1606
2383
  animation = true,
1607
2384
  showTooltip = true,
1608
2385
  showValues = false,
2386
+ labelFormatter,
2387
+ xAxisConfig,
2388
+ yAxisConfig,
2389
+ tooltipFormatter,
2390
+ tooltipValueFormatter,
2391
+ option: optionOverride,
1609
2392
  onClick,
1610
2393
  class: className,
1611
2394
  ...props
1612
2395
  }, ref) => {
1613
2396
  const isEmpty = !data?.data?.length || !xAxis?.length || !yAxis?.length;
1614
- const values = data?.data?.map(([, , value]) => value) || [];
1615
- const minValue = data?.min ?? Math.min(...values);
1616
- const maxValue = data?.max ?? Math.max(...values);
1617
- const option = React14.useMemo(() => {
2397
+ let dataMin = Infinity;
2398
+ let dataMax = -Infinity;
2399
+ for (const [, , value] of data?.data ?? []) {
2400
+ if (value < dataMin) dataMin = value;
2401
+ if (value > dataMax) dataMax = value;
2402
+ }
2403
+ const minValue = data?.min ?? dataMin;
2404
+ const maxValue = data?.max ?? dataMax;
2405
+ const option = React15.useMemo(() => {
1618
2406
  if (isEmpty) return {};
1619
- return {
1620
- tooltip: showTooltip ? {
1621
- position: "top",
1622
- formatter: (params) => {
1623
- const p = params;
1624
- if (!p.value) return "";
1625
- const [xIdx, yIdx, value] = p.value;
1626
- return `${xAxis[xIdx]} / ${yAxis[yIdx]}: ${value}`;
1627
- }
1628
- } : void 0,
1629
- grid: {
1630
- left: "3%",
1631
- right: "10%",
1632
- bottom: "3%",
1633
- containLabel: true
1634
- },
1635
- xAxis: {
1636
- type: "category",
1637
- data: xAxis,
1638
- splitArea: {
1639
- show: true
1640
- }
1641
- },
1642
- yAxis: {
1643
- type: "category",
1644
- data: yAxis,
1645
- splitArea: {
1646
- show: true
1647
- }
1648
- },
1649
- visualMap: {
1650
- min: minValue,
1651
- max: maxValue,
1652
- calculable: true,
1653
- orient: "vertical",
1654
- right: "0%",
1655
- top: "center"
1656
- },
1657
- series: [
1658
- {
1659
- name: title,
1660
- type: "heatmap",
1661
- data: data.data,
1662
- label: showValues ? {
1663
- show: true
1664
- } : void 0,
1665
- emphasis: {
1666
- itemStyle: {
1667
- shadowBlur: 10,
1668
- 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))}`;
1669
2417
  }
2418
+ },
2419
+ {
2420
+ show: showTooltip,
2421
+ formatter: tooltipFormatter,
2422
+ valueFormatter: tooltipValueFormatter
1670
2423
  }
1671
- }
1672
- ]
1673
- };
1674
- }, [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
+ ]);
2496
+ const latest = useLatestRef({ onClick, xAxis, yAxis });
1675
2497
  const { containerRef } = useChart({
1676
2498
  option,
1677
2499
  theme,
1678
2500
  animation,
1679
2501
  onReady: (chart) => {
1680
- if (onClick) {
1681
- chart.on("click", (params) => {
1682
- const [xIdx, yIdx, value] = params.value || [0, 0, 0];
1683
- onClick({
1684
- type: params.type || "click",
1685
- componentType: params.componentType || "series",
1686
- xIndex: xIdx,
1687
- yIndex: yIdx,
1688
- xLabel: xAxis[xIdx],
1689
- yLabel: yAxis[yIdx],
1690
- value
1691
- });
2502
+ chart.on("click", (params) => {
2503
+ const { onClick: onClick2, xAxis: xAxis2, yAxis: yAxis2 } = latest.current;
2504
+ if (!onClick2) return;
2505
+ const [xIdx, yIdx, value] = params.value || [0, 0, 0];
2506
+ onClick2({
2507
+ type: params.type || "click",
2508
+ componentType: params.componentType || "series",
2509
+ xIndex: xIdx,
2510
+ yIndex: yIdx,
2511
+ xLabel: xAxis2[xIdx],
2512
+ yLabel: yAxis2[yIdx],
2513
+ value
1692
2514
  });
1693
- }
2515
+ });
1694
2516
  }
1695
2517
  });
1696
2518
  const accessibilityDescription = description || (!isEmpty ? `${title}. Heatmap with ${xAxis.length} columns and ${yAxis.length} rows.` : void 0);
1697
- return /* @__PURE__ */ jsx14(
2519
+ return /* @__PURE__ */ jsx15(
1698
2520
  ChartContainer,
1699
2521
  {
1700
2522
  ref,
@@ -1707,7 +2529,7 @@ var HeatmapChart = React14.forwardRef(
1707
2529
  emptyMessage,
1708
2530
  className: cn(className),
1709
2531
  ...props,
1710
- children: /* @__PURE__ */ jsx14("div", { ref: containerRef, className: "h-full w-full" })
2532
+ children: /* @__PURE__ */ jsx15("div", { ref: containerRef, className: "h-full w-full" })
1711
2533
  }
1712
2534
  );
1713
2535
  }
@@ -1715,14 +2537,25 @@ var HeatmapChart = React14.forwardRef(
1715
2537
  HeatmapChart.displayName = "HeatmapChart";
1716
2538
 
1717
2539
  // src/components/extended/TreemapChart.tsx
1718
- import * as React15 from "react";
2540
+ import * as React16 from "react";
1719
2541
  import * as echarts13 from "echarts/core";
1720
2542
  import { TreemapChart as EChartsTreemapChart } from "echarts/charts";
1721
- import { TooltipComponent as TooltipComponent10, TitleComponent as TitleComponent11 } from "echarts/components";
2543
+ import { TooltipComponent as TooltipComponent11, TitleComponent as TitleComponent11 } from "echarts/components";
1722
2544
  import { CanvasRenderer as CanvasRenderer11 } from "echarts/renderers";
1723
- import { jsx as jsx15 } from "react/jsx-runtime";
1724
- echarts13.use([EChartsTreemapChart, TooltipComponent10, TitleComponent11, CanvasRenderer11]);
1725
- 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(
1726
2559
  ({
1727
2560
  title,
1728
2561
  description,
@@ -1735,89 +2568,122 @@ var TreemapChart = React15.forwardRef(
1735
2568
  theme = "auto",
1736
2569
  animation = true,
1737
2570
  showTooltip = true,
2571
+ labelFormatter,
2572
+ tooltipFormatter,
2573
+ tooltipValueFormatter,
2574
+ option: optionOverride,
1738
2575
  onClick,
1739
2576
  class: className,
1740
2577
  ...props
1741
2578
  }, ref) => {
1742
2579
  const isEmpty = !data?.length;
1743
- const option = React15.useMemo(() => {
2580
+ const currentTheme = useChartTheme(theme);
2581
+ const chartColors = getChartColors10(currentTheme);
2582
+ const option = React16.useMemo(() => {
1744
2583
  if (isEmpty) return {};
1745
- return {
1746
- tooltip: showTooltip ? {
1747
- formatter: (params) => {
1748
- const p = params;
1749
- return `${p.name}: ${p.value}`;
1750
- }
1751
- } : void 0,
1752
- series: [
1753
- {
1754
- type: "treemap",
1755
- data,
1756
- roam: false,
1757
- levels: levels || [
1758
- {
1759
- itemStyle: {
1760
- borderColor: "#555",
1761
- borderWidth: 4,
1762
- gapWidth: 4
1763
- }
1764
- },
1765
- {
1766
- colorSaturation: [0.3, 0.6],
1767
- itemStyle: {
1768
- borderColorSaturation: 0.7,
1769
- gapWidth: 2,
1770
- borderWidth: 2
1771
- }
1772
- },
1773
- {
1774
- colorSaturation: [0.3, 0.5],
1775
- itemStyle: {
1776
- borderColorSaturation: 0.6,
1777
- 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]
1778
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
1779
2644
  },
1780
- {
1781
- colorSaturation: [0.3, 0.5]
2645
+ breadcrumb: {
2646
+ show: true
1782
2647
  }
1783
- ],
1784
- label: {
1785
- show: true,
1786
- formatter: "{b}"
1787
- },
1788
- upperLabel: {
1789
- show: true,
1790
- height: 30
1791
- },
1792
- breadcrumb: {
1793
- show: true
1794
2648
  }
1795
- }
1796
- ]
1797
- };
1798
- }, [data, levels, isEmpty, showTooltip]);
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
+ ]);
2664
+ const latest = useLatestRef({ onClick });
1799
2665
  const { containerRef } = useChart({
1800
2666
  option,
1801
- theme,
2667
+ theme: currentTheme === THEME_DARK6 ? "dark" : "light",
1802
2668
  animation,
1803
2669
  onReady: (chart) => {
1804
- if (onClick) {
1805
- chart.on("click", (params) => {
1806
- const treePathInfo = params.treePathInfo || [];
1807
- onClick({
1808
- type: params.type || "click",
1809
- componentType: params.componentType || "series",
1810
- name: params.name || "",
1811
- value: params.value,
1812
- path: treePathInfo.map((item) => item.name || ""),
1813
- depth: treePathInfo.length - 1
1814
- });
2670
+ chart.on("click", (params) => {
2671
+ const { onClick: onClick2 } = latest.current;
2672
+ if (!onClick2) return;
2673
+ const treePathInfo = params.treePathInfo || [];
2674
+ onClick2({
2675
+ type: params.type || "click",
2676
+ componentType: params.componentType || "series",
2677
+ name: params.name || "",
2678
+ value: params.value,
2679
+ path: treePathInfo.map((item) => item.name || ""),
2680
+ depth: treePathInfo.length - 1
1815
2681
  });
1816
- }
2682
+ });
1817
2683
  }
1818
2684
  });
1819
2685
  const accessibilityDescription = description || (!isEmpty ? `${title}. Treemap chart showing hierarchical data.` : void 0);
1820
- return /* @__PURE__ */ jsx15(
2686
+ return /* @__PURE__ */ jsx16(
1821
2687
  ChartContainer,
1822
2688
  {
1823
2689
  ref,
@@ -1830,7 +2696,7 @@ var TreemapChart = React15.forwardRef(
1830
2696
  emptyMessage,
1831
2697
  className: cn(className),
1832
2698
  ...props,
1833
- children: /* @__PURE__ */ jsx15("div", { ref: containerRef, className: "h-full w-full" })
2699
+ children: /* @__PURE__ */ jsx16("div", { ref: containerRef, className: "h-full w-full" })
1834
2700
  }
1835
2701
  );
1836
2702
  }
@@ -1838,7 +2704,7 @@ var TreemapChart = React15.forwardRef(
1838
2704
  TreemapChart.displayName = "TreemapChart";
1839
2705
 
1840
2706
  // src/components/extended/CandlestickChart.tsx
1841
- import * as React16 from "react";
2707
+ import * as React17 from "react";
1842
2708
  import * as echarts14 from "echarts/core";
1843
2709
  import {
1844
2710
  CandlestickChart as EChartsCandlestickChart,
@@ -1846,22 +2712,29 @@ import {
1846
2712
  } from "echarts/charts";
1847
2713
  import {
1848
2714
  GridComponent as GridComponent6,
1849
- TooltipComponent as TooltipComponent11,
2715
+ TooltipComponent as TooltipComponent12,
1850
2716
  TitleComponent as TitleComponent12,
1851
2717
  DataZoomComponent
1852
2718
  } from "echarts/components";
1853
2719
  import { CanvasRenderer as CanvasRenderer12 } from "echarts/renderers";
1854
- 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";
1855
2728
  echarts14.use([
1856
2729
  EChartsCandlestickChart,
1857
2730
  EChartsBarChart2,
1858
2731
  GridComponent6,
1859
- TooltipComponent11,
2732
+ TooltipComponent12,
1860
2733
  TitleComponent12,
1861
2734
  DataZoomComponent,
1862
2735
  CanvasRenderer12
1863
2736
  ]);
1864
- var CandlestickChart = React16.forwardRef(
2737
+ var CandlestickChart = React17.forwardRef(
1865
2738
  ({
1866
2739
  title,
1867
2740
  description,
@@ -1874,12 +2747,19 @@ var CandlestickChart = React16.forwardRef(
1874
2747
  animation = true,
1875
2748
  showTooltip = true,
1876
2749
  showVolume = false,
2750
+ xAxisConfig,
2751
+ yAxisConfig,
2752
+ tooltipFormatter,
2753
+ tooltipValueFormatter,
2754
+ option: optionOverride,
1877
2755
  onClick,
1878
2756
  class: className,
1879
2757
  ...props
1880
2758
  }, ref) => {
1881
2759
  const isEmpty = !data?.dates?.length || !data?.data?.length;
1882
- const option = React16.useMemo(() => {
2760
+ const currentTheme = useChartTheme(theme);
2761
+ const chartColors = getChartColors11(currentTheme);
2762
+ const option = React17.useMemo(() => {
1883
2763
  if (isEmpty) return {};
1884
2764
  const candlestickData = data.data.map((item) => [item.open, item.close, item.low, item.high]);
1885
2765
  const seriesArr = [
@@ -1888,12 +2768,12 @@ var CandlestickChart = React16.forwardRef(
1888
2768
  type: "candlestick",
1889
2769
  data: candlestickData,
1890
2770
  itemStyle: {
1891
- color: "#ef4444",
2771
+ color: chartColors.destructive,
1892
2772
  // Up color (red for bearish in some markets)
1893
- color0: "#22c55e",
2773
+ color0: chartColors.success,
1894
2774
  // Down color (green for bullish)
1895
- borderColor: "#ef4444",
1896
- borderColor0: "#22c55e"
2775
+ borderColor: chartColors.destructive,
2776
+ borderColor0: chartColors.success
1897
2777
  }
1898
2778
  }
1899
2779
  ];
@@ -1906,23 +2786,29 @@ var CandlestickChart = React16.forwardRef(
1906
2786
  }
1907
2787
  ];
1908
2788
  const xAxisArr = [
1909
- {
1910
- type: "category",
1911
- data: data.dates,
1912
- boundaryGap: false,
1913
- axisLine: { onZero: false },
1914
- splitLine: { show: false },
1915
- min: "dataMin",
1916
- max: "dataMax"
1917
- }
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
+ )
1918
2801
  ];
1919
2802
  const yAxisArr = [
1920
- {
1921
- scale: true,
1922
- splitArea: {
1923
- show: true
1924
- }
1925
- }
2803
+ buildAxis6(
2804
+ {
2805
+ scale: true,
2806
+ splitArea: {
2807
+ show: true
2808
+ }
2809
+ },
2810
+ yAxisConfig
2811
+ )
1926
2812
  ];
1927
2813
  if (showVolume && data.volume) {
1928
2814
  gridArr.push({
@@ -1959,62 +2845,86 @@ var CandlestickChart = React16.forwardRef(
1959
2845
  yAxisIndex: 1,
1960
2846
  data: data.volume,
1961
2847
  itemStyle: {
1962
- color: "#6b7280"
2848
+ color: chartColors.mutedForeground
1963
2849
  }
1964
2850
  });
1965
2851
  }
1966
- return {
1967
- tooltip: showTooltip ? {
1968
- trigger: "axis",
1969
- axisPointer: {
1970
- type: "cross"
1971
- }
1972
- } : void 0,
1973
- grid: gridArr,
1974
- xAxis: xAxisArr,
1975
- yAxis: yAxisArr,
1976
- dataZoom: [
1977
- {
1978
- type: "inside",
1979
- start: 80,
1980
- end: 100
1981
- },
1982
- {
1983
- show: true,
1984
- type: "slider",
1985
- top: "90%",
1986
- start: 80,
1987
- end: 100
1988
- }
1989
- ],
1990
- series: seriesArr
1991
- };
1992
- }, [data, isEmpty, showTooltip, showVolume, title]);
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
+ ]);
2901
+ const latest = useLatestRef({ onClick, data });
1993
2902
  const { containerRef } = useChart({
1994
2903
  option,
1995
- theme,
2904
+ theme: currentTheme === THEME_DARK7 ? "dark" : "light",
1996
2905
  animation,
1997
2906
  onReady: (chart) => {
1998
- if (onClick) {
1999
- chart.on("click", (params) => {
2000
- const dataIndex = params.dataIndex || 0;
2001
- const item = data.data[dataIndex];
2002
- onClick({
2003
- type: params.type || "click",
2004
- componentType: params.componentType || "series",
2005
- date: data.dates[dataIndex],
2006
- open: item.open,
2007
- close: item.close,
2008
- high: item.high,
2009
- low: item.low,
2010
- volume: data.volume?.[dataIndex]
2011
- });
2907
+ chart.on("click", (params) => {
2908
+ const { onClick: onClick2, data: data2 } = latest.current;
2909
+ if (!onClick2) return;
2910
+ const dataIndex = params.dataIndex || 0;
2911
+ const item = data2.data[dataIndex];
2912
+ if (!item) return;
2913
+ onClick2({
2914
+ type: params.type || "click",
2915
+ componentType: params.componentType || "series",
2916
+ date: data2.dates[dataIndex],
2917
+ open: item.open,
2918
+ close: item.close,
2919
+ high: item.high,
2920
+ low: item.low,
2921
+ volume: data2.volume?.[dataIndex]
2012
2922
  });
2013
- }
2923
+ });
2014
2924
  }
2015
2925
  });
2016
2926
  const accessibilityDescription = description || (!isEmpty ? `${title}. Candlestick chart with ${data.dates.length} data points.` : void 0);
2017
- return /* @__PURE__ */ jsx16(
2927
+ return /* @__PURE__ */ jsx17(
2018
2928
  ChartContainer,
2019
2929
  {
2020
2930
  ref,
@@ -2027,7 +2937,7 @@ var CandlestickChart = React16.forwardRef(
2027
2937
  emptyMessage,
2028
2938
  className: cn(className),
2029
2939
  ...props,
2030
- children: /* @__PURE__ */ jsx16("div", { ref: containerRef, className: "h-full w-full" })
2940
+ children: /* @__PURE__ */ jsx17("div", { ref: containerRef, className: "h-full w-full" })
2031
2941
  }
2032
2942
  );
2033
2943
  }
@@ -2035,14 +2945,21 @@ var CandlestickChart = React16.forwardRef(
2035
2945
  CandlestickChart.displayName = "CandlestickChart";
2036
2946
 
2037
2947
  // src/components/extended/SankeyChart.tsx
2038
- import * as React17 from "react";
2948
+ import * as React18 from "react";
2039
2949
  import * as echarts15 from "echarts/core";
2040
2950
  import { SankeyChart as EChartsSankeyChart } from "echarts/charts";
2041
- import { TooltipComponent as TooltipComponent12, TitleComponent as TitleComponent13 } from "echarts/components";
2951
+ import { TooltipComponent as TooltipComponent13, TitleComponent as TitleComponent13 } from "echarts/components";
2042
2952
  import { CanvasRenderer as CanvasRenderer13 } from "echarts/renderers";
2043
- import { jsx as jsx17 } from "react/jsx-runtime";
2044
- echarts15.use([EChartsSankeyChart, TooltipComponent12, TitleComponent13, CanvasRenderer13]);
2045
- 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(
2046
2963
  ({
2047
2964
  title,
2048
2965
  description,
@@ -2055,68 +2972,97 @@ var SankeyChart = React17.forwardRef(
2055
2972
  animation = true,
2056
2973
  showTooltip = true,
2057
2974
  orient = "horizontal",
2975
+ labelFormatter,
2976
+ tooltipFormatter,
2977
+ tooltipValueFormatter,
2978
+ option: optionOverride,
2058
2979
  onClick,
2059
2980
  class: className,
2060
2981
  ...props
2061
2982
  }, ref) => {
2062
2983
  const isEmpty = !data?.nodes?.length || !data?.links?.length;
2063
- const option = React17.useMemo(() => {
2984
+ const option = React18.useMemo(() => {
2064
2985
  if (isEmpty) return {};
2065
- return {
2066
- tooltip: showTooltip ? {
2067
- trigger: "item",
2068
- triggerOn: "mousemove"
2069
- } : void 0,
2070
- series: [
2071
- {
2072
- type: "sankey",
2073
- orient,
2074
- emphasis: {
2075
- focus: "adjacency"
2076
- },
2077
- data: data.nodes.map((node) => ({
2078
- name: node.name,
2079
- itemStyle: node.color ? { color: node.color } : void 0
2080
- })),
2081
- links: data.links.map((link) => ({
2082
- source: link.source,
2083
- target: link.target,
2084
- value: link.value
2085
- })),
2086
- lineStyle: {
2087
- color: "gradient",
2088
- curveness: 0.5
2986
+ return mergeChartOption13(
2987
+ {
2988
+ tooltip: buildTooltip13(
2989
+ {
2990
+ trigger: "item",
2991
+ triggerOn: "mousemove"
2089
2992
  },
2090
- label: {
2091
- position: orient === "horizontal" ? "right" : "bottom"
2993
+ {
2994
+ show: showTooltip,
2995
+ formatter: tooltipFormatter,
2996
+ valueFormatter: tooltipValueFormatter
2092
2997
  }
2093
- }
2094
- ]
2095
- };
2096
- }, [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
+ ]);
3042
+ const latest = useLatestRef({ onClick });
2097
3043
  const { containerRef } = useChart({
2098
3044
  option,
2099
3045
  theme,
2100
3046
  animation,
2101
3047
  onReady: (chart) => {
2102
- if (onClick) {
2103
- chart.on("click", (params) => {
2104
- const isEdge = params.dataType === "edge";
2105
- onClick({
2106
- type: params.type || "click",
2107
- componentType: params.componentType || "series",
2108
- dataType: isEdge ? "edge" : "node",
2109
- name: params.name || "",
2110
- value: isEdge ? params.value : void 0,
2111
- source: isEdge ? params.data?.source : void 0,
2112
- target: isEdge ? params.data?.target : void 0
2113
- });
3048
+ chart.on("click", (params) => {
3049
+ const { onClick: onClick2 } = latest.current;
3050
+ if (!onClick2) return;
3051
+ const isEdge = params.dataType === "edge";
3052
+ onClick2({
3053
+ type: params.type || "click",
3054
+ componentType: params.componentType || "series",
3055
+ dataType: isEdge ? "edge" : "node",
3056
+ name: params.name || "",
3057
+ value: isEdge ? params.value : void 0,
3058
+ source: isEdge ? params.data?.source : void 0,
3059
+ target: isEdge ? params.data?.target : void 0
2114
3060
  });
2115
- }
3061
+ });
2116
3062
  }
2117
3063
  });
2118
3064
  const accessibilityDescription = description || (!isEmpty ? `${title}. Sankey diagram with ${data.nodes.length} nodes and ${data.links.length} links.` : void 0);
2119
- return /* @__PURE__ */ jsx17(
3065
+ return /* @__PURE__ */ jsx18(
2120
3066
  ChartContainer,
2121
3067
  {
2122
3068
  ref,
@@ -2129,7 +3075,7 @@ var SankeyChart = React17.forwardRef(
2129
3075
  emptyMessage,
2130
3076
  className: cn(className),
2131
3077
  ...props,
2132
- children: /* @__PURE__ */ jsx17("div", { ref: containerRef, className: "h-full w-full" })
3078
+ children: /* @__PURE__ */ jsx18("div", { ref: containerRef, className: "h-full w-full" })
2133
3079
  }
2134
3080
  );
2135
3081
  }
@@ -2142,6 +3088,7 @@ export {
2142
3088
  ChartContainer,
2143
3089
  ChartEmpty,
2144
3090
  ChartError,
3091
+ ChartLegend,
2145
3092
  ChartSkeleton,
2146
3093
  DonutChart,
2147
3094
  FunnelChart,
@@ -2156,6 +3103,7 @@ export {
2156
3103
  cn,
2157
3104
  registerClassicThemes,
2158
3105
  useChart,
3106
+ useChartLegend,
2159
3107
  useChartResize,
2160
3108
  useChartTheme,
2161
3109
  useReducedMotion