@acorex/charts 0.0.1
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/README.md +72 -0
- package/bar-chart/README.md +3 -0
- package/bar-chart/index.d.ts +211 -0
- package/chart-legend/index.d.ts +104 -0
- package/chart-tooltip/README.md +3 -0
- package/chart-tooltip/index.d.ts +54 -0
- package/donut-chart/README.md +3 -0
- package/donut-chart/index.d.ts +251 -0
- package/fesm2022/acorex-charts-bar-chart.mjs +772 -0
- package/fesm2022/acorex-charts-bar-chart.mjs.map +1 -0
- package/fesm2022/acorex-charts-chart-legend.mjs +109 -0
- package/fesm2022/acorex-charts-chart-legend.mjs.map +1 -0
- package/fesm2022/acorex-charts-chart-tooltip.mjs +74 -0
- package/fesm2022/acorex-charts-chart-tooltip.mjs.map +1 -0
- package/fesm2022/acorex-charts-donut-chart.mjs +758 -0
- package/fesm2022/acorex-charts-donut-chart.mjs.map +1 -0
- package/fesm2022/acorex-charts-gauge-chart.mjs +686 -0
- package/fesm2022/acorex-charts-gauge-chart.mjs.map +1 -0
- package/fesm2022/acorex-charts-hierarchy-chart.mjs +700 -0
- package/fesm2022/acorex-charts-hierarchy-chart.mjs.map +1 -0
- package/fesm2022/acorex-charts-line-chart.mjs +995 -0
- package/fesm2022/acorex-charts-line-chart.mjs.map +1 -0
- package/fesm2022/acorex-charts.mjs +41 -0
- package/fesm2022/acorex-charts.mjs.map +1 -0
- package/gauge-chart/README.md +3 -0
- package/gauge-chart/index.d.ts +218 -0
- package/hierarchy-chart/README.md +61 -0
- package/hierarchy-chart/index.d.ts +368 -0
- package/index.d.ts +14 -0
- package/line-chart/README.md +3 -0
- package/line-chart/index.d.ts +184 -0
- package/package.json +52 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
import { AX_CHART_COLOR_PALETTE, getChartColor } from '@acorex/charts';
|
|
2
|
+
import { AXChartTooltipComponent } from '@acorex/charts/chart-tooltip';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { InjectionToken, inject, ChangeDetectorRef, input, output, viewChild, signal, computed, afterNextRender, effect, untracked, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
|
|
5
|
+
import { AX_GLOBAL_CONFIG } from '@acorex/core/config';
|
|
6
|
+
import { set } from 'lodash-es';
|
|
7
|
+
|
|
8
|
+
const AXDonutChartDefaultConfig = {
|
|
9
|
+
showTooltip: true,
|
|
10
|
+
showDataLabels: true,
|
|
11
|
+
donutWidth: 35,
|
|
12
|
+
cornerRadius: 4,
|
|
13
|
+
animationDuration: 800,
|
|
14
|
+
animationEasing: 'cubic-out',
|
|
15
|
+
};
|
|
16
|
+
const AX_DONUT_CHART_CONFIG = new InjectionToken('AX_DONUT_CHART_CONFIG', {
|
|
17
|
+
providedIn: 'root',
|
|
18
|
+
factory: () => {
|
|
19
|
+
const global = inject(AX_GLOBAL_CONFIG);
|
|
20
|
+
set(global, 'chart.donutChart', AXDonutChartDefaultConfig);
|
|
21
|
+
return AXDonutChartDefaultConfig;
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
function donutChartConfig(config = {}) {
|
|
25
|
+
const result = {
|
|
26
|
+
...AXDonutChartDefaultConfig,
|
|
27
|
+
...config,
|
|
28
|
+
};
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Donut Chart Component
|
|
34
|
+
* Displays data in a circular donut chart with interactive segments
|
|
35
|
+
*/
|
|
36
|
+
class AXDonutChartComponent {
|
|
37
|
+
// Dependency Injection
|
|
38
|
+
cdr = inject(ChangeDetectorRef);
|
|
39
|
+
// Inputs
|
|
40
|
+
/** Chart data input */
|
|
41
|
+
data = input([]);
|
|
42
|
+
/** Chart options input */
|
|
43
|
+
options = input({});
|
|
44
|
+
// Outputs
|
|
45
|
+
/** Emitted when a segment is clicked */
|
|
46
|
+
segmentClick = output();
|
|
47
|
+
/** Emitted when a segment has mouse hover
|
|
48
|
+
* Can be used by external elements to highlight this segment
|
|
49
|
+
*/
|
|
50
|
+
segmentHover = output();
|
|
51
|
+
// Chart container reference
|
|
52
|
+
chartContainerEl = viewChild.required('chartContainer');
|
|
53
|
+
// D3 reference - loaded asynchronously
|
|
54
|
+
d3;
|
|
55
|
+
// Chart SVG reference
|
|
56
|
+
svg;
|
|
57
|
+
pieData = [];
|
|
58
|
+
// State management
|
|
59
|
+
hiddenSegments = new Set();
|
|
60
|
+
_initialized = signal(false);
|
|
61
|
+
_rendered = signal(false);
|
|
62
|
+
_isInitialAnimating = signal(false);
|
|
63
|
+
// Tooltip state
|
|
64
|
+
_tooltipVisible = signal(false);
|
|
65
|
+
_tooltipPosition = signal({ x: 0, y: 0 });
|
|
66
|
+
_tooltipData = signal({
|
|
67
|
+
title: '',
|
|
68
|
+
value: 0,
|
|
69
|
+
percentage: '0%',
|
|
70
|
+
color: '',
|
|
71
|
+
});
|
|
72
|
+
// Public computed properties for the template
|
|
73
|
+
tooltipVisible = this._tooltipVisible.asReadonly();
|
|
74
|
+
tooltipPosition = this._tooltipPosition.asReadonly();
|
|
75
|
+
tooltipData = this._tooltipData.asReadonly();
|
|
76
|
+
// Inject configuration
|
|
77
|
+
configToken = inject(AX_DONUT_CHART_CONFIG);
|
|
78
|
+
// Inject the chart colors
|
|
79
|
+
chartColors = inject(AX_CHART_COLOR_PALETTE);
|
|
80
|
+
// Computed configuration options
|
|
81
|
+
effectiveOptions = computed(() => {
|
|
82
|
+
return {
|
|
83
|
+
...this.configToken,
|
|
84
|
+
...this.options(),
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
// Data accessor for handling different incoming data formats
|
|
88
|
+
chartDataArray = computed(() => {
|
|
89
|
+
return this.data() || [];
|
|
90
|
+
});
|
|
91
|
+
// Color accessor method
|
|
92
|
+
getColor(index) {
|
|
93
|
+
return getChartColor(index, this.chartColors);
|
|
94
|
+
}
|
|
95
|
+
// Segment visibility check
|
|
96
|
+
isSegmentHidden(id) {
|
|
97
|
+
return this.hiddenSegments.has(id);
|
|
98
|
+
}
|
|
99
|
+
constructor() {
|
|
100
|
+
// Dynamically load D3 and initialize the chart when the component is ready
|
|
101
|
+
afterNextRender(() => {
|
|
102
|
+
this._initialized.set(true);
|
|
103
|
+
this.loadD3();
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
#eff = effect(() => {
|
|
107
|
+
// Access inputs to track them
|
|
108
|
+
this.data();
|
|
109
|
+
// this.effectiveOptions();
|
|
110
|
+
// Only update if already rendered
|
|
111
|
+
untracked(() => {
|
|
112
|
+
if (this._rendered()) {
|
|
113
|
+
this.updateChart();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
/**
|
|
118
|
+
* Highlights a specific segment by ID
|
|
119
|
+
* @param id The segment ID to highlight, or null to clear highlight
|
|
120
|
+
*/
|
|
121
|
+
highlightSegment(id) {
|
|
122
|
+
if (this._isInitialAnimating())
|
|
123
|
+
return;
|
|
124
|
+
if (!this.svg)
|
|
125
|
+
return;
|
|
126
|
+
const transitionDuration = 150;
|
|
127
|
+
// Helper to unhighlight all segments and reset them to their normal state
|
|
128
|
+
const unhighlightAllSegments = () => {
|
|
129
|
+
this.svg
|
|
130
|
+
.selectAll('path')
|
|
131
|
+
.classed('ax-donut-chart-highlighted', false)
|
|
132
|
+
.classed('ax-donut-chart-dimmed', false)
|
|
133
|
+
.transition()
|
|
134
|
+
.duration(transitionDuration)
|
|
135
|
+
.attr('transform', 'scale(1)')
|
|
136
|
+
.style('filter', null)
|
|
137
|
+
.style('opacity', 1);
|
|
138
|
+
this.segmentHover.emit(null);
|
|
139
|
+
};
|
|
140
|
+
// Helper to get full data for the segment ID
|
|
141
|
+
const getSegmentDataById = (segmentId) => {
|
|
142
|
+
return untracked(() => this.chartDataArray()).find((d) => d.id === segmentId) || null;
|
|
143
|
+
};
|
|
144
|
+
if (id === null) {
|
|
145
|
+
unhighlightAllSegments();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const targetSegment = this.svg.selectAll('path').filter((d) => d?.data?.id === id);
|
|
149
|
+
if (targetSegment.empty()) {
|
|
150
|
+
unhighlightAllSegments(); // Target not found, so unhighlight all
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const isCurrentlyHighlighted = targetSegment.classed('ax-donut-chart-highlighted');
|
|
154
|
+
if (isCurrentlyHighlighted) {
|
|
155
|
+
unhighlightAllSegments(); // Toggle off if already highlighted
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
// Dim all segments that are NOT the target
|
|
159
|
+
this.svg
|
|
160
|
+
.selectAll('path')
|
|
161
|
+
.filter((d) => d?.data?.id !== id)
|
|
162
|
+
.classed('ax-donut-chart-highlighted', false) // Ensure not highlighted
|
|
163
|
+
.classed('ax-donut-chart-dimmed', true)
|
|
164
|
+
.transition()
|
|
165
|
+
.duration(transitionDuration)
|
|
166
|
+
.attr('transform', 'scale(1)') // Reset scale if it was previously highlighted
|
|
167
|
+
.style('filter', null) // Remove filter if it had one
|
|
168
|
+
.style('opacity', 0.5); // Dim opacity
|
|
169
|
+
// Highlight the target segment
|
|
170
|
+
targetSegment
|
|
171
|
+
.classed('ax-donut-chart-dimmed', false) // Ensure not dimmed
|
|
172
|
+
.classed('ax-donut-chart-highlighted', true)
|
|
173
|
+
.transition()
|
|
174
|
+
.duration(transitionDuration)
|
|
175
|
+
.attr('transform', 'scale(1.02)')
|
|
176
|
+
.style('filter', 'url(#ax-donut-chart-segment-shadow)') // Apply shadow
|
|
177
|
+
.style('opacity', 1); // Ensure full opacity
|
|
178
|
+
this.segmentHover.emit(getSegmentDataById(id));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Toggles visibility of a segment by ID
|
|
183
|
+
* @param id Segment ID to toggle
|
|
184
|
+
* @returns New visibility state (true = visible, false = hidden)
|
|
185
|
+
*/
|
|
186
|
+
toggleSegment(id) {
|
|
187
|
+
const wasAllHidden = this.chartDataArray().length > 0 && this.chartDataArray().every((d) => this.isSegmentHidden(d.id));
|
|
188
|
+
if (this.hiddenSegments.has(id)) {
|
|
189
|
+
// Making a segment visible
|
|
190
|
+
this.hiddenSegments.delete(id);
|
|
191
|
+
// If all segments were previously hidden, we need to ensure the message is cleared
|
|
192
|
+
if (wasAllHidden) {
|
|
193
|
+
// Force complete DOM cleanup and redraw
|
|
194
|
+
if (this.chartContainerEl()?.nativeElement) {
|
|
195
|
+
// Clear everything in the container
|
|
196
|
+
this.d3?.select(this.chartContainerEl().nativeElement).selectAll('*').remove();
|
|
197
|
+
// Force full redraw
|
|
198
|
+
setTimeout(() => {
|
|
199
|
+
this.createChart();
|
|
200
|
+
}, 0);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
this.updateChart();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// Hiding a segment
|
|
209
|
+
this.hiddenSegments.add(id);
|
|
210
|
+
this.updateChart();
|
|
211
|
+
}
|
|
212
|
+
return !this.isSegmentHidden(id);
|
|
213
|
+
}
|
|
214
|
+
ngOnDestroy() {
|
|
215
|
+
this.cleanupChart();
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Loads D3.js dynamically
|
|
219
|
+
*/
|
|
220
|
+
async loadD3() {
|
|
221
|
+
try {
|
|
222
|
+
this.d3 = await import('d3');
|
|
223
|
+
// If container is ready, create chart
|
|
224
|
+
if (this._initialized() && this.chartContainerEl()) {
|
|
225
|
+
this.createChart();
|
|
226
|
+
this._rendered.set(true);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
console.error('Failed to load D3.js:', error);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
onSegmentClick(item) {
|
|
234
|
+
// this.toggleSegmentVisibility(item.id);
|
|
235
|
+
this.segmentClick.emit(item);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Creates the donut chart
|
|
239
|
+
*/
|
|
240
|
+
createChart() {
|
|
241
|
+
if (!this.d3 || !this.chartContainerEl()?.nativeElement)
|
|
242
|
+
return;
|
|
243
|
+
try {
|
|
244
|
+
const containerElement = this.chartContainerEl().nativeElement;
|
|
245
|
+
this.clearChart(containerElement);
|
|
246
|
+
const data = this.chartDataArray();
|
|
247
|
+
if (!data || data.length === 0) {
|
|
248
|
+
this.showNoDataMessage();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// Filter out hidden segments
|
|
252
|
+
const visibleData = data.filter((item) => !this.hiddenSegments.has(item.id));
|
|
253
|
+
// If all segments are hidden, show message
|
|
254
|
+
if (visibleData.length === 0) {
|
|
255
|
+
this.showAllSegmentsHiddenMessage();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const options = this.effectiveOptions();
|
|
259
|
+
const { width, height } = this.setupDimensions(containerElement, options);
|
|
260
|
+
this.renderDonutChart(containerElement, width, height, visibleData);
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
console.error('Error creating donut chart:', error);
|
|
264
|
+
this.handleChartError();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Updates the chart with new data
|
|
269
|
+
*/
|
|
270
|
+
updateChart() {
|
|
271
|
+
this.createChart(); // Recreate the chart with updated data
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Clears the chart container
|
|
275
|
+
*/
|
|
276
|
+
clearChart(container) {
|
|
277
|
+
this.d3.select(container).selectAll('svg').remove();
|
|
278
|
+
this._tooltipVisible.set(false);
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Shows a message when no data is available
|
|
282
|
+
*/
|
|
283
|
+
showNoDataMessage() {
|
|
284
|
+
if (!this.chartContainerEl()?.nativeElement)
|
|
285
|
+
return;
|
|
286
|
+
const container = this.chartContainerEl().nativeElement;
|
|
287
|
+
this.d3.select(container).selectAll('*').remove();
|
|
288
|
+
const messageContainer = this.d3
|
|
289
|
+
.select(container)
|
|
290
|
+
.append('div')
|
|
291
|
+
// Apply generic container class and specific background class
|
|
292
|
+
.attr('class', 'ax-donut-chart-no-data-message');
|
|
293
|
+
messageContainer
|
|
294
|
+
.append('div')
|
|
295
|
+
// Apply generic icon class and specific donut chart icon class
|
|
296
|
+
.attr('class', 'ax-chart-message-icon ax-donut-chart-no-data-icon')
|
|
297
|
+
.html('<i class="fa-light fa-chart-pie fa-2x"></i>'); // Donut chart icon
|
|
298
|
+
messageContainer
|
|
299
|
+
.append('div')
|
|
300
|
+
// Apply generic text class and specific donut chart text class
|
|
301
|
+
.attr('class', 'ax-chart-message-text ax-donut-chart-no-data-text')
|
|
302
|
+
.text('No data available');
|
|
303
|
+
messageContainer
|
|
304
|
+
.append('div')
|
|
305
|
+
// Apply generic help class and specific donut chart help class
|
|
306
|
+
.attr('class', 'ax-chart-message-help ax-donut-chart-no-data-help')
|
|
307
|
+
.text('Please provide data to display the chart');
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Shows a message when all segments are hidden
|
|
311
|
+
*/
|
|
312
|
+
showAllSegmentsHiddenMessage() {
|
|
313
|
+
if (!this.chartContainerEl()?.nativeElement)
|
|
314
|
+
return;
|
|
315
|
+
const container = this.chartContainerEl().nativeElement;
|
|
316
|
+
this.d3.select(container).selectAll('*').remove();
|
|
317
|
+
const messageContainer = this.d3
|
|
318
|
+
.select(container)
|
|
319
|
+
.append('div')
|
|
320
|
+
// Apply generic container class and specific background class
|
|
321
|
+
.attr('class', 'ax-donut-chart-no-data-message');
|
|
322
|
+
messageContainer
|
|
323
|
+
.append('div')
|
|
324
|
+
// Apply generic icon class and specific donut chart icon class if any distinction is needed later
|
|
325
|
+
.attr('class', 'ax-chart-message-icon ax-donut-chart-no-data-icon')
|
|
326
|
+
.html('<i class="fa-light fa-eye-slash fa-2x"></i>');
|
|
327
|
+
messageContainer
|
|
328
|
+
.append('div')
|
|
329
|
+
// Apply generic text class and specific donut chart text class if needed
|
|
330
|
+
.attr('class', 'ax-chart-message-text ax-donut-chart-no-data-text')
|
|
331
|
+
.text('All segments are hidden');
|
|
332
|
+
messageContainer
|
|
333
|
+
.append('div')
|
|
334
|
+
// Apply generic help class and specific donut chart help class if needed
|
|
335
|
+
.attr('class', 'ax-chart-message-help ax-donut-chart-no-data-help')
|
|
336
|
+
.text('Please toggle visibility of at least one segment');
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Setups chart dimensions based on container and options
|
|
340
|
+
*/
|
|
341
|
+
setupDimensions(container, options) {
|
|
342
|
+
// Get container dimensions or use defaults
|
|
343
|
+
const containerWidth = container.clientWidth || 400;
|
|
344
|
+
const containerHeight = container.clientHeight || 400;
|
|
345
|
+
// Ensure minimum dimensions for the chart
|
|
346
|
+
const minDim = 200;
|
|
347
|
+
const width = Math.max(options?.width || containerWidth, minDim);
|
|
348
|
+
const height = Math.max(options?.height || containerHeight, minDim);
|
|
349
|
+
return { width, height };
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Renders the donut chart with visible data
|
|
353
|
+
*/
|
|
354
|
+
renderDonutChart(container, width, height, visibleData) {
|
|
355
|
+
const total = visibleData.reduce((sum, item) => sum + item.value, 0);
|
|
356
|
+
// Create SVG container with filters
|
|
357
|
+
const svg = this.createSvgWithFilters(container, width, height);
|
|
358
|
+
// Create main chart group
|
|
359
|
+
this.svg = svg.append('g').attr('transform', `translate(${width / 2}, ${height / 2})`);
|
|
360
|
+
// Create donut segments
|
|
361
|
+
this.createDonutSegments(width, height, visibleData, total);
|
|
362
|
+
// Add total in center
|
|
363
|
+
this.addCenterDisplay(total);
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Create SVG element with filter definitions for shadows
|
|
367
|
+
*/
|
|
368
|
+
createSvgWithFilters(container, width, height) {
|
|
369
|
+
const svg = this.d3
|
|
370
|
+
.select(container)
|
|
371
|
+
.append('svg')
|
|
372
|
+
.attr('width', '100%')
|
|
373
|
+
.attr('height', '100%')
|
|
374
|
+
.attr('viewBox', `0 0 ${width} ${height}`)
|
|
375
|
+
.attr('preserveAspectRatio', 'xMidYMid meet');
|
|
376
|
+
// Add drop shadow filter
|
|
377
|
+
const defs = svg.append('defs');
|
|
378
|
+
const filter = defs.append('filter').attr('id', 'ax-donut-chart-segment-shadow').attr('height', '130%');
|
|
379
|
+
filter.append('feGaussianBlur').attr('in', 'SourceAlpha').attr('stdDeviation', 2).attr('result', 'blur');
|
|
380
|
+
filter.append('feOffset').attr('in', 'blur').attr('dx', 1).attr('dy', 1).attr('result', 'offsetBlur');
|
|
381
|
+
const feComponentTransfer = filter
|
|
382
|
+
.append('feComponentTransfer')
|
|
383
|
+
.attr('in', 'offsetBlur')
|
|
384
|
+
.attr('result', 'offsetBlur');
|
|
385
|
+
feComponentTransfer.append('feFuncA').attr('type', 'linear').attr('slope', 0.3);
|
|
386
|
+
const feMerge = filter.append('feMerge');
|
|
387
|
+
feMerge.append('feMergeNode').attr('in', 'offsetBlur');
|
|
388
|
+
feMerge.append('feMergeNode').attr('in', 'SourceGraphic');
|
|
389
|
+
return svg;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Create and render the donut segments with animations
|
|
393
|
+
*/
|
|
394
|
+
createDonutSegments(chartWidth, chartHeight, data, total) {
|
|
395
|
+
this._isInitialAnimating.set(true);
|
|
396
|
+
// Create pie layout
|
|
397
|
+
const pie = this.d3
|
|
398
|
+
.pie()
|
|
399
|
+
.value((d) => d.value)
|
|
400
|
+
.sort(null)
|
|
401
|
+
.padAngle(0.02);
|
|
402
|
+
// Calculate the radius of the donut chart
|
|
403
|
+
const radius = (Math.min(chartWidth, chartHeight) / 2) * 0.85;
|
|
404
|
+
// Calculate inner radius based on donutWidth percentage
|
|
405
|
+
const donutWidthPercent = this.effectiveOptions().donutWidth / 100;
|
|
406
|
+
const innerRadius = radius * (1 - donutWidthPercent);
|
|
407
|
+
// Create arc generator with the configured radius and corner radius
|
|
408
|
+
const arc = this.d3
|
|
409
|
+
.arc()
|
|
410
|
+
.innerRadius(innerRadius)
|
|
411
|
+
.outerRadius(radius * 0.95)
|
|
412
|
+
.cornerRadius(this.effectiveOptions().cornerRadius);
|
|
413
|
+
// Create hover arc for animation
|
|
414
|
+
const hoverArc = this.d3
|
|
415
|
+
.arc()
|
|
416
|
+
.innerRadius(innerRadius)
|
|
417
|
+
.outerRadius(radius + 10)
|
|
418
|
+
.cornerRadius(this.effectiveOptions().cornerRadius);
|
|
419
|
+
// Create label arc generator for placing labels (middle of the donut ring)
|
|
420
|
+
const labelArc = this.d3
|
|
421
|
+
.arc()
|
|
422
|
+
.innerRadius(innerRadius + (radius * 0.95 - innerRadius) / 2)
|
|
423
|
+
.outerRadius(innerRadius + (radius * 0.95 - innerRadius) / 2);
|
|
424
|
+
// Get animation options
|
|
425
|
+
const animationDuration = this.effectiveOptions().animationDuration;
|
|
426
|
+
const animationEasing = this.getEasingFunction(this.effectiveOptions().animationEasing);
|
|
427
|
+
// Generate pie data
|
|
428
|
+
this.pieData = pie(data);
|
|
429
|
+
// Add segments with animation
|
|
430
|
+
const segments = this.svg
|
|
431
|
+
.selectAll('path')
|
|
432
|
+
.data(this.pieData)
|
|
433
|
+
.enter()
|
|
434
|
+
.append('path')
|
|
435
|
+
.attr('class', 'ax-donut-chart-segment')
|
|
436
|
+
.attr('fill', (d /* d is PieArcDatum<AXDonutChartData> */) => {
|
|
437
|
+
const chartItem = d.data; // AXDonutChartData from visibleData
|
|
438
|
+
if (chartItem.color) {
|
|
439
|
+
// Prioritize explicit color on the item
|
|
440
|
+
return chartItem.color;
|
|
441
|
+
}
|
|
442
|
+
// Fallback: find original index for consistent palette color
|
|
443
|
+
const originalFullData = untracked(() => this.chartDataArray());
|
|
444
|
+
const originalIndex = originalFullData.findIndex((item) => item.id === chartItem.id);
|
|
445
|
+
return this.getColor(originalIndex !== -1 ? originalIndex : 0); // Ensure valid index
|
|
446
|
+
})
|
|
447
|
+
.style('opacity', 0)
|
|
448
|
+
.style('cursor', 'pointer') // Add cursor pointer to segments
|
|
449
|
+
.on('mouseenter', (event, d) => {
|
|
450
|
+
if (!this.effectiveOptions().showTooltip)
|
|
451
|
+
return;
|
|
452
|
+
this.handleSliceHover(event, d.data);
|
|
453
|
+
})
|
|
454
|
+
.on('mouseleave', (event, d) => {
|
|
455
|
+
this.handleSegmentLeave(event, d, arc);
|
|
456
|
+
})
|
|
457
|
+
.on('mousemove', (event) => {
|
|
458
|
+
if (this._tooltipVisible()) {
|
|
459
|
+
this.updateTooltipPosition(event);
|
|
460
|
+
}
|
|
461
|
+
})
|
|
462
|
+
.on('click', (event, d) => {
|
|
463
|
+
this.onSegmentClick(d.data);
|
|
464
|
+
});
|
|
465
|
+
// Animate segments
|
|
466
|
+
segments
|
|
467
|
+
.transition()
|
|
468
|
+
.duration(animationDuration)
|
|
469
|
+
.ease(animationEasing)
|
|
470
|
+
.delay((d, i) => i * 50)
|
|
471
|
+
.style('opacity', 1)
|
|
472
|
+
.attrTween('d', (d) => {
|
|
473
|
+
const interpolate = this.d3.interpolate({ startAngle: d.startAngle, endAngle: d.startAngle }, d);
|
|
474
|
+
return (t) => arc(interpolate(t));
|
|
475
|
+
});
|
|
476
|
+
// Add data labels if enabled
|
|
477
|
+
if (this.effectiveOptions().showDataLabels) {
|
|
478
|
+
// Calculate optimal font size based on segment size and chart dimensions
|
|
479
|
+
const calculateFontSize = (d) => {
|
|
480
|
+
// Calculate angle size in radians for the segment
|
|
481
|
+
const angleSize = d.endAngle - d.startAngle;
|
|
482
|
+
// Calculate the segment's percentage of the whole
|
|
483
|
+
const segmentPercentage = (d.data.value / total) * 100;
|
|
484
|
+
// Base minimum font size on segment size
|
|
485
|
+
const minFontSize = segmentPercentage < 5
|
|
486
|
+
? 0 // Hide very small segments
|
|
487
|
+
: segmentPercentage < 10
|
|
488
|
+
? 7 // Smaller font for small segments
|
|
489
|
+
: segmentPercentage < 15
|
|
490
|
+
? 8 // Medium font for medium segments
|
|
491
|
+
: 9; // Regular font for large segments
|
|
492
|
+
// Adjust font size based on chart size
|
|
493
|
+
return Math.min(Math.max(minFontSize, (angleSize * radius) / 10), 12);
|
|
494
|
+
};
|
|
495
|
+
// Format percentage value with appropriate precision
|
|
496
|
+
const formatPercentage = (value) => {
|
|
497
|
+
if (value < 1)
|
|
498
|
+
return '<1%';
|
|
499
|
+
if (value < 10)
|
|
500
|
+
return `${value.toFixed(1)}%`;
|
|
501
|
+
return `${Math.round(value)}%`;
|
|
502
|
+
};
|
|
503
|
+
const labels = this.svg
|
|
504
|
+
.selectAll('.ax-donut-chart-data-label')
|
|
505
|
+
.data(this.pieData)
|
|
506
|
+
.enter()
|
|
507
|
+
.append('text')
|
|
508
|
+
.attr('class', 'ax-donut-chart-data-label')
|
|
509
|
+
.style('opacity', 0)
|
|
510
|
+
.style('fill', 'rgb(var(--ax-comp-donut-chart-data-labels-color))')
|
|
511
|
+
.attr('transform', (d) => {
|
|
512
|
+
// Calculate the centroid position for the label
|
|
513
|
+
const centroid = labelArc.centroid(d);
|
|
514
|
+
return `translate(${centroid[0]}, ${centroid[1]})`;
|
|
515
|
+
})
|
|
516
|
+
.attr('text-anchor', 'middle')
|
|
517
|
+
.attr('dominant-baseline', 'middle')
|
|
518
|
+
.style('font-size', (d) => `${calculateFontSize(d)}px`)
|
|
519
|
+
.style('font-weight', (d) => ((d.data.value / total) * 100 >= 15 ? '600' : '500'))
|
|
520
|
+
.text((d) => {
|
|
521
|
+
// Calculate percentage for labels
|
|
522
|
+
const percentage = (d.data.value / total) * 100;
|
|
523
|
+
// Only show if segment is large enough to display text
|
|
524
|
+
if (percentage < 1)
|
|
525
|
+
return '';
|
|
526
|
+
const label = d.data.label || '';
|
|
527
|
+
const percentageText = formatPercentage(percentage);
|
|
528
|
+
return label ? `${label} (${percentageText})` : percentageText;
|
|
529
|
+
});
|
|
530
|
+
// Animate labels
|
|
531
|
+
labels
|
|
532
|
+
.transition()
|
|
533
|
+
.duration(animationDuration)
|
|
534
|
+
.delay((d, i) => i * 50 + 200)
|
|
535
|
+
.style('opacity', 1);
|
|
536
|
+
}
|
|
537
|
+
// Determine when all animations are complete
|
|
538
|
+
const numSegments = this.pieData.length;
|
|
539
|
+
let maxIndividualDelay = 0;
|
|
540
|
+
if (numSegments > 0) {
|
|
541
|
+
// Base delay for segments
|
|
542
|
+
maxIndividualDelay = (numSegments - 1) * 50;
|
|
543
|
+
if (this.effectiveOptions().showDataLabels) {
|
|
544
|
+
// Labels have an additional fixed delay
|
|
545
|
+
maxIndividualDelay = Math.max(maxIndividualDelay, (numSegments - 1) * 50 + 200);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
const totalAnimationCycleTime = maxIndividualDelay + animationDuration;
|
|
549
|
+
setTimeout(() => {
|
|
550
|
+
this._isInitialAnimating.set(false);
|
|
551
|
+
this.cdr.detectChanges();
|
|
552
|
+
}, totalAnimationCycleTime);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Gets the appropriate D3 easing function based on the option string
|
|
556
|
+
*/
|
|
557
|
+
getEasingFunction(easing) {
|
|
558
|
+
switch (easing) {
|
|
559
|
+
case 'linear':
|
|
560
|
+
return this.d3.easeLinear;
|
|
561
|
+
case 'ease':
|
|
562
|
+
return this.d3.easePolyInOut;
|
|
563
|
+
case 'ease-in':
|
|
564
|
+
return this.d3.easePolyIn;
|
|
565
|
+
case 'ease-out':
|
|
566
|
+
return this.d3.easePolyOut;
|
|
567
|
+
case 'ease-in-out':
|
|
568
|
+
return this.d3.easePolyInOut;
|
|
569
|
+
case 'cubic':
|
|
570
|
+
return this.d3.easeCubic;
|
|
571
|
+
case 'cubic-in':
|
|
572
|
+
return this.d3.easeCubicIn;
|
|
573
|
+
case 'cubic-out':
|
|
574
|
+
return this.d3.easeCubicOut;
|
|
575
|
+
case 'cubic-in-out':
|
|
576
|
+
return this.d3.easeCubicInOut;
|
|
577
|
+
default:
|
|
578
|
+
return this.d3.easeCubicOut; // Default easing
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Handle hover effects on a segment
|
|
583
|
+
*/
|
|
584
|
+
handleSliceHover(event, datum) {
|
|
585
|
+
if (this._isInitialAnimating())
|
|
586
|
+
return;
|
|
587
|
+
if (this.effectiveOptions().showTooltip !== false) {
|
|
588
|
+
const index = this.data().findIndex((item) => item.id === datum.id);
|
|
589
|
+
const color = datum.color || getChartColor(index, this.chartColors);
|
|
590
|
+
// Calculate percentage of total
|
|
591
|
+
// Ensure data() is accessed within a tracking context if it's a signal, or use untracked if appropriate
|
|
592
|
+
const total = untracked(() => this.data()).reduce((sum, item) => sum + item.value, 0);
|
|
593
|
+
const percentage = total > 0 ? ((datum.value / total) * 100).toFixed(1) : '0';
|
|
594
|
+
this._tooltipData.set({
|
|
595
|
+
title: datum.tooltipLabel || datum.label,
|
|
596
|
+
value: datum.value.toString(),
|
|
597
|
+
percentage: `${percentage}%`,
|
|
598
|
+
color: color,
|
|
599
|
+
});
|
|
600
|
+
this.updateTooltipPosition(event);
|
|
601
|
+
this._tooltipVisible.set(true);
|
|
602
|
+
}
|
|
603
|
+
this.highlightSegment(datum.id); // This will now also handle emitting segmentHover
|
|
604
|
+
// No direct segmentHover.emit(datum) here anymore
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Handles mouse leave from segments
|
|
608
|
+
*/
|
|
609
|
+
handleSegmentLeave(event, d, normalArc) {
|
|
610
|
+
if (this._isInitialAnimating())
|
|
611
|
+
return;
|
|
612
|
+
// Hide tooltip
|
|
613
|
+
this._tooltipVisible.set(false);
|
|
614
|
+
this.cdr.detectChanges();
|
|
615
|
+
// Emit null to indicate no segment is hovered - highlightSegment will handle this.
|
|
616
|
+
// this.segmentHover.emit(null); // Removed
|
|
617
|
+
// Remove hover effect (visual style related to classes and scale)
|
|
618
|
+
// Calling highlightSegment with the ID of the segment being left.
|
|
619
|
+
// If it was highlighted, this will toggle it off and emit null for segmentHover.
|
|
620
|
+
this.highlightSegment(d.data.id);
|
|
621
|
+
// Animate the arc back to normal for the specific segment being left.
|
|
622
|
+
this.d3
|
|
623
|
+
.select(event.currentTarget)
|
|
624
|
+
.transition()
|
|
625
|
+
.duration(200)
|
|
626
|
+
.attr('d', (arcData) => normalArc(arcData)); // Ensure arcData is used if 'd' is shadowed
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Updates tooltip position
|
|
630
|
+
* Ensures the tooltip is visible by adjusting position when near edges
|
|
631
|
+
*/
|
|
632
|
+
updateTooltipPosition(event) {
|
|
633
|
+
const container = this.chartContainerEl().nativeElement.getBoundingClientRect();
|
|
634
|
+
const tooltipEl = this.chartContainerEl().nativeElement.querySelector('.chart-tooltip');
|
|
635
|
+
if (!tooltipEl) {
|
|
636
|
+
const x = event.clientX - container.left;
|
|
637
|
+
const y = event.clientY - container.top;
|
|
638
|
+
this._tooltipPosition.set({ x, y });
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
const tooltipRect = tooltipEl.getBoundingClientRect();
|
|
642
|
+
const cursorX = event.clientX - container.left;
|
|
643
|
+
const cursorY = event.clientY - container.top;
|
|
644
|
+
const gap = 20; // Gap between cursor and tooltip
|
|
645
|
+
// Position tooltip to the right by default
|
|
646
|
+
let x = cursorX + gap / 3;
|
|
647
|
+
// If tooltip would go off the right edge, position it to the left
|
|
648
|
+
if (x + tooltipRect.width > container.width) {
|
|
649
|
+
x = cursorX - tooltipRect.width - gap;
|
|
650
|
+
}
|
|
651
|
+
// Keep tooltip within container bounds
|
|
652
|
+
x = Math.max(gap, Math.min(x, container.width - tooltipRect.width - gap));
|
|
653
|
+
const y = Math.max(gap, Math.min(cursorY, container.height - tooltipRect.height - gap));
|
|
654
|
+
this._tooltipPosition.set({ x, y });
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Adds center display with total value
|
|
658
|
+
*/
|
|
659
|
+
addCenterDisplay(total) {
|
|
660
|
+
if (!this.svg)
|
|
661
|
+
return;
|
|
662
|
+
// Calculate appropriate font sizes based on chart dimensions
|
|
663
|
+
const chartContainerWidth = this.chartContainerEl().nativeElement.clientWidth;
|
|
664
|
+
const baseFontSize = Math.max(1.4, Math.min(2.4, chartContainerWidth / 200)); // Scale between 1.4rem and 2.4rem
|
|
665
|
+
const subTextFontSize = baseFontSize * 0.5;
|
|
666
|
+
// Create group for the total display
|
|
667
|
+
const totalDisplay = this.svg
|
|
668
|
+
.append('g')
|
|
669
|
+
.attr('class', 'ax-donut-chart-total-display')
|
|
670
|
+
.attr('text-anchor', 'middle');
|
|
671
|
+
// Add total value
|
|
672
|
+
totalDisplay
|
|
673
|
+
.append('text')
|
|
674
|
+
.attr('class', 'ax-donut-chart-total-value')
|
|
675
|
+
.style('font-size', `${baseFontSize}rem`)
|
|
676
|
+
.style('font-weight', '600')
|
|
677
|
+
.style('fill', 'rgb(var(--ax-comp-donut-chart-value-color))')
|
|
678
|
+
.text(total.toLocaleString());
|
|
679
|
+
// Add label
|
|
680
|
+
totalDisplay
|
|
681
|
+
.append('text')
|
|
682
|
+
.attr('class', 'ax-donut-chart-total-label')
|
|
683
|
+
.attr('dy', '1.4em')
|
|
684
|
+
.style('font-size', `${subTextFontSize}rem`)
|
|
685
|
+
.style('fill', 'rgb(var(--ax-comp-donut-chart-value-color))')
|
|
686
|
+
.style('opacity', '0.8')
|
|
687
|
+
.text(this.effectiveOptions().totalLabel || 'Total');
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Handles chart rendering errors
|
|
691
|
+
*/
|
|
692
|
+
handleChartError() {
|
|
693
|
+
const container = this.chartContainerEl()?.nativeElement;
|
|
694
|
+
if (container) {
|
|
695
|
+
this.showNoDataMessage();
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Cleans up chart resources
|
|
700
|
+
*/
|
|
701
|
+
cleanupChart() {
|
|
702
|
+
if (this.svg) {
|
|
703
|
+
this.d3.select(this.chartContainerEl()?.nativeElement).selectAll('svg').remove();
|
|
704
|
+
this.svg = null;
|
|
705
|
+
this.pieData = [];
|
|
706
|
+
}
|
|
707
|
+
this.hiddenSegments.clear();
|
|
708
|
+
this._tooltipVisible.set(false);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Gets an accessibility label describing the donut chart for screen readers
|
|
712
|
+
*/
|
|
713
|
+
getAccessibilityLabel() {
|
|
714
|
+
const data = this.chartDataArray();
|
|
715
|
+
if (!data || data.length === 0) {
|
|
716
|
+
return 'Empty donut chart. No data available.';
|
|
717
|
+
}
|
|
718
|
+
// Calculate total
|
|
719
|
+
const total = data.reduce((sum, item) => sum + item.value, 0);
|
|
720
|
+
// Generate a description of the chart with percentages
|
|
721
|
+
const segmentDescriptions = data
|
|
722
|
+
.map((segment) => {
|
|
723
|
+
const percentage = ((segment.value / total) * 100).toFixed(1);
|
|
724
|
+
return `${segment.label}: ${segment.value} (${percentage}%)`;
|
|
725
|
+
})
|
|
726
|
+
.join('; ');
|
|
727
|
+
return `Donut chart with ${data.length} segments. Total value: ${total}. ${segmentDescriptions}`;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Returns the data items for the legend
|
|
731
|
+
* @implements AXChartLegendCompatible
|
|
732
|
+
*/
|
|
733
|
+
getLegendItems() {
|
|
734
|
+
return this.chartDataArray().map((item, index) => {
|
|
735
|
+
return {
|
|
736
|
+
id: item.id,
|
|
737
|
+
name: item.label || `Segment ${index + 1}`,
|
|
738
|
+
value: item.value,
|
|
739
|
+
color: item.color || this.getColor(index),
|
|
740
|
+
percentage: (item.value / this.data().reduce((sum, item) => sum + item.value, 0)) * 100,
|
|
741
|
+
hidden: this.isSegmentHidden(item.id),
|
|
742
|
+
};
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: AXDonutChartComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
746
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.0.4", 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 }], ngImport: i0, template: "<div class=\"ax-donut-chart\" #chartContainer role=\"img\" [attr.aria-label]=\"getAccessibilityLabel()\" tabindex=\"0\">\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%;--ax-comp-donut-chart-bg-color: var(--ax-sys-color-lightest-surface);--ax-comp-donut-chart-data-labels-color: var(--ax-sys-color-dark);--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;display:flex;align-items:center;justify-content:center;border-radius:.5rem;overflow:hidden;color:rgb(var(--ax-comp-donut-chart-data-labels-color));background-color:rgb(var(--ax-comp-donut-chart-bg-color))}ax-donut-chart .ax-donut-chart svg{width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}ax-donut-chart .ax-donut-chart-no-data-message{text-align:center;background-color:rgb(var(--ax-comp-donut-chart-bg-color));padding:1.5rem;border-radius:.5rem;box-shadow:0 2px 12px #00000014;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 });
|
|
747
|
+
}
|
|
748
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.0.4", ngImport: i0, type: AXDonutChartComponent, decorators: [{
|
|
749
|
+
type: Component,
|
|
750
|
+
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()\" tabindex=\"0\">\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%;--ax-comp-donut-chart-bg-color: var(--ax-sys-color-lightest-surface);--ax-comp-donut-chart-data-labels-color: var(--ax-sys-color-dark);--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;display:flex;align-items:center;justify-content:center;border-radius:.5rem;overflow:hidden;color:rgb(var(--ax-comp-donut-chart-data-labels-color));background-color:rgb(var(--ax-comp-donut-chart-bg-color))}ax-donut-chart .ax-donut-chart svg{width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}ax-donut-chart .ax-donut-chart-no-data-message{text-align:center;background-color:rgb(var(--ax-comp-donut-chart-bg-color));padding:1.5rem;border-radius:.5rem;box-shadow:0 2px 12px #00000014;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"] }]
|
|
751
|
+
}], ctorParameters: () => [] });
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Generated bundle index. Do not edit.
|
|
755
|
+
*/
|
|
756
|
+
|
|
757
|
+
export { AXDonutChartComponent, AXDonutChartDefaultConfig, AX_DONUT_CHART_CONFIG, donutChartConfig };
|
|
758
|
+
//# sourceMappingURL=acorex-charts-donut-chart.mjs.map
|