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.
@@ -1594,8 +1594,10 @@ class LinesChart {
1594
1594
  show: this.config?.showXAxisLabel ?? true,
1595
1595
  color: '#434A51',
1596
1596
  fontSize: 12,
1597
- alignMinLabel: 'left',
1598
- alignMaxLabel: 'right',
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: ['50%', '68%'],
5150
- radius: '90%',
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: ['50%', '68%'],
5255
- radius: '90%',
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
- width: 6,
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
- return this.clamp(axisLineWidth ?? progressWidth ?? fallback, 1, 80);
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
- return this.config.midValueType === 'percentage'
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
- switch (rule.operator) {
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
- return normalized.map((point, index) => [
5536
- this.clamp(point.position / 100, 0, 1),
5537
- this.withOpacity(stylePoints[index]?.color ?? this.config.colors?.[index] ?? '#4378ea', stylePoints[index]?.opacity ?? 100),
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();