cats-charts 0.0.83 → 0.0.85
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/fesm2022/cats-charts.mjs +163 -21
- package/fesm2022/cats-charts.mjs.map +1 -1
- package/index.d.ts +30 -1
- package/package.json +1 -1
package/fesm2022/cats-charts.mjs
CHANGED
|
@@ -1594,8 +1594,10 @@ class LinesChart {
|
|
|
1594
1594
|
show: this.config?.showXAxisLabel ?? true,
|
|
1595
1595
|
color: '#434A51',
|
|
1596
1596
|
fontSize: 12,
|
|
1597
|
-
|
|
1598
|
-
|
|
1597
|
+
...(!this.config?.xAxis?.axisLabel?.rotate && {
|
|
1598
|
+
alignMinLabel: 'left',
|
|
1599
|
+
alignMaxLabel: 'right',
|
|
1600
|
+
}),
|
|
1599
1601
|
...(this.config?.xAxis?.axisLabel || {}),
|
|
1600
1602
|
},
|
|
1601
1603
|
nameTextStyle: {
|
|
@@ -5073,6 +5075,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
5073
5075
|
}] } });
|
|
5074
5076
|
|
|
5075
5077
|
echarts.use([GaugeChart$1, GraphicComponent, TooltipComponent, TitleComponent, CanvasRenderer]);
|
|
5078
|
+
const DEFAULT_GAUGE_CENTER = ['50%', '84%'];
|
|
5079
|
+
const DEFAULT_GAUGE_RADIUS = '140%';
|
|
5076
5080
|
class GaugeChart {
|
|
5077
5081
|
config = {};
|
|
5078
5082
|
theme;
|
|
@@ -5080,6 +5084,7 @@ class GaugeChart {
|
|
|
5080
5084
|
chartEvent = new EventEmitter();
|
|
5081
5085
|
chartOption = {};
|
|
5082
5086
|
chartInstance;
|
|
5087
|
+
chartResizeObserver;
|
|
5083
5088
|
previousReactiveSignature = '';
|
|
5084
5089
|
ngOnChanges(changes) {
|
|
5085
5090
|
if (changes['theme'] && this.chartInstance) {
|
|
@@ -5099,7 +5104,7 @@ class GaugeChart {
|
|
|
5099
5104
|
}
|
|
5100
5105
|
buildOption() {
|
|
5101
5106
|
if (this.config.option) {
|
|
5102
|
-
this.chartOption = this.config.option;
|
|
5107
|
+
this.chartOption = this.applyTargetPointerSizing(this.config.option);
|
|
5103
5108
|
if (this.chartInstance) {
|
|
5104
5109
|
this.chartInstance.setOption(this.chartOption, true);
|
|
5105
5110
|
}
|
|
@@ -5146,8 +5151,8 @@ class GaugeChart {
|
|
|
5146
5151
|
max: displayMax,
|
|
5147
5152
|
startAngle: 180,
|
|
5148
5153
|
endAngle: 0,
|
|
5149
|
-
center: [
|
|
5150
|
-
radius:
|
|
5154
|
+
center: [...DEFAULT_GAUGE_CENTER],
|
|
5155
|
+
radius: DEFAULT_GAUGE_RADIUS,
|
|
5151
5156
|
splitNumber,
|
|
5152
5157
|
progress: {
|
|
5153
5158
|
show: progressShow,
|
|
@@ -5170,6 +5175,9 @@ class GaugeChart {
|
|
|
5170
5175
|
pointer: {
|
|
5171
5176
|
show: false,
|
|
5172
5177
|
},
|
|
5178
|
+
title: {
|
|
5179
|
+
show: false,
|
|
5180
|
+
},
|
|
5173
5181
|
anchor: {
|
|
5174
5182
|
show: false,
|
|
5175
5183
|
},
|
|
@@ -5196,7 +5204,18 @@ class GaugeChart {
|
|
|
5196
5204
|
distance: this.config.midValuePosition === 'inside' ? -36 : 10,
|
|
5197
5205
|
color: '#111827',
|
|
5198
5206
|
fontSize: 10,
|
|
5207
|
+
align: 'center',
|
|
5208
|
+
verticalAlign: 'middle',
|
|
5199
5209
|
formatter: (value) => this.axisLabelFormatter(value, displayMin, displayMax, showMidValue),
|
|
5210
|
+
rich: {
|
|
5211
|
+
gaugeLabel: {
|
|
5212
|
+
padding: showMidValue ? [0, 0, 0, 0] : [16, 0, 0, 0],
|
|
5213
|
+
color: '#434a51',
|
|
5214
|
+
fontWeight: 600,
|
|
5215
|
+
align: 'center',
|
|
5216
|
+
verticalAlign: 'middle',
|
|
5217
|
+
},
|
|
5218
|
+
},
|
|
5200
5219
|
},
|
|
5201
5220
|
detail: {
|
|
5202
5221
|
offsetCenter: [0, '-10%'],
|
|
@@ -5227,6 +5246,7 @@ class GaugeChart {
|
|
|
5227
5246
|
gaugeSeries['min'] = displayMin;
|
|
5228
5247
|
gaugeSeries['max'] = displayMax;
|
|
5229
5248
|
gaugeSeries['data'] = [{ value: displayValue, name: this.config.name ?? 'Value' }];
|
|
5249
|
+
const targetPointer = this.calculateTargetPointerGeometry(gaugeSeries);
|
|
5230
5250
|
const generatedChartOption = {
|
|
5231
5251
|
backgroundColor: this.config.backgroundColor ?? 'transparent',
|
|
5232
5252
|
title: {
|
|
@@ -5251,8 +5271,8 @@ class GaugeChart {
|
|
|
5251
5271
|
max: displayMax,
|
|
5252
5272
|
startAngle: 180,
|
|
5253
5273
|
endAngle: 0,
|
|
5254
|
-
center: ['
|
|
5255
|
-
radius: '
|
|
5274
|
+
center: gaugeSeries['center'] ?? [...DEFAULT_GAUGE_CENTER],
|
|
5275
|
+
radius: gaugeSeries['radius'] ?? DEFAULT_GAUGE_RADIUS,
|
|
5256
5276
|
axisLine: {
|
|
5257
5277
|
lineStyle: {
|
|
5258
5278
|
width: 0,
|
|
@@ -5262,9 +5282,7 @@ class GaugeChart {
|
|
|
5262
5282
|
pointer: {
|
|
5263
5283
|
show: true,
|
|
5264
5284
|
icon: 'rect',
|
|
5265
|
-
|
|
5266
|
-
length: '18%',
|
|
5267
|
-
offsetCenter: [0, '-91%'],
|
|
5285
|
+
...targetPointer,
|
|
5268
5286
|
itemStyle: {
|
|
5269
5287
|
color: '#374151',
|
|
5270
5288
|
},
|
|
@@ -5273,6 +5291,7 @@ class GaugeChart {
|
|
|
5273
5291
|
axisTick: { show: false },
|
|
5274
5292
|
splitLine: { show: false },
|
|
5275
5293
|
axisLabel: { show: false },
|
|
5294
|
+
title: { show: false },
|
|
5276
5295
|
detail: { show: false },
|
|
5277
5296
|
data: [{ value: displayTarget, name: 'Target' }],
|
|
5278
5297
|
},
|
|
@@ -5289,6 +5308,57 @@ class GaugeChart {
|
|
|
5289
5308
|
this.chartInstance = ec;
|
|
5290
5309
|
this.buildOption();
|
|
5291
5310
|
this.bindEvents();
|
|
5311
|
+
this.observeChartResize();
|
|
5312
|
+
}
|
|
5313
|
+
/**
|
|
5314
|
+
* Calculates all target-marker geometry in one place. A gauge pointer icon
|
|
5315
|
+
* is drawn from `offsetCenter - length` through `offsetCenter`, so the
|
|
5316
|
+
* offset must account for the inner edge while length covers the band and
|
|
5317
|
+
* both edge overflows. Use the widest band because progress and axisLine
|
|
5318
|
+
* may be configured independently.
|
|
5319
|
+
*/
|
|
5320
|
+
calculateTargetPointerGeometry(bandSeries) {
|
|
5321
|
+
const barWidth = this.resolveGaugeWidth(bandSeries, 38, true);
|
|
5322
|
+
const edgeOverflow = Math.max(6, Math.round(barWidth * 0.25));
|
|
5323
|
+
const pointerLength = barWidth + edgeOverflow * 2;
|
|
5324
|
+
const radius = this.resolveGaugeRadius(bandSeries['radius']);
|
|
5325
|
+
if (radius === undefined) {
|
|
5326
|
+
return {
|
|
5327
|
+
width: 10,
|
|
5328
|
+
length: pointerLength,
|
|
5329
|
+
offsetCenter: [0, '-91%'],
|
|
5330
|
+
};
|
|
5331
|
+
}
|
|
5332
|
+
return {
|
|
5333
|
+
width: 10,
|
|
5334
|
+
length: pointerLength,
|
|
5335
|
+
offsetCenter: [0, -(radius - barWidth - edgeOverflow)],
|
|
5336
|
+
};
|
|
5337
|
+
}
|
|
5338
|
+
resolveGaugeRadius(radiusOption) {
|
|
5339
|
+
if (!this.chartInstance)
|
|
5340
|
+
return undefined;
|
|
5341
|
+
const radius = this.toFiniteNumber(radiusOption);
|
|
5342
|
+
if (radius !== undefined)
|
|
5343
|
+
return radius;
|
|
5344
|
+
if (typeof radiusOption !== 'string' || !radiusOption.endsWith('%'))
|
|
5345
|
+
return undefined;
|
|
5346
|
+
const percentage = this.toFiniteNumber(radiusOption.slice(0, -1));
|
|
5347
|
+
if (percentage === undefined)
|
|
5348
|
+
return undefined;
|
|
5349
|
+
return (Math.min(this.chartInstance.getWidth(), this.chartInstance.getHeight()) / 2) * (percentage / 100);
|
|
5350
|
+
}
|
|
5351
|
+
observeChartResize() {
|
|
5352
|
+
if (typeof ResizeObserver === 'undefined' || !this.chartInstance)
|
|
5353
|
+
return;
|
|
5354
|
+
this.chartResizeObserver?.disconnect();
|
|
5355
|
+
this.chartResizeObserver = new ResizeObserver(() => {
|
|
5356
|
+
if (this.chartInstance.isDisposed())
|
|
5357
|
+
return;
|
|
5358
|
+
this.chartInstance.resize();
|
|
5359
|
+
this.buildOption();
|
|
5360
|
+
});
|
|
5361
|
+
this.chartResizeObserver.observe(this.chartInstance.getDom());
|
|
5292
5362
|
}
|
|
5293
5363
|
bindEvents() {
|
|
5294
5364
|
if (!this.chartInstance)
|
|
@@ -5312,6 +5382,42 @@ class GaugeChart {
|
|
|
5312
5382
|
return undefined;
|
|
5313
5383
|
return series.find((seriesOption) => seriesOption?.type === 'gauge') ?? series[0];
|
|
5314
5384
|
}
|
|
5385
|
+
/**
|
|
5386
|
+
* Custom ECharts options can provide a second, transparent gauge series for
|
|
5387
|
+
* the target marker. Apply the same automatic marker sizing used by the
|
|
5388
|
+
* generated option without changing any of the caller's other settings.
|
|
5389
|
+
*/
|
|
5390
|
+
applyTargetPointerSizing(option) {
|
|
5391
|
+
if (!Array.isArray(option['series']))
|
|
5392
|
+
return option;
|
|
5393
|
+
const bandSeries = option['series'].find((seriesOption) => seriesOption?.['type'] === 'gauge' && !this.isTargetPointerSeries(seriesOption));
|
|
5394
|
+
if (!bandSeries)
|
|
5395
|
+
return option;
|
|
5396
|
+
const targetPointer = this.calculateTargetPointerGeometry(bandSeries);
|
|
5397
|
+
let changed = false;
|
|
5398
|
+
const series = option['series'].map((seriesOption) => {
|
|
5399
|
+
if (!this.isTargetPointerSeries(seriesOption))
|
|
5400
|
+
return seriesOption;
|
|
5401
|
+
changed = true;
|
|
5402
|
+
return {
|
|
5403
|
+
...seriesOption,
|
|
5404
|
+
center: bandSeries['center'] ?? seriesOption['center'] ?? [...DEFAULT_GAUGE_CENTER],
|
|
5405
|
+
radius: bandSeries['radius'] ?? seriesOption['radius'] ?? DEFAULT_GAUGE_RADIUS,
|
|
5406
|
+
pointer: {
|
|
5407
|
+
...seriesOption['pointer'],
|
|
5408
|
+
...targetPointer,
|
|
5409
|
+
},
|
|
5410
|
+
};
|
|
5411
|
+
});
|
|
5412
|
+
return changed ? { ...option, series } : option;
|
|
5413
|
+
}
|
|
5414
|
+
isTargetPointerSeries(seriesOption) {
|
|
5415
|
+
const axisLineWidth = this.toFiniteNumber(seriesOption?.['axisLine']?.['lineStyle']?.['width']);
|
|
5416
|
+
return (seriesOption?.['type'] === 'gauge' &&
|
|
5417
|
+
seriesOption?.['pointer']?.['show'] === true &&
|
|
5418
|
+
seriesOption?.['progress']?.['show'] !== true &&
|
|
5419
|
+
(seriesOption?.['axisLine']?.['show'] === false || axisLineWidth === 0));
|
|
5420
|
+
}
|
|
5315
5421
|
resolveStyleSeriesOption(style) {
|
|
5316
5422
|
if (!style)
|
|
5317
5423
|
return {};
|
|
@@ -5326,10 +5432,16 @@ class GaugeChart {
|
|
|
5326
5432
|
...(style.anchor ? { anchor: style.anchor } : {}),
|
|
5327
5433
|
};
|
|
5328
5434
|
}
|
|
5329
|
-
resolveGaugeWidth(seriesOption, fallback) {
|
|
5435
|
+
resolveGaugeWidth(seriesOption, fallback, visibleOnly = false) {
|
|
5330
5436
|
const axisLineWidth = this.toFiniteNumber(seriesOption?.['axisLine']?.['lineStyle']?.['width']);
|
|
5331
5437
|
const progressWidth = this.toFiniteNumber(seriesOption?.['progress']?.['width']);
|
|
5332
|
-
|
|
5438
|
+
const configuredWidths = [
|
|
5439
|
+
...(!visibleOnly || seriesOption?.['axisLine']?.['show'] !== false ? [axisLineWidth] : []),
|
|
5440
|
+
...(!visibleOnly || seriesOption?.['progress']?.['show'] === true ? [progressWidth] : []),
|
|
5441
|
+
].filter((width) => width !== undefined);
|
|
5442
|
+
// Cover whichever configured band is thicker. Fall back only when neither
|
|
5443
|
+
// width is supplied, so a deliberately thin gauge is not enlarged.
|
|
5444
|
+
return this.clamp(configuredWidths.length ? Math.max(...configuredWidths) : fallback, 1, 80);
|
|
5333
5445
|
}
|
|
5334
5446
|
resolveProgressVisibility(seriesOption, conditionalEnabled) {
|
|
5335
5447
|
const configuredProgressShow = seriesOption?.['progress']?.['show'];
|
|
@@ -5380,9 +5492,11 @@ class GaugeChart {
|
|
|
5380
5492
|
const rounded = this.config.midValueType === 'percentage' ? Math.round(value) : this.niceNumber(value);
|
|
5381
5493
|
if (!showMidValue && value !== min && value !== max)
|
|
5382
5494
|
return '';
|
|
5383
|
-
|
|
5495
|
+
const label = this.config.midValueType === 'percentage'
|
|
5384
5496
|
? `${rounded}%`
|
|
5385
5497
|
: this.formatNumber(rounded, this.config.style?.compactNumber !== false, 0);
|
|
5498
|
+
const endpointOffset = showMidValue ? '' : '\n';
|
|
5499
|
+
return `{gaugeLabel|${endpointOffset}${label}}`;
|
|
5386
5500
|
}
|
|
5387
5501
|
resolveMetricValue() {
|
|
5388
5502
|
const configured = this.toFiniteNumber(this.config.value);
|
|
@@ -5408,7 +5522,7 @@ class GaugeChart {
|
|
|
5408
5522
|
const conditional = this.config.style?.conditionalFormatting;
|
|
5409
5523
|
if (conditional?.enabled) {
|
|
5410
5524
|
return conditional.type === 'gradient'
|
|
5411
|
-
? this.resolveGradientStops()
|
|
5525
|
+
? this.resolveGradientStops(min, max)
|
|
5412
5526
|
: this.resolveRuleStops(min, max, conditional.rules ?? []);
|
|
5413
5527
|
}
|
|
5414
5528
|
const barColor = this.config.style?.barColor;
|
|
@@ -5446,7 +5560,7 @@ class GaugeChart {
|
|
|
5446
5560
|
return this.withOpacity(matchedRule.color, matchedRule.opacity ?? 100);
|
|
5447
5561
|
}
|
|
5448
5562
|
if (conditional?.enabled && conditional.type === 'gradient') {
|
|
5449
|
-
const stops = this.resolveGradientStops();
|
|
5563
|
+
const stops = this.resolveGradientStops(min, max);
|
|
5450
5564
|
const ratio = this.clamp((value - min) / (max - min || 1), 0, 1);
|
|
5451
5565
|
const stop = stops.find(([position]) => ratio <= position) ?? stops[stops.length - 1];
|
|
5452
5566
|
if (stop)
|
|
@@ -5502,7 +5616,17 @@ class GaugeChart {
|
|
|
5502
5616
|
const secondValue = this.ruleValueToNumber(rule.valueTo, rule.unit, min, max);
|
|
5503
5617
|
if (firstValue === undefined)
|
|
5504
5618
|
return false;
|
|
5505
|
-
|
|
5619
|
+
const firstMatches = this.matchesRuleCondition(value, firstValue, rule.operator, secondValue);
|
|
5620
|
+
if (!firstMatches)
|
|
5621
|
+
return false;
|
|
5622
|
+
if (rule.secondCondition) {
|
|
5623
|
+
const second = this.ruleValueToNumber(rule.secondCondition.value, rule.secondCondition.unit, min, max);
|
|
5624
|
+
return second !== undefined && this.matchesRuleCondition(value, second, rule.secondCondition.operator);
|
|
5625
|
+
}
|
|
5626
|
+
return true;
|
|
5627
|
+
}
|
|
5628
|
+
matchesRuleCondition(value, firstValue, operator, secondValue) {
|
|
5629
|
+
switch (operator) {
|
|
5506
5630
|
case '>=':
|
|
5507
5631
|
return value >= firstValue;
|
|
5508
5632
|
case '>':
|
|
@@ -5519,7 +5643,7 @@ class GaugeChart {
|
|
|
5519
5643
|
return false;
|
|
5520
5644
|
}
|
|
5521
5645
|
}
|
|
5522
|
-
resolveGradientStops() {
|
|
5646
|
+
resolveGradientStops(min, max) {
|
|
5523
5647
|
const points = [...(this.config.style?.conditionalFormatting?.gradientPoints ?? [])]
|
|
5524
5648
|
.sort((a, b) => a.position - b.position);
|
|
5525
5649
|
const normalized = points.length
|
|
@@ -5532,10 +5656,27 @@ class GaugeChart {
|
|
|
5532
5656
|
const stylePoints = this.config.style?.conditionalFormatting?.reverseGradient
|
|
5533
5657
|
? [...normalized].reverse()
|
|
5534
5658
|
: normalized;
|
|
5535
|
-
|
|
5536
|
-
this.
|
|
5537
|
-
|
|
5538
|
-
|
|
5659
|
+
const stops = normalized.map((point, index) => {
|
|
5660
|
+
const from = this.gradientValueToRatio(point.valueFrom, point.unit, min ?? 0, max ?? 1);
|
|
5661
|
+
const position = from ?? this.clamp(point.position / 100, 0, 1);
|
|
5662
|
+
return [
|
|
5663
|
+
position,
|
|
5664
|
+
this.withOpacity(stylePoints[index]?.color ?? this.config.colors?.[index] ?? '#4378ea', stylePoints[index]?.opacity ?? 100),
|
|
5665
|
+
];
|
|
5666
|
+
});
|
|
5667
|
+
normalized.forEach((point, index) => {
|
|
5668
|
+
const to = this.gradientValueToRatio(point.valueTo, point.unit, min ?? 0, max ?? 1);
|
|
5669
|
+
if (to !== undefined && stops[index])
|
|
5670
|
+
stops[index][0] = Math.max(stops[index][0], to);
|
|
5671
|
+
});
|
|
5672
|
+
return stops.sort((a, b) => a[0] - b[0]);
|
|
5673
|
+
}
|
|
5674
|
+
gradientValueToRatio(value, unit, min, max) {
|
|
5675
|
+
const numeric = this.toFiniteNumber(value);
|
|
5676
|
+
if (numeric === undefined)
|
|
5677
|
+
return undefined;
|
|
5678
|
+
const actual = unit === 'percentage' ? min + (this.clamp(numeric, 0, 100) / 100) * (max - min) : numeric;
|
|
5679
|
+
return this.clamp((actual - min) / ((max - min) || 1), 0, 1);
|
|
5539
5680
|
}
|
|
5540
5681
|
ruleValueToNumber(value, unit, min, max) {
|
|
5541
5682
|
const numericValue = this.toFiniteNumber(value);
|
|
@@ -5649,6 +5790,7 @@ class GaugeChart {
|
|
|
5649
5790
|
return Math.min(Math.max(value, min), max);
|
|
5650
5791
|
}
|
|
5651
5792
|
ngOnDestroy() {
|
|
5793
|
+
this.chartResizeObserver?.disconnect();
|
|
5652
5794
|
if (this.chartInstance) {
|
|
5653
5795
|
this.chartInstance.off();
|
|
5654
5796
|
this.chartInstance.dispose();
|