@trackunit/react-chart-components 2.1.43-alpha-6a85feaada3.0 → 2.1.43

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/index.cjs.js CHANGED
@@ -1,14 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  var jsxRuntime = require('react/jsx-runtime');
4
- var reactComponents = require('@trackunit/react-components');
5
4
  var cssClassVarianceUtilities = require('@trackunit/css-class-variance-utilities');
6
- var sharedUtils = require('@trackunit/shared-utils');
7
- var echarts = require('echarts');
8
- var react = require('react');
9
- var uiDesignTokens = require('@trackunit/ui-design-tokens');
10
5
  var dateAndTimeUtils = require('@trackunit/date-and-time-utils');
11
6
  var reactDateAndTimeHooks = require('@trackunit/react-date-and-time-hooks');
7
+ var uiDesignTokens = require('@trackunit/ui-design-tokens');
8
+ var react = require('react');
9
+ var reactComponents = require('@trackunit/react-components');
10
+ var sharedUtils = require('@trackunit/shared-utils');
11
+ var echarts = require('echarts');
12
12
 
13
13
  function _interopNamespaceDefault(e) {
14
14
  var n = Object.create(null);
@@ -303,6 +303,241 @@ const useChartColor = () => {
303
303
  }), [chartColor, chartColorArray, chartStatusColor]);
304
304
  };
305
305
 
306
+ /**
307
+ * Locale-aware number formatting helpers for charts. Mirrors `useChartColor`:
308
+ * a hook that returns memoized formatting functions so chart consumers share one
309
+ * consistent compact-number style instead of hand-rolling `>= 1000 → k` logic.
310
+ *
311
+ * @returns {{ formatCompact: (value: number) => string }} An object exposing
312
+ * `formatCompact`, a locale-aware compact formatter (e.g. `40000 → "40K"`, `40_000_000 → "40M"`).
313
+ * @example Abbreviate a y-axis value
314
+ * ```tsx
315
+ * const { formatCompact } = useChartNumberFormat();
316
+ * formatCompact(40000); // "40K"
317
+ * ```
318
+ */
319
+ const useChartNumberFormat = () => {
320
+ const locale = reactDateAndTimeHooks.useLocale();
321
+ const formatCompact = react.useCallback((value) => new Intl.NumberFormat(locale, { notation: "compact", maximumFractionDigits: 1 }).format(value), [locale]);
322
+ return react.useMemo(() => ({ formatCompact }), [formatCompact]);
323
+ };
324
+
325
+ const DEFAULT_DATE_FORMAT = { selectFormat: "dateOnly", dateFormat: "medium" };
326
+ const DEFAULT_ROTATE_LABELS = 45;
327
+ const isDateData = (data) => !!data && "date" in data;
328
+ const isEChartsTooltipParam = (param) => typeof param === "object" &&
329
+ param !== null &&
330
+ "seriesName" in param &&
331
+ typeof param.seriesName === "string" &&
332
+ "seriesIndex" in param &&
333
+ typeof param.seriesIndex === "number" &&
334
+ "dataIndex" in param &&
335
+ typeof param.dataIndex === "number" &&
336
+ "color" in param &&
337
+ typeof param.color === "string" &&
338
+ "marker" in param &&
339
+ typeof param.marker === "string";
340
+ /**
341
+ * Create a BarChart with automatic legends and formatting. Built on top of the Chart component
342
+ * with sensible defaults for displaying categorical or time-series data.
343
+ *
344
+ * All customization props are optional and default to the component's original rendering, so
345
+ * existing usages are unaffected.
346
+ *
347
+ * ### When to use
348
+ * - To compare values across categories
349
+ * - To show trends over time with discrete intervals
350
+ * - When you need multiple series displayed side by side
351
+ *
352
+ * @example Bar chart with date-based data
353
+ * ```tsx
354
+ * import { BarChart } from "@trackunit/react-chart-components";
355
+ *
356
+ * const UtilizationChart = () => (
357
+ * <BarChart
358
+ * series={{
359
+ * name: "Utilization",
360
+ * data: [
361
+ * { date: "2024-01-01", value: 85 },
362
+ * { date: "2024-01-02", value: 72 },
363
+ * { date: "2024-01-03", value: 91 },
364
+ * ],
365
+ * }}
366
+ * units="%"
367
+ * onClick={(event) => console.log("Clicked bar:", event.data)}
368
+ * />
369
+ * );
370
+ * ```
371
+ * @example Multi-series bar chart with data zoom
372
+ * ```tsx
373
+ * import { BarChart } from "@trackunit/react-chart-components";
374
+ *
375
+ * const ComparisonChart = () => (
376
+ * <BarChart
377
+ * series={[
378
+ * { name: "2023", color: "#3b82f6", data: [{ key: "Q1", value: 100 }, { key: "Q2", value: 120 }] },
379
+ * { name: "2024", color: "#10b981", data: [{ key: "Q1", value: 130 }, { key: "Q2", value: 145 }] },
380
+ * ]}
381
+ * units="units"
382
+ * showDataZoom={true}
383
+ * />
384
+ * );
385
+ * ```
386
+ * @example Abbreviated y-axis, horizontal x-axis labels and a hidden legend
387
+ * ```tsx
388
+ * import { BarChart } from "@trackunit/react-chart-components";
389
+ *
390
+ * const EmissionsChart = () => (
391
+ * <BarChart
392
+ * series={{ name: "Emissions", data: [{ key: "Excavator", value: 40000, color: "#3b82f6" }] }}
393
+ * units="t"
394
+ * yAxis={{ abbreviate: true }}
395
+ * xAxis={{ rotateLabels: 0, formatLabel: (value) => value.slice(0, 10) }}
396
+ * showLegend={false}
397
+ * />
398
+ * );
399
+ * ```
400
+ * @example Custom tooltip from the typed payload
401
+ * ```tsx
402
+ * import { BarChart } from "@trackunit/react-chart-components";
403
+ *
404
+ * const TrendChart = () => (
405
+ * <BarChart
406
+ * series={{ name: "Emissions", data: [{ date: "2024-01-01", value: 120 }] }}
407
+ * tooltip={{
408
+ * trigger: "axis",
409
+ * formatter: ({ axisValue, entries }) =>
410
+ * `${axisValue}<br/>${entries.map((e) => `${e.marker} ${e.seriesName}: ${e.value ?? 0}`).join("<br/>")}`,
411
+ * }}
412
+ * />
413
+ * );
414
+ * ```
415
+ * @param {BarChartProps} props - The props for the Chart component
416
+ * @returns {ReactElement} Chart component
417
+ */
418
+ const BarChart = ({ series, loading = false, onClick, className, style, "data-testid": dataTestId, units, showDataZoom = false, xAxis, yAxis, tooltip, showLegend = true, ref, }) => {
419
+ const { formatDate } = reactDateAndTimeHooks.useDateAndTime();
420
+ const { chartColor } = useChartColor();
421
+ const { formatCompact } = useChartNumberFormat();
422
+ const seriesData = react.useMemo(() => {
423
+ const seriesAsArray = Array.isArray(series) ? series : series !== undefined ? [series] : [];
424
+ return seriesAsArray.map((s, i) => ({
425
+ ...s,
426
+ color: s.color || chartColor(i),
427
+ type: "bar",
428
+ cursor: "arrow",
429
+ barGap: "0",
430
+ data: s.data.map(d => ({
431
+ ...d,
432
+ itemStyle: d.color ? { color: d.color } : undefined,
433
+ })),
434
+ }));
435
+ }, [series, chartColor]);
436
+ const yAxisLabelFormatter = react.useMemo(() => {
437
+ if (yAxis?.formatLabel) {
438
+ return yAxis.formatLabel;
439
+ }
440
+ if (yAxis?.abbreviate) {
441
+ return (value) => formatCompact(value);
442
+ }
443
+ return undefined;
444
+ }, [yAxis, formatCompact]);
445
+ const buildTooltipParams = react.useCallback((rawParams) => {
446
+ const params = Array.isArray(rawParams) ? rawParams : [rawParams];
447
+ const validParams = params.filter(isEChartsTooltipParam);
448
+ const firstParam = validParams[0];
449
+ const axisValueRaw = firstParam?.axisValue ?? firstParam?.name;
450
+ const axisValue = axisValueRaw === undefined ? "" : String(axisValueRaw);
451
+ const entries = validParams.map(param => {
452
+ const dataItem = seriesData[param.seriesIndex]?.data[param.dataIndex];
453
+ return {
454
+ seriesName: param.seriesName,
455
+ value: dataItem?.value,
456
+ color: param.color,
457
+ marker: param.marker,
458
+ dataIndex: param.dataIndex,
459
+ original: dataItem?.original,
460
+ };
461
+ });
462
+ return { axisValue, entries };
463
+ }, [seriesData]);
464
+ const tooltipTrigger = tooltip?.trigger ?? "item";
465
+ const tooltipFormatter = tooltip?.formatter;
466
+ const tooltipValueFormatter = tooltip?.formatValue;
467
+ return (jsxRuntime.jsxs("div", { className: cvaChartRoot$1({ className }), "data-testid": dataTestId, ref: ref, style: style, children: [showLegend ? (jsxRuntime.jsx("div", { className: "flex-0 flex flex-row gap-2 place-self-end pr-8", "data-testid": "legend", children: seriesData.map((item, index) => (jsxRuntime.jsx(LegendItem, { color: item.color || chartColor(index), "data-testid": `legend-${item.name}`, label: item.name }, item.name))) })) : null, jsxRuntime.jsx("div", { className: "flex w-full flex-1", children: jsxRuntime.jsx(Chart, { "data-testid": "bar-chart", onClick: onClick
468
+ ? (e) => {
469
+ onClick(e);
470
+ }
471
+ : undefined, options: {
472
+ tooltip: {
473
+ trigger: tooltipTrigger,
474
+ confine: true,
475
+ axisPointer: tooltipTrigger === "axis" ? { type: "shadow" } : undefined,
476
+ formatter: tooltipFormatter
477
+ ? (rawParams) => tooltipFormatter(buildTooltipParams(rawParams))
478
+ : undefined,
479
+ valueFormatter: tooltipFormatter
480
+ ? undefined
481
+ : value => tooltipValueFormatter && typeof value === "number"
482
+ ? tooltipValueFormatter(value)
483
+ : `${value} ${units ?? ""}`,
484
+ },
485
+ grid: {
486
+ left: "2%",
487
+ right: "2%",
488
+ bottom: "10%",
489
+ top: "10%",
490
+ containLabel: true,
491
+ },
492
+ xAxis: [
493
+ {
494
+ type: "category",
495
+ axisLabel: {
496
+ formatter: (value, index) => {
497
+ if (xAxis?.formatLabel) {
498
+ return xAxis.formatLabel(value, index);
499
+ }
500
+ if (isDateData(seriesData[0]?.data[0])) {
501
+ const date = formatDate(dateAndTimeUtils.toDateUtil(value), xAxis?.dateFormat ?? DEFAULT_DATE_FORMAT);
502
+ return date.replace(",", ",\n");
503
+ }
504
+ return value;
505
+ },
506
+ fontSize: 10,
507
+ fontFamily: uiDesignTokens.fontFamily,
508
+ rotate: xAxis?.rotateLabels ?? DEFAULT_ROTATE_LABELS,
509
+ overflow: "truncate",
510
+ },
511
+ data: seriesData[0]?.data.map(data => (isDateData(data) ? data.date : data.key)),
512
+ },
513
+ ],
514
+ dataZoom: showDataZoom
515
+ ? [
516
+ {
517
+ type: "slider",
518
+ id: "insideX",
519
+ xAxisIndex: 0,
520
+ startValue: 0,
521
+ endValue: (seriesData[0]?.data.length ?? 0) > 10 ? 10 : (seriesData[0]?.data.length ?? 0),
522
+ showDetail: false,
523
+ },
524
+ ]
525
+ : [],
526
+ yAxis: [
527
+ {
528
+ type: "value",
529
+ axisLabel: {
530
+ fontSize: 10,
531
+ fontFamily: uiDesignTokens.fontFamily,
532
+ formatter: yAxisLabelFormatter,
533
+ },
534
+ },
535
+ ],
536
+ series: seriesData,
537
+ }, showLoading: loading, style: { height: "100%", minHeight: "150px", width: "100%" } }) })] }));
538
+ };
539
+ const cvaChartRoot$1 = cssClassVarianceUtilities.cvaMerge(["flex", "flex-col", "items-center", "h-full"]);
540
+
306
541
  /**
307
542
  * Limits the data set to the given limit.
308
543
  * If the data set is larger than the limit, the data set is limited to the limit and the rest of the data is added to the "Others" group.
@@ -508,9 +743,9 @@ const DonutChart = ({ data, size = "full", loading = false, onClick, className,
508
743
  if (loading) {
509
744
  return jsxRuntime.jsx(reactComponents.Spinner, { centering: "centered", "data-testid": dataTestId ? `${dataTestId}-loading` : "donut-chart-loading" });
510
745
  }
511
- return (jsxRuntime.jsxs("div", { className: cvaChartRoot$1({ className }), "data-testid": dataTestId, ref: mergedRef, style: style, children: [jsxRuntime.jsx("div", { className: cvaChartContainer(), children: jsxRuntime.jsx(Chart, { className: cvaChart({ size }), "data-testid": dataTestId ? `chart-${dataTestId}` : undefined, onChartReady: handleChartReady, onClick: handleChartClick, onEvents: handleChartEvents, options: chartOptions, style: { width: "100%", height: "100%" } }) }), size === "full" && (jsxRuntime.jsx("div", { className: cvaLegend(), "data-testid": "legend", children: limitedData.map((item, index) => (jsxRuntime.jsx(LegendItem, { className: "p-1.5 py-0.5", color: item.color ?? chartColor(index), count: item.value, "data-testid": `legend-${item.id}`, disabled: (item.value ?? 0) === 0, hideValue: hideLegendValues, label: item.name, onClick: onClick ? () => onClick(item) : undefined, onMouseEnter: () => handleLegendMouseEnter(item), onMouseLeave: handleLegendMouseLeave, selected: item.selected, unit: unit }, item.id))) }))] }));
746
+ return (jsxRuntime.jsxs("div", { className: cvaChartRoot({ className }), "data-testid": dataTestId, ref: mergedRef, style: style, children: [jsxRuntime.jsx("div", { className: cvaChartContainer(), children: jsxRuntime.jsx(Chart, { className: cvaChart({ size }), "data-testid": dataTestId ? `chart-${dataTestId}` : undefined, onChartReady: handleChartReady, onClick: handleChartClick, onEvents: handleChartEvents, options: chartOptions, style: { width: "100%", height: "100%" } }) }), size === "full" && (jsxRuntime.jsx("div", { className: cvaLegend(), "data-testid": "legend", children: limitedData.map((item, index) => (jsxRuntime.jsx(LegendItem, { className: "p-1.5 py-0.5", color: item.color ?? chartColor(index), count: item.value, "data-testid": `legend-${item.id}`, disabled: (item.value ?? 0) === 0, hideValue: hideLegendValues, label: item.name, onClick: onClick ? () => onClick(item) : undefined, onMouseEnter: () => handleLegendMouseEnter(item), onMouseLeave: handleLegendMouseLeave, selected: item.selected, unit: unit }, item.id))) }))] }));
512
747
  };
513
- const cvaChartRoot$1 = cssClassVarianceUtilities.cvaMerge([
748
+ const cvaChartRoot = cssClassVarianceUtilities.cvaMerge([
514
749
  "flex",
515
750
  "w-full",
516
751
  "h-full",
@@ -533,139 +768,10 @@ const cvaChart = cssClassVarianceUtilities.cvaMerge(["flex-0", "max-w-[200px]",
533
768
  });
534
769
  const cvaLegend = cssClassVarianceUtilities.cvaMerge(["flex", "overflow-auto", "justify-start", "flex-col", "flex-1"]);
535
770
 
536
- /**
537
- * Create a BarChart with automatic legends and formatting. Built on top of the Chart component
538
- * with sensible defaults for displaying categorical or time-series data.
539
- *
540
- * ### When to use
541
- * - To compare values across categories
542
- * - To show trends over time with discrete intervals
543
- * - When you need multiple series displayed side by side
544
- *
545
- * @example Bar chart with date-based data
546
- * ```tsx
547
- * import { BarChart } from "@trackunit/react-chart-components";
548
- *
549
- * const UtilizationChart = () => (
550
- * <BarChart
551
- * series={{
552
- * name: "Utilization",
553
- * data: [
554
- * { date: "2024-01-01", value: 85 },
555
- * { date: "2024-01-02", value: 72 },
556
- * { date: "2024-01-03", value: 91 },
557
- * ],
558
- * }}
559
- * units="%"
560
- * onClick={(event) => console.log("Clicked bar:", event.data)}
561
- * />
562
- * );
563
- * ```
564
- * @example Multi-series bar chart with data zoom
565
- * ```tsx
566
- * import { BarChart } from "@trackunit/react-chart-components";
567
- *
568
- * const ComparisonChart = () => (
569
- * <BarChart
570
- * series={[
571
- * { name: "2023", color: "#3b82f6", data: [{ key: "Q1", value: 100 }, { key: "Q2", value: 120 }] },
572
- * { name: "2024", color: "#10b981", data: [{ key: "Q1", value: 130 }, { key: "Q2", value: 145 }] },
573
- * ]}
574
- * units="units"
575
- * showDataZoom={true}
576
- * />
577
- * );
578
- * ```
579
- * @param {BarChartProps} props - The props for the Chart component
580
- * @returns {ReactElement} Chart component
581
- */
582
- const BarChart = ({ series, loading = false, onClick, className, style, "data-testid": dataTestId, units, showDataZoom = false, ref, }) => {
583
- const { formatDate } = reactDateAndTimeHooks.useDateAndTime();
584
- const { chartColor } = useChartColor();
585
- const seriesData = react.useMemo(() => {
586
- const seriesAsArray = Array.isArray(series) ? series : series !== undefined ? [series] : [];
587
- return seriesAsArray.map((s, i) => ({
588
- ...s,
589
- color: s.color || chartColor(i),
590
- type: "bar",
591
- cursor: "arrow",
592
- barGap: "0",
593
- data: s.data.map(d => ({
594
- ...d,
595
- })),
596
- }));
597
- }, [series, chartColor]);
598
- const isDateData = (data) => !!data && "date" in data;
599
- return (jsxRuntime.jsxs("div", { className: cvaChartRoot({ className }), "data-testid": dataTestId, ref: ref, style: style, children: [jsxRuntime.jsx("div", { className: "flex-0 flex flex-row gap-2 place-self-end pr-8", "data-testid": "legend", children: seriesData.map((item, index) => (jsxRuntime.jsx(LegendItem, { color: item.color || chartColor(index), "data-testid": `legend-${item.name}`, label: item.name }, item.name))) }), jsxRuntime.jsx("div", { className: "flex w-full flex-1", children: jsxRuntime.jsx(Chart, { "data-testid": "bar-chart", onClick: onClick
600
- ? (e) => {
601
- onClick(e);
602
- }
603
- : undefined, options: {
604
- tooltip: {
605
- trigger: "item",
606
- confine: true,
607
- valueFormatter: value => `${value} ${units ?? ""}`,
608
- },
609
- grid: {
610
- left: "2%",
611
- right: "2%",
612
- bottom: "10%",
613
- top: "10%",
614
- containLabel: true,
615
- },
616
- xAxis: [
617
- {
618
- type: "category",
619
- axisLabel: {
620
- formatter: value => {
621
- if (isDateData(seriesData[0]?.data[0])) {
622
- const date = formatDate(dateAndTimeUtils.toDateUtil(value), {
623
- selectFormat: "dateOnly",
624
- dateFormat: "medium",
625
- });
626
- return date.replace(",", ",\n");
627
- }
628
- return value;
629
- },
630
- fontSize: 10,
631
- fontFamily: uiDesignTokens.fontFamily,
632
- rotate: 45,
633
- overflow: "truncate",
634
- },
635
- data: seriesData[0]?.data
636
- .filter(({ value }) => Boolean(value))
637
- .map(data => (isDateData(data) ? data.date : data.key)),
638
- },
639
- ],
640
- dataZoom: showDataZoom
641
- ? [
642
- {
643
- type: "slider",
644
- id: "insideX",
645
- xAxisIndex: 0,
646
- startValue: 0,
647
- endValue: (seriesData[0]?.data.length ?? 0) > 10 ? 10 : (seriesData[0]?.data.length ?? 0),
648
- showDetail: false,
649
- },
650
- ]
651
- : [],
652
- yAxis: [
653
- {
654
- type: "value",
655
- axisLabel: {
656
- fontSize: 10,
657
- fontFamily: uiDesignTokens.fontFamily,
658
- },
659
- },
660
- ],
661
- series: seriesData,
662
- }, showLoading: loading, style: { height: "100%", minHeight: "150px", width: "100%" } }) })] }));
663
- };
664
- const cvaChartRoot = cssClassVarianceUtilities.cvaMerge(["flex", "flex-col", "items-center", "h-full"]);
665
-
666
771
  exports.BarChart = BarChart;
667
772
  exports.Chart = Chart;
668
773
  exports.DonutChart = DonutChart;
669
774
  exports.EChart = EChart;
670
775
  exports.LegendItem = LegendItem;
671
776
  exports.useChartColor = useChartColor;
777
+ exports.useChartNumberFormat = useChartNumberFormat;
package/index.esm.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
- import { Spinner, Text, useMergeRefs } from '@trackunit/react-components';
3
2
  import { cvaMerge } from '@trackunit/css-class-variance-utilities';
3
+ import { toDateUtil } from '@trackunit/date-and-time-utils';
4
+ import { useLocale, useDateAndTime } from '@trackunit/react-date-and-time-hooks';
5
+ import { DEFAULT_CHART_COLORS, CHART_STATUS_COLORS, fontFamily, DEFAULT_CHART_OTHER } from '@trackunit/ui-design-tokens';
6
+ import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
7
+ import { Spinner, Text, useMergeRefs } from '@trackunit/react-components';
4
8
  import { objectKeys } from '@trackunit/shared-utils';
5
9
  import * as echarts from 'echarts';
6
- import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
7
- import { DEFAULT_CHART_COLORS, CHART_STATUS_COLORS, DEFAULT_CHART_OTHER, fontFamily } from '@trackunit/ui-design-tokens';
8
- import { toDateUtil } from '@trackunit/date-and-time-utils';
9
- import { useDateAndTime } from '@trackunit/react-date-and-time-hooks';
10
10
 
11
11
  function isECElementEvent(value) {
12
12
  return (typeof value === "object" &&
@@ -282,6 +282,241 @@ const useChartColor = () => {
282
282
  }), [chartColor, chartColorArray, chartStatusColor]);
283
283
  };
284
284
 
285
+ /**
286
+ * Locale-aware number formatting helpers for charts. Mirrors `useChartColor`:
287
+ * a hook that returns memoized formatting functions so chart consumers share one
288
+ * consistent compact-number style instead of hand-rolling `>= 1000 → k` logic.
289
+ *
290
+ * @returns {{ formatCompact: (value: number) => string }} An object exposing
291
+ * `formatCompact`, a locale-aware compact formatter (e.g. `40000 → "40K"`, `40_000_000 → "40M"`).
292
+ * @example Abbreviate a y-axis value
293
+ * ```tsx
294
+ * const { formatCompact } = useChartNumberFormat();
295
+ * formatCompact(40000); // "40K"
296
+ * ```
297
+ */
298
+ const useChartNumberFormat = () => {
299
+ const locale = useLocale();
300
+ const formatCompact = useCallback((value) => new Intl.NumberFormat(locale, { notation: "compact", maximumFractionDigits: 1 }).format(value), [locale]);
301
+ return useMemo(() => ({ formatCompact }), [formatCompact]);
302
+ };
303
+
304
+ const DEFAULT_DATE_FORMAT = { selectFormat: "dateOnly", dateFormat: "medium" };
305
+ const DEFAULT_ROTATE_LABELS = 45;
306
+ const isDateData = (data) => !!data && "date" in data;
307
+ const isEChartsTooltipParam = (param) => typeof param === "object" &&
308
+ param !== null &&
309
+ "seriesName" in param &&
310
+ typeof param.seriesName === "string" &&
311
+ "seriesIndex" in param &&
312
+ typeof param.seriesIndex === "number" &&
313
+ "dataIndex" in param &&
314
+ typeof param.dataIndex === "number" &&
315
+ "color" in param &&
316
+ typeof param.color === "string" &&
317
+ "marker" in param &&
318
+ typeof param.marker === "string";
319
+ /**
320
+ * Create a BarChart with automatic legends and formatting. Built on top of the Chart component
321
+ * with sensible defaults for displaying categorical or time-series data.
322
+ *
323
+ * All customization props are optional and default to the component's original rendering, so
324
+ * existing usages are unaffected.
325
+ *
326
+ * ### When to use
327
+ * - To compare values across categories
328
+ * - To show trends over time with discrete intervals
329
+ * - When you need multiple series displayed side by side
330
+ *
331
+ * @example Bar chart with date-based data
332
+ * ```tsx
333
+ * import { BarChart } from "@trackunit/react-chart-components";
334
+ *
335
+ * const UtilizationChart = () => (
336
+ * <BarChart
337
+ * series={{
338
+ * name: "Utilization",
339
+ * data: [
340
+ * { date: "2024-01-01", value: 85 },
341
+ * { date: "2024-01-02", value: 72 },
342
+ * { date: "2024-01-03", value: 91 },
343
+ * ],
344
+ * }}
345
+ * units="%"
346
+ * onClick={(event) => console.log("Clicked bar:", event.data)}
347
+ * />
348
+ * );
349
+ * ```
350
+ * @example Multi-series bar chart with data zoom
351
+ * ```tsx
352
+ * import { BarChart } from "@trackunit/react-chart-components";
353
+ *
354
+ * const ComparisonChart = () => (
355
+ * <BarChart
356
+ * series={[
357
+ * { name: "2023", color: "#3b82f6", data: [{ key: "Q1", value: 100 }, { key: "Q2", value: 120 }] },
358
+ * { name: "2024", color: "#10b981", data: [{ key: "Q1", value: 130 }, { key: "Q2", value: 145 }] },
359
+ * ]}
360
+ * units="units"
361
+ * showDataZoom={true}
362
+ * />
363
+ * );
364
+ * ```
365
+ * @example Abbreviated y-axis, horizontal x-axis labels and a hidden legend
366
+ * ```tsx
367
+ * import { BarChart } from "@trackunit/react-chart-components";
368
+ *
369
+ * const EmissionsChart = () => (
370
+ * <BarChart
371
+ * series={{ name: "Emissions", data: [{ key: "Excavator", value: 40000, color: "#3b82f6" }] }}
372
+ * units="t"
373
+ * yAxis={{ abbreviate: true }}
374
+ * xAxis={{ rotateLabels: 0, formatLabel: (value) => value.slice(0, 10) }}
375
+ * showLegend={false}
376
+ * />
377
+ * );
378
+ * ```
379
+ * @example Custom tooltip from the typed payload
380
+ * ```tsx
381
+ * import { BarChart } from "@trackunit/react-chart-components";
382
+ *
383
+ * const TrendChart = () => (
384
+ * <BarChart
385
+ * series={{ name: "Emissions", data: [{ date: "2024-01-01", value: 120 }] }}
386
+ * tooltip={{
387
+ * trigger: "axis",
388
+ * formatter: ({ axisValue, entries }) =>
389
+ * `${axisValue}<br/>${entries.map((e) => `${e.marker} ${e.seriesName}: ${e.value ?? 0}`).join("<br/>")}`,
390
+ * }}
391
+ * />
392
+ * );
393
+ * ```
394
+ * @param {BarChartProps} props - The props for the Chart component
395
+ * @returns {ReactElement} Chart component
396
+ */
397
+ const BarChart = ({ series, loading = false, onClick, className, style, "data-testid": dataTestId, units, showDataZoom = false, xAxis, yAxis, tooltip, showLegend = true, ref, }) => {
398
+ const { formatDate } = useDateAndTime();
399
+ const { chartColor } = useChartColor();
400
+ const { formatCompact } = useChartNumberFormat();
401
+ const seriesData = useMemo(() => {
402
+ const seriesAsArray = Array.isArray(series) ? series : series !== undefined ? [series] : [];
403
+ return seriesAsArray.map((s, i) => ({
404
+ ...s,
405
+ color: s.color || chartColor(i),
406
+ type: "bar",
407
+ cursor: "arrow",
408
+ barGap: "0",
409
+ data: s.data.map(d => ({
410
+ ...d,
411
+ itemStyle: d.color ? { color: d.color } : undefined,
412
+ })),
413
+ }));
414
+ }, [series, chartColor]);
415
+ const yAxisLabelFormatter = useMemo(() => {
416
+ if (yAxis?.formatLabel) {
417
+ return yAxis.formatLabel;
418
+ }
419
+ if (yAxis?.abbreviate) {
420
+ return (value) => formatCompact(value);
421
+ }
422
+ return undefined;
423
+ }, [yAxis, formatCompact]);
424
+ const buildTooltipParams = useCallback((rawParams) => {
425
+ const params = Array.isArray(rawParams) ? rawParams : [rawParams];
426
+ const validParams = params.filter(isEChartsTooltipParam);
427
+ const firstParam = validParams[0];
428
+ const axisValueRaw = firstParam?.axisValue ?? firstParam?.name;
429
+ const axisValue = axisValueRaw === undefined ? "" : String(axisValueRaw);
430
+ const entries = validParams.map(param => {
431
+ const dataItem = seriesData[param.seriesIndex]?.data[param.dataIndex];
432
+ return {
433
+ seriesName: param.seriesName,
434
+ value: dataItem?.value,
435
+ color: param.color,
436
+ marker: param.marker,
437
+ dataIndex: param.dataIndex,
438
+ original: dataItem?.original,
439
+ };
440
+ });
441
+ return { axisValue, entries };
442
+ }, [seriesData]);
443
+ const tooltipTrigger = tooltip?.trigger ?? "item";
444
+ const tooltipFormatter = tooltip?.formatter;
445
+ const tooltipValueFormatter = tooltip?.formatValue;
446
+ return (jsxs("div", { className: cvaChartRoot$1({ className }), "data-testid": dataTestId, ref: ref, style: style, children: [showLegend ? (jsx("div", { className: "flex-0 flex flex-row gap-2 place-self-end pr-8", "data-testid": "legend", children: seriesData.map((item, index) => (jsx(LegendItem, { color: item.color || chartColor(index), "data-testid": `legend-${item.name}`, label: item.name }, item.name))) })) : null, jsx("div", { className: "flex w-full flex-1", children: jsx(Chart, { "data-testid": "bar-chart", onClick: onClick
447
+ ? (e) => {
448
+ onClick(e);
449
+ }
450
+ : undefined, options: {
451
+ tooltip: {
452
+ trigger: tooltipTrigger,
453
+ confine: true,
454
+ axisPointer: tooltipTrigger === "axis" ? { type: "shadow" } : undefined,
455
+ formatter: tooltipFormatter
456
+ ? (rawParams) => tooltipFormatter(buildTooltipParams(rawParams))
457
+ : undefined,
458
+ valueFormatter: tooltipFormatter
459
+ ? undefined
460
+ : value => tooltipValueFormatter && typeof value === "number"
461
+ ? tooltipValueFormatter(value)
462
+ : `${value} ${units ?? ""}`,
463
+ },
464
+ grid: {
465
+ left: "2%",
466
+ right: "2%",
467
+ bottom: "10%",
468
+ top: "10%",
469
+ containLabel: true,
470
+ },
471
+ xAxis: [
472
+ {
473
+ type: "category",
474
+ axisLabel: {
475
+ formatter: (value, index) => {
476
+ if (xAxis?.formatLabel) {
477
+ return xAxis.formatLabel(value, index);
478
+ }
479
+ if (isDateData(seriesData[0]?.data[0])) {
480
+ const date = formatDate(toDateUtil(value), xAxis?.dateFormat ?? DEFAULT_DATE_FORMAT);
481
+ return date.replace(",", ",\n");
482
+ }
483
+ return value;
484
+ },
485
+ fontSize: 10,
486
+ fontFamily,
487
+ rotate: xAxis?.rotateLabels ?? DEFAULT_ROTATE_LABELS,
488
+ overflow: "truncate",
489
+ },
490
+ data: seriesData[0]?.data.map(data => (isDateData(data) ? data.date : data.key)),
491
+ },
492
+ ],
493
+ dataZoom: showDataZoom
494
+ ? [
495
+ {
496
+ type: "slider",
497
+ id: "insideX",
498
+ xAxisIndex: 0,
499
+ startValue: 0,
500
+ endValue: (seriesData[0]?.data.length ?? 0) > 10 ? 10 : (seriesData[0]?.data.length ?? 0),
501
+ showDetail: false,
502
+ },
503
+ ]
504
+ : [],
505
+ yAxis: [
506
+ {
507
+ type: "value",
508
+ axisLabel: {
509
+ fontSize: 10,
510
+ fontFamily,
511
+ formatter: yAxisLabelFormatter,
512
+ },
513
+ },
514
+ ],
515
+ series: seriesData,
516
+ }, showLoading: loading, style: { height: "100%", minHeight: "150px", width: "100%" } }) })] }));
517
+ };
518
+ const cvaChartRoot$1 = cvaMerge(["flex", "flex-col", "items-center", "h-full"]);
519
+
285
520
  /**
286
521
  * Limits the data set to the given limit.
287
522
  * If the data set is larger than the limit, the data set is limited to the limit and the rest of the data is added to the "Others" group.
@@ -487,9 +722,9 @@ const DonutChart = ({ data, size = "full", loading = false, onClick, className,
487
722
  if (loading) {
488
723
  return jsx(Spinner, { centering: "centered", "data-testid": dataTestId ? `${dataTestId}-loading` : "donut-chart-loading" });
489
724
  }
490
- return (jsxs("div", { className: cvaChartRoot$1({ className }), "data-testid": dataTestId, ref: mergedRef, style: style, children: [jsx("div", { className: cvaChartContainer(), children: jsx(Chart, { className: cvaChart({ size }), "data-testid": dataTestId ? `chart-${dataTestId}` : undefined, onChartReady: handleChartReady, onClick: handleChartClick, onEvents: handleChartEvents, options: chartOptions, style: { width: "100%", height: "100%" } }) }), size === "full" && (jsx("div", { className: cvaLegend(), "data-testid": "legend", children: limitedData.map((item, index) => (jsx(LegendItem, { className: "p-1.5 py-0.5", color: item.color ?? chartColor(index), count: item.value, "data-testid": `legend-${item.id}`, disabled: (item.value ?? 0) === 0, hideValue: hideLegendValues, label: item.name, onClick: onClick ? () => onClick(item) : undefined, onMouseEnter: () => handleLegendMouseEnter(item), onMouseLeave: handleLegendMouseLeave, selected: item.selected, unit: unit }, item.id))) }))] }));
725
+ return (jsxs("div", { className: cvaChartRoot({ className }), "data-testid": dataTestId, ref: mergedRef, style: style, children: [jsx("div", { className: cvaChartContainer(), children: jsx(Chart, { className: cvaChart({ size }), "data-testid": dataTestId ? `chart-${dataTestId}` : undefined, onChartReady: handleChartReady, onClick: handleChartClick, onEvents: handleChartEvents, options: chartOptions, style: { width: "100%", height: "100%" } }) }), size === "full" && (jsx("div", { className: cvaLegend(), "data-testid": "legend", children: limitedData.map((item, index) => (jsx(LegendItem, { className: "p-1.5 py-0.5", color: item.color ?? chartColor(index), count: item.value, "data-testid": `legend-${item.id}`, disabled: (item.value ?? 0) === 0, hideValue: hideLegendValues, label: item.name, onClick: onClick ? () => onClick(item) : undefined, onMouseEnter: () => handleLegendMouseEnter(item), onMouseLeave: handleLegendMouseLeave, selected: item.selected, unit: unit }, item.id))) }))] }));
491
726
  };
492
- const cvaChartRoot$1 = cvaMerge([
727
+ const cvaChartRoot = cvaMerge([
493
728
  "flex",
494
729
  "w-full",
495
730
  "h-full",
@@ -512,134 +747,4 @@ const cvaChart = cvaMerge(["flex-0", "max-w-[200px]", "max-h-[200px]", "place-se
512
747
  });
513
748
  const cvaLegend = cvaMerge(["flex", "overflow-auto", "justify-start", "flex-col", "flex-1"]);
514
749
 
515
- /**
516
- * Create a BarChart with automatic legends and formatting. Built on top of the Chart component
517
- * with sensible defaults for displaying categorical or time-series data.
518
- *
519
- * ### When to use
520
- * - To compare values across categories
521
- * - To show trends over time with discrete intervals
522
- * - When you need multiple series displayed side by side
523
- *
524
- * @example Bar chart with date-based data
525
- * ```tsx
526
- * import { BarChart } from "@trackunit/react-chart-components";
527
- *
528
- * const UtilizationChart = () => (
529
- * <BarChart
530
- * series={{
531
- * name: "Utilization",
532
- * data: [
533
- * { date: "2024-01-01", value: 85 },
534
- * { date: "2024-01-02", value: 72 },
535
- * { date: "2024-01-03", value: 91 },
536
- * ],
537
- * }}
538
- * units="%"
539
- * onClick={(event) => console.log("Clicked bar:", event.data)}
540
- * />
541
- * );
542
- * ```
543
- * @example Multi-series bar chart with data zoom
544
- * ```tsx
545
- * import { BarChart } from "@trackunit/react-chart-components";
546
- *
547
- * const ComparisonChart = () => (
548
- * <BarChart
549
- * series={[
550
- * { name: "2023", color: "#3b82f6", data: [{ key: "Q1", value: 100 }, { key: "Q2", value: 120 }] },
551
- * { name: "2024", color: "#10b981", data: [{ key: "Q1", value: 130 }, { key: "Q2", value: 145 }] },
552
- * ]}
553
- * units="units"
554
- * showDataZoom={true}
555
- * />
556
- * );
557
- * ```
558
- * @param {BarChartProps} props - The props for the Chart component
559
- * @returns {ReactElement} Chart component
560
- */
561
- const BarChart = ({ series, loading = false, onClick, className, style, "data-testid": dataTestId, units, showDataZoom = false, ref, }) => {
562
- const { formatDate } = useDateAndTime();
563
- const { chartColor } = useChartColor();
564
- const seriesData = useMemo(() => {
565
- const seriesAsArray = Array.isArray(series) ? series : series !== undefined ? [series] : [];
566
- return seriesAsArray.map((s, i) => ({
567
- ...s,
568
- color: s.color || chartColor(i),
569
- type: "bar",
570
- cursor: "arrow",
571
- barGap: "0",
572
- data: s.data.map(d => ({
573
- ...d,
574
- })),
575
- }));
576
- }, [series, chartColor]);
577
- const isDateData = (data) => !!data && "date" in data;
578
- return (jsxs("div", { className: cvaChartRoot({ className }), "data-testid": dataTestId, ref: ref, style: style, children: [jsx("div", { className: "flex-0 flex flex-row gap-2 place-self-end pr-8", "data-testid": "legend", children: seriesData.map((item, index) => (jsx(LegendItem, { color: item.color || chartColor(index), "data-testid": `legend-${item.name}`, label: item.name }, item.name))) }), jsx("div", { className: "flex w-full flex-1", children: jsx(Chart, { "data-testid": "bar-chart", onClick: onClick
579
- ? (e) => {
580
- onClick(e);
581
- }
582
- : undefined, options: {
583
- tooltip: {
584
- trigger: "item",
585
- confine: true,
586
- valueFormatter: value => `${value} ${units ?? ""}`,
587
- },
588
- grid: {
589
- left: "2%",
590
- right: "2%",
591
- bottom: "10%",
592
- top: "10%",
593
- containLabel: true,
594
- },
595
- xAxis: [
596
- {
597
- type: "category",
598
- axisLabel: {
599
- formatter: value => {
600
- if (isDateData(seriesData[0]?.data[0])) {
601
- const date = formatDate(toDateUtil(value), {
602
- selectFormat: "dateOnly",
603
- dateFormat: "medium",
604
- });
605
- return date.replace(",", ",\n");
606
- }
607
- return value;
608
- },
609
- fontSize: 10,
610
- fontFamily,
611
- rotate: 45,
612
- overflow: "truncate",
613
- },
614
- data: seriesData[0]?.data
615
- .filter(({ value }) => Boolean(value))
616
- .map(data => (isDateData(data) ? data.date : data.key)),
617
- },
618
- ],
619
- dataZoom: showDataZoom
620
- ? [
621
- {
622
- type: "slider",
623
- id: "insideX",
624
- xAxisIndex: 0,
625
- startValue: 0,
626
- endValue: (seriesData[0]?.data.length ?? 0) > 10 ? 10 : (seriesData[0]?.data.length ?? 0),
627
- showDetail: false,
628
- },
629
- ]
630
- : [],
631
- yAxis: [
632
- {
633
- type: "value",
634
- axisLabel: {
635
- fontSize: 10,
636
- fontFamily,
637
- },
638
- },
639
- ],
640
- series: seriesData,
641
- }, showLoading: loading, style: { height: "100%", minHeight: "150px", width: "100%" } }) })] }));
642
- };
643
- const cvaChartRoot = cvaMerge(["flex", "flex-col", "items-center", "h-full"]);
644
-
645
- export { BarChart, Chart, DonutChart, EChart, LegendItem, useChartColor };
750
+ export { BarChart, Chart, DonutChart, EChart, LegendItem, useChartColor, useChartNumberFormat };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trackunit/react-chart-components",
3
- "version": "2.1.43-alpha-6a85feaada3.0",
3
+ "version": "2.1.43",
4
4
  "repository": "https://github.com/Trackunit/manager",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "migrations": "./migrations.json",
@@ -9,12 +9,12 @@
9
9
  },
10
10
  "dependencies": {
11
11
  "echarts": "5.6.0",
12
- "@trackunit/date-and-time-utils": "1.13.47-alpha-6a85feaada3.0",
13
- "@trackunit/react-date-and-time-hooks": "2.1.42-alpha-6a85feaada3.0",
14
- "@trackunit/ui-design-tokens": "1.13.44-alpha-6a85feaada3.0",
15
- "@trackunit/shared-utils": "1.15.44-alpha-6a85feaada3.0",
16
- "@trackunit/css-class-variance-utilities": "1.13.44-alpha-6a85feaada3.0",
17
- "@trackunit/react-components": "2.1.40-alpha-6a85feaada3.0"
12
+ "@trackunit/date-and-time-utils": "1.13.46",
13
+ "@trackunit/react-date-and-time-hooks": "2.1.42",
14
+ "@trackunit/ui-design-tokens": "1.13.43",
15
+ "@trackunit/shared-utils": "1.15.43",
16
+ "@trackunit/css-class-variance-utilities": "1.13.43",
17
+ "@trackunit/react-components": "2.1.40"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "@apollo/client": "^3.13.8",
@@ -1,3 +1,4 @@
1
+ import { type TemporalFormat } from "@trackunit/date-and-time-utils";
1
2
  import { CommonProps, Refable, type Styleable } from "@trackunit/react-components";
2
3
  import { ECElementEvent } from "echarts";
3
4
  import { ReactElement } from "react";
@@ -10,6 +11,10 @@ export interface BarChartData<TProps extends object = object> {
10
11
  * If selected, it'll be highlighted
11
12
  */
12
13
  selected?: boolean;
14
+ /**
15
+ * Per-bar color. Falls back to the series color / auto color when omitted.
16
+ */
17
+ color?: string;
13
18
  /**
14
19
  * Supply the original object that this chart data item was constructed from. It'll be available on callbacks
15
20
  */
@@ -32,6 +37,88 @@ export interface SeriesData<TProps extends object> {
32
37
  color?: string;
33
38
  data: Array<BarChartGenericData<TProps>> | Array<BarChartDateData<TProps>>;
34
39
  }
40
+ /**
41
+ * A single entry in a tooltip payload. For `trigger: "item"` there is one entry;
42
+ * for `trigger: "axis"` there is one entry per series at the hovered category.
43
+ */
44
+ export interface BarChartTooltipEntry<TProps extends object = object> {
45
+ /**
46
+ * The name of the series this entry belongs to
47
+ */
48
+ seriesName: string;
49
+ /**
50
+ * The numeric value of the hovered bar
51
+ */
52
+ value: number | undefined;
53
+ /**
54
+ * The resolved color of the bar
55
+ */
56
+ color: string;
57
+ /**
58
+ * The colored-dot marker HTML that ECharts provides
59
+ */
60
+ marker: string;
61
+ /**
62
+ * The index of the data item within its series
63
+ */
64
+ dataIndex: number;
65
+ /**
66
+ * The original object the data item was constructed from
67
+ */
68
+ original?: TProps;
69
+ }
70
+ /**
71
+ * Typed tooltip payload passed to `tooltip.formatter`.
72
+ */
73
+ export interface BarChartTooltipParams<TProps extends object = object> {
74
+ /**
75
+ * The x-axis category/date value being hovered
76
+ */
77
+ axisValue: string;
78
+ /**
79
+ * One entry for `trigger: "item"`, one entry per series for `trigger: "axis"`
80
+ */
81
+ entries: Array<BarChartTooltipEntry<TProps>>;
82
+ }
83
+ export interface BarChartXAxisOptions {
84
+ /**
85
+ * Date granularity for date-based x-axis labels. Defaults to
86
+ * `{ selectFormat: "dateOnly", dateFormat: "medium" }`.
87
+ */
88
+ dateFormat?: TemporalFormat;
89
+ /**
90
+ * Full control over the x-axis label string. Wins over `dateFormat`.
91
+ */
92
+ formatLabel?: (value: string, index: number) => string;
93
+ /**
94
+ * Rotation of the x-axis labels in degrees. Defaults to `45`; `0` renders horizontal labels.
95
+ */
96
+ rotateLabels?: number;
97
+ }
98
+ export interface BarChartYAxisOptions {
99
+ /**
100
+ * Locale-aware compact y-axis labels (e.g. `40,000 → 40K`). Defaults to `false`.
101
+ */
102
+ abbreviate?: boolean;
103
+ /**
104
+ * Full control over the y-axis label string. Wins over `abbreviate`.
105
+ */
106
+ formatLabel?: (value: number) => string;
107
+ }
108
+ export interface BarChartTooltipOptions<TProps extends object = object> {
109
+ /**
110
+ * Format a single tooltip value. Defaults to `` `${value} ${units}` ``.
111
+ */
112
+ formatValue?: (value: number) => string;
113
+ /**
114
+ * Build the entire tooltip from a typed payload. Wins over `formatValue`.
115
+ */
116
+ formatter?: (params: BarChartTooltipParams<TProps>) => string;
117
+ /**
118
+ * Tooltip trigger mode. Defaults to `"item"`.
119
+ */
120
+ trigger?: "item" | "axis";
121
+ }
35
122
  export interface BarChartProps<TProps extends object> extends CommonProps, Refable<HTMLDivElement>, Styleable {
36
123
  /**
37
124
  * Array of series of data points to show
@@ -53,11 +140,30 @@ export interface BarChartProps<TProps extends object> extends CommonProps, Refab
53
140
  * Show data zoom
54
141
  */
55
142
  showDataZoom?: boolean;
143
+ /**
144
+ * Curated x-axis customization (label formatting and rotation).
145
+ */
146
+ xAxis?: BarChartXAxisOptions;
147
+ /**
148
+ * Curated y-axis customization (label formatting and abbreviation).
149
+ */
150
+ yAxis?: BarChartYAxisOptions;
151
+ /**
152
+ * Curated tooltip customization (value/payload formatting and trigger mode).
153
+ */
154
+ tooltip?: BarChartTooltipOptions<TProps>;
155
+ /**
156
+ * Toggle the custom legend row. Defaults to `true`.
157
+ */
158
+ showLegend?: boolean;
56
159
  }
57
160
  /**
58
161
  * Create a BarChart with automatic legends and formatting. Built on top of the Chart component
59
162
  * with sensible defaults for displaying categorical or time-series data.
60
163
  *
164
+ * All customization props are optional and default to the component's original rendering, so
165
+ * existing usages are unaffected.
166
+ *
61
167
  * ### When to use
62
168
  * - To compare values across categories
63
169
  * - To show trends over time with discrete intervals
@@ -97,7 +203,36 @@ export interface BarChartProps<TProps extends object> extends CommonProps, Refab
97
203
  * />
98
204
  * );
99
205
  * ```
206
+ * @example Abbreviated y-axis, horizontal x-axis labels and a hidden legend
207
+ * ```tsx
208
+ * import { BarChart } from "@trackunit/react-chart-components";
209
+ *
210
+ * const EmissionsChart = () => (
211
+ * <BarChart
212
+ * series={{ name: "Emissions", data: [{ key: "Excavator", value: 40000, color: "#3b82f6" }] }}
213
+ * units="t"
214
+ * yAxis={{ abbreviate: true }}
215
+ * xAxis={{ rotateLabels: 0, formatLabel: (value) => value.slice(0, 10) }}
216
+ * showLegend={false}
217
+ * />
218
+ * );
219
+ * ```
220
+ * @example Custom tooltip from the typed payload
221
+ * ```tsx
222
+ * import { BarChart } from "@trackunit/react-chart-components";
223
+ *
224
+ * const TrendChart = () => (
225
+ * <BarChart
226
+ * series={{ name: "Emissions", data: [{ date: "2024-01-01", value: 120 }] }}
227
+ * tooltip={{
228
+ * trigger: "axis",
229
+ * formatter: ({ axisValue, entries }) =>
230
+ * `${axisValue}<br/>${entries.map((e) => `${e.marker} ${e.seriesName}: ${e.value ?? 0}`).join("<br/>")}`,
231
+ * }}
232
+ * />
233
+ * );
234
+ * ```
100
235
  * @param {BarChartProps} props - The props for the Chart component
101
236
  * @returns {ReactElement} Chart component
102
237
  */
103
- export declare const BarChart: <TProps extends object>({ series, loading, onClick, className, style, "data-testid": dataTestId, units, showDataZoom, ref, }: BarChartProps<TProps>) => ReactElement;
238
+ export declare const BarChart: <TProps extends object>({ series, loading, onClick, className, style, "data-testid": dataTestId, units, showDataZoom, xAxis, yAxis, tooltip, showLegend, ref, }: BarChartProps<TProps>) => ReactElement;
package/src/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
+ export * from "./BarChart/BarChart";
1
2
  export * from "./Chart/Chart";
2
3
  export * from "./DonutChart/DonutChart";
3
- export * from "./BarChart/BarChart";
4
+ export * from "./EChart/EChart";
4
5
  export * from "./LegendItem/LegendItem";
5
6
  export * from "./utils/useChartColor";
6
- export * from "./EChart/EChart";
7
+ export * from "./utils/useChartNumberFormat";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Locale-aware number formatting helpers for charts. Mirrors `useChartColor`:
3
+ * a hook that returns memoized formatting functions so chart consumers share one
4
+ * consistent compact-number style instead of hand-rolling `>= 1000 → k` logic.
5
+ *
6
+ * @returns {{ formatCompact: (value: number) => string }} An object exposing
7
+ * `formatCompact`, a locale-aware compact formatter (e.g. `40000 → "40K"`, `40_000_000 → "40M"`).
8
+ * @example Abbreviate a y-axis value
9
+ * ```tsx
10
+ * const { formatCompact } = useChartNumberFormat();
11
+ * formatCompact(40000); // "40K"
12
+ * ```
13
+ */
14
+ export declare const useChartNumberFormat: () => {
15
+ formatCompact: (value: number) => string;
16
+ };