Rift.IO OSM R1 Initial Submission
[osm/UI.git] / skyquake / framework / widgets / gauge / gauge.js
diff --git a/skyquake/framework/widgets/gauge/gauge.js b/skyquake/framework/widgets/gauge/gauge.js
new file mode 100644 (file)
index 0000000..dc1a083
--- /dev/null
@@ -0,0 +1,275 @@
+
+/*
+ * 
+ *   Copyright 2016 RIFT.IO Inc
+ *
+ *   Licensed under the Apache License, Version 2.0 (the "License");
+ *   you may not use this file except in compliance with the License.
+ *   You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *   Unless required by applicable law or agreed to in writing, software
+ *   distributed under the License is distributed on an "AS IS" BASIS,
+ *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *   See the License for the specific language governing permissions and
+ *   limitations under the License.
+ *
+ */
+var React = require("react");
+var ReactDOM = require('react-dom');
+var MIXINS = require("../mixins/ButtonEventListener.js");
+var Gauge = require("../../js/gauge-modified.js");
+var GUID = require("utils/guid");
+import _ from 'underscore'
+
+
+
+
+/**
+ *  Gauge Component
+ *  It's props values and a brief description below
+ *
+ *  min:         minimum value expected
+ *  max:         maximum value expected
+ *  width:       width of gauge in px
+ *  height:      height of gauge in px
+ *  value:       the number displayed on the gauge
+ *  resize:      should the gauge resize with container
+ *  unit:        the units displayed on the gauge
+ *  valueFormat: An object with an 'int' and 'dec' property. The 'int' is the min number of integer digits displayed
+ *                 and the 'dec' object is the min number of fractional digits displayed.
+ *
+ *
+ **/
+module.exports = React.createClass({
+  displayName: 'Gauge',
+  mixins:MIXINS,
+  propTypes: {
+    min:           React.PropTypes.number,
+    max:           React.PropTypes.number,
+    width:         React.PropTypes.number,
+    height:        React.PropTypes.string,
+    value:         React.PropTypes.number,
+    resize:        React.PropTypes.bool,
+    isAggregate:   React.PropTypes.bool,
+    units:         React.PropTypes.string,
+    valueFormat:   React.PropTypes.shape({
+      'int':         React.PropTypes.number,
+      'dec':         React.PropTypes.number
+    })
+  },
+  clone: function(obj) {
+      if (null == obj || "object" != typeof obj) return obj;
+      var copy = obj.constructor();
+      for (var attr in obj) {
+          if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
+      }
+      return copy;
+  },
+
+  /**
+   * Defines default state.
+   *
+   *  min:         minimum value expected
+   *  max:         maximum value expected
+   *  nSteps:      fixed number for now. The number of ticks in the gauge.
+   *  width:       width of gauge in px
+   *  height:      height of gauge in px
+   *  value:       the number displayed on the gauge
+   *  resize:      should the gauge resize with container
+   *  unit:        the units displayed on the gauge
+   *  valueFormat: An object with an 'int' and 'dec' property. The 'int' is the min number of integer digits displayed
+   *                 and the 'dec' object is the min number of fractional digits displayed.
+   */
+  getInitialState: function() {
+    var valueFormatState = null
+    this.gauge = null;
+    this.gaugeID = GUID();
+    if (!this.props.valueFormat) {
+        if ((this.props.max && this.props.max > 1000) || this.props.value) {
+            valueFormatState = {
+                "int": 1,
+                "dec": 0
+            };
+        } else {
+            valueFormatState = {
+                "int": 1,
+                "dec": 2
+            };
+        }
+    } else {
+      valueFormatState = this.props.valueFormat;
+    }
+    return {
+      //sizeOfButton:   this.props.size || '',  //There is no Medium value in CSS, default size is the absence of a value
+      min: this.props.min || 0,
+      max: this.props.max || 0,
+      nSteps: 14,
+      height: this.props.height || 200,
+      width: this.props.width || 200,
+      color: this.props.color || 'hsla(212, 57%, 50%, 1)',
+      value: this.props.value || 0,
+      valueFormat: valueFormatState,
+      isAggregate: this.props.isAggregate || false,
+      units: this.props.units || '',
+      resize:this.props.resize || false
+
+    }
+  },
+
+
+  /**
+   *  Called when props are changed.  Syncs props with state.
+   */
+  componentWillReceiveProps: function(nextProps) {
+    this.setState({
+      max:nextProps.max || this.state.max,
+      value:nextProps.value || 0,
+      valueFormat:nextProps.valueFormat || this.state.valueFormat
+    });
+  },
+
+  /**
+   * Calls the render on the gauge object once the component first mounts
+   */
+  componentDidMount: function() {
+    this.canvasRender(this.state);
+  },
+
+  /**
+   * If any of the state variables have changed, the component should update.
+   * Note, this is where the render step occures for the gauge object.
+   */
+  shouldComponentUpdate: function(nextProps, nextState) {
+    var currentStateString = String(this.state.max) + String(this.state.valueFormat.int) + String(this.state.valueFormat.dec) + String(this.state.value);
+    var nextStateString = String(nextState.max) + String(nextState.valueFormat.int) + String(nextState.valueFormat.dec) + String(nextState.value);
+    if (currentStateString == nextStateString) {
+      return false;
+    }
+    this.state.valueFormat = this.determineValueFormat(nextState.value);
+    this.canvasRender(nextState);
+    return true;
+  },
+
+  /**
+   * Default value format based on units.
+   */
+  determineValueFormat: function(value) {
+          if (value > 999 || this.state.units == "%") {
+              return {
+                  "int": 1,
+                  "dec": 0
+              }
+          }
+
+          return {
+              "int": 1,
+              "dec": 2
+          }
+      },
+
+
+  /**
+   * Render step for the gauge object. Sets some defaults, passes some of the component's state down.
+   */
+  canvasRender: function(state) {
+    if (state.max == state.min) {
+        state.max = 14;
+    }
+    var range = state.max - state.min;
+    var step = Math.round(range / state.nSteps);
+    var majorTicks = [];
+    for (var i = 0; i <= state.nSteps; i++) {
+        majorTicks.push(state.min + (i * step));
+    }
+    var redLine = state.min + (range * 0.9);
+    var config = {
+        isAggregate: state.isAggregate,
+        renderTo: ReactDOM.findDOMNode(document.getElementById(this.gaugeID)),
+        width: state.width,
+        height: state.height,
+        glow: false,
+        units: state.units,
+        title: false,
+        minValue: state.min,
+        maxValue: state.max,
+        majorTicks: majorTicks,
+        valueFormat: this.determineValueFormat(self.value),
+        minorTicks: 0,
+        strokeTicks: false,
+        highlights: [],
+        colors: {
+            plate: 'rgba(0,0,0,0)',
+            majorTicks: 'rgba(15, 123, 182, .84)',
+            minorTicks: '#ccc',
+            title: 'rgba(50,50,50,100)',
+            units: 'rgba(50,50,50,100)',
+            numbers: '#fff',
+            needle: {
+                start: 'rgba(255, 255, 255, 1)',
+                end: 'rgba(255, 255, 255, 1)'
+            }
+        }
+    };
+
+    var min = config.minValue;
+    var max = config.maxValue;
+    var N = 1000;
+    var increment = (max - min) / N;
+    for (i = 0; i < N; i++) {
+        var temp_color = 'rgb(0, 172, 238)';
+        if (i > 0.5714 * N && i <= 0.6428 * N) {
+            temp_color = 'rgb(0,157,217)';
+        } else if (i >= 0.6428 * N && i < 0.7142 * N) {
+            temp_color = 'rgb(0,142,196)';
+        } else if (i >= 0.7142 * N && i < 0.7857 * N) {
+            temp_color = 'rgb(0,126,175)';
+        } else if (i >= 0.7857 * N && i < 0.8571 * N) {
+            temp_color = 'rgb(0,122,154)';
+        } else if (i >= 0.8571 * N && i < 0.9285 * N) {
+            temp_color = 'rgb(0,96,133)';
+        } else if (i >= 0.9285 * N) {
+            temp_color = 'rgb(0,80,112)';
+        }
+        config.highlights.push({
+            from: i * increment,
+            to: increment * (i + 2),
+            color: temp_color
+        })
+    }
+    var updateSize = _.debounce(function() {
+        config.maxValue = state.max;
+    }, 500);
+    if (state.resize) $(window).resize(updateSize)
+
+    if (this.gauge) {
+        this.gauge.setValue(Math.ceil(state.value* 100) / 100)
+        this.gauge.updateConfig(config);
+    } else {
+        this.gauge = new Gauge(config);
+        this.gauge.setValue(Math.ceil(state.value* 100) / 100)
+        this.gauge.draw();
+    }
+  },
+
+  /**
+   * Renders the Gauge Component
+   * Returns the canvas element the gauge will be housed in.
+   * @returns {*}
+   */
+  render: function() {
+    var gaugeDOM = React.createElement("div", null,
+      React.createElement("canvas",
+        {className: "rwgauge", style:
+          {width:'100%','maxWidth':this.state.width + 'px','maxHeight':this.state.width},
+          id:this.gaugeID
+        }
+      )
+    )
+
+
+
+    return gaugeDOM;
+  }
+});