(this.canvasElement = element)}
+ />
);
}
}
-
-export default Gauge;
diff --git a/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx b/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx
new file mode 100644
index 00000000000..767be6764ca
--- /dev/null
+++ b/packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx
@@ -0,0 +1,77 @@
+import React, { PureComponent } from 'react';
+import { SingleStatValueInfo, VizOrientation } from '../../types';
+
+interface RenderProps {
+ vizWidth: number;
+ vizHeight: number;
+ valueInfo: SingleStatValueInfo;
+}
+
+interface Props {
+ children: (renderProps: RenderProps) => JSX.Element | JSX.Element[];
+ height: number;
+ width: number;
+ values: SingleStatValueInfo[];
+ orientation: VizOrientation;
+}
+
+const SPACE_BETWEEN = 10;
+
+export class VizRepeater extends PureComponent
{
+ getOrientation(): VizOrientation {
+ const { orientation, width, height } = this.props;
+
+ if (orientation === VizOrientation.Auto) {
+ if (width > height) {
+ return VizOrientation.Vertical;
+ } else {
+ return VizOrientation.Horizontal;
+ }
+ }
+
+ return orientation;
+ }
+
+ render() {
+ const { children, height, values, width } = this.props;
+ const orientation = this.getOrientation();
+
+ const itemStyles: React.CSSProperties = {
+ display: 'flex',
+ };
+
+ const repeaterStyle: React.CSSProperties = {
+ display: 'flex',
+ };
+
+ let vizHeight = height;
+ let vizWidth = width;
+
+ if (orientation === VizOrientation.Horizontal) {
+ repeaterStyle.flexDirection = 'column';
+ itemStyles.margin = `${SPACE_BETWEEN / 2}px 0`;
+ vizWidth = width;
+ vizHeight = height / values.length - SPACE_BETWEEN;
+ } else {
+ repeaterStyle.flexDirection = 'row';
+ itemStyles.margin = `0 ${SPACE_BETWEEN / 2}px`;
+ vizHeight = height;
+ vizWidth = width / values.length - SPACE_BETWEEN;
+ }
+
+ itemStyles.width = `${vizWidth}px`;
+ itemStyles.height = `${vizHeight}px`;
+
+ return (
+
+ {values.map((valueInfo, index) => {
+ return (
+
+ {children({ vizHeight, vizWidth, valueInfo })}
+
+ );
+ })}
+
+ );
+ }
+}
diff --git a/packages/grafana-ui/src/components/index.scss b/packages/grafana-ui/src/components/index.scss
index ffd11febc29..91e5c88e33e 100644
--- a/packages/grafana-ui/src/components/index.scss
+++ b/packages/grafana-ui/src/components/index.scss
@@ -11,3 +11,4 @@
@import 'ValueMappingsEditor/ValueMappingsEditor';
@import 'EmptySearchResult/EmptySearchResult';
@import 'FormField/FormField';
+@import 'BarGauge/BarGauge';
diff --git a/packages/grafana-ui/src/components/index.ts b/packages/grafana-ui/src/components/index.ts
index b62fdff6498..b8c8d66cead 100644
--- a/packages/grafana-ui/src/components/index.ts
+++ b/packages/grafana-ui/src/components/index.ts
@@ -19,11 +19,15 @@ export { LoadingPlaceholder } from './LoadingPlaceholder/LoadingPlaceholder';
export { ColorPicker, SeriesColorPicker } from './ColorPicker/ColorPicker';
export { SeriesColorPickerPopover, SeriesColorPickerPopoverWithTheme } from './ColorPicker/SeriesColorPickerPopover';
export { ThresholdsEditor } from './ThresholdsEditor/ThresholdsEditor';
-export { Graph } from './Graph/Graph';
export { PanelOptionsGroup } from './PanelOptionsGroup/PanelOptionsGroup';
export { PanelOptionsGrid } from './PanelOptionsGrid/PanelOptionsGrid';
export { ValueMappingsEditor } from './ValueMappingsEditor/ValueMappingsEditor';
-export { Gauge } from './Gauge/Gauge';
export { Switch } from './Switch/Switch';
export { EmptySearchResult } from './EmptySearchResult/EmptySearchResult';
export { UnitPicker } from './UnitPicker/UnitPicker';
+
+// Visualizations
+export { Gauge } from './Gauge/Gauge';
+export { Graph } from './Graph/Graph';
+export { BarGauge } from './BarGauge/BarGauge';
+export { VizRepeater } from './VizRepeater/VizRepeater';
diff --git a/packages/grafana-ui/src/types/data.ts b/packages/grafana-ui/src/types/data.ts
index e7e0bdc2b4e..b36e23ae6c0 100644
--- a/packages/grafana-ui/src/types/data.ts
+++ b/packages/grafana-ui/src/types/data.ts
@@ -48,10 +48,7 @@ export enum NullValueMode {
}
/** View model projection of many time series */
-export interface TimeSeriesVMs {
- [index: number]: TimeSeriesVM;
- length: number;
-}
+export type TimeSeriesVMs = TimeSeriesVM[];
export interface Column {
text: string;
@@ -69,3 +66,12 @@ export interface TableData {
type: string;
columnMap: any;
}
+
+export type SingleStatValue = number | string | null;
+
+/*
+ * So we can add meta info like tags & series name
+ */
+export interface SingleStatValueInfo {
+ value: SingleStatValue;
+}
diff --git a/packages/grafana-ui/src/types/index.ts b/packages/grafana-ui/src/types/index.ts
index 263b1de4287..b09d88bab4d 100644
--- a/packages/grafana-ui/src/types/index.ts
+++ b/packages/grafana-ui/src/types/index.ts
@@ -4,3 +4,4 @@ export * from './panel';
export * from './plugin';
export * from './datasource';
export * from './theme';
+export * from './threshold';
diff --git a/packages/grafana-ui/src/types/panel.ts b/packages/grafana-ui/src/types/panel.ts
index 260ff78df76..889e4ab310e 100644
--- a/packages/grafana-ui/src/types/panel.ts
+++ b/packages/grafana-ui/src/types/panel.ts
@@ -26,10 +26,13 @@ export interface PanelEditorProps {
onOptionsChange: (options: T) => void;
}
+export type PreservePanelOptionsHandler = (pluginId: string, prevOptions: any) => Partial;
+
export class ReactPanelPlugin {
panel: ComponentClass>;
editor?: ComponentClass>;
defaults?: TOptions;
+ preserveOptions?: PreservePanelOptionsHandler;
constructor(panel: ComponentClass>) {
this.panel = panel;
@@ -42,6 +45,10 @@ export class ReactPanelPlugin {
setDefaults(defaults: TOptions) {
this.defaults = defaults;
}
+
+ setPreserveOptionsHandler(handler: PreservePanelOptionsHandler) {
+ this.preserveOptions = handler;
+ }
}
export interface PanelSize {
@@ -58,17 +65,6 @@ export interface PanelMenuItem {
subMenu?: PanelMenuItem[];
}
-export interface Threshold {
- index: number;
- value: number;
- color: string;
-}
-
-export enum BasicGaugeColor {
- Green = '#299c46',
- Red = '#d44a3a',
-}
-
export enum MappingType {
ValueToText = 1,
RangeToText = 2,
@@ -91,3 +87,9 @@ export interface RangeMap extends BaseMap {
from: string;
to: string;
}
+
+export enum VizOrientation {
+ Auto = 'auto',
+ Vertical = 'vertical',
+ Horizontal = 'horizontal',
+}
diff --git a/packages/grafana-ui/src/types/threshold.ts b/packages/grafana-ui/src/types/threshold.ts
new file mode 100644
index 00000000000..741ecd940bf
--- /dev/null
+++ b/packages/grafana-ui/src/types/threshold.ts
@@ -0,0 +1,5 @@
+export interface Threshold {
+ index: number;
+ value: number;
+ color: string;
+}
diff --git a/packages/grafana-ui/src/utils/index.ts b/packages/grafana-ui/src/utils/index.ts
index a2acc828752..a08b9ce1a89 100644
--- a/packages/grafana-ui/src/utils/index.ts
+++ b/packages/grafana-ui/src/utils/index.ts
@@ -1,7 +1,9 @@
export * from './processTimeSeries';
+export * from './singlestat';
export * from './valueFormats/valueFormats';
export * from './colors';
export * from './namedColorsPalette';
+export * from './thresholds';
export * from './string';
export * from './deprecationWarning';
export { getMappedValue } from './valueMappings';
diff --git a/packages/grafana-ui/src/utils/singlestat.ts b/packages/grafana-ui/src/utils/singlestat.ts
new file mode 100644
index 00000000000..5f5fbb8f247
--- /dev/null
+++ b/packages/grafana-ui/src/utils/singlestat.ts
@@ -0,0 +1,33 @@
+import { PanelData, NullValueMode, SingleStatValueInfo } from '../types';
+import { processTimeSeries } from './processTimeSeries';
+
+export interface SingleStatProcessingOptions {
+ panelData: PanelData;
+ stat: string;
+}
+
+//
+// This is a temporary thing, waiting for a better data model and maybe unification between time series & table data
+//
+export function processSingleStatPanelData(options: SingleStatProcessingOptions): SingleStatValueInfo[] {
+ const { panelData, stat } = options;
+
+ if (panelData.timeSeries) {
+ const timeSeries = processTimeSeries({
+ timeSeries: panelData.timeSeries,
+ nullValueMode: NullValueMode.Null,
+ });
+
+ return timeSeries.map((series, index) => {
+ const value = stat !== 'name' ? series.stats[stat] : series.label;
+
+ return {
+ value: value,
+ };
+ });
+ } else if (panelData.tableData) {
+ throw { message: 'Panel data not supported' };
+ }
+
+ return [];
+}
diff --git a/packages/grafana-ui/src/utils/thresholds.ts b/packages/grafana-ui/src/utils/thresholds.ts
new file mode 100644
index 00000000000..2fdca67194a
--- /dev/null
+++ b/packages/grafana-ui/src/utils/thresholds.ts
@@ -0,0 +1,23 @@
+import { Threshold } from '../types';
+
+export function getThresholdForValue(
+ thresholds: Threshold[],
+ value: number | null | string | undefined
+): Threshold | null {
+ if (thresholds.length === 1) {
+ return thresholds[0];
+ }
+
+ const atThreshold = thresholds.filter(threshold => (value as number) === threshold.value)[0];
+ if (atThreshold) {
+ return atThreshold;
+ }
+
+ const belowThreshold = thresholds.filter(threshold => (value as number) > threshold.value);
+ if (belowThreshold.length > 0) {
+ const nearestThreshold = belowThreshold.sort((t1: Threshold, t2: Threshold) => t2.value - t1.value)[0];
+ return nearestThreshold;
+ }
+
+ return null;
+}
diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go
index ff84f290a4f..67a511b8b4d 100644
--- a/pkg/api/frontendsettings.go
+++ b/pkg/api/frontendsettings.go
@@ -192,16 +192,18 @@ func getPanelSort(id string) int {
sort = 2
case "gauge":
sort = 3
- case "table":
+ case "bargauge":
sort = 4
- case "text":
+ case "table":
sort = 5
- case "heatmap":
+ case "text":
sort = 6
- case "alertlist":
+ case "heatmap":
sort = 7
- case "dashlist":
+ case "alertlist":
sort = 8
+ case "dashlist":
+ sort = 9
}
return sort
}
diff --git a/public/app/core/utils/ConfigProvider.tsx b/public/app/core/utils/ConfigProvider.tsx
index cb3ad88b191..2162abae29b 100644
--- a/public/app/core/utils/ConfigProvider.tsx
+++ b/public/app/core/utils/ConfigProvider.tsx
@@ -15,6 +15,7 @@ export const provideConfig = (component: React.ComponentType) => {
export const getCurrentThemeName = () =>
config.bootData.user.lightTheme ? GrafanaThemeType.Light : GrafanaThemeType.Dark;
+
export const getCurrentTheme = () => getTheme(getCurrentThemeName());
export const ThemeProvider = ({ children }: { children: React.ReactNode }) => {
diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx
index 9aeddd5a0d9..3955f45d926 100644
--- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx
+++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx
@@ -76,21 +76,26 @@ export class DashboardPanel extends PureComponent {
// unmount angular panel
this.cleanUpAngularPanel();
- if (panel.type !== pluginId) {
- this.props.panel.changeType(pluginId, fromAngularPanel);
- }
-
- if (plugin.exports) {
- this.setState({ plugin, angularPanel: null });
- } else {
+ if (!plugin.exports) {
try {
plugin.exports = await importPluginModule(plugin.module);
} catch (e) {
plugin = getPanelPluginNotFound(pluginId);
}
-
- this.setState({ plugin, angularPanel: null });
}
+
+ if (panel.type !== pluginId) {
+ if (fromAngularPanel) {
+ // for angular panels only we need to remove all events and let angular panels do some cleanup
+ panel.destroy();
+
+ this.props.panel.changeType(pluginId);
+ } else {
+ panel.changeType(pluginId, plugin.exports.reactPanel.preserveOptions);
+ }
+ }
+
+ this.setState({ plugin, angularPanel: null });
}
}
diff --git a/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx b/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx
index 4067f361f06..fa16be55e75 100644
--- a/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx
+++ b/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx
@@ -2,9 +2,12 @@
import _ from 'lodash';
import React, { PureComponent } from 'react';
+// Components
+import { AlertBox } from 'app/core/components/AlertBox/AlertBox';
+
// Types
+import { PanelPlugin, AppNotificationSeverity } from 'app/types';
import { PanelProps, ReactPanelPlugin } from '@grafana/ui';
-import { PanelPlugin } from 'app/types';
interface Props {
pluginId: string;
@@ -19,15 +22,13 @@ class PanelPluginNotFound extends PureComponent {
const style = {
display: 'flex',
alignItems: 'center',
- textAlign: 'center' as 'center',
+ justifyContent: 'center',
height: '100%',
};
return (
-
- Panel plugin with id {this.props.pluginId} could not be found
-
+
);
}
diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts
index 079946b1521..686a8ba6d28 100644
--- a/public/app/features/dashboard/state/PanelModel.test.ts
+++ b/public/app/features/dashboard/state/PanelModel.test.ts
@@ -1,5 +1,4 @@
-import _ from 'lodash';
-import { PanelModel } from '../state/PanelModel';
+import { PanelModel } from './PanelModel';
describe('PanelModel', () => {
describe('when creating new panel model', () => {
@@ -66,7 +65,7 @@ describe('PanelModel', () => {
describe('when changing panel type', () => {
beforeEach(() => {
- model.changeType('graph', true);
+ model.changeType('graph');
model.alert = { id: 2 };
});
@@ -75,12 +74,12 @@ describe('PanelModel', () => {
});
it('should restore table properties when changing back', () => {
- model.changeType('table', true);
+ model.changeType('table');
expect(model.showColumns).toBe(true);
});
it('should remove alert rule when changing type that does not support it', () => {
- model.changeType('table', true);
+ model.changeType('table');
expect(model.alert).toBe(undefined);
});
});
diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts
index 0c3ab44d8e8..88065fdf208 100644
--- a/public/app/features/dashboard/state/PanelModel.ts
+++ b/public/app/features/dashboard/state/PanelModel.ts
@@ -229,10 +229,6 @@ export class PanelModel {
}, {});
}
- private saveCurrentPanelOptions() {
- this.cachedPluginOptions[this.type] = this.getOptionsToRemember();
- }
-
private restorePanelOptions(pluginId: string) {
const prevOptions = this.cachedPluginOptions[pluginId] || {};
@@ -241,14 +237,11 @@ export class PanelModel {
});
}
- changeType(pluginId: string, fromAngularPanel: boolean) {
- this.saveCurrentPanelOptions();
- this.type = pluginId;
+ changeType(pluginId: string, preserveOptions?: any) {
+ const oldOptions: any = this.getOptionsToRemember();
+ const oldPluginId = this.type;
- // for angular panels only we need to remove all events and let angular panels do some cleanup
- if (fromAngularPanel) {
- this.destroy();
- }
+ this.type = pluginId;
// remove panel type specific options
for (const key of _.keys(this)) {
@@ -259,7 +252,13 @@ export class PanelModel {
delete this[key];
}
+ this.cachedPluginOptions[oldPluginId] = oldOptions;
this.restorePanelOptions(pluginId);
+
+ if (preserveOptions && oldOptions) {
+ this.options = this.options || {};
+ Object.assign(this.options, preserveOptions(oldPluginId, oldOptions.options));
+ }
}
addQuery(query?: Partial) {
diff --git a/public/app/features/plugins/built_in_plugins.ts b/public/app/features/plugins/built_in_plugins.ts
index dba6478baab..9a156652a65 100644
--- a/public/app/features/plugins/built_in_plugins.ts
+++ b/public/app/features/plugins/built_in_plugins.ts
@@ -27,6 +27,7 @@ import * as table2Panel from 'app/plugins/panel/table2/module';
import * as singlestatPanel from 'app/plugins/panel/singlestat/module';
import * as gettingStartedPanel from 'app/plugins/panel/gettingstarted/module';
import * as gaugePanel from 'app/plugins/panel/gauge/module';
+import * as barGaugePanel from 'app/plugins/panel/bargauge/module';
const builtInPlugins = {
'app/plugins/datasource/graphite/module': graphitePlugin,
@@ -58,6 +59,7 @@ const builtInPlugins = {
'app/plugins/panel/singlestat/module': singlestatPanel,
'app/plugins/panel/gettingstarted/module': gettingStartedPanel,
'app/plugins/panel/gauge/module': gaugePanel,
+ 'app/plugins/panel/bargauge/module': barGaugePanel,
};
export default builtInPlugins;
diff --git a/public/app/plugins/panel/bargauge/BarGaugePanel.tsx b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx
new file mode 100644
index 00000000000..cef28414e73
--- /dev/null
+++ b/public/app/plugins/panel/bargauge/BarGaugePanel.tsx
@@ -0,0 +1,56 @@
+// Libraries
+import React, { PureComponent } from 'react';
+
+// Services & Utils
+import { processSingleStatPanelData } from '@grafana/ui';
+import { config } from 'app/core/config';
+
+// Components
+import { BarGauge, VizRepeater } from '@grafana/ui';
+
+// Types
+import { BarGaugeOptions } from './types';
+import { PanelProps } from '@grafana/ui/src/types';
+
+interface Props extends PanelProps {}
+
+export class BarGaugePanel extends PureComponent {
+ renderBarGauge(value, width, height) {
+ const { replaceVariables, options } = this.props;
+ const { valueOptions } = options;
+
+ const prefix = replaceVariables(valueOptions.prefix);
+ const suffix = replaceVariables(valueOptions.suffix);
+
+ return (
+
+ );
+ }
+
+ render() {
+ const { panelData, options, width, height } = this.props;
+
+ const values = processSingleStatPanelData({
+ panelData: panelData,
+ stat: options.valueOptions.stat,
+ });
+
+ return (
+
+ {({ vizHeight, vizWidth, valueInfo }) => this.renderBarGauge(valueInfo.value, vizWidth, vizHeight)}
+
+ );
+ }
+}
diff --git a/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx b/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx
new file mode 100644
index 00000000000..4232155228b
--- /dev/null
+++ b/public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx
@@ -0,0 +1,64 @@
+// Libraries
+import React, { PureComponent } from 'react';
+
+// Components
+import { SingleStatValueEditor } from 'app/plugins/panel/gauge/SingleStatValueEditor';
+import { ThresholdsEditor, ValueMappingsEditor, PanelOptionsGrid, PanelOptionsGroup, FormField } from '@grafana/ui';
+
+// Types
+import { FormLabel, PanelEditorProps, Threshold, Select, ValueMapping } from '@grafana/ui';
+import { BarGaugeOptions, orientationOptions } from './types';
+import { SingleStatValueOptions } from '../gauge/types';
+
+export class BarGaugePanelEditor extends PureComponent> {
+ onThresholdsChanged = (thresholds: Threshold[]) =>
+ this.props.onOptionsChange({
+ ...this.props.options,
+ thresholds,
+ });
+
+ onValueMappingsChanged = (valueMappings: ValueMapping[]) =>
+ this.props.onOptionsChange({
+ ...this.props.options,
+ valueMappings,
+ });
+
+ onValueOptionsChanged = (valueOptions: SingleStatValueOptions) =>
+ this.props.onOptionsChange({
+ ...this.props.options,
+ valueOptions,
+ });
+
+ onMinValueChange = ({ target }) => this.props.onOptionsChange({ ...this.props.options, minValue: target.value });
+ onMaxValueChange = ({ target }) => this.props.onOptionsChange({ ...this.props.options, maxValue: target.value });
+ onOrientationChange = ({ value }) => this.props.onOptionsChange({ ...this.props.options, orientation: value });
+
+ render() {
+ const { options } = this.props;
+
+ return (
+ <>
+
+
+
+
+
+
+ Orientation
+
+
+
+
+
+
+ >
+ );
+ }
+}
diff --git a/public/app/plugins/panel/bargauge/img/icon_bar_gauge.svg b/public/app/plugins/panel/bargauge/img/icon_bar_gauge.svg
new file mode 100644
index 00000000000..2da2cc78196
--- /dev/null
+++ b/public/app/plugins/panel/bargauge/img/icon_bar_gauge.svg
@@ -0,0 +1,96 @@
+
+
+
diff --git a/public/app/plugins/panel/bargauge/module.tsx b/public/app/plugins/panel/bargauge/module.tsx
new file mode 100644
index 00000000000..d5dcbabc34f
--- /dev/null
+++ b/public/app/plugins/panel/bargauge/module.tsx
@@ -0,0 +1,22 @@
+import { ReactPanelPlugin } from '@grafana/ui';
+
+import { BarGaugePanel } from './BarGaugePanel';
+import { BarGaugePanelEditor } from './BarGaugePanelEditor';
+import { BarGaugeOptions, defaults } from './types';
+
+export const reactPanel = new ReactPanelPlugin(BarGaugePanel);
+
+reactPanel.setEditor(BarGaugePanelEditor);
+reactPanel.setDefaults(defaults);
+reactPanel.setPreserveOptionsHandler((pluginId: string, prevOptions: any) => {
+ const options: Partial = {};
+
+ if (prevOptions.valueOptions) {
+ options.valueOptions = prevOptions.valueOptions;
+ options.thresholds = prevOptions.thresholds;
+ options.maxValue = prevOptions.maxValue;
+ options.minValue = prevOptions.minValue;
+ }
+
+ return options;
+});
diff --git a/public/app/plugins/panel/bargauge/plugin.json b/public/app/plugins/panel/bargauge/plugin.json
new file mode 100644
index 00000000000..aa7fc94212e
--- /dev/null
+++ b/public/app/plugins/panel/bargauge/plugin.json
@@ -0,0 +1,19 @@
+{
+ "type": "panel",
+ "name": "Bar Gauge",
+ "id": "bargauge",
+ "state": "alpha",
+
+ "dataFormats": ["time_series"],
+
+ "info": {
+ "author": {
+ "name": "Grafana Project",
+ "url": "https://grafana.com"
+ },
+ "logos": {
+ "small": "img/icon_bar_gauge.svg",
+ "large": "img/icon_bar_gauge.svg"
+ }
+ }
+}
diff --git a/public/app/plugins/panel/bargauge/types.ts b/public/app/plugins/panel/bargauge/types.ts
new file mode 100644
index 00000000000..962d18d0e3d
--- /dev/null
+++ b/public/app/plugins/panel/bargauge/types.ts
@@ -0,0 +1,31 @@
+import { Threshold, SelectOptionItem, ValueMapping, VizOrientation } from '@grafana/ui';
+import { SingleStatValueOptions } from '../gauge/types';
+
+export interface BarGaugeOptions {
+ minValue: number;
+ maxValue: number;
+ orientation: VizOrientation;
+ valueOptions: SingleStatValueOptions;
+ valueMappings: ValueMapping[];
+ thresholds: Threshold[];
+}
+
+export const orientationOptions: SelectOptionItem[] = [
+ { value: VizOrientation.Horizontal, label: 'Horizontal' },
+ { value: VizOrientation.Vertical, label: 'Vertical' },
+];
+
+export const defaults: BarGaugeOptions = {
+ minValue: 0,
+ maxValue: 100,
+ orientation: VizOrientation.Horizontal,
+ valueOptions: {
+ unit: 'none',
+ stat: 'avg',
+ prefix: '',
+ suffix: '',
+ decimals: null,
+ },
+ thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
+ valueMappings: [],
+};
diff --git a/public/app/plugins/panel/gauge/GaugeOptionsBox.tsx b/public/app/plugins/panel/gauge/GaugeOptionsBox.tsx
index bb3043ddd8b..d0cb9dc3598 100644
--- a/public/app/plugins/panel/gauge/GaugeOptionsBox.tsx
+++ b/public/app/plugins/panel/gauge/GaugeOptionsBox.tsx
@@ -9,6 +9,8 @@ import { FormField, PanelEditorProps } from '@grafana/ui';
import { GaugeOptions } from './types';
export class GaugeOptionsBox extends PureComponent> {
+ labelWidth = 8;
+
onToggleThresholdLabels = () =>
this.props.onOptionsChange({ ...this.props.options, showThresholdLabels: !this.props.options.showThresholdLabels });
@@ -28,17 +30,17 @@ export class GaugeOptionsBox extends PureComponent
-
-
+
+
diff --git a/public/app/plugins/panel/gauge/GaugePanel.tsx b/public/app/plugins/panel/gauge/GaugePanel.tsx
index b75d4a1c7f3..ca7f4d5057e 100644
--- a/public/app/plugins/panel/gauge/GaugePanel.tsx
+++ b/public/app/plugins/panel/gauge/GaugePanel.tsx
@@ -1,82 +1,59 @@
// Libraries
-import React, { Component } from 'react';
+import React, { PureComponent } from 'react';
// Services & Utils
-import { processTimeSeries, ThemeContext } from '@grafana/ui';
+import { processSingleStatPanelData } from '@grafana/ui';
+import { config } from 'app/core/config';
// Components
-import { Gauge } from '@grafana/ui';
+import { Gauge, VizRepeater } from '@grafana/ui';
// Types
import { GaugeOptions } from './types';
-import { PanelProps, NullValueMode, TimeSeriesValue } from '@grafana/ui/src/types';
+import { PanelProps, VizOrientation } from '@grafana/ui/src/types';
interface Props extends PanelProps {}
-interface State {
- value: TimeSeriesValue;
-}
-export class GaugePanel extends Component {
- constructor(props: Props) {
- super(props);
- this.state = {
- value: this.findValue(props),
- };
- }
-
- componentDidUpdate(prevProps: Props) {
- if (this.props.panelData !== prevProps.panelData) {
- this.setState({ value: this.findValue(this.props) });
- }
- }
-
- findValue(props: Props): number | null {
- const { panelData, options } = props;
+export class GaugePanel extends PureComponent {
+ renderGauge(value, width, height) {
+ const { replaceVariables, options } = this.props;
const { valueOptions } = options;
- if (panelData.timeSeries) {
- const vmSeries = processTimeSeries({
- timeSeries: panelData.timeSeries,
- nullValueMode: NullValueMode.Null,
- });
-
- if (vmSeries[0]) {
- return vmSeries[0].stats[valueOptions.stat];
- }
- } else if (panelData.tableData) {
- return panelData.tableData.rows[0].find(prop => prop > 0);
- }
- return null;
- }
-
- render() {
- const { width, height, replaceVariables, options } = this.props;
- const { valueOptions } = options;
- const { value } = this.state;
-
const prefix = replaceVariables(valueOptions.prefix);
const suffix = replaceVariables(valueOptions.suffix);
+
return (
-
- {theme => (
-
- )}
-
+
+ );
+ }
+
+ render() {
+ const { panelData, options, height, width } = this.props;
+
+ const values = processSingleStatPanelData({
+ panelData: panelData,
+ stat: options.valueOptions.stat,
+ });
+
+ return (
+
+ {({ vizHeight, vizWidth, valueInfo }) => this.renderGauge(valueInfo.value, vizWidth, vizHeight)}
+
);
}
}
diff --git a/public/app/plugins/panel/gauge/GaugePanelEditor.tsx b/public/app/plugins/panel/gauge/GaugePanelEditor.tsx
index f226be7328c..f33ded6a28d 100644
--- a/public/app/plugins/panel/gauge/GaugePanelEditor.tsx
+++ b/public/app/plugins/panel/gauge/GaugePanelEditor.tsx
@@ -1,3 +1,4 @@
+// Libraries
import React, { PureComponent } from 'react';
import {
PanelEditorProps,
diff --git a/public/app/plugins/panel/gauge/module.tsx b/public/app/plugins/panel/gauge/module.tsx
index a32cb7cd538..95a6e29ae4d 100644
--- a/public/app/plugins/panel/gauge/module.tsx
+++ b/public/app/plugins/panel/gauge/module.tsx
@@ -8,3 +8,15 @@ export const reactPanel = new ReactPanelPlugin(GaugePanel);
reactPanel.setEditor(GaugePanelEditor);
reactPanel.setDefaults(defaults);
+reactPanel.setPreserveOptionsHandler((pluginId: string, prevOptions: any) => {
+ const options: Partial = {};
+
+ if (prevOptions.valueOptions) {
+ options.valueOptions = prevOptions.valueOptions;
+ options.thresholds = prevOptions.thresholds;
+ options.maxValue = prevOptions.maxValue;
+ options.minValue = prevOptions.minValue;
+ }
+
+ return options;
+});
diff --git a/public/app/plugins/panel/gauge/types.ts b/public/app/plugins/panel/gauge/types.ts
index 10dd475eff5..6922538c210 100644
--- a/public/app/plugins/panel/gauge/types.ts
+++ b/public/app/plugins/panel/gauge/types.ts
@@ -31,5 +31,5 @@ export const defaults: GaugeOptions = {
unit: 'none',
},
valueMappings: [],
- thresholds: [],
+ thresholds: [{ index: 0, value: -Infinity, color: 'green' }, { index: 1, value: 80, color: 'red' }],
};
diff --git a/public/sass/utils/_utils.scss b/public/sass/utils/_utils.scss
index 16a84784de8..41a0df5f363 100644
--- a/public/sass/utils/_utils.scss
+++ b/public/sass/utils/_utils.scss
@@ -96,7 +96,9 @@ button.close {
}
.center-vh {
+ height: 100%;
display: flex;
+ flex-direction: column;
align-items: center;
justify-content: center;
justify-items: center;