@c8y/ngx-components 1023.82.4 → 1023.83.2

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.
@@ -456,6 +456,7 @@ class EchartsOptionsService {
456
456
  this.severityLabelPipe = severityLabelPipe;
457
457
  this.translate = translate;
458
458
  this.router = router;
459
+ this.highlightedSeriesName = null;
459
460
  this.AGGREGATED_SERIES_TYPE = 'aggr';
460
461
  this.TOOLTIP_WIDTH = 300;
461
462
  }
@@ -1464,7 +1465,7 @@ class EchartsOptionsService {
1464
1465
  data,
1465
1466
  ...(displayOptions.forceMergeDatapoints ? {} : { yAxisIndex: idx }),
1466
1467
  ...this.chartTypesService.getSeriesOptions(dp, isMinMaxChart, renderType),
1467
- ...(isLine && { smooth: displayOptions.smoothLines ?? false })
1468
+ ...(isLine && { smooth: displayOptions.smoothLines ?? false, triggerLineEvent: true })
1468
1469
  };
1469
1470
  }
1470
1471
  /**
@@ -1527,10 +1528,12 @@ class EchartsOptionsService {
1527
1528
  `</div></div>`;
1528
1529
  }
1529
1530
  const itemStyle = series['itemStyle'];
1530
- YAxisReadings.push(`<div class="d-flex a-i-center p-b-8 text-default"><span class='dlt-c8y-icon-circle m-r-4' style='color: ${echarts.format.encodeHTML(itemStyle.color)}'></span>` + // color circle
1531
+ const isDimmed = this.highlightedSeriesName !== null && series['name'] !== this.highlightedSeriesName;
1532
+ YAxisReadings.push(`<div style="opacity: ${isDimmed ? 0.4 : 1}">` +
1533
+ `<div class="d-flex a-i-center p-b-8 text-default"><span class='dlt-c8y-icon-circle m-r-4' style='color: ${echarts.format.encodeHTML(itemStyle.color)}'></span>` + // color circle
1531
1534
  `<strong class="text-truncate">${echarts.format.encodeHTML(series['datapointLabel'])}</strong></div>` + // name
1532
- `${value}` // single value or min-max range
1533
- );
1535
+ `${value}` + // single value or min-max range
1536
+ `</div>`);
1534
1537
  });
1535
1538
  return `<div inner-e-cbg-component style="width: ${this.TOOLTIP_WIDTH}px">${YAxisReadings.join('')}</div>`;
1536
1539
  };
@@ -1747,6 +1750,14 @@ class ChartRealtimeService {
1747
1750
  if (!seriesMatchingDatapoint) {
1748
1751
  return;
1749
1752
  }
1753
+ // If the datapoint already has a unit (e.g. from config), keep it. Otherwise, try to get it from the realtime measurement.
1754
+ if (!seriesMatchingDatapoint['datapointUnit'] && measurements.length > 0) {
1755
+ const unit = measurements[0]?.[datapoint.fragment]?.[datapoint.series].unit;
1756
+ if (unit) {
1757
+ const renderType = datapoint.renderType || 'min';
1758
+ seriesMatchingDatapoint['datapointUnit'] = renderType !== 'count' ? unit : '';
1759
+ }
1760
+ }
1750
1761
  const seriesDataToUpdate = seriesMatchingDatapoint['data'];
1751
1762
  let lastExistingTimestamp = seriesDataToUpdate.length
1752
1763
  ? new Date(seriesDataToUpdate[seriesDataToUpdate.length - 1][0]).valueOf()
@@ -2143,6 +2154,8 @@ class ChartsComponent {
2143
2154
  this.isChangeFromZoom = false;
2144
2155
  /** Tracks if the slider is being dragged — suppresses config updates mid-drag */
2145
2156
  this.isSliderDragging = false;
2157
+ /** Tracks the currently highlighted series name, null when nothing is highlighted */
2158
+ this.highlightedSeriesName = null;
2146
2159
  this.chartOption$ = this.configChangedSubject.pipe(tap(() => (this.isFetching = true)), switchMap(() => this.loadAlarmsAndEvents()), switchMap(() => this.fetchSeriesForDatapoints$()), switchMap((datapointsWithValues) => {
2147
2160
  if (datapointsWithValues.length === 0 && this.config.alarmsEventsConfigs?.length === 0) {
2148
2161
  this.echartsInstance?.clear();
@@ -2152,22 +2165,28 @@ class ChartsComponent {
2152
2165
  // Use padded range since data is fetched with 60s padding
2153
2166
  this.loadedTimeRange = { ...this.getTimeRange(60_000) };
2154
2167
  return this.getChartOptions(datapointsWithValues);
2155
- }), tap(v => {
2168
+ }), tap(options => {
2156
2169
  this.isFetching = false;
2157
2170
  this.finishLoading.emit(false);
2158
- if (v['empty']) {
2171
+ if (options['empty']) {
2159
2172
  return;
2160
2173
  }
2161
2174
  this.chartRealtimeService.stopRealtime();
2162
2175
  this.startRealtimeIfPossible();
2163
2176
  if (this.echartsInstance) {
2177
+ // If a series is highlighted, set the opacity into the chart options before
2178
+ // rendering so the lazy setOption already carries the correct visual state.
2179
+ // This avoids any async race — the render itself is the source of truth.
2180
+ if (this.highlightedSeriesName) {
2181
+ this.applyHighlightToChartOptions(options, this.highlightedSeriesName);
2182
+ }
2164
2183
  /**
2165
2184
  * * Set the chart options
2166
2185
  * First parameter is the options to set
2167
2186
  * Second parameter is a boolean to indicate that the chart should be re-rendered as we do not want to merge.
2168
2187
  * Third parameter is a boolean to indicate that we want to lazy load the chart.
2169
2188
  */
2170
- this.echartsInstance.setOption(v, true, true);
2189
+ this.echartsInstance.setOption(options, true, true);
2171
2190
  }
2172
2191
  }));
2173
2192
  this.widgetTimeContextDateRangeService.fullReload$
@@ -2307,6 +2326,12 @@ class ChartsComponent {
2307
2326
  tooltip: { triggerOn: 'mousemove' },
2308
2327
  series: [{ markArea: { data: [] }, markLine: { data: [] } }]
2309
2328
  });
2329
+ if (params.componentType === 'series' && params.seriesName) {
2330
+ this.toggleStickyHighlight(params.seriesName);
2331
+ }
2332
+ else {
2333
+ this.setStickyHighlight(null);
2334
+ }
2310
2335
  return;
2311
2336
  }
2312
2337
  const clickedAlarms = this.alarms.filter(alarm => alarm.type === params.data.itemType);
@@ -2344,8 +2369,8 @@ class ChartsComponent {
2344
2369
  this.echartsInstance.setOption(updatedOptions);
2345
2370
  }
2346
2371
  isAlarmOrEventClick(params) {
2347
- return (this.alarms.some(alarm => alarm.type === params.data.itemType) ||
2348
- this.events.some(event => event.type === params.data.itemType));
2372
+ return (this.alarms.some(alarm => alarm.type === params.data?.itemType) ||
2373
+ this.events.some(event => event.type === params.data?.itemType));
2349
2374
  }
2350
2375
  hasMarkArea(options) {
2351
2376
  return options?.series?.[0]?.markArea?.data?.length > 0;
@@ -2453,15 +2478,91 @@ class ChartsComponent {
2453
2478
  }
2454
2479
  const type = alarmOrEvent.filters.type;
2455
2480
  const actionType = alarmOrEvent.__hidden ? 'legendUnSelect' : 'legendSelect';
2456
- const matchingSeries = allSeries.filter(s => s.name?.startsWith(`${type}/`) &&
2457
- (s.name?.endsWith('-markLine') || s.name?.endsWith('-markPoint')));
2481
+ const matchingSeries = allSeries.filter(s => typeof s.name === 'string' &&
2482
+ s.name.startsWith(`${type}/`) &&
2483
+ (s.name.endsWith('-markLine') || s.name.endsWith('-markPoint')));
2458
2484
  matchingSeries.forEach(series => {
2485
+ if (typeof series.name !== 'string') {
2486
+ return;
2487
+ }
2459
2488
  this.echartsInstance.dispatchAction({
2460
2489
  type: actionType,
2461
2490
  name: series.name
2462
2491
  });
2463
2492
  });
2464
2493
  }
2494
+ getDataSeriesNames() {
2495
+ const series = this.echartsInstance.getOption().series;
2496
+ const allSeries = Array.isArray(series) ? series : [series];
2497
+ return allSeries
2498
+ .filter(s => s.name &&
2499
+ s['typeOfSeries'] !== 'alarm' &&
2500
+ s['typeOfSeries'] !== 'event' &&
2501
+ s['typeOfSeries'] !== 'fake' &&
2502
+ s['datapointId'] !== 'aggregated')
2503
+ .map(s => s.name);
2504
+ }
2505
+ /**
2506
+ * Applies highlight opacity directly to an EChartsOption object's series array.
2507
+ * Used to set the highlight into chart options before setOption.
2508
+ * @param options - The chart options object to mutate in-place.
2509
+ * @param highlightedName - The series name that should remain at full opacity.
2510
+ */
2511
+ applyHighlightToChartOptions(options, highlightedName) {
2512
+ if (!Array.isArray(options.series)) {
2513
+ return;
2514
+ }
2515
+ const series = options.series;
2516
+ const dataSeriesNames = this.getDataSeriesNames();
2517
+ series.forEach(s => {
2518
+ const updatedSeries = this.changeSeriesOpacity(dataSeriesNames, highlightedName, s);
2519
+ Object.assign(s, updatedSeries);
2520
+ });
2521
+ }
2522
+ /**
2523
+ * Applies or removes a sticky highlight by directly setting opacity on series styles.
2524
+ * @param seriesName - The series to pin as highlighted, or null to restore all.
2525
+ */
2526
+ setStickyHighlight(seriesName) {
2527
+ this.highlightedSeriesName = seriesName;
2528
+ this.echartsOptionsService.highlightedSeriesName = seriesName;
2529
+ const allSeries = this.echartsInstance.getOption().series;
2530
+ const dataSeriesNames = this.getDataSeriesNames();
2531
+ this.echartsInstance.setOption({
2532
+ series: allSeries.map(s => {
2533
+ return this.changeSeriesOpacity(dataSeriesNames, seriesName, s);
2534
+ })
2535
+ });
2536
+ }
2537
+ changeSeriesOpacity(dataSeriesName, seriesName, series) {
2538
+ const dataSeriesNames = new Set(dataSeriesName);
2539
+ if (typeof series.name !== 'string' || !dataSeriesNames.has(series.name)) {
2540
+ return series;
2541
+ }
2542
+ const isDimmed = seriesName !== null && series.name !== seriesName;
2543
+ const opacity = isDimmed ? 0.2 : 1;
2544
+ return {
2545
+ ...series,
2546
+ lineStyle: { ...(series.lineStyle ?? {}), opacity },
2547
+ itemStyle: { ...(series.itemStyle ?? {}), opacity },
2548
+ emphasis: {
2549
+ ...(series.emphasis ?? {}),
2550
+ // Disable ECharts' automatic focus-blur so hovering other series
2551
+ // does not override the pinned opacity we set here.
2552
+ focus: 'none',
2553
+ lineStyle: { ...(series.emphasis?.lineStyle ?? {}), opacity },
2554
+ itemStyle: { ...(series.emphasis?.itemStyle ?? {}), opacity }
2555
+ }
2556
+ };
2557
+ }
2558
+ toggleStickyHighlight(seriesName) {
2559
+ if (this.highlightedSeriesName === seriesName) {
2560
+ // Same series clicked again — remove the lock
2561
+ this.setStickyHighlight(null);
2562
+ return;
2563
+ }
2564
+ this.setStickyHighlight(seriesName);
2565
+ }
2465
2566
  get displayOptions() {
2466
2567
  const chartDefaults = CHART_DISPLAY_OPTION_DEFAULTS;
2467
2568
  return {
@@ -2743,6 +2844,13 @@ class ChartsComponent {
2743
2844
  this.alerts.clear();
2744
2845
  }
2745
2846
  }
2847
+ // If the datapoint already has a unit (e.g. from config), keep it. Otherwise, try to get it from the series response.
2848
+ if (!dp.unit) {
2849
+ const unitFromSeries = res.data.series[0]?.unit;
2850
+ if (unitFromSeries) {
2851
+ dp.unit = unitFromSeries;
2852
+ }
2853
+ }
2746
2854
  return { ...dp, values, truncated: res.data.truncated };
2747
2855
  }));
2748
2856
  datapointsWithValuesRequests.push(request);