Przeglądaj źródła

added orientation option

Peter Holmberg 7 lat temu
rodzic
commit
2fc136e615

+ 1 - 0
packages/grafana-ui/src/components/BarGauge/BarGauge.test.tsx

@@ -21,6 +21,7 @@ const setup = (propOverrides?: object) => {
     value: 25,
     decimals: 0,
     theme: getTheme(),
+    orientation: 'horizontal',
   };
 
   Object.assign(props, propOverrides);

+ 68 - 12
packages/grafana-ui/src/components/BarGauge/BarGauge.tsx

@@ -17,6 +17,7 @@ export interface Props extends Themeable {
   value: TimeSeriesValue;
   maxValue: number;
   minValue: number;
+  orientation: string;
   prefix?: string;
   suffix?: string;
   decimals?: number;
@@ -28,6 +29,7 @@ export class BarGauge extends PureComponent<Props> {
     minValue: 0,
     value: 100,
     unit: 'none',
+    orientation: 'horizontal',
     thresholds: [],
     valueMappings: [],
   };
@@ -75,35 +77,89 @@ export class BarGauge extends PureComponent<Props> {
     };
   }
 
+  renderHorizontalBar(valueStyles: React.CSSProperties, valueFormatted: string, barStyles: React.CSSProperties) {
+    const { height, width } = this.props;
+
+    const containerStyles = {
+      width: `${width}px`,
+      height: `${height}px`,
+      display: 'flex',
+      flexDirection: 'column',
+      justifyContent: 'flex-end',
+    } as React.CSSProperties;
+
+    return (
+      <div style={containerStyles}>
+        <div className="bar-gauge__value" style={valueStyles}>
+          {valueFormatted}
+        </div>
+        <div style={barStyles} />
+      </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, unit, decimals } = this.props;
+    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 containerStyles = { width: `${width}px`, height: `${height}px` };
     const valueStyles = this.getValueStyles(valueFormatted, colors.value);
-    const barStyles = {
+    const vertical = orientation === 'vertical';
+
+    const horizontalBarStyles = {
       height: `${barHeight}px`,
       width: `${width}px`,
       backgroundColor: colors.bar,
       borderTop: `1px solid ${colors.border}`,
     };
 
-    return (
-      <div className="bar-gauge" style={containerStyles}>
-        <div className="bar-gauge__value" style={valueStyles}>
-          {valueFormatted}
-        </div>
-        <div style={barStyles} />
-      </div>
-    );
+    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);
   }
 }
 

+ 34 - 24
packages/grafana-ui/src/components/VizRepeater/VizRepeater.tsx

@@ -1,4 +1,4 @@
-import { Component } from 'react';
+import React, { PureComponent } from 'react';
 import { TimeSeriesVMs } from '../../types';
 
 interface RenderProps {
@@ -12,45 +12,55 @@ interface Props {
   height: number;
   width: number;
   timeSeries: TimeSeriesVMs;
+  orientation?: string;
 }
 
-export class VizRepeater extends Component<Props> {
+export class VizRepeater extends PureComponent<Props> {
   render() {
-    const { children, height, timeSeries, width } = this.props;
+    const { children, orientation, height, timeSeries, width } = this.props;
 
-    const singleStatWidth = 1 / timeSeries.length * 100;
-    const singleStatHeight = 1 / timeSeries.length * 100;
-    const repeatingGaugeWidth = Math.floor(width / timeSeries.length) - 10; // make Gauge slightly smaller than panel.
-    const repeatingGaugeHeight = Math.floor(height / timeSeries.length) - 10;
+    const vizContainerWidth = 1 / timeSeries.length * 100;
+    const vizContainerHeight = 1 / timeSeries.length * 100;
+    const repeatingVizWidth = Math.floor(width / timeSeries.length) - 10; // make Gauge slightly smaller than panel.
+    const repeatingVizHeight = Math.floor(height / timeSeries.length) - 10;
 
-    const horizontalPanels = {
-      display: 'inline-block',
+    const horizontalVisualization = {
       height: height,
-      width: `${singleStatWidth}%`,
+      width: `${vizContainerWidth}%`,
     };
 
-    const verticalPanels = {
-      display: 'block',
+    const verticalVisualization = {
       width: width,
-      height: `${singleStatHeight}%`,
+      height: `${vizContainerHeight}%`,
     };
 
+    const repeaterStyle = {
+      display: 'flex',
+      flexDirection: orientation === 'vertical' || height > width ? 'column' : 'row',
+    } as React.CSSProperties;
+
     let vizContainerStyle = {};
     let vizWidth = width;
     let vizHeight = height;
 
-    if (width > height) {
-      vizContainerStyle = horizontalPanels;
-      vizWidth = repeatingGaugeWidth;
-    } else if (height > width) {
-      vizContainerStyle = verticalPanels;
-      vizHeight = repeatingGaugeHeight;
+    if (orientation === 'horizontal' || width > height) {
+      vizContainerStyle = horizontalVisualization;
+      vizWidth = repeatingVizWidth;
+    }
+
+    if (orientation === 'vertical' || height > width) {
+      vizContainerStyle = verticalVisualization;
+      vizHeight = repeatingVizHeight;
     }
 
-    return children({
-      vizHeight,
-      vizWidth,
-      vizContainerStyle,
-    });
+    return (
+      <div style={repeaterStyle}>
+        {children({
+          vizHeight,
+          vizWidth,
+          vizContainerStyle,
+        })}
+      </div>
+    );
   }
 }

+ 50 - 31
public/app/plugins/panel/bargauge/BarGaugePanel.tsx

@@ -2,59 +2,78 @@
 import React, { PureComponent } from 'react';
 
 // Services & Utils
-import { processTimeSeries, ThemeContext } from '@grafana/ui';
+import { processTimeSeries } from '@grafana/ui';
+import { config } from 'app/core/config';
 
 // Components
-import { BarGauge } from '@grafana/ui';
+import { BarGauge, VizRepeater } from '@grafana/ui';
 
 // 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> {}
 
 export class BarGaugePanel extends PureComponent<Props> {
-  render() {
-    const { panelData, width, height, onInterpolate, options } = this.props;
+  renderBarGauge(value, width, height) {
+    const { onInterpolate, options } = this.props;
     const { valueOptions } = options;
-
     const prefix = onInterpolate(valueOptions.prefix);
     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) {
-      const vmSeries = processTimeSeries({
+      const timeSeries = processTimeSeries({
         timeSeries: panelData.timeSeries,
         nullValueMode: NullValueMode.Null,
       });
 
-      if (vmSeries[0]) {
-        value = vmSeries[0].stats[valueOptions.stat];
-      } else {
-        value = null;
+      if (timeSeries.length > 1) {
+        return (
+          <VizRepeater height={height} width={width} timeSeries={timeSeries} orientation={options.orientation}>
+            {({ 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) {
-      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 (
-      <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>
-    );
+    return <div className="singlestat-panel">No time series data available</div>;
   }
 }

+ 13 - 2
public/app/plugins/panel/bargauge/BarGaugePanelEditor.tsx

@@ -6,8 +6,8 @@ import { SingleStatValueEditor } from 'app/plugins/panel/gauge/SingleStatValueEd
 import { ThresholdsEditor, ValueMappingsEditor, PanelOptionsGrid, PanelOptionsGroup, FormField } from '@grafana/ui';
 
 // Types
-import { PanelEditorProps, Threshold, ValueMapping } from '@grafana/ui';
-import { BarGaugeOptions } from './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<PanelEditorProps<BarGaugeOptions>> {
@@ -31,6 +31,7 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
 
   onMinValueChange = ({ target }) => this.props.onChange({ ...this.props.options, minValue: target.value });
   onMaxValueChange = ({ target }) => this.props.onChange({ ...this.props.options, maxValue: target.value });
+  onOrientationChange = ({ value }) => this.props.onChange({ ...this.props.options, orientation: value });
 
   render() {
     const { options } = this.props;
@@ -42,6 +43,16 @@ export class BarGaugePanelEditor extends PureComponent<PanelEditorProps<BarGauge
           <PanelOptionsGroup title="Gauge">
             <FormField label="Min value" labelWidth={8} onChange={this.onMinValueChange} value={options.minValue} />
             <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>
           <ThresholdsEditor onChange={this.onThresholdsChanged} thresholds={options.thresholds} />
         </PanelOptionsGrid>

+ 14 - 2
public/app/plugins/panel/bargauge/types.ts

@@ -1,17 +1,29 @@
-import { Threshold, ValueMapping } from '@grafana/ui';
+import { Threshold, SelectOptionItem, ValueMapping } from '@grafana/ui';
 import { SingleStatValueOptions } from '../gauge/types';
 
 export interface BarGaugeOptions {
   minValue: number;
   maxValue: number;
+  orientation: string;
   valueOptions: SingleStatValueOptions;
   valueMappings: ValueMapping[];
   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 = {
   minValue: 0,
   maxValue: 100,
+  orientation: OrientationModes.Horizontal,
   valueOptions: {
     unit: 'none',
     stat: 'avg',
@@ -19,6 +31,6 @@ export const defaults: BarGaugeOptions = {
     suffix: '',
     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: [],
 };