diff --git a/public/app/features/plugins/built_in_plugins.ts b/public/app/features/plugins/built_in_plugins.ts index b9779190a8b..a0cedb3ebfd 100644 --- a/public/app/features/plugins/built_in_plugins.ts +++ b/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 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'; const builtInPlugins = { 'app/plugins/datasource/graphite/module': graphitePlugin, @@ -52,6 +53,7 @@ const builtInPlugins = { 'app/plugins/panel/table/module': tablePanel, 'app/plugins/panel/singlestat/module': singlestatPanel, 'app/plugins/panel/gettingstarted/module': gettingStartedPanel, + 'app/plugins/panel/gauge/module': gaugePanel, }; export default builtInPlugins; diff --git a/public/app/plugins/panel/gauge/module.tsx b/public/app/plugins/panel/gauge/module.tsx new file mode 100644 index 00000000000..48dc4d809f3 --- /dev/null +++ b/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 {} + +export class GaugePanel extends PureComponent { + render() { + const { timeSeries } = this.props; + + const vmSeries = getTimeSeriesVMs({ + timeSeries: timeSeries, + nullValueMode: NullValueMode.Ignore, + }); + + return ; + } +} + +export { GaugePanel as PanelComponent }; diff --git a/public/app/plugins/panel/gauge/plugin.json b/public/app/plugins/panel/gauge/plugin.json new file mode 100644 index 00000000000..1fff76fdd3b --- /dev/null +++ b/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": { + + } + } +} + diff --git a/public/app/viz/Gauge.tsx b/public/app/viz/Gauge.tsx new file mode 100644 index 00000000000..5e90fd870db --- /dev/null +++ b/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 { + 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 ( +
(this.parentElement = element)}> +
(this.canvasElement = element)} + /> +
+ ); + } +} + +export default withSize({ monitorHeight: true })(Gauge); diff --git a/public/app/viz/GaugeOptions.tsx b/public/app/viz/GaugeOptions.tsx new file mode 100644 index 00000000000..031855e1c78 --- /dev/null +++ b/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> { + render() { + return ( +
+
+
Draw Modes
+
+
+ ); + } +} diff --git a/public/vendor/flot/jquery.flot.gauge.js b/public/vendor/flot/jquery.flot.gauge.js index 14866dbef20..db1cb1ffe24 100644 --- a/public/vendor/flot/jquery.flot.gauge.js +++ b/public/vendor/flot/jquery.flot.gauge.js @@ -583,30 +583,31 @@ * @param {Number} [a] the angle of the value drawn */ function drawText(x, y, id, text, textOptions, a) { - var span = $("." + id, placeholder); + var span = $(placeholder).find("#" + id); var exists = span.length; if (!exists) { span = $("") 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); } + + 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); // after append, readjust the left position span.css("left", x + "px"); // for redraw, resetting the left position is needed here