added orientation option

This commit is contained in:
Peter Holmberg
2019-02-20 15:07:30 +01:00
parent 4c5c162045
commit 2fc136e615
6 changed files with 187 additions and 78 deletions
@@ -21,6 +21,7 @@ const setup = (propOverrides?: object) => {
value: 25, value: 25,
decimals: 0, decimals: 0,
theme: getTheme(), theme: getTheme(),
orientation: 'horizontal',
}; };
Object.assign(props, propOverrides); Object.assign(props, propOverrides);
@@ -17,6 +17,7 @@ export interface Props extends Themeable {
value: TimeSeriesValue; value: TimeSeriesValue;
maxValue: number; maxValue: number;
minValue: number; minValue: number;
orientation: string;
prefix?: string; prefix?: string;
suffix?: string; suffix?: string;
decimals?: number; decimals?: number;
@@ -28,6 +29,7 @@ export class BarGauge extends PureComponent<Props> {
minValue: 0, minValue: 0,
value: 100, value: 100,
unit: 'none', unit: 'none',
orientation: 'horizontal',
thresholds: [], thresholds: [],
valueMappings: [], valueMappings: [],
}; };
@@ -75,29 +77,19 @@ export class BarGauge extends PureComponent<Props> {
}; };
} }
render() { renderHorizontalBar(valueStyles: React.CSSProperties, valueFormatted: string, barStyles: React.CSSProperties) {
const { height, width, maxValue, minValue, unit, decimals } = this.props; const { height, width } = this.props;
const numericValue = this.getNumericValue(); const containerStyles = {
const barMaxHeight = height * 0.8; // 20% for value & name
const valuePercent = numericValue / (maxValue - minValue);
const barHeight = Math.max(valuePercent * barMaxHeight, 0);
const formatFunc = getValueFormat(unit);
const valueFormatted = formatFunc(numericValue, decimals);
const colors = this.getColors();
const containerStyles = { width: `${width}px`, height: `${height}px` };
const valueStyles = this.getValueStyles(valueFormatted, colors.value);
const barStyles = {
height: `${barHeight}px`,
width: `${width}px`, width: `${width}px`,
backgroundColor: colors.bar, height: `${height}px`,
borderTop: `1px solid ${colors.border}`, display: 'flex',
}; flexDirection: 'column',
justifyContent: 'flex-end',
} as React.CSSProperties;
return ( return (
<div className="bar-gauge" style={containerStyles}> <div style={containerStyles}>
<div className="bar-gauge__value" style={valueStyles}> <div className="bar-gauge__value" style={valueStyles}>
{valueFormatted} {valueFormatted}
</div> </div>
@@ -105,6 +97,70 @@ export class BarGauge extends PureComponent<Props> {
</div> </div>
); );
} }
renderVerticalBar(valueFormatted: string, barStyles: React.CSSProperties) {
const { height, width } = this.props;
const colors = this.getColors();
const containerStyles = {
width: `${width}px`,
height: `${height}px`,
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
marginBottom: '8px',
} as React.CSSProperties;
const valueStyles = this.getValueStyles(valueFormatted, colors.value);
Object.assign(valueStyles, { marginLeft: '8px' });
return (
<div style={containerStyles}>
<div style={barStyles} />
<div className="bar-gauge__value" style={valueStyles}>
{valueFormatted}
</div>
</div>
);
}
render() {
const { height, width, maxValue, minValue, orientation, unit, decimals } = this.props;
const numericValue = this.getNumericValue();
const barMaxHeight = height * 0.8; // 20% for value & name
const barMaxWidth = width * 0.8;
const valuePercent = numericValue / (maxValue - minValue);
const barHeight = Math.max(valuePercent * barMaxHeight, 0);
const barWidth = Math.max(valuePercent * barMaxWidth, 0);
const formatFunc = getValueFormat(unit);
const valueFormatted = formatFunc(numericValue, decimals);
const colors = this.getColors();
const valueStyles = this.getValueStyles(valueFormatted, colors.value);
const vertical = orientation === 'vertical';
const horizontalBarStyles = {
height: `${barHeight}px`,
width: `${width}px`,
backgroundColor: colors.bar,
borderTop: `1px solid ${colors.border}`,
};
const verticalBarStyles = {
height: `${height}px`,
width: `${barWidth}px`,
backgroundColor: colors.bar,
borderRight: `1px solid ${colors.border}`,
};
const barStyles = vertical ? verticalBarStyles : horizontalBarStyles;
return vertical
? this.renderVerticalBar(valueFormatted, barStyles)
: this.renderHorizontalBar(valueStyles, valueFormatted, barStyles);
}
} }
interface BarColors { interface BarColors {
@@ -1,4 +1,4 @@
import { Component } from 'react'; import React, { PureComponent } from 'react';
import { TimeSeriesVMs } from '../../types'; import { TimeSeriesVMs } from '../../types';
interface RenderProps { interface RenderProps {
@@ -12,45 +12,55 @@ interface Props {
height: number; height: number;
width: number; width: number;
timeSeries: TimeSeriesVMs; timeSeries: TimeSeriesVMs;
orientation?: string;
} }
export class VizRepeater extends Component<Props> { export class VizRepeater extends PureComponent<Props> {
render() { render() {
const { children, height, timeSeries, width } = this.props; const { children, orientation, height, timeSeries, width } = this.props;
const singleStatWidth = 1 / timeSeries.length * 100; const vizContainerWidth = 1 / timeSeries.length * 100;
const singleStatHeight = 1 / timeSeries.length * 100; const vizContainerHeight = 1 / timeSeries.length * 100;
const repeatingGaugeWidth = Math.floor(width / timeSeries.length) - 10; // make Gauge slightly smaller than panel. const repeatingVizWidth = Math.floor(width / timeSeries.length) - 10; // make Gauge slightly smaller than panel.
const repeatingGaugeHeight = Math.floor(height / timeSeries.length) - 10; const repeatingVizHeight = Math.floor(height / timeSeries.length) - 10;
const horizontalPanels = { const horizontalVisualization = {
display: 'inline-block',
height: height, height: height,
width: `${singleStatWidth}%`, width: `${vizContainerWidth}%`,
}; };
const verticalPanels = { const verticalVisualization = {
display: 'block',
width: width, width: width,
height: `${singleStatHeight}%`, height: `${vizContainerHeight}%`,
}; };
const repeaterStyle = {
display: 'flex',
flexDirection: orientation === 'vertical' || height > width ? 'column' : 'row',
} as React.CSSProperties;
let vizContainerStyle = {}; let vizContainerStyle = {};
let vizWidth = width; let vizWidth = width;
let vizHeight = height; let vizHeight = height;
if (width > height) { if (orientation === 'horizontal' || width > height) {
vizContainerStyle = horizontalPanels; vizContainerStyle = horizontalVisualization;
vizWidth = repeatingGaugeWidth; vizWidth = repeatingVizWidth;
} else if (height > width) {
vizContainerStyle = verticalPanels;
vizHeight = repeatingGaugeHeight;
} }
return children({ if (orientation === 'vertical' || height > width) {
vizHeight, vizContainerStyle = verticalVisualization;
vizWidth, vizHeight = repeatingVizHeight;
vizContainerStyle, }
});
return (
<div style={repeaterStyle}>
{children({
vizHeight,
vizWidth,
vizContainerStyle,
})}
</div>
);
} }
} }
@@ -2,59 +2,78 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
// Services & Utils // Services & Utils
import { processTimeSeries, ThemeContext } from '@grafana/ui'; import { processTimeSeries } from '@grafana/ui';
import { config } from 'app/core/config';
// Components // Components
import { BarGauge } from '@grafana/ui'; import { BarGauge, VizRepeater } from '@grafana/ui';
// Types // Types
import { BarGaugeOptions } from './types'; import { BarGaugeOptions } from './types';
import { PanelProps, NullValueMode, TimeSeriesValue } from '@grafana/ui/src/types'; import { PanelProps, NullValueMode } from '@grafana/ui/src/types';
interface Props extends PanelProps<BarGaugeOptions> {} interface Props extends PanelProps<BarGaugeOptions> {}
export class BarGaugePanel extends PureComponent<Props> { export class BarGaugePanel extends PureComponent<Props> {
render() { renderBarGauge(value, width, height) {
const { panelData, width, height, onInterpolate, options } = this.props; const { onInterpolate, options } = this.props;
const { valueOptions } = options; const { valueOptions } = options;
const prefix = onInterpolate(valueOptions.prefix); const prefix = onInterpolate(valueOptions.prefix);
const suffix = onInterpolate(valueOptions.suffix); const suffix = onInterpolate(valueOptions.suffix);
let value: TimeSeriesValue; return (
<BarGauge
value={value}
width={width}
height={height}
prefix={prefix}
suffix={suffix}
orientation={options.orientation}
unit={valueOptions.unit}
decimals={valueOptions.decimals}
thresholds={options.thresholds}
valueMappings={options.valueMappings}
theme={config.theme}
/>
);
}
render() {
const { panelData, options, width, height } = this.props;
const { stat } = options.valueOptions;
if (panelData.timeSeries) { if (panelData.timeSeries) {
const vmSeries = processTimeSeries({ const timeSeries = processTimeSeries({
timeSeries: panelData.timeSeries, timeSeries: panelData.timeSeries,
nullValueMode: NullValueMode.Null, nullValueMode: NullValueMode.Null,
}); });
if (vmSeries[0]) { if (timeSeries.length > 1) {
value = vmSeries[0].stats[valueOptions.stat]; return (
} else { <VizRepeater height={height} width={width} timeSeries={timeSeries} orientation={options.orientation}>
value = null; {({ vizHeight, vizWidth, vizContainerStyle }) => {
return timeSeries.map((series, index) => {
const value = stat !== 'name' ? series.stats[stat] : series.label;
return (
<div key={index} style={vizContainerStyle}>
{this.renderBarGauge(value, vizWidth, vizHeight)}
</div>
);
});
}}
</VizRepeater>
);
} else if (timeSeries.length > 0) {
const value = timeSeries[0].stats[options.valueOptions.stat];
return this.renderBarGauge(value, width, height);
} }
} else if (panelData.tableData) { } else if (panelData.tableData) {
value = panelData.tableData.rows[0].find(prop => prop > 0); const value = panelData.tableData.rows[0].find(prop => prop > 0);
return this.renderBarGauge(value, width, height);
} }
return ( return <div className="singlestat-panel">No time series data available</div>;
<ThemeContext.Consumer>
{theme => (
<BarGauge
value={value}
width={width}
height={height}
prefix={prefix}
suffix={suffix}
unit={valueOptions.unit}
decimals={valueOptions.decimals}
thresholds={options.thresholds}
valueMappings={options.valueMappings}
theme={theme}
/>
)}
</ThemeContext.Consumer>
);
} }
} }
@@ -6,8 +6,8 @@ import { SingleStatValueEditor } from 'app/plugins/panel/gauge/SingleStatValueEd
import { ThresholdsEditor, ValueMappingsEditor, PanelOptionsGrid, PanelOptionsGroup, FormField } from '@grafana/ui'; import { ThresholdsEditor, ValueMappingsEditor, PanelOptionsGrid, PanelOptionsGroup, FormField } from '@grafana/ui';
// Types // Types
import { PanelEditorProps, Threshold, ValueMapping } from '@grafana/ui'; import { FormLabel, PanelEditorProps, Threshold, Select, ValueMapping } from '@grafana/ui';
import { BarGaugeOptions } from './types'; import { BarGaugeOptions, orientationOptions } from './types';
import { SingleStatValueOptions } from '../gauge/types'; import { SingleStatValueOptions } from '../gauge/types';
export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> { export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGaugeOptions>> {
@@ -31,6 +31,7 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
onMinValueChange = ({ target }) => this.props.onChange({ ...this.props.options, minValue: target.value }); onMinValueChange = ({ target }) => this.props.onChange({ ...this.props.options, minValue: target.value });
onMaxValueChange = ({ target }) => this.props.onChange({ ...this.props.options, maxValue: target.value }); onMaxValueChange = ({ target }) => this.props.onChange({ ...this.props.options, maxValue: target.value });
onOrientationChange = ({ value }) => this.props.onChange({ ...this.props.options, orientation: value });
render() { render() {
const { options } = this.props; const { options } = this.props;
@@ -42,6 +43,16 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
<PanelOptionsGroup title="Gauge"> <PanelOptionsGroup title="Gauge">
<FormField label="Min value" labelWidth={8} onChange={this.onMinValueChange} value={options.minValue} /> <FormField label="Min value" labelWidth={8} onChange={this.onMinValueChange} value={options.minValue} />
<FormField label="Max value" labelWidth={8} onChange={this.onMaxValueChange} value={options.maxValue} /> <FormField label="Max value" labelWidth={8} onChange={this.onMaxValueChange} value={options.maxValue} />
<div className="form-field">
<FormLabel width={8}>Orientation</FormLabel>
<Select
width={12}
options={orientationOptions}
defaultValue={orientationOptions[0]}
onChange={this.onOrientationChange}
value={orientationOptions.find(item => item.value === options.orientation)}
/>
</div>
</PanelOptionsGroup> </PanelOptionsGroup>
<ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} /> <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
</PanelOptionsGrid> </PanelOptionsGrid>
+14 -2
View File
@@ -1,17 +1,29 @@
import { Threshold, ValueMapping } from '@grafana/ui'; import { Threshold, SelectOptionItem, ValueMapping } from '@grafana/ui';
import { SingleStatValueOptions } from '../gauge/types'; import { SingleStatValueOptions } from '../gauge/types';
export interface BarGaugeOptions { export interface BarGaugeOptions {
minValue: number; minValue: number;
maxValue: number; maxValue: number;
orientation: string;
valueOptions: SingleStatValueOptions; valueOptions: SingleStatValueOptions;
valueMappings: ValueMapping[]; valueMappings: ValueMapping[];
thresholds: Threshold[]; thresholds: Threshold[];
} }
export enum OrientationModes {
Vertical = 'vertical',
Horizontal = 'horizontal',
}
export const orientationOptions: SelectOptionItem[] = [
{ value: OrientationModes.Horizontal, label: 'Horizontal' },
{ value: OrientationModes.Vertical, label: 'Vertical' },
];
export const defaults: BarGaugeOptions = { export const defaults: BarGaugeOptions = {
minValue: 0, minValue: 0,
maxValue: 100, maxValue: 100,
orientation: OrientationModes.Horizontal,
valueOptions: { valueOptions: {
unit: 'none', unit: 'none',
stat: 'avg', stat: 'avg',
@@ -19,6 +31,6 @@ export const defaults: BarGaugeOptions = {
suffix: '', suffix: '',
decimals: null, decimals: null,
}, },
thresholds: [{ index: 1, value: 80, color: 'red' }, { index: 0, value: -Infinity, color: 'green' }], thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
valueMappings: [], valueMappings: [],
}; };