Commit 392819c5 by Torkel Ödegaard Committed by GitHub

New bar gauge style: Unfilled (#21201)

* BarGauge: Added unfilled option

* Fixed white theme and added 2 unit tests

* Added another demo dashboard

* Fixed dev env dashboards
parent 587e4009
...@@ -106,6 +106,20 @@ describe('BarGauge', () => { ...@@ -106,6 +106,20 @@ describe('BarGauge', () => {
}); });
const styles = getBasicAndGradientStyles(props); const styles = getBasicAndGradientStyles(props);
expect(styles.bar.height).toBe('249px'); expect(styles.bar.height).toBe('249px');
expect(styles.emptyBar.bottom).toBe('-3px');
});
});
describe('Horizontal bar', () => {
it('should stretch items', () => {
const props = getProps({
height: 300,
value: getValue(100, 'ServerA'),
orientation: VizOrientation.Horizontal,
});
const styles = getBasicAndGradientStyles(props);
expect(styles.wrapper.alignItems).toBe('stretch');
expect(styles.emptyBar.left).toBe('-3px');
}); });
}); });
......
...@@ -42,6 +42,7 @@ export interface Props extends Themeable { ...@@ -42,6 +42,7 @@ export interface Props extends Themeable {
displayMode: 'basic' | 'lcd' | 'gradient'; displayMode: 'basic' | 'lcd' | 'gradient';
onClick?: React.MouseEventHandler<HTMLElement>; onClick?: React.MouseEventHandler<HTMLElement>;
className?: string; className?: string;
showUnfilled?: boolean;
alignmentFactors?: DisplayValueAlignmentFactors; alignmentFactors?: DisplayValueAlignmentFactors;
} }
...@@ -57,6 +58,7 @@ export class BarGauge extends PureComponent<Props> { ...@@ -57,6 +58,7 @@ export class BarGauge extends PureComponent<Props> {
orientation: VizOrientation.Horizontal, orientation: VizOrientation.Horizontal,
thresholds: [], thresholds: [],
itemSpacing: 10, itemSpacing: 10,
showUnfilled: true,
}; };
render() { render() {
...@@ -92,13 +94,14 @@ export class BarGauge extends PureComponent<Props> { ...@@ -92,13 +94,14 @@ export class BarGauge extends PureComponent<Props> {
} }
renderBasicAndGradientBars(): ReactNode { renderBasicAndGradientBars(): ReactNode {
const { value } = this.props; const { value, showUnfilled } = this.props;
const styles = getBasicAndGradientStyles(this.props); const styles = getBasicAndGradientStyles(this.props);
return ( return (
<div style={styles.wrapper}> <div style={styles.wrapper}>
<FormattedValueDisplay className="bar-gauge__value" value={value} style={styles.value} /> <FormattedValueDisplay className="bar-gauge__value" value={value} style={styles.value} />
{showUnfilled && <div style={styles.emptyBar} />}
<div style={styles.bar} /> <div style={styles.bar} />
</div> </div>
); );
...@@ -260,7 +263,7 @@ function calculateTitleDimensions(props: Props): TitleDimensions { ...@@ -260,7 +263,7 @@ function calculateTitleDimensions(props: Props): TitleDimensions {
// if height above 40 put text to above bar // if height above 40 put text to above bar
if (height > 40) { if (height > 40) {
const maxTitleHeightRatio = 0.35; const maxTitleHeightRatio = 0.45;
const titleHeight = Math.max(Math.min(height * maxTitleHeightRatio, MAX_VALUE_HEIGHT), 17); const titleHeight = Math.max(Math.min(height * maxTitleHeightRatio, MAX_VALUE_HEIGHT), 17);
return { return {
...@@ -289,6 +292,7 @@ export function getTitleStyles(props: Props): { wrapper: CSSProperties; title: C ...@@ -289,6 +292,7 @@ export function getTitleStyles(props: Props): { wrapper: CSSProperties; title: C
const wrapperStyles: CSSProperties = { const wrapperStyles: CSSProperties = {
display: 'flex', display: 'flex',
overflow: 'hidden', overflow: 'hidden',
width: '100%',
}; };
const titleDim = calculateTitleDimensions(props); const titleDim = calculateTitleDimensions(props);
...@@ -327,6 +331,7 @@ export function getTitleStyles(props: Props): { wrapper: CSSProperties; title: C ...@@ -327,6 +331,7 @@ export function getTitleStyles(props: Props): { wrapper: CSSProperties; title: C
interface BasicAndGradientStyles { interface BasicAndGradientStyles {
wrapper: CSSProperties; wrapper: CSSProperties;
bar: CSSProperties; bar: CSSProperties;
emptyBar: CSSProperties;
value: CSSProperties; value: CSSProperties;
} }
...@@ -390,7 +395,7 @@ export function getValuePercent(value: number, minValue: number, maxValue: numbe ...@@ -390,7 +395,7 @@ export function getValuePercent(value: number, minValue: number, maxValue: numbe
* Only exported to for unit test * Only exported to for unit test
*/ */
export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles { export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles {
const { displayMode, maxValue, minValue, value, alignmentFactors, orientation } = props; const { displayMode, maxValue, minValue, value, alignmentFactors, orientation, theme } = props;
const { valueWidth, valueHeight, maxBarHeight, maxBarWidth } = calculateBarAndValueDimensions(props); const { valueWidth, valueHeight, maxBarHeight, maxBarWidth } = calculateBarAndValueDimensions(props);
const valuePercent = getValuePercent(value.numeric, minValue, maxValue); const valuePercent = getValuePercent(value.numeric, minValue, maxValue);
...@@ -402,10 +407,21 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles ...@@ -402,10 +407,21 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles
const isBasic = displayMode === 'basic'; const isBasic = displayMode === 'basic';
const wrapperStyles: CSSProperties = { const wrapperStyles: CSSProperties = {
display: 'flex', display: 'flex',
flexGrow: 1,
}; };
const barStyles: CSSProperties = { const barStyles: CSSProperties = {
borderRadius: '3px', borderRadius: '3px',
position: 'relative',
zIndex: 1,
};
const emptyBar: CSSProperties = {
background: `rgba(${theme.isDark ? '255,255,255' : '0,0,0'}, 0.07)`,
flexGrow: 1,
display: 'flex',
borderRadius: '3px',
position: 'relative',
}; };
if (isVertical(orientation)) { if (isVertical(orientation)) {
...@@ -419,11 +435,15 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles ...@@ -419,11 +435,15 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles
barStyles.height = `${barHeight}px`; barStyles.height = `${barHeight}px`;
barStyles.width = `${maxBarWidth}px`; barStyles.width = `${maxBarWidth}px`;
// adjust so that filled in bar is at the bottom
emptyBar.bottom = '-3px';
if (isBasic) { if (isBasic) {
// Basic styles // Basic styles
barStyles.background = `${tinycolor(valueColor) barStyles.background = `${tinycolor(valueColor)
.setAlpha(0.25) .setAlpha(0.35)
.toRgbString()}`; .toRgbString()}`;
barStyles.borderTop = `2px solid ${valueColor}`; barStyles.borderTop = `2px solid ${valueColor}`;
} else { } else {
// Gradient styles // Gradient styles
...@@ -435,16 +455,19 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles ...@@ -435,16 +455,19 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles
// Custom styles for horizontal orientation // Custom styles for horizontal orientation
wrapperStyles.flexDirection = 'row-reverse'; wrapperStyles.flexDirection = 'row-reverse';
wrapperStyles.justifyContent = 'flex-end'; wrapperStyles.justifyContent = 'flex-end';
wrapperStyles.alignItems = 'center'; wrapperStyles.alignItems = 'stretch';
barStyles.transition = 'width 1s'; barStyles.transition = 'width 1s';
barStyles.height = `${maxBarHeight}px`; barStyles.height = `${maxBarHeight}px`;
barStyles.width = `${barWidth}px`; barStyles.width = `${barWidth}px`;
// shift empty region back to fill gaps due to border radius
emptyBar.left = '-3px';
if (isBasic) { if (isBasic) {
// Basic styles // Basic styles
barStyles.background = `${tinycolor(valueColor) barStyles.background = `${tinycolor(valueColor)
.setAlpha(0.25) .setAlpha(0.35)
.toRgbString()}`; .toRgbString()}`;
barStyles.borderRight = `2px solid ${valueColor}`; barStyles.borderRight = `2px solid ${valueColor}`;
} else { } else {
...@@ -457,6 +480,7 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles ...@@ -457,6 +480,7 @@ export function getBasicAndGradientStyles(props: Props): BasicAndGradientStyles
wrapper: wrapperStyles, wrapper: wrapperStyles,
bar: barStyles, bar: barStyles,
value: valueStyles, value: valueStyles,
emptyBar,
}; };
} }
...@@ -523,18 +547,27 @@ function getValueStyles( ...@@ -523,18 +547,27 @@ function getValueStyles(
// how many pixels in wide can the text be? // how many pixels in wide can the text be?
let textWidth = width; let textWidth = width;
const formattedValueString = formattedValueToString(value);
if (isVertical(orientation)) { if (isVertical(orientation)) {
styles.fontSize = calculateFontSize(formattedValueString, textWidth, height, VALUE_LINE_HEIGHT);
styles.justifyContent = `center`; styles.justifyContent = `center`;
} else { } else {
styles.justifyContent = `flex-start`; styles.fontSize = calculateFontSize(
formattedValueString,
textWidth - VALUE_LEFT_PADDING * 2,
height,
VALUE_LINE_HEIGHT
);
styles.justifyContent = `flex-end`;
styles.paddingLeft = `${VALUE_LEFT_PADDING}px`; styles.paddingLeft = `${VALUE_LEFT_PADDING}px`;
styles.paddingRight = `${VALUE_LEFT_PADDING}px`;
// Need to remove the left padding from the text width constraints // Need to remove the left padding from the text width constraints
textWidth -= VALUE_LEFT_PADDING; textWidth -= VALUE_LEFT_PADDING;
}
const formattedValueString = formattedValueToString(value); // adjust width of title box
styles.fontSize = calculateFontSize(formattedValueString, textWidth, height, VALUE_LINE_HEIGHT); styles.width = measureText(formattedValueString, styles.fontSize).width + VALUE_LEFT_PADDING * 2;
}
return styles; return styles;
} }
...@@ -7,15 +7,17 @@ exports[`BarGauge Render with basic options should render 1`] = ` ...@@ -7,15 +7,17 @@ exports[`BarGauge Render with basic options should render 1`] = `
"display": "flex", "display": "flex",
"flexDirection": "column", "flexDirection": "column",
"overflow": "hidden", "overflow": "hidden",
"width": "100%",
} }
} }
> >
<div <div
style={ style={
Object { Object {
"alignItems": "center", "alignItems": "stretch",
"display": "flex", "display": "flex",
"flexDirection": "row-reverse", "flexDirection": "row-reverse",
"flexGrow": 1,
"justifyContent": "flex-end", "justifyContent": "flex-end",
} }
} }
...@@ -27,12 +29,13 @@ exports[`BarGauge Render with basic options should render 1`] = ` ...@@ -27,12 +29,13 @@ exports[`BarGauge Render with basic options should render 1`] = `
"alignItems": "center", "alignItems": "center",
"color": "#73BF69", "color": "#73BF69",
"display": "flex", "display": "flex",
"fontSize": 175, "fontSize": 140,
"height": "300px", "height": "300px",
"justifyContent": "flex-start", "justifyContent": "flex-end",
"lineHeight": 1, "lineHeight": 1,
"paddingLeft": "10px", "paddingLeft": "10px",
"width": "60px", "paddingRight": "10px",
"width": 22,
} }
} }
value={ value={
...@@ -45,12 +48,26 @@ exports[`BarGauge Render with basic options should render 1`] = ` ...@@ -45,12 +48,26 @@ exports[`BarGauge Render with basic options should render 1`] = `
<div <div
style={ style={
Object { Object {
"background": "rgba(115, 191, 105, 0.25)", "background": "rgba(255,255,255, 0.07)",
"borderRadius": "3px",
"display": "flex",
"flexGrow": 1,
"left": "-3px",
"position": "relative",
}
}
/>
<div
style={
Object {
"background": "rgba(115, 191, 105, 0.35)",
"borderRadius": "3px", "borderRadius": "3px",
"borderRight": "2px solid #73BF69", "borderRight": "2px solid #73BF69",
"height": "300px", "height": "300px",
"position": "relative",
"transition": "width 1s", "transition": "width 1s",
"width": "60px", "width": "60px",
"zIndex": 1,
} }
} }
/> />
......
...@@ -43,6 +43,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> { ...@@ -43,6 +43,7 @@ export class BarGaugePanel extends PureComponent<PanelProps<BarGaugeOptions>> {
onClick={openMenu} onClick={openMenu}
className={targetClassName} className={targetClassName}
alignmentFactors={alignmentFactors} alignmentFactors={alignmentFactors}
showUnfilled={options.showUnfilled}
/> />
); );
}} }}
......
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
FormLabel, FormLabel,
Select, Select,
DataLinksEditor, DataLinksEditor,
Switch,
} from '@grafana/ui'; } from '@grafana/ui';
import { FieldDisplayOptions, FieldConfig, DataLink, PanelEditorProps } from '@grafana/data'; import { FieldDisplayOptions, FieldConfig, DataLink, PanelEditorProps } from '@grafana/data';
...@@ -54,6 +55,9 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge ...@@ -54,6 +55,9 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value }); onOrientationChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, orientation: value });
onDisplayModeChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, displayMode: value }); onDisplayModeChange = ({ value }: any) => this.props.onOptionsChange({ ...this.props.options, displayMode: value });
onToggleShowUnfilled = () => {
this.props.onOptionsChange({ ...this.props.options, showUnfilled: !this.props.options.showUnfilled });
};
onDataLinksChanged = (links: DataLink[]) => { onDataLinksChanged = (links: DataLink[]) => {
this.onDefaultsChange({ this.onDefaultsChange({
...@@ -96,6 +100,14 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge ...@@ -96,6 +100,14 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
value={displayModes.find(item => item.value === options.displayMode)} value={displayModes.find(item => item.value === options.displayMode)}
/> />
</div> </div>
{options.displayMode !== 'lcd' && (
<Switch
label="Unfilled"
labelClass={`width-${labelWidth}`}
checked={options.showUnfilled}
onChange={this.onToggleShowUnfilled}
/>
)}
</PanelOptionsGroup> </PanelOptionsGroup>
<PanelOptionsGroup title="Field"> <PanelOptionsGroup title="Field">
<FieldPropertiesEditor <FieldPropertiesEditor
......
...@@ -4,6 +4,7 @@ import { VizOrientation, SelectableValue } from '@grafana/data'; ...@@ -4,6 +4,7 @@ import { VizOrientation, SelectableValue } from '@grafana/data';
export interface BarGaugeOptions extends SingleStatBaseOptions { export interface BarGaugeOptions extends SingleStatBaseOptions {
displayMode: 'basic' | 'lcd' | 'gradient'; displayMode: 'basic' | 'lcd' | 'gradient';
showUnfilled: boolean;
} }
export const displayModes: Array<SelectableValue<string>> = [ export const displayModes: Array<SelectableValue<string>> = [
...@@ -16,4 +17,5 @@ export const defaults: BarGaugeOptions = { ...@@ -16,4 +17,5 @@ export const defaults: BarGaugeOptions = {
displayMode: 'lcd', displayMode: 'lcd',
orientation: VizOrientation.Horizontal, orientation: VizOrientation.Horizontal,
fieldOptions: standardGaugeFieldOptions, fieldOptions: standardGaugeFieldOptions,
showUnfilled: true,
}; };
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