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

riemer pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to refs/heads/dev by this push:
     new 7e68b8dda6 feat: Improve dashboard appearance (#4276)
7e68b8dda6 is described below

commit 7e68b8dda6ee198f0b1ef989474cbe7a608cf281
Author: Dominik Riemer <[email protected]>
AuthorDate: Mon Mar 23 15:41:43 2026 +0100

    feat: Improve dashboard appearance (#4276)
---
 .../chart-container/chart-container.component.ts   |  6 ++
 .../base/base-data-explorer-widget.directive.ts    |  2 +
 .../charts/base/echarts-widget.component.ts        | 40 +++++++--
 .../charts/gauge/gauge-renderer.service.ts         | 74 +++++++++++++++--
 .../components/charts/pie/pie-renderer.service.ts  | 60 ++++++++++++++
 .../sp-timeseries-renderer.service.ts              | 95 +++++++++++++++++++++-
 .../models/dataview-dashboard.model.ts             |  5 ++
 .../chart-view/chart-view.component.html           |  1 +
 .../components/chart-view/chart-view.component.ts  | 42 ++++++++--
 .../chart-data-preview.component.ts                |  4 +
 .../grid-view/dashboard-grid-view.component.html   |  4 +
 .../grid-view/dashboard-grid-view.component.scss   |  3 +-
 .../grid-view/dashboard-grid-view.component.ts     | 23 +++++-
 .../slide-view/dashboard-slide-view.component.html |  4 +
 .../overview/dashboard-overview.component.ts       |  7 +-
 .../components/panel/dashboard-panel.component.ts  | 14 +++-
 .../edit-dashboard-dialog.component.html           | 29 +++++++
 .../edit-dashboard-dialog.component.ts             | 13 +++
 18 files changed, 400 insertions(+), 26 deletions(-)

diff --git 
a/ui/src/app/chart-shared/components/chart-container/chart-container.component.ts
 
b/ui/src/app/chart-shared/components/chart-container/chart-container.component.ts
index 9cdbd8e479..735874318d 100644
--- 
a/ui/src/app/chart-shared/components/chart-container/chart-container.component.ts
+++ 
b/ui/src/app/chart-shared/components/chart-container/chart-container.component.ts
@@ -61,6 +61,7 @@ import {
 import { ChartSharedService } from '../../services/chart-shared.service';
 import {
     BaseWidgetData,
+    DashboardChartOverrides,
     ObservableGenerator,
 } from '../../models/dataview-dashboard.model';
 import { MatMenu, MatMenuItem, MatMenuTrigger } from '@angular/material/menu';
@@ -150,6 +151,9 @@ export class ChartContainerComponent
     @Input()
     observableGenerator: ObservableGenerator;
 
+    @Input()
+    dashboardChartOverrides: DashboardChartOverrides = {};
+
     @Output() deleteCallback: EventEmitter<number> = new 
EventEmitter<number>();
     @Output() startEditModeEmitter: EventEmitter<DataExplorerWidgetModel> =
         new EventEmitter<DataExplorerWidgetModel>();
@@ -347,6 +351,8 @@ export class ChartContainerComponent
         this.componentRef.instance.widgetIndex = this.widgetIndex;
         this.componentRef.instance.observableGenerator =
             this.observableGenerator;
+        this.componentRef.instance.dashboardChartOverrides =
+            this.dashboardChartOverrides;
         const remove$ =
             this.componentRef.instance.removeWidgetCallback.subscribe(ev =>
                 this.removeWidget(),
diff --git 
a/ui/src/app/chart-shared/components/charts/base/base-data-explorer-widget.directive.ts
 
b/ui/src/app/chart-shared/components/charts/base/base-data-explorer-widget.directive.ts
index a1f862a65b..4b8d9f3f4a 100644
--- 
a/ui/src/app/chart-shared/components/charts/base/base-data-explorer-widget.directive.ts
+++ 
b/ui/src/app/chart-shared/components/charts/base/base-data-explorer-widget.directive.ts
@@ -38,6 +38,7 @@ import {
 import { ResizeService } from '../../../services/resize.service';
 import {
     BaseWidgetData,
+    DashboardChartOverrides,
     FieldProvider,
     ObservableGenerator,
 } from '../../../models/dataview-dashboard.model';
@@ -90,6 +91,7 @@ export abstract class BaseDataExplorerWidgetDirective<
 
     @Input() dataViewDashboardItem: ClientDashboardItem;
     @Input() dataExplorerWidget: T;
+    @Input() dashboardChartOverrides: DashboardChartOverrides = {};
 
     @Input()
     widgetIndex: number;
diff --git 
a/ui/src/app/chart-shared/components/charts/base/echarts-widget.component.ts 
b/ui/src/app/chart-shared/components/charts/base/echarts-widget.component.ts
index 9303a864a0..07d669d1cb 100644
--- a/ui/src/app/chart-shared/components/charts/base/echarts-widget.component.ts
+++ b/ui/src/app/chart-shared/components/charts/base/echarts-widget.component.ts
@@ -113,16 +113,22 @@ export class SpEchartsWidgetComponent<T extends 
DataExplorerWidgetModel>
                 true
         ) {
             this.showInvalidConfiguration = false;
+            const effectiveWidgetConfig =
+                this.getWidgetConfigWithDashboardOverrides();
             this.option = {
-                ...this.renderer.render(
-                    spQueryResult,
-                    this.dataExplorerWidget,
-                    {
-                        width: this.currentWidth,
-                        height: this.currentHeight,
-                    },
-                ),
+                ...this.renderer.render(spQueryResult, effectiveWidgetConfig, {
+                    width: this.currentWidth,
+                    height: this.currentHeight,
+                }),
             };
+            if (this.dashboardChartOverrides?.hideToolbox) {
+                const toolbox = this.option['toolbox'];
+                if (toolbox) {
+                    (Array.isArray(toolbox) ? toolbox : [toolbox]).forEach(
+                        tb => (tb.show = false),
+                    );
+                }
+            }
             if (this.kioskMode) {
                 ['toolbox', 'visualMap'].forEach(key => {
                     const item = this.option[key];
@@ -146,6 +152,24 @@ export class SpEchartsWidgetComponent<T extends 
DataExplorerWidgetModel>
         }
     }
 
+    private getWidgetConfigWithDashboardOverrides(): T {
+        if (!this.dashboardChartOverrides?.hideToolbox) {
+            return this.dataExplorerWidget;
+        }
+
+        return {
+            ...this.dataExplorerWidget,
+            baseAppearanceConfig: {
+                ...this.dataExplorerWidget.baseAppearanceConfig,
+                chartAppearance: {
+                    ...this.dataExplorerWidget.baseAppearanceConfig
+                        ?.chartAppearance,
+                    showToolbox: false,
+                },
+            },
+        };
+    }
+
     refreshView() {
         this.renderSubject.next();
     }
diff --git 
a/ui/src/app/chart-shared/components/charts/gauge/gauge-renderer.service.ts 
b/ui/src/app/chart-shared/components/charts/gauge/gauge-renderer.service.ts
index 14603b455c..f72bfcf52d 100644
--- a/ui/src/app/chart-shared/components/charts/gauge/gauge-renderer.service.ts
+++ b/ui/src/app/chart-shared/components/charts/gauge/gauge-renderer.service.ts
@@ -45,12 +45,16 @@ export class SpGaugeRendererService implements 
SpEchartsRenderer<GaugeWidgetMode
         value: number,
         widgetConfig: GaugeWidgetModel,
         widgetSize: WidgetSize,
+        gaugeLayout: GaugeLayout,
     ): GaugeSeriesOption {
         const visConfig = widgetConfig.visualizationConfig;
-        const clamp = Math.min(Math.max(widgetSize.width / 400, 0.7), 1.4);
+        const minDimension = Math.min(widgetSize.width, widgetSize.height);
+        const clamp = Math.min(Math.max(minDimension / 320, 0.7), 1.4);
         return {
             name: seriesName,
             type: 'gauge',
+            center: ['50%', gaugeLayout.centerY],
+            radius: gaugeLayout.radius,
             progress: {
                 show: true,
             },
@@ -62,7 +66,7 @@ export class SpGaugeRendererService implements 
SpEchartsRenderer<GaugeWidgetMode
                 valueAnimation: false,
                 formatter: '{value}',
                 fontSize: 14 * clamp,
-                offsetCenter: [0, '70%'],
+                offsetCenter: [0, gaugeLayout.detailOffsetY],
             },
             min: visConfig.min,
             max: visConfig.max,
@@ -105,10 +109,31 @@ export class SpGaugeRendererService implements 
SpEchartsRenderer<GaugeWidgetMode
             selectedField.fullDbName,
         );
         const data = parseFloat(dataSeries.rows[0][columnIndex].toFixed(2));
+        const legend =
+            !Array.isArray(option.legend) && option.legend ? option.legend : 
{};
+        const toolbox =
+            !Array.isArray(option.toolbox) && option.toolbox
+                ? option.toolbox
+                : {};
+        const showLegend = false;
+        const showToolbox = toolbox.show ?? true;
+        const gaugeLayout = this.makeGaugeLayout(
+            widgetSize,
+            showToolbox,
+            showLegend,
+        );
+
         Object.assign(option, {
-            grid: {
-                width: '100%',
-                height: '100%',
+            toolbox: {
+                ...toolbox,
+                left: 10,
+                right: 'auto',
+                top: 4,
+                show: showToolbox,
+            },
+            legend: {
+                ...legend,
+                show: showLegend,
             },
             series: this.makeSeriesItem(
                 '',
@@ -116,9 +141,48 @@ export class SpGaugeRendererService implements 
SpEchartsRenderer<GaugeWidgetMode
                 data,
                 widgetConfig,
                 widgetSize,
+                gaugeLayout,
             ),
         });
 
         return option;
     }
+
+    private makeGaugeLayout(
+        widgetSize: WidgetSize,
+        showToolbox: boolean,
+        showLegend: boolean,
+    ): GaugeLayout {
+        const topPadding = 8;
+        const bottomPadding = 14;
+        const toolboxHeight = showToolbox ? 30 : 0;
+        const legendHeight = showLegend ? 30 : 0;
+        const gap = showToolbox && showLegend ? 6 : 0;
+        const topReserved = topPadding + toolboxHeight + gap + legendHeight;
+
+        const availableHeight = Math.max(
+            100,
+            widgetSize.height - topReserved - bottomPadding,
+        );
+        const availableWidth = Math.max(100, widgetSize.width - 20);
+        const diameter = Math.max(
+            90,
+            Math.min(availableHeight, availableWidth),
+        );
+        const radius = Math.round(diameter * 0.46);
+        const centerY = topReserved + Math.round(availableHeight / 2);
+        const detailOffsetY = Math.round(radius * 0.62);
+
+        return {
+            centerY,
+            radius,
+            detailOffsetY,
+        };
+    }
+}
+
+interface GaugeLayout {
+    centerY: number;
+    radius: number;
+    detailOffsetY: number;
 }
diff --git 
a/ui/src/app/chart-shared/components/charts/pie/pie-renderer.service.ts 
b/ui/src/app/chart-shared/components/charts/pie/pie-renderer.service.ts
index 4df52ba493..8c61215663 100644
--- a/ui/src/app/chart-shared/components/charts/pie/pie-renderer.service.ts
+++ b/ui/src/app/chart-shared/components/charts/pie/pie-renderer.service.ts
@@ -80,6 +80,7 @@ export class SpPieRendererService extends 
SpBaseSingleFieldEchartsRenderer<
                 },
             };
         }
+        this.applySinglePieResponsiveLayout(option);
     }
 
     addSeriesItem(
@@ -167,4 +168,63 @@ export class SpPieRendererService extends 
SpBaseSingleFieldEchartsRenderer<
     getDefaultSeriesName(widgetConfig: PieChartWidgetModel): string {
         return widgetConfig.visualizationConfig.selectedProperty.fullDbName;
     }
+
+    private applySinglePieResponsiveLayout(option: EChartsOption): void {
+        const pieSeries = Array.isArray(option.series)
+            ? option.series
+            : option.series
+              ? [option.series]
+              : [];
+
+        // Keep grouped/tagged pie layout unchanged.
+        if (pieSeries.length !== 1) {
+            return;
+        }
+
+        const legend =
+            !Array.isArray(option.legend) && option.legend ? option.legend : 
{};
+        const toolbox =
+            !Array.isArray(option.toolbox) && option.toolbox
+                ? option.toolbox
+                : {};
+
+        const showLegend = legend.show ?? true;
+        const showToolbox = toolbox.show ?? true;
+        const toolboxTop = 4;
+        const toolboxHeight = showToolbox ? 28 : 0;
+        const legendTop = showToolbox ? 36 : 6;
+        const legendHeight = showLegend ? 24 : 0;
+        const topControlsBottom = Math.max(
+            showToolbox ? toolboxTop + toolboxHeight : 0,
+            showLegend ? legendTop + legendHeight : 0,
+        );
+        const pieTop = topControlsBottom > 0 ? topControlsBottom + 8 : 8;
+        const pieBottom = 8;
+
+        option.toolbox = {
+            ...toolbox,
+            show: showToolbox,
+            left: 10,
+            right: 'auto',
+            top: toolboxTop,
+        };
+        option.legend = {
+            ...legend,
+            show: showLegend,
+            type: 'scroll',
+            left: showToolbox ? 120 : 10,
+            right: 10,
+            top: legendTop,
+            bottom: 'auto',
+        };
+
+        const singlePieSeries = pieSeries[0] as PieSeriesOption;
+        delete singlePieSeries.width;
+        delete singlePieSeries.height;
+        singlePieSeries.left = 10;
+        singlePieSeries.right = 10;
+        singlePieSeries.top = pieTop;
+        singlePieSeries.bottom = pieBottom;
+        singlePieSeries.center = ['50%', '50%'];
+    }
 }
diff --git 
a/ui/src/app/chart-shared/components/charts/time-series-chart/sp-timeseries-renderer.service.ts
 
b/ui/src/app/chart-shared/components/charts/time-series-chart/sp-timeseries-renderer.service.ts
index 3c221f4cfe..726265bb10 100644
--- 
a/ui/src/app/chart-shared/components/charts/time-series-chart/sp-timeseries-renderer.service.ts
+++ 
b/ui/src/app/chart-shared/components/charts/time-series-chart/sp-timeseries-renderer.service.ts
@@ -34,7 +34,7 @@ import {
 } from '../../../models/dataview-dashboard.model';
 import type { ToolboxFeatureOption } from 
'echarts/types/src/component/toolbox/featureManager.d.ts';
 import type { ToolboxDataZoomFeatureOption } from 
'echarts/types/src/component/toolbox/feature/DataZoom.d.ts';
-import { YAXisOption } from 'echarts/types/dist/shared';
+import { XAXisOption, YAXisOption } from 'echarts/types/dist/shared';
 import type { CartesianAxisPosition } from 
'echarts/types/src/coord/cartesian/AxisModel.d.ts';
 import type { FieldUpdateInfo } from '../../../models/field-update.model';
 
@@ -44,9 +44,9 @@ export class SpTimeseriesRendererService extends 
SpBaseEchartsRenderer<TimeSerie
         generatedDataset: GeneratedDataset,
         options: EChartsOption,
         widgetConfig: TimeSeriesChartWidgetModel,
-        _widgetSize: WidgetSize,
+        widgetSize: WidgetSize,
     ): void {
-        this.addAxisOptions(widgetConfig, options);
+        this.addAxisOptions(widgetConfig, options, widgetSize);
         const finalSeries: SeriesOption[] = [];
 
         
widgetConfig.visualizationConfig.selectedTimeSeriesChartProperties.forEach(
@@ -95,6 +95,7 @@ export class SpTimeseriesRendererService extends 
SpBaseEchartsRenderer<TimeSerie
         );
 
         this.addDataZoomOptions(widgetConfig, options);
+        this.applyResponsiveLayoutOptions(options, widgetConfig, widgetSize);
 
         const showTooltip =
             widgetConfig.baseAppearanceConfig.chartAppearance?.showTooltip;
@@ -260,12 +261,22 @@ export class SpTimeseriesRendererService extends 
SpBaseEchartsRenderer<TimeSerie
     private addAxisOptions(
         config: TimeSeriesChartWidgetModel,
         options: EChartsOption,
+        widgetSize: WidgetSize,
     ): void {
         const xAxisOption = this.axisGeneratorService.makeAxis(
             'time',
             0,
             config.baseAppearanceConfig as WidgetBaseAppearanceConfig,
-        );
+        ) as XAXisOption;
+        if (xAxisOption.type === 'time') {
+            xAxisOption.splitNumber = this.makeResponsiveSplitNumber(
+                widgetSize.width,
+            );
+        }
+        xAxisOption.axisLabel = {
+            ...xAxisOption.axisLabel,
+            hideOverlap: true,
+        };
 
         const yAxisOptions: YAXisOption[] = [];
 
@@ -298,4 +309,80 @@ export class SpTimeseriesRendererService extends 
SpBaseEchartsRenderer<TimeSerie
             yAxis: yAxisOptions,
         });
     }
+
+    private applyResponsiveLayoutOptions(
+        options: EChartsOption,
+        config: TimeSeriesChartWidgetModel,
+        widgetSize: WidgetSize,
+    ): void {
+        const width = widgetSize.width ?? 0;
+        const isSmallWidget = width > 0 && width < 700;
+        const hasSliderDataZoom =
+            config.baseAppearanceConfig.dataZoom?.show &&
+            config.baseAppearanceConfig.dataZoom?.type === 'slider';
+
+        const legend =
+            !Array.isArray(options.legend) && options.legend
+                ? options.legend
+                : {};
+        const toolbox =
+            !Array.isArray(options.toolbox) && options.toolbox
+                ? options.toolbox
+                : {};
+
+        const showLegend = legend.show ?? true;
+        const showToolbox = toolbox.show ?? true;
+        const horizontalPadding = isSmallWidget ? 14 : 18;
+        const topToolboxTop = 4;
+        const toolboxHeight = showToolbox ? 28 : 0;
+        const topLegendTop = showToolbox
+            ? topToolboxTop + toolboxHeight + 4
+            : 6;
+        const topLegendHeight = showLegend ? 24 : 0;
+        const topControlsBottom = Math.max(
+            showToolbox ? topToolboxTop + toolboxHeight : 0,
+            showLegend ? topLegendTop + topLegendHeight : 0,
+        );
+        const gridTop = topControlsBottom > 0 ? topControlsBottom + 8 : 16;
+
+        options.toolbox = {
+            ...toolbox,
+            show: showToolbox,
+            left: 10,
+            right: 'auto',
+            top: topToolboxTop,
+        };
+
+        options.legend = {
+            ...legend,
+            show: showLegend,
+            orient: 'horizontal',
+            type: 'scroll',
+            left: 'center',
+            right: 'auto',
+            top: topLegendTop,
+            bottom: 'auto',
+        };
+
+        options.grid = {
+            left: horizontalPadding,
+            right: horizontalPadding,
+            top: gridTop,
+            bottom: hasSliderDataZoom ? 72 : 34,
+            containLabel: true,
+        };
+    }
+
+    private makeResponsiveSplitNumber(width: number): number {
+        if (!width || Number.isNaN(width)) {
+            return 5;
+        }
+
+        const targetPixelPerLabel = 120;
+        const minTicks = 2;
+        const maxTicks = 12;
+        const estimatedTicks = Math.floor(width / targetPixelPerLabel);
+
+        return Math.min(maxTicks, Math.max(minTicks, estimatedTicks));
+    }
 }
diff --git a/ui/src/app/chart-shared/models/dataview-dashboard.model.ts 
b/ui/src/app/chart-shared/models/dataview-dashboard.model.ts
index 3f6e310754..8a6dc64a9a 100644
--- a/ui/src/app/chart-shared/models/dataview-dashboard.model.ts
+++ b/ui/src/app/chart-shared/models/dataview-dashboard.model.ts
@@ -50,10 +50,15 @@ export interface BaseWidgetData<T extends 
DataExplorerWidgetModel> {
     previewMode: boolean;
     gridMode: boolean;
     widgetIndex?: number;
+    dashboardChartOverrides?: DashboardChartOverrides;
 
     cleanupSubscriptions(): void;
 }
 
+export interface DashboardChartOverrides {
+    hideToolbox?: boolean;
+}
+
 export interface ObservableGenerator {
     generateObservables(
         startTime: number,
diff --git a/ui/src/app/chart/components/chart-view/chart-view.component.html 
b/ui/src/app/chart/components/chart-view/chart-view.component.html
index cdfb4ae0af..cfcde07378 100644
--- a/ui/src/app/chart/components/chart-view/chart-view.component.html
+++ b/ui/src/app/chart/components/chart-view/chart-view.component.html
@@ -120,6 +120,7 @@
 
                             <sp-chart-data-preview
                                 [queryResults]="latestQueryResults"
+                                (sizeChanged)="onDataPreviewSizeChanged()"
                             >
                             </sp-chart-data-preview>
                         </div>
diff --git a/ui/src/app/chart/components/chart-view/chart-view.component.ts 
b/ui/src/app/chart/components/chart-view/chart-view.component.ts
index d34a91c876..668726008c 100644
--- a/ui/src/app/chart/components/chart-view/chart-view.component.ts
+++ b/ui/src/app/chart/components/chart-view/chart-view.component.ts
@@ -61,6 +61,7 @@ import { MatDialog } from '@angular/material/dialog';
 import { catchError, map, switchMap } from 'rxjs/operators';
 import { TranslatePipe, TranslateService } from '@ngx-translate/core';
 import { ResizeEchartsService } from 
'../../../chart-shared/services/resize-echarts.service';
+import { ResizeService } from '../../../chart-shared/services/resize.service';
 import { AssetDialogComponent } from '../../dialog/asset-dialog.component';
 import { AuthService } from '../../../services/auth.service';
 import { UserRole } from '../../../core/auth/user-role.enum';
@@ -118,6 +119,7 @@ export class ChartViewComponent
     originalAssets = [];
 
     resizeEchartsService = inject(ResizeEchartsService);
+    resizeService = inject(ResizeService);
 
     private dataExplorerSharedService = inject(ChartSharedService);
     private detectChangesService = inject(ChartDetectChangesService);
@@ -490,11 +492,41 @@ export class ChartViewComponent
 
     onWidthChanged(newWidth: number) {
         this.drawerWidth = newWidth;
-        setTimeout(() => {
-            this.resizeEchartsService.notify(
-                this.outerPanel.nativeElement.offsetWidth,
-            );
-        }, 100);
+        this.scheduleChartPanelResize(100);
+    }
+
+    onDataPreviewSizeChanged(): void {
+        // Preview height animates; send resize updates during and after 
transition.
+        [0, 100, 220, 350].forEach(delay =>
+            this.scheduleChartPanelResize(delay),
+        );
+    }
+
+    private scheduleChartPanelResize(delayMs = 0): void {
+        setTimeout(
+            () => requestAnimationFrame(() => this.notifyChartPanelResize()),
+            delayMs,
+        );
+    }
+
+    private notifyChartPanelResize(): void {
+        const panel = this.outerPanel?.nativeElement;
+        if (!panel) {
+            return;
+        }
+
+        const widgetContent = panel.querySelector(
+            '.widget-content',
+        ) as HTMLDivElement | null;
+        const width = widgetContent?.clientWidth ?? panel.offsetWidth;
+        const height = widgetContent?.clientHeight ?? panel.offsetHeight;
+
+        this.resizeService.notify({
+            width,
+            height,
+            widgetId: undefined,
+        });
+        this.resizeEchartsService.notify(width);
     }
 
     private async saveAssets(linkageData: LinkageData[]): Promise<void> {
diff --git 
a/ui/src/app/chart/components/chart-view/query-result-preview/chart-data-preview.component.ts
 
b/ui/src/app/chart/components/chart-view/query-result-preview/chart-data-preview.component.ts
index 68c827acc5..ed2e8fddd1 100644
--- 
a/ui/src/app/chart/components/chart-view/query-result-preview/chart-data-preview.component.ts
+++ 
b/ui/src/app/chart/components/chart-view/query-result-preview/chart-data-preview.component.ts
@@ -20,9 +20,11 @@ import { DatePipe } from '@angular/common';
 import {
     ChangeDetectionStrategy,
     Component,
+    EventEmitter,
     HostBinding,
     Input,
     OnChanges,
+    Output,
     SimpleChanges,
 } from '@angular/core';
 import { MatIcon } from '@angular/material/icon';
@@ -57,6 +59,7 @@ export class ChartDataPreviewComponent implements OnChanges {
 
     @Input() queryResults: SpQueryResult[] = [];
     @Input() defaultExpanded = false;
+    @Output() sizeChanged = new EventEmitter<void>();
 
     columns: string[] = [];
     rows: PreviewRow[] = [];
@@ -200,5 +203,6 @@ export class ChartDataPreviewComponent implements OnChanges 
{
 
     toggleExpanded(): void {
         this.expanded = !this.expanded;
+        this.sizeChanged.emit();
     }
 }
diff --git 
a/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.html
 
b/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.html
index 2bb1a357c6..c1dbe2d9c0 100644
--- 
a/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.html
+++ 
b/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.html
@@ -26,6 +26,7 @@
     [options]="gridOptions"
     (changeCB)="onGridChange($event)"
     class="dashboard-outer"
+    [style.max-width.px]="kioskMode ? null : maxGridWidthPx"
     #grid
 >
     @for (item of dashboard.widgets; let i = $index; track item.id) {
@@ -53,6 +54,9 @@
                     [kioskMode]="kioskMode"
                     [gridMode]="true"
                     [widgetIndex]="i"
+                    [dashboardChartOverrides]="
+                        dashboard.dashboardGeneralSettings?.chartOverrides || 
{}
+                    "
                 ></sp-chart-container>
             }
         </gridstack-item>
diff --git 
a/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.scss
 
b/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.scss
index 98b6560038..0638da0734 100644
--- 
a/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.scss
+++ 
b/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.scss
@@ -21,5 +21,6 @@
 }
 
 .dashboard-outer {
-    margin: 5px;
+    width: 100%;
+    margin: 5px auto;
 }
diff --git 
a/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.ts
 
b/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.ts
index cadf536fb4..7e735f7747 100644
--- 
a/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.ts
+++ 
b/ui/src/app/dashboard-shared/components/chart-view/grid-view/dashboard-grid-view.component.ts
@@ -48,6 +48,12 @@ export class DashboardGridViewComponent
     extends AbstractChartViewDirective
     implements OnInit, AfterViewInit, OnChanges
 {
+    private readonly defaultGridCellHeightPx = 90;
+    private readonly minGridCellHeightPx = 40;
+    private readonly maxGridCellHeightPx = 200;
+
+    readonly maxGridWidthPx = 1440;
+
     @Input()
     kioskMode = false;
 
@@ -70,7 +76,7 @@ export class DashboardGridViewComponent
             minRow: 5,
             column: this.dashboard.gridColumns,
             margin: 2,
-            cellHeight: 'initial',
+            cellHeight: this.getGridCellHeight(),
             disableResize: !this.editMode,
             disableDrag: !this.editMode,
             float: true,
@@ -104,6 +110,21 @@ export class DashboardGridViewComponent
 
     onWidgetsAvailable(): void {}
 
+    private getGridCellHeight(): number {
+        const configuredValue = Number(
+            this.dashboard?.dashboardGeneralSettings?.gridRowHeightPx,
+        );
+
+        if (Number.isNaN(configuredValue)) {
+            return this.defaultGridCellHeightPx;
+        }
+
+        return Math.min(
+            this.maxGridCellHeightPx,
+            Math.max(this.minGridCellHeightPx, configuredValue),
+        );
+    }
+
     isGridView(): boolean {
         return true;
     }
diff --git 
a/ui/src/app/dashboard-shared/components/chart-view/slide-view/dashboard-slide-view.component.html
 
b/ui/src/app/dashboard-shared/components/chart-view/slide-view/dashboard-slide-view.component.html
index 5cfc09ad9b..efdb497400 100644
--- 
a/ui/src/app/dashboard-shared/components/chart-view/slide-view/dashboard-slide-view.component.html
+++ 
b/ui/src/app/dashboard-shared/components/chart-view/slide-view/dashboard-slide-view.component.html
@@ -69,6 +69,10 @@
                         [editMode]="editMode"
                         [gridMode]="false"
                         [widgetIndex]="i"
+                        [dashboardChartOverrides]="
+                            dashboard.dashboardGeneralSettings
+                                ?.chartOverrides || {}
+                        "
                     ></sp-chart-container>
                 }
             </div>
diff --git 
a/ui/src/app/dashboard/components/overview/dashboard-overview.component.ts 
b/ui/src/app/dashboard/components/overview/dashboard-overview.component.ts
index 9c54b4f110..dba612fa9a 100644
--- a/ui/src/app/dashboard/components/overview/dashboard-overview.component.ts
+++ b/ui/src/app/dashboard/components/overview/dashboard-overview.component.ts
@@ -81,7 +81,11 @@ export class DashboardOverviewComponent implements OnInit, 
OnDestroy {
 
     openNewDashboardDialog() {
         const dataViewDashboard: Dashboard = {
-            dashboardGeneralSettings: {},
+            dashboardGeneralSettings: {
+                chartOverrides: {
+                    hideToolbox: false,
+                },
+            },
             widgets: [],
             name: '',
             dashboardLiveSettings: {
@@ -95,6 +99,7 @@ export class DashboardOverviewComponent implements OnInit, 
OnDestroy {
             },
             gridColumns: 12,
         };
+        dataViewDashboard.dashboardGeneralSettings.gridRowHeightPx = 90;
 
         this.openDashboardModificationDialog(true, dataViewDashboard);
     }
diff --git a/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts 
b/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts
index 1ac4b28e90..f645eaa59e 100644
--- a/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts
+++ b/ui/src/app/dashboard/components/panel/dashboard-panel.component.ts
@@ -165,7 +165,7 @@ export class DashboardPanelComponent
         dashboardItem.w = 3;
         dashboardItem.h = 4;
         dashboardItem.x = 0;
-        dashboardItem.y = 0;
+        dashboardItem.y = this.getNextWidgetY();
         dashboardItem.dataViewElementId = dataViewElementId;
         this.dashboard.widgets.push(dashboardItem);
         setTimeout(() => {
@@ -177,6 +177,18 @@ export class DashboardPanelComponent
         });
     }
 
+    private getNextWidgetY(): number {
+        if (!this.dashboard?.widgets?.length) {
+            return 0;
+        }
+
+        return this.dashboard.widgets.reduce((maxY, widget) => {
+            const currentY = widget.y ?? 0;
+            const currentHeight = widget.h ?? widget.rows ?? 1;
+            return Math.max(maxY, currentY + currentHeight);
+        }, 0);
+    }
+
     setShouldShowConfirm(): boolean {
         const originalTimeSettings = this.originalDashboard
             .dashboardTimeSettings as TimeSettings;
diff --git 
a/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.html
 
b/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.html
index e2fccabfd5..747ee94bd1 100644
--- 
a/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.html
+++ 
b/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.html
@@ -91,6 +91,25 @@
                         }}</mat-error>
                     </mat-form-field>
                 </sp-form-field>
+                <sp-form-field
+                    [level]="3"
+                    [label]="'Grid row height (px)' | translate"
+                >
+                    <mat-form-field floatLabel="auto" color="accent">
+                        <input
+                            matInput
+                            type="number"
+                            min="40"
+                            max="200"
+                            step="5"
+                            data-cy="grid-row-height"
+                            [(ngModel)]="
+                                dashboard.dashboardGeneralSettings
+                                    .gridRowHeightPx
+                            "
+                        />
+                    </mat-form-field>
+                </sp-form-field>
                 <div class="mt-10" fxLayout="column">
                     <mat-checkbox
                         [(ngModel)]="
@@ -101,6 +120,16 @@
                                 | translate
                         }}
                     </mat-checkbox>
+                    <mat-checkbox
+                        [(ngModel)]="
+                            dashboard.dashboardGeneralSettings.chartOverrides
+                                .hideToolbox
+                        "
+                    >
+                        {{
+                            'Hide chart toolboxes in this dashboard' | 
translate
+                        }}
+                    </mat-checkbox>
                 </div>
 
                 @if (isAssetAdmin) {
diff --git 
a/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.ts
 
b/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.ts
index 6c70ec2954..8b27be9d00 100644
--- 
a/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.ts
+++ 
b/ui/src/app/dashboard/dialogs/edit-dashboard/edit-dashboard-dialog.component.ts
@@ -109,6 +109,19 @@ export class EditDashboardDialogComponent implements 
OnInit {
         ) {
             this.dashboard.dashboardGeneralSettings.globalTimeEnabled = true;
         }
+        this.dashboard.dashboardGeneralSettings.chartOverrides ??= {};
+        if (
+            this.dashboard.dashboardGeneralSettings.chartOverrides
+                .hideToolbox === undefined
+        ) {
+            this.dashboard.dashboardGeneralSettings.chartOverrides.hideToolbox 
= false;
+        }
+        if (
+            this.dashboard.dashboardGeneralSettings.gridRowHeightPx ===
+            undefined
+        ) {
+            this.dashboard.dashboardGeneralSettings.gridRowHeightPx = 90;
+        }
         if (!this.createMode) {
             this.addToAssets = true;
         }

Reply via email to