@c8y/ngx-components 1023.82.8 → 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
  };
@@ -2151,6 +2154,8 @@ class ChartsComponent {
2151
2154
  this.isChangeFromZoom = false;
2152
2155
  /** Tracks if the slider is being dragged — suppresses config updates mid-drag */
2153
2156
  this.isSliderDragging = false;
2157
+ /** Tracks the currently highlighted series name, null when nothing is highlighted */
2158
+ this.highlightedSeriesName = null;
2154
2159
  this.chartOption$ = this.configChangedSubject.pipe(tap(() => (this.isFetching = true)), switchMap(() => this.loadAlarmsAndEvents()), switchMap(() => this.fetchSeriesForDatapoints$()), switchMap((datapointsWithValues) => {
2155
2160
  if (datapointsWithValues.length === 0 && this.config.alarmsEventsConfigs?.length === 0) {
2156
2161
  this.echartsInstance?.clear();
@@ -2160,22 +2165,28 @@ class ChartsComponent {
2160
2165
  // Use padded range since data is fetched with 60s padding
2161
2166
  this.loadedTimeRange = { ...this.getTimeRange(60_000) };
2162
2167
  return this.getChartOptions(datapointsWithValues);
2163
- }), tap(v => {
2168
+ }), tap(options => {
2164
2169
  this.isFetching = false;
2165
2170
  this.finishLoading.emit(false);
2166
- if (v['empty']) {
2171
+ if (options['empty']) {
2167
2172
  return;
2168
2173
  }
2169
2174
  this.chartRealtimeService.stopRealtime();
2170
2175
  this.startRealtimeIfPossible();
2171
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
+ }
2172
2183
  /**
2173
2184
  * * Set the chart options
2174
2185
  * First parameter is the options to set
2175
2186
  * Second parameter is a boolean to indicate that the chart should be re-rendered as we do not want to merge.
2176
2187
  * Third parameter is a boolean to indicate that we want to lazy load the chart.
2177
2188
  */
2178
- this.echartsInstance.setOption(v, true, true);
2189
+ this.echartsInstance.setOption(options, true, true);
2179
2190
  }
2180
2191
  }));
2181
2192
  this.widgetTimeContextDateRangeService.fullReload$
@@ -2315,6 +2326,12 @@ class ChartsComponent {
2315
2326
  tooltip: { triggerOn: 'mousemove' },
2316
2327
  series: [{ markArea: { data: [] }, markLine: { data: [] } }]
2317
2328
  });
2329
+ if (params.componentType === 'series' && params.seriesName) {
2330
+ this.toggleStickyHighlight(params.seriesName);
2331
+ }
2332
+ else {
2333
+ this.setStickyHighlight(null);
2334
+ }
2318
2335
  return;
2319
2336
  }
2320
2337
  const clickedAlarms = this.alarms.filter(alarm => alarm.type === params.data.itemType);
@@ -2352,8 +2369,8 @@ class ChartsComponent {
2352
2369
  this.echartsInstance.setOption(updatedOptions);
2353
2370
  }
2354
2371
  isAlarmOrEventClick(params) {
2355
- return (this.alarms.some(alarm => alarm.type === params.data.itemType) ||
2356
- 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));
2357
2374
  }
2358
2375
  hasMarkArea(options) {
2359
2376
  return options?.series?.[0]?.markArea?.data?.length > 0;
@@ -2461,15 +2478,91 @@ class ChartsComponent {
2461
2478
  }
2462
2479
  const type = alarmOrEvent.filters.type;
2463
2480
  const actionType = alarmOrEvent.__hidden ? 'legendUnSelect' : 'legendSelect';
2464
- const matchingSeries = allSeries.filter(s => s.name?.startsWith(`${type}/`) &&
2465
- (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')));
2466
2484
  matchingSeries.forEach(series => {
2485
+ if (typeof series.name !== 'string') {
2486
+ return;
2487
+ }
2467
2488
  this.echartsInstance.dispatchAction({
2468
2489
  type: actionType,
2469
2490
  name: series.name
2470
2491
  });
2471
2492
  });
2472
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
+ }
2473
2566
  get displayOptions() {
2474
2567
  const chartDefaults = CHART_DISPLAY_OPTION_DEFAULTS;
2475
2568
  return {