This is an automated email from the ASF dual-hosted git repository. riemer pushed a commit to branch improve-dashboard-appearance in repository https://gitbox.apache.org/repos/asf/streampipes.git
commit 40b2391c1bf6173cc606e031bdfb2c9867d8b305 Author: Dominik Riemer <[email protected]> AuthorDate: Sun Mar 22 22:23:46 2026 +0100 feat: Improve dashboard appearance --- .../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; }
