Commit 65942efb by Torkel Ödegaard Committed by GitHub

Panels: Add support for panels with no padding (#20012)

* Panels: Added support to set panel padding to zero

* WIP: fullChromeControl work

* Tweaks to header position

* Reverted some overlay mechanic and now back to no title only

* Fixed test

* Fixed transparent flag

* Added show title

* Added font weight to value

* Reverted back to no padding option

* Fixed issue with border and width and height
parent a4a97152
......@@ -20,8 +20,8 @@ datasources:
url: http://localhost:3011
- name: gdev-testdata
type: testdata
isDefault: true
type: testdata
- name: gdev-influxdb
type: influxdb
......
......@@ -74,6 +74,7 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta>
defaults?: TOptions;
onPanelMigration?: PanelMigrationHandler<TOptions>;
onPanelTypeChanged?: PanelTypeChangedHandler<TOptions>;
noPadding?: boolean;
/**
* Legacy angular ctrl. If this exists it will be used instead of the panel
......@@ -95,6 +96,11 @@ export class PanelPlugin<TOptions = any> extends GrafanaPlugin<PanelPluginMeta>
return this;
}
setNoPadding() {
this.noPadding = true;
return this;
}
/**
* This function is called before the panel first loads if
* the current version is different than the version that was saved.
......
......@@ -160,7 +160,7 @@ export function calculateLayout(props: Props): LayoutResult {
export function getTitleStyles(layout: LayoutResult) {
const styles: CSSProperties = {
fontSize: `${layout.titleFontSize}px`,
textShadow: '#333 1px 1px 5px',
textShadow: '#333 0px 0px 1px',
color: '#EEE',
};
......@@ -175,7 +175,8 @@ export function getValueStyles(layout: LayoutResult) {
const styles: CSSProperties = {
fontSize: `${layout.valueFontSize}px`,
color: '#EEE',
textShadow: '#333 1px 1px 5px',
textShadow: '#333 0px 0px 1px',
fontWeight: 500,
lineHeight: LINE_HEIGHT,
};
......@@ -347,7 +348,7 @@ function renderLineGeom(layout: LayoutResult) {
const lineStyle: any = {
stroke: '#CCC',
lineWidth: 2,
shadowBlur: 15,
shadowBlur: 10,
shadowColor: '#444',
shadowOffsetY: 7,
};
......@@ -359,7 +360,7 @@ function renderVibrant2Geom(layout: LayoutResult) {
const lineStyle: any = {
stroke: '#CCC',
lineWidth: 2,
shadowBlur: 15,
shadowBlur: 10,
shadowColor: '#444',
shadowOffsetY: -5,
};
......
import React, { PureComponent } from 'react';
import React, { PureComponent, CSSProperties } from 'react';
import { VizOrientation } from '@grafana/data';
interface Props<V, D> {
......@@ -87,12 +87,13 @@ export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>>
repeaterStyle.flexDirection = 'column';
itemStyles.marginBottom = `${itemSpacing}px`;
vizWidth = width;
vizHeight = height / values.length - itemSpacing;
vizHeight = height / values.length - itemSpacing + itemSpacing / values.length;
} else {
repeaterStyle.flexDirection = 'row';
repeaterStyle.justifyContent = 'space-between';
itemStyles.marginRight = `${itemSpacing}px`;
vizHeight = height;
vizWidth = width / values.length - itemSpacing;
vizWidth = width / values.length - itemSpacing + itemSpacing / values.length;
}
itemStyles.width = `${vizWidth}px`;
......@@ -103,7 +104,7 @@ export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>>
<div style={repeaterStyle}>
{values.map((value, index) => {
return (
<div key={index} style={itemStyles}>
<div key={index} style={getItemStylesForIndex(itemStyles, index, values.length)}>
{renderValue(value, vizWidth, vizHeight, dims)}
</div>
);
......@@ -112,3 +113,17 @@ export class VizRepeater<V, D = {}> extends PureComponent<Props<V, D>, State<V>>
);
}
}
/*
* Removes any padding on the last item
*/
function getItemStylesForIndex(itemStyles: CSSProperties, index: number, length: number): CSSProperties {
if (index === length - 1) {
return {
...itemStyles,
marginRight: 0,
marginBottom: 0,
};
}
return itemStyles;
}
......@@ -7,13 +7,14 @@ import { PanelHeader } from './PanelHeader/PanelHeader';
import { ErrorBoundary } from '@grafana/ui';
// Utils & Services
import { getTimeSrv, TimeSrv } from '../services/TimeSrv';
import { applyPanelTimeOverrides, calculateInnerPanelHeight } from 'app/features/dashboard/utils/panel';
import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
import { profiler } from 'app/core/profiler';
import { getProcessedDataFrames } from '../state/runRequest';
import templateSrv from 'app/features/templating/template_srv';
import config from 'app/core/config';
// Types
import { DashboardModel, PanelModel } from '../state';
import { PANEL_BORDER } from 'app/core/constants';
import {
LoadingState,
ScopedVars,
......@@ -260,13 +261,22 @@ export class PanelChrome extends PureComponent<Props, State> {
}
const PanelComponent = plugin.panel;
const innerPanelHeight = calculateInnerPanelHeight(panel, height);
const timeRange = data.timeRange || this.timeSrv.timeRange();
const headerHeight = this.hasOverlayHeader() ? 0 : theme.panelHeaderHeight;
const chromePadding = plugin.noPadding ? 0 : theme.panelPadding;
const panelWidth = width - chromePadding * 2 - PANEL_BORDER;
const innerPanelHeight = height - headerHeight - chromePadding * 2 - PANEL_BORDER;
const panelContentClassNames = classNames({
'panel-content': true,
'panel-content--no-padding': plugin.noPadding,
});
return (
<>
{loading === LoadingState.Loading && this.renderLoadingState()}
<div className="panel-content">
<div className={panelContentClassNames}>
<PanelComponent
id={panel.id}
data={data}
......@@ -274,7 +284,7 @@ export class PanelChrome extends PureComponent<Props, State> {
timeZone={this.props.dashboard.getTimezone()}
options={panel.getOptions()}
transparent={panel.transparent}
width={width - theme.panelPadding * 2}
width={panelWidth}
height={innerPanelHeight}
renderCounter={renderCounter}
replaceVariables={this.replaceVariables}
......@@ -294,6 +304,23 @@ export class PanelChrome extends PureComponent<Props, State> {
);
}
hasOverlayHeader() {
const { panel } = this.props;
const { errorMessage, data } = this.state;
// always show normal header if we have an error message
if (errorMessage) {
return false;
}
// always show normal header if we have time override
if (data.request && data.request.timeInfo) {
return false;
}
return !panel.hasTitle();
}
render() {
const { dashboard, panel, isFullscreen, width, height } = this.props;
const { errorMessage, data } = this.state;
......@@ -302,8 +329,8 @@ export class PanelChrome extends PureComponent<Props, State> {
const containerClassNames = classNames({
'panel-container': true,
'panel-container--absolute': true,
'panel-container--no-title': !panel.hasTitle(),
'panel-transparent': transparent,
'panel-container--transparent': transparent,
'panel-container--no-title': this.hasOverlayHeader(),
});
return (
......
......@@ -103,13 +103,11 @@ export class PanelHeader extends Component<Props, State> {
<span className="panel-title-text">
{title} <span className="fa fa-caret-down panel-menu-toggle" />
</span>
{this.state.panelMenuOpen && (
<ClickOutsideWrapper onClick={this.closeMenu}>
<PanelHeaderMenu panel={panel} dashboard={dashboard} />
</ClickOutsideWrapper>
)}
{timeInfo && (
<span className="panel-time-info">
<i className="fa fa-clock-o" /> {timeInfo}
......
import { TimeRange } from '@grafana/data';
import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel';
import { applyPanelTimeOverrides, calculateInnerPanelHeight } from 'app/features/dashboard/utils/panel';
import { advanceTo, clear } from 'jest-date-mock';
import { dateTime, DateTime } from '@grafana/data';
import { PanelModel } from '../state';
import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks';
const dashboardTimeRange: TimeRange = {
from: dateTime([2019, 1, 11, 12, 0]),
......@@ -71,4 +73,19 @@ describe('applyPanelTimeOverrides', () => {
expect((overrides.timeRange.raw.from as DateTime).toISOString()).toEqual(expectedFromDate.toISOString());
expect((overrides.timeRange.raw.to as DateTime).toISOString()).toEqual(expectedToDate.toISOString());
});
it('Calculate panel height', () => {
const panelModel = new PanelModel({});
const height = calculateInnerPanelHeight(panelModel, 100);
expect(height).toBe(82);
});
it('Calculate panel height with panel plugin zeroChromePadding', () => {
const panelModel = new PanelModel({});
panelModel.pluginLoaded(getPanelPlugin({ id: 'table' }, null, null).setNoPadding());
const height = calculateInnerPanelHeight(panelModel, 100);
expect(height).toBe(98);
});
});
......@@ -173,10 +173,7 @@ export function getResolution(panel: PanelModel): number {
}
export function calculateInnerPanelHeight(panel: PanelModel, containerHeight: number): number {
return (
containerHeight -
(panel.hasTitle() ? config.theme.panelHeaderHeight : 0) -
config.theme.panelPadding * 2 -
PANEL_BORDER
);
const chromePadding = panel.plugin && panel.plugin.noPadding ? 0 : config.theme.panelPadding * 2;
const headerHeight = panel.hasTitle() ? config.theme.panelHeaderHeight : 0;
return containerHeight - headerHeight - chromePadding - PANEL_BORDER;
}
......@@ -15,7 +15,7 @@ import {
import { Threshold, ValueMapping, FieldConfig, DataLink, PanelEditorProps, FieldDisplayOptions } from '@grafana/data';
import { SingleStatOptions, SparklineOptions, displayModes, colorModes } from './types';
import { SingleStatOptions, SparklineOptions, displayModes } from './types';
import { SparklineEditor } from './SparklineEditor';
import {
getDataLinksVariableSuggestions,
......@@ -52,7 +52,6 @@ export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatO
});
onDisplayModeChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, displayMode: value });
onColorModeChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, colorMode: value });
onDefaultsChange = (field: FieldConfig) => {
this.onDisplayOptionsChanged({
......@@ -91,21 +90,16 @@ export class SingleStatEditor extends PureComponent<PanelEditorProps<SingleStatO
value={displayModes.find(item => item.value === options.displayMode)}
/>
</div>
<div className="form-field">
<FormLabel width={8}>Color by</FormLabel>
<Select
width={12}
options={colorModes}
defaultValue={colorModes[0]}
onChange={this.onColorModeChange}
value={colorModes.find(item => item.value === options.colorMode)}
/>
</div>
<SparklineEditor options={options.sparkline} onChange={this.onSparklineChanged} />
</PanelOptionsGroup>
<PanelOptionsGroup title="Field (default)">
<FieldPropertiesEditor showMinMax={true} onChange={this.onDefaultsChange} value={defaults} />
<PanelOptionsGroup title="Field">
<FieldPropertiesEditor
showMinMax={true}
onChange={this.onDefaultsChange}
value={defaults}
showTitle={true}
/>
</PanelOptionsGroup>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={defaults.thresholds} />
......
......@@ -7,5 +7,6 @@ import { SingleStatEditor } from './SingleStatEditor';
export const plugin = new PanelPlugin<SingleStatOptions>(SingleStatPanel)
.setDefaults(defaults)
.setEditor(SingleStatEditor)
.setNoPadding()
.setPanelChangeHandler(sharedSingleStatPanelChangedHandler)
.setMigrationHandler(sharedSingleStatMigrationHandler);
......@@ -8,27 +8,15 @@ export interface SparklineOptions {
// Structure copied from angular
export interface SingleStatOptions extends SingleStatBaseOptions {
sparkline: SparklineOptions;
colorMode: ColorMode;
displayMode: SingleStatDisplayMode;
}
export const displayModes: Array<SelectableValue<SingleStatDisplayMode>> = [
{ value: SingleStatDisplayMode.Classic, label: 'Classic' },
{ value: SingleStatDisplayMode.Classic2, label: 'Classic 2' },
{ value: SingleStatDisplayMode.Vibrant, label: 'Vibrant' },
{ value: SingleStatDisplayMode.Vibrant2, label: 'Vibrant 2' },
];
export enum ColorMode {
Thresholds,
Series,
}
export const colorModes: Array<SelectableValue<ColorMode>> = [
{ value: ColorMode.Thresholds, label: 'Thresholds' },
{ value: ColorMode.Series, label: 'Series' },
];
export const standardFieldDisplayOptions: FieldDisplayOptions = {
values: false,
calcs: [ReducerID.mean],
......@@ -48,7 +36,6 @@ export const defaults: SingleStatOptions = {
sparkline: {
show: true,
},
colorMode: ColorMode.Thresholds,
displayMode: SingleStatDisplayMode.Vibrant,
fieldOptions: standardFieldDisplayOptions,
orientation: VizOrientation.Auto,
......
......@@ -55,8 +55,8 @@ div.flot-text {
height: 100%;
width: 100%;
&.panel-transparent {
background-color: transparent;
&--transparent {
background-color: $page-bg;
border: none;
}
......@@ -82,8 +82,11 @@ div.flot-text {
height: calc(100% - #{$panel-header-height});
width: 100%;
position: relative;
// Fixes scrolling on mobile devices
overflow: auto;
&--no-padding {
padding: 0;
}
}
// For larger screens, set back to hidden to avoid double scroll bars
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment