@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.
- package/{types/acorex-charts-bar-chart.d.ts → bar-chart/index.d.ts} +3 -28
- package/{types/acorex-charts-donut-chart.d.ts → donut-chart/index.d.ts} +8 -22
- package/fesm2022/acorex-charts-bar-chart.mjs +66 -91
- package/fesm2022/acorex-charts-bar-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-chart-legend.mjs +13 -13
- package/fesm2022/acorex-charts-chart-legend.mjs.map +1 -1
- package/fesm2022/acorex-charts-chart-tooltip.mjs +11 -11
- package/fesm2022/acorex-charts-chart-tooltip.mjs.map +1 -1
- package/fesm2022/acorex-charts-donut-chart.mjs +94 -101
- package/fesm2022/acorex-charts-donut-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-funnel-chart.mjs +19 -19
- package/fesm2022/acorex-charts-funnel-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-gauge-chart.mjs +64 -45
- package/fesm2022/acorex-charts-gauge-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-heatmap-chart.mjs +16 -19
- package/fesm2022/acorex-charts-heatmap-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-hierarchy-chart.mjs +39 -80
- package/fesm2022/acorex-charts-hierarchy-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-line-chart.mjs +42 -109
- package/fesm2022/acorex-charts-line-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts.mjs +69 -5
- package/fesm2022/acorex-charts.mjs.map +1 -1
- package/{types/acorex-charts-funnel-chart.d.ts → funnel-chart/index.d.ts} +3 -25
- package/{types/acorex-charts-gauge-chart.d.ts → gauge-chart/index.d.ts} +6 -21
- package/{types/acorex-charts-heatmap-chart.d.ts → heatmap-chart/index.d.ts} +2 -28
- package/{types/acorex-charts-hierarchy-chart.d.ts → hierarchy-chart/index.d.ts} +2 -27
- package/{types/acorex-charts.d.ts → index.d.ts} +52 -2
- package/{types/acorex-charts-line-chart.d.ts → line-chart/index.d.ts} +2 -38
- package/package.json +15 -16
- /package/{types/acorex-charts-chart-legend.d.ts → chart-legend/index.d.ts} +0 -0
- /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
|
-
|
|
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
|
-
|
|
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" }] :
|
|
42
|
+
data = input([], ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
44
43
|
/** Chart options input */
|
|
45
|
-
options = input({}, ...(ngDevMode ? [{ debugName: "options" }] :
|
|
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" }] :
|
|
65
|
-
_rendered = signal(false, ...(ngDevMode ? [{ debugName: "_rendered" }] :
|
|
66
|
-
_isInitialAnimating = signal(false, ...(ngDevMode ? [{ debugName: "_isInitialAnimating" }] :
|
|
67
|
-
_currentlyHighlightedSegment = signal(null, ...(ngDevMode ? [{ debugName: "_currentlyHighlightedSegment" }] :
|
|
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" }] :
|
|
70
|
-
_tooltipPosition = signal({ x: 0, y: 0 }, ...(ngDevMode ? [{ debugName: "_tooltipPosition" }] :
|
|
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" }] :
|
|
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" }] :
|
|
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" }] :
|
|
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',
|
|
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
|
|
329
|
-
|
|
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
|
|
338
|
-
|
|
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
|
|
345
|
-
const containerHeight = container.clientHeight
|
|
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(
|
|
352
|
-
|
|
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
|
|
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,
|
|
356
|
+
this.createDonutSegments(width, height, visibleData, layoutTotal);
|
|
367
357
|
// Add total in center
|
|
368
|
-
this.addCenterDisplay(
|
|
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',
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
524
|
-
.style('font-weight', (d) => ((d.data.value
|
|
525
|
-
.text((d) => this.getTruncatedLabelText(d, outerRadius, innerRadius,
|
|
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,
|
|
530
|
+
calculateLabelFontSize(d, outerRadius, innerRadius, layoutTotal) {
|
|
541
531
|
const angle = d.endAngle - d.startAngle;
|
|
542
|
-
const segmentPercentage = (d.data.value
|
|
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,
|
|
575
|
-
const fontSize = this.calculateLabelFontSize(d, outerRadius, innerRadius,
|
|
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
|
|
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
|
|
656
|
-
const
|
|
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(
|
|
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(
|
|
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
|
|
758
|
+
const displayTotal = this.getDisplayTotal(data);
|
|
759
|
+
const layoutTotal = this.getLayoutTotal(data);
|
|
782
760
|
const segmentDescriptions = data
|
|
783
761
|
.map((segment) => {
|
|
784
|
-
const percentage =
|
|
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: ${
|
|
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
|
|
796
|
-
|
|
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:
|
|
780
|
+
percentage: this.getSegmentPercentage(item.value, layoutTotal),
|
|
802
781
|
hidden: this.isSegmentHidden(item.id),
|
|
803
782
|
}));
|
|
804
783
|
}
|
|
805
|
-
|
|
806
|
-
|
|
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: "
|
|
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:
|
|
811
|
-
}], ctorParameters: () => []
|
|
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.
|