1012 lines
27 KiB
JavaScript
1012 lines
27 KiB
JavaScript
// Copyright 2007 The Closure Library Authors. All Rights Reserved.
|
|
//
|
|
// 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.
|
|
|
|
|
|
/**
|
|
* @fileoverview Gauge UI component, using browser vector graphics.
|
|
* @see ../demos/gauge.html
|
|
*/
|
|
|
|
|
|
goog.provide('goog.ui.Gauge');
|
|
goog.provide('goog.ui.GaugeColoredRange');
|
|
|
|
|
|
goog.require('goog.a11y.aria');
|
|
goog.require('goog.asserts');
|
|
goog.require('goog.events');
|
|
goog.require('goog.fx.Animation');
|
|
goog.require('goog.fx.Transition');
|
|
goog.require('goog.fx.easing');
|
|
goog.require('goog.graphics');
|
|
goog.require('goog.graphics.Font');
|
|
goog.require('goog.graphics.Path');
|
|
goog.require('goog.graphics.SolidFill');
|
|
goog.require('goog.math');
|
|
goog.require('goog.ui.Component');
|
|
goog.require('goog.ui.GaugeTheme');
|
|
|
|
|
|
|
|
/**
|
|
* Information on how to decorate a range in the gauge.
|
|
* This is an internal-only class.
|
|
* @param {number} fromValue The range start (minimal) value.
|
|
* @param {number} toValue The range end (maximal) value.
|
|
* @param {string} backgroundColor Color to fill the range background with.
|
|
* @constructor
|
|
*/
|
|
goog.ui.GaugeColoredRange = function(fromValue, toValue, backgroundColor) {
|
|
|
|
/**
|
|
* The range start (minimal) value.
|
|
* @type {number}
|
|
*/
|
|
this.fromValue = fromValue;
|
|
|
|
|
|
/**
|
|
* The range end (maximal) value.
|
|
* @type {number}
|
|
*/
|
|
this.toValue = toValue;
|
|
|
|
|
|
/**
|
|
* Color to fill the range background with.
|
|
* @type {string}
|
|
*/
|
|
this.backgroundColor = backgroundColor;
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
* A UI component that displays a gauge.
|
|
* A gauge displayes a current value within a round axis that represents a
|
|
* given range.
|
|
* The gauge is built from an external border, and internal border inside it,
|
|
* ticks and labels inside the internal border, and a needle that points to
|
|
* the current value.
|
|
* @param {number} width The width in pixels.
|
|
* @param {number} height The height in pixels.
|
|
* @param {goog.dom.DomHelper=} opt_domHelper The DOM helper object for the
|
|
* document we want to render in.
|
|
* @constructor
|
|
* @extends {goog.ui.Component}
|
|
*/
|
|
goog.ui.Gauge = function(width, height, opt_domHelper) {
|
|
goog.ui.Component.call(this, opt_domHelper);
|
|
|
|
/**
|
|
* The width in pixels of this component.
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this.width_ = width;
|
|
|
|
|
|
/**
|
|
* The height in pixels of this component.
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
this.height_ = height;
|
|
|
|
|
|
/**
|
|
* The underlying graphics.
|
|
* @type {goog.graphics.AbstractGraphics}
|
|
* @private
|
|
*/
|
|
this.graphics_ = goog.graphics.createGraphics(width, height,
|
|
null, null, opt_domHelper);
|
|
|
|
|
|
/**
|
|
* Colors to paint the background of certain ranges (optional).
|
|
* @type {Array.<goog.ui.GaugeColoredRange>}
|
|
* @private
|
|
*/
|
|
this.rangeColors_ = [];
|
|
};
|
|
goog.inherits(goog.ui.Gauge, goog.ui.Component);
|
|
|
|
|
|
/**
|
|
* Constant for a background color for a gauge area.
|
|
*/
|
|
goog.ui.Gauge.RED = '#ffc0c0';
|
|
|
|
|
|
/**
|
|
* Constant for a background color for a gauge area.
|
|
*/
|
|
goog.ui.Gauge.GREEN = '#c0ffc0';
|
|
|
|
|
|
/**
|
|
* Constant for a background color for a gauge area.
|
|
*/
|
|
goog.ui.Gauge.YELLOW = '#ffffa0';
|
|
|
|
|
|
/**
|
|
* The radius of the entire gauge from the canvas size.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_RADIUS_FROM_SIZE = 0.45;
|
|
|
|
|
|
/**
|
|
* The ratio of internal gauge radius from entire radius.
|
|
* The remaining area is the border around the gauge.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_MAIN_AREA = 0.9;
|
|
|
|
|
|
/**
|
|
* The ratio of the colored background area for value ranges.
|
|
* The colored area width is computed as
|
|
* InternalRadius * (1 - FACTOR_COLOR_RADIUS)
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_COLOR_RADIUS = 0.75;
|
|
|
|
|
|
/**
|
|
* The ratio of the major ticks length start position, from the radius.
|
|
* The major ticks length width is computed as
|
|
* InternalRadius * (1 - FACTOR_MAJOR_TICKS)
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_MAJOR_TICKS = 0.8;
|
|
|
|
|
|
/**
|
|
* The ratio of the minor ticks length start position, from the radius.
|
|
* The minor ticks length width is computed as
|
|
* InternalRadius * (1 - FACTOR_MINOR_TICKS)
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_MINOR_TICKS = 0.9;
|
|
|
|
|
|
/**
|
|
* The length of the needle front (value facing) from the internal radius.
|
|
* The needle front is the part of the needle that points to the value.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_NEEDLE_FRONT = 0.95;
|
|
|
|
|
|
/**
|
|
* The length of the needle back relative to the internal radius.
|
|
* The needle back is the part of the needle that points away from the value.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_NEEDLE_BACK = 0.3;
|
|
|
|
|
|
/**
|
|
* The width of the needle front at the hinge.
|
|
* This is the width of the curve control point, the actual width is
|
|
* computed by the curve itself.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_NEEDLE_WIDTH = 0.07;
|
|
|
|
|
|
/**
|
|
* The width (radius) of the needle hinge from the gauge radius.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_NEEDLE_HINGE = 0.15;
|
|
|
|
|
|
/**
|
|
* The title font size (height) for titles relative to the internal radius.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_TITLE_FONT_SIZE = 0.16;
|
|
|
|
|
|
/**
|
|
* The offset of the title from the center, relative to the internal radius.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_TITLE_OFFSET = 0.35;
|
|
|
|
|
|
/**
|
|
* The formatted value font size (height) relative to the internal radius.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_VALUE_FONT_SIZE = 0.18;
|
|
|
|
|
|
/**
|
|
* The title font size (height) for tick labels relative to the internal radius.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_TICK_LABEL_FONT_SIZE = 0.14;
|
|
|
|
|
|
/**
|
|
* The offset of the formatted value down from the center, relative to the
|
|
* internal radius.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.FACTOR_VALUE_OFFSET = 0.75;
|
|
|
|
|
|
/**
|
|
* The font name for title text.
|
|
* @type {string}
|
|
*/
|
|
goog.ui.Gauge.TITLE_FONT_NAME = 'arial';
|
|
|
|
|
|
/**
|
|
* The maximal size of a step the needle can move (percent from size of range).
|
|
* If the needle needs to move more, it will be moved in animated steps, to
|
|
* show a smooth transition between values.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.NEEDLE_MOVE_MAX_STEP = 0.02;
|
|
|
|
|
|
/**
|
|
* Time in miliseconds for animating a move of the value pointer.
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.NEEDLE_MOVE_TIME = 400;
|
|
|
|
|
|
/**
|
|
* Tolerance factor for how much values can exceed the range (being too
|
|
* low or too high). The value is presented as a position (percentage).
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.MAX_EXCEED_POSITION_POSITION = 0.02;
|
|
|
|
|
|
/**
|
|
* The minimal value that can be displayed.
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.prototype.minValue_ = 0;
|
|
|
|
|
|
/**
|
|
* The maximal value that can be displayed.
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.prototype.maxValue_ = 100;
|
|
|
|
|
|
/**
|
|
* The number of major tick sections.
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.prototype.majorTicks_ = 5;
|
|
|
|
|
|
/**
|
|
* The number of minor tick sections in each major tick section.
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.prototype.minorTicks_ = 2;
|
|
|
|
|
|
/**
|
|
* The current value that needs to be displayed in the gauge.
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.prototype.value_ = 0;
|
|
|
|
|
|
/**
|
|
* The current value formatted into a String.
|
|
* @private
|
|
* @type {?string}
|
|
*/
|
|
goog.ui.Gauge.prototype.formattedValue_ = null;
|
|
|
|
|
|
/**
|
|
* The current colors theme.
|
|
* @private
|
|
* @type {goog.ui.GaugeTheme?}
|
|
*/
|
|
goog.ui.Gauge.prototype.theme_ = null;
|
|
|
|
|
|
/**
|
|
* Title to display above the gauge center.
|
|
* @private
|
|
* @type {?string}
|
|
*/
|
|
goog.ui.Gauge.prototype.titleTop_ = null;
|
|
|
|
|
|
/**
|
|
* Title to display below the gauge center.
|
|
* @private
|
|
* @type {?string}
|
|
*/
|
|
goog.ui.Gauge.prototype.titleBottom_ = null;
|
|
|
|
|
|
/**
|
|
* Font to use for drawing titles.
|
|
* If null (default), computed dynamically with a size relative to the
|
|
* gauge radius.
|
|
* @private
|
|
* @type {goog.graphics.Font?}
|
|
*/
|
|
goog.ui.Gauge.prototype.titleFont_ = null;
|
|
|
|
|
|
/**
|
|
* Font to use for drawing the formatted value.
|
|
* If null (default), computed dynamically with a size relative to the
|
|
* gauge radius.
|
|
* @private
|
|
* @type {goog.graphics.Font?}
|
|
*/
|
|
goog.ui.Gauge.prototype.valueFont_ = null;
|
|
|
|
|
|
/**
|
|
* Font to use for drawing tick labels.
|
|
* If null (default), computed dynamically with a size relative to the
|
|
* gauge radius.
|
|
* @private
|
|
* @type {goog.graphics.Font?}
|
|
*/
|
|
goog.ui.Gauge.prototype.tickLabelFont_ = null;
|
|
|
|
|
|
/**
|
|
* The size in angles of the gauge axis area.
|
|
* @private
|
|
* @type {number}
|
|
*/
|
|
goog.ui.Gauge.prototype.angleSpan_ = 270;
|
|
|
|
|
|
/**
|
|
* The radius for drawing the needle.
|
|
* Computed on full redraw, and used on every animation step of moving
|
|
* the needle.
|
|
* @type {number}
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.needleRadius_ = 0;
|
|
|
|
|
|
/**
|
|
* The group elemnt of the needle. Contains all elements that change when the
|
|
* gauge value changes.
|
|
* @type {goog.graphics.GroupElement?}
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.needleGroup_ = null;
|
|
|
|
|
|
/**
|
|
* The current position (0-1) of the visible needle.
|
|
* Initially set to null to prevent animation on first opening of the gauge.
|
|
* @type {?number}
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.needleValuePosition_ = null;
|
|
|
|
|
|
/**
|
|
* Text labels to display by major tick marks.
|
|
* @type {Array.<string>?}
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.majorTickLabels_ = null;
|
|
|
|
|
|
/**
|
|
* Animation object while needle is being moved (animated).
|
|
* @type {goog.fx.Animation?}
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.animation_ = null;
|
|
|
|
|
|
/**
|
|
* @return {number} The minimum value of the range.
|
|
*/
|
|
goog.ui.Gauge.prototype.getMinimum = function() {
|
|
return this.minValue_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the minimum value of the range
|
|
* @param {number} min The minimum value of the range.
|
|
*/
|
|
goog.ui.Gauge.prototype.setMinimum = function(min) {
|
|
this.minValue_ = min;
|
|
var element = this.getElement();
|
|
if (element) {
|
|
goog.a11y.aria.setState(element, 'valuemin', min);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @return {number} The maximum value of the range.
|
|
*/
|
|
goog.ui.Gauge.prototype.getMaximum = function() {
|
|
return this.maxValue_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the maximum number of the range
|
|
* @param {number} max The maximum value of the range.
|
|
*/
|
|
goog.ui.Gauge.prototype.setMaximum = function(max) {
|
|
this.maxValue_ = max;
|
|
|
|
var element = this.getElement();
|
|
if (element) {
|
|
goog.a11y.aria.setState(element, 'valuemax', max);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the current value range displayed by the gauge.
|
|
* @param {number} value The current value for the gauge. This value
|
|
* determines the position of the needle of the gauge.
|
|
* @param {string=} opt_formattedValue The string value to show in the gauge.
|
|
* If not specified, no string value will be displayed.
|
|
*/
|
|
goog.ui.Gauge.prototype.setValue = function(value, opt_formattedValue) {
|
|
this.value_ = value;
|
|
this.formattedValue_ = opt_formattedValue || null;
|
|
|
|
this.stopAnimation_(); // Stop the active animation if exists
|
|
|
|
// Compute desired value position (normalize value to range 0-1)
|
|
var valuePosition = this.valueToRangePosition_(value);
|
|
if (this.needleValuePosition_ == null) {
|
|
// No animation on initial display
|
|
this.needleValuePosition_ = valuePosition;
|
|
this.drawValue_();
|
|
} else {
|
|
// Animate move
|
|
this.animation_ = new goog.fx.Animation([this.needleValuePosition_],
|
|
[valuePosition],
|
|
goog.ui.Gauge.NEEDLE_MOVE_TIME,
|
|
goog.fx.easing.inAndOut);
|
|
|
|
var events = [goog.fx.Transition.EventType.BEGIN,
|
|
goog.fx.Animation.EventType.ANIMATE,
|
|
goog.fx.Transition.EventType.END];
|
|
goog.events.listen(this.animation_, events, this.onAnimate_, false, this);
|
|
goog.events.listen(this.animation_, goog.fx.Transition.EventType.END,
|
|
this.onAnimateEnd_, false, this);
|
|
|
|
// Start animation
|
|
this.animation_.play(false);
|
|
}
|
|
|
|
var element = this.getElement();
|
|
if (element) {
|
|
goog.a11y.aria.setState(element, 'valuenow', this.value_);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the number of major tick sections and minor tick sections.
|
|
* @param {number} majorUnits The number of major tick sections.
|
|
* @param {number} minorUnits The number of minor tick sections for each major
|
|
* tick section.
|
|
*/
|
|
goog.ui.Gauge.prototype.setTicks = function(majorUnits, minorUnits) {
|
|
this.majorTicks_ = Math.max(1, majorUnits);
|
|
this.minorTicks_ = Math.max(1, minorUnits);
|
|
this.draw_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the labels of the major ticks.
|
|
* @param {Array.<string>} tickLabels A text label for each major tick value.
|
|
*/
|
|
goog.ui.Gauge.prototype.setMajorTickLabels = function(tickLabels) {
|
|
this.majorTickLabels_ = tickLabels;
|
|
this.draw_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the top title of the gauge.
|
|
* The top title is displayed above the center.
|
|
* @param {string} text The top title text.
|
|
*/
|
|
goog.ui.Gauge.prototype.setTitleTop = function(text) {
|
|
this.titleTop_ = text;
|
|
this.draw_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the bottom title of the gauge.
|
|
* The top title is displayed below the center.
|
|
* @param {string} text The bottom title text.
|
|
*/
|
|
goog.ui.Gauge.prototype.setTitleBottom = function(text) {
|
|
this.titleBottom_ = text;
|
|
this.draw_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the font for displaying top and bottom titles.
|
|
* @param {goog.graphics.Font} font The font for titles.
|
|
*/
|
|
goog.ui.Gauge.prototype.setTitleFont = function(font) {
|
|
this.titleFont_ = font;
|
|
this.draw_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the font for displaying the formatted value.
|
|
* @param {goog.graphics.Font} font The font for displaying the value.
|
|
*/
|
|
goog.ui.Gauge.prototype.setValueFont = function(font) {
|
|
this.valueFont_ = font;
|
|
this.drawValue_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Sets the color theme for drawing the gauge.
|
|
* @param {goog.ui.GaugeTheme} theme The color theme to use.
|
|
*/
|
|
goog.ui.Gauge.prototype.setTheme = function(theme) {
|
|
this.theme_ = theme;
|
|
this.draw_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Set the background color for a range of values on the gauge.
|
|
* @param {number} fromValue The lower (start) value of the colored range.
|
|
* @param {number} toValue The higher (end) value of the colored range.
|
|
* @param {string} color The color name to paint the range with. For example
|
|
* 'red', '#ffcc00' or constants like goog.ui.Gauge.RED.
|
|
*/
|
|
goog.ui.Gauge.prototype.addBackgroundColor = function(fromValue, toValue,
|
|
color) {
|
|
this.rangeColors_.push(
|
|
new goog.ui.GaugeColoredRange(fromValue, toValue, color));
|
|
this.draw_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Creates the DOM representation of the graphics area.
|
|
* @override
|
|
*/
|
|
goog.ui.Gauge.prototype.createDom = function() {
|
|
this.setElementInternal(this.getDomHelper().createDom(
|
|
'div', goog.getCssName('goog-gauge'), this.graphics_.getElement()));
|
|
};
|
|
|
|
|
|
/**
|
|
* Clears the entire graphics area.
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.clear_ = function() {
|
|
this.graphics_.clear();
|
|
this.needleGroup_ = null;
|
|
};
|
|
|
|
|
|
/**
|
|
* Redraw the entire gauge.
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.draw_ = function() {
|
|
if (!this.isInDocument()) {
|
|
return;
|
|
}
|
|
|
|
this.clear_();
|
|
|
|
var x, y;
|
|
var size = Math.min(this.width_, this.height_);
|
|
var r = Math.round(goog.ui.Gauge.FACTOR_RADIUS_FROM_SIZE * size);
|
|
var cx = this.width_ / 2;
|
|
var cy = this.height_ / 2;
|
|
|
|
var theme = this.theme_;
|
|
if (!theme) {
|
|
// Lazy allocation of default theme, common to all instances
|
|
theme = goog.ui.Gauge.prototype.theme_ = new goog.ui.GaugeTheme();
|
|
}
|
|
|
|
// Draw main circle frame around gauge
|
|
var graphics = this.graphics_;
|
|
var stroke = this.theme_.getExternalBorderStroke();
|
|
var fill = theme.getExternalBorderFill(cx, cy, r);
|
|
graphics.drawCircle(cx, cy, r, stroke, fill);
|
|
|
|
r -= stroke.getWidth();
|
|
r = Math.round(r * goog.ui.Gauge.FACTOR_MAIN_AREA);
|
|
stroke = theme.getInternalBorderStroke();
|
|
fill = theme.getInternalBorderFill(cx, cy, r);
|
|
graphics.drawCircle(cx, cy, r, stroke, fill);
|
|
r -= stroke.getWidth() * 2;
|
|
|
|
// Draw Background with external and internal borders
|
|
var rBackgroundInternal = r * goog.ui.Gauge.FACTOR_COLOR_RADIUS;
|
|
for (var i = 0; i < this.rangeColors_.length; i++) {
|
|
var rangeColor = this.rangeColors_[i];
|
|
var fromValue = rangeColor.fromValue;
|
|
var toValue = rangeColor.toValue;
|
|
var path = new goog.graphics.Path();
|
|
var fromAngle = this.valueToAngle_(fromValue);
|
|
var toAngle = this.valueToAngle_(toValue);
|
|
// Move to outer point at "from" angle
|
|
path.moveTo(
|
|
cx + goog.math.angleDx(fromAngle, r),
|
|
cy + goog.math.angleDy(fromAngle, r));
|
|
// Arc to outer point at "to" angle
|
|
path.arcTo(r, r, fromAngle, toAngle - fromAngle);
|
|
// Line to inner point at "to" angle
|
|
path.lineTo(
|
|
cx + goog.math.angleDx(toAngle, rBackgroundInternal),
|
|
cy + goog.math.angleDy(toAngle, rBackgroundInternal));
|
|
// Arc to inner point at "from" angle
|
|
path.arcTo(
|
|
rBackgroundInternal, rBackgroundInternal, toAngle, fromAngle - toAngle);
|
|
path.close();
|
|
fill = new goog.graphics.SolidFill(rangeColor.backgroundColor);
|
|
graphics.drawPath(path, null, fill);
|
|
}
|
|
|
|
// Draw titles
|
|
if (this.titleTop_ || this.titleBottom_) {
|
|
var font = this.titleFont_;
|
|
if (!font) {
|
|
// Lazy creation of font
|
|
var fontSize =
|
|
Math.round(r * goog.ui.Gauge.FACTOR_TITLE_FONT_SIZE);
|
|
font = new goog.graphics.Font(
|
|
fontSize, goog.ui.Gauge.TITLE_FONT_NAME);
|
|
this.titleFont_ = font;
|
|
}
|
|
fill = new goog.graphics.SolidFill(theme.getTitleColor());
|
|
if (this.titleTop_) {
|
|
y = cy - Math.round(r * goog.ui.Gauge.FACTOR_TITLE_OFFSET);
|
|
graphics.drawTextOnLine(this.titleTop_, 0, y, this.width_, y,
|
|
'center', font, null, fill);
|
|
}
|
|
if (this.titleBottom_) {
|
|
y = cy + Math.round(r * goog.ui.Gauge.FACTOR_TITLE_OFFSET);
|
|
graphics.drawTextOnLine(this.titleBottom_, 0, y, this.width_, y,
|
|
'center', font, null, fill);
|
|
}
|
|
}
|
|
|
|
// Draw tick marks
|
|
var majorTicks = this.majorTicks_;
|
|
var minorTicks = this.minorTicks_;
|
|
var rMajorTickInternal = r * goog.ui.Gauge.FACTOR_MAJOR_TICKS;
|
|
var rMinorTickInternal = r * goog.ui.Gauge.FACTOR_MINOR_TICKS;
|
|
var ticks = majorTicks * minorTicks;
|
|
var valueRange = this.maxValue_ - this.minValue_;
|
|
var tickValueSpan = valueRange / ticks;
|
|
var majorTicksPath = new goog.graphics.Path();
|
|
var minorTicksPath = new goog.graphics.Path();
|
|
|
|
var tickLabelFill = new goog.graphics.SolidFill(theme.getTickLabelColor());
|
|
var tickLabelFont = this.tickLabelFont_;
|
|
if (!tickLabelFont) {
|
|
tickLabelFont = new goog.graphics.Font(
|
|
Math.round(r * goog.ui.Gauge.FACTOR_TICK_LABEL_FONT_SIZE),
|
|
goog.ui.Gauge.TITLE_FONT_NAME);
|
|
}
|
|
var tickLabelFontSize = tickLabelFont.size;
|
|
|
|
for (var i = 0; i <= ticks; i++) {
|
|
var angle = this.valueToAngle_(i * tickValueSpan + this.minValue_);
|
|
var isMajorTick = i % minorTicks == 0;
|
|
var rInternal = isMajorTick ? rMajorTickInternal : rMinorTickInternal;
|
|
var path = isMajorTick ? majorTicksPath : minorTicksPath;
|
|
x = cx + goog.math.angleDx(angle, rInternal);
|
|
y = cy + goog.math.angleDy(angle, rInternal);
|
|
path.moveTo(x, y);
|
|
x = cx + goog.math.angleDx(angle, r);
|
|
y = cy + goog.math.angleDy(angle, r);
|
|
path.lineTo(x, y);
|
|
|
|
// Draw the tick's label for major ticks
|
|
if (isMajorTick && this.majorTickLabels_) {
|
|
var tickIndex = Math.floor(i / minorTicks);
|
|
var label = this.majorTickLabels_[tickIndex];
|
|
if (label) {
|
|
x = cx + goog.math.angleDx(angle, rInternal - tickLabelFontSize / 2);
|
|
y = cy + goog.math.angleDy(angle, rInternal - tickLabelFontSize / 2);
|
|
var x1, x2;
|
|
var align = 'center';
|
|
if (angle > 280 || angle < 90) {
|
|
align = 'right';
|
|
x1 = 0;
|
|
x2 = x;
|
|
} else if (angle >= 90 && angle < 260) {
|
|
align = 'left';
|
|
x1 = x;
|
|
x2 = this.width_;
|
|
} else {
|
|
// Values around top (angle 260-280) are centered around point
|
|
var dw = Math.min(x, this.width_ - x); // Nearest side border
|
|
x1 = x - dw;
|
|
x2 = x + dw;
|
|
y += Math.round(tickLabelFontSize / 4); // Movea bit down
|
|
}
|
|
graphics.drawTextOnLine(label, x1, y, x2, y,
|
|
align, tickLabelFont, null, tickLabelFill);
|
|
}
|
|
}
|
|
}
|
|
stroke = theme.getMinorTickStroke();
|
|
graphics.drawPath(minorTicksPath, stroke, null);
|
|
stroke = theme.getMajorTickStroke();
|
|
graphics.drawPath(majorTicksPath, stroke, null);
|
|
|
|
// Draw the needle and the value label. Stop animation when doing
|
|
// full redraw and jump to the final value position.
|
|
this.stopAnimation_();
|
|
this.valuePosition_ = this.valueToRangePosition_(this.value);
|
|
this.needleRadius_ = r;
|
|
this.drawValue_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Handle animation events while the hand is moving.
|
|
* @param {goog.fx.AnimationEvent} e The event.
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.onAnimate_ = function(e) {
|
|
this.needleValuePosition_ = e.x;
|
|
this.drawValue_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Handle animation events when hand move is complete.
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.onAnimateEnd_ = function() {
|
|
this.stopAnimation_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Stop the current animation, if it is active.
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.stopAnimation_ = function() {
|
|
if (this.animation_) {
|
|
goog.events.removeAll(this.animation_);
|
|
this.animation_.stop(false);
|
|
this.animation_ = null;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Convert a value to the position in the range. The returned position
|
|
* is a value between 0 and 1, where 0 indicates the lowest range value,
|
|
* 1 is the highest range, and any value in between is proportional
|
|
* to mapping the range to (0-1).
|
|
* If the value is not within the range, the returned value may be a bit
|
|
* lower than 0, or a bit higher than 1. This is done so that values out
|
|
* of range will be displayed just a bit outside of the gauge axis.
|
|
* @param {number} value The value to convert.
|
|
* @private
|
|
* @return {number} The range position.
|
|
*/
|
|
goog.ui.Gauge.prototype.valueToRangePosition_ = function(value) {
|
|
var valueRange = this.maxValue_ - this.minValue_;
|
|
var valuePct = (value - this.minValue_) / valueRange; // 0 to 1
|
|
|
|
// If value is out of range, trim it not to be too much out of range
|
|
valuePct = Math.max(valuePct,
|
|
-goog.ui.Gauge.MAX_EXCEED_POSITION_POSITION);
|
|
valuePct = Math.min(valuePct,
|
|
1 + goog.ui.Gauge.MAX_EXCEED_POSITION_POSITION);
|
|
|
|
return valuePct;
|
|
};
|
|
|
|
|
|
/**
|
|
* Convert a value to an angle based on the value range and angle span
|
|
* @param {number} value The value.
|
|
* @return {number} The angle where this value is located on the round
|
|
* axis, based on the range and angle span.
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.valueToAngle_ = function(value) {
|
|
var valuePct = this.valueToRangePosition_(value);
|
|
return this.valuePositionToAngle_(valuePct);
|
|
};
|
|
|
|
|
|
/**
|
|
* Convert a value-position (percent in the range) to an angle based on
|
|
* the angle span. A value-position is a value that has been proportinally
|
|
* adjusted to a value betwwen 0-1, proportionaly to the range.
|
|
* @param {number} valuePct The value.
|
|
* @return {number} The angle where this value is located on the round
|
|
* axis, based on the range and angle span.
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.valuePositionToAngle_ = function(valuePct) {
|
|
var startAngle = goog.math.standardAngle((360 - this.angleSpan_) / 2 + 90);
|
|
return this.angleSpan_ * valuePct + startAngle;
|
|
};
|
|
|
|
|
|
/**
|
|
* Draw the elements that depend on the current value (the needle and
|
|
* the formatted value). This function is called whenever a value is changed
|
|
* or when the entire gauge is redrawn.
|
|
* @private
|
|
*/
|
|
goog.ui.Gauge.prototype.drawValue_ = function() {
|
|
if (!this.isInDocument()) {
|
|
return;
|
|
}
|
|
|
|
var r = this.needleRadius_;
|
|
var graphics = this.graphics_;
|
|
var theme = this.theme_;
|
|
var cx = this.width_ / 2;
|
|
var cy = this.height_ / 2;
|
|
var angle = this.valuePositionToAngle_(
|
|
/** @type {number} */(this.needleValuePosition_));
|
|
|
|
// Compute the needle path
|
|
var frontRadius =
|
|
Math.round(r * goog.ui.Gauge.FACTOR_NEEDLE_FRONT);
|
|
var backRadius =
|
|
Math.round(r * goog.ui.Gauge.FACTOR_NEEDLE_BACK);
|
|
var frontDx = goog.math.angleDx(angle, frontRadius);
|
|
var frontDy = goog.math.angleDy(angle, frontRadius);
|
|
var backDx = goog.math.angleDx(angle, backRadius);
|
|
var backDy = goog.math.angleDy(angle, backRadius);
|
|
var angleRight = goog.math.standardAngle(angle + 90);
|
|
var distanceControlPointBase = r * goog.ui.Gauge.FACTOR_NEEDLE_WIDTH;
|
|
var controlPointMidDx = goog.math.angleDx(angleRight,
|
|
distanceControlPointBase);
|
|
var controlPointMidDy = goog.math.angleDy(angleRight,
|
|
distanceControlPointBase);
|
|
|
|
var path = new goog.graphics.Path();
|
|
path.moveTo(cx + frontDx, cy + frontDy);
|
|
path.curveTo(cx + controlPointMidDx, cy + controlPointMidDy,
|
|
cx - backDx + (controlPointMidDx / 2),
|
|
cy - backDy + (controlPointMidDy / 2),
|
|
cx - backDx, cy - backDy);
|
|
path.curveTo(cx - backDx - (controlPointMidDx / 2),
|
|
cy - backDy - (controlPointMidDy / 2),
|
|
cx - controlPointMidDx, cy - controlPointMidDy,
|
|
cx + frontDx, cy + frontDy);
|
|
|
|
// Draw the needle hinge
|
|
var rh = Math.round(r * goog.ui.Gauge.FACTOR_NEEDLE_HINGE);
|
|
|
|
// Clean previous needle
|
|
var needleGroup = this.needleGroup_;
|
|
if (needleGroup) {
|
|
needleGroup.clear();
|
|
} else {
|
|
needleGroup = this.needleGroup_ = graphics.createGroup();
|
|
}
|
|
|
|
// Draw current formatted value if provided.
|
|
if (this.formattedValue_) {
|
|
var font = this.valueFont_;
|
|
if (!font) {
|
|
var fontSize =
|
|
Math.round(r * goog.ui.Gauge.FACTOR_VALUE_FONT_SIZE);
|
|
font = new goog.graphics.Font(fontSize,
|
|
goog.ui.Gauge.TITLE_FONT_NAME);
|
|
font.bold = true;
|
|
this.valueFont_ = font;
|
|
}
|
|
var fill = new goog.graphics.SolidFill(theme.getValueColor());
|
|
var y = cy + Math.round(r * goog.ui.Gauge.FACTOR_VALUE_OFFSET);
|
|
graphics.drawTextOnLine(this.formattedValue_, 0, y, this.width_, y,
|
|
'center', font, null, fill, needleGroup);
|
|
}
|
|
|
|
// Draw the needle
|
|
var stroke = theme.getNeedleStroke();
|
|
var fill = theme.getNeedleFill(cx, cy, rh);
|
|
graphics.drawPath(path, stroke, fill, needleGroup);
|
|
stroke = theme.getHingeStroke();
|
|
fill = theme.getHingeFill(cx, cy, rh);
|
|
graphics.drawCircle(cx, cy, rh, stroke, fill, needleGroup);
|
|
};
|
|
|
|
|
|
/**
|
|
* Redraws the entire gauge.
|
|
* Should be called after theme colors have been changed.
|
|
*/
|
|
goog.ui.Gauge.prototype.redraw = function() {
|
|
this.draw_();
|
|
};
|
|
|
|
|
|
/** @override */
|
|
goog.ui.Gauge.prototype.enterDocument = function() {
|
|
goog.ui.Gauge.superClass_.enterDocument.call(this);
|
|
|
|
// set roles and states
|
|
var el = this.getElement();
|
|
goog.asserts.assert(el, 'The DOM element for the gauge cannot be null.');
|
|
goog.a11y.aria.setRole(el, 'progressbar');
|
|
goog.a11y.aria.setState(el, 'live', 'polite');
|
|
goog.a11y.aria.setState(el, 'valuemin', this.minValue_);
|
|
goog.a11y.aria.setState(el, 'valuemax', this.maxValue_);
|
|
goog.a11y.aria.setState(el, 'valuenow', this.value_);
|
|
this.draw_();
|
|
};
|
|
|
|
|
|
/** @override */
|
|
goog.ui.Gauge.prototype.exitDocument = function() {
|
|
goog.ui.Gauge.superClass_.exitDocument.call(this);
|
|
this.stopAnimation_();
|
|
};
|
|
|
|
|
|
/** @override */
|
|
goog.ui.Gauge.prototype.disposeInternal = function() {
|
|
this.stopAnimation_();
|
|
this.graphics_.dispose();
|
|
delete this.graphics_;
|
|
delete this.needleGroup_;
|
|
delete this.theme_;
|
|
delete this.rangeColors_;
|
|
goog.ui.Gauge.superClass_.disposeInternal.call(this);
|
|
};
|