cats-charts 0.0.83 → 0.0.84

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