15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)/** 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * A TimelineGraphView displays a timeline graph on a canvas element. 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)var TimelineGraphView = (function() { 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 'use strict'; 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // We inherit from TopMidBottomView. 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var superClass = TopMidBottomView; 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Default starting scale factor, in terms of milliseconds per pixel. 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var DEFAULT_SCALE = 1000; 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Maximum number of labels placed vertically along the sides of the graph. 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var MAX_VERTICAL_LABELS = 6; 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Vertical spacing between labels and between the graph and labels. 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var LABEL_VERTICAL_SPACING = 4; 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Horizontal spacing between vertically placed labels and the edges of the 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // graph. 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var LABEL_HORIZONTAL_SPACING = 3; 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Horizintal spacing between two horitonally placed labels along the bottom 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // of the graph. 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var LABEL_LABEL_HORIZONTAL_SPACING = 25; 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Length of ticks, in pixels, next to y-axis labels. The x-axis only has 295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // one set of labels, so it can use lines instead. 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var Y_AXIS_TICK_LENGTH = 10; 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // The number of units mouse wheel deltas increase for each tick of the 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // wheel. 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var MOUSE_WHEEL_UNITS_PER_CLICK = 120; 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Amount we zoom for one vertical tick of the mouse wheel, as a ratio. 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var MOUSE_WHEEL_ZOOM_RATE = 1.25; 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Amount we scroll for one horizontal tick of the mouse wheel, in pixels. 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var MOUSE_WHEEL_SCROLL_RATE = MOUSE_WHEEL_UNITS_PER_CLICK; 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Number of pixels to scroll per pixel the mouse is dragged. 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var MOUSE_WHEEL_DRAG_RATE = 3; 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var GRID_COLOR = '#CCC'; 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var TEXT_COLOR = '#000'; 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var BACKGROUND_COLOR = '#FFF'; 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Which side of the canvas y-axis labels should go on, for a given Graph. 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // TODO(mmenke): Figure out a reasonable way to handle more than 2 sets 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // of labels. 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var LabelAlign = { 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) LEFT: 0, 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) RIGHT: 1 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @constructor 575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) function TimelineGraphView(divId, canvasId, scrollbarId, scrollbarInnerId) { 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.scrollbar_ = new HorizontalScrollbarView(scrollbarId, 605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scrollbarInnerId, 615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.onScroll_.bind(this)); 625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Call superclass's constructor. 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) superClass.call(this, null, new DivView(divId), this.scrollbar_); 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.graphDiv_ = $(divId); 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.canvas_ = $(canvasId); 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.canvas_.onmousewheel = this.onMouseWheel_.bind(this); 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.canvas_.onmousedown = this.onMouseDown_.bind(this); 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.canvas_.onmousemove = this.onMouseMove_.bind(this); 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.canvas_.onmouseup = this.onMouseUp_.bind(this); 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.canvas_.onmouseout = this.onMouseUp_.bind(this); 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Used for click and drag scrolling of graph. Drag-zooming not supported, 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // for a more stable scrolling experience. 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.isDragging_ = false; 765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.dragX_ = 0; 775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Set the range and scale of the graph. Times are in milliseconds since 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // the Unix epoch. 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // All measurements we have must be after this time. 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.startTime_ = 0; 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // The current rightmost position of the graph is always at most this. 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // We may have some later events. When actively capturing new events, it's 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // updated on a timer. 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.endTime_ = 1; 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Current scale, in terms of milliseconds per pixel. Each column of 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // pixels represents a point in time |scale_| milliseconds after the 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // previous one. We only display times that are of the form 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // |startTime_| + K * |scale_| to avoid jittering, and the rightmost 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // pixel that we can display has a time <= |endTime_|. Non-integer values 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // are allowed. 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.scale_ = DEFAULT_SCALE; 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.graphs_ = []; 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Initialize the scrollbar. 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.updateScrollbarRange_(true); 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Smallest allowed scaling factor. 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) TimelineGraphView.MIN_SCALE = 5; 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) TimelineGraphView.prototype = { 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Inherit the superclass's methods. 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) __proto__: superClass.prototype, 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) setGeometry: function(left, top, width, height) { 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) superClass.prototype.setGeometry.call(this, left, top, width, height); 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // The size of the canvas can only be set by using its |width| and 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // |height| properties, which do not take padding into account, so we 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // need to use them ourselves. 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var style = getComputedStyle(this.canvas_); 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var horizontalPadding = parseInt(style.paddingRight) + 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parseInt(style.paddingLeft); 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var verticalPadding = parseInt(style.paddingTop) + 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parseInt(style.paddingBottom); 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var canvasWidth = 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parseInt(this.graphDiv_.style.width) - horizontalPadding; 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // For unknown reasons, there's an extra 3 pixels border between the 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // bottom of the canvas and the bottom margin of the enclosing div. 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var canvasHeight = 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) parseInt(this.graphDiv_.style.height) - verticalPadding - 3; 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Protect against degenerates. 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (canvasWidth < 10) 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) canvasWidth = 10; 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (canvasHeight < 10) 1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) canvasHeight = 10; 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.canvas_.width = canvasWidth; 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.canvas_.height = canvasHeight; 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Use the same font style for the canvas as we use elsewhere. 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Has to be updated every resize. 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.canvas_.getContext('2d').font = getComputedStyle(this.canvas_).font; 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.updateScrollbarRange_(this.graphScrolledToRightEdge_()); 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.repaint(); 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) show: function(isVisible) { 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) superClass.prototype.show.call(this, isVisible); 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (isVisible) 1475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.repaint(); 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Returns the total length of the graph, in pixels. 1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) getLength_: function() { 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var timeRange = this.endTime_ - this.startTime_; 1535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Math.floor is used to ignore the last partial area, of length less 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // than |scale_|. 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return Math.floor(timeRange / this.scale_); 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Returns true if the graph is scrolled all the way to the right. 1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) graphScrolledToRightEdge_: function() { 1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return this.scrollbar_.getPosition() == this.scrollbar_.getRange(); 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Update the range of the scrollbar. If |resetPosition| is true, also 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * sets the slider to point at the rightmost position and triggers a 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * repaint. 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) updateScrollbarRange_: function(resetPosition) { 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var scrollbarRange = this.getLength_() - this.canvas_.width; 1725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (scrollbarRange < 0) 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scrollbarRange = 0; 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If we've decreased the range to less than the current scroll position, 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // we need to move the scroll position. 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (this.scrollbar_.getPosition() > scrollbarRange) 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) resetPosition = true; 1795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.scrollbar_.setRange(scrollbarRange); 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (resetPosition) { 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.scrollbar_.setPosition(scrollbarRange); 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.repaint(); 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Sets the date range displayed on the graph, switches to the default 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * scale factor, and moves the scrollbar all the way to the right. 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) setDateRange: function(startDate, endDate) { 1925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.startTime_ = startDate.getTime(); 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.endTime_ = endDate.getTime(); 1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Safety check. 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (this.endTime_ <= this.startTime_) 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.startTime_ = this.endTime_ - 1; 1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.scale_ = DEFAULT_SCALE; 2005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.updateScrollbarRange_(true); 2015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 2025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 2045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Updates the end time at the right of the graph to be the current time. 2055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Specifically, updates the scrollbar's range, and if the scrollbar is 2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * all the way to the right, keeps it all the way to the right. Otherwise, 2075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * leaves the view as-is and doesn't redraw anything. 2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) updateEndDate: function() { 2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.endTime_ = timeutil.getCurrentTime(); 2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.updateScrollbarRange_(this.graphScrolledToRightEdge_()); 2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) getStartDate: function() { 2155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return new Date(this.startTime_); 2165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 2195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Scrolls the graph horizontally by the specified amount. 2205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 2215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) horizontalScroll_: function(delta) { 2225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var newPosition = this.scrollbar_.getPosition() + Math.round(delta); 2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Make sure the new position is in the right range. 2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (newPosition < 0) { 2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) newPosition = 0; 2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else if (newPosition > this.scrollbar_.getRange()) { 2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) newPosition = this.scrollbar_.getRange(); 2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (this.scrollbar_.getPosition() == newPosition) 2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.scrollbar_.setPosition(newPosition); 2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.onScroll_(); 2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Zooms the graph by the specified amount. 2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) zoom_: function(ratio) { 2405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var oldScale = this.scale_; 2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.scale_ *= ratio; 2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (this.scale_ < TimelineGraphView.MIN_SCALE) 2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.scale_ = TimelineGraphView.MIN_SCALE; 2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (this.scale_ == oldScale) 2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If we were at the end of the range before, remain at the end of the 2495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // range. 2505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (this.graphScrolledToRightEdge_()) { 2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.updateScrollbarRange_(true); 2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Otherwise, do our best to maintain the old position. We use the 2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // position at the far right of the graph for consistency. 2575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var oldMaxTime = 2585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) oldScale * (this.scrollbar_.getPosition() + this.canvas_.width); 2595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var newMaxTime = Math.round(oldMaxTime / this.scale_); 2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var newPosition = newMaxTime - this.canvas_.width; 2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Update range and scroll position. 2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.updateScrollbarRange_(false); 2645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.horizontalScroll_(newPosition - this.scrollbar_.getPosition()); 2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) onMouseWheel_: function(event) { 2685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) event.preventDefault(); 2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.horizontalScroll_( 2705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) MOUSE_WHEEL_SCROLL_RATE * 2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) -event.wheelDeltaX / MOUSE_WHEEL_UNITS_PER_CLICK); 2725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.zoom_(Math.pow(MOUSE_WHEEL_ZOOM_RATE, 2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) -event.wheelDeltaY / MOUSE_WHEEL_UNITS_PER_CLICK)); 2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) onMouseDown_: function(event) { 2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) event.preventDefault(); 2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.isDragging_ = true; 2795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.dragX_ = event.clientX; 2805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) onMouseMove_: function(event) { 2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!this.isDragging_) 2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) event.preventDefault(); 2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.horizontalScroll_( 2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) MOUSE_WHEEL_DRAG_RATE * (event.clientX - this.dragX_)); 2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.dragX_ = event.clientX; 2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) onMouseUp_: function(event) { 2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.isDragging_ = false; 2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 2945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) onScroll_: function() { 2965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.repaint(); 2975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 2985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 3005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Replaces the current TimelineDataSeries with |dataSeries|. 3015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 3025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) setDataSeries: function(dataSeries) { 3035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Simplest just to recreate the Graphs. 3045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.graphs_ = []; 3055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.graphs_[TimelineDataType.BYTES_PER_SECOND] = 3065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new Graph(TimelineDataType.BYTES_PER_SECOND, LabelAlign.RIGHT); 3075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.graphs_[TimelineDataType.SOURCE_COUNT] = 3085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) new Graph(TimelineDataType.SOURCE_COUNT, LabelAlign.LEFT); 3095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < dataSeries.length; ++i) 3105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.graphs_[dataSeries[i].getDataType()].addDataSeries(dataSeries[i]); 3115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.repaint(); 3135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 3145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Draws the graph on |canvas_|. 3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) repaint: function() { 3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.repaintTimerRunning_ = false; 3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!this.isVisible()) 3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var width = this.canvas_.width; 3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var height = this.canvas_.height; 3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var context = this.canvas_.getContext('2d'); 3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Clear the canvas. 3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.fillStyle = BACKGROUND_COLOR; 3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.fillRect(0, 0, width, height); 3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Try to get font height in pixels. Needed for layout. 3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var fontHeightString = context.font.match(/([0-9]+)px/)[1]; 3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var fontHeight = parseInt(fontHeightString); 3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Safety check, to avoid drawing anything too ugly. 3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (fontHeightString.length == 0 || fontHeight <= 0 || 3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) fontHeight * 4 > height || width < 50) { 3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Save current transformation matrix so we can restore it later. 3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.save(); 3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // The center of an HTML canvas pixel is technically at (0.5, 0.5). This 3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // makes near straight lines look bad, due to anti-aliasing. This 3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // translation reduces the problem a little. 3475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.translate(0.5, 0.5); 3485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Figure out what time values to display. 3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var position = this.scrollbar_.getPosition(); 3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If the entire time range is being displayed, align the right edge of 3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // the graph to the end of the time range. 3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (this.scrollbar_.getRange() == 0) 3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) position = this.getLength_() - this.canvas_.width; 3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var visibleStartTime = this.startTime_ + position * this.scale_; 3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Make space at the bottom of the graph for the time labels, and then 3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // draw the labels. 3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var textHeight = height; 3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) height -= fontHeight + LABEL_VERTICAL_SPACING; 3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.drawTimeLabels(context, width, height, textHeight, visibleStartTime); 3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Draw outline of the main graph area. 3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.strokeStyle = GRID_COLOR; 3655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.strokeRect(0, 0, width - 1, height - 1); 3665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Layout graphs and have them draw their tick marks. 3685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < this.graphs_.length; ++i) { 3695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.graphs_[i].layout(width, height, fontHeight, visibleStartTime, 3705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.scale_); 3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.graphs_[i].drawTicks(context); 3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Draw the lines of all graphs, and then draw their labels. 3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < this.graphs_.length; ++i) 3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.graphs_[i].drawLines(context); 3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < this.graphs_.length; ++i) 3785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.graphs_[i].drawLabels(context); 3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Restore original transformation matrix. 3815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.restore(); 3825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 3835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 3855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Draw time labels below the graph. Takes in start time as an argument 3865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * since it may not be |startTime_|, when we're displaying the entire 3875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * time range. 3885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 3895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) drawTimeLabels: function(context, width, height, textHeight, startTime) { 3905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Text for a time string to use in determining how far apart 3915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // to place text labels. 3925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var sampleText = (new Date(startTime)).toLocaleTimeString(); 3935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // The desired spacing for text labels. 3955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var targetSpacing = context.measureText(sampleText).width + 3965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) LABEL_LABEL_HORIZONTAL_SPACING; 3975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // The allowed time step values between adjacent labels. Anything much 3995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // over a couple minutes isn't terribly realistic, given how much memory 4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // we use, and how slow a lot of the net-internals code is. 4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var timeStepValues = [ 4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1000, // 1 second 4035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1000 * 5, 4045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1000 * 30, 4055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1000 * 60, // 1 minute 4065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1000 * 60 * 5, 4075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1000 * 60 * 30, 4085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1000 * 60 * 60, // 1 hour 4095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1000 * 60 * 60 * 5 4105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ]; 4115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Find smallest time step value that gives us at least |targetSpacing|, 4135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // if any. 4145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var timeStep = null; 4155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < timeStepValues.length; ++i) { 4165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (timeStepValues[i] / this.scale_ >= targetSpacing) { 4175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) timeStep = timeStepValues[i]; 4185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) break; 4195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If no such value, give up. 4235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!timeStep) 4245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 4255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Find the time for the first label. This time is a perfect multiple of 4275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // timeStep because of how UTC times work. 4285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var time = Math.ceil(startTime / timeStep) * timeStep; 4295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.textBaseline = 'bottom'; 4315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.textAlign = 'center'; 4325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.fillStyle = TEXT_COLOR; 4335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.strokeStyle = GRID_COLOR; 4345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Draw labels and vertical grid lines. 4365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) while (true) { 4375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var x = Math.round((time - startTime) / this.scale_); 4385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (x >= width) 4395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) break; 4405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var text = (new Date(time)).toLocaleTimeString(); 4415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.fillText(text, x, textHeight); 4425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.beginPath(); 4435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.lineTo(x, 0); 4445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.lineTo(x, height); 4455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.stroke(); 4465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) time += timeStep; 4475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 4505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 4525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * A Graph is responsible for drawing all the TimelineDataSeries that have 4535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * the same data type. Graphs are responsible for scaling the values, laying 4545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * out labels, and drawing both labels and lines for its data series. 4555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 4565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var Graph = (function() { 4575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 4585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * |dataType| is the DataType that will be shared by all its DataSeries. 4595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * |labelAlign| is the LabelAlign value indicating whether the labels 4605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * should be aligned to the right of left of the graph. 4615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @constructor 4625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 4635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) function Graph(dataType, labelAlign) { 4645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.dataType_ = dataType; 4655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.dataSeries_ = []; 4665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.labelAlign_ = labelAlign; 4675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Cached properties of the graph, set in layout. 4695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.width_ = 0; 4705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.height_ = 0; 4715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.fontHeight_ = 0; 4725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.startTime_ = 0; 4735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.scale_ = 0; 4745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // At least the highest value in the displayed range of the graph. 4765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Used for scaling and setting labels. Set in layoutLabels. 4775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.max_ = 0; 4785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Cached text of equally spaced labels. Set in layoutLabels. 4805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.labels_ = []; 4815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 4845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * A Label is the label at a particular position along the y-axis. 4855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * @constructor 4865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 4875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) function Label(height, text) { 4885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.height = height; 4895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.text = text; 4905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 4915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Graph.prototype = { 4935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) addDataSeries: function(dataSeries) { 4945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.dataSeries_.push(dataSeries); 4955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 4965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 4985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Returns a list of all the values that should be displayed for a given 4995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * data series, using the current graph layout. 5005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 5015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) getValues: function(dataSeries) { 5025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!dataSeries.isVisible()) 5035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return null; 5045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return dataSeries.getValues(this.startTime_, this.scale_, this.width_); 5055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 5065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 5085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Updates the graph's layout. In particular, both the max value and 5095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * label positions are updated. Must be called before calling any of the 5105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * drawing functions. 5115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 5125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) layout: function(width, height, fontHeight, startTime, scale) { 5135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.width_ = width; 5145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.height_ = height; 5155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.fontHeight_ = fontHeight; 5165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.startTime_ = startTime; 5175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.scale_ = scale; 5185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Find largest value. 5205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var max = 0; 5215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < this.dataSeries_.length; ++i) { 5225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var values = this.getValues(this.dataSeries_[i]); 5235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!values) 5245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) continue; 5255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var j = 0; j < values.length; ++j) { 5265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (values[j] > max) 5275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) max = values[j]; 5285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 5295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 5305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.layoutLabels_(max); 5325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 5335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 5355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Lays out labels and sets |max_|, taking the time units into 5365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * consideration. |maxValue| is the actual maximum value, and 5375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * |max_| will be set to the value of the largest label, which 5385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * will be at least |maxValue|. 5395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 5405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) layoutLabels_: function(maxValue) { 5415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (this.dataType_ != TimelineDataType.BYTES_PER_SECOND) { 5425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.layoutLabelsBasic_(maxValue, 0); 5435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 5445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 5455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Special handling for data rates. 5475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Find appropriate units to use. 5495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var units = ['B/s', 'kB/s', 'MB/s', 'GB/s', 'TB/s', 'PB/s']; 5505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Units to use for labels. 0 is bytes, 1 is kilobytes, etc. 5515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // We start with kilobytes, and work our way up. 5525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var unit = 1; 5535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Update |maxValue| to be in the right units. 5545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) maxValue = maxValue / 1024; 5555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) while (units[unit + 1] && maxValue >= 999) { 5565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) maxValue /= 1024; 5575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) ++unit; 5585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 5595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Calculate labels. 5615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.layoutLabelsBasic_(maxValue, 1); 5625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Append units to labels. 5645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < this.labels_.length; ++i) 5655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.labels_[i] += ' ' + units[unit]; 5665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Convert |max_| back to bytes, so it can be used when scaling values 5685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // for display. 5695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.max_ *= Math.pow(1024, unit); 5705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 5715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 5735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Same as layoutLabels_, but ignores units. |maxDecimalDigits| is the 5745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * maximum number of decimal digits allowed. The minimum allowed 5755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * difference between two adjacent labels is 10^-|maxDecimalDigits|. 5765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 5775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) layoutLabelsBasic_: function(maxValue, maxDecimalDigits) { 5785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.labels_ = []; 5795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // No labels if |maxValue| is 0. 5805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (maxValue == 0) { 5815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.max_ = maxValue; 5825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 5835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 5845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // The maximum number of equally spaced labels allowed. |fontHeight_| 5865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // is doubled because the top two labels are both drawn in the same 5875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // gap. 5885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var minLabelSpacing = 2 * this.fontHeight_ + LABEL_VERTICAL_SPACING; 5895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // The + 1 is for the top label. 5915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var maxLabels = 1 + this.height_ / minLabelSpacing; 5925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (maxLabels < 2) { 5935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) maxLabels = 2; 5945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else if (maxLabels > MAX_VERTICAL_LABELS) { 5955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) maxLabels = MAX_VERTICAL_LABELS; 5965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 5975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 5985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Initial try for step size between conecutive labels. 5995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var stepSize = Math.pow(10, -maxDecimalDigits); 6005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Number of digits to the right of the decimal of |stepSize|. 6015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Used for formating label strings. 6025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var stepSizeDecimalDigits = maxDecimalDigits; 6035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Pick a reasonable step size. 6055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) while (true) { 6065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If we use a step size of |stepSize| between labels, we'll need: 6075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // 6085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Math.ceil(maxValue / stepSize) + 1 6095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // 6105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // labels. The + 1 is because we need labels at both at 0 and at 6115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // the top of the graph. 6125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Check if we can use steps of size |stepSize|. 6145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (Math.ceil(maxValue / stepSize) + 1 <= maxLabels) 6155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) break; 6165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Check |stepSize| * 2. 6175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (Math.ceil(maxValue / (stepSize * 2)) + 1 <= maxLabels) { 6185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) stepSize *= 2; 6195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) break; 6205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 6215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Check |stepSize| * 5. 6225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (Math.ceil(maxValue / (stepSize * 5)) + 1 <= maxLabels) { 6235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) stepSize *= 5; 6245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) break; 6255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 6265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) stepSize *= 10; 6275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (stepSizeDecimalDigits > 0) 6285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) --stepSizeDecimalDigits; 6295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 6305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Set the max so it's an exact multiple of the chosen step size. 6325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.max_ = Math.ceil(maxValue / stepSize) * stepSize; 6335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Create labels. 6355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var label = this.max_; label >= 0; label -= stepSize) 6365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) this.labels_.push(label.toFixed(stepSizeDecimalDigits)); 6375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 6385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 6405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Draws tick marks for each of the labels in |labels_|. 6415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 6425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) drawTicks: function(context) { 6435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var x1; 6445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var x2; 6455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (this.labelAlign_ == LabelAlign.RIGHT) { 6465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) x1 = this.width_ - 1; 6475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) x2 = this.width_ - 1 - Y_AXIS_TICK_LENGTH; 6485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 6495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) x1 = 0; 6505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) x2 = Y_AXIS_TICK_LENGTH; 6515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 6525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.fillStyle = GRID_COLOR; 6545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.beginPath(); 6555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 1; i < this.labels_.length - 1; ++i) { 6565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // The rounding is needed to avoid ugly 2-pixel wide anti-aliased 6575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // lines. 6585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var y = Math.round(this.height_ * i / (this.labels_.length - 1)); 6595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.moveTo(x1, y); 6605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.lineTo(x2, y); 6615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 6625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.stroke(); 6635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 6645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 6665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Draws a graph line for each of the data series. 6675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 6685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) drawLines: function(context) { 6695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Factor by which to scale all values to convert them to a number from 6705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // 0 to height - 1. 6715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var scale = 0; 6725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var bottom = this.height_ - 1; 6735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (this.max_) 6745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scale = bottom / this.max_; 6755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Draw in reverse order, so earlier data series are drawn on top of 6775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // subsequent ones. 6785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = this.dataSeries_.length - 1; i >= 0; --i) { 6795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var values = this.getValues(this.dataSeries_[i]); 6805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!values) 6815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) continue; 6825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.strokeStyle = this.dataSeries_[i].getColor(); 6835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.beginPath(); 6845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var x = 0; x < values.length; ++x) { 6855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // The rounding is needed to avoid ugly 2-pixel wide anti-aliased 6865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // horizontal lines. 6875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.lineTo(x, bottom - Math.round(values[x] * scale)); 6885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 6895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.stroke(); 6905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 6915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }, 6925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) /** 6945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) * Draw labels in |labels_|. 6955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) */ 6965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) drawLabels: function(context) { 6975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (this.labels_.length == 0) 6985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 6995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var x; 7005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (this.labelAlign_ == LabelAlign.RIGHT) { 7015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) x = this.width_ - LABEL_HORIZONTAL_SPACING; 7025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 7035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Find the width of the widest label. 7045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var maxTextWidth = 0; 7055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 0; i < this.labels_.length; ++i) { 7065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var textWidth = context.measureText(this.labels_[i]).width; 7075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (maxTextWidth < textWidth) 7085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) maxTextWidth = textWidth; 7095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 7105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) x = maxTextWidth + LABEL_HORIZONTAL_SPACING; 7115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 7125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Set up the context. 7145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.fillStyle = TEXT_COLOR; 7155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.textAlign = 'right'; 7165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Draw top label, which is the only one that appears below its tick 7185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // mark. 7195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.textBaseline = 'top'; 7205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.fillText(this.labels_[0], x, 0); 7215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Draw all the other labels. 7235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.textBaseline = 'bottom'; 7245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) var step = (this.height_ - 1) / (this.labels_.length - 1); 7255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) for (var i = 1; i < this.labels_.length; ++i) 7265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context.fillText(this.labels_[i], x, step * i); 7275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 7285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 7295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return Graph; 7315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) })(); 7325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 7335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return TimelineGraphView; 7345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)})(); 735