@acorex/charts 19.13.2

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 (46) hide show
  1. package/README.md +72 -0
  2. package/bar-chart/README.md +3 -0
  3. package/bar-chart/index.d.ts +3 -0
  4. package/bar-chart/lib/bar-chart.component.d.ts +123 -0
  5. package/bar-chart/lib/bar-chart.config.d.ts +6 -0
  6. package/bar-chart/lib/bar-chart.type.d.ts +44 -0
  7. package/chart-tooltip/README.md +3 -0
  8. package/chart-tooltip/index.d.ts +2 -0
  9. package/chart-tooltip/lib/chart-tooltip.component.d.ts +43 -0
  10. package/chart-tooltip/lib/chart-tooltip.type.d.ts +7 -0
  11. package/donut-chart/README.md +3 -0
  12. package/donut-chart/index.d.ts +3 -0
  13. package/donut-chart/lib/donut-chart.component.d.ts +143 -0
  14. package/donut-chart/lib/donut-chart.config.d.ts +6 -0
  15. package/donut-chart/lib/donut-chart.type.d.ts +25 -0
  16. package/fesm2022/acorex-charts-bar-chart.mjs +563 -0
  17. package/fesm2022/acorex-charts-bar-chart.mjs.map +1 -0
  18. package/fesm2022/acorex-charts-chart-tooltip.mjs +75 -0
  19. package/fesm2022/acorex-charts-chart-tooltip.mjs.map +1 -0
  20. package/fesm2022/acorex-charts-donut-chart.mjs +616 -0
  21. package/fesm2022/acorex-charts-donut-chart.mjs.map +1 -0
  22. package/fesm2022/acorex-charts-gauge-chart.mjs +548 -0
  23. package/fesm2022/acorex-charts-gauge-chart.mjs.map +1 -0
  24. package/fesm2022/acorex-charts-hierarchy-chart.mjs +652 -0
  25. package/fesm2022/acorex-charts-hierarchy-chart.mjs.map +1 -0
  26. package/fesm2022/acorex-charts-line-chart.mjs +738 -0
  27. package/fesm2022/acorex-charts-line-chart.mjs.map +1 -0
  28. package/fesm2022/acorex-charts.mjs +8 -0
  29. package/fesm2022/acorex-charts.mjs.map +1 -0
  30. package/gauge-chart/README.md +3 -0
  31. package/gauge-chart/index.d.ts +3 -0
  32. package/gauge-chart/lib/gauge-chart.component.d.ts +110 -0
  33. package/gauge-chart/lib/gauge-chart.config.d.ts +6 -0
  34. package/gauge-chart/lib/gauge-chart.type.d.ts +37 -0
  35. package/hierarchy-chart/README.md +61 -0
  36. package/hierarchy-chart/index.d.ts +3 -0
  37. package/hierarchy-chart/lib/hierarchy-chart.component.d.ts +99 -0
  38. package/hierarchy-chart/lib/hierarchy-chart.config.d.ts +6 -0
  39. package/hierarchy-chart/lib/hierarchy-chart.type.d.ts +227 -0
  40. package/index.d.ts +1 -0
  41. package/line-chart/README.md +3 -0
  42. package/line-chart/index.d.ts +3 -0
  43. package/line-chart/lib/line-chart.component.d.ts +96 -0
  44. package/line-chart/lib/line-chart.config.d.ts +6 -0
  45. package/line-chart/lib/line-chart.type.d.ts +61 -0
  46. package/package.json +48 -0
@@ -0,0 +1,738 @@
1
+ import { AXChartTooltipComponent } from '@acorex/charts/chart-tooltip';
2
+ import { NXComponent } from '@acorex/components/common';
3
+ import { CommonModule } from '@angular/common';
4
+ import * as i0 from '@angular/core';
5
+ import { InjectionToken, inject, input, output, viewChild, signal, computed, effect, ChangeDetectionStrategy, Component } from '@angular/core';
6
+ import { AX_GLOBAL_CONFIG } from '@acorex/core/config';
7
+ import { set } from 'lodash-es';
8
+
9
+ const AXLineChartDefaultConfig = {
10
+ margins: {
11
+ top: 20,
12
+ right: 25,
13
+ bottom: 40,
14
+ left: 50,
15
+ },
16
+ showXAxis: true,
17
+ showYAxis: true,
18
+ showGrid: true,
19
+ showVerticalGrid: false,
20
+ yAxisStartsAtZero: true,
21
+ axisPadding: 5,
22
+ showTooltip: true,
23
+ lineWidth: 2,
24
+ showPoints: true,
25
+ pointRadius: 4,
26
+ smoothLine: false,
27
+ fillArea: false,
28
+ fillOpacity: 20,
29
+ showCrosshair: false,
30
+ animationDuration: 1000,
31
+ animationEasing: 'cubic-out',
32
+ };
33
+ const AX_LINE_CHART_CONFIG = new InjectionToken('AX_LINE_CHART_CONFIG', {
34
+ providedIn: 'root',
35
+ factory: () => {
36
+ const global = inject(AX_GLOBAL_CONFIG);
37
+ set(global, 'chart.lineChart', AXLineChartDefaultConfig);
38
+ return AXLineChartDefaultConfig;
39
+ },
40
+ });
41
+ function lineChartConfig(config = {}) {
42
+ const result = {
43
+ ...AXLineChartDefaultConfig,
44
+ ...config,
45
+ };
46
+ return result;
47
+ }
48
+
49
+ const AXPLineChartColors = {
50
+ defaultColors: [
51
+ '#4361ee',
52
+ '#3a0ca3',
53
+ '#7209b7',
54
+ '#f72585',
55
+ '#4cc9f0',
56
+ '#4895ef',
57
+ '#560bad',
58
+ '#f15bb5',
59
+ '#00bbf9',
60
+ '#00f5d4',
61
+ ],
62
+ getColor: (index, customPalette) => {
63
+ const palette = customPalette || AXPLineChartColors.defaultColors;
64
+ return palette[index % palette.length];
65
+ },
66
+ };
67
+ /**
68
+ * Line Chart Component for rendering data as lines with interactive hover effects and animations
69
+ */
70
+ class AXLineChartComponent extends NXComponent {
71
+ data = input([]);
72
+ options = input({});
73
+ pointClick = output();
74
+ chartContainerEl = viewChild.required('chartContainer');
75
+ d3;
76
+ svg;
77
+ chart;
78
+ xScale;
79
+ yScale;
80
+ xAxis;
81
+ yAxis;
82
+ width;
83
+ height;
84
+ margin = { top: 20, right: 25, bottom: 40, left: 50 };
85
+ _tooltipVisible = signal(false);
86
+ _tooltipPosition = signal({ x: 0, y: 0 });
87
+ _tooltipData = signal({
88
+ title: '',
89
+ value: '0',
90
+ percentage: '0%',
91
+ color: '',
92
+ });
93
+ _initialized = signal(false);
94
+ _rendered = signal(false);
95
+ tooltipVisible = this._tooltipVisible.asReadonly();
96
+ tooltipPosition = this._tooltipPosition.asReadonly();
97
+ tooltipData = this._tooltipData.asReadonly();
98
+ // Inject configuration
99
+ configToken = inject(AX_LINE_CHART_CONFIG);
100
+ effectiveOptions = computed(() => {
101
+ return {
102
+ ...this.configToken,
103
+ ...this.options(),
104
+ };
105
+ });
106
+ ngOnInit() {
107
+ this.loadD3();
108
+ }
109
+ #effect = effect(() => {
110
+ this.data();
111
+ this.effectiveOptions();
112
+ if (this._rendered()) {
113
+ this.updateChart();
114
+ }
115
+ });
116
+ ngAfterViewInit() {
117
+ this._initialized.set(true);
118
+ if (this.d3 && this.chartContainerEl()) {
119
+ this.createChart();
120
+ this._rendered.set(true);
121
+ }
122
+ }
123
+ ngOnDestroy() {
124
+ this.cleanupChart();
125
+ }
126
+ async loadD3() {
127
+ try {
128
+ this.d3 = await import('d3');
129
+ if (this._initialized() && this.chartContainerEl()) {
130
+ this.createChart();
131
+ this._rendered.set(true);
132
+ }
133
+ }
134
+ catch (error) {
135
+ console.error('Failed to load D3.js:', error);
136
+ }
137
+ }
138
+ createChart() {
139
+ if (!this.d3 || !this.chartContainerEl()?.nativeElement)
140
+ return;
141
+ const containerElement = this.chartContainerEl().nativeElement;
142
+ const chartData = this.data();
143
+ let normalizedData = [];
144
+ if (Array.isArray(chartData)) {
145
+ normalizedData = chartData;
146
+ }
147
+ else if (chartData && 'data' in chartData) {
148
+ normalizedData = [chartData];
149
+ }
150
+ this.d3.select(containerElement).selectAll('svg').remove();
151
+ if (normalizedData.length === 0 || normalizedData.some((series) => !series.data || series.data.length === 0)) {
152
+ this.showNoDataMessage(containerElement);
153
+ return;
154
+ }
155
+ const chartOptions = this.effectiveOptions();
156
+ this.setupDimensions(containerElement, chartOptions);
157
+ this.setupScales(normalizedData);
158
+ this.createAxes(chartOptions);
159
+ this.renderLines(normalizedData);
160
+ }
161
+ updateChart() {
162
+ this.createChart();
163
+ }
164
+ cleanupChart() {
165
+ if (this.svg) {
166
+ this.d3?.select(this.chartContainerEl()?.nativeElement).selectAll('svg').remove();
167
+ this.svg = null;
168
+ this.chart = null;
169
+ }
170
+ this._tooltipVisible.set(false);
171
+ }
172
+ setupDimensions(containerElement, options) {
173
+ this.calculateMargins(options);
174
+ const containerWidth = containerElement.clientWidth;
175
+ const containerHeight = containerElement.clientHeight;
176
+ const minDim = Math.min(200, containerWidth, containerHeight);
177
+ if (options.width && options.height) {
178
+ this.width = options.width - this.margin.left - this.margin.right;
179
+ this.height = options.height - this.margin.top - this.margin.bottom;
180
+ }
181
+ else {
182
+ this.width = Math.max(containerWidth, minDim) - this.margin.left - this.margin.right;
183
+ this.height = Math.max(containerHeight, minDim) - this.margin.top - this.margin.bottom;
184
+ }
185
+ this.width = Math.max(this.width, 100);
186
+ this.height = Math.max(this.height, 100);
187
+ const totalWidth = this.width + this.margin.left + this.margin.right;
188
+ const totalHeight = this.height + this.margin.top + this.margin.bottom;
189
+ const svg = this.d3
190
+ .select(containerElement)
191
+ .append('svg')
192
+ .attr('width', '100%')
193
+ .attr('height', '100%')
194
+ .attr('viewBox', `0 0 ${totalWidth} ${totalHeight}`)
195
+ .attr('preserveAspectRatio', 'xMidYMid meet');
196
+ this.svg = svg;
197
+ this.chart = this.svg.append('g').attr('transform', `translate(${this.margin.left},${this.margin.top})`);
198
+ }
199
+ calculateMargins(options) {
200
+ this.margin = {
201
+ top: options.margins?.top ?? 20,
202
+ right: options.margins?.right ?? 25,
203
+ bottom: options.margins?.bottom ?? 40,
204
+ left: options.margins?.left ?? 50,
205
+ };
206
+ if (options.xAxisLabel) {
207
+ const xLabelLength = options.xAxisLabel.length;
208
+ const extraBottomMargin = Math.min(20, Math.max(10, xLabelLength * 0.8));
209
+ this.margin.bottom = Math.max(this.margin.bottom, 40 + extraBottomMargin);
210
+ }
211
+ if (options.yAxisLabel) {
212
+ const yLabelLength = options.yAxisLabel.length;
213
+ const extraLeftMargin = Math.min(20, Math.max(10, yLabelLength * 0.8));
214
+ this.margin.left = Math.max(this.margin.left, 50 + extraLeftMargin);
215
+ }
216
+ if (options.showXAxis !== false) {
217
+ this.margin.bottom = Math.max(this.margin.bottom, 45);
218
+ }
219
+ if (options.showYAxis !== false) {
220
+ this.margin.left = Math.max(this.margin.left, 55);
221
+ }
222
+ }
223
+ setupScales(data) {
224
+ const chartOptions = this.effectiveOptions();
225
+ const padding = chartOptions.axisPadding ?? 0;
226
+ const paddingMultiplier = padding / 100;
227
+ const allDataPoints = data.flatMap((series) => series.data);
228
+ const allNumericX = allDataPoints.every((d) => typeof d.x === 'number');
229
+ const allDates = !allNumericX && allDataPoints.every((d) => !isNaN(new Date(d.x).getTime()));
230
+ if (allNumericX) {
231
+ const xMin = this.d3.min(allDataPoints, (d) => d.x) || 0;
232
+ const xMax = this.d3.max(allDataPoints, (d) => d.x) || 0;
233
+ if (xMin === xMax) {
234
+ this.xScale = this.d3
235
+ .scaleLinear()
236
+ .domain([xMin - 1, xMax + 1])
237
+ .range([0, this.width]);
238
+ }
239
+ else {
240
+ this.xScale = this.d3.scaleLinear().domain([xMin, xMax]).range([0, this.width]);
241
+ }
242
+ }
243
+ else if (allDates) {
244
+ const xMin = this.d3.min(allDataPoints, (d) => new Date(d.x)) || new Date();
245
+ const xMax = this.d3.max(allDataPoints, (d) => new Date(d.x)) || new Date();
246
+ if (xMin.getTime() === xMax.getTime()) {
247
+ const oneDayMs = 86400000;
248
+ this.xScale = this.d3
249
+ .scaleTime()
250
+ .domain([new Date(xMin.getTime() - oneDayMs), new Date(xMax.getTime() + oneDayMs)])
251
+ .range([0, this.width]);
252
+ }
253
+ else {
254
+ this.xScale = this.d3.scaleTime().domain([xMin, xMax]).range([0, this.width]);
255
+ }
256
+ }
257
+ else {
258
+ this.xScale = this.d3
259
+ .scaleBand()
260
+ .domain(allDataPoints.map((d) => String(d.x)))
261
+ .range([0, this.width])
262
+ .paddingInner(0.2)
263
+ .paddingOuter(0);
264
+ }
265
+ const yAxisStartsAtZero = chartOptions.yAxisStartsAtZero !== false;
266
+ const yMin = yAxisStartsAtZero ? 0 : this.d3.min(allDataPoints, (d) => d.y) || 0;
267
+ const yMax = this.d3.max(allDataPoints, (d) => d.y) || 0;
268
+ const yRange = yMax - yMin;
269
+ this.yScale = this.d3
270
+ .scaleLinear()
271
+ .domain([yMin, yMax + yRange * paddingMultiplier])
272
+ .nice()
273
+ .range([this.height, 0]);
274
+ }
275
+ createAxes(options) {
276
+ const showXAxis = options.showXAxis !== false;
277
+ const showYAxis = options.showYAxis !== false;
278
+ const showGrid = options.showGrid !== false;
279
+ const isBandScale = this.xScale.bandwidth !== undefined;
280
+ const axesGroup = this.chart.append('g').attr('class', 'ax-line-chart-axes');
281
+ if (showXAxis) {
282
+ this.xAxis = axesGroup
283
+ .append('g')
284
+ .attr('class', 'ax-line-chart-axis-x')
285
+ .attr('transform', `translate(0,${this.height})`)
286
+ .call(this.d3.axisBottom(this.xScale).tickSize(5).tickPadding(8));
287
+ this.xAxis.selectAll('text').style('font-size', '11px').style('font-weight', '400').style('fill', '#666');
288
+ if (options.xAxisLabel) {
289
+ const labelY = this.height + this.margin.bottom * 0.65;
290
+ axesGroup
291
+ .append('text')
292
+ .attr('class', 'ax-line-chart-axis-label ax-x-axis-label')
293
+ .attr('text-anchor', 'middle')
294
+ .attr('dominant-baseline', 'middle')
295
+ .attr('x', this.width / 2)
296
+ .attr('y', labelY)
297
+ .style('font-size', '13px')
298
+ .style('font-weight', '500')
299
+ .style('fill', '#555')
300
+ .text(options.xAxisLabel);
301
+ }
302
+ }
303
+ if (showYAxis) {
304
+ this.yAxis = axesGroup
305
+ .append('g')
306
+ .attr('class', 'ax-line-chart-axis-y')
307
+ .call(this.d3.axisLeft(this.yScale).tickSize(5).tickPadding(8));
308
+ this.yAxis.selectAll('text').style('font-size', '11px').style('font-weight', '400').style('fill', '#666');
309
+ if (options.yAxisLabel) {
310
+ const labelX = -this.height / 2;
311
+ const labelY = -this.margin.left * 0.65;
312
+ axesGroup
313
+ .append('text')
314
+ .attr('class', 'ax-line-chart-axis-label ax-y-axis-label')
315
+ .attr('text-anchor', 'middle')
316
+ .attr('dominant-baseline', 'middle')
317
+ .attr('transform', 'rotate(-90)')
318
+ .attr('x', labelX)
319
+ .attr('y', labelY)
320
+ .style('font-size', '13px')
321
+ .style('font-weight', '500')
322
+ .style('fill', '#555')
323
+ .text(options.yAxisLabel);
324
+ }
325
+ }
326
+ if (showGrid) {
327
+ const gridGroup = this.chart.append('g').attr('class', 'ax-line-chart-grid-container');
328
+ gridGroup
329
+ .append('g')
330
+ .attr('class', 'ax-line-chart-grid')
331
+ .call(this.d3
332
+ .axisLeft(this.yScale)
333
+ .tickSize(-this.width)
334
+ .tickFormat(() => '')
335
+ .tickValues(this.yScale.ticks()))
336
+ .selectAll('.tick')
337
+ .style('color', 'rgb(153 153 153 / 30%)');
338
+ if (options.showVerticalGrid) {
339
+ gridGroup
340
+ .append('g')
341
+ .attr('class', 'ax-line-chart-grid-vertical')
342
+ .attr('transform', `translate(0,${this.height})`)
343
+ .call(this.d3
344
+ .axisBottom(this.xScale)
345
+ .tickSize(-this.height)
346
+ .tickFormat(() => '')
347
+ .tickValues(isBandScale ? undefined : this.xScale.ticks()))
348
+ .selectAll('.tick')
349
+ .style('color', 'rgb(153 153 153 / 20%)');
350
+ }
351
+ }
352
+ }
353
+ renderLines(data) {
354
+ const isBandScale = this.xScale.bandwidth !== undefined;
355
+ const getX = (d) => {
356
+ if (isBandScale) {
357
+ return this.xScale(String(d.x)) + this.xScale.bandwidth() / 2;
358
+ }
359
+ else {
360
+ return this.xScale(typeof d.x === 'number' ? d.x : new Date(d.x));
361
+ }
362
+ };
363
+ const lineGenerator = this.d3
364
+ .line()
365
+ .x(getX)
366
+ .y((d) => this.yScale(d.y));
367
+ if (this.effectiveOptions().smoothLine !== false) {
368
+ lineGenerator.curve(this.d3.curveMonotoneX);
369
+ }
370
+ const areaGenerator = this.d3
371
+ .area()
372
+ .x(getX)
373
+ .y0(this.height)
374
+ .y1((d) => this.yScale(d.y));
375
+ if (this.effectiveOptions().smoothLine !== false) {
376
+ areaGenerator.curve(this.d3.curveMonotoneX);
377
+ }
378
+ const allSeriesGroup = this.chart.append('g').attr('class', 'ax-line-chart-all-series');
379
+ const allPointsGroup = this.chart.append('g').attr('class', 'ax-line-chart-all-points');
380
+ if (this.effectiveOptions().showCrosshair === true) {
381
+ const crosshairGroup = this.chart
382
+ .append('g')
383
+ .attr('class', 'ax-line-chart-crosshair')
384
+ .style('pointer-events', 'none');
385
+ }
386
+ // Get animation options
387
+ const animationDuration = this.effectiveOptions().animationDuration;
388
+ const animationEasing = this.getEasingFunction(this.effectiveOptions().animationEasing);
389
+ data.forEach((series, seriesIndex) => {
390
+ if (!series.data || series.data.length === 0)
391
+ return;
392
+ const seriesGroup = allSeriesGroup
393
+ .append('g')
394
+ .attr('class', `ax-line-chart-series ax-line-chart-series-${seriesIndex}`)
395
+ .attr('data-series', series.label || `series-${seriesIndex}`);
396
+ const lineColor = series.lineColor || AXPLineChartColors.getColor(seriesIndex);
397
+ const fillColor = series.fillColor || lineColor;
398
+ const line = seriesGroup
399
+ .append('path')
400
+ .datum(series.data)
401
+ .attr('class', 'ax-line-chart-line')
402
+ .attr('stroke', lineColor)
403
+ .attr('stroke-width', this.effectiveOptions().lineWidth ?? 2)
404
+ .attr('d', lineGenerator)
405
+ .attr('fill', 'none');
406
+ const totalLength = line.node().getTotalLength();
407
+ line
408
+ .attr('stroke-dasharray', totalLength + ' ' + totalLength)
409
+ .attr('stroke-dashoffset', totalLength)
410
+ .transition()
411
+ .duration(animationDuration)
412
+ .ease(animationEasing)
413
+ .attr('stroke-dashoffset', 0);
414
+ if (this.effectiveOptions().fillArea) {
415
+ const area = seriesGroup
416
+ .append('path')
417
+ .datum(series.data)
418
+ .attr('class', 'ax-line-chart-area')
419
+ .attr('fill', fillColor)
420
+ .attr('opacity', 0)
421
+ .attr('d', areaGenerator);
422
+ area
423
+ .transition()
424
+ .duration(animationDuration)
425
+ .ease(animationEasing)
426
+ .attr('opacity', (this.effectiveOptions().fillOpacity ?? 20) / 100);
427
+ }
428
+ });
429
+ if (this.effectiveOptions().showPoints !== false) {
430
+ const pointMap = new Map();
431
+ data.forEach((series, seriesIndex) => {
432
+ if (!series.data || series.data.length === 0)
433
+ return;
434
+ const lineColor = series.lineColor || AXPLineChartColors.getColor(seriesIndex);
435
+ const maxPoints = 100;
436
+ const pointData = series.data.length > maxPoints ? this.getReducedDataPoints(series.data, maxPoints) : series.data;
437
+ pointData.forEach((point) => {
438
+ const x = getX(point);
439
+ const y = this.yScale(point.y);
440
+ const key = `${Math.round(x * 10) / 10},${Math.round(y * 10) / 10}`;
441
+ if (!pointMap.has(key)) {
442
+ pointMap.set(key, []);
443
+ }
444
+ pointMap.get(key)?.push({
445
+ point,
446
+ series,
447
+ seriesIndex,
448
+ });
449
+ });
450
+ });
451
+ data.forEach((series, seriesIndex) => {
452
+ if (!series.data || series.data.length === 0)
453
+ return;
454
+ const lineColor = series.lineColor || AXPLineChartColors.getColor(seriesIndex);
455
+ const pointsGroup = allPointsGroup
456
+ .append('g')
457
+ .attr('class', `ax-line-chart-points ax-line-chart-points-${seriesIndex}`);
458
+ pointsGroup
459
+ .on('mouseenter', () => this.handlePointGroupEnter(seriesIndex, lineColor))
460
+ .on('mouseleave', () => this.handlePointGroupLeave());
461
+ const maxPoints = 100;
462
+ const pointData = series.data.length > maxPoints ? this.getReducedDataPoints(series.data, maxPoints) : series.data;
463
+ pointsGroup
464
+ .selectAll('circle')
465
+ .data(pointData)
466
+ .enter()
467
+ .append('circle')
468
+ .attr('class', 'ax-line-chart-point')
469
+ .attr('cx', getX)
470
+ .attr('cy', (d) => this.yScale(d.y))
471
+ .attr('r', 0)
472
+ .attr('fill', lineColor)
473
+ .attr('stroke', '#fff')
474
+ .attr('stroke-width', 1)
475
+ .attr('data-x', (d) => d.x)
476
+ .attr('data-y', (d) => d.y)
477
+ .style('cursor', 'pointer')
478
+ .on('mouseenter', (event, d) => {
479
+ const x = getX(d);
480
+ const y = this.yScale(d.y);
481
+ const key = `${Math.round(x * 10) / 10},${Math.round(y * 10) / 10}`;
482
+ const overlappingPoints = pointMap.get(key) || [];
483
+ if (overlappingPoints.length > 1) {
484
+ this.handleOverlappingPointsHover(event, overlappingPoints);
485
+ }
486
+ else {
487
+ this.handlePointHover(event, d, series, seriesIndex);
488
+ }
489
+ this.d3
490
+ .select(event.target)
491
+ .transition()
492
+ .duration(150)
493
+ .attr('r', (this.effectiveOptions().pointRadius ?? 4) * 1.5);
494
+ })
495
+ .on('mousemove', (event) => this.updateTooltipPosition(event))
496
+ .on('mouseleave', (event) => {
497
+ this._tooltipVisible.set(false);
498
+ this.d3
499
+ .select(event.target)
500
+ .transition()
501
+ .duration(150)
502
+ .attr('r', this.effectiveOptions().pointRadius ?? 4);
503
+ })
504
+ .on('click', (event, d) => {
505
+ const x = getX(d);
506
+ const y = this.yScale(d.y);
507
+ const key = `${Math.round(x * 10) / 10},${Math.round(y * 10) / 10}`;
508
+ const overlappingPoints = pointMap.get(key) || [];
509
+ if (overlappingPoints.length > 1) {
510
+ overlappingPoints.forEach(({ point, series }) => {
511
+ this.handlePointClick(event, point, series);
512
+ });
513
+ }
514
+ else {
515
+ this.handlePointClick(event, d, series);
516
+ }
517
+ })
518
+ .transition()
519
+ .delay((d, i) => i * Math.min(50, animationDuration * 0.05) + animationDuration * 0.5)
520
+ .duration(animationDuration * 0.3)
521
+ .ease(animationEasing)
522
+ .attr('r', this.effectiveOptions().pointRadius ?? 4);
523
+ });
524
+ }
525
+ }
526
+ handlePointGroupEnter(seriesIndex, color) {
527
+ this.chart
528
+ .select(`.ax-line-chart-series-${seriesIndex} .ax-line-chart-line`)
529
+ .transition()
530
+ .duration(150)
531
+ .attr('stroke-width', (this.effectiveOptions().lineWidth ?? 2) * 1.5);
532
+ }
533
+ handlePointGroupLeave() {
534
+ this.chart
535
+ .selectAll('.ax-line-chart-line')
536
+ .transition()
537
+ .duration(150)
538
+ .attr('stroke-width', this.effectiveOptions().lineWidth ?? 2);
539
+ if (this.effectiveOptions().showCrosshair === true) {
540
+ this.chart.select('.ax-line-chart-crosshair').selectAll('*').remove();
541
+ }
542
+ }
543
+ getReducedDataPoints(data, maxPoints) {
544
+ if (data.length <= maxPoints)
545
+ return data;
546
+ const result = [];
547
+ const step = Math.ceil(data.length / maxPoints);
548
+ for (let i = 0; i < data.length; i += step) {
549
+ result.push(data[i]);
550
+ }
551
+ if (result[result.length - 1] !== data[data.length - 1]) {
552
+ result.push(data[data.length - 1]);
553
+ }
554
+ return result;
555
+ }
556
+ handlePointHover(event, dataPoint, series, seriesIndex) {
557
+ if (this.effectiveOptions().showTooltip !== false) {
558
+ const color = series.lineColor || AXPLineChartColors.getColor(seriesIndex);
559
+ this._tooltipData.set({
560
+ title: series.label || `Series ${seriesIndex + 1}`,
561
+ value: dataPoint.y.toString(),
562
+ seriesName: `x: ${dataPoint.x}`,
563
+ color: color,
564
+ });
565
+ this._tooltipVisible.set(true);
566
+ this.updateTooltipPosition(event);
567
+ if (this.effectiveOptions().showCrosshair === true) {
568
+ this.showCrosshairLines(dataPoint);
569
+ }
570
+ }
571
+ }
572
+ showCrosshairLines(dataPoint) {
573
+ const isBandScale = this.xScale.bandwidth !== undefined;
574
+ let x;
575
+ if (isBandScale) {
576
+ x = this.xScale(String(dataPoint.x)) + this.xScale.bandwidth() / 2;
577
+ }
578
+ else {
579
+ x = this.xScale(typeof dataPoint.x === 'number' ? dataPoint.x : new Date(dataPoint.x));
580
+ }
581
+ const y = this.yScale(dataPoint.y);
582
+ let crosshairGroup = this.chart.select('.ax-line-chart-crosshair');
583
+ if (crosshairGroup.empty()) {
584
+ crosshairGroup = this.chart.append('g').attr('class', 'ax-line-chart-crosshair').style('pointer-events', 'none');
585
+ }
586
+ crosshairGroup.selectAll('*').remove();
587
+ crosshairGroup
588
+ .append('line')
589
+ .attr('class', 'ax-line-chart-crosshair-vertical')
590
+ .attr('x1', x)
591
+ .attr('y1', 0)
592
+ .attr('x2', x)
593
+ .attr('y2', this.height)
594
+ .attr('stroke', '#999')
595
+ .attr('stroke-width', 1)
596
+ .attr('stroke-dasharray', '3,3')
597
+ .attr('opacity', 0.7)
598
+ .style('pointer-events', 'none');
599
+ crosshairGroup
600
+ .append('line')
601
+ .attr('class', 'ax-line-chart-crosshair-horizontal')
602
+ .attr('x1', 0)
603
+ .attr('y1', y)
604
+ .attr('x2', this.width)
605
+ .attr('y2', y)
606
+ .attr('stroke', '#999')
607
+ .attr('stroke-width', 1)
608
+ .attr('stroke-dasharray', '3,3')
609
+ .attr('opacity', 0.7)
610
+ .style('pointer-events', 'none');
611
+ }
612
+ updateTooltipPosition(event) {
613
+ const container = this.chartContainerEl().nativeElement.getBoundingClientRect();
614
+ const tooltipEl = this.chartContainerEl().nativeElement.querySelector('.chart-tooltip');
615
+ if (!tooltipEl) {
616
+ const x = event.clientX - container.left;
617
+ const y = event.clientY - container.top;
618
+ this._tooltipPosition.set({ x, y });
619
+ return;
620
+ }
621
+ const tooltipRect = tooltipEl.getBoundingClientRect();
622
+ let x = event.clientX - container.left;
623
+ let y = event.clientY - container.top;
624
+ if (x + tooltipRect.width + 10 > container.width) {
625
+ x = x - tooltipRect.width - 10;
626
+ }
627
+ else {
628
+ x = x + 10;
629
+ }
630
+ if (y + tooltipRect.height / 2 > container.height) {
631
+ y = container.height - tooltipRect.height / 2;
632
+ }
633
+ else if (y - tooltipRect.height / 2 < 0) {
634
+ y = tooltipRect.height / 2;
635
+ }
636
+ this._tooltipPosition.set({ x, y });
637
+ }
638
+ handlePointClick(event, dataPoint, series) {
639
+ this.pointClick.emit({
640
+ series: series,
641
+ point: dataPoint,
642
+ event: {
643
+ nativeEvent: event,
644
+ sender: this,
645
+ },
646
+ });
647
+ }
648
+ showNoDataMessage(containerElement) {
649
+ const messageContainer = this.d3
650
+ .select(containerElement)
651
+ .append('div')
652
+ .attr('class', 'axp-line-chart-no-data-message')
653
+ .style('width', '100%')
654
+ .style('height', '100%')
655
+ .style('display', 'flex')
656
+ .style('flex-direction', 'column')
657
+ .style('align-items', 'center')
658
+ .style('justify-content', 'center')
659
+ .style('text-align', 'center');
660
+ messageContainer
661
+ .append('div')
662
+ .attr('class', 'axp-line-chart-no-data-icon')
663
+ .style('margin-bottom', '10px')
664
+ .style('color', 'var(--ax-text-muted, #999)')
665
+ .html('<i class="fa-light fa-chart-line fa-2x"></i>');
666
+ messageContainer
667
+ .append('div')
668
+ .attr('class', 'axp-line-chart-no-data-text')
669
+ .style('font-size', '16px')
670
+ .style('font-weight', '600')
671
+ .style('color', 'var(--ax-text-color, #333)')
672
+ .text('No data available');
673
+ }
674
+ handleOverlappingPointsHover(event, overlappingPoints) {
675
+ if (this.effectiveOptions().showTooltip === false)
676
+ return;
677
+ const tooltipData = {
678
+ title: 'Multiple Series',
679
+ value: '',
680
+ seriesName: '',
681
+ color: '',
682
+ items: overlappingPoints.map(({ point, series, seriesIndex }) => {
683
+ const color = series.lineColor || AXPLineChartColors.getColor(seriesIndex);
684
+ return {
685
+ label: series.label || `Series ${seriesIndex + 1}`,
686
+ value: point.y.toString(),
687
+ color: color,
688
+ };
689
+ }),
690
+ };
691
+ this._tooltipData.set(tooltipData);
692
+ this._tooltipVisible.set(true);
693
+ this.updateTooltipPosition(event);
694
+ if (this.effectiveOptions().showCrosshair === true && overlappingPoints.length > 0) {
695
+ this.showCrosshairLines(overlappingPoints[0].point);
696
+ }
697
+ }
698
+ /**
699
+ * Gets the appropriate D3 easing function based on the option string
700
+ */
701
+ getEasingFunction(easing) {
702
+ switch (easing) {
703
+ case 'linear':
704
+ return this.d3.easeLinear;
705
+ case 'ease':
706
+ return this.d3.easePolyInOut;
707
+ case 'ease-in':
708
+ return this.d3.easePolyIn;
709
+ case 'ease-out':
710
+ return this.d3.easePolyOut;
711
+ case 'ease-in-out':
712
+ return this.d3.easePolyInOut;
713
+ case 'cubic':
714
+ return this.d3.easeCubic;
715
+ case 'cubic-in':
716
+ return this.d3.easeCubicIn;
717
+ case 'cubic-out':
718
+ return this.d3.easeCubicOut;
719
+ case 'cubic-in-out':
720
+ return this.d3.easeCubicInOut;
721
+ default:
722
+ return this.d3.easeCubicOut; // Default easing
723
+ }
724
+ }
725
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXLineChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
726
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "19.2.9", type: AXLineChartComponent, isStandalone: true, selector: "ax-line-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: { pointClick: "pointClick" }, viewQueries: [{ propertyName: "chartContainerEl", first: true, predicate: ["chartContainer"], descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"ax-line-chart\" #chartContainer>\n <!-- Shared tooltip component -->\n <ax-chart-tooltip\n [visible]=\"tooltipVisible()\"\n [position]=\"tooltipPosition()\"\n [data]=\"tooltipData()\"\n [showPercentage]=\"false\"\n ></ax-chart-tooltip>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:200px}.ax-line-chart{width:100%;height:100%;position:relative;display:flex;align-items:center;justify-content:center;border-radius:.5rem;overflow:hidden}.ax-line-chart svg{width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}.ax-line-chart-line{fill:none;stroke-width:2px;stroke-linejoin:round;stroke-linecap:round;transition:stroke-width .3s ease}.ax-line-chart-line:hover{stroke-width:3px}.ax-line-chart-area{opacity:.2;transition:opacity .3s ease}.ax-line-chart-area:hover{opacity:.3}.ax-line-chart-point{cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1)}.ax-line-chart-point:hover{r:6;stroke-width:2;stroke:var(--ax-background-color, #fff)}.ax-line-chart-axis-x path,.ax-line-chart-axis-y path{stroke:var(--ax-border-color, #e0e0e0)}.ax-line-chart-axis-x line,.ax-line-chart-axis-y line,.ax-line-chart-grid line{stroke:var(--ax-border-color, #e0e0e0);stroke-dasharray:2,2;stroke-opacity:.5}.ax-line-chart-grid path{stroke-width:0}.ax-line-chart-axis-x text,.ax-line-chart-axis-y text{fill:var(--ax-text-muted, #666);font-size:11px;font-weight:400}.ax-line-chart-axis-label{font-family:var(--ax-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);font-size:13px;font-weight:500;fill:var(--ax-text-color, #555);pointer-events:none}.ax-x-axis-label{transform:translateY(5px)}.ax-y-axis-label{transform:translate(-5px)}.ax-line-chart-no-data-message{font-family:var(--ax-font-family, system-ui, sans-serif);display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:1rem;width:100%;height:100%}.ax-line-chart-no-data-icon{margin-bottom:.75rem;color:var(--ax-text-muted, #999)}.ax-line-chart-no-data-text{font-weight:600;color:var(--ax-text-color, #333)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: AXChartTooltipComponent, selector: "ax-chart-tooltip", inputs: ["data", "position", "visible", "showPercentage", "style"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
727
+ }
728
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.9", ngImport: i0, type: AXLineChartComponent, decorators: [{
729
+ type: Component,
730
+ args: [{ selector: 'ax-line-chart', standalone: true, imports: [CommonModule, AXChartTooltipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"ax-line-chart\" #chartContainer>\n <!-- Shared tooltip component -->\n <ax-chart-tooltip\n [visible]=\"tooltipVisible()\"\n [position]=\"tooltipPosition()\"\n [data]=\"tooltipData()\"\n [showPercentage]=\"false\"\n ></ax-chart-tooltip>\n</div>\n", styles: [":host{display:block;width:100%;height:100%;min-height:200px}.ax-line-chart{width:100%;height:100%;position:relative;display:flex;align-items:center;justify-content:center;border-radius:.5rem;overflow:hidden}.ax-line-chart svg{width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}.ax-line-chart-line{fill:none;stroke-width:2px;stroke-linejoin:round;stroke-linecap:round;transition:stroke-width .3s ease}.ax-line-chart-line:hover{stroke-width:3px}.ax-line-chart-area{opacity:.2;transition:opacity .3s ease}.ax-line-chart-area:hover{opacity:.3}.ax-line-chart-point{cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1)}.ax-line-chart-point:hover{r:6;stroke-width:2;stroke:var(--ax-background-color, #fff)}.ax-line-chart-axis-x path,.ax-line-chart-axis-y path{stroke:var(--ax-border-color, #e0e0e0)}.ax-line-chart-axis-x line,.ax-line-chart-axis-y line,.ax-line-chart-grid line{stroke:var(--ax-border-color, #e0e0e0);stroke-dasharray:2,2;stroke-opacity:.5}.ax-line-chart-grid path{stroke-width:0}.ax-line-chart-axis-x text,.ax-line-chart-axis-y text{fill:var(--ax-text-muted, #666);font-size:11px;font-weight:400}.ax-line-chart-axis-label{font-family:var(--ax-font-family, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif);font-size:13px;font-weight:500;fill:var(--ax-text-color, #555);pointer-events:none}.ax-x-axis-label{transform:translateY(5px)}.ax-y-axis-label{transform:translate(-5px)}.ax-line-chart-no-data-message{font-family:var(--ax-font-family, system-ui, sans-serif);display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:1rem;width:100%;height:100%}.ax-line-chart-no-data-icon{margin-bottom:.75rem;color:var(--ax-text-muted, #999)}.ax-line-chart-no-data-text{font-weight:600;color:var(--ax-text-color, #333)}\n"] }]
731
+ }] });
732
+
733
+ /**
734
+ * Generated bundle index. Do not edit.
735
+ */
736
+
737
+ export { AXLineChartComponent, AXLineChartDefaultConfig, AXPLineChartColors, AX_LINE_CHART_CONFIG, lineChartConfig };
738
+ //# sourceMappingURL=acorex-charts-line-chart.mjs.map