@acorex/charts 21.0.3-next.3 → 21.0.3-next.31

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.
Files changed (31) hide show
  1. package/{types/acorex-charts-bar-chart.d.ts → bar-chart/index.d.ts} +3 -28
  2. package/{types/acorex-charts-donut-chart.d.ts → donut-chart/index.d.ts} +8 -22
  3. package/fesm2022/acorex-charts-bar-chart.mjs +66 -91
  4. package/fesm2022/acorex-charts-bar-chart.mjs.map +1 -1
  5. package/fesm2022/acorex-charts-chart-legend.mjs +13 -13
  6. package/fesm2022/acorex-charts-chart-legend.mjs.map +1 -1
  7. package/fesm2022/acorex-charts-chart-tooltip.mjs +11 -11
  8. package/fesm2022/acorex-charts-chart-tooltip.mjs.map +1 -1
  9. package/fesm2022/acorex-charts-donut-chart.mjs +94 -101
  10. package/fesm2022/acorex-charts-donut-chart.mjs.map +1 -1
  11. package/fesm2022/acorex-charts-funnel-chart.mjs +19 -19
  12. package/fesm2022/acorex-charts-funnel-chart.mjs.map +1 -1
  13. package/fesm2022/acorex-charts-gauge-chart.mjs +64 -45
  14. package/fesm2022/acorex-charts-gauge-chart.mjs.map +1 -1
  15. package/fesm2022/acorex-charts-heatmap-chart.mjs +16 -19
  16. package/fesm2022/acorex-charts-heatmap-chart.mjs.map +1 -1
  17. package/fesm2022/acorex-charts-hierarchy-chart.mjs +39 -80
  18. package/fesm2022/acorex-charts-hierarchy-chart.mjs.map +1 -1
  19. package/fesm2022/acorex-charts-line-chart.mjs +42 -109
  20. package/fesm2022/acorex-charts-line-chart.mjs.map +1 -1
  21. package/fesm2022/acorex-charts.mjs +69 -5
  22. package/fesm2022/acorex-charts.mjs.map +1 -1
  23. package/{types/acorex-charts-funnel-chart.d.ts → funnel-chart/index.d.ts} +3 -25
  24. package/{types/acorex-charts-gauge-chart.d.ts → gauge-chart/index.d.ts} +6 -21
  25. package/{types/acorex-charts-heatmap-chart.d.ts → heatmap-chart/index.d.ts} +2 -28
  26. package/{types/acorex-charts-hierarchy-chart.d.ts → hierarchy-chart/index.d.ts} +2 -27
  27. package/{types/acorex-charts.d.ts → index.d.ts} +52 -2
  28. package/{types/acorex-charts-line-chart.d.ts → line-chart/index.d.ts} +2 -38
  29. package/package.json +15 -16
  30. /package/{types/acorex-charts-chart-legend.d.ts → chart-legend/index.d.ts} +0 -0
  31. /package/{types/acorex-charts-chart-tooltip.d.ts → chart-tooltip/index.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import { AXChartComponent, AX_CHART_COLOR_PALETTE, getChartColor, getEasingFunction, computeTooltipPosition } from '@acorex/charts';
1
+ import { AX_CHART_DEFAULT_MESSAGES, AXChartComponent, AX_CHART_COLOR_PALETTE, mergeChartOptions, createChartInstanceId, mergeChartMessages, getChartColor, renderChartEmptyMessage, getEasingFunction, computeTooltipPosition } from '@acorex/charts';
2
2
  import { AXChartTooltipComponent } from '@acorex/charts/chart-tooltip';
3
3
  import * as i0 from '@angular/core';
4
4
  import { InjectionToken, inject, ChangeDetectorRef, input, output, viewChild, signal, computed, afterNextRender, effect, untracked, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
@@ -11,12 +11,8 @@ const AXDonutChartDefaultConfig = {
11
11
  animationDuration: 800,
12
12
  animationEasing: 'cubic-out',
13
13
  messages: {
14
- noData: 'No data available',
15
- noDataHelp: 'Please provide data to display the chart',
16
- allHidden: 'All segments are hidden',
17
- allHiddenHelp: 'Please toggle visibility of at least one segment',
14
+ ...AX_CHART_DEFAULT_MESSAGES,
18
15
  noDataIcon: 'fa-light fa-chart-pie',
19
- allHiddenIcon: 'fa-light fa-eye-slash',
20
16
  },
21
17
  };
22
18
  const AX_DONUT_CHART_CONFIG = new InjectionToken('AX_DONUT_CHART_CONFIG', {
@@ -24,11 +20,14 @@ const AX_DONUT_CHART_CONFIG = new InjectionToken('AX_DONUT_CHART_CONFIG', {
24
20
  factory: () => AXDonutChartDefaultConfig,
25
21
  });
26
22
  function donutChartConfig(config = {}) {
27
- const result = {
23
+ return {
28
24
  ...AXDonutChartDefaultConfig,
29
25
  ...config,
26
+ messages: {
27
+ ...AXDonutChartDefaultConfig.messages,
28
+ ...config.messages,
29
+ },
30
30
  };
31
- return result;
32
31
  }
33
32
 
34
33
  /**
@@ -40,9 +39,9 @@ class AXDonutChartComponent extends AXChartComponent {
40
39
  cdr = inject(ChangeDetectorRef);
41
40
  // Inputs
42
41
  /** Chart data input */
43
- data = input([], ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
42
+ data = input([], ...(ngDevMode ? [{ debugName: "data" }] : []));
44
43
  /** Chart options input */
45
- options = input({}, ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
44
+ options = input({}, ...(ngDevMode ? [{ debugName: "options" }] : []));
46
45
  // Outputs
47
46
  /** Emitted when a segment is clicked */
48
47
  segmentClick = output();
@@ -61,19 +60,19 @@ class AXDonutChartComponent extends AXChartComponent {
61
60
  pieData = [];
62
61
  // State management
63
62
  hiddenSegments = new Set();
64
- _initialized = signal(false, ...(ngDevMode ? [{ debugName: "_initialized" }] : /* istanbul ignore next */ []));
65
- _rendered = signal(false, ...(ngDevMode ? [{ debugName: "_rendered" }] : /* istanbul ignore next */ []));
66
- _isInitialAnimating = signal(false, ...(ngDevMode ? [{ debugName: "_isInitialAnimating" }] : /* istanbul ignore next */ []));
67
- _currentlyHighlightedSegment = signal(null, ...(ngDevMode ? [{ debugName: "_currentlyHighlightedSegment" }] : /* istanbul ignore next */ []));
63
+ _initialized = signal(false, ...(ngDevMode ? [{ debugName: "_initialized" }] : []));
64
+ _rendered = signal(false, ...(ngDevMode ? [{ debugName: "_rendered" }] : []));
65
+ _isInitialAnimating = signal(false, ...(ngDevMode ? [{ debugName: "_isInitialAnimating" }] : []));
66
+ _currentlyHighlightedSegment = signal(null, ...(ngDevMode ? [{ debugName: "_currentlyHighlightedSegment" }] : []));
68
67
  // Tooltip state
69
- _tooltipVisible = signal(false, ...(ngDevMode ? [{ debugName: "_tooltipVisible" }] : /* istanbul ignore next */ []));
70
- _tooltipPosition = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "_tooltipPosition" }] : /* istanbul ignore next */ []));
68
+ _tooltipVisible = signal(false, ...(ngDevMode ? [{ debugName: "_tooltipVisible" }] : []));
69
+ _tooltipPosition = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "_tooltipPosition" }] : []));
71
70
  _tooltipData = signal({
72
71
  title: '',
73
72
  value: 0,
74
73
  percentage: '0%',
75
74
  color: '',
76
- }, ...(ngDevMode ? [{ debugName: "_tooltipData" }] : /* istanbul ignore next */ []));
75
+ }, ...(ngDevMode ? [{ debugName: "_tooltipData" }] : []));
77
76
  _tooltipRafId = null;
78
77
  _pendingTooltipCoords = null;
79
78
  // Public computed properties for the template
@@ -85,12 +84,7 @@ class AXDonutChartComponent extends AXChartComponent {
85
84
  // Inject the chart colors
86
85
  chartColors = inject(AX_CHART_COLOR_PALETTE);
87
86
  // Computed configuration options
88
- effectiveOptions = computed(() => {
89
- return {
90
- ...this.configToken,
91
- ...this.options(),
92
- };
93
- }, ...(ngDevMode ? [{ debugName: "effectiveOptions" }] : /* istanbul ignore next */ []));
87
+ effectiveOptions = computed(() => mergeChartOptions(this.configToken, this.options()), ...(ngDevMode ? [{ debugName: "effectiveOptions" }] : []));
94
88
  // Constants
95
89
  TOOLTIP_GAP = 10;
96
90
  HIGHLIGHT_TRANSITION_DURATION = 150;
@@ -102,25 +96,16 @@ class AXDonutChartComponent extends AXChartComponent {
102
96
  CHART_INSET = 12;
103
97
  DIMMED_OPACITY = 0.5;
104
98
  VISIBLE_OPACITY = 1;
99
+ instanceId = createChartInstanceId('ax-donut');
100
+ get segmentShadowFilterId() {
101
+ return `${this.instanceId}-segment-shadow`;
102
+ }
105
103
  // Messages with defaults
106
- effectiveMessages = computed(() => {
107
- const defaultMessages = {
108
- noData: 'No data available',
109
- noDataHelp: 'Please provide data to display the chart',
110
- allHidden: 'All segments are hidden',
111
- allHiddenHelp: 'Please toggle visibility of at least one segment',
112
- noDataIcon: 'fa-light fa-chart-pie',
113
- allHiddenIcon: 'fa-light fa-eye-slash',
114
- };
115
- return {
116
- ...defaultMessages,
117
- ...this.effectiveOptions().messages,
118
- };
119
- }, ...(ngDevMode ? [{ debugName: "effectiveMessages" }] : /* istanbul ignore next */ []));
104
+ effectiveMessages = computed(() => mergeChartMessages(this.configToken.messages, this.options()?.messages), ...(ngDevMode ? [{ debugName: "effectiveMessages" }] : []));
120
105
  // Data accessor for handling different incoming data formats
121
106
  chartDataArray = computed(() => {
122
107
  return this.data() || [];
123
- }, ...(ngDevMode ? [{ debugName: "chartDataArray" }] : /* istanbul ignore next */ []));
108
+ }, ...(ngDevMode ? [{ debugName: "chartDataArray" }] : []));
124
109
  // Color accessor method
125
110
  getColor(index) {
126
111
  return getChartColor(index, this.chartColors);
@@ -147,7 +132,7 @@ class AXDonutChartComponent extends AXChartComponent {
147
132
  this.updateChart();
148
133
  }
149
134
  });
150
- }, ...(ngDevMode ? [{ debugName: "dataChangeEffect" }] : /* istanbul ignore next */ []));
135
+ }, ...(ngDevMode ? [{ debugName: "dataChangeEffect" }] : []));
151
136
  /**
152
137
  * Highlights a specific segment by ID
153
138
  * @param id The segment ID to highlight, or null to clear highlight
@@ -211,7 +196,7 @@ class AXDonutChartComponent extends AXChartComponent {
211
196
  .classed('ax-donut-chart-highlighted', true)
212
197
  .transition(this.HIGHLIGHT_TRANSITION_NAME)
213
198
  .duration(this.HIGHLIGHT_TRANSITION_DURATION)
214
- .style('filter', 'url(#ax-donut-chart-segment-shadow)')
199
+ .style('filter', `url(#${this.segmentShadowFilterId})`)
215
200
  .style('opacity', this.VISIBLE_OPACITY);
216
201
  }
217
202
  getSegmentDataById(segmentId) {
@@ -235,7 +220,7 @@ class AXDonutChartComponent extends AXChartComponent {
235
220
  // Clear only chart SVG and message containers (preserve tooltip component)
236
221
  this.d3
237
222
  ?.select(this.chartContainerEl().nativeElement)
238
- .selectAll('svg, .ax-chart-message-container, .ax-donut-chart-no-data-message')
223
+ .selectAll('svg, .ax-chart-empty, .ax-chart-message-container, .ax-donut-chart-no-data-message')
239
224
  .remove();
240
225
  // Force full redraw
241
226
  setTimeout(() => {
@@ -316,7 +301,7 @@ class AXDonutChartComponent extends AXChartComponent {
316
301
  * Clears the chart container
317
302
  */
318
303
  clearChart(container) {
319
- this.d3.select(container).selectAll('svg, .ax-chart-message-container, .ax-donut-chart-no-data-message').remove();
304
+ this.d3.select(container).selectAll('svg, .ax-chart-empty, .ax-chart-message-container, .ax-donut-chart-no-data-message').remove();
320
305
  this._tooltipVisible.set(false);
321
306
  }
322
307
  /**
@@ -325,8 +310,12 @@ class AXDonutChartComponent extends AXChartComponent {
325
310
  showNoDataMessage() {
326
311
  if (!this.chartContainerEl()?.nativeElement)
327
312
  return;
328
- const container = this.chartContainerEl().nativeElement;
329
- this.renderMessage(container, this.effectiveMessages().noDataIcon, this.effectiveMessages().noData, this.effectiveMessages().noDataHelp, 'ax-donut-chart-no-data-message');
313
+ const messages = this.effectiveMessages();
314
+ renderChartEmptyMessage(this.d3, this.chartContainerEl().nativeElement, {
315
+ icon: messages.noDataIcon,
316
+ title: messages.noData,
317
+ help: messages.noDataHelp,
318
+ });
330
319
  }
331
320
  /**
332
321
  * Shows a message when all segments are hidden
@@ -334,38 +323,39 @@ class AXDonutChartComponent extends AXChartComponent {
334
323
  showAllSegmentsHiddenMessage() {
335
324
  if (!this.chartContainerEl()?.nativeElement)
336
325
  return;
337
- const container = this.chartContainerEl().nativeElement;
338
- this.renderMessage(container, this.effectiveMessages().allHiddenIcon, this.effectiveMessages().allHidden, this.effectiveMessages().allHiddenHelp, 'ax-donut-chart-no-data-message');
326
+ const messages = this.effectiveMessages();
327
+ renderChartEmptyMessage(this.d3, this.chartContainerEl().nativeElement, {
328
+ icon: messages.allHiddenIcon,
329
+ title: messages.allHidden,
330
+ help: messages.allHiddenHelp,
331
+ });
339
332
  }
340
333
  /**
341
334
  * Setups chart dimensions based on container and options
342
335
  */
343
336
  setupDimensions(container, options) {
344
- const containerWidth = container.clientWidth || 400;
345
- const containerHeight = container.clientHeight || 400;
337
+ const containerWidth = Math.max(container.clientWidth, 1);
338
+ const containerHeight = Math.max(container.clientHeight, 1);
346
339
  const desiredWidth = options?.width || containerWidth;
347
340
  const desiredHeight = options?.height || containerHeight;
348
- const boundedWidth = Math.max(this.MIN_CHART_DIMENSION, desiredWidth);
349
- const boundedHeight = Math.max(this.MIN_CHART_DIMENSION, desiredHeight);
350
341
  // Keep donut geometry square so side spacing stays visually balanced.
351
- const chartSize = Math.max(this.MIN_CHART_DIMENSION, Math.min(boundedWidth, boundedHeight));
352
- const width = chartSize;
353
- const height = chartSize;
354
- return { width, height };
342
+ const chartSize = Math.max(1, Math.min(desiredWidth, desiredHeight));
343
+ return { width: chartSize, height: chartSize };
355
344
  }
356
345
  /**
357
346
  * Renders the donut chart with visible data
358
347
  */
359
348
  renderDonutChart(container, width, height, visibleData) {
360
- const total = visibleData.reduce((sum, item) => sum + item.value, 0);
349
+ const displayTotal = this.getDisplayTotal(visibleData);
350
+ const layoutTotal = this.getLayoutTotal(visibleData);
361
351
  // Create SVG container with filters
362
352
  const svg = this.createSvgWithFilters(container, width, height);
363
353
  // Create main chart group
364
354
  this.svg = svg.append('g').attr('transform', `translate(${width / 2}, ${height / 2})`);
365
355
  // Create donut segments
366
- this.createDonutSegments(width, height, visibleData, total);
356
+ this.createDonutSegments(width, height, visibleData, layoutTotal);
367
357
  // Add total in center
368
- this.addCenterDisplay(total);
358
+ this.addCenterDisplay(displayTotal);
369
359
  }
370
360
  /**
371
361
  * Create SVG element with filter definitions for shadows
@@ -382,7 +372,7 @@ class AXDonutChartComponent extends AXChartComponent {
382
372
  .attr('preserveAspectRatio', 'xMidYMid meet');
383
373
  // Add drop shadow filter
384
374
  const defs = svg.append('defs');
385
- const filter = defs.append('filter').attr('id', 'ax-donut-chart-segment-shadow').attr('height', '130%');
375
+ const filter = defs.append('filter').attr('id', this.segmentShadowFilterId).attr('height', '130%');
386
376
  filter.append('feGaussianBlur').attr('in', 'SourceAlpha').attr('stdDeviation', 2).attr('result', 'blur');
387
377
  filter.append('feOffset').attr('in', 'blur').attr('dx', 1).attr('dy', 1).attr('result', 'offsetBlur');
388
378
  const feComponentTransfer = filter
@@ -398,12 +388,12 @@ class AXDonutChartComponent extends AXChartComponent {
398
388
  /**
399
389
  * Create and render the donut segments with animations
400
390
  */
401
- createDonutSegments(chartWidth, chartHeight, data, total) {
391
+ createDonutSegments(chartWidth, chartHeight, data, layoutTotal) {
402
392
  this._isInitialAnimating.set(true);
403
393
  // Create pie layout
404
394
  const pie = this.d3
405
395
  .pie()
406
- .value((d) => d.value)
396
+ .value((d) => this.getSegmentLayoutValue(d.value))
407
397
  .sort(null)
408
398
  .padAngle(0.02);
409
399
  // Calculate the radius of the donut chart
@@ -487,7 +477,7 @@ class AXDonutChartComponent extends AXChartComponent {
487
477
  });
488
478
  // Add data labels if enabled and chart size is sufficient
489
479
  if (this.effectiveOptions().showDataLabels && this.isChartLargeEnoughForLabels(chartWidth, chartHeight)) {
490
- this.addDataLabels(labelArc, outerRadius, innerRadius, total, animationDuration);
480
+ this.addDataLabels(labelArc, outerRadius, innerRadius, layoutTotal, animationDuration);
491
481
  }
492
482
  // Determine when all segment animations are complete (handle empty case)
493
483
  if (numSegments === 0) {
@@ -499,11 +489,11 @@ class AXDonutChartComponent extends AXChartComponent {
499
489
  return Math.min(width, height) >= this.MIN_CHART_FOR_LABELS;
500
490
  }
501
491
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
502
- addDataLabels(labelArc, outerRadius, innerRadius, total, animationDuration) {
492
+ addDataLabels(labelArc, outerRadius, innerRadius, layoutTotal, animationDuration) {
503
493
  if (!this.svg)
504
494
  return;
505
495
  const dataWithLabels = this.pieData.filter((d) => {
506
- return this.calculateLabelFontSize(d, outerRadius, innerRadius, total) > 0;
496
+ return this.calculateLabelFontSize(d, outerRadius, innerRadius, layoutTotal) > 0;
507
497
  });
508
498
  const labels = this.svg
509
499
  .selectAll('.ax-donut-chart-data-label')
@@ -520,9 +510,9 @@ class AXDonutChartComponent extends AXChartComponent {
520
510
  })
521
511
  .attr('text-anchor', 'middle')
522
512
  .attr('dominant-baseline', 'middle')
523
- .style('font-size', (d) => `${this.calculateLabelFontSize(d, outerRadius, innerRadius, total)}px`)
524
- .style('font-weight', (d) => ((d.data.value / total) * 100 >= 15 ? '600' : '500'))
525
- .text((d) => this.getTruncatedLabelText(d, outerRadius, innerRadius, total));
513
+ .style('font-size', (d) => `${this.calculateLabelFontSize(d, outerRadius, innerRadius, layoutTotal)}px`)
514
+ .style('font-weight', (d) => (this.getSegmentPercentage(d.data.value, layoutTotal) >= 15 ? '600' : '500'))
515
+ .text((d) => this.getTruncatedLabelText(d, outerRadius, innerRadius, layoutTotal));
526
516
  labels
527
517
  .transition()
528
518
  .duration(animationDuration)
@@ -537,9 +527,9 @@ class AXDonutChartComponent extends AXChartComponent {
537
527
  * visual hierarchy.
538
528
  */
539
529
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
540
- calculateLabelFontSize(d, outerRadius, innerRadius, total) {
530
+ calculateLabelFontSize(d, outerRadius, innerRadius, layoutTotal) {
541
531
  const angle = d.endAngle - d.startAngle;
542
- const segmentPercentage = (d.data.value / total) * 100;
532
+ const segmentPercentage = this.getSegmentPercentage(d.data.value, layoutTotal);
543
533
  if (segmentPercentage < 2)
544
534
  return 0;
545
535
  const ringWidth = outerRadius - innerRadius;
@@ -571,8 +561,8 @@ class AXDonutChartComponent extends AXChartComponent {
571
561
  * from "Label (XX%)" → "Label" → "XX%" → truncated label → truncated percentage.
572
562
  */
573
563
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
574
- getTruncatedLabelText(d, outerRadius, innerRadius, total) {
575
- const fontSize = this.calculateLabelFontSize(d, outerRadius, innerRadius, total);
564
+ getTruncatedLabelText(d, outerRadius, innerRadius, layoutTotal) {
565
+ const fontSize = this.calculateLabelFontSize(d, outerRadius, innerRadius, layoutTotal);
576
566
  if (fontSize === 0)
577
567
  return '';
578
568
  const ringWidth = outerRadius - innerRadius;
@@ -580,7 +570,7 @@ class AXDonutChartComponent extends AXChartComponent {
580
570
  const angle = d.endAngle - d.startAngle;
581
571
  const chordLength = 2 * labelRadius * Math.sin(angle / 2);
582
572
  const availableWidth = chordLength * 0.8;
583
- const percentage = (d.data.value / total) * 100;
573
+ const percentage = this.getSegmentPercentage(d.data.value, layoutTotal);
584
574
  if (percentage < 2)
585
575
  return '';
586
576
  const label = d.data.label || '';
@@ -652,8 +642,9 @@ class AXDonutChartComponent extends AXChartComponent {
652
642
  this.highlightSegment(datum.id);
653
643
  }
654
644
  showTooltip(event, datum) {
655
- const total = untracked(() => this.data()).reduce((sum, item) => sum + item.value, 0);
656
- const percentage = total > 0 ? ((datum.value / total) * 100).toFixed(1) : '0';
645
+ const visibleData = untracked(() => this.chartDataArray()).filter((item) => !this.hiddenSegments.has(item.id));
646
+ const layoutTotal = this.getLayoutTotal(visibleData);
647
+ const percentage = layoutTotal > 0 ? this.getSegmentPercentage(datum.value, layoutTotal).toFixed(1) : '0';
657
648
  const color = this.getSegmentColor(datum);
658
649
  this._tooltipData.set({
659
650
  title: datum.tooltipLabel || datum.label,
@@ -695,7 +686,7 @@ class AXDonutChartComponent extends AXChartComponent {
695
686
  /**
696
687
  * Adds center display with total value
697
688
  */
698
- addCenterDisplay(total) {
689
+ addCenterDisplay(displayTotal) {
699
690
  if (!this.svg)
700
691
  return;
701
692
  const chartContainerWidth = this.chartContainerEl().nativeElement.clientWidth;
@@ -714,7 +705,7 @@ class AXDonutChartComponent extends AXChartComponent {
714
705
  .style('font-size', `${baseFontSize}rem`)
715
706
  .style('font-weight', '600')
716
707
  .style('fill', 'rgb(var(--ax-comp-donut-chart-value-color))')
717
- .text(total.toLocaleString());
708
+ .text(displayTotal.toLocaleString());
718
709
  // Add label
719
710
  totalDisplay
720
711
  .append('text')
@@ -745,7 +736,7 @@ class AXDonutChartComponent extends AXChartComponent {
745
736
  this._pendingTooltipCoords = null;
746
737
  const container = this.chartContainerEl()?.nativeElement;
747
738
  if (container) {
748
- this.d3?.select(container).selectAll('svg, .ax-chart-message-container, .ax-donut-chart-no-data-message').remove();
739
+ this.d3?.select(container).selectAll('svg, .ax-chart-empty, .ax-chart-message-container, .ax-donut-chart-no-data-message').remove();
749
740
  }
750
741
  this.svg = null;
751
742
  this.pieData = [];
@@ -754,20 +745,6 @@ class AXDonutChartComponent extends AXChartComponent {
754
745
  this._currentlyHighlightedSegment.set(null);
755
746
  }
756
747
  // ===== Helper utilities for modularity and maintainability =====
757
- renderMessage(container, iconClass, messageText, helpText, specificClass) {
758
- // Clear container area first (preserve tooltip component)
759
- this.d3.select(container).selectAll('svg, .ax-chart-message-container, .ax-donut-chart-no-data-message').remove();
760
- const messageContainer = this.d3
761
- .select(container)
762
- .append('div')
763
- .attr('class', `ax-chart-message-container ax-donut-chart-message-background ${specificClass}`);
764
- messageContainer
765
- .append('div')
766
- .attr('class', 'ax-chart-message-icon ax-donut-chart-no-data-icon')
767
- .html(`<i class="${iconClass} fa-2x"></i>`);
768
- messageContainer.append('div').attr('class', 'ax-chart-message-text ax-donut-chart-no-data-text').text(messageText);
769
- messageContainer.append('div').attr('class', 'ax-chart-message-help ax-donut-chart-no-data-help').text(helpText);
770
- }
771
748
  // computeTooltipPosition moved to shared chart utils
772
749
  /**
773
750
  * Gets an accessibility label describing the donut chart for screen readers
@@ -778,37 +755,53 @@ class AXDonutChartComponent extends AXChartComponent {
778
755
  if (!data || data.length === 0) {
779
756
  return 'Empty donut chart. No data available.';
780
757
  }
781
- const total = data.reduce((sum, item) => sum + item.value, 0);
758
+ const displayTotal = this.getDisplayTotal(data);
759
+ const layoutTotal = this.getLayoutTotal(data);
782
760
  const segmentDescriptions = data
783
761
  .map((segment) => {
784
- const percentage = total > 0 ? ((segment.value / total) * 100).toFixed(1) : '0';
762
+ const percentage = layoutTotal > 0 ? this.getSegmentPercentage(segment.value, layoutTotal).toFixed(1) : '0';
785
763
  return `${segment.label}: ${segment.value} (${percentage}%)`;
786
764
  })
787
765
  .join('; ');
788
- return `Donut chart with ${data.length} segments. Total value: ${total}. ${segmentDescriptions}`;
766
+ return `Donut chart with ${data.length} segments. Total value: ${displayTotal}. ${segmentDescriptions}`;
789
767
  }
790
768
  /**
791
769
  * Returns the data items for the legend
792
770
  * @implements AXChartLegendCompatible
793
771
  */
794
772
  getLegendItems() {
795
- const total = this.data().reduce((sum, item) => sum + item.value, 0);
796
- return this.chartDataArray().map((item, index) => ({
773
+ const data = this.chartDataArray();
774
+ const layoutTotal = this.getLayoutTotal(data);
775
+ return data.map((item, index) => ({
797
776
  id: item.id,
798
777
  name: item.label || `Segment ${index + 1}`,
799
778
  value: item.value,
800
779
  color: this.getSegmentColor(item),
801
- percentage: total > 0 ? (item.value / total) * 100 : 0,
780
+ percentage: this.getSegmentPercentage(item.value, layoutTotal),
802
781
  hidden: this.isSegmentHidden(item.id),
803
782
  }));
804
783
  }
805
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXDonutChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
806
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "21.2.9", type: AXDonutChartComponent, isStandalone: true, selector: "ax-donut-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { segmentClick: "segmentClick", segmentHover: "segmentHover" }, viewQueries: [{ propertyName: "chartContainerEl", first: true, predicate: ["chartContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"ax-donut-chart\" #chartContainer role=\"img\" [attr.aria-label]=\"getAccessibilityLabel()\">\n <!-- Tooltip component -->\n <ax-chart-tooltip\n [visible]=\"tooltipVisible()\"\n [position]=\"tooltipPosition()\"\n [data]=\"tooltipData()\"\n [showPercentage]=\"true\"\n ></ax-chart-tooltip>\n</div>\n", styles: ["ax-donut-chart{display:block;width:100%;height:100%;min-height:0;box-sizing:border-box;--ax-comp-donut-chart-bg-color: 0, 0, 0, 0;--ax-comp-donut-chart-data-labels-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-donut-chart-value-color: var(--ax-sys-color-on-lightest-surface)}ax-donut-chart .ax-donut-chart{width:100%;height:100%;position:relative;box-sizing:border-box;padding:clamp(.5rem,1.2vw,.875rem);border-radius:.5rem;overflow:hidden;background-color:rgba(var(--ax-comp-donut-chart-bg-color))}ax-donut-chart .ax-donut-chart svg{display:block;width:100%;height:100%;max-width:100%;max-height:100%;overflow:hidden}ax-donut-chart .ax-donut-chart svg g:has(text){font-family:inherit}ax-donut-chart .ax-donut-chart-no-data-message{text-align:center;background-color:rgb(var(--ax-comp-donut-chart-bg-color));border:1px solid rgba(var(--ax-sys-color-surface));padding:1.5rem;border-radius:.5rem;width:80%;max-width:300px}ax-donut-chart .ax-donut-chart-no-data-message .ax-donut-chart-no-data-icon{opacity:.6;margin-bottom:.75rem}ax-donut-chart .ax-donut-chart-no-data-message .ax-donut-chart-no-data-text{font-size:1rem;font-weight:600;margin-bottom:.5rem}ax-donut-chart .ax-donut-chart-no-data-message .ax-donut-chart-no-data-help{font-size:.8rem;opacity:.6}\n"], dependencies: [{ kind: "component", type: AXChartTooltipComponent, selector: "ax-chart-tooltip", inputs: ["data", "position", "visible", "showPercentage", "style"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
784
+ getSegmentLayoutValue(value) {
785
+ return Math.abs(value);
786
+ }
787
+ getLayoutTotal(data) {
788
+ return data.reduce((sum, item) => sum + this.getSegmentLayoutValue(item.value), 0);
789
+ }
790
+ getDisplayTotal(data) {
791
+ return data.reduce((sum, item) => sum + item.value, 0);
792
+ }
793
+ getSegmentPercentage(segmentValue, layoutTotal) {
794
+ if (layoutTotal <= 0)
795
+ return 0;
796
+ return (this.getSegmentLayoutValue(segmentValue) / layoutTotal) * 100;
797
+ }
798
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXDonutChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
799
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.3", type: AXDonutChartComponent, isStandalone: true, selector: "ax-donut-chart", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { segmentClick: "segmentClick", segmentHover: "segmentHover" }, viewQueries: [{ propertyName: "chartContainerEl", first: true, predicate: ["chartContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"ax-donut-chart\" #chartContainer role=\"img\" [attr.aria-label]=\"getAccessibilityLabel()\">\n <!-- Tooltip component -->\n <ax-chart-tooltip\n [visible]=\"tooltipVisible()\"\n [position]=\"tooltipPosition()\"\n [data]=\"tooltipData()\"\n [showPercentage]=\"true\"\n ></ax-chart-tooltip>\n</div>\n", styles: [".ax-chart-empty{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;display:flex;align-items:center;justify-content:center;box-sizing:border-box;padding:1rem;pointer-events:none}.ax-chart-empty__card{text-align:center;padding:1.5rem;border-radius:.5rem;border:1px solid rgba(var(--ax-sys-color-surface));width:80%;max-width:300px;box-sizing:border-box}.ax-chart-empty__icon{opacity:.6;margin-bottom:.75rem}.ax-chart-empty__title{font-size:1rem;font-weight:600;margin-bottom:.5rem}.ax-chart-empty__help{font-size:.8rem;opacity:.6}ax-donut-chart{display:block;width:100%;height:100%;min-height:clamp(220px,38vw,360px);box-sizing:border-box;--ax-comp-donut-chart-bg-color: 0, 0, 0, 0;--ax-comp-donut-chart-data-labels-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-donut-chart-value-color: var(--ax-sys-color-on-lightest-surface)}ax-donut-chart .ax-donut-chart{width:100%;height:100%;position:relative;min-height:0;box-sizing:border-box;padding:clamp(.5rem,1.2vw,.875rem);border-radius:.5rem;overflow:hidden;background-color:rgba(var(--ax-comp-donut-chart-bg-color))}ax-donut-chart .ax-donut-chart svg{display:block;width:100%;height:100%;max-width:100%;max-height:100%;overflow:hidden}ax-donut-chart .ax-donut-chart svg g:has(text){font-family:inherit}\n"], dependencies: [{ kind: "component", type: AXChartTooltipComponent, selector: "ax-chart-tooltip", inputs: ["data", "position", "visible", "showPercentage", "style"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
807
800
  }
808
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AXDonutChartComponent, decorators: [{
801
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXDonutChartComponent, decorators: [{
809
802
  type: Component,
810
- args: [{ selector: 'ax-donut-chart', encapsulation: ViewEncapsulation.None, imports: [AXChartTooltipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ax-donut-chart\" #chartContainer role=\"img\" [attr.aria-label]=\"getAccessibilityLabel()\">\n <!-- Tooltip component -->\n <ax-chart-tooltip\n [visible]=\"tooltipVisible()\"\n [position]=\"tooltipPosition()\"\n [data]=\"tooltipData()\"\n [showPercentage]=\"true\"\n ></ax-chart-tooltip>\n</div>\n", styles: ["ax-donut-chart{display:block;width:100%;height:100%;min-height:0;box-sizing:border-box;--ax-comp-donut-chart-bg-color: 0, 0, 0, 0;--ax-comp-donut-chart-data-labels-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-donut-chart-value-color: var(--ax-sys-color-on-lightest-surface)}ax-donut-chart .ax-donut-chart{width:100%;height:100%;position:relative;box-sizing:border-box;padding:clamp(.5rem,1.2vw,.875rem);border-radius:.5rem;overflow:hidden;background-color:rgba(var(--ax-comp-donut-chart-bg-color))}ax-donut-chart .ax-donut-chart svg{display:block;width:100%;height:100%;max-width:100%;max-height:100%;overflow:hidden}ax-donut-chart .ax-donut-chart svg g:has(text){font-family:inherit}ax-donut-chart .ax-donut-chart-no-data-message{text-align:center;background-color:rgb(var(--ax-comp-donut-chart-bg-color));border:1px solid rgba(var(--ax-sys-color-surface));padding:1.5rem;border-radius:.5rem;width:80%;max-width:300px}ax-donut-chart .ax-donut-chart-no-data-message .ax-donut-chart-no-data-icon{opacity:.6;margin-bottom:.75rem}ax-donut-chart .ax-donut-chart-no-data-message .ax-donut-chart-no-data-text{font-size:1rem;font-weight:600;margin-bottom:.5rem}ax-donut-chart .ax-donut-chart-no-data-message .ax-donut-chart-no-data-help{font-size:.8rem;opacity:.6}\n"] }]
811
- }], ctorParameters: () => [], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], segmentClick: [{ type: i0.Output, args: ["segmentClick"] }], segmentHover: [{ type: i0.Output, args: ["segmentHover"] }], chartContainerEl: [{ type: i0.ViewChild, args: ['chartContainer', { isSignal: true }] }] } });
803
+ args: [{ selector: 'ax-donut-chart', encapsulation: ViewEncapsulation.None, imports: [AXChartTooltipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ax-donut-chart\" #chartContainer role=\"img\" [attr.aria-label]=\"getAccessibilityLabel()\">\n <!-- Tooltip component -->\n <ax-chart-tooltip\n [visible]=\"tooltipVisible()\"\n [position]=\"tooltipPosition()\"\n [data]=\"tooltipData()\"\n [showPercentage]=\"true\"\n ></ax-chart-tooltip>\n</div>\n", styles: [".ax-chart-empty{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;display:flex;align-items:center;justify-content:center;box-sizing:border-box;padding:1rem;pointer-events:none}.ax-chart-empty__card{text-align:center;padding:1.5rem;border-radius:.5rem;border:1px solid rgba(var(--ax-sys-color-surface));width:80%;max-width:300px;box-sizing:border-box}.ax-chart-empty__icon{opacity:.6;margin-bottom:.75rem}.ax-chart-empty__title{font-size:1rem;font-weight:600;margin-bottom:.5rem}.ax-chart-empty__help{font-size:.8rem;opacity:.6}ax-donut-chart{display:block;width:100%;height:100%;min-height:clamp(220px,38vw,360px);box-sizing:border-box;--ax-comp-donut-chart-bg-color: 0, 0, 0, 0;--ax-comp-donut-chart-data-labels-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-donut-chart-value-color: var(--ax-sys-color-on-lightest-surface)}ax-donut-chart .ax-donut-chart{width:100%;height:100%;position:relative;min-height:0;box-sizing:border-box;padding:clamp(.5rem,1.2vw,.875rem);border-radius:.5rem;overflow:hidden;background-color:rgba(var(--ax-comp-donut-chart-bg-color))}ax-donut-chart .ax-donut-chart svg{display:block;width:100%;height:100%;max-width:100%;max-height:100%;overflow:hidden}ax-donut-chart .ax-donut-chart svg g:has(text){font-family:inherit}\n"] }]
804
+ }], ctorParameters: () => [] });
812
805
 
813
806
  /**
814
807
  * Generated bundle index. Do not edit.