@acorex/charts 21.0.0-next.13 → 21.0.0-next.15
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 +58 -2
- package/donut-chart/index.d.ts +42 -3
- package/fesm2022/acorex-charts-bar-chart.mjs +258 -119
- package/fesm2022/acorex-charts-bar-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-chart-legend.mjs.map +1 -1
- package/fesm2022/acorex-charts-donut-chart.mjs +289 -186
- package/fesm2022/acorex-charts-donut-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-gauge-chart.mjs +80 -56
- package/fesm2022/acorex-charts-gauge-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts-line-chart.mjs +286 -134
- package/fesm2022/acorex-charts-line-chart.mjs.map +1 -1
- package/fesm2022/acorex-charts.mjs +23 -1
- package/fesm2022/acorex-charts.mjs.map +1 -1
- package/gauge-chart/index.d.ts +4 -1
- package/index.d.ts +5 -1
- package/line-chart/index.d.ts +66 -1
- package/package.json +8 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AXChartComponent, AX_CHART_COLOR_PALETTE, getChartColor, computeTooltipPosition } from '@acorex/charts';
|
|
1
|
+
import { AXChartComponent, AX_CHART_COLOR_PALETTE, formatLargeNumber, getChartColor, getEasingFunction, computeTooltipPosition } from '@acorex/charts';
|
|
2
2
|
import { AXChartTooltipComponent } from '@acorex/charts/chart-tooltip';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
4
|
import { InjectionToken, input, output, viewChild, signal, inject, computed, effect, ChangeDetectionStrategy, ViewEncapsulation, Component } from '@angular/core';
|
|
@@ -105,22 +105,124 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
105
105
|
...this.effectiveOptions().messages,
|
|
106
106
|
};
|
|
107
107
|
}, ...(ngDevMode ? [{ debugName: "effectiveMessages" }] : []));
|
|
108
|
-
//
|
|
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
|
|
109
152
|
TOOLTIP_GAP = 10;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const rawData = this.data();
|
|
153
|
+
/**
|
|
154
|
+
* Normalizes input data to consistent array format with originalIndex
|
|
155
|
+
*/
|
|
156
|
+
normalizeData(rawData) {
|
|
115
157
|
if (Array.isArray(rawData)) {
|
|
116
|
-
|
|
158
|
+
return rawData.map((s, i) => ({ ...s, originalIndex: i }));
|
|
117
159
|
}
|
|
118
|
-
|
|
119
|
-
|
|
160
|
+
if (rawData && 'data' in rawData) {
|
|
161
|
+
return [{ ...rawData, originalIndex: 0 }];
|
|
120
162
|
}
|
|
121
|
-
|
|
122
|
-
|
|
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
|
+
}
|
|
123
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());
|
|
124
226
|
this.effectiveOptions();
|
|
125
227
|
if (this._rendered()) {
|
|
126
228
|
this.updateChart();
|
|
@@ -142,14 +244,15 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
142
244
|
return sum + series.data.reduce((sSum, p) => sSum + p.y, 0);
|
|
143
245
|
}, 0);
|
|
144
246
|
return this._fullNormalizedData.map((series) => {
|
|
247
|
+
const seriesIdentifier = this.getSeriesIdentifier(series);
|
|
145
248
|
const seriesSum = series.data.reduce((sum, p) => sum + p.y, 0);
|
|
146
249
|
const percentage = totalSum > 0 ? (seriesSum / totalSum) * 100 : 0;
|
|
147
250
|
return {
|
|
148
|
-
id:
|
|
251
|
+
id: seriesIdentifier,
|
|
149
252
|
name: series.label || `Series ${series.originalIndex + 1}`,
|
|
150
253
|
value: seriesSum,
|
|
151
254
|
color: series.lineColor || getChartColor(series.originalIndex, this.chartColors),
|
|
152
|
-
hidden: this.hiddenSeries.has(
|
|
255
|
+
hidden: this.hiddenSeries.has(seriesIdentifier),
|
|
153
256
|
percentage: parseFloat(percentage.toFixed(2)),
|
|
154
257
|
};
|
|
155
258
|
});
|
|
@@ -164,11 +267,11 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
164
267
|
allPointsGroups.classed('ax-chart-dimmed', false).style('opacity', 1);
|
|
165
268
|
allSeriesGroups
|
|
166
269
|
.selectAll('.ax-line-chart-line')
|
|
167
|
-
.attr('stroke-width', this.effectiveOptions().lineWidth ??
|
|
270
|
+
.attr('stroke-width', this.effectiveOptions().lineWidth ?? this.DEFAULT_LINE_WIDTH)
|
|
168
271
|
.classed('ax-chart-highlighted-path', false);
|
|
169
272
|
allPointsGroups
|
|
170
273
|
.selectAll('circle.ax-line-chart-point')
|
|
171
|
-
.attr('r', this.effectiveOptions().pointRadius ??
|
|
274
|
+
.attr('r', this.effectiveOptions().pointRadius ?? this.DEFAULT_POINT_RADIUS)
|
|
172
275
|
.classed('ax-chart-highlighted-point', false);
|
|
173
276
|
return;
|
|
174
277
|
}
|
|
@@ -180,23 +283,22 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
180
283
|
targetPointsGroup.classed('ax-chart-dimmed', false).style('opacity', 1);
|
|
181
284
|
targetSeriesGroup
|
|
182
285
|
.selectAll('.ax-line-chart-line')
|
|
183
|
-
.attr('stroke-width', (this.effectiveOptions().lineWidth ??
|
|
286
|
+
.attr('stroke-width', (this.effectiveOptions().lineWidth ?? this.DEFAULT_LINE_WIDTH) * this.LINE_HIGHLIGHT_MULTIPLIER)
|
|
184
287
|
.classed('ax-chart-highlighted-path', true);
|
|
185
288
|
targetPointsGroup
|
|
186
289
|
.selectAll('circle.ax-line-chart-point')
|
|
187
|
-
.attr('r', (this.effectiveOptions().pointRadius ??
|
|
188
|
-
.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);
|
|
189
292
|
}
|
|
190
293
|
toggleSegment(id) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
this.hiddenSeries.delete(seriesId);
|
|
294
|
+
if (this.hiddenSeries.has(id)) {
|
|
295
|
+
this.hiddenSeries.delete(id);
|
|
194
296
|
}
|
|
195
297
|
else {
|
|
196
|
-
this.hiddenSeries.add(
|
|
298
|
+
this.hiddenSeries.add(id);
|
|
197
299
|
}
|
|
198
300
|
this.updateChart();
|
|
199
|
-
return !this.hiddenSeries.has(
|
|
301
|
+
return !this.hiddenSeries.has(id);
|
|
200
302
|
}
|
|
201
303
|
async loadD3() {
|
|
202
304
|
try {
|
|
@@ -224,7 +326,7 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
224
326
|
this.showNoDataMessage(containerElement);
|
|
225
327
|
return;
|
|
226
328
|
}
|
|
227
|
-
const visibleDataForRendering = allSeriesData.filter((series) => !this.hiddenSeries.has(
|
|
329
|
+
const visibleDataForRendering = allSeriesData.filter((series) => !this.hiddenSeries.has(this.getSeriesIdentifier(series)));
|
|
228
330
|
if (visibleDataForRendering.length === 0) {
|
|
229
331
|
// All data is present, but all series are hidden by the legend
|
|
230
332
|
this.showAllSeriesHiddenMessage(containerElement);
|
|
@@ -232,17 +334,11 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
232
334
|
// For now, we will show the message and return, as scales/axes can't be drawn.
|
|
233
335
|
return;
|
|
234
336
|
}
|
|
235
|
-
// Scales should be based on potentially visible data to avoid errors with empty domains
|
|
236
|
-
// If all are hidden, scales might become an issue. Let's use all data for scales,
|
|
237
|
-
// and rendering will handle visibility.
|
|
238
|
-
const dataForScales = allSeriesData.flatMap((s) => s.data).length > 0 ? allSeriesData : [{ data: [{ x: 0, y: 0 }] }];
|
|
239
337
|
const chartOptions = this.effectiveOptions();
|
|
240
338
|
this.setupDimensions(containerElement, chartOptions);
|
|
241
|
-
// Use allSeriesData for domain calculation to keep scales consistent
|
|
242
|
-
// but filter out series with no data points for scale calculation.
|
|
339
|
+
// Use allSeriesData for domain calculation to keep scales consistent
|
|
243
340
|
const dataForScaleSetup = allSeriesData.filter((s) => s.data && s.data.length > 0);
|
|
244
341
|
if (dataForScaleSetup.length === 0) {
|
|
245
|
-
// If after filtering, there's no data with points (e.g. all series have empty data arrays)
|
|
246
342
|
this.showNoDataMessage(containerElement);
|
|
247
343
|
return;
|
|
248
344
|
}
|
|
@@ -265,7 +361,7 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
265
361
|
this.calculateMargins(options, containerElement.clientWidth);
|
|
266
362
|
const containerWidth = containerElement.clientWidth;
|
|
267
363
|
const containerHeight = containerElement.clientHeight;
|
|
268
|
-
const minDim = Math.min(
|
|
364
|
+
const minDim = Math.min(this.MIN_CONTAINER_DIMENSION, containerWidth, containerHeight);
|
|
269
365
|
if (options.width && options.height) {
|
|
270
366
|
this.width = options.width - this.margin.left - this.margin.right;
|
|
271
367
|
this.height = options.height - this.margin.top - this.margin.bottom;
|
|
@@ -274,8 +370,8 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
274
370
|
this.width = Math.max(containerWidth, minDim) - this.margin.left - this.margin.right;
|
|
275
371
|
this.height = Math.max(containerHeight, minDim) - this.margin.top - this.margin.bottom;
|
|
276
372
|
}
|
|
277
|
-
this.width = Math.max(this.width,
|
|
278
|
-
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);
|
|
279
375
|
const totalWidth = this.width + this.margin.left + this.margin.right;
|
|
280
376
|
const totalHeight = this.height + this.margin.top + this.margin.bottom;
|
|
281
377
|
const svg = this.d3
|
|
@@ -297,16 +393,16 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
297
393
|
}
|
|
298
394
|
calculateMargins(options, containerWidth) {
|
|
299
395
|
this.margin = {
|
|
300
|
-
top: options.margins?.top ??
|
|
301
|
-
right: options.margins?.right ??
|
|
302
|
-
bottom: options.margins?.bottom ??
|
|
303
|
-
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,
|
|
304
400
|
};
|
|
305
401
|
const allDataPoints = this._fullNormalizedData.flatMap((series) => series.data);
|
|
306
402
|
if (allDataPoints.length > 0) {
|
|
307
403
|
const allNumericX = allDataPoints.every((d) => typeof d.x === 'number');
|
|
308
404
|
if (!allNumericX) {
|
|
309
|
-
const visibleSeries = this._fullNormalizedData.filter((s) => !this.hiddenSeries.has(
|
|
405
|
+
const visibleSeries = this._fullNormalizedData.filter((s) => !this.hiddenSeries.has(this.getSeriesIdentifier(s)));
|
|
310
406
|
const allXValues = new Set(visibleSeries.flatMap((series) => series.data.map((d) => String(d.x))));
|
|
311
407
|
const labelCount = allXValues.size;
|
|
312
408
|
if (labelCount > 0 && containerWidth > 0) {
|
|
@@ -315,11 +411,16 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
315
411
|
const availableWidthPerLabel = workingWidth / labelCount;
|
|
316
412
|
const labels = Array.from(allXValues);
|
|
317
413
|
const longestLabel = labels.reduce((a, b) => (a.length > b.length ? a : b), '');
|
|
318
|
-
const estimatedFontSize =
|
|
319
|
-
|
|
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;
|
|
320
419
|
if (estimatedLongestLabelWidth > availableWidthPerLabel) {
|
|
321
|
-
|
|
322
|
-
|
|
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);
|
|
323
424
|
}
|
|
324
425
|
}
|
|
325
426
|
}
|
|
@@ -331,15 +432,23 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
331
432
|
this.margin.bottom = Math.max(this.margin.bottom, 40 + extraBottomMargin);
|
|
332
433
|
}
|
|
333
434
|
if (options.yAxisLabel) {
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
|
|
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);
|
|
337
446
|
}
|
|
338
447
|
if (options.showXAxis !== false) {
|
|
339
|
-
this.margin.bottom = Math.max(this.margin.bottom,
|
|
448
|
+
this.margin.bottom = Math.max(this.margin.bottom, this.MIN_MARGIN_BOTTOM);
|
|
340
449
|
}
|
|
341
450
|
if (options.showYAxis !== false) {
|
|
342
|
-
this.margin.left = Math.max(this.margin.left,
|
|
451
|
+
this.margin.left = Math.max(this.margin.left, this.MIN_MARGIN_LEFT);
|
|
343
452
|
}
|
|
344
453
|
}
|
|
345
454
|
setupScales(data) {
|
|
@@ -355,16 +464,17 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
355
464
|
}
|
|
356
465
|
const allNumericX = allDataPoints.every((d) => typeof d.x === 'number');
|
|
357
466
|
if (allNumericX) {
|
|
358
|
-
const xMin = this.d3.min(allDataPoints, (d) => d.x) ?? 0;
|
|
359
467
|
const xMax = this.d3.max(allDataPoints, (d) => d.x) ?? 0;
|
|
360
|
-
if (
|
|
468
|
+
if (xMax === 0) {
|
|
469
|
+
// If all values are 0, show -1 to 1 range
|
|
361
470
|
this.xScale = this.d3
|
|
362
471
|
.scaleLinear()
|
|
363
|
-
.domain([
|
|
472
|
+
.domain([-1, 1])
|
|
364
473
|
.range([0, this.width]);
|
|
365
474
|
}
|
|
366
475
|
else {
|
|
367
|
-
|
|
476
|
+
// Always start X axis from 0 for numeric data
|
|
477
|
+
this.xScale = this.d3.scaleLinear().domain([0, xMax]).range([0, this.width]);
|
|
368
478
|
}
|
|
369
479
|
}
|
|
370
480
|
else {
|
|
@@ -396,11 +506,58 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
396
506
|
const isRtl = document.documentElement.dir === 'rtl' || document.body.dir === 'rtl';
|
|
397
507
|
const axesGroup = this.chart.append('g').attr('class', 'ax-line-chart-axes');
|
|
398
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
|
+
}
|
|
399
556
|
this.xAxis = axesGroup
|
|
400
557
|
.append('g')
|
|
401
558
|
.attr('class', 'ax-line-chart-axis-x')
|
|
402
559
|
.attr('transform', `translate(0,${this.height})`)
|
|
403
|
-
.call(
|
|
560
|
+
.call(xAxisGenerator);
|
|
404
561
|
// Style the x-axis path and lines
|
|
405
562
|
this.xAxis.selectAll('path').attr('stroke', 'rgba(var(--ax-comp-line-chart-axis-color), 0.2)');
|
|
406
563
|
this.xAxis
|
|
@@ -408,19 +565,21 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
408
565
|
.attr('stroke', 'rgba(var(--ax-comp-line-chart-grid-lines-color), 0.2)')
|
|
409
566
|
.attr('stroke-dasharray', '2,2')
|
|
410
567
|
.attr('stroke-opacity', '0.5');
|
|
411
|
-
const dynamicXAxisTickFontSize =
|
|
568
|
+
const dynamicXAxisTickFontSize = this.calculateDynamicFontSize(this.width, 45, this.MIN_FONT_SIZE_X, this.MAX_FONT_SIZE_X);
|
|
412
569
|
const xAxisTicks = this.xAxis
|
|
413
570
|
.selectAll('text')
|
|
414
571
|
.style('font-size', `${dynamicXAxisTickFontSize}px`)
|
|
415
572
|
.style('font-weight', '400')
|
|
416
573
|
.style('fill', 'rgba(var(--ax-comp-line-chart-labels-color), 0.7)');
|
|
417
|
-
// Automatically rotate labels if they are likely to overlap
|
|
418
|
-
|
|
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) {
|
|
419
577
|
const step = this.xScale.step();
|
|
420
578
|
const longestLabel = this.xScale.domain().reduce((a, b) => (a.length > b.length ? a : b), '');
|
|
421
|
-
// Using
|
|
422
|
-
const estimatedLongestLabelWidth = longestLabel.length * dynamicXAxisTickFontSize *
|
|
579
|
+
// Using char width ratio constant for better estimate
|
|
580
|
+
const estimatedLongestLabelWidth = longestLabel.length * dynamicXAxisTickFontSize * this.CHAR_WIDTH_RATIO;
|
|
423
581
|
if (estimatedLongestLabelWidth > step) {
|
|
582
|
+
labelsAreRotated = true;
|
|
424
583
|
xAxisTicks
|
|
425
584
|
.attr('transform', 'rotate(-45)')
|
|
426
585
|
.style('text-anchor', 'end')
|
|
@@ -428,16 +587,17 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
428
587
|
.attr('dy', '0.15em');
|
|
429
588
|
}
|
|
430
589
|
}
|
|
431
|
-
if
|
|
432
|
-
|
|
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) {
|
|
433
594
|
axesGroup
|
|
434
595
|
.append('text')
|
|
435
596
|
.attr('class', 'ax-line-chart-axis-label ax-x-axis-label')
|
|
436
597
|
.attr('text-anchor', 'middle')
|
|
437
|
-
.attr('dominant-baseline', 'middle')
|
|
438
598
|
.attr('x', this.width / 2)
|
|
439
|
-
.attr('y',
|
|
440
|
-
.attr('
|
|
599
|
+
.attr('y', this.height + this.margin.bottom - 5)
|
|
600
|
+
.attr('direction', 'ltr')
|
|
441
601
|
.attr('style', `
|
|
442
602
|
font-size: 14px;
|
|
443
603
|
font-weight: 500;
|
|
@@ -448,10 +608,24 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
448
608
|
}
|
|
449
609
|
}
|
|
450
610
|
if (showYAxis) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
.
|
|
454
|
-
.
|
|
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);
|
|
455
629
|
// Style the y-axis path and lines
|
|
456
630
|
this.yAxis.selectAll('path').attr('stroke', 'rgba(var(--ax-comp-line-chart-axis-color), 0.2)');
|
|
457
631
|
this.yAxis
|
|
@@ -459,7 +633,7 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
459
633
|
.attr('stroke', 'rgba(var(--ax-comp-line-chart-grid-lines-color), 0.2)')
|
|
460
634
|
.attr('stroke-dasharray', '2,2')
|
|
461
635
|
.attr('stroke-opacity', '0.5');
|
|
462
|
-
const dynamicYAxisTickFontSize =
|
|
636
|
+
const dynamicYAxisTickFontSize = this.calculateDynamicFontSize(this.height, 30, this.MIN_FONT_SIZE_Y, this.MAX_FONT_SIZE_Y);
|
|
463
637
|
const yTickTexts = this.yAxis.selectAll('text').attr('style', `
|
|
464
638
|
font-size: ${dynamicYAxisTickFontSize}px;
|
|
465
639
|
font-weight: 400;
|
|
@@ -469,16 +643,23 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
469
643
|
yTickTexts.attr('text-anchor', 'start');
|
|
470
644
|
}
|
|
471
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
|
|
472
653
|
const labelX = -this.height / 2;
|
|
473
|
-
const labelY = -this.margin.left * 0.8;
|
|
474
654
|
axesGroup
|
|
475
655
|
.append('text')
|
|
476
656
|
.attr('class', 'ax-line-chart-axis-label ax-y-axis-label')
|
|
477
657
|
.attr('text-anchor', 'middle')
|
|
478
658
|
.attr('dominant-baseline', 'middle')
|
|
479
|
-
.attr('transform', 'rotate(-90)
|
|
659
|
+
.attr('transform', 'rotate(-90)')
|
|
480
660
|
.attr('x', labelX)
|
|
481
661
|
.attr('y', labelY)
|
|
662
|
+
.attr('direction', 'ltr')
|
|
482
663
|
.attr('style', `
|
|
483
664
|
font-size: 14px;
|
|
484
665
|
font-weight: 500;
|
|
@@ -568,12 +749,11 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
568
749
|
this.chart.append('g').attr('class', 'ax-line-chart-crosshair').attr('pointer-events', 'none');
|
|
569
750
|
}
|
|
570
751
|
const animationDuration = this.effectiveOptions().animationDuration;
|
|
571
|
-
const animationEasing =
|
|
752
|
+
const animationEasing = getEasingFunction(this.d3, this.effectiveOptions().animationEasing);
|
|
572
753
|
allSeriesData.forEach((series) => {
|
|
573
|
-
const seriesIdentifier =
|
|
754
|
+
const seriesIdentifier = this.getSeriesIdentifier(series);
|
|
574
755
|
if (this.hiddenSeries.has(seriesIdentifier)) {
|
|
575
|
-
this.
|
|
576
|
-
this.chart.selectAll(`.ax-line-chart-points[data-series-identifier="${seriesIdentifier}"]`).remove();
|
|
756
|
+
this.removeSeriesElements(seriesIdentifier);
|
|
577
757
|
return;
|
|
578
758
|
}
|
|
579
759
|
if (!series.data || series.data.length === 0)
|
|
@@ -590,7 +770,7 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
590
770
|
.datum(series.data)
|
|
591
771
|
.attr('class', 'ax-line-chart-line')
|
|
592
772
|
.attr('stroke', lineColor)
|
|
593
|
-
.attr('stroke-width', this.effectiveOptions().lineWidth ??
|
|
773
|
+
.attr('stroke-width', this.effectiveOptions().lineWidth ?? this.DEFAULT_LINE_WIDTH)
|
|
594
774
|
.attr('stroke-linejoin', 'round')
|
|
595
775
|
.attr('stroke-linecap', 'round')
|
|
596
776
|
.attr('d', lineGenerator)
|
|
@@ -601,14 +781,14 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
601
781
|
.on('mouseenter', () => {
|
|
602
782
|
line
|
|
603
783
|
.transition()
|
|
604
|
-
.duration(
|
|
605
|
-
.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);
|
|
606
786
|
})
|
|
607
787
|
.on('mouseleave', () => {
|
|
608
788
|
line
|
|
609
789
|
.transition()
|
|
610
|
-
.duration(
|
|
611
|
-
.attr('stroke-width', this.effectiveOptions().lineWidth ??
|
|
790
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
791
|
+
.attr('stroke-width', this.effectiveOptions().lineWidth ?? this.DEFAULT_LINE_WIDTH);
|
|
612
792
|
});
|
|
613
793
|
const totalLength = line.node().getTotalLength();
|
|
614
794
|
line
|
|
@@ -635,13 +815,13 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
635
815
|
.on('mouseenter', () => {
|
|
636
816
|
area
|
|
637
817
|
.transition()
|
|
638
|
-
.duration(
|
|
818
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
639
819
|
.attr('opacity', ((this.effectiveOptions().fillOpacity ?? 20) / 100) * 1.5);
|
|
640
820
|
})
|
|
641
821
|
.on('mouseleave', () => {
|
|
642
822
|
area
|
|
643
823
|
.transition()
|
|
644
|
-
.duration(
|
|
824
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
645
825
|
.attr('opacity', (this.effectiveOptions().fillOpacity ?? 20) / 100);
|
|
646
826
|
});
|
|
647
827
|
area
|
|
@@ -657,17 +837,15 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
657
837
|
if (this.effectiveOptions().showPoints !== false) {
|
|
658
838
|
const pointMap = new Map();
|
|
659
839
|
allSeriesData.forEach((series) => {
|
|
660
|
-
const seriesIdentifier =
|
|
840
|
+
const seriesIdentifier = this.getSeriesIdentifier(series);
|
|
661
841
|
if (this.hiddenSeries.has(seriesIdentifier) || !series.data || series.data.length === 0) {
|
|
662
842
|
return;
|
|
663
843
|
}
|
|
664
|
-
const
|
|
665
|
-
const maxPoints = 100;
|
|
666
|
-
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);
|
|
667
845
|
pointData.forEach((point) => {
|
|
668
846
|
const x = getX(point);
|
|
669
847
|
const y = this.yScale(point.y);
|
|
670
|
-
const key =
|
|
848
|
+
const key = this.createPointKey(x, y);
|
|
671
849
|
if (!pointMap.has(key)) {
|
|
672
850
|
pointMap.set(key, []);
|
|
673
851
|
}
|
|
@@ -675,9 +853,9 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
675
853
|
});
|
|
676
854
|
});
|
|
677
855
|
let animationCounter = 0;
|
|
678
|
-
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;
|
|
679
857
|
allSeriesData.forEach((series) => {
|
|
680
|
-
const seriesIdentifier =
|
|
858
|
+
const seriesIdentifier = this.getSeriesIdentifier(series);
|
|
681
859
|
if (this.hiddenSeries.has(seriesIdentifier) || !series.data || series.data.length === 0) {
|
|
682
860
|
return;
|
|
683
861
|
}
|
|
@@ -688,10 +866,9 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
688
866
|
.attr('data-series-identifier', seriesIdentifier)
|
|
689
867
|
.attr('pointer-events', 'none');
|
|
690
868
|
pointsGroup
|
|
691
|
-
.on('mouseenter', () => this.handlePointGroupEnter(series.originalIndex
|
|
869
|
+
.on('mouseenter', () => this.handlePointGroupEnter(series.originalIndex))
|
|
692
870
|
.on('mouseleave', () => this.handlePointGroupLeave());
|
|
693
|
-
const
|
|
694
|
-
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);
|
|
695
872
|
pointsGroup
|
|
696
873
|
.selectAll('circle')
|
|
697
874
|
.data(pointData)
|
|
@@ -711,7 +888,7 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
711
888
|
.on('mouseenter', (event, d) => {
|
|
712
889
|
const x = getX(d);
|
|
713
890
|
const y = this.yScale(d.y);
|
|
714
|
-
const key =
|
|
891
|
+
const key = this.createPointKey(x, y);
|
|
715
892
|
const overlappingPoints = pointMap.get(key) || [];
|
|
716
893
|
if (overlappingPoints.length > 1) {
|
|
717
894
|
this.handleOverlappingPointsHover(event, overlappingPoints);
|
|
@@ -722,7 +899,7 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
722
899
|
this.d3
|
|
723
900
|
.select(event.target)
|
|
724
901
|
.transition()
|
|
725
|
-
.duration(
|
|
902
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
726
903
|
.attr('r', (this.effectiveOptions().pointRadius ?? 4) * 1.5)
|
|
727
904
|
.attr('stroke-width', 2)
|
|
728
905
|
.attr('stroke', `rgb(var(--ax-comp-line-chart-bg-color))`);
|
|
@@ -733,15 +910,15 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
733
910
|
this.d3
|
|
734
911
|
.select(event.target)
|
|
735
912
|
.transition()
|
|
736
|
-
.duration(
|
|
737
|
-
.attr('r', this.effectiveOptions().pointRadius ??
|
|
913
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
914
|
+
.attr('r', this.effectiveOptions().pointRadius ?? this.DEFAULT_POINT_RADIUS)
|
|
738
915
|
.attr('stroke-width', 1)
|
|
739
916
|
.attr('stroke', '#fff');
|
|
740
917
|
})
|
|
741
918
|
.on('click', (event, d) => {
|
|
742
919
|
const x = getX(d);
|
|
743
920
|
const y = this.yScale(d.y);
|
|
744
|
-
const key =
|
|
921
|
+
const key = this.createPointKey(x, y);
|
|
745
922
|
const overlappingPoints = pointMap.get(key) || [];
|
|
746
923
|
if (overlappingPoints.length > 1) {
|
|
747
924
|
overlappingPoints.forEach(({ point, series }) => {
|
|
@@ -753,10 +930,11 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
753
930
|
}
|
|
754
931
|
})
|
|
755
932
|
.transition()
|
|
756
|
-
.delay((
|
|
757
|
-
|
|
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)
|
|
758
936
|
.ease(animationEasing)
|
|
759
|
-
.attr('r', this.effectiveOptions().pointRadius ??
|
|
937
|
+
.attr('r', this.effectiveOptions().pointRadius ?? this.DEFAULT_POINT_RADIUS)
|
|
760
938
|
.on('end', (d, i, nodes) => {
|
|
761
939
|
if (i === nodes.length - 1) {
|
|
762
940
|
animationCounter++;
|
|
@@ -768,9 +946,7 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
768
946
|
});
|
|
769
947
|
}
|
|
770
948
|
else {
|
|
771
|
-
setTimeout(() =>
|
|
772
|
-
this.enablePointerEventsAfterAnimation();
|
|
773
|
-
}, animationDuration || 0 + 100);
|
|
949
|
+
setTimeout(() => this.enablePointerEventsAfterAnimation(), (animationDuration || 0) + this.ANIMATION_END_BUFFER);
|
|
774
950
|
}
|
|
775
951
|
}
|
|
776
952
|
enablePointerEventsAfterAnimation() {
|
|
@@ -785,19 +961,19 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
785
961
|
this.chart.selectAll('.ax-line-chart-point').attr('pointer-events', 'all');
|
|
786
962
|
this.svg.classed('ax-line-chart-animating', false);
|
|
787
963
|
}
|
|
788
|
-
handlePointGroupEnter(originalIndex
|
|
964
|
+
handlePointGroupEnter(originalIndex) {
|
|
789
965
|
this.chart
|
|
790
966
|
.select(`.ax-line-chart-series-${originalIndex} .ax-line-chart-line`)
|
|
791
967
|
.transition()
|
|
792
|
-
.duration(
|
|
793
|
-
.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);
|
|
794
970
|
}
|
|
795
971
|
handlePointGroupLeave() {
|
|
796
972
|
this.chart
|
|
797
973
|
.selectAll('.ax-line-chart-line')
|
|
798
974
|
.transition()
|
|
799
|
-
.duration(
|
|
800
|
-
.attr('stroke-width', this.effectiveOptions().lineWidth ??
|
|
975
|
+
.duration(this.HOVER_TRANSITION_DURATION)
|
|
976
|
+
.attr('stroke-width', this.effectiveOptions().lineWidth ?? this.DEFAULT_LINE_WIDTH);
|
|
801
977
|
if (this.effectiveOptions().showCrosshair === true) {
|
|
802
978
|
this.chart.select('.ax-line-chart-crosshair').selectAll('*').remove();
|
|
803
979
|
}
|
|
@@ -820,7 +996,7 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
820
996
|
const color = series.lineColor || getChartColor(originalIndex, this.chartColors);
|
|
821
997
|
this._tooltipData.set({
|
|
822
998
|
title: dataPoint.tooltipLabel || series.tooltipLabel || series.label || `Series ${originalIndex + 1}`,
|
|
823
|
-
value: dataPoint.y
|
|
999
|
+
value: formatLargeNumber(dataPoint.y),
|
|
824
1000
|
color: color,
|
|
825
1001
|
});
|
|
826
1002
|
this._tooltipVisible.set(true);
|
|
@@ -964,7 +1140,7 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
964
1140
|
return;
|
|
965
1141
|
const tooltipData = {
|
|
966
1142
|
title: overlappingPoints.map(({ series }) => series.label),
|
|
967
|
-
value: overlappingPoints[0].point.y
|
|
1143
|
+
value: formatLargeNumber(overlappingPoints[0].point.y),
|
|
968
1144
|
color: overlappingPoints.map(({ series }) => series.lineColor || getChartColor(series.originalIndex, this.chartColors)),
|
|
969
1145
|
};
|
|
970
1146
|
this._tooltipData.set(tooltipData);
|
|
@@ -974,30 +1150,6 @@ class AXLineChartComponent extends AXChartComponent {
|
|
|
974
1150
|
this.showCrosshairLines(overlappingPoints[0].point);
|
|
975
1151
|
}
|
|
976
1152
|
}
|
|
977
|
-
getEasingFunction(easing) {
|
|
978
|
-
switch (easing) {
|
|
979
|
-
case 'linear':
|
|
980
|
-
return this.d3.easeLinear;
|
|
981
|
-
case 'ease':
|
|
982
|
-
return this.d3.easePolyInOut;
|
|
983
|
-
case 'ease-in':
|
|
984
|
-
return this.d3.easePolyIn;
|
|
985
|
-
case 'ease-out':
|
|
986
|
-
return this.d3.easePolyOut;
|
|
987
|
-
case 'ease-in-out':
|
|
988
|
-
return this.d3.easePolyInOut;
|
|
989
|
-
case 'cubic':
|
|
990
|
-
return this.d3.easeCubic;
|
|
991
|
-
case 'cubic-in':
|
|
992
|
-
return this.d3.easeCubicIn;
|
|
993
|
-
case 'cubic-out':
|
|
994
|
-
return this.d3.easeCubicOut;
|
|
995
|
-
case 'cubic-in-out':
|
|
996
|
-
return this.d3.easeCubicInOut;
|
|
997
|
-
default:
|
|
998
|
-
return this.d3.easeCubicOut;
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
1153
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.6", ngImport: i0, type: AXLineChartComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
1002
1154
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.6", 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 });
|
|
1003
1155
|
}
|