15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/*
25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Copyright (c) 2012 The Chromium Authors. All rights reserved.
35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  Use of this source code is governed by a BSD-style license that can be
45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  found in the LICENSE file.
55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)*/
65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @fileoverview Collection of functions and classes used to plot data in a
95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     <canvas>.  Create a Plotter() to generate a plot.
105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Adds commas to a given number.
145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Examples:
165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *  1234.56 => "1,234.56"
175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *  99999 => "99,999"
185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string|number} number The number to format.
205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {string} String representation of |number| with commas for every
215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     three digits to the left of a decimal point.
225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function addCommas(number) {
245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  number += '';  // Convert number to string if not already a string.
255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var numberParts = number.split('.');
265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var integralPart = numberParts[0];
275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var fractionalPart = numberParts.length > 1 ? '.' + numberParts[1] : '';
285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var reThreeDigits = /(\d+)(\d{3})/;
295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while (reThreeDigits.test(integralPart))
305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    integralPart = integralPart.replace(reThreeDigits, '$1' + ',' + '$2');
315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return integralPart + fractionalPart;
325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Vertical marker to highlight data points that are being hovered over by the
365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * mouse.
375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} color The color to make the marker, e.g., 'rgb(100,80,240)'.
395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {Element} A div Element object representing the vertical marker.
405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function VerticalMarker(color) {
425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var m = document.createElement('div');
435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  m.style.backgroundColor = color;
445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  m.style.opacity = '0.3';
455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  m.style.position = 'absolute';
465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  m.style.left = '-2px';
475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  m.style.top = '-2px';
485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  m.style.width = '0px';
495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  m.style.height = '0px';
505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return m;
515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Class representing a horizontal marker at the indicated mouse location.
555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @constructor
565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Element} canvasElement The canvas bounds.
585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Number} yValue The data value corresponding to the vertical click
595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     location.
605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Number} yOtherValue If the plot is overlaying two coordinate systems,
615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     this is the data value corresponding to the vertical click location in
625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     the second coordinate system.  Can be null.
635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function HorizontalMarker(canvasElement, yValue, yOtherValue) {
655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var m = document.createElement('div');
665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  m.style.backgroundColor = HorizontalMarker.COLOR;
675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  m.style.opacity = '0.3';
685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  m.style.position = 'absolute';
695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  m.style.width = canvasElement.offsetWidth + 'px';
705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  m.style.height = HorizontalMarker.HEIGHT + 'px';
715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.markerDiv = m;
735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.value = yValue;
745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.otherValue = yOtherValue;
755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HorizontalMarker.HEIGHT = 5;
785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HorizontalMarker.COLOR = 'rgb(0,100,100)';
795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Locates this element at a specified position.
825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Element} canvasElement The canvas element at which this element is
845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     to be placed.
855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} y Y position relative to the canvas element.
865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HorizontalMarker.prototype.locateAt = function(canvasElement, y) {
885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var div = this.markerDiv;
895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  div.style.left = domUtils.pageXY(canvasElement).x -
905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   domUtils.pageXY(div.offsetParent) + 'px';
915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  div.style.top = (y + domUtils.pageXY(canvasElement).y
925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   - domUtils.pageXY(div.offsetParent).y
935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   - (HorizontalMarker.HEIGHT / 2)) + 'px';
945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Removes the horizontal marker from the graph.
985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HorizontalMarker.prototype.remove = function() {
1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.markerDiv.parentNode.removeChild(this.markerDiv);
1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * An information indicator hovering around the mouse cursor on the graph.
1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * This class is used to show a legend near the mouse cursor.
1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * A set of legends under the graph is managed separately in
1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * {@code Plotter.createLegendsSummaryElement_}.
1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @constructor
1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function HoveringInfo() {
1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.containerDiv_ = document.createElement('div');
1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.containerDiv_.style.display = 'none';
1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.containerDiv_.style.position = 'absolute';
1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.containerDiv_.style.border = '1px solid #000';
1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.containerDiv_.style.padding = '0.12em';
1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.containerDiv_.style.backgroundColor = '#ddd';
1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.colorIndicator_ = document.createElement('div');
1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.colorIndicator_.style.display = 'inline-block';
1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.colorIndicator_.style.width = '1em';
1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.colorIndicator_.style.height = '1em';
1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.colorIndicator_.style.verticalAlign = 'text-bottom';
1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.colorIndicator_.style.margin = '0 0.24em 0 0';
1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.colorIndicator_.style.border = '1px solid #000';
1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.legendText_ = document.createElement('span');
1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.itemValueText_ = document.createElement('span');
1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.containerDiv_.appendChild(this.colorIndicator_);
1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.containerDiv_.appendChild(this.legendText_);
1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var div = document.createElement('div');
1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  div.appendChild(this.itemValueText_);
1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.containerDiv_.appendChild(div);
1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Returns the container element;
1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {Element} The container element.
1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HoveringInfo.prototype.getElement = function() {
1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return this.containerDiv_;
1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Shows or hides the element.
1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {boolean} show Shows the element if true, or hides it.
1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HoveringInfo.prototype.show = function(show) {
1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.containerDiv_.style.display = show ? 'block' : 'none';
1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Returns the position of the container element in the page coordinate.
1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {Object} A point object which has {@code x} and {@code y} fields.
1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HoveringInfo.prototype.pageXY = function() {
1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return domUtils.pageXY(this.containerDiv_);
1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Locates the element at the specified position.
1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} x X position in the page coordinate.
1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} y Y position in the page coordinate.
1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HoveringInfo.prototype.locateAtPageXY = function(x, y) {
1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var parentXY = domUtils.pageXY(this.containerDiv_.offsetParent);
1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.containerDiv_.style.left = x - parentXY.x + 'px';
1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.containerDiv_.style.top = y - parentXY.y + 'px';
1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Returns the legend text.
1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {?string} The legend text.
1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HoveringInfo.prototype.getLegendText = function() {
1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return this.legendText_.textContent;
1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Changes the legend text.
1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} text The new text to be set.
1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HoveringInfo.prototype.setLegendText = function(text) {
1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.legendText_.textContent = text;
1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Changes the item value.
1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} value The new value to be shown.
1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HoveringInfo.prototype.setItemValue = function(value) {
1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.itemValueText_.textContent = 'Item value = ' + addCommas(value);
2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Changes the color of the color indicator.
2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} color The new color to be set.
2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)HoveringInfo.prototype.setColorIndicator = function(color) {
2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.colorIndicator_.style.backgroundColor = color;
2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Main class that does the actual plotting.
2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Draws a chart using a canvas element. Takes an array of lines to draw.
2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @constructor
2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array} plotData list of arrays that represent individual lines. The
2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     line itself is an Array of points.
2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array} dataDescriptions list of data descriptions for each line in
2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     |plotData|.
2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} eventName The string name of an event to overlay on the
2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     graph.  Should be 'null' if there are no events to overlay.
2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} eventInfo If |eventName| is specified, an array of event
2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     points to overlay on the graph.  Each event point in the array is itself
2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     a 2-element array, where the first element is the x-axis value at which
2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     the event occurred during the test, and the second element is a
2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     dictionary of kay/value pairs representing metadata associated with the
2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     event.
2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} unitsX The x-axis units of the data being plotted.
2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} unitsY The y-axis units of the data being plotted.
2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} unitsYOther If another graph (with different y-axis units) is
2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     being overlayed over the first graph, this represents the units of the
2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     other graph.  Otherwise, this should be 'null'.
2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {?number} graphsOtherStartIndex Specifies the starting index of
2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     the second set of lines.  {@code plotData} in the range of
2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     [0, {@code graphsOtherStartIndex}) are treated as the first set of lines,
2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     and ones in the range of
2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     [{@code graphsOtherStartIndex}, {@code plotData.length}) are as
2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     the second set.  0, {@code plotData.length} and {@code null} mean
2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     no second set, i.e. all the data in {@code plotData} represent the single
2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     set of lines.
2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Element} resultNode A DOM Element object representing the DOM node to
2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     which the plot should be attached.
2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {boolean} is_lookout Whether or not the graph should be drawn
2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     in 'lookout' mode, which is a summarized view that is made for overview
2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     pages when the graph is drawn in a more confined space.
2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {boolean} stackedGraph Whether or not the first set of lines is
2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     a stacked graph.
2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {boolean} stackedGraphOther Whether or not the second set of lines is
2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     a stacked graph.
2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Example of the |plotData|:
2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *  [
2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *    [line 1 data],
2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *    [line 2 data]
2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *  ].
2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *  Line data looks like  [[point one], [point two]].
2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *  And individual points are [x value, y value]
2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)function Plotter(plotData, dataDescriptions, eventName, eventInfo, unitsX,
2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 unitsY, unitsYOther, graphsOtherStartIndex, resultNode,
2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 is_lookout, stackedGraph, stackedGraphOther) {
2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.plotData_ = plotData;
2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.dataDescriptions_ = dataDescriptions;
2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.eventName_ = eventName;
2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.eventInfo_ = eventInfo;
2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.unitsX_ = unitsX;
2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.unitsY_ = unitsY;
2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.unitsYOther_ = unitsYOther;
2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.graphsOtherStartIndex_ =
2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      (0 < graphsOtherStartIndex && graphsOtherStartIndex < plotData.length) ?
2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      graphsOtherStartIndex : null;
2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.resultNode_ = resultNode;
2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.is_lookout_ = is_lookout;
2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.stackedGraph_ = stackedGraph;
2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.stackedGraphOther_ = stackedGraphOther;
2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.dataColors_ = [];
2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.coordinates = null;
2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.coordinatesOther = null;
2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.unitsYOther_ && this.graphsOtherStartIndex_) {
2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Need two different coordinate systems to overlay on the same graph.
2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.coordinates = new Coordinates(
2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.plotData_.slice(0, this.graphsOtherStartIndex_));
2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.coordinatesOther = new Coordinates(
2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.plotData_.slice(this.graphsOtherStartIndex_));
2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.coordinates = new Coordinates(this.plotData_);
2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // A color palette that's unambigous for normal and color-deficient viewers.
2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Values are (red, green, blue) on a scale of 255.
2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Taken from http://jfly.iam.u-tokyo.ac.jp/html/manuals/pdf/color_blind.pdf.
2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.colors = [[0, 114, 178],   // Blue.
2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 [230, 159, 0],   // Orange.
2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 [0, 158, 115],   // Green.
2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 [204, 121, 167], // Purplish pink.
2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 [86, 180, 233],  // Sky blue.
3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 [213, 94, 0],    // Dark orange.
3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 [0, 0, 0],       // Black.
3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                 [240, 228, 66]   // Yellow.
3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                ];
3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var i = 0, colorIndex = 0; i < this.dataDescriptions_.length; ++i)
3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.dataColors_[i] = this.makeColor(colorIndex++);
3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}
3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Generates a string representing a color corresponding to the given index
3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * in a color array.  Handles wrapping around the color array if necessary.
3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} i An index into the |this.colors| array.
3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {string} A string representing a color in 'rgb(X,Y,Z)' format.
3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.makeColor = function(i) {
3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var index = i % this.colors.length;
3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 'rgb(' + this.colors[index][0] + ',' +
3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  this.colors[index][1] + ',' +
3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  this.colors[index][2] + ')';
3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Same as function makeColor above, but also takes a transparency value
3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * indicating how transparent to make the color appear.
3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} i An index into the |this.colors| array.
3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} transparencyPercent Percentage transparency to make the
3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     color, e.g., 0.75.
3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {string} A string representing a color in 'rgb(X,Y,Z,A)' format,
3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     where A is the percentage transparency.
3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.makeColorTransparent = function(i, transparencyPercent) {
3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var index = i % this.colors.length;
3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return 'rgba(' + this.colors[index][0] + ',' +
3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   this.colors[index][1] + ',' +
3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                   this.colors[index][2] + ',' + transparencyPercent + ')';
3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Gets the data color value associated with a specified color index.
3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} i An index into the |this.colors| array.
3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {string} A string representing a color in 'rgb(X,Y,Z,A)' format,
3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     where A is the percentage transparency.
3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.getDataColor = function(i) {
3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.dataColors_[i])
3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return this.dataColors_[i];
3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else
3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return this.makeColor(i);
3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Gets the fill color value associated with a specified color index.
3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} i An index into the |this.colors| array.
3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {string} A string representing a color in 'rgba(R,G,B,A)' format,
3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     where A is the percentage transparency.
3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.getFillColor = function(i) {
3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return this.makeColorTransparent(i, 0.4);
3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Does the actual plotting.
3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.plot = function() {
3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var self = this;
3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.canvasElement_ = this.canvas_();
3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.rulerDiv_ = this.ruler_();
3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Markers for the result point(s)/events that the mouse is currently
3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // hovering over.
3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.cursorDiv_ = new VerticalMarker('rgb(100,80,240)');
3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.cursorDivOther_ = new VerticalMarker('rgb(50,50,50)');
3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.eventDiv_ = new VerticalMarker('rgb(255, 0, 0)');
3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.hoveringInfo_ = new HoveringInfo();
3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.resultNode_.appendChild(this.canvasElement_);
3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.resultNode_.appendChild(this.coordinates_());
3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.resultNode_.appendChild(this.rulerDiv_);
3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.resultNode_.appendChild(this.cursorDiv_);
3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.resultNode_.appendChild(this.cursorDivOther_);
3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.resultNode_.appendChild(this.eventDiv_);
3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.resultNode_.appendChild(this.hoveringInfo_.getElement());
3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.attachEventListeners_();
3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Now draw the canvas.
3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var ctx = this.canvasElement_.getContext('2d');
3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Clear it with white: otherwise canvas will draw on top of existing data.
3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.clearRect(0, 0, this.canvasElement_.width, this.canvasElement_.height);
3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Draw all data lines in the reverse order so the last graph appears on
3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // the backmost and the first graph appears on the frontmost.
3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function draw(plotData, coordinates, colorOffset, stack) {
3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (var i = plotData.length - 1; i >= 0; --i) {
4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (stack) {
4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.plotAreaUnderLine_(ctx, self.getFillColor(colorOffset + i),
4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                plotData[i], coordinates);
4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      self.plotLine_(ctx, self.getDataColor(colorOffset + i),
4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                     plotData[i], coordinates);
4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  draw(this.plotData_.slice(0,
4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            this.graphsOtherStartIndex_ ?
4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            this.graphsOtherStartIndex_ :
4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                            this.plotData_.length),
4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)       this.coordinates, 0, this.stackedGraph_);
4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.graphsOtherStartIndex_) {
4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    draw(this.plotData_.slice(this.graphsOtherStartIndex_),
4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         this.unitsYOther_ ? this.coordinatesOther : this.coordinates,
4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         this.graphsOtherStartIndex_, this.stackedGraphOther_);
4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Draw events overlayed on graph if needed.
4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.eventName_ && this.eventInfo_)
4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.plotEvents_(ctx, 'rgb(255, 150, 150)', this.coordinates);
4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.graduation_divs_ = this.graduations_(this.coordinates, 0, false);
4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.unitsYOther_) {
4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.graduation_divs_ = this.graduation_divs_.concat(
4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.graduations_(this.coordinatesOther, 1, true));
4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var i = 0; i < this.graduation_divs_.length; ++i)
4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.resultNode_.appendChild(this.graduation_divs_[i]);
4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Draws events overlayed on top of an existing graph.
4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} ctx A canvas element object for drawing.
4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} strokeStyles A string representing the drawing style.
4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} coordinateSystem A Coordinates object representing the
4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     coordinate system of the graph.
4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.plotEvents_ = function(ctx, strokeStyles, coordinateSystem) {
4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.strokeStyle = strokeStyles;
4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.fillStyle = strokeStyles;
4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.lineWidth = 1.0;
4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.beginPath();
4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var data = this.eventInfo_;
4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var index = 0; index < data.length; ++index) {
4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var event_time = data[index][0];
4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var x = coordinateSystem.xPixel(event_time);
4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ctx.moveTo(x, 0);
4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ctx.lineTo(x, this.canvasElement_.offsetHeight);
4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.closePath();
4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.stroke();
4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
4585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Draws a line on the graph.
4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} ctx A canvas element object for drawing.
4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} strokeStyles A string representing the drawing style.
4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array} data A list of [x, y] values representing the line to plot.
4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} coordinateSystem A Coordinates object representing the
4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     coordinate system of the graph.
4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.plotLine_ = function(ctx, strokeStyles, data,
4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                       coordinateSystem) {
4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.strokeStyle = strokeStyles;
4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.fillStyle = strokeStyles;
4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.lineWidth = 2.0;
4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.beginPath();
4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var initial = true;
4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var allPoints = [];
4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var i = 0; i < data.length; ++i) {
4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var pointX = parseFloat(data[i][0]);
4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var pointY = parseFloat(data[i][1]);
4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var x = coordinateSystem.xPixel(pointX);
4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var y = coordinateSystem.yPixel(0);
4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (isNaN(pointY)) {
4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // Re-set 'initial' if we're at a gap in the data.
4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      initial = true;
4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      y = coordinateSystem.yPixel(pointY);
4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (initial)
4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        initial = false;
4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      else
4885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ctx.lineTo(x, y);
4895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    ctx.moveTo(x, y);
4925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!data[i].interpolated) {
4935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      allPoints.push([x, y]);
4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.closePath();
4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.stroke();
4985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
4995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!this.is_lookout_) {
5005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Draw a small dot at each point.
5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (var i = 0; i < allPoints.length; ++i) {
5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ctx.beginPath();
5035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ctx.arc(allPoints[i][0], allPoints[i][1], 3, 0, Math.PI*2, true);
5045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ctx.fill();
5055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
5075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
5085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
5105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Fills an area under the given line on the graph.
5115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
5125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} ctx A canvas element object for drawing.
5135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {string} fillStyle A string representing the drawing style.
5145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Array} data A list of [x, y] values representing the line to plot.
5155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} coordinateSystem A Coordinates object representing the
5165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     coordinate system of the graph.
5175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
5185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.plotAreaUnderLine_ = function(ctx, fillStyle, data,
5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                                coordinateSystem) {
5205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!data[0]) {
5215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;  // nothing to draw
5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
5235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.beginPath();
5255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var x = coordinateSystem.xPixel(parseFloat(data[0][0]) || 0);
5265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var y = coordinateSystem.yPixel(parseFloat(data[0][1]) || 0);
5275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var y0 = coordinateSystem.yPixel(coordinateSystem.yMinValue());
5285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.moveTo(x, y0);
5295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var point, i = 0; point = data[i]; ++i) {
5305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var pointX = parseFloat(point[0]);
5315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var pointY = parseFloat(point[1]);
5325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (isNaN(pointX)) { continue; }  // Skip an invalid point.
5335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (isNaN(pointY)) {
5345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ctx.lineTo(x, y0);
5355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var yWasNaN = true;
5365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
5375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      x = coordinateSystem.xPixel(pointX);
5385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      y = coordinateSystem.yPixel(pointY);
5395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (yWasNaN) {
5405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ctx.lineTo(x, y0);
5415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        yWasNaN = false;
5425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
5435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      ctx.lineTo(x, y);
5445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
5455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
5465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.lineTo(x, y0);
5475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.lineWidth = 0;
5495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Clear the area with white color first.
5505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var COLOR_WHITE = 'rgb(255,255,255)';
5515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.strokeStyle = COLOR_WHITE;
5525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.fillStyle = COLOR_WHITE;
5535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.fill();
5545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Then, fill the area with the specified color.
5555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.strokeStyle = fillStyle;
5565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.fillStyle = fillStyle;
5575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ctx.fill();
5585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
5595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
5615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Attaches event listeners to DOM nodes.
5625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
5635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.attachEventListeners_ = function() {
5645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var self = this;
5655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.canvasElement_.parentNode.addEventListener(
5665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'mousemove', function(evt) { self.onMouseMove_(evt); }, false);
5675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.canvasElement_.parentNode.addEventListener(
5685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'mouseover', function(evt) { self.onMouseOver_(evt); }, false);
5695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.canvasElement_.parentNode.addEventListener(
5705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'mouseout', function(evt) { self.onMouseOut_(evt); }, false);
5715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.cursorDiv_.addEventListener(
5725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'click', function(evt) { self.onMouseClick_(evt); }, false);
5735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.cursorDivOther_.addEventListener(
5745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'click', function(evt) { self.onMouseClick_(evt); }, false);
5755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.eventDiv_.addEventListener(
5765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    'click', function(evt) { self.onMouseClick_(evt); }, false);
5775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
5785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
5805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Update the horizontal line that is following where the mouse is hovering.
5815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
5825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} evt A mouse event object representing a mouse move event.
5835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
5845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.updateRuler_ = function(evt) {
5855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var r = this.rulerDiv_;
5865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  r.style.left = this.canvasElement_.offsetLeft + 'px';
5875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  r.style.top = this.canvasElement_.offsetTop + 'px';
5885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  r.style.width = this.canvasElement_.offsetWidth + 'px';
5895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var h = domUtils.pageXYOfEvent(evt).y -
5905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          domUtils.pageXY(this.canvasElement_).y;
5915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (h > this.canvasElement_.offsetHeight)
5925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    h = this.canvasElement_.offsetHeight;
5935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  r.style.height = h + 'px';
5945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
5955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
5965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
5975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Update the highlighted data point at the x value that the mouse is hovering
5985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * over.
5995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
6005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} coordinateSystem A Coordinates object representing the
6015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     coordinate system of the graph.
6025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} currentIndex The index into the |this.plotData| array of the
6035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     data point being hovered over, for a given line.
6045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} cursorDiv A DOM element div object representing the highlight
6055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     itself.
6065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} dataIndex The index into the |this.plotData| array of the
6075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     line being hovered over.
6085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
6095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.updateCursor_ = function(coordinateSystem, currentIndex,
6105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                           cursorDiv, dataIndex) {
6115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var c = cursorDiv;
6125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  c.style.top = this.canvasElement_.offsetTop + 'px';
6135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  c.style.height = this.canvasElement_.offsetHeight + 'px';
6145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Left point is half-way to the previous x value, unless it's the first
6165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // point, in which case it's the x value of the current point.
6175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var leftPoint = null;
6185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (currentIndex == 0) {
6195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    leftPoint = this.canvasElement_.offsetLeft +
6205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        coordinateSystem.xPixel(this.plotData_[dataIndex][0][0]);
6215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
6225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else {
6235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var left_x = this.canvasElement_.offsetLeft +
6245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        coordinateSystem.xPixel(this.plotData_[dataIndex][currentIndex - 1][0]);
6255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var curr_x = this.canvasElement_.offsetLeft +
6265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        coordinateSystem.xPixel(this.plotData_[dataIndex][currentIndex][0]);
6275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    leftPoint = (left_x + curr_x) / 2;
6285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
6295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  c.style.left = leftPoint;
6305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Width is half-way to the next x value minus the left point, unless it's
6325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // the last point, in which case it's the x value of the current point minus
6335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // the left point.
6345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (currentIndex == this.plotData_[dataIndex].length - 1) {
6355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var curr_x = this.canvasElement_.offsetLeft +
6365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        coordinateSystem.xPixel(this.plotData_[dataIndex][currentIndex][0]);
6375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    c.style.width = curr_x - leftPoint;
6385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
6395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  else {
6405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var next_x = this.canvasElement_.offsetLeft +
6415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        coordinateSystem.xPixel(this.plotData_[dataIndex][currentIndex + 1][0]);
6425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var curr_x = this.canvasElement_.offsetLeft +
6435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        coordinateSystem.xPixel(this.plotData_[dataIndex][currentIndex][0]);
6445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    c.style.width = ((next_x + curr_x) / 2) - leftPoint;
6455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
6465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
6475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
6495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Update the highlighted event at the x value that the mouse is hovering over.
6505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
6515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} x The x-value (pixel) at which to draw the event highlight
6525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     div.
6535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {boolean} show Whether or not to show the highlight div.
6545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
6555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.updateEventDiv_ = function(x, show) {
6565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var c = this.eventDiv_;
6575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  c.style.top = this.canvasElement_.offsetTop + 'px';
6585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  c.style.height = this.canvasElement_.offsetHeight + 'px';
6595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (show) {
6615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    c.style.left = this.canvasElement_.offsetLeft + (x - 2);
6625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    c.style.width = 8;
6635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
6645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    c.style.width = 0;
6655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
6665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
6675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
6695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Updates the hovering information.
6705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
6715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Event} evt An event object, which specifies the position of the mouse
6725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     cursor.
6735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {boolean} show Whether or not to show the hovering info.  Even if it's
6745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     true, if the cursor position is out of the appropriate area, nothing will
6755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     be shown.
6765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
6775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.updateHoveringInfo_ = function(evt, show) {
6785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var evtPageXY = domUtils.pageXYOfEvent(evt);
6795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var hoveringInfoPageXY = this.hoveringInfo_.pageXY();
6805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var canvasPageXY = domUtils.pageXY(this.canvasElement_);
6815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var coord = this.coordinates;
6835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // p = the mouse cursor position in value coordinates.
6845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var p = {'x': coord.xValue(evtPageXY.x - canvasPageXY.x),
6855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)           'y': coord.yValue(evtPageXY.y - canvasPageXY.y)};
6865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!show ||
6875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      !(this.stackedGraph_ || this.stackedGraphOther_) ||
6885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      p.x < coord.xMinValue() || coord.xMaxValue() < p.x ||
6895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      p.y < coord.yMinValue() || coord.yMaxValue() < p.y) {
6905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.hoveringInfo_.show(false);
6915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
6925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {
6935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.hoveringInfo_.show(true);
6945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
6955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
6965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  /**
6975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * Finds the closest lines (upside and downside of the cursor position).
6985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * Returns a set of upside/downside line indices and point index on success
6995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   * or null.
7005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)   */
7015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function findClosestLines(lines, opt_startIndex, opt_endIndex) {
7025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var offsetIndex = opt_startIndex || 0;
7035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    lines =
7045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        opt_endIndex != null ? lines.slice(offsetIndex, opt_endIndex) :
7055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        opt_startIndex != null ? lines.slice(offsetIndex) :
7065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        lines;
7075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var upsideClosestLineIndex = null;
7095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var upsideClosestYDistance = coord.yValueRange();
7105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var downsideClosestLineIndex = null;
7115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var downsideClosestYDistance = coord.yValueRange();
7125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var upsideClosestPointIndex = null;
7135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (var lineIndex = 0, line; line = lines[lineIndex]; ++lineIndex) {
7155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      for (var i = 1; line[i]; ++i) {
7165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        var p0 = line[i - 1], p1 = line[i];
7175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        if (p0[0] <= p.x && p.x < p1[0]) {
7185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          // Calculate y-value of the line at p.x, which is the cursor point.
7195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          var y = (p.x - p0[0]) / (p1[0] - p0[0]) * (p1[1] - p0[1]) + p0[1];
7205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          if (p.y < y && y - p.y < upsideClosestYDistance) {
7215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            upsideClosestLineIndex = lineIndex;
7225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            upsideClosestYDistance = y - p.y;
7235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            if (p.x - p0[0] < p1[0] - p.x) {
7255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              upsideClosestPointIndex = i - 1;
7265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            } else {
7275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)              upsideClosestPointIndex = i;
7285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            }
7295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          } else if (y <= p.y && p.y - y < downsideClosestYDistance) {
7305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            downsideClosestLineIndex = lineIndex;
7315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            downsideClosestYDistance = p.y - y;
7325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          }
7335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          break;
7345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        }
7355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
7365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
7375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return (upsideClosestLineIndex != null &&
7395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            upsideClosestPointIndex != null) ?
7405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        {'upsideLineIndex': offsetIndex + upsideClosestLineIndex,
7415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         'downsideLineIndex': downsideClosestYDistance == null ? null :
7425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                              offsetIndex + downsideClosestLineIndex,
7435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         'upsidePointIndex': offsetIndex + upsideClosestPointIndex} :
7445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        null;
7455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
7465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Find the closest lines above and below the mouse cursor.
7485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var closest = null;
7495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Since the other set of graphs are drawn over the first set, try to find
7505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // the closest lines from the other set of graphs first.
7515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.graphsOtherStartIndex_ && this.stackedGraphOther_) {
7525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    closest = findClosestLines(this.plotData_, this.graphsOtherStartIndex_);
7535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
7545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!closest && this.stackedGraph_) {
7555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    closest = this.graphsOtherStartIndex_ ?
7565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        findClosestLines(this.plotData_, 0, this.graphsOtherStartIndex_) :
7575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        findClosestLines(this.plotData_);
7585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
7595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!closest) {
7605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.hoveringInfo_.show(false);
7615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return;
7625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
7635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Update the contents of the hovering info box.
7655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Color indicator, description and the value of the item.
7665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.hoveringInfo_.setColorIndicator(
7675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.getDataColor(closest.upsideLineIndex));
7685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.hoveringInfo_.setLegendText(
7695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.dataDescriptions_[closest.upsideLineIndex]);
7705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var y1 = this.plotData_[closest.upsideLineIndex][closest.upsidePointIndex][1];
7715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var y0 = closest.downsideLineIndex == null ?
7725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      0 :
7735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.plotData_[closest.downsideLineIndex][closest.upsidePointIndex][1];
7745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.hoveringInfo_.setItemValue(y1 - y0);
7755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Locate the hovering info box near the mouse cursor.
7775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var DIV_X_OFFSET = 10, DIV_Y_OFFSET = -20;
7785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (evtPageXY.x + this.hoveringInfo_.getElement().offsetWidth <
7795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      canvasPageXY.x + this.canvasElement_.offsetWidth) {
7805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.hoveringInfo_.locateAtPageXY(evtPageXY.x + DIV_X_OFFSET,
7815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                      evtPageXY.y + DIV_Y_OFFSET);
7825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else {  // If lacking space at the right side, locate it at the left side.
7835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.hoveringInfo_.locateAtPageXY(
7845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      evtPageXY.x - this.hoveringInfo_.getElement().offsetWidth - DIV_X_OFFSET,
7855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      evtPageXY.y + DIV_Y_OFFSET);
7865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
7875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
7885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
7905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Handle a mouse move event.
7915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
7925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} evt A mouse event object representing a mouse move event.
7935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
7945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.onMouseMove_ = function(evt) {
7955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var self = this;
7965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
7975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var canvas = evt.currentTarget.firstChild;
7985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var evtPageXY = domUtils.pageXYOfEvent(evt);
7995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var canvasPageXY = domUtils.pageXY(this.canvasElement_);
8005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var positionX = evtPageXY.x - canvasPageXY.x;
8015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var positionY = evtPageXY.y - canvasPageXY.y;
8025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Identify the index of the x value that is closest to the mouse x value.
8045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var xValue = this.coordinates.xValue(positionX);
8055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var lineIndex = !this.stackedGraph_ ? 0 :
8065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.graphsOtherStartIndex_ ? this.graphsOtherStartIndex_ - 1 :
8075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.plotData_.length - 1;
8085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var line = this.plotData_[lineIndex];
8095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var min_diff = Math.abs(line[0][0] - xValue);
8105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  indexValueX = 0;
8115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var i = 1; i < line.length; ++i) {
8125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var diff = Math.abs(line[i][0] - xValue);
8135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (diff < min_diff) {
8145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      min_diff = diff;
8155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      indexValueX = i;
8165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
8175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
8185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Identify the index of the x value closest to the mouse x value for the
8205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // other graph being overlayed on top of the original graph, if one exists.
8215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.unitsYOther_) {
8225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var xValue = this.coordinatesOther.xValue(positionX);
8235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var lineIndexOther = !this.stackedGraphOther_ ?
8245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.graphsOtherStartIndex_ : this.plotData_.length - 1;
8255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var lineOther = this.plotData_[lineIndexOther];
8265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var min_diff = Math.abs(lineOther[0][0] - xValue);
8275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var indexValueXOther = 0;
8285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (var i = 1; i < lineOther.length; ++i) {
8295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var diff = Math.abs(lineOther[i][0] - xValue);
8305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (diff < min_diff) {
8315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        min_diff = diff;
8325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        indexValueXOther = i;
8335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
8345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
8355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
8365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Update coordinate information displayed directly underneath the graph.
8385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function legendLabel(lineIndex, opt_labelText) {
8395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return '<span style="color:' + self.getDataColor(lineIndex) + '">' +
8405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (opt_labelText || self.dataDescriptions_[lineIndex]) +
8415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        '</span>:&nbsp;&nbsp;';
8425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
8435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  function valuesAtCursor(lineIndex, pointIndex, unitsY, yValue) {
8445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    return '<span style="color:' + self.getDataColor(lineIndex) + '">' +
8455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        self.plotData_[lineIndex][pointIndex][0] + ' ' + self.unitsX_ + ': ' +
8465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        addCommas(self.plotData_[lineIndex][pointIndex][1].toFixed(2)) + ' ' +
8475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        unitsY  + '</span> [hovering at ' + addCommas(yValue.toFixed(2)) +
8485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        ' ' + unitsY + ']';
8495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
8505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.infoBox_.rows[0].label.innerHTML = legendLabel(lineIndex);
8525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.infoBox_.rows[0].content.innerHTML = valuesAtCursor(
8535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    lineIndex, indexValueX, this.unitsY_, this.coordinates.yValue(positionY));
8545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var row = this.infoBox_.rows[1];
8555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.unitsYOther_) {
8565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    row.label.innerHTML = legendLabel(lineIndexOther);
8575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    row.content.innerHTML = valuesAtCursor(
8585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      lineIndexOther, indexValueXOther, this.unitsYOther_,
8595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.coordinatesOther.yValue(positionY));
8605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else if (this.graphsOtherStartIndex_) {
8615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    row.label.innerHTML = legendLabel(
8625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.stackedGraphOther_ ?
8635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          this.plotData_.length - 1 : this.graphsOtherStartIndex_);
8645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    row.content.innerHTML = valuesAtCursor(
8655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.stackedGraphOther_ ?
8665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          this.plotData_.length - 1 : this.graphsOtherStartIndex_,
8675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      indexValueX, this.unitsY_, this.coordinates.yValue(positionY));
8685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else if (!this.stackedGraph_ && this.dataDescriptions_.length > 1) {
8695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    row.label.innerHTML = legendLabel(1);
8705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    row.content.innerHTML = valuesAtCursor(
8715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      1, indexValueX, this.unitsY_, this.coordinates.yValue(positionY));
8725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  } else if (row) {
8735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    row.label.innerHTML = '';
8745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    row.content.innerHTML = '';
8755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
8765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If there is a horizontal marker, also display deltas relative to it.
8785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.horizontal_marker_) {
8795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var baseline = this.horizontal_marker_.value;
8805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var delta = this.coordinates.yValue(positionY) - baseline;
8815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var fraction = delta / baseline;  // Allow division by 0.
8825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var deltaStr = (delta >= 0 ? '+' : '') + delta.toFixed(0) + ' ' +
8845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.unitsY_;
8855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var percentStr = (fraction >= 0 ? '+' : '') + (fraction * 100).toFixed(3) +
8865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        '%';
8875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.baselineDeltasTd_.innerHTML = deltaStr + ': ' + percentStr;
8895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (this.unitsYOther_) {
8915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var baseline = this.horizontal_marker_.otherValue;
8925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var yValue2 = this.coordinatesOther.yValue(positionY);
8935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var delta = yValue2 - baseline;
8945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var fraction = delta / baseline;  // Allow division by 0.
8955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
8965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var deltaStr = (delta >= 0 ? '+' : '') + delta.toFixed(0) + ' ' +
8975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          this.unitsYOther_;
8985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var percentStr = (fraction >= 0 ? '+' : '') +
8995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          (fraction * 100).toFixed(3) + '%';
9005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.baselineDeltasTd_.innerHTML += '<br>' + deltaStr + ': ' + percentStr;
9015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
9025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
9035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.updateRuler_(evt);
9055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.updateCursor_(this.coordinates, indexValueX, this.cursorDiv_, 0);
9065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.unitsYOther_ && this.graphsOtherStartIndex_) {
9075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.updateCursor_(this.coordinatesOther, indexValueXOther,
9085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                       this.cursorDivOther_, this.graphsOtherStartIndex_);
9095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
9105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // If there are events displayed, see if we're hovering close to an existing
9125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // event on the graph, and if so, display the metadata associated with it.
9135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.eventName_ != null && this.eventInfo_ != null) {
9145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.infoBox_.rows[1].label.innerHTML = 'Event "' + this.eventName_ +
9155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        '":&nbsp;&nbsp;';
9165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var data = this.eventInfo_;
9175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var showed_event = false;
9185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var x = 0;
9195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    for (var index = 0; index < data.length; ++index) {
9205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      var event_time = data[index][0];
9215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      x = this.coordinates.xPixel(event_time);
9225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      if (positionX >= x - 10 && positionX <= x + 10) {
9235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        var metadata = data[index][1];
9245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        var metadata_str = "";
9255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        for (var meta_key in metadata)
9265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          metadata_str += meta_key + ': ' + metadata[meta_key] + ', ';
9275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        metadata_str = metadata_str.substring(0, metadata_str.length - 2);
9285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.infoBox_.rows[1].content.innerHTML = event_time + ' ' +
9295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)            this.unitsX_ + ': {' + metadata_str + '}';
9305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        showed_event = true;
9315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.updateEventDiv_(x, true);
9325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        break;
9335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      }
9345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
9355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (!showed_event) {
9365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.coordinatesTdOther_.innerHTML =
9375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          'move mouse close to vertical event marker';
9385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.updateEventDiv_(x, false);
9395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
9405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
9415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.updateHoveringInfo_(evt, true);
9435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
9445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
9465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Handle a mouse over event.
9475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
9485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} evt A mouse event object representing a mouse move event.
9495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
9505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.onMouseOver_ = function(evt) {
9515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.updateHoveringInfo_(evt, true);
9525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
9535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
9555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Handle a mouse out event.
9565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
9575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} evt A mouse event object representing a mouse move event.
9585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
9595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.onMouseOut_ = function(evt) {
9605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.updateHoveringInfo_(evt, false);
9615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
9625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
9645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Handle a mouse click event.
9655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
9665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} evt A mouse event object representing a mouse click event.
9675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
9685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.onMouseClick_ = function(evt) {
9695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Shift-click controls the horizontal reference line.
9705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (evt.shiftKey) {
9715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (this.horizontal_marker_)
9725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      this.horizontal_marker_.remove();
9735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var canvasY = domUtils.pageXYOfEvent(evt).y -
9755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                  domUtils.pageXY(this.canvasElement_).y;
9765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.horizontal_marker_ = new HorizontalMarker(
9775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.canvasElement_,
9785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.coordinates.yValue(canvasY),
9795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        (this.coordinatesOther ? this.coordinatesOther.yValue(canvasY) : null));
9805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Insert before cursor node, otherwise it catches clicks.
9815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.cursorDiv_.parentNode.insertBefore(
9825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.horizontal_marker_.markerDiv, this.cursorDiv_);
9835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.horizontal_marker_.locateAt(this.canvasElement_, canvasY);
9845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
9855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
9865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
9875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
9885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Generates and returns a list of div objects representing horizontal lines in
9895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the graph that indicate y-axis values at a computed interval.
9905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
9915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {Object} coordinateSystem a Coordinates object representing the
9925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     coordinate system for which the graduations should be created.
9935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} colorIndex An index into the |this.colors| array representing
9945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     the color to make the graduations in the event that two graphs with
9955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     different coordinate systems are being overlayed on the same plot.
9965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {boolean} isRightSide Whether or not the graduations should have
9975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     right-aligned text (used when the graduations are for a second graph
9985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     that is being overlayed on top of another graph).
9995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {Array} An array of DOM Element objects representing the divs.
10005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
10015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.graduations_ = function(coordinateSystem, colorIndex,
10025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                          isRightSide) {
10035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Don't allow a graduation in the bottom 5% of the chart or the number label
10045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // would overflow the chart bounds.
10055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var yMin = coordinateSystem.yLowerLimitValue() +
10065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      .05 * coordinateSystem.yValueRange();
10075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var yRange = coordinateSystem.yUpperLimitValue() - yMin;
10085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Use the largest scale that fits 3 or more graduations.
10105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // We allow scales of [...,500, 250, 100, 50, 25, 10,...].
10115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var scale = 5000000000;
10125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while (scale) {
10135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (Math.floor(yRange / scale) > 2) break;  // 5s.
10145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    scale /= 2;
10155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (Math.floor(yRange / scale) > 2) break;  // 2.5s.
10165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    scale /= 2.5;
10175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (Math.floor(yRange / scale) > 2) break;  // 1s.
10185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    scale /= 2;
10195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
10205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var graduationPosition = yMin + (scale - yMin % scale);
10225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var graduationDivs = [];
10235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  while (graduationPosition < coordinateSystem.yUpperLimitValue() ||
10245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)         yRange == 0) {
10255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var graduation = document.createElement('div');
10265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var canvasPosition;
10275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (yRange == 0) {
10285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      // Center the graduation vertically.
10295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      canvasPosition = this.canvasElement_.offsetHeight / 2;
10305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
10315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      canvasPosition = coordinateSystem.yPixel(graduationPosition);
10325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
10335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (this.unitsYOther_) {
10345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      graduation.style.borderTop = '1px dashed ' +
10355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          this.makeColorTransparent(colorIndex, 0.4)
10365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
10375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      graduation.style.borderTop = '1px dashed rgba(0,0,0,.08)';
10385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
10395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graduation.style.position = 'absolute';
10405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graduation.style.left = this.canvasElement_.offsetLeft + 'px';
10415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graduation.style.top = canvasPosition + this.canvasElement_.offsetTop +
10425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        'px';
10435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graduation.style.width = this.canvasElement_.offsetWidth -
10445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.canvasElement_.offsetLeft + 'px';
10455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graduation.style.paddingLeft = '4px';
10465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (this.unitsYOther_)
10475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      graduation.style.color = this.makeColorTransparent(colorIndex, 0.9)
10485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else
10495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      graduation.style.color = 'rgba(0,0,0,.4)';
10505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graduation.style.fontSize = '9px';
10515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graduation.style.paddingTop = '0';
10525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graduation.style.zIndex = '-1';
10535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (isRightSide)
10545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      graduation.style.textAlign = 'right';
10555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (yRange == 0)
10565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      graduation.innerHTML = addCommas(yMin);
10575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    else
10585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      graduation.innerHTML = addCommas(graduationPosition);
10595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graduationDivs.push(graduation);
10605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (yRange == 0)
10615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      break;
10625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    graduationPosition += scale;
10635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
10645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return graduationDivs;
10655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
10665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
10685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Generates and returns a div object representing the horizontal line that
10695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * follows the mouse pointer around the plot.
10705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
10715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {Object} A DOM Element object representing the div.
10725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
10735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.ruler_ = function() {
10745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var ruler = document.createElement('div');
10755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ruler.setAttribute('class', 'plot-ruler');
10765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ruler.style.borderBottom = '1px dotted black';
10775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ruler.style.position = 'absolute';
10785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ruler.style.left = '-2px';
10795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ruler.style.top = '-2px';
10805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ruler.style.width = '0px';
10815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  ruler.style.height = '0px';
10825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return ruler;
10835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
10845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
10855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
10865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Generates and returns a canvas object representing the plot itself.
10875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
10885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {Object} A DOM Element object representing the canvas.
10895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
10905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.canvas_ = function() {
10915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var canvas = document.createElement('canvas');
10925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  canvas.setAttribute('id', '_canvas');
10935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  canvas.setAttribute('class', 'plot');
10945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  canvas.setAttribute('width', this.coordinates.widthMax);
10955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  canvas.setAttribute('height', this.coordinates.heightMax);
10965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  canvas.plotter = this;
10975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return canvas;
10985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
10995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
11015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Generates and returns a div object representing the coordinate information
11025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * displayed directly underneath a graph.
11035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
11045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {Object} A DOM Element object representing the div.
11055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
11065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.coordinates_ = function() {
11075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var coordinatesDiv = document.createElement('div');
11085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var table_html = '<table border=0 width="100%"';
11095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.is_lookout_) {
11105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    table_html += ' style="font-size:0.8em"';
11115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
11125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  table_html += '><tbody><tr>';
11135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  table_html += '<td><span class="legend_item"></span>' +
11145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      '<span class="plot-coordinates"><i>move mouse over graph</i></span></td>';
11155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  table_html += '<td align="right">x-axis is ' + this.unitsX_ + '</td>';
11165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  table_html += '</tr><tr>';
11175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  table_html += '<td><span class="legend_item"></span>' +
11185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      '<span class="plot-coordinates"></span></td>';
11195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (!this.is_lookout_) {
11215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    table_html += '<td align="right" style="color: ' + HorizontalMarker.COLOR +
11225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        '"><i>Shift-click to place baseline.</i></td>';
11235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
11245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  table_html += '</tr></tbody></table>';
11255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  coordinatesDiv.innerHTML = table_html;
11265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var trs = coordinatesDiv.querySelectorAll('tr');
11285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.infoBox_ = {rows: []};
11295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.infoBox_.rows.push({
11305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    label: trs[0].querySelector('span.legend_item'),
11315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    content: trs[0].querySelector('span.plot-coordinates')});
11325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.dataDescriptions_.length > 1 || this.eventName_) {
11335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    this.infoBox_.rows.push({
11345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      label: trs[1].querySelector('span.legend_item'),
11355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      content: trs[1].querySelector('span.plot-coordinates')});
11365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
11375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  this.baselineDeltasTd_ = trs[1].childNodes[1];
11395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  // Add a summary of legends in case of stacked graphs.
11415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  if (this.stackedGraph_ || this.stackedGraphOther_) {
11425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var legendPane = document.createElement('div');
11435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    legendPane.style.fontSize = '80%';
11445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    coordinatesDiv.appendChild(legendPane);
11455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    if (this.graphsOtherStartIndex_) {
11475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      legendPane.appendChild(
11485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.createLegendsSummaryElement_(
11495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          this.dataDescriptions_.slice(0, this.graphsOtherStartIndex_),
11505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          0));
11515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      legendPane.appendChild(
11525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.createLegendsSummaryElement_(
11535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          this.dataDescriptions_.slice(this.graphsOtherStartIndex_),
11545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)          this.graphsOtherStartIndex_));
11555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    } else {
11565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)      legendPane.appendChild(
11575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.createLegendsSummaryElement_(this.dataDescriptions_, 0));
11585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    }
11595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
11605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return coordinatesDiv;
11625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
11635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/**
11655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Creates and returns a DOM element which shows a summary of legends.
11665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *
11675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {!Array.<string>} legendTexts An array of legend texts.
11685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @param {number} colorIndexOffset Offset index for color.  i-th legend text
11695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *     has an indicator in {@code (colorIndexOffset + i)}-th color
11705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @return {!Element} An element which shows a summary of legends.
11715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */
11725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)Plotter.prototype.createLegendsSummaryElement_ = function(legendTexts,
11735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)                                                          colorIndexOffset) {
11745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  var containerElem = document.createElement('div');
11755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  for (var i = 0, text; text = legendTexts[i]; ++i) {
11775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var colorIndicatorElem = document.createElement('div');
11785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    colorIndicatorElem.style.display = 'inline-block';
11795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    colorIndicatorElem.style.width = '1em';
11805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    colorIndicatorElem.style.height = '1em';
11815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    colorIndicatorElem.style.verticalAlign = 'text-bottom';
11825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    colorIndicatorElem.style.margin = '0 0.24em 0 0';
11835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    colorIndicatorElem.style.border = '1px solid #000';
11845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    colorIndicatorElem.style.backgroundColor =
11855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)        this.getDataColor(colorIndexOffset + i);
11865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var legendTextElem = document.createElement('span');
11875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    legendTextElem.textContent = text;
11885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    var legendElem = document.createElement('span');
11895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    legendElem.style.whiteSpace = 'nowrap';
11905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    legendElem.appendChild(colorIndicatorElem);
11915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    legendElem.appendChild(legendTextElem);
11925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    legendElem.style.margin = '0 0.8em 0 0';
11935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    containerElem.appendChild(legendElem);
11945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    // Add a space to break lines if necessary.
11955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)    containerElem.appendChild(document.createTextNode(' '));
11965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  }
11975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)
11985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)  return containerElem;
11995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)};
1200