Jelajahi Sumber

Merge pull request #14023 from grafana/gauge-panel

React Gauge Panel - first step
Torkel Ödegaard 7 tahun lalu
induk
melakukan
a2a4458923

+ 2 - 0
public/app/features/plugins/built_in_plugins.ts

@@ -24,6 +24,7 @@ import * as heatmapPanel from 'app/plugins/panel/heatmap/module';
 import * as tablePanel from 'app/plugins/panel/table/module';
 import * as tablePanel from 'app/plugins/panel/table/module';
 import * as singlestatPanel from 'app/plugins/panel/singlestat/module';
 import * as singlestatPanel from 'app/plugins/panel/singlestat/module';
 import * as gettingStartedPanel from 'app/plugins/panel/gettingstarted/module';
 import * as gettingStartedPanel from 'app/plugins/panel/gettingstarted/module';
+import * as gaugePanel from 'app/plugins/panel/gauge/module';
 
 
 const builtInPlugins = {
 const builtInPlugins = {
   'app/plugins/datasource/graphite/module': graphitePlugin,
   'app/plugins/datasource/graphite/module': graphitePlugin,
@@ -52,6 +53,7 @@ const builtInPlugins = {
   'app/plugins/panel/table/module': tablePanel,
   'app/plugins/panel/table/module': tablePanel,
   'app/plugins/panel/singlestat/module': singlestatPanel,
   'app/plugins/panel/singlestat/module': singlestatPanel,
   'app/plugins/panel/gettingstarted/module': gettingStartedPanel,
   'app/plugins/panel/gettingstarted/module': gettingStartedPanel,
+  'app/plugins/panel/gauge/module': gaugePanel,
 };
 };
 
 
 export default builtInPlugins;
 export default builtInPlugins;

+ 23 - 0
public/app/plugins/panel/gauge/module.tsx

@@ -0,0 +1,23 @@
+import React, { PureComponent } from 'react';
+import Gauge from 'app/viz/Gauge';
+import { NullValueMode, PanelProps } from 'app/types';
+import { getTimeSeriesVMs } from 'app/viz/state/timeSeries';
+
+export interface Options {}
+
+interface Props extends PanelProps<Options> {}
+
+export class GaugePanel extends PureComponent<Props> {
+  render() {
+    const { timeSeries } = this.props;
+
+    const vmSeries = getTimeSeriesVMs({
+      timeSeries: timeSeries,
+      nullValueMode: NullValueMode.Ignore,
+    });
+
+    return <Gauge maxValue={100} minValue={0} timeSeries={vmSeries} thresholds={[0, 100]} />;
+  }
+}
+
+export { GaugePanel as PanelComponent };

+ 18 - 0
public/app/plugins/panel/gauge/plugin.json

@@ -0,0 +1,18 @@
+{
+  "type": "panel",
+  "name": "Gauge",
+  "id": "gauge",
+
+  "state": "alpha",
+
+  "info": {
+    "author": {
+      "name": "Grafana Project",
+      "url": "https://grafana.com"
+    },
+    "logos": {
+
+    }
+  }
+}
+

+ 133 - 0
public/app/viz/Gauge.tsx

@@ -0,0 +1,133 @@
+import React, { PureComponent } from 'react';
+import $ from 'jquery';
+import { withSize } from 'react-sizeme';
+import { TimeSeriesVMs } from 'app/types';
+import config from '../core/config';
+
+interface Props {
+  timeSeries: TimeSeriesVMs;
+  minValue: number;
+  maxValue: number;
+  showThresholdMarkers?: boolean;
+  thresholds?: number[];
+  showThresholdLables?: boolean;
+  size?: { width: number; height: number };
+}
+
+const colors = ['rgba(50, 172, 45, 0.97)', 'rgba(237, 129, 40, 0.89)', 'rgba(245, 54, 54, 0.9)'];
+
+export class Gauge extends PureComponent<Props> {
+  parentElement: any;
+  canvasElement: any;
+
+  static defaultProps = {
+    minValue: 0,
+    maxValue: 100,
+    showThresholdMarkers: true,
+    showThresholdLables: false,
+    thresholds: [],
+  };
+
+  componentDidMount() {
+    this.draw();
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    this.draw();
+  }
+
+  draw() {
+    const { maxValue, minValue, showThresholdLables, size, showThresholdMarkers, timeSeries, thresholds } = this.props;
+
+    const width = size.width;
+    const height = size.height;
+    const dimension = Math.min(width, height * 1.3);
+
+    const backgroundColor = config.bootData.user.lightTheme ? 'rgb(230,230,230)' : 'rgb(38,38,38)';
+    const fontColor = config.bootData.user.lightTheme ? 'rgb(38,38,38)' : 'rgb(230,230,230)';
+    const fontScale = parseInt('80', 10) / 100;
+    const fontSize = Math.min(dimension / 5, 100) * fontScale;
+    const gaugeWidth = Math.min(dimension / 6, 60);
+    const thresholdMarkersWidth = gaugeWidth / 5;
+    const thresholdLabelFontSize = fontSize / 2.5;
+
+    const formattedThresholds = [];
+
+    thresholds.forEach((threshold, index) => {
+      formattedThresholds.push({
+        value: threshold,
+        color: colors[index],
+      });
+    });
+
+    const options = {
+      series: {
+        gauges: {
+          gauge: {
+            min: minValue,
+            max: maxValue,
+            background: { color: backgroundColor },
+            border: { color: null },
+            shadow: { show: false },
+            width: gaugeWidth,
+          },
+          frame: { show: false },
+          label: { show: false },
+          layout: { margin: 0, thresholdWidth: 0 },
+          cell: { border: { width: 0 } },
+          threshold: {
+            values: formattedThresholds,
+            label: {
+              show: showThresholdLables,
+              margin: thresholdMarkersWidth + 1,
+              font: { size: thresholdLabelFontSize },
+            },
+            show: showThresholdMarkers,
+            width: thresholdMarkersWidth,
+          },
+          value: {
+            color: fontColor,
+            formatter: () => {
+              return Math.round(timeSeries[0].stats.avg);
+            },
+            font: {
+              size: fontSize,
+              family: '"Helvetica Neue", Helvetica, Arial, sans-serif',
+            },
+          },
+          show: true,
+        },
+      },
+    };
+
+    const plotSeries = {
+      data: [[0, timeSeries[0].stats.avg]],
+    };
+
+    try {
+      $.plot(this.canvasElement, [plotSeries], options);
+    } catch (err) {
+      console.log('Gauge rendering error', err, options, timeSeries);
+    }
+  }
+
+  render() {
+    const { height, width } = this.props.size;
+
+    return (
+      <div className="singlestat-panel" ref={element => (this.parentElement = element)}>
+        <div
+          style={{
+            height: `${height * 0.9}px`,
+            width: `${Math.min(width, height * 1.3)}px`,
+            top: '10px',
+            margin: 'auto',
+          }}
+          ref={element => (this.canvasElement = element)}
+        />
+      </div>
+    );
+  }
+}
+
+export default withSize({ monitorHeight: true })(Gauge);

+ 16 - 0
public/app/viz/GaugeOptions.tsx

@@ -0,0 +1,16 @@
+import React, { PureComponent } from 'react';
+import { PanelOptionsProps } from '../types';
+
+interface Props {}
+
+export class GaugeOptions extends PureComponent<PanelOptionsProps<Props>> {
+  render() {
+    return (
+      <div>
+        <div className="section gf-form-group">
+          <h5 className="page-heading">Draw Modes</h5>
+        </div>
+      </div>
+    );
+  }
+}

+ 19 - 18
public/vendor/flot/jquery.flot.gauge.js

@@ -583,30 +583,31 @@
          * @param  {Number} [a] the angle of the value drawn
          * @param  {Number} [a] the angle of the value drawn
          */
          */
         function drawText(x, y, id, text, textOptions, a) {
         function drawText(x, y, id, text, textOptions, a) {
-            var span = $("." + id, placeholder);
+            var span = $(placeholder).find("#" + id);
             var exists = span.length;
             var exists = span.length;
             if (!exists) {
             if (!exists) {
                 span = $("<span></span>")
                 span = $("<span></span>")
                 span.attr("id", id);
                 span.attr("id", id);
-                span.css("position", "absolute");
-                span.css("top", y + "px");
-                if (textOptions.font.size) {
-                    span.css("font-size", textOptions.font.size + "px");
-                }
-                if (textOptions.font.family) {
-                    span.css("font-family", textOptions.font.family);
-                }
-                if (textOptions.color) {
-                    span.css("color", textOptions.color);
-                }
-                if (textOptions.background.color) {
-                    span.css("background-color", textOptions.background.color);
-                }
-                if (textOptions.background.opacity) {
-                    span.css("opacity", textOptions.background.opacity);
-                }
                 placeholder.append(span);
                 placeholder.append(span);
             }
             }
+
+            span.css("position", "absolute");
+            span.css("top", y + "px");
+            if (textOptions.font.size) {
+              span.css("font-size", textOptions.font.size + "px");
+            }
+            if (textOptions.font.family) {
+              span.css("font-family", textOptions.font.family);
+            }
+            if (textOptions.color) {
+              span.css("color", textOptions.color);
+            }
+            if (textOptions.background.color) {
+              span.css("background-color", textOptions.background.color);
+            }
+            if (textOptions.background.opacity) {
+              span.css("opacity", textOptions.background.opacity);
+            }
             span.text(text);
             span.text(text);
             // after append, readjust the left position
             // after append, readjust the left position
             span.css("left", x + "px"); // for redraw, resetting the left position is needed here
             span.css("left", x + "px"); // for redraw, resetting the left position is needed here