@acorex/charts 21.0.0-next.6 → 21.0.0-next.61
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/bar-chart/index.d.ts +67 -7
- package/donut-chart/index.d.ts +46 -8
- package/fesm2022/acorex-charts-bar-chart.mjs +342 -124
- package/fesm2022/acorex-charts-bar-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-chart-legend.mjs +5 -5
- package/fesm2022/acorex-charts-chart-legend.mjs.map +1 -1
- package/fesm2022/acorex-charts-chart-tooltip.mjs +4 -4
- package/fesm2022/acorex-charts-chart-tooltip.mjs.map +1 -1
- package/fesm2022/acorex-charts-donut-chart.mjs +298 -200
- package/fesm2022/acorex-charts-donut-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-gauge-chart.mjs +93 -68
- package/fesm2022/acorex-charts-gauge-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-hierarchy-chart.mjs +22 -16
- package/fesm2022/acorex-charts-hierarchy-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-line-chart.mjs +295 -153
- package/fesm2022/acorex-charts-line-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts.mjs +93 -7
- package/fesm2022/acorex-charts.mjs.map +1 -1
- package/gauge-chart/index.d.ts +10 -7
- package/hierarchy-chart/index.d.ts +4 -5
- package/index.d.ts +43 -4
- package/line-chart/index.d.ts +70 -7
- package/package.json +1 -3
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { AX_CHART_COLOR_PALETTE, getChartColor, computeTooltipPosition } from '@acorex/charts';
|
|
1
|
+
import { AXChartComponent, AX_CHART_COLOR_PALETTE, formatLargeNumber, getChartColor, getEasingFunction, computeTooltipPosition } from '@acorex/charts';
|
|
3
2
|
import { AXChartTooltipComponent } from '@acorex/charts/chart-tooltip';
|
|
4
|
-
import { AXPlatform } from '@acorex/core/platform';
|
|
5
3
|
import * as i0 from '@angular/core';
|
|
6
|
-
import { InjectionToken,
|
|
7
|
-
import { AX_GLOBAL_CONFIG } from '@acorex/core/config';
|
|
8
|
-
import { set } from 'lodash-es';
|
|
4
|
+
import { InjectionToken, input, output, viewChild, signal, inject, computed, effect, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
|
|
9
5
|
|
|
10
6
|
const AXLineChartDefaultConfig = {
|
|
11
7
|
margins: {
|
|
@@ -41,11 +37,7 @@ const AXLineChartDefaultConfig = {
|
|
|
41
37
|
};
|
|
42
38
|
const AX_LINE_CHART_CONFIG = new InjectionToken('AX_LINE_CHART_CONFIG', {
|
|
43
39
|
providedIn: 'root',
|
|
44
|
-
factory: () =>
|
|
45
|
-
const global = inject(AX_GLOBAL_CONFIG);
|
|
46
|
-
set(global, 'chart.lineChart', AXLineChartDefaultConfig);
|
|
47
|
-
return AXLineChartDefaultConfig;
|
|
48
|
-
},
|
|
40
|
+
factory: () => AXLineChartDefaultConfig,
|
|
49
41
|
});
|
|
50
42
|
function lineChartConfig(config = {}) {
|
|
51
43
|
const result = {
|
|
@@ -58,7 +50,7 @@ function lineChartConfig(config = {}) {
|
|
|
58
50
|
/**
|
|
59
51
|
* Line Chart Component for rendering data as lines with interactive hover effects and animations
|
|
60
52
|
*/
|
|
61
|
-
class AXLineChartComponent extends
|
|
53
|
+
class AXLineChartComponent extends AXChartComponent {
|
|
62
54
|
data = input([], ...(ngDevMode ? [{ debugName: "data" }] : []));
|
|
63
55
|
options = input({}, ...(ngDevMode ? [{ debugName: "options" }] : []));
|
|
64
56
|
pointClick = output();
|
|
@@ -92,8 +84,6 @@ class AXLineChartComponent extends NXComponent {
|
|
|
92
84
|
configToken = inject(AX_LINE_CHART_CONFIG);
|
|
93
85
|
// Inject the chart colors
|
|
94
86
|
chartColors = inject(AX_CHART_COLOR_PALETTE);
|
|
95
|
-
// Inject AXPlatform
|
|
96
|
-
platform = inject(AXPlatform);
|
|
97
87
|
effectiveOptions = computed(() => {
|
|
98
88
|
return {
|
|
99
89
|
...this.configToken,
|
|
@@ -115,22 +105,124 @@ class AXLineChartComponent extends NXComponent {
|
|
|
115
105
|
...this.effectiveOptions().messages,
|
|
116
106
|
};
|
|
117
107
|
}, ...(ngDevMode ? [{ debugName: "effectiveMessages" }] : []));
|
|
118
|
-
//
|
|
108
|
+
// Layout & Dimensions
|
|
109
|
+
MIN_DIMENSION = 100;
|
|
110
|
+
MIN_CONTAINER_DIMENSION = 200;
|
|
111
|
+
DEFAULT_MARGIN_TOP = 20;
|
|
112
|
+
DEFAULT_MARGIN_RIGHT = 25;
|
|
113
|
+
DEFAULT_MARGIN_BOTTOM = 40;
|
|
114
|
+
DEFAULT_MARGIN_LEFT = 50;
|
|
115
|
+
MIN_MARGIN_BOTTOM = 45;
|
|
116
|
+
MIN_MARGIN_LEFT = 55;
|
|
117
|
+
MAX_EXTRA_MARGIN = 60;
|
|
118
|
+
// Styling & Visual
|
|
119
|
+
DEFAULT_LINE_WIDTH = 2;
|
|
120
|
+
DEFAULT_POINT_RADIUS = 4;
|
|
121
|
+
DEFAULT_FILL_OPACITY = 20;
|
|
122
|
+
LINE_HIGHLIGHT_MULTIPLIER = 1.5;
|
|
123
|
+
POINT_HIGHLIGHT_MULTIPLIER = 1.5;
|
|
124
|
+
POINT_STROKE_WIDTH = 1;
|
|
125
|
+
POINT_HOVER_STROKE_WIDTH = 2;
|
|
126
|
+
GRID_DASH_ARRAY = '2,2';
|
|
127
|
+
CROSSHAIR_DASH_ARRAY = '3,3';
|
|
128
|
+
CROSSHAIR_OPACITY = 0.7;
|
|
129
|
+
// Animation
|
|
130
|
+
HOVER_TRANSITION_DURATION = 150;
|
|
131
|
+
ANIMATION_END_BUFFER = 100;
|
|
132
|
+
POINT_ANIMATION_DELAY_MS = 50;
|
|
133
|
+
POINT_ANIMATION_DELAY_RATIO = 0.5;
|
|
134
|
+
POINT_ANIMATION_DURATION_RATIO = 0.3;
|
|
135
|
+
// Text & Labels
|
|
136
|
+
AXIS_LABEL_FONT_SIZE = 14;
|
|
137
|
+
CHAR_WIDTH_RATIO = 0.6;
|
|
138
|
+
FONT_WIDTH_MULTIPLIER = 0.6;
|
|
139
|
+
MAX_LABEL_LENGTH = 20;
|
|
140
|
+
MIN_FONT_SIZE_X = 11;
|
|
141
|
+
MAX_FONT_SIZE_X = 15;
|
|
142
|
+
MIN_FONT_SIZE_Y = 11;
|
|
143
|
+
MAX_FONT_SIZE_Y = 15;
|
|
144
|
+
FONT_PADDING = 10;
|
|
145
|
+
Y_AXIS_PADDING = 10;
|
|
146
|
+
// Data & Performance
|
|
147
|
+
MAX_POINTS_TO_RENDER = 100;
|
|
148
|
+
POINT_COORDINATE_PRECISION = 10;
|
|
149
|
+
MANY_ITEMS_THRESHOLD = 20;
|
|
150
|
+
VERY_MANY_ITEMS_THRESHOLD = 50;
|
|
151
|
+
// Tooltip
|
|
119
152
|
TOOLTIP_GAP = 10;
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const rawData = this.data();
|
|
153
|
+
/**
|
|
154
|
+
* Normalizes input data to consistent array format with originalIndex
|
|
155
|
+
*/
|
|
156
|
+
normalizeData(rawData) {
|
|
125
157
|
if (Array.isArray(rawData)) {
|
|
126
|
-
|
|
158
|
+
return rawData.map((s, i) => ({ ...s, originalIndex: i }));
|
|
127
159
|
}
|
|
128
|
-
|
|
129
|
-
|
|
160
|
+
if (rawData && 'data' in rawData) {
|
|
161
|
+
return [{ ...rawData, originalIndex: 0 }];
|
|
130
162
|
}
|
|
131
|
-
|
|
132
|
-
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Gets unique identifier for a series
|
|
167
|
+
*/
|
|
168
|
+
getSeriesIdentifier(series) {
|
|
169
|
+
return series.id || series.label || `series-${series.originalIndex}`;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Calculates dynamic font size based on available space
|
|
173
|
+
*/
|
|
174
|
+
calculateDynamicFontSize(dimension, divisor, min, max) {
|
|
175
|
+
return Math.max(min, Math.min(max, Math.round(dimension / divisor)));
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Creates a unique key for point coordinates (for overlap detection)
|
|
179
|
+
*/
|
|
180
|
+
createPointKey(x, y) {
|
|
181
|
+
const precision = this.POINT_COORDINATE_PRECISION;
|
|
182
|
+
return `${Math.round(x * precision) / precision},${Math.round(y * precision) / precision}`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Removes series elements from the chart
|
|
186
|
+
*/
|
|
187
|
+
removeSeriesElements(seriesIdentifier) {
|
|
188
|
+
this.chart.selectAll(`.ax-line-chart-series[data-series-identifier="${seriesIdentifier}"]`).remove();
|
|
189
|
+
this.chart.selectAll(`.ax-line-chart-points[data-series-identifier="${seriesIdentifier}"]`).remove();
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Truncates long labels with ellipsis
|
|
193
|
+
*/
|
|
194
|
+
truncateLabel(label, maxLength = this.MAX_LABEL_LENGTH) {
|
|
195
|
+
if (label.length <= maxLength)
|
|
196
|
+
return label;
|
|
197
|
+
return label.substring(0, maxLength - 1) + '…';
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Calculates maximum width needed for Y-axis tick labels
|
|
201
|
+
*/
|
|
202
|
+
calculateMaxYAxisTickLabelWidth() {
|
|
203
|
+
const allSeriesData = this._fullNormalizedData.filter((s) => !this.hiddenSeries.has(this.getSeriesIdentifier(s)));
|
|
204
|
+
let maxValue = 0;
|
|
205
|
+
let minValue = 0;
|
|
206
|
+
for (const series of allSeriesData) {
|
|
207
|
+
if (series.data && series.data.length > 0) {
|
|
208
|
+
for (const point of series.data) {
|
|
209
|
+
if (typeof point.y === 'number') {
|
|
210
|
+
maxValue = Math.max(maxValue, point.y);
|
|
211
|
+
minValue = Math.min(minValue, point.y);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
133
215
|
}
|
|
216
|
+
// Check both max and min (for negative values)
|
|
217
|
+
const maxAbsValue = Math.max(Math.abs(maxValue), Math.abs(minValue));
|
|
218
|
+
const tickLabelText = Number.isFinite(maxAbsValue) ? formatLargeNumber(maxAbsValue) : '00000';
|
|
219
|
+
return tickLabelText.length * this.AXIS_LABEL_FONT_SIZE * this.FONT_WIDTH_MULTIPLIER + this.FONT_PADDING;
|
|
220
|
+
}
|
|
221
|
+
ngOnInit() {
|
|
222
|
+
this.loadD3();
|
|
223
|
+
}
|
|
224
|
+
#effect = effect(() => {
|
|
225
|
+
this._fullNormalizedData = this.normalizeData(this.data());
|
|
134
226
|
this.effectiveOptions();
|
|
135
227
|
if (this._rendered()) {
|
|
136
228
|
this.updateChart();
|
|
@@ -152,14 +244,15 @@ class AXLineChartComponent extends NXComponent {
|
|
|
152
244
|
return sum + series.data.reduce((sSum, p) => sSum + p.y, 0);
|
|
153
245
|
}, 0);
|
|
154
246
|
return this._fullNormalizedData.map((series) => {
|
|
247
|
+
const seriesIdentifier = this.getSeriesIdentifier(series);
|
|
155
248
|
const seriesSum = series.data.reduce((sum, p) => sum + p.y, 0);
|
|
156
249
|
const percentage = totalSum > 0 ? (seriesSum / totalSum) * 100 : 0;
|
|
157
250
|
return {
|
|
158
|
-
id:
|
|
251
|
+
id: seriesIdentifier,
|
|
159
252
|
name: series.label || `Series ${series.originalIndex + 1}`,
|
|
160
253
|
value: seriesSum,
|
|
161
254
|
color: series.lineColor || getChartColor(series.originalIndex, this.chartColors),
|
|
162
|
-
hidden: this.hiddenSeries.has(
|
|
255
|
+
hidden: this.hiddenSeries.has(seriesIdentifier),
|
|
163
256
|
percentage: parseFloat(percentage.toFixed(2)),
|
|
164
257
|
};
|
|
165
258
|
});
|
|
@@ -174,11 +267,11 @@ class AXLineChartComponent extends NXComponent {
|
|
|
174
267
|
allPointsGroups.classed('ax-chart-dimmed', false).style('opacity', 1);
|
|
175
268
|
allSeriesGroups
|
|
176
269
|
.selectAll('.ax-line-chart-line')
|
|
177
|
-
.attr('stroke-width', this.effectiveOptions().lineWidth ??
|
|
270
|
+
.attr('stroke-width', this.effectiveOptions().lineWidth ?? this.DEFAULT_LINE_WIDTH)
|
|
178
271
|
.classed('ax-chart-highlighted-path', false);
|
|
179
272
|
allPointsGroups
|
|
180
273
|
.selectAll('circle.ax-line-chart-point')
|
|
181
|
-
.attr('r', this.effectiveOptions().pointRadius ??
|
|
274
|
+
.attr('r', this.effectiveOptions().pointRadius ?? this.DEFAULT_POINT_RADIUS)
|
|
182
275
|
.classed('ax-chart-highlighted-point', false);
|
|
183
276
|
return;
|
|
184
277
|
}
|
|
@@ -190,23 +283,22 @@ class AXLineChartComponent extends NXComponent {
|
|
|
190
283
|
targetPointsGroup.classed('ax-chart-dimmed', false).style('opacity', 1);
|
|
191
284
|
targetSeriesGroup
|
|
192
285
|
.selectAll('.ax-line-chart-line')
|
|
193
|
-
.attr('stroke-width', (this.effectiveOptions().lineWidth ??
|
|
286
|
+
.attr('stroke-width', (this.effectiveOptions().lineWidth ?? this.DEFAULT_LINE_WIDTH) * this.LINE_HIGHLIGHT_MULTIPLIER)
|
|
194
287
|
.classed('ax-chart-highlighted-path', true);
|
|
195
288
|
targetPointsGroup
|
|
196
289
|
.selectAll('circle.ax-line-chart-point')
|
|
197
|
-
.attr('r', (this.effectiveOptions().pointRadius ??
|
|
198
|
-
.classed('ax-chart-highlighted-
|
|
290
|
+
.attr('r', (this.effectiveOptions().pointRadius ?? this.DEFAULT_POINT_RADIUS) * this.POINT_HIGHLIGHT_MULTIPLIER)
|
|
291
|
+
.classed('ax-chart-highlighted-path', true);
|
|
199
292
|
}
|
|
200
293
|
toggleSegment(id) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
this.hiddenSeries.delete(seriesId);
|
|
294
|
+
if (this.hiddenSeries.has(id)) {
|
|
295
|
+
this.hiddenSeries.delete(id);
|
|
204
296
|
}
|
|
205
297
|
else {
|
|
206
|
-
this.hiddenSeries.add(
|
|
298
|
+
this.hiddenSeries.add(id);
|
|
207
299
|
}
|
|
208
300
|
this.updateChart();
|
|
209
|
-
return !this.hiddenSeries.has(
|
|
301
|
+
return !this.hiddenSeries.has(id);
|
|
210
302
|
}
|
|
211
303
|
async loadD3() {
|
|
212
304
|
try {
|
|
@@ -234,7 +326,7 @@ class AXLineChartComponent extends NXComponent {
|
|
|
234
326
|
this.showNoDataMessage(containerElement);
|
|
235
327
|
return;
|
|
236
328
|
}
|
|
237
|
-
const visibleDataForRendering = allSeriesData.filter((series) => !this.hiddenSeries.has(
|
|
329
|
+
const visibleDataForRendering = allSeriesData.filter((series) => !this.hiddenSeries.has(this.getSeriesIdentifier(series)));
|
|
238
330
|
if (visibleDataForRendering.length === 0) {
|
|
239
331
|
// All data is present, but all series are hidden by the legend
|
|
240
332
|
this.showAllSeriesHiddenMessage(containerElement);
|
|
@@ -242,17 +334,11 @@ class AXLineChartComponent extends NXComponent {
|
|
|
242
334
|
// For now, we will show the message and return, as scales/axes can't be drawn.
|
|
243
335
|
return;
|
|
244
336
|
}
|
|
245
|
-
// Scales should be based on potentially visible data to avoid errors with empty domains
|
|
246
|
-
// If all are hidden, scales might become an issue. Let's use all data for scales,
|
|
247
|
-
// and rendering will handle visibility.
|
|
248
|
-
const dataForScales = allSeriesData.flatMap((s) => s.data).length > 0 ? allSeriesData : [{ data: [{ x: 0, y: 0 }] }];
|
|
249
337
|
const chartOptions = this.effectiveOptions();
|
|
250
338
|
this.setupDimensions(containerElement, chartOptions);
|
|
251
|
-
// Use allSeriesData for domain calculation to keep scales consistent
|
|
252
|
-
// but filter out series with no data points for scale calculation.
|
|
339
|
+
// Use allSeriesData for domain calculation to keep scales consistent
|
|
253
340
|
const dataForScaleSetup = allSeriesData.filter((s) => s.data && s.data.length > 0);
|
|
254
341
|
if (dataForScaleSetup.length === 0) {
|
|
255
|
-
// If after filtering, there's no data with points (e.g. all series have empty data arrays)
|
|
256
342
|
this.showNoDataMessage(containerElement);
|
|
257
343
|
return;
|
|
258
344
|
}
|
|
@@ -275,7 +361,7 @@ class AXLineChartComponent extends NXComponent {
|
|
|
275
361
|
this.calculateMargins(options, containerElement.clientWidth);
|
|
276
362
|
const containerWidth = containerElement.clientWidth;
|
|
277
363
|
const containerHeight = containerElement.clientHeight;
|
|
278
|
-
const minDim = Math.min(
|
|
364
|
+
const minDim = Math.min(this.MIN_CONTAINER_DIMENSION, containerWidth, containerHeight);
|
|
279
365
|
if (options.width && options.height) {
|
|
280
366
|
this.width = options.width - this.margin.left - this.margin.right;
|
|
281
367
|
this.height = options.height - this.margin.top - this.margin.bottom;
|
|
@@ -284,8 +370,8 @@ class AXLineChartComponent extends NXComponent {
|
|
|
284
370
|
this.width = Math.max(containerWidth, minDim) - this.margin.left - this.margin.right;
|
|
285
371
|
this.height = Math.max(containerHeight, minDim) - this.margin.top - this.margin.bottom;
|
|
286
372
|
}
|
|
287
|
-
this.width = Math.max(this.width,
|
|
288
|
-
this.height = Math.max(this.height,
|
|
373
|
+
this.width = Math.max(this.width, this.MIN_DIMENSION);
|
|
374
|
+
this.height = Math.max(this.height, this.MIN_DIMENSION);
|
|
289
375
|
const totalWidth = this.width + this.margin.left + this.margin.right;
|
|
290
376
|
const totalHeight = this.height + this.margin.top + this.margin.bottom;
|
|
291
377
|
const svg = this.d3
|
|
@@ -307,16 +393,16 @@ class AXLineChartComponent extends NXComponent {
|
|
|
307
393
|
}
|
|
308
394
|
calculateMargins(options, containerWidth) {
|
|
309
395
|
this.margin = {
|
|
310
|
-
top: options.margins?.top ??
|
|
311
|
-
right: options.margins?.right ??
|
|
312
|
-
bottom: options.margins?.bottom ??
|
|
313
|
-
left: options.margins?.left ??
|
|
396
|
+
top: options.margins?.top ?? this.DEFAULT_MARGIN_TOP,
|
|
397
|
+
right: options.margins?.right ?? this.DEFAULT_MARGIN_RIGHT,
|
|
398
|
+
bottom: options.margins?.bottom ?? this.DEFAULT_MARGIN_BOTTOM,
|
|
399
|
+
left: options.margins?.left ?? this.DEFAULT_MARGIN_LEFT,
|
|
314
400
|
};
|
|
315
401
|
const allDataPoints = this._fullNormalizedData.flatMap((series) => series.data);
|
|
316
402
|
if (allDataPoints.length > 0) {
|
|
317
403
|
const allNumericX = allDataPoints.every((d) => typeof d.x === 'number');
|
|
318
404
|
if (!allNumericX) {
|
|
319
|
-
const visibleSeries = this._fullNormalizedData.filter((s) => !this.hiddenSeries.has(
|
|
405
|
+
const visibleSeries = this._fullNormalizedData.filter((s) => !this.hiddenSeries.has(this.getSeriesIdentifier(s)));
|
|
320
406
|
const allXValues = new Set(visibleSeries.flatMap((series) => series.data.map((d) => String(d.x))));
|
|
321
407
|
const labelCount = allXValues.size;
|
|
322
408
|
if (labelCount > 0 && containerWidth > 0) {
|
|
@@ -325,11 +411,16 @@ class AXLineChartComponent extends NXComponent {
|
|
|
325
411
|
const availableWidthPerLabel = workingWidth / labelCount;
|
|
326
412
|
const labels = Array.from(allXValues);
|
|
327
413
|
const longestLabel = labels.reduce((a, b) => (a.length > b.length ? a : b), '');
|
|
328
|
-
const estimatedFontSize =
|
|
329
|
-
|
|
414
|
+
const estimatedFontSize = this.calculateDynamicFontSize(workingWidth, 45, this.MIN_FONT_SIZE_X, this.MAX_FONT_SIZE_X);
|
|
415
|
+
// Account for label truncation in width calculation
|
|
416
|
+
const maxLabelLength = labelCount > this.MANY_ITEMS_THRESHOLD ? 10 : this.MAX_LABEL_LENGTH;
|
|
417
|
+
const effectiveLabelLength = Math.min(longestLabel.length, maxLabelLength);
|
|
418
|
+
const estimatedLongestLabelWidth = effectiveLabelLength * estimatedFontSize * this.CHAR_WIDTH_RATIO;
|
|
330
419
|
if (estimatedLongestLabelWidth > availableWidthPerLabel) {
|
|
331
|
-
|
|
332
|
-
|
|
420
|
+
// Calculate diagonal height when rotated -45 degrees
|
|
421
|
+
const diagonalHeight = estimatedLongestLabelWidth * Math.sin(Math.PI / 4);
|
|
422
|
+
const requiredExtraMargin = diagonalHeight + 15; // Extra padding for safety
|
|
423
|
+
this.margin.bottom += Math.min(this.MAX_EXTRA_MARGIN, requiredExtraMargin);
|
|
333
424
|
}
|
|
334
425
|
}
|
|
335
426
|
}
|
|
@@ -341,15 +432,23 @@ class AXLineChartComponent extends NXComponent {
|
|
|
341
432
|
this.margin.bottom = Math.max(this.margin.bottom, 40 + extraBottomMargin);
|
|
342
433
|
}
|
|
343
434
|
if (options.yAxisLabel) {
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
435
|
+
// Calculate space needed for Y-axis: tick labels + padding + title
|
|
436
|
+
const maxTickLabelWidth = this.calculateMaxYAxisTickLabelWidth();
|
|
437
|
+
const yAxisTitleThickness = 20; // Height of rotated title text
|
|
438
|
+
const yAxisTitlePadding = this.Y_AXIS_PADDING;
|
|
439
|
+
const totalYAxisWidth = maxTickLabelWidth + yAxisTitlePadding + yAxisTitleThickness;
|
|
440
|
+
this.margin.left = Math.max(this.margin.left, totalYAxisWidth);
|
|
441
|
+
}
|
|
442
|
+
else if (options.showYAxis !== false) {
|
|
443
|
+
// Just tick labels, no title
|
|
444
|
+
const maxTickLabelWidth = this.calculateMaxYAxisTickLabelWidth();
|
|
445
|
+
this.margin.left = Math.max(this.margin.left, maxTickLabelWidth + 10);
|
|
347
446
|
}
|
|
348
447
|
if (options.showXAxis !== false) {
|
|
349
|
-
this.margin.bottom = Math.max(this.margin.bottom,
|
|
448
|
+
this.margin.bottom = Math.max(this.margin.bottom, this.MIN_MARGIN_BOTTOM);
|
|
350
449
|
}
|
|
351
450
|
if (options.showYAxis !== false) {
|
|
352
|
-
this.margin.left = Math.max(this.margin.left,
|
|
451
|
+
this.margin.left = Math.max(this.margin.left, this.MIN_MARGIN_LEFT);
|
|
353
452
|
}
|
|
354
453
|
}
|
|
355
454
|
setupScales(data) {
|
|
@@ -365,16 +464,17 @@ class AXLineChartComponent extends NXComponent {
|
|
|
365
464
|
}
|
|
366
465
|
const allNumericX = allDataPoints.every((d) => typeof d.x === 'number');
|
|
367
466
|
if (allNumericX) {
|
|
368
|
-
const xMin = this.d3.min(allDataPoints, (d) => d.x) ?? 0;
|
|
369
467
|
const xMax = this.d3.max(allDataPoints, (d) => d.x) ?? 0;
|
|
370
|
-
if (
|
|
468
|
+
if (xMax === 0) {
|
|
469
|
+
// If all values are 0, show -1 to 1 range
|
|
371
470
|
this.xScale = this.d3
|
|
372
471
|
.scaleLinear()
|
|
373
|
-
.domain([
|
|
472
|
+
.domain([-1, 1])
|
|
374
473
|
.range([0, this.width]);
|
|
375
474
|
}
|
|
376
475
|
else {
|
|
377
|
-
|
|
476
|
+
// Always start X axis from 0 for numeric data
|
|
477
|
+
this.xScale = this.d3.scaleLinear().domain([0, xMax]).range([0, this.width]);
|
|
378
478
|
}
|
|
379
479
|
}
|
|
380
480
|
else {
|
|
@@ -403,14 +503,61 @@ class AXLineChartComponent extends NXComponent {
|
|
|
403
503
|
const showYAxis = options.showYAxis !== false;
|
|
404
504
|
const showGrid = options.showGrid !== false;
|
|
405
505
|
const isBandScale = this.xScale.bandwidth !== undefined;
|
|
406
|
-
const isRtl =
|
|
506
|
+
const isRtl = document.documentElement.dir === 'rtl' || document.body.dir === 'rtl';
|
|
407
507
|
const axesGroup = this.chart.append('g').attr('class', 'ax-line-chart-axes');
|
|
408
508
|
if (showXAxis) {
|
|
509
|
+
let xAxisGenerator;
|
|
510
|
+
let itemCount = 0;
|
|
511
|
+
if (isBandScale) {
|
|
512
|
+
// Band scale (categorical data)
|
|
513
|
+
itemCount = this.xScale.domain().length;
|
|
514
|
+
// Smart tick reduction for many items (like bar chart)
|
|
515
|
+
let tickValues = this.xScale.domain();
|
|
516
|
+
if (itemCount > this.VERY_MANY_ITEMS_THRESHOLD) {
|
|
517
|
+
// Show every 5th tick for 50+ items
|
|
518
|
+
tickValues = tickValues.filter((_d, i) => i % 5 === 0);
|
|
519
|
+
}
|
|
520
|
+
else if (itemCount > this.MANY_ITEMS_THRESHOLD) {
|
|
521
|
+
// Show every 2nd tick for 20-50 items
|
|
522
|
+
tickValues = tickValues.filter((_d, i) => i % 2 === 0);
|
|
523
|
+
}
|
|
524
|
+
xAxisGenerator = this.d3
|
|
525
|
+
.axisBottom(this.xScale)
|
|
526
|
+
.tickValues(tickValues)
|
|
527
|
+
.tickSize(5)
|
|
528
|
+
.tickPadding(8)
|
|
529
|
+
.tickFormat((d) => {
|
|
530
|
+
const label = String(d);
|
|
531
|
+
// Truncate long labels intelligently
|
|
532
|
+
const maxLength = itemCount > this.MANY_ITEMS_THRESHOLD ? 10 : this.MAX_LABEL_LENGTH;
|
|
533
|
+
return this.truncateLabel(label, maxLength);
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
// Linear scale (numeric data)
|
|
538
|
+
// Let D3 determine optimal tick count, but ensure we show reasonable number
|
|
539
|
+
// Calculate optimal tick count based on width
|
|
540
|
+
const optimalTickCount = Math.min(12, Math.max(5, Math.floor(this.width / 80)));
|
|
541
|
+
xAxisGenerator = this.d3
|
|
542
|
+
.axisBottom(this.xScale)
|
|
543
|
+
.ticks(optimalTickCount)
|
|
544
|
+
.tickSize(5)
|
|
545
|
+
.tickPadding(8)
|
|
546
|
+
.tickFormat((d) => {
|
|
547
|
+
// Format numbers nicely
|
|
548
|
+
const num = Number(d);
|
|
549
|
+
if (Number.isInteger(num)) {
|
|
550
|
+
return String(num);
|
|
551
|
+
}
|
|
552
|
+
return num.toFixed(1);
|
|
553
|
+
});
|
|
554
|
+
itemCount = optimalTickCount;
|
|
555
|
+
}
|
|
409
556
|
this.xAxis = axesGroup
|
|
410
557
|
.append('g')
|
|
411
558
|
.attr('class', 'ax-line-chart-axis-x')
|
|
412
559
|
.attr('transform', `translate(0,${this.height})`)
|
|
413
|
-
.call(
|
|
560
|
+
.call(xAxisGenerator);
|
|
414
561
|
// Style the x-axis path and lines
|
|
415
562
|
this.xAxis.selectAll('path').attr('stroke', 'rgba(var(--ax-comp-line-chart-axis-color), 0.2)');
|
|
416
563
|
this.xAxis
|
|
@@ -418,19 +565,21 @@ class AXLineChartComponent extends NXComponent {
|
|
|
418
565
|
.attr('stroke', 'rgba(var(--ax-comp-line-chart-grid-lines-color), 0.2)')
|
|
419
566
|
.attr('stroke-dasharray', '2,2')
|
|
420
567
|
.attr('stroke-opacity', '0.5');
|
|
421
|
-
const dynamicXAxisTickFontSize =
|
|
568
|
+
const dynamicXAxisTickFontSize = this.calculateDynamicFontSize(this.width, 45, this.MIN_FONT_SIZE_X, this.MAX_FONT_SIZE_X);
|
|
422
569
|
const xAxisTicks = this.xAxis
|
|
423
570
|
.selectAll('text')
|
|
424
571
|
.style('font-size', `${dynamicXAxisTickFontSize}px`)
|
|
425
572
|
.style('font-weight', '400')
|
|
426
573
|
.style('fill', 'rgba(var(--ax-comp-line-chart-labels-color), 0.7)');
|
|
427
|
-
// Automatically rotate labels if they are likely to overlap
|
|
428
|
-
|
|
574
|
+
// Automatically rotate labels if they are likely to overlap (only for band scale)
|
|
575
|
+
let labelsAreRotated = false;
|
|
576
|
+
if (isBandScale && this.xScale.bandwidth && this.xScale.domain().length > 0) {
|
|
429
577
|
const step = this.xScale.step();
|
|
430
578
|
const longestLabel = this.xScale.domain().reduce((a, b) => (a.length > b.length ? a : b), '');
|
|
431
|
-
// Using
|
|
432
|
-
const estimatedLongestLabelWidth = longestLabel.length * dynamicXAxisTickFontSize *
|
|
579
|
+
// Using char width ratio constant for better estimate
|
|
580
|
+
const estimatedLongestLabelWidth = longestLabel.length * dynamicXAxisTickFontSize * this.CHAR_WIDTH_RATIO;
|
|
433
581
|
if (estimatedLongestLabelWidth > step) {
|
|
582
|
+
labelsAreRotated = true;
|
|
434
583
|
xAxisTicks
|
|
435
584
|
.attr('transform', 'rotate(-45)')
|
|
436
585
|
.style('text-anchor', 'end')
|
|
@@ -438,16 +587,17 @@ class AXLineChartComponent extends NXComponent {
|
|
|
438
587
|
.attr('dy', '0.15em');
|
|
439
588
|
}
|
|
440
589
|
}
|
|
441
|
-
if
|
|
442
|
-
|
|
590
|
+
// Only show X-axis title if labels are NOT rotated and item count is reasonable
|
|
591
|
+
// This prevents overlap with rotated labels and improves readability
|
|
592
|
+
const shouldShowXAxisTitle = options.xAxisLabel && !labelsAreRotated && itemCount <= this.MANY_ITEMS_THRESHOLD;
|
|
593
|
+
if (shouldShowXAxisTitle) {
|
|
443
594
|
axesGroup
|
|
444
595
|
.append('text')
|
|
445
596
|
.attr('class', 'ax-line-chart-axis-label ax-x-axis-label')
|
|
446
597
|
.attr('text-anchor', 'middle')
|
|
447
|
-
.attr('dominant-baseline', 'middle')
|
|
448
598
|
.attr('x', this.width / 2)
|
|
449
|
-
.attr('y',
|
|
450
|
-
.attr('
|
|
599
|
+
.attr('y', this.height + this.margin.bottom - 5)
|
|
600
|
+
.attr('direction', 'ltr')
|
|
451
601
|
.attr('style', `
|
|
452
602
|
font-size: 14px;
|
|
453
603
|
font-weight: 500;
|
|
@@ -458,10 +608,24 @@ class AXLineChartComponent extends NXComponent {
|
|
|
458
608
|
}
|
|
459
609
|
}
|
|
460
610
|
if (showYAxis) {
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
.
|
|
464
|
-
.
|
|
611
|
+
// Create Y axis with smart number formatting
|
|
612
|
+
const yAxisGenerator = this.d3
|
|
613
|
+
.axisLeft(this.yScale)
|
|
614
|
+
.tickSize(5)
|
|
615
|
+
.tickPadding(8)
|
|
616
|
+
.tickFormat((value) => {
|
|
617
|
+
const numValue = typeof value === 'number' ? value : value.valueOf();
|
|
618
|
+
return formatLargeNumber(numValue);
|
|
619
|
+
});
|
|
620
|
+
// Reduce tick count for better readability with large numbers
|
|
621
|
+
const maxValue = this.yScale.domain()[1];
|
|
622
|
+
if (maxValue > 1e6) {
|
|
623
|
+
yAxisGenerator.ticks(6); // Fewer ticks for millions+
|
|
624
|
+
}
|
|
625
|
+
else if (maxValue > 1e3) {
|
|
626
|
+
yAxisGenerator.ticks(8);
|
|
627
|
+
}
|
|
628
|
+
this.yAxis = axesGroup.append('g').attr('class', 'ax-line-chart-axis-y').call(yAxisGenerator);
|
|
465
629
|
// Style the y-axis path and lines
|
|
466
630
|
this.yAxis.selectAll('path').attr('stroke', 'rgba(var(--ax-comp-line-chart-axis-color), 0.2)');
|
|
467
631
|
this.yAxis
|
|
@@ -469,7 +633,7 @@ class AXLineChartComponent extends NXComponent {
|
|
|
469
633
|
.attr('stroke', 'rgba(var(--ax-comp-line-chart-grid-lines-color), 0.2)')
|
|
470
634
|
.attr('stroke-dasharray', '2,2')
|
|
471
635
|
.attr('stroke-opacity', '0.5');
|
|
472
|
-
const dynamicYAxisTickFontSize =
|
|
636
|
+
const dynamicYAxisTickFontSize = this.calculateDynamicFontSize(this.height, 30, this.MIN_FONT_SIZE_Y, this.MAX_FONT_SIZE_Y);
|
|
473
637
|
const yTickTexts = this.yAxis.selectAll('text').attr('style', `
|
|
474
638
|
font-size: ${dynamicYAxisTickFontSize}px;
|
|
475
639
|
font-weight: 400;
|
|
@@ -479,16 +643,23 @@ class AXLineChartComponent extends NXComponent {
|
|
|
479
643
|
yTickTexts.attr('text-anchor', 'start');
|
|
480
644
|
}
|
|
481
645
|
if (options.yAxisLabel) {
|
|
646
|
+
// Calculate proper position for Y-axis title
|
|
647
|
+
// Position it to the left of the tick labels with proper spacing
|
|
648
|
+
const maxTickLabelWidth = this.calculateMaxYAxisTickLabelWidth();
|
|
649
|
+
const padding = this.Y_AXIS_PADDING;
|
|
650
|
+
// Position title to the left of the tick labels
|
|
651
|
+
const labelY = -maxTickLabelWidth - padding;
|
|
652
|
+
// Center the title vertically
|
|
482
653
|
const labelX = -this.height / 2;
|
|
483
|
-
const labelY = -this.margin.left * 0.8;
|
|
484
654
|
axesGroup
|
|
485
655
|
.append('text')
|
|
486
656
|
.attr('class', 'ax-line-chart-axis-label ax-y-axis-label')
|
|
487
657
|
.attr('text-anchor', 'middle')
|
|
488
658
|
.attr('dominant-baseline', 'middle')
|
|
489
|
-
.attr('transform', 'rotate(-90)
|
|
659
|
+
.attr('transform', 'rotate(-90)')
|
|
490
660
|
.attr('x', labelX)
|
|
491
661
|
.attr('y', labelY)
|
|
662
|
+
.attr('direction', 'ltr')
|
|
492
663
|
.attr('style', `
|
|
493
664
|
font-size: 14px;
|
|
494
665
|
font-weight: 500;
|
|
@@ -578,12 +749,11 @@ class AXLineChartComponent extends NXComponent {
|
|
|
578
749
|
this.chart.append('g').attr('class', 'ax-line-chart-crosshair').attr('pointer-events', 'none');
|
|
579
750
|
}
|
|
580
751
|
const animationDuration = this.effectiveOptions().animationDuration;
|
|
581
|
-
const animationEasing =
|
|
752
|
+
const animationEasing = getEasingFunction(this.d3, this.effectiveOptions().animationEasing);
|
|
582
753
|
allSeriesData.forEach((series) => {
|
|
583
|
-
const seriesIdentifier =
|
|
754
|
+
const seriesIdentifier = this.getSeriesIdentifier(series);
|
|
584
755
|
if (this.hiddenSeries.has(seriesIdentifier)) {
|
|
585
|
-
this.
|
|
586
|
-
this.chart.selectAll(`.ax-line-chart-points[data-series-identifier="${seriesIdentifier}"]`).remove();
|
|
756
|
+
this.removeSeriesElements(seriesIdentifier);
|
|
587
757
|
return;
|
|
588
758
|
}
|
|
589
759
|
if (!series.data || series.data.length === 0)
|
|
@@ -600,7 +770,7 @@ class AXLineChartComponent extends NXComponent {
|
|
|
600
770
|
.datum(series.data)
|
|
601
771
|
.attr('class', 'ax-line-chart-line')
|
|
602
772
|
.attr('stroke', lineColor)
|
|
603
|
-
.attr('stroke-width', this.effectiveOptions().lineWidth ??
|
|
773
|
+
.attr('stroke-width', this.effectiveOptions().lineWidth ?? this.DEFAULT_LINE_WIDTH)
|
|
604
774
|
.attr('stroke-linejoin', 'round')
|
|
605
775
|
.attr('stroke-linecap', 'round')
|
|
606
776
|
.attr('d', lineGenerator)
|
|
@@ -611,14 +781,14 @@ class AXLineChartComponent extends NXComponent {
|
|
|
611
781
|
.on('mouseenter', () => {
|
|
612
782
|
line
|
|
613
783
|
.transition()
|
|
614
|
-
.duration(
|
|
615
|
-
.attr('stroke-width', (this.effectiveOptions().lineWidth ??
|
|
784
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
785
|
+
.attr('stroke-width', (this.effectiveOptions().lineWidth ?? this.DEFAULT_LINE_WIDTH) * this.LINE_HIGHLIGHT_MULTIPLIER);
|
|
616
786
|
})
|
|
617
787
|
.on('mouseleave', () => {
|
|
618
788
|
line
|
|
619
789
|
.transition()
|
|
620
|
-
.duration(
|
|
621
|
-
.attr('stroke-width', this.effectiveOptions().lineWidth ??
|
|
790
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
791
|
+
.attr('stroke-width', this.effectiveOptions().lineWidth ?? this.DEFAULT_LINE_WIDTH);
|
|
622
792
|
});
|
|
623
793
|
const totalLength = line.node().getTotalLength();
|
|
624
794
|
line
|
|
@@ -645,13 +815,13 @@ class AXLineChartComponent extends NXComponent {
|
|
|
645
815
|
.on('mouseenter', () => {
|
|
646
816
|
area
|
|
647
817
|
.transition()
|
|
648
|
-
.duration(
|
|
818
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
649
819
|
.attr('opacity', ((this.effectiveOptions().fillOpacity ?? 20) / 100) * 1.5);
|
|
650
820
|
})
|
|
651
821
|
.on('mouseleave', () => {
|
|
652
822
|
area
|
|
653
823
|
.transition()
|
|
654
|
-
.duration(
|
|
824
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
655
825
|
.attr('opacity', (this.effectiveOptions().fillOpacity ?? 20) / 100);
|
|
656
826
|
});
|
|
657
827
|
area
|
|
@@ -667,17 +837,15 @@ class AXLineChartComponent extends NXComponent {
|
|
|
667
837
|
if (this.effectiveOptions().showPoints !== false) {
|
|
668
838
|
const pointMap = new Map();
|
|
669
839
|
allSeriesData.forEach((series) => {
|
|
670
|
-
const seriesIdentifier =
|
|
840
|
+
const seriesIdentifier = this.getSeriesIdentifier(series);
|
|
671
841
|
if (this.hiddenSeries.has(seriesIdentifier) || !series.data || series.data.length === 0) {
|
|
672
842
|
return;
|
|
673
843
|
}
|
|
674
|
-
const
|
|
675
|
-
const maxPoints = 100;
|
|
676
|
-
const pointData = series.data.length > maxPoints ? this.getReducedDataPoints(series.data, maxPoints) : series.data;
|
|
844
|
+
const pointData = this.getReducedDataPoints(series.data, this.MAX_POINTS_TO_RENDER);
|
|
677
845
|
pointData.forEach((point) => {
|
|
678
846
|
const x = getX(point);
|
|
679
847
|
const y = this.yScale(point.y);
|
|
680
|
-
const key =
|
|
848
|
+
const key = this.createPointKey(x, y);
|
|
681
849
|
if (!pointMap.has(key)) {
|
|
682
850
|
pointMap.set(key, []);
|
|
683
851
|
}
|
|
@@ -685,9 +853,9 @@ class AXLineChartComponent extends NXComponent {
|
|
|
685
853
|
});
|
|
686
854
|
});
|
|
687
855
|
let animationCounter = 0;
|
|
688
|
-
const totalVisibleSeriesCount = allSeriesData.filter((s) => !this.hiddenSeries.has(
|
|
856
|
+
const totalVisibleSeriesCount = allSeriesData.filter((s) => !this.hiddenSeries.has(this.getSeriesIdentifier(s)) && s.data && s.data.length > 0).length;
|
|
689
857
|
allSeriesData.forEach((series) => {
|
|
690
|
-
const seriesIdentifier =
|
|
858
|
+
const seriesIdentifier = this.getSeriesIdentifier(series);
|
|
691
859
|
if (this.hiddenSeries.has(seriesIdentifier) || !series.data || series.data.length === 0) {
|
|
692
860
|
return;
|
|
693
861
|
}
|
|
@@ -698,10 +866,9 @@ class AXLineChartComponent extends NXComponent {
|
|
|
698
866
|
.attr('data-series-identifier', seriesIdentifier)
|
|
699
867
|
.attr('pointer-events', 'none');
|
|
700
868
|
pointsGroup
|
|
701
|
-
.on('mouseenter', () => this.handlePointGroupEnter(series.originalIndex
|
|
869
|
+
.on('mouseenter', () => this.handlePointGroupEnter(series.originalIndex))
|
|
702
870
|
.on('mouseleave', () => this.handlePointGroupLeave());
|
|
703
|
-
const
|
|
704
|
-
const pointData = series.data.length > maxPoints ? this.getReducedDataPoints(series.data, maxPoints) : series.data;
|
|
871
|
+
const pointData = this.getReducedDataPoints(series.data, this.MAX_POINTS_TO_RENDER);
|
|
705
872
|
pointsGroup
|
|
706
873
|
.selectAll('circle')
|
|
707
874
|
.data(pointData)
|
|
@@ -721,7 +888,7 @@ class AXLineChartComponent extends NXComponent {
|
|
|
721
888
|
.on('mouseenter', (event, d) => {
|
|
722
889
|
const x = getX(d);
|
|
723
890
|
const y = this.yScale(d.y);
|
|
724
|
-
const key =
|
|
891
|
+
const key = this.createPointKey(x, y);
|
|
725
892
|
const overlappingPoints = pointMap.get(key) || [];
|
|
726
893
|
if (overlappingPoints.length > 1) {
|
|
727
894
|
this.handleOverlappingPointsHover(event, overlappingPoints);
|
|
@@ -732,7 +899,7 @@ class AXLineChartComponent extends NXComponent {
|
|
|
732
899
|
this.d3
|
|
733
900
|
.select(event.target)
|
|
734
901
|
.transition()
|
|
735
|
-
.duration(
|
|
902
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
736
903
|
.attr('r', (this.effectiveOptions().pointRadius ?? 4) * 1.5)
|
|
737
904
|
.attr('stroke-width', 2)
|
|
738
905
|
.attr('stroke', `rgb(var(--ax-comp-line-chart-bg-color))`);
|
|
@@ -743,15 +910,15 @@ class AXLineChartComponent extends NXComponent {
|
|
|
743
910
|
this.d3
|
|
744
911
|
.select(event.target)
|
|
745
912
|
.transition()
|
|
746
|
-
.duration(
|
|
747
|
-
.attr('r', this.effectiveOptions().pointRadius ??
|
|
913
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
914
|
+
.attr('r', this.effectiveOptions().pointRadius ?? this.DEFAULT_POINT_RADIUS)
|
|
748
915
|
.attr('stroke-width', 1)
|
|
749
916
|
.attr('stroke', '#fff');
|
|
750
917
|
})
|
|
751
918
|
.on('click', (event, d) => {
|
|
752
919
|
const x = getX(d);
|
|
753
920
|
const y = this.yScale(d.y);
|
|
754
|
-
const key =
|
|
921
|
+
const key = this.createPointKey(x, y);
|
|
755
922
|
const overlappingPoints = pointMap.get(key) || [];
|
|
756
923
|
if (overlappingPoints.length > 1) {
|
|
757
924
|
overlappingPoints.forEach(({ point, series }) => {
|
|
@@ -763,10 +930,11 @@ class AXLineChartComponent extends NXComponent {
|
|
|
763
930
|
}
|
|
764
931
|
})
|
|
765
932
|
.transition()
|
|
766
|
-
.delay((
|
|
767
|
-
|
|
933
|
+
.delay((_d, i) => i * Math.min(this.POINT_ANIMATION_DELAY_MS, (animationDuration || 0) * 0.05) +
|
|
934
|
+
(animationDuration || 0) * this.POINT_ANIMATION_DELAY_RATIO)
|
|
935
|
+
.duration((animationDuration || 0) * this.POINT_ANIMATION_DURATION_RATIO)
|
|
768
936
|
.ease(animationEasing)
|
|
769
|
-
.attr('r', this.effectiveOptions().pointRadius ??
|
|
937
|
+
.attr('r', this.effectiveOptions().pointRadius ?? this.DEFAULT_POINT_RADIUS)
|
|
770
938
|
.on('end', (d, i, nodes) => {
|
|
771
939
|
if (i === nodes.length - 1) {
|
|
772
940
|
animationCounter++;
|
|
@@ -778,9 +946,7 @@ class AXLineChartComponent extends NXComponent {
|
|
|
778
946
|
});
|
|
779
947
|
}
|
|
780
948
|
else {
|
|
781
|
-
setTimeout(() =>
|
|
782
|
-
this.enablePointerEventsAfterAnimation();
|
|
783
|
-
}, animationDuration || 0 + 100);
|
|
949
|
+
setTimeout(() => this.enablePointerEventsAfterAnimation(), (animationDuration || 0) + this.ANIMATION_END_BUFFER);
|
|
784
950
|
}
|
|
785
951
|
}
|
|
786
952
|
enablePointerEventsAfterAnimation() {
|
|
@@ -795,19 +961,19 @@ class AXLineChartComponent extends NXComponent {
|
|
|
795
961
|
this.chart.selectAll('.ax-line-chart-point').attr('pointer-events', 'all');
|
|
796
962
|
this.svg.classed('ax-line-chart-animating', false);
|
|
797
963
|
}
|
|
798
|
-
handlePointGroupEnter(originalIndex
|
|
964
|
+
handlePointGroupEnter(originalIndex) {
|
|
799
965
|
this.chart
|
|
800
966
|
.select(`.ax-line-chart-series-${originalIndex} .ax-line-chart-line`)
|
|
801
967
|
.transition()
|
|
802
|
-
.duration(
|
|
803
|
-
.attr('stroke-width', (this.effectiveOptions().lineWidth ??
|
|
968
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
969
|
+
.attr('stroke-width', (this.effectiveOptions().lineWidth ?? this.DEFAULT_LINE_WIDTH) * this.LINE_HIGHLIGHT_MULTIPLIER);
|
|
804
970
|
}
|
|
805
971
|
handlePointGroupLeave() {
|
|
806
972
|
this.chart
|
|
807
973
|
.selectAll('.ax-line-chart-line')
|
|
808
974
|
.transition()
|
|
809
|
-
.duration(
|
|
810
|
-
.attr('stroke-width', this.effectiveOptions().lineWidth ??
|
|
975
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
976
|
+
.attr('stroke-width', this.effectiveOptions().lineWidth ?? this.DEFAULT_LINE_WIDTH);
|
|
811
977
|
if (this.effectiveOptions().showCrosshair === true) {
|
|
812
978
|
this.chart.select('.ax-line-chart-crosshair').selectAll('*').remove();
|
|
813
979
|
}
|
|
@@ -830,7 +996,7 @@ class AXLineChartComponent extends NXComponent {
|
|
|
830
996
|
const color = series.lineColor || getChartColor(originalIndex, this.chartColors);
|
|
831
997
|
this._tooltipData.set({
|
|
832
998
|
title: dataPoint.tooltipLabel || series.tooltipLabel || series.label || `Series ${originalIndex + 1}`,
|
|
833
|
-
value: dataPoint.y
|
|
999
|
+
value: formatLargeNumber(dataPoint.y),
|
|
834
1000
|
color: color,
|
|
835
1001
|
});
|
|
836
1002
|
this._tooltipVisible.set(true);
|
|
@@ -974,7 +1140,7 @@ class AXLineChartComponent extends NXComponent {
|
|
|
974
1140
|
return;
|
|
975
1141
|
const tooltipData = {
|
|
976
1142
|
title: overlappingPoints.map(({ series }) => series.label),
|
|
977
|
-
value: overlappingPoints[0].point.y
|
|
1143
|
+
value: formatLargeNumber(overlappingPoints[0].point.y),
|
|
978
1144
|
color: overlappingPoints.map(({ series }) => series.lineColor || getChartColor(series.originalIndex, this.chartColors)),
|
|
979
1145
|
};
|
|
980
1146
|
this._tooltipData.set(tooltipData);
|
|
@@ -984,37 +1150,13 @@ class AXLineChartComponent extends NXComponent {
|
|
|
984
1150
|
this.showCrosshairLines(overlappingPoints[0].point);
|
|
985
1151
|
}
|
|
986
1152
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
case 'linear':
|
|
990
|
-
return this.d3.easeLinear;
|
|
991
|
-
case 'ease':
|
|
992
|
-
return this.d3.easePolyInOut;
|
|
993
|
-
case 'ease-in':
|
|
994
|
-
return this.d3.easePolyIn;
|
|
995
|
-
case 'ease-out':
|
|
996
|
-
return this.d3.easePolyOut;
|
|
997
|
-
case 'ease-in-out':
|
|
998
|
-
return this.d3.easePolyInOut;
|
|
999
|
-
case 'cubic':
|
|
1000
|
-
return this.d3.easeCubic;
|
|
1001
|
-
case 'cubic-in':
|
|
1002
|
-
return this.d3.easeCubicIn;
|
|
1003
|
-
case 'cubic-out':
|
|
1004
|
-
return this.d3.easeCubicOut;
|
|
1005
|
-
case 'cubic-in-out':
|
|
1006
|
-
return this.d3.easeCubicInOut;
|
|
1007
|
-
default:
|
|
1008
|
-
return this.d3.easeCubicOut;
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1011
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AXLineChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1012
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.3", 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: ["ax-line-chart{display:block;width:100%;height:100%;min-height:200px;--ax-comp-line-chart-labels-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-line-chart-axis-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-line-chart-grid-lines-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-line-chart-bg-color: 0, 0, 0, 0;--ax-comp-line-chart-text-color: var(--ax-sys-color-on-lightest-surface)}ax-line-chart .ax-line-chart{width:100%;height:100%;position:relative;display:flex;align-items:center;justify-content:center;border-radius:.5rem;overflow:hidden;color:rgba(var(--ax-comp-line-chart-text-color));background-color:rgb(var(--ax-comp-line-chart-bg-color))}ax-line-chart .ax-line-chart svg{width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}ax-line-chart .ax-line-chart svg g:has(text){font-family:inherit}ax-line-chart .ax-line-chart-no-data-message{text-align:center;background-color:rgb(var(--ax-comp-line-chart-bg-color));padding:1.5rem;border-radius:.5rem;border:1px solid rgba(var(--ax-sys-color-surface));width:80%;max-width:300px}ax-line-chart .ax-line-chart-no-data-message .ax-line-chart-no-data-icon{opacity:.6;margin-bottom:.75rem}ax-line-chart .ax-line-chart-no-data-message .ax-line-chart-no-data-text{font-size:1rem;font-weight:600;margin-bottom:.5rem}ax-line-chart .ax-line-chart-no-data-message .ax-line-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 });
|
|
1153
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXLineChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1154
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.15", 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: ["ax-line-chart{display:block;width:100%;height:100%;min-height:200px;--ax-comp-line-chart-labels-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-line-chart-axis-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-line-chart-grid-lines-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-line-chart-bg-color: 0, 0, 0, 0;--ax-comp-line-chart-text-color: var(--ax-sys-color-on-lightest-surface)}ax-line-chart .ax-line-chart{width:100%;height:100%;position:relative;display:flex;align-items:center;justify-content:center;border-radius:.5rem;color:rgba(var(--ax-comp-line-chart-text-color));background-color:rgb(var(--ax-comp-line-chart-bg-color))}ax-line-chart .ax-line-chart svg{width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}ax-line-chart .ax-line-chart svg g:has(text){font-family:inherit}ax-line-chart .ax-line-chart-no-data-message{text-align:center;background-color:rgb(var(--ax-comp-line-chart-bg-color));padding:1.5rem;border-radius:.5rem;border:1px solid rgba(var(--ax-sys-color-surface));width:80%;max-width:300px}ax-line-chart .ax-line-chart-no-data-message .ax-line-chart-no-data-icon{opacity:.6;margin-bottom:.75rem}ax-line-chart .ax-line-chart-no-data-message .ax-line-chart-no-data-text{font-size:1rem;font-weight:600;margin-bottom:.5rem}ax-line-chart .ax-line-chart-no-data-message .ax-line-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 });
|
|
1013
1155
|
}
|
|
1014
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1156
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AXLineChartComponent, decorators: [{
|
|
1015
1157
|
type: Component,
|
|
1016
|
-
args: [{ selector: 'ax-line-chart', standalone: true, encapsulation: ViewEncapsulation.None, imports: [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: ["ax-line-chart{display:block;width:100%;height:100%;min-height:200px;--ax-comp-line-chart-labels-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-line-chart-axis-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-line-chart-grid-lines-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-line-chart-bg-color: 0, 0, 0, 0;--ax-comp-line-chart-text-color: var(--ax-sys-color-on-lightest-surface)}ax-line-chart .ax-line-chart{width:100%;height:100%;position:relative;display:flex;align-items:center;justify-content:center;border-radius:.5rem;
|
|
1017
|
-
}] });
|
|
1158
|
+
args: [{ selector: 'ax-line-chart', standalone: true, encapsulation: ViewEncapsulation.None, imports: [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: ["ax-line-chart{display:block;width:100%;height:100%;min-height:200px;--ax-comp-line-chart-labels-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-line-chart-axis-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-line-chart-grid-lines-color: var(--ax-sys-color-on-lightest-surface);--ax-comp-line-chart-bg-color: 0, 0, 0, 0;--ax-comp-line-chart-text-color: var(--ax-sys-color-on-lightest-surface)}ax-line-chart .ax-line-chart{width:100%;height:100%;position:relative;display:flex;align-items:center;justify-content:center;border-radius:.5rem;color:rgba(var(--ax-comp-line-chart-text-color));background-color:rgb(var(--ax-comp-line-chart-bg-color))}ax-line-chart .ax-line-chart svg{width:100%;height:100%;max-width:100%;max-height:100%;overflow:visible}ax-line-chart .ax-line-chart svg g:has(text){font-family:inherit}ax-line-chart .ax-line-chart-no-data-message{text-align:center;background-color:rgb(var(--ax-comp-line-chart-bg-color));padding:1.5rem;border-radius:.5rem;border:1px solid rgba(var(--ax-sys-color-surface));width:80%;max-width:300px}ax-line-chart .ax-line-chart-no-data-message .ax-line-chart-no-data-icon{opacity:.6;margin-bottom:.75rem}ax-line-chart .ax-line-chart-no-data-message .ax-line-chart-no-data-text{font-size:1rem;font-weight:600;margin-bottom:.5rem}ax-line-chart .ax-line-chart-no-data-message .ax-line-chart-no-data-help{font-size:.8rem;opacity:.6}\n"] }]
|
|
1159
|
+
}], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], pointClick: [{ type: i0.Output, args: ["pointClick"] }], chartContainerEl: [{ type: i0.ViewChild, args: ['chartContainer', { isSignal: true }] }] } });
|
|
1018
1160
|
|
|
1019
1161
|
/**
|
|
1020
1162
|
* Generated bundle index. Do not edit.
|