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;
}