This is an automated email from the ASF dual-hosted git repository.

dominikriemer pushed a commit to branch improve-chart-selection-loading
in repository https://gitbox.apache.org/repos/asf/streampipes.git

commit 6caa3c82557f8839bdf03a7cf196be7c210d7598
Author: Dominik Riemer <[email protected]>
AuthorDate: Wed Jun 17 15:17:21 2026 +0200

    feat: Improve performance of chart fetching
---
 .../chart-selection-panel.component.html           |  2 +-
 .../chart-selection-panel.component.scss           |  5 ++
 .../chart-preview/chart-preview.component.html     | 61 ++++++----------------
 .../chart-preview/chart-preview.component.scss     | 32 +++++++++++-
 .../chart-preview/chart-preview.component.ts       | 22 ++------
 .../chart-selection/chart-selection.component.html | 21 +++++---
 .../chart-selection/chart-selection.component.scss | 29 +++++++++-
 .../chart-selection/chart-selection.component.ts   | 31 ++++++++++-
 .../panel/dashboard-panel.component.html           | 12 ++++-
 .../panel/dashboard-panel.component.scss           | 21 ++++++++
 10 files changed, 161 insertions(+), 75 deletions(-)

diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.html
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.html
index 37166e0a2e..e1f0fd1508 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.html
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.html
@@ -27,7 +27,7 @@
                 data-cy="designer-panel-data-config"
                 [label]="'Charts' | translate"
             >
-                <div class="scroll-tab-content">
+                <div class="scroll-tab-content chart-selection-tab-content">
                     <sp-chart-selection
                         (addChartEmitter)="addChartEmitter.emit($event)"
                         fxFlex="100"
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.scss
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.scss
index e280dd74d5..1ef662e462 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.scss
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection-panel.component.scss
@@ -15,3 +15,8 @@
  * limitations under the License.
  *
  */
+
+.chart-selection-tab-content {
+    height: 100%;
+    overflow-y: hidden;
+}
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.html
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.html
index 56576d9cf0..29eddbab1d 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.html
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.html
@@ -17,48 +17,27 @@
   -->
 
 <div
-    fxLayout="row"
-    fxLayoutAlign="space-between center"
-    fxLayoutGap="12px"
     class="data-view-preview-outer"
-    [attr.data-cy]="'add-data-view-btn-' + chart.name.replaceAll(' ', '')"
+    [attr.data-cy]="dataCyId"
+    [attr.title]="chart.name"
     (click)="addChart()"
 >
-    <div
-        fxLayout="row"
-        fxLayoutAlign="start center"
-        fxLayoutGap="12px"
-        fxFlex
-        class="chart-preview-main"
-    >
-        <div
-            class="chart-preview-icon-shell"
-            fxLayout="row"
-            fxLayoutAlign="center center"
-            [matTooltip]="widgetTypeLabel"
-        >
+    <div class="chart-preview-header">
+        <div class="chart-preview-icon-shell" [attr.title]="widgetTypeLabel">
             <mat-icon>{{ widgetTypeIcon }}</mat-icon>
         </div>
 
-        <div fxFlex fxLayout="column" class="chart-preview-copy">
-            <div
-                fxLayout="row"
-                fxLayoutAlign="start center"
-                fxLayoutGap="8px"
-                class="chart-preview-title-row"
-                [matTooltip]="chart.name"
-            >
-                <h5 fxFlex class="chart-preview-title">{{ chart.name }}</h5>
+        <div class="chart-preview-copy">
+            <div class="chart-preview-title-row">
+                <h5 class="chart-preview-title">{{ chart.name }}</h5>
             </div>
 
-            <div
-                fxLayout="row"
-                fxLayoutAlign="start center"
-                fxLayoutGap="12px"
-                class="chart-preview-meta"
-            >
+            <div class="chart-preview-meta">
                 @if (chart.datasetName) {
-                    <span class="chart-preview-meta-item 
chart-preview-dataset">
+                    <span
+                        class="chart-preview-meta-item chart-preview-dataset"
+                        [attr.title]="chart.datasetName"
+                    >
                         {{ chart.datasetName }}
                     </span>
                 }
@@ -66,26 +45,18 @@
         </div>
     </div>
 
-    <div
-        fxLayout="row"
-        fxLayoutAlign="end center"
-        fxLayoutGap="4px"
-        class="chart-preview-actions"
-    >
+    <div class="chart-preview-actions">
         <button
             mat-icon-button
             type="button"
-            [matTooltip]="'Preview chart' | translate"
+            [attr.aria-label]="'Preview chart' | translate"
+            [attr.title]="'Preview chart' | translate"
             (click)="openPreview($event)"
             data-cy="chart-preview-feature-card"
         >
             <mat-icon>visibility</mat-icon>
         </button>
-        <div
-            class="chart-preview-add-indicator"
-            fxLayout="row"
-            fxLayoutAlign="center center"
-        >
+        <div class="chart-preview-add-indicator">
             <mat-icon>add</mat-icon>
         </div>
     </div>
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.scss
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.scss
index 5282cd1fc1..3d7a82ba85 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.scss
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.scss
@@ -17,6 +17,10 @@
  */
 
 .data-view-preview-outer {
+    display: flex;
+    flex-direction: column;
+    align-items: stretch;
+    gap: 8px;
     min-height: 72px;
     border: 1px solid color-mix(in srgb, var(--color-bg-3) 68%, transparent);
     border-radius: 14px;
@@ -41,7 +45,6 @@
     );
     background: color-mix(in srgb, var(--color-bg-1) 86%, white 14%);
     box-shadow: 0 10px 24px rgba(15, 23, 42, 0.08);
-    transform: translateY(-1px);
     cursor: pointer;
 }
 
@@ -50,6 +53,9 @@ mat-icon {
 }
 
 .chart-preview-icon-shell {
+    display: flex;
+    align-items: center;
+    justify-content: center;
     width: 40px;
     min-width: 40px;
     height: 40px;
@@ -63,11 +69,23 @@ mat-icon {
 }
 
 .chart-preview-main {
+    display: contents;
+}
+
+.chart-preview-header {
+    display: flex;
+    align-items: center;
+    gap: 12px;
     min-width: 0;
+    overflow: hidden;
 }
 
 .chart-preview-copy {
+    display: flex;
+    flex: 1 1 auto;
+    flex-direction: column;
     min-width: 0;
+    overflow: hidden;
 }
 
 .chart-preview-title-row {
@@ -88,13 +106,16 @@ mat-icon {
 
 .chart-preview-meta {
     min-width: 0;
+    display: block;
     width: 100%;
+    overflow: hidden;
     margin-top: 2px;
     color: var(--color-paragraph);
     font-size: var(--font-size-sm);
 }
 
 .chart-preview-meta-item {
+    display: block;
     overflow: hidden;
     text-overflow: ellipsis;
     white-space: nowrap;
@@ -113,10 +134,19 @@ mat-icon {
 }
 
 .chart-preview-actions {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    gap: 4px;
     flex-shrink: 0;
+    width: 100%;
+    margin-left: 0;
 }
 
 .chart-preview-add-indicator {
+    display: flex;
+    align-items: center;
+    justify-content: center;
     width: 28px;
     height: 28px;
     border-radius: 50%;
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.ts
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.ts
index 153a49c417..4ca3306b26 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.ts
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-preview/chart-preview.component.ts
@@ -17,6 +17,7 @@
  */
 
 import {
+    ChangeDetectionStrategy,
     Component,
     EventEmitter,
     Input,
@@ -26,15 +27,8 @@ import {
 } from '@angular/core';
 import { ChartSummaryDto } from '@streampipes/platform-services';
 import { ChartRegistry } from 
'../../../../../../chart-shared/registry/chart-registry.service';
-import {
-    FlexDirective,
-    LayoutAlignDirective,
-    LayoutDirective,
-    LayoutGapDirective,
-} from '@ngbracket/ngx-layout/flex';
 import { MatIcon } from '@angular/material/icon';
 import { MatIconButton } from '@angular/material/button';
-import { MatTooltip } from '@angular/material/tooltip';
 import { FeatureCardService } from '@streampipes/shared-ui';
 import { TranslatePipe } from '@ngx-translate/core';
 
@@ -42,16 +36,8 @@ import { TranslatePipe } from '@ngx-translate/core';
     selector: 'sp-chart-preview',
     templateUrl: './chart-preview.component.html',
     styleUrls: ['./chart-preview.component.scss'],
-    imports: [
-        LayoutDirective,
-        FlexDirective,
-        LayoutGapDirective,
-        LayoutAlignDirective,
-        MatIcon,
-        MatIconButton,
-        MatTooltip,
-        TranslatePipe,
-    ],
+    changeDetection: ChangeDetectionStrategy.OnPush,
+    imports: [MatIcon, MatIconButton, TranslatePipe],
 })
 export class ChartPreviewComponent implements OnInit {
     private widgetRegistryService = inject(ChartRegistry);
@@ -62,6 +48,7 @@ export class ChartPreviewComponent implements OnInit {
 
     widgetTypeLabel = '';
     widgetTypeIcon = 'insert_chart';
+    dataCyId = '';
 
     @Output()
     addChartEmitter: EventEmitter<string> = new EventEmitter<string>();
@@ -72,6 +59,7 @@ export class ChartPreviewComponent implements OnInit {
         );
         this.widgetTypeLabel = template?.label ?? this.chart.widgetType;
         this.widgetTypeIcon = template?.icon ?? 'insert_chart';
+        this.dataCyId = `add-data-view-btn-${this.chart.name.replaceAll(' ', 
'')}`;
     }
 
     addChart(): void {
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
index d77aeb90f0..8fd0b96a9e 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.html
@@ -100,20 +100,27 @@
     </div>
 
     @if (charts.length > 0 && filteredCharts.length > 0) {
-        <div
+        <cdk-virtual-scroll-viewport
             fxFlexFill
-            fxLayout="column"
-            fxLayoutGap="8px"
-            class="chart-selection-results"
+            class="chart-selection-results chart-selection-viewport"
+            [itemSize]="chartItemSize"
+            [minBufferPx]="chartItemSize * 4"
+            [maxBufferPx]="chartItemSize * 8"
         >
-            @for (chart of filteredCharts; track chart.elementId) {
+            <div
+                *cdkVirtualFor="
+                    let chart of filteredCharts;
+                    trackBy: trackByChartId
+                "
+                class="chart-selection-item"
+            >
                 <sp-chart-preview
                     [chart]="chart"
                     (addChartEmitter)="addChartEmitter.emit($event)"
                 >
                 </sp-chart-preview>
-            }
-        </div>
+            </div>
+        </cdk-virtual-scroll-viewport>
     } @else if (charts.length === 0) {
         <div fxLayoutAlign="center center" fxLayout="column" fxFlex="100">
             <span class="no-widget-hint">{{
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.scss
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.scss
index 445d919e70..50eee74146 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.scss
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.scss
@@ -17,7 +17,12 @@
  */
 
 .chart-selection-shell {
+    box-sizing: border-box;
+    display: flex;
+    flex-direction: column;
     height: 100%;
+    min-height: 0;
+    overflow: hidden;
     padding: 6px 6px 8px;
     background: linear-gradient(
         180deg,
@@ -27,6 +32,7 @@
 }
 
 .chart-selection-toolbar {
+    flex: 0 0 auto;
     position: sticky;
     top: 0;
     z-index: 2;
@@ -64,7 +70,28 @@
 }
 
 .chart-selection-results {
-    padding-top: 2px;
+    flex: 1 1 0;
+    min-height: 0;
+    padding-top: 0;
+}
+
+.chart-selection-viewport {
+    box-sizing: border-box;
+    width: 100%;
+    overflow-y: auto;
+    overflow-x: hidden;
+}
+
+.chart-selection-item {
+    box-sizing: border-box;
+    width: 100%;
+    height: 132px;
+    padding: 0 0 6px;
+}
+
+.chart-selection-viewport ::ng-deep .cdk-virtual-scroll-content-wrapper {
+    width: 100%;
+    max-width: 100%;
 }
 
 .chart-selection-empty {
diff --git 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
index 9059f2b978..e9a346177f 100644
--- 
a/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
+++ 
b/ui/src/app/dashboard/components/panel/chart-selection-panel/chart-selection/chart-selection.component.ts
@@ -16,7 +16,15 @@
  *
  */
 
-import { Component, EventEmitter, inject, OnInit, Output } from 
'@angular/core';
+import {
+    ChangeDetectionStrategy,
+    ChangeDetectorRef,
+    Component,
+    EventEmitter,
+    inject,
+    OnInit,
+    Output,
+} from '@angular/core';
 import { ChartService, ChartSummaryDto } from '@streampipes/platform-services';
 import { AuthService } from '../../../../../services/auth.service';
 import { UserPrivilege } from '../../../../../core/auth/user-privilege.enum';
@@ -40,11 +48,17 @@ import {
 } from '@angular/material/form-field';
 import { MatInput } from '@angular/material/input';
 import { MatTooltip } from '@angular/material/tooltip';
+import {
+    CdkFixedSizeVirtualScroll,
+    CdkVirtualForOf,
+    CdkVirtualScrollViewport,
+} from '@angular/cdk/scrolling';
 
 @Component({
     selector: 'sp-chart-selection',
     templateUrl: './chart-selection.component.html',
     styleUrls: ['./chart-selection.component.scss'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
     imports: [
         FlexDirective,
         FlexFillDirective,
@@ -61,6 +75,9 @@ import { MatTooltip } from '@angular/material/tooltip';
         MatInput,
         TranslatePipe,
         MatTooltip,
+        CdkVirtualScrollViewport,
+        CdkFixedSizeVirtualScroll,
+        CdkVirtualForOf,
     ],
 })
 export class ChartSelectionComponent implements OnInit {
@@ -68,6 +85,7 @@ export class ChartSelectionComponent implements OnInit {
     private authService = inject(AuthService);
     private chartRegistryService = inject(ChartRegistry);
     private chartRoutingService = inject(ChartRoutingService);
+    private cdr = inject(ChangeDetectorRef);
 
     @Output()
     addChartEmitter: EventEmitter<string> = new EventEmitter();
@@ -76,6 +94,7 @@ export class ChartSelectionComponent implements OnInit {
     filteredCharts: ChartSummaryDto[] = [];
     searchTerm = '';
     isRefreshing = false;
+    readonly chartItemSize = 132;
 
     hasChartWritePrivileges: boolean = false;
 
@@ -93,18 +112,22 @@ export class ChartSelectionComponent implements OnInit {
 
     refreshCharts(): void {
         this.isRefreshing = true;
+        this.cdr.markForCheck();
         this.dataViewService.getChartSummary().subscribe({
             next: chartSummary => {
                 this.charts = chartSummary.resources.sort((a, b) =>
                     a.name.localeCompare(b.name),
                 );
                 this.applySearch();
+                this.cdr.markForCheck();
             },
             complete: () => {
                 this.isRefreshing = false;
+                this.cdr.markForCheck();
             },
             error: () => {
                 this.isRefreshing = false;
+                this.cdr.markForCheck();
             },
         });
     }
@@ -112,17 +135,23 @@ export class ChartSelectionComponent implements OnInit {
     onSearchTermChanged(value: string): void {
         this.searchTerm = value;
         this.applySearch();
+        this.cdr.markForCheck();
     }
 
     clearSearch(): void {
         this.searchTerm = '';
         this.applySearch();
+        this.cdr.markForCheck();
     }
 
     hasActiveSearch(): boolean {
         return this.searchTerm.trim().length > 0;
     }
 
+    trackByChartId(index: number, chart: ChartSummaryDto): string {
+        return chart.elementId;
+    }
+
     private applySearch(): void {
         const query = this.searchTerm.trim().toLowerCase();
         if (!query) {
diff --git 
a/ui/src/app/dashboard/components/panel/dashboard-panel.component.html 
b/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
index 86eda57b85..d60106f42e 100644
--- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
+++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.html
@@ -75,17 +75,23 @@
                         position="end"
                         class="designer-panel"
                     >
-                        <div fxLayout="column" fxFlex="100">
+                        <div
+                            fxLayout="column"
+                            fxFlex="100"
+                            style="min-height: 0; overflow: hidden"
+                        >
                             @if (editMode) {
                                 <div
                                     fxFlex="100"
                                     class="designer-panel-content"
                                     fxLayout="column"
+                                    style="min-height: 0"
                                 >
                                     <div
                                         fxFlex="100"
                                         fxLayout="column"
                                         class="no-overflow"
+                                        style="min-height: 0"
                                     >
                                         <mat-tab-group
                                             color="accent"
@@ -99,7 +105,9 @@
                                                 
data-cy="designer-panel-data-config"
                                                 [label]="'Charts' | translate"
                                             >
-                                                <div 
class="scroll-tab-content">
+                                                <div
+                                                    class="scroll-tab-content 
chart-selection-tab-content"
+                                                >
                                                     <sp-chart-selection
                                                         (addChartEmitter)="
                                                             
addChartToDashboard(
diff --git 
a/ui/src/app/dashboard/components/panel/dashboard-panel.component.scss 
b/ui/src/app/dashboard/components/panel/dashboard-panel.component.scss
index 3708772e94..2655e51d21 100644
--- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.scss
+++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.scss
@@ -45,6 +45,27 @@
 
 .designer-panel {
     width: 350px;
+    overflow: hidden;
+}
+
+.chart-selection-tab-content {
+    height: 100%;
+    overflow-y: hidden;
+}
+
+.designer-panel ::ng-deep .mat-drawer-inner-container {
+    overflow: hidden;
+}
+
+.designer-panel ::ng-deep .mat-mdc-tab-body-wrapper,
+.designer-panel ::ng-deep .mat-mdc-tab-body,
+.designer-panel ::ng-deep .mat-mdc-tab-body-content {
+    height: 100%;
+    min-height: 0;
+}
+
+.designer-panel ::ng-deep .mat-mdc-tab-body-content {
+    overflow: hidden;
 }
 
 .panel-toggle-button {

Reply via email to