Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(414)

Side by Side Diff: perf/dashboard/ui/endure_js/plotter.js

Issue 1654813003: Remove old dead perf dashboard pages and js (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/
Patch Set: Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « perf/dashboard/ui/endure_js/graph_utils.js ('k') | perf/dashboard/ui/endure_plotter.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 Use of this source code is governed by a BSD-style license that can be
4 found in the LICENSE file.
5 */
6
7 /**
8 * @fileoverview Collection of functions and classes used to plot data in a
9 * <canvas>. Create a Plotter() to generate a plot.
10 */
11
12 /**
13 * Adds commas to a given number.
14 *
15 * Examples:
16 * 1234.56 => "1,234.56"
17 * 99999 => "99,999"
18 *
19 * @param {string|number} number The number to format.
20 * @return {string} String representation of |number| with commas for every
21 * three digits to the left of a decimal point.
22 */
23 function addCommas(number) {
24 number += ''; // Convert number to string if not already a string.
25 var numberParts = number.split('.');
26 var integralPart = numberParts[0];
27 var fractionalPart = numberParts.length > 1 ? '.' + numberParts[1] : '';
28 var reThreeDigits = /(\d+)(\d{3})/;
29 while (reThreeDigits.test(integralPart))
30 integralPart = integralPart.replace(reThreeDigits, '$1' + ',' + '$2');
31 return integralPart + fractionalPart;
32 }
33
34 /**
35 * Vertical marker to highlight data points that are being hovered over by the
36 * mouse.
37 *
38 * @param {string} color The color to make the marker, e.g., 'rgb(100,80,240)'.
39 * @return {Element} A div Element object representing the vertical marker.
40 */
41 function VerticalMarker(color) {
42 var m = document.createElement('div');
43 m.style.backgroundColor = color;
44 m.style.opacity = '0.3';
45 m.style.position = 'absolute';
46 m.style.left = '-2px';
47 m.style.top = '-2px';
48 m.style.width = '0px';
49 m.style.height = '0px';
50 return m;
51 }
52
53 /**
54 * Class representing a horizontal marker at the indicated mouse location.
55 * @constructor
56 *
57 * @param {Element} canvasElement The canvas bounds.
58 * @param {Number} yValue The data value corresponding to the vertical click
59 * location.
60 * @param {Number} yOtherValue If the plot is overlaying two coordinate systems,
61 * this is the data value corresponding to the vertical click location in
62 * the second coordinate system. Can be null.
63 */
64 function HorizontalMarker(canvasElement, yValue, yOtherValue) {
65 var m = document.createElement('div');
66 m.style.backgroundColor = HorizontalMarker.COLOR;
67 m.style.opacity = '0.3';
68 m.style.position = 'absolute';
69 m.style.width = canvasElement.offsetWidth + 'px';
70 m.style.height = HorizontalMarker.HEIGHT + 'px';
71
72 this.markerDiv = m;
73 this.value = yValue;
74 this.otherValue = yOtherValue;
75 }
76
77 HorizontalMarker.HEIGHT = 5;
78 HorizontalMarker.COLOR = 'rgb(0,100,100)';
79
80 /**
81 * Locates this element at a specified position.
82 *
83 * @param {Element} canvasElement The canvas element at which this element is
84 * to be placed.
85 * @param {number} y Y position relative to the canvas element.
86 */
87 HorizontalMarker.prototype.locateAt = function(canvasElement, y) {
88 var div = this.markerDiv;
89 div.style.left = domUtils.pageXY(canvasElement).x -
90 domUtils.pageXY(div.offsetParent) + 'px';
91 div.style.top = (y + domUtils.pageXY(canvasElement).y
92 - domUtils.pageXY(div.offsetParent).y
93 - (HorizontalMarker.HEIGHT / 2)) + 'px';
94 };
95
96 /**
97 * Removes the horizontal marker from the graph.
98 */
99 HorizontalMarker.prototype.remove = function() {
100 this.markerDiv.parentNode.removeChild(this.markerDiv);
101 };
102
103 /**
104 * An information indicator hovering around the mouse cursor on the graph.
105 * This class is used to show a legend near the mouse cursor.
106 *
107 * A set of legends under the graph is managed separately in
108 * {@code Plotter.createLegendsSummaryElement_}.
109 *
110 * @constructor
111 */
112 function HoveringInfo() {
113 this.containerDiv_ = document.createElement('div');
114 this.containerDiv_.style.display = 'none';
115 this.containerDiv_.style.position = 'absolute';
116 this.containerDiv_.style.border = '1px solid #000';
117 this.containerDiv_.style.padding = '0.12em';
118 this.containerDiv_.style.backgroundColor = '#ddd';
119 this.colorIndicator_ = document.createElement('div');
120 this.colorIndicator_.style.display = 'inline-block';
121 this.colorIndicator_.style.width = '1em';
122 this.colorIndicator_.style.height = '1em';
123 this.colorIndicator_.style.verticalAlign = 'text-bottom';
124 this.colorIndicator_.style.margin = '0 0.24em 0 0';
125 this.colorIndicator_.style.border = '1px solid #000';
126 this.legendText_ = document.createElement('span');
127 this.itemValueText_ = document.createElement('span');
128
129 this.containerDiv_.appendChild(this.colorIndicator_);
130 this.containerDiv_.appendChild(this.legendText_);
131 var div = document.createElement('div');
132 div.appendChild(this.itemValueText_);
133 this.containerDiv_.appendChild(div);
134 }
135
136 /**
137 * Returns the container element;
138 *
139 * @return {Element} The container element.
140 */
141 HoveringInfo.prototype.getElement = function() {
142 return this.containerDiv_;
143 };
144
145 /**
146 * Shows or hides the element.
147 *
148 * @param {boolean} show Shows the element if true, or hides it.
149 */
150 HoveringInfo.prototype.show = function(show) {
151 this.containerDiv_.style.display = show ? 'block' : 'none';
152 };
153
154 /**
155 * Returns the position of the container element in the page coordinate.
156 *
157 * @return {Object} A point object which has {@code x} and {@code y} fields.
158 */
159 HoveringInfo.prototype.pageXY = function() {
160 return domUtils.pageXY(this.containerDiv_);
161 };
162
163 /**
164 * Locates the element at the specified position.
165 *
166 * @param {number} x X position in the page coordinate.
167 * @param {number} y Y position in the page coordinate.
168 */
169 HoveringInfo.prototype.locateAtPageXY = function(x, y) {
170 var parentXY = domUtils.pageXY(this.containerDiv_.offsetParent);
171 this.containerDiv_.style.left = x - parentXY.x + 'px';
172 this.containerDiv_.style.top = y - parentXY.y + 'px';
173 };
174
175 /**
176 * Returns the legend text.
177 *
178 * @return {?string} The legend text.
179 */
180 HoveringInfo.prototype.getLegendText = function() {
181 return this.legendText_.textContent;
182 };
183
184 /**
185 * Changes the legend text.
186 *
187 * @param {string} text The new text to be set.
188 */
189 HoveringInfo.prototype.setLegendText = function(text) {
190 this.legendText_.textContent = text;
191 };
192
193 /**
194 * Changes the item value.
195 *
196 * @param {number} value The new value to be shown.
197 */
198 HoveringInfo.prototype.setItemValue = function(value) {
199 this.itemValueText_.textContent = 'Item value = ' + addCommas(value);
200 };
201
202 /**
203 * Changes the color of the color indicator.
204 *
205 * @param {string} color The new color to be set.
206 */
207 HoveringInfo.prototype.setColorIndicator = function(color) {
208 this.colorIndicator_.style.backgroundColor = color;
209 };
210
211 /**
212 * Main class that does the actual plotting.
213 *
214 * Draws a chart using a canvas element. Takes an array of lines to draw.
215 * @constructor
216 *
217 * @param {Array} plotData list of arrays that represent individual lines. The
218 * line itself is an Array of points.
219 * @param {Array} dataDescriptions list of data descriptions for each line in
220 * |plotData|.
221 * @param {string} eventName The string name of an event to overlay on the
222 * graph. Should be 'null' if there are no events to overlay.
223 * @param {Object} eventInfo If |eventName| is specified, an array of event
224 * points to overlay on the graph. Each event point in the array is itself
225 * a 2-element array, where the first element is the x-axis value at which
226 * the event occurred during the test, and the second element is a
227 * dictionary of kay/value pairs representing metadata associated with the
228 * event.
229 * @param {string} unitsX The x-axis units of the data being plotted.
230 * @param {string} unitsY The y-axis units of the data being plotted.
231 * @param {string} unitsYOther If another graph (with different y-axis units) is
232 * being overlayed over the first graph, this represents the units of the
233 * other graph. Otherwise, this should be 'null'.
234 * @param {?number} graphsOtherStartIndex Specifies the starting index of
235 * the second set of lines. {@code plotData} in the range of
236 * [0, {@code graphsOtherStartIndex}) are treated as the first set of lines,
237 * and ones in the range of
238 * [{@code graphsOtherStartIndex}, {@code plotData.length}) are as
239 * the second set. 0, {@code plotData.length} and {@code null} mean
240 * no second set, i.e. all the data in {@code plotData} represent the single
241 * set of lines.
242 * @param {Element} resultNode A DOM Element object representing the DOM node to
243 * which the plot should be attached.
244 * @param {boolean} is_lookout Whether or not the graph should be drawn
245 * in 'lookout' mode, which is a summarized view that is made for overview
246 * pages when the graph is drawn in a more confined space.
247 * @param {boolean} stackedGraph Whether or not the first set of lines is
248 * a stacked graph.
249 * @param {boolean} stackedGraphOther Whether or not the second set of lines is
250 * a stacked graph.
251 *
252 * Example of the |plotData|:
253 * [
254 * [line 1 data],
255 * [line 2 data]
256 * ].
257 * Line data looks like [[point one], [point two]].
258 * And individual points are [x value, y value]
259 */
260 function Plotter(plotData, dataDescriptions, eventName, eventInfo, unitsX,
261 unitsY, unitsYOther, graphsOtherStartIndex, resultNode,
262 is_lookout, stackedGraph, stackedGraphOther) {
263 this.plotData_ = plotData;
264 this.dataDescriptions_ = dataDescriptions;
265 this.eventName_ = eventName;
266 this.eventInfo_ = eventInfo;
267 this.unitsX_ = unitsX;
268 this.unitsY_ = unitsY;
269 this.unitsYOther_ = unitsYOther;
270 this.graphsOtherStartIndex_ =
271 (0 < graphsOtherStartIndex && graphsOtherStartIndex < plotData.length) ?
272 graphsOtherStartIndex : null;
273 this.resultNode_ = resultNode;
274 this.is_lookout_ = is_lookout;
275 this.stackedGraph_ = stackedGraph;
276 this.stackedGraphOther_ = stackedGraphOther;
277
278 this.dataColors_ = [];
279
280 this.coordinates = null;
281 this.coordinatesOther = null;
282 if (this.unitsYOther_ && this.graphsOtherStartIndex_) {
283 // Need two different coordinate systems to overlay on the same graph.
284 this.coordinates = new Coordinates(
285 this.plotData_.slice(0, this.graphsOtherStartIndex_));
286 this.coordinatesOther = new Coordinates(
287 this.plotData_.slice(this.graphsOtherStartIndex_));
288 } else {
289 this.coordinates = new Coordinates(this.plotData_);
290 }
291
292 // A color palette that's unambigous for normal and color-deficient viewers.
293 // Values are (red, green, blue) on a scale of 255.
294 // Taken from http://jfly.iam.u-tokyo.ac.jp/html/manuals/pdf/color_blind.pdf.
295 this.colors = [[0, 114, 178], // Blue.
296 [230, 159, 0], // Orange.
297 [0, 158, 115], // Green.
298 [204, 121, 167], // Purplish pink.
299 [86, 180, 233], // Sky blue.
300 [213, 94, 0], // Dark orange.
301 [0, 0, 0], // Black.
302 [240, 228, 66] // Yellow.
303 ];
304
305 for (var i = 0, colorIndex = 0; i < this.dataDescriptions_.length; ++i)
306 this.dataColors_[i] = this.makeColor(colorIndex++);
307 }
308
309 /**
310 * Generates a string representing a color corresponding to the given index
311 * in a color array. Handles wrapping around the color array if necessary.
312 *
313 * @param {number} i An index into the |this.colors| array.
314 * @return {string} A string representing a color in 'rgb(X,Y,Z)' format.
315 */
316 Plotter.prototype.makeColor = function(i) {
317 var index = i % this.colors.length;
318 return 'rgb(' + this.colors[index][0] + ',' +
319 this.colors[index][1] + ',' +
320 this.colors[index][2] + ')';
321 };
322
323 /**
324 * Same as function makeColor above, but also takes a transparency value
325 * indicating how transparent to make the color appear.
326 *
327 * @param {number} i An index into the |this.colors| array.
328 * @param {number} transparencyPercent Percentage transparency to make the
329 * color, e.g., 0.75.
330 * @return {string} A string representing a color in 'rgb(X,Y,Z,A)' format,
331 * where A is the percentage transparency.
332 */
333 Plotter.prototype.makeColorTransparent = function(i, transparencyPercent) {
334 var index = i % this.colors.length;
335 return 'rgba(' + this.colors[index][0] + ',' +
336 this.colors[index][1] + ',' +
337 this.colors[index][2] + ',' + transparencyPercent + ')';
338 };
339
340 /**
341 * Gets the data color value associated with a specified color index.
342 *
343 * @param {number} i An index into the |this.colors| array.
344 * @return {string} A string representing a color in 'rgb(X,Y,Z,A)' format,
345 * where A is the percentage transparency.
346 */
347 Plotter.prototype.getDataColor = function(i) {
348 if (this.dataColors_[i])
349 return this.dataColors_[i];
350 else
351 return this.makeColor(i);
352 };
353
354 /**
355 * Gets the fill color value associated with a specified color index.
356 *
357 * @param {number} i An index into the |this.colors| array.
358 * @return {string} A string representing a color in 'rgba(R,G,B,A)' format,
359 * where A is the percentage transparency.
360 */
361 Plotter.prototype.getFillColor = function(i) {
362 return this.makeColorTransparent(i, 0.4);
363 };
364
365 /**
366 * Does the actual plotting.
367 */
368 Plotter.prototype.plot = function() {
369 var self = this;
370
371 this.canvasElement_ = this.canvas_();
372 this.rulerDiv_ = this.ruler_();
373
374 // Markers for the result point(s)/events that the mouse is currently
375 // hovering over.
376 this.cursorDiv_ = new VerticalMarker('rgb(100,80,240)');
377 this.cursorDivOther_ = new VerticalMarker('rgb(50,50,50)');
378 this.eventDiv_ = new VerticalMarker('rgb(255, 0, 0)');
379 this.hoveringInfo_ = new HoveringInfo();
380
381 this.resultNode_.appendChild(this.canvasElement_);
382 this.resultNode_.appendChild(this.coordinates_());
383 this.resultNode_.appendChild(this.rulerDiv_);
384 this.resultNode_.appendChild(this.cursorDiv_);
385 this.resultNode_.appendChild(this.cursorDivOther_);
386 this.resultNode_.appendChild(this.eventDiv_);
387 this.resultNode_.appendChild(this.hoveringInfo_.getElement());
388 this.attachEventListeners_();
389
390 // Now draw the canvas.
391 var ctx = this.canvasElement_.getContext('2d');
392
393 // Clear it with white: otherwise canvas will draw on top of existing data.
394 ctx.clearRect(0, 0, this.canvasElement_.width, this.canvasElement_.height);
395
396 // Draw all data lines in the reverse order so the last graph appears on
397 // the backmost and the first graph appears on the frontmost.
398 function draw(plotData, coordinates, colorOffset, stack) {
399 for (var i = plotData.length - 1; i >= 0; --i) {
400 if (stack) {
401 self.plotAreaUnderLine_(ctx, self.getFillColor(colorOffset + i),
402 plotData[i], coordinates);
403 }
404 self.plotLine_(ctx, self.getDataColor(colorOffset + i),
405 plotData[i], coordinates);
406 }
407 }
408 draw(this.plotData_.slice(0,
409 this.graphsOtherStartIndex_ ?
410 this.graphsOtherStartIndex_ :
411 this.plotData_.length),
412 this.coordinates, 0, this.stackedGraph_);
413 if (this.graphsOtherStartIndex_) {
414 draw(this.plotData_.slice(this.graphsOtherStartIndex_),
415 this.unitsYOther_ ? this.coordinatesOther : this.coordinates,
416 this.graphsOtherStartIndex_, this.stackedGraphOther_);
417 }
418
419 // Draw events overlayed on graph if needed.
420 if (this.eventName_ && this.eventInfo_)
421 this.plotEvents_(ctx, 'rgb(255, 150, 150)', this.coordinates);
422
423 this.graduation_divs_ = this.graduations_(this.coordinates, 0, false);
424 if (this.unitsYOther_) {
425 this.graduation_divs_ = this.graduation_divs_.concat(
426 this.graduations_(this.coordinatesOther, 1, true));
427 }
428 for (var i = 0; i < this.graduation_divs_.length; ++i)
429 this.resultNode_.appendChild(this.graduation_divs_[i]);
430 };
431
432 /**
433 * Draws events overlayed on top of an existing graph.
434 *
435 * @param {Object} ctx A canvas element object for drawing.
436 * @param {string} strokeStyles A string representing the drawing style.
437 * @param {Object} coordinateSystem A Coordinates object representing the
438 * coordinate system of the graph.
439 */
440 Plotter.prototype.plotEvents_ = function(ctx, strokeStyles, coordinateSystem) {
441 ctx.strokeStyle = strokeStyles;
442 ctx.fillStyle = strokeStyles;
443 ctx.lineWidth = 1.0;
444
445 ctx.beginPath();
446 var data = this.eventInfo_;
447 for (var index = 0; index < data.length; ++index) {
448 var event_time = data[index][0];
449 var x = coordinateSystem.xPixel(event_time);
450 ctx.moveTo(x, 0);
451 ctx.lineTo(x, this.canvasElement_.offsetHeight);
452 }
453 ctx.closePath();
454 ctx.stroke();
455 };
456
457 /**
458 * Draws a line on the graph.
459 *
460 * @param {Object} ctx A canvas element object for drawing.
461 * @param {string} strokeStyles A string representing the drawing style.
462 * @param {Array} data A list of [x, y] values representing the line to plot.
463 * @param {Object} coordinateSystem A Coordinates object representing the
464 * coordinate system of the graph.
465 */
466 Plotter.prototype.plotLine_ = function(ctx, strokeStyles, data,
467 coordinateSystem) {
468 ctx.strokeStyle = strokeStyles;
469 ctx.fillStyle = strokeStyles;
470 ctx.lineWidth = 2.0;
471
472 ctx.beginPath();
473 var initial = true;
474 var allPoints = [];
475 for (var i = 0; i < data.length; ++i) {
476 var pointX = parseFloat(data[i][0]);
477 var pointY = parseFloat(data[i][1]);
478 var x = coordinateSystem.xPixel(pointX);
479 var y = coordinateSystem.yPixel(0);
480 if (isNaN(pointY)) {
481 // Re-set 'initial' if we're at a gap in the data.
482 initial = true;
483 } else {
484 y = coordinateSystem.yPixel(pointY);
485 if (initial)
486 initial = false;
487 else
488 ctx.lineTo(x, y);
489 }
490
491 ctx.moveTo(x, y);
492 if (!data[i].interpolated) {
493 allPoints.push([x, y]);
494 }
495 }
496 ctx.closePath();
497 ctx.stroke();
498
499 if (!this.is_lookout_) {
500 // Draw a small dot at each point.
501 for (var i = 0; i < allPoints.length; ++i) {
502 ctx.beginPath();
503 ctx.arc(allPoints[i][0], allPoints[i][1], 3, 0, Math.PI*2, true);
504 ctx.fill();
505 }
506 }
507 };
508
509 /**
510 * Fills an area under the given line on the graph.
511 *
512 * @param {Object} ctx A canvas element object for drawing.
513 * @param {string} fillStyle A string representing the drawing style.
514 * @param {Array} data A list of [x, y] values representing the line to plot.
515 * @param {Object} coordinateSystem A Coordinates object representing the
516 * coordinate system of the graph.
517 */
518 Plotter.prototype.plotAreaUnderLine_ = function(ctx, fillStyle, data,
519 coordinateSystem) {
520 if (!data[0]) {
521 return; // nothing to draw
522 }
523
524 ctx.beginPath();
525 var x = coordinateSystem.xPixel(parseFloat(data[0][0]) || 0);
526 var y = coordinateSystem.yPixel(parseFloat(data[0][1]) || 0);
527 var y0 = coordinateSystem.yPixel(coordinateSystem.yMinValue());
528 ctx.moveTo(x, y0);
529 for (var point, i = 0; point = data[i]; ++i) {
530 var pointX = parseFloat(point[0]);
531 var pointY = parseFloat(point[1]);
532 if (isNaN(pointX)) { continue; } // Skip an invalid point.
533 if (isNaN(pointY)) {
534 ctx.lineTo(x, y0);
535 var yWasNaN = true;
536 } else {
537 x = coordinateSystem.xPixel(pointX);
538 y = coordinateSystem.yPixel(pointY);
539 if (yWasNaN) {
540 ctx.lineTo(x, y0);
541 yWasNaN = false;
542 }
543 ctx.lineTo(x, y);
544 }
545 }
546 ctx.lineTo(x, y0);
547
548 ctx.lineWidth = 0;
549 // Clear the area with white color first.
550 var COLOR_WHITE = 'rgb(255,255,255)';
551 ctx.strokeStyle = COLOR_WHITE;
552 ctx.fillStyle = COLOR_WHITE;
553 ctx.fill();
554 // Then, fill the area with the specified color.
555 ctx.strokeStyle = fillStyle;
556 ctx.fillStyle = fillStyle;
557 ctx.fill();
558 };
559
560 /**
561 * Attaches event listeners to DOM nodes.
562 */
563 Plotter.prototype.attachEventListeners_ = function() {
564 var self = this;
565 this.canvasElement_.parentNode.addEventListener(
566 'mousemove', function(evt) { self.onMouseMove_(evt); }, false);
567 this.canvasElement_.parentNode.addEventListener(
568 'mouseover', function(evt) { self.onMouseOver_(evt); }, false);
569 this.canvasElement_.parentNode.addEventListener(
570 'mouseout', function(evt) { self.onMouseOut_(evt); }, false);
571 this.cursorDiv_.addEventListener(
572 'click', function(evt) { self.onMouseClick_(evt); }, false);
573 this.cursorDivOther_.addEventListener(
574 'click', function(evt) { self.onMouseClick_(evt); }, false);
575 this.eventDiv_.addEventListener(
576 'click', function(evt) { self.onMouseClick_(evt); }, false);
577 };
578
579 /**
580 * Update the horizontal line that is following where the mouse is hovering.
581 *
582 * @param {Object} evt A mouse event object representing a mouse move event.
583 */
584 Plotter.prototype.updateRuler_ = function(evt) {
585 var r = this.rulerDiv_;
586 r.style.left = this.canvasElement_.offsetLeft + 'px';
587 r.style.top = this.canvasElement_.offsetTop + 'px';
588 r.style.width = this.canvasElement_.offsetWidth + 'px';
589 var h = domUtils.pageXYOfEvent(evt).y -
590 domUtils.pageXY(this.canvasElement_).y;
591 if (h > this.canvasElement_.offsetHeight)
592 h = this.canvasElement_.offsetHeight;
593 r.style.height = h + 'px';
594 };
595
596 /**
597 * Update the highlighted data point at the x value that the mouse is hovering
598 * over.
599 *
600 * @param {Object} coordinateSystem A Coordinates object representing the
601 * coordinate system of the graph.
602 * @param {number} currentIndex The index into the |this.plotData| array of the
603 * data point being hovered over, for a given line.
604 * @param {Object} cursorDiv A DOM element div object representing the highlight
605 * itself.
606 * @param {number} dataIndex The index into the |this.plotData| array of the
607 * line being hovered over.
608 */
609 Plotter.prototype.updateCursor_ = function(coordinateSystem, currentIndex,
610 cursorDiv, dataIndex) {
611 var c = cursorDiv;
612 c.style.top = this.canvasElement_.offsetTop + 'px';
613 c.style.height = this.canvasElement_.offsetHeight + 'px';
614
615 // Left point is half-way to the previous x value, unless it's the first
616 // point, in which case it's the x value of the current point.
617 var leftPoint = null;
618 if (currentIndex == 0) {
619 leftPoint = this.canvasElement_.offsetLeft +
620 coordinateSystem.xPixel(this.plotData_[dataIndex][0][0]);
621 }
622 else {
623 var left_x = this.canvasElement_.offsetLeft +
624 coordinateSystem.xPixel(this.plotData_[dataIndex][currentIndex - 1][0]);
625 var curr_x = this.canvasElement_.offsetLeft +
626 coordinateSystem.xPixel(this.plotData_[dataIndex][currentIndex][0]);
627 leftPoint = (left_x + curr_x) / 2;
628 }
629 c.style.left = leftPoint;
630
631 // Width is half-way to the next x value minus the left point, unless it's
632 // the last point, in which case it's the x value of the current point minus
633 // the left point.
634 if (currentIndex == this.plotData_[dataIndex].length - 1) {
635 var curr_x = this.canvasElement_.offsetLeft +
636 coordinateSystem.xPixel(this.plotData_[dataIndex][currentIndex][0]);
637 c.style.width = curr_x - leftPoint;
638 }
639 else {
640 var next_x = this.canvasElement_.offsetLeft +
641 coordinateSystem.xPixel(this.plotData_[dataIndex][currentIndex + 1][0]);
642 var curr_x = this.canvasElement_.offsetLeft +
643 coordinateSystem.xPixel(this.plotData_[dataIndex][currentIndex][0]);
644 c.style.width = ((next_x + curr_x) / 2) - leftPoint;
645 }
646 };
647
648 /**
649 * Update the highlighted event at the x value that the mouse is hovering over.
650 *
651 * @param {number} x The x-value (pixel) at which to draw the event highlight
652 * div.
653 * @param {boolean} show Whether or not to show the highlight div.
654 */
655 Plotter.prototype.updateEventDiv_ = function(x, show) {
656 var c = this.eventDiv_;
657 c.style.top = this.canvasElement_.offsetTop + 'px';
658 c.style.height = this.canvasElement_.offsetHeight + 'px';
659
660 if (show) {
661 c.style.left = this.canvasElement_.offsetLeft + (x - 2);
662 c.style.width = 8;
663 } else {
664 c.style.width = 0;
665 }
666 };
667
668 /**
669 * Updates the hovering information.
670 *
671 * @param {Event} evt An event object, which specifies the position of the mouse
672 * cursor.
673 * @param {boolean} show Whether or not to show the hovering info. Even if it's
674 * true, if the cursor position is out of the appropriate area, nothing will
675 * be shown.
676 */
677 Plotter.prototype.updateHoveringInfo_ = function(evt, show) {
678 var evtPageXY = domUtils.pageXYOfEvent(evt);
679 var hoveringInfoPageXY = this.hoveringInfo_.pageXY();
680 var canvasPageXY = domUtils.pageXY(this.canvasElement_);
681
682 var coord = this.coordinates;
683 // p = the mouse cursor position in value coordinates.
684 var p = {'x': coord.xValue(evtPageXY.x - canvasPageXY.x),
685 'y': coord.yValue(evtPageXY.y - canvasPageXY.y)};
686 if (!show ||
687 !(this.stackedGraph_ || this.stackedGraphOther_) ||
688 p.x < coord.xMinValue() || coord.xMaxValue() < p.x ||
689 p.y < coord.yMinValue() || coord.yMaxValue() < p.y) {
690 this.hoveringInfo_.show(false);
691 return;
692 } else {
693 this.hoveringInfo_.show(true);
694 }
695
696 /**
697 * Finds the closest lines (upside and downside of the cursor position).
698 * Returns a set of upside/downside line indices and point index on success
699 * or null.
700 */
701 function findClosestLines(lines, opt_startIndex, opt_endIndex) {
702 var offsetIndex = opt_startIndex || 0;
703 lines =
704 opt_endIndex != null ? lines.slice(offsetIndex, opt_endIndex) :
705 opt_startIndex != null ? lines.slice(offsetIndex) :
706 lines;
707
708 var upsideClosestLineIndex = null;
709 var upsideClosestYDistance = coord.yValueRange();
710 var downsideClosestLineIndex = null;
711 var downsideClosestYDistance = coord.yValueRange();
712 var upsideClosestPointIndex = null;
713
714 for (var lineIndex = 0, line; line = lines[lineIndex]; ++lineIndex) {
715 for (var i = 1; line[i]; ++i) {
716 var p0 = line[i - 1], p1 = line[i];
717 if (p0[0] <= p.x && p.x < p1[0]) {
718 // Calculate y-value of the line at p.x, which is the cursor point.
719 var y = (p.x - p0[0]) / (p1[0] - p0[0]) * (p1[1] - p0[1]) + p0[1];
720 if (p.y < y && y - p.y < upsideClosestYDistance) {
721 upsideClosestLineIndex = lineIndex;
722 upsideClosestYDistance = y - p.y;
723
724 if (p.x - p0[0] < p1[0] - p.x) {
725 upsideClosestPointIndex = i - 1;
726 } else {
727 upsideClosestPointIndex = i;
728 }
729 } else if (y <= p.y && p.y - y < downsideClosestYDistance) {
730 downsideClosestLineIndex = lineIndex;
731 downsideClosestYDistance = p.y - y;
732 }
733 break;
734 }
735 }
736 }
737
738 return (upsideClosestLineIndex != null &&
739 upsideClosestPointIndex != null) ?
740 {'upsideLineIndex': offsetIndex + upsideClosestLineIndex,
741 'downsideLineIndex': downsideClosestYDistance == null ? null :
742 offsetIndex + downsideClosestLineIndex,
743 'upsidePointIndex': offsetIndex + upsideClosestPointIndex} :
744 null;
745 }
746
747 // Find the closest lines above and below the mouse cursor.
748 var closest = null;
749 // Since the other set of graphs are drawn over the first set, try to find
750 // the closest lines from the other set of graphs first.
751 if (this.graphsOtherStartIndex_ && this.stackedGraphOther_) {
752 closest = findClosestLines(this.plotData_, this.graphsOtherStartIndex_);
753 }
754 if (!closest && this.stackedGraph_) {
755 closest = this.graphsOtherStartIndex_ ?
756 findClosestLines(this.plotData_, 0, this.graphsOtherStartIndex_) :
757 findClosestLines(this.plotData_);
758 }
759 if (!closest) {
760 this.hoveringInfo_.show(false);
761 return;
762 }
763
764 // Update the contents of the hovering info box.
765 // Color indicator, description and the value of the item.
766 this.hoveringInfo_.setColorIndicator(
767 this.getDataColor(closest.upsideLineIndex));
768 this.hoveringInfo_.setLegendText(
769 this.dataDescriptions_[closest.upsideLineIndex]);
770 var y1 = this.plotData_[closest.upsideLineIndex][closest.upsidePointIndex][1];
771 var y0 = closest.downsideLineIndex == null ?
772 0 :
773 this.plotData_[closest.downsideLineIndex][closest.upsidePointIndex][1];
774 this.hoveringInfo_.setItemValue(y1 - y0);
775
776 // Locate the hovering info box near the mouse cursor.
777 var DIV_X_OFFSET = 10, DIV_Y_OFFSET = -20;
778 if (evtPageXY.x + this.hoveringInfo_.getElement().offsetWidth <
779 canvasPageXY.x + this.canvasElement_.offsetWidth) {
780 this.hoveringInfo_.locateAtPageXY(evtPageXY.x + DIV_X_OFFSET,
781 evtPageXY.y + DIV_Y_OFFSET);
782 } else { // If lacking space at the right side, locate it at the left side.
783 this.hoveringInfo_.locateAtPageXY(
784 evtPageXY.x - this.hoveringInfo_.getElement().offsetWidth - DIV_X_OFFSET,
785 evtPageXY.y + DIV_Y_OFFSET);
786 }
787 };
788
789 /**
790 * Handle a mouse move event.
791 *
792 * @param {Object} evt A mouse event object representing a mouse move event.
793 */
794 Plotter.prototype.onMouseMove_ = function(evt) {
795 var self = this;
796
797 var canvas = evt.currentTarget.firstChild;
798 var evtPageXY = domUtils.pageXYOfEvent(evt);
799 var canvasPageXY = domUtils.pageXY(this.canvasElement_);
800 var positionX = evtPageXY.x - canvasPageXY.x;
801 var positionY = evtPageXY.y - canvasPageXY.y;
802
803 // Identify the index of the x value that is closest to the mouse x value.
804 var xValue = this.coordinates.xValue(positionX);
805 var lineIndex = !this.stackedGraph_ ? 0 :
806 this.graphsOtherStartIndex_ ? this.graphsOtherStartIndex_ - 1 :
807 this.plotData_.length - 1;
808 var line = this.plotData_[lineIndex];
809 var min_diff = Math.abs(line[0][0] - xValue);
810 indexValueX = 0;
811 for (var i = 1; i < line.length; ++i) {
812 var diff = Math.abs(line[i][0] - xValue);
813 if (diff < min_diff) {
814 min_diff = diff;
815 indexValueX = i;
816 }
817 }
818
819 // Identify the index of the x value closest to the mouse x value for the
820 // other graph being overlayed on top of the original graph, if one exists.
821 if (this.unitsYOther_) {
822 var xValue = this.coordinatesOther.xValue(positionX);
823 var lineIndexOther = !this.stackedGraphOther_ ?
824 this.graphsOtherStartIndex_ : this.plotData_.length - 1;
825 var lineOther = this.plotData_[lineIndexOther];
826 var min_diff = Math.abs(lineOther[0][0] - xValue);
827 var indexValueXOther = 0;
828 for (var i = 1; i < lineOther.length; ++i) {
829 var diff = Math.abs(lineOther[i][0] - xValue);
830 if (diff < min_diff) {
831 min_diff = diff;
832 indexValueXOther = i;
833 }
834 }
835 }
836
837 // Update coordinate information displayed directly underneath the graph.
838 function legendLabel(lineIndex, opt_labelText) {
839 return '<span style="color:' + self.getDataColor(lineIndex) + '">' +
840 (opt_labelText || self.dataDescriptions_[lineIndex]) +
841 '</span>:&nbsp;&nbsp;';
842 }
843 function valuesAtCursor(lineIndex, pointIndex, unitsY, yValue) {
844 return '<span style="color:' + self.getDataColor(lineIndex) + '">' +
845 self.plotData_[lineIndex][pointIndex][0] + ' ' + self.unitsX_ + ': ' +
846 addCommas(self.plotData_[lineIndex][pointIndex][1].toFixed(2)) + ' ' +
847 unitsY + '</span> [hovering at ' + addCommas(yValue.toFixed(2)) +
848 ' ' + unitsY + ']';
849 }
850
851 this.infoBox_.rows[0].label.innerHTML = legendLabel(lineIndex);
852 this.infoBox_.rows[0].content.innerHTML = valuesAtCursor(
853 lineIndex, indexValueX, this.unitsY_, this.coordinates.yValue(positionY));
854 var row = this.infoBox_.rows[1];
855 if (this.unitsYOther_) {
856 row.label.innerHTML = legendLabel(lineIndexOther);
857 row.content.innerHTML = valuesAtCursor(
858 lineIndexOther, indexValueXOther, this.unitsYOther_,
859 this.coordinatesOther.yValue(positionY));
860 } else if (this.graphsOtherStartIndex_) {
861 row.label.innerHTML = legendLabel(
862 this.stackedGraphOther_ ?
863 this.plotData_.length - 1 : this.graphsOtherStartIndex_);
864 row.content.innerHTML = valuesAtCursor(
865 this.stackedGraphOther_ ?
866 this.plotData_.length - 1 : this.graphsOtherStartIndex_,
867 indexValueX, this.unitsY_, this.coordinates.yValue(positionY));
868 } else if (!this.stackedGraph_ && this.dataDescriptions_.length > 1) {
869 row.label.innerHTML = legendLabel(1);
870 row.content.innerHTML = valuesAtCursor(
871 1, indexValueX, this.unitsY_, this.coordinates.yValue(positionY));
872 } else if (row) {
873 row.label.innerHTML = '';
874 row.content.innerHTML = '';
875 }
876
877 // If there is a horizontal marker, also display deltas relative to it.
878 if (this.horizontal_marker_) {
879 var baseline = this.horizontal_marker_.value;
880 var delta = this.coordinates.yValue(positionY) - baseline;
881 var fraction = delta / baseline; // Allow division by 0.
882
883 var deltaStr = (delta >= 0 ? '+' : '') + delta.toFixed(0) + ' ' +
884 this.unitsY_;
885 var percentStr = (fraction >= 0 ? '+' : '') + (fraction * 100).toFixed(3) +
886 '%';
887
888 this.baselineDeltasTd_.innerHTML = deltaStr + ': ' + percentStr;
889
890 if (this.unitsYOther_) {
891 var baseline = this.horizontal_marker_.otherValue;
892 var yValue2 = this.coordinatesOther.yValue(positionY);
893 var delta = yValue2 - baseline;
894 var fraction = delta / baseline; // Allow division by 0.
895
896 var deltaStr = (delta >= 0 ? '+' : '') + delta.toFixed(0) + ' ' +
897 this.unitsYOther_;
898 var percentStr = (fraction >= 0 ? '+' : '') +
899 (fraction * 100).toFixed(3) + '%';
900 this.baselineDeltasTd_.innerHTML += '<br>' + deltaStr + ': ' + percentStr;
901 }
902 }
903
904 this.updateRuler_(evt);
905 this.updateCursor_(this.coordinates, indexValueX, this.cursorDiv_, 0);
906 if (this.unitsYOther_ && this.graphsOtherStartIndex_) {
907 this.updateCursor_(this.coordinatesOther, indexValueXOther,
908 this.cursorDivOther_, this.graphsOtherStartIndex_);
909 }
910
911 // If there are events displayed, see if we're hovering close to an existing
912 // event on the graph, and if so, display the metadata associated with it.
913 if (this.eventName_ != null && this.eventInfo_ != null) {
914 this.infoBox_.rows[1].label.innerHTML = 'Event "' + this.eventName_ +
915 '":&nbsp;&nbsp;';
916 var data = this.eventInfo_;
917 var showed_event = false;
918 var x = 0;
919 for (var index = 0; index < data.length; ++index) {
920 var event_time = data[index][0];
921 x = this.coordinates.xPixel(event_time);
922 if (positionX >= x - 10 && positionX <= x + 10) {
923 var metadata = data[index][1];
924 var metadata_str = "";
925 for (var meta_key in metadata)
926 metadata_str += meta_key + ': ' + metadata[meta_key] + ', ';
927 metadata_str = metadata_str.substring(0, metadata_str.length - 2);
928 this.infoBox_.rows[1].content.innerHTML = event_time + ' ' +
929 this.unitsX_ + ': {' + metadata_str + '}';
930 showed_event = true;
931 this.updateEventDiv_(x, true);
932 break;
933 }
934 }
935 if (!showed_event) {
936 this.coordinatesTdOther_.innerHTML =
937 'move mouse close to vertical event marker';
938 this.updateEventDiv_(x, false);
939 }
940 }
941
942 this.updateHoveringInfo_(evt, true);
943 };
944
945 /**
946 * Handle a mouse over event.
947 *
948 * @param {Object} evt A mouse event object representing a mouse move event.
949 */
950 Plotter.prototype.onMouseOver_ = function(evt) {
951 this.updateHoveringInfo_(evt, true);
952 };
953
954 /**
955 * Handle a mouse out event.
956 *
957 * @param {Object} evt A mouse event object representing a mouse move event.
958 */
959 Plotter.prototype.onMouseOut_ = function(evt) {
960 this.updateHoveringInfo_(evt, false);
961 };
962
963 /**
964 * Handle a mouse click event.
965 *
966 * @param {Object} evt A mouse event object representing a mouse click event.
967 */
968 Plotter.prototype.onMouseClick_ = function(evt) {
969 // Shift-click controls the horizontal reference line.
970 if (evt.shiftKey) {
971 if (this.horizontal_marker_)
972 this.horizontal_marker_.remove();
973
974 var canvasY = domUtils.pageXYOfEvent(evt).y -
975 domUtils.pageXY(this.canvasElement_).y;
976 this.horizontal_marker_ = new HorizontalMarker(
977 this.canvasElement_,
978 this.coordinates.yValue(canvasY),
979 (this.coordinatesOther ? this.coordinatesOther.yValue(canvasY) : null));
980 // Insert before cursor node, otherwise it catches clicks.
981 this.cursorDiv_.parentNode.insertBefore(
982 this.horizontal_marker_.markerDiv, this.cursorDiv_);
983 this.horizontal_marker_.locateAt(this.canvasElement_, canvasY);
984 }
985 };
986
987 /**
988 * Generates and returns a list of div objects representing horizontal lines in
989 * the graph that indicate y-axis values at a computed interval.
990 *
991 * @param {Object} coordinateSystem a Coordinates object representing the
992 * coordinate system for which the graduations should be created.
993 * @param {number} colorIndex An index into the |this.colors| array representing
994 * the color to make the graduations in the event that two graphs with
995 * different coordinate systems are being overlayed on the same plot.
996 * @param {boolean} isRightSide Whether or not the graduations should have
997 * right-aligned text (used when the graduations are for a second graph
998 * that is being overlayed on top of another graph).
999 * @return {Array} An array of DOM Element objects representing the divs.
1000 */
1001 Plotter.prototype.graduations_ = function(coordinateSystem, colorIndex,
1002 isRightSide) {
1003 // Don't allow a graduation in the bottom 5% of the chart or the number label
1004 // would overflow the chart bounds.
1005 var yMin = coordinateSystem.yLowerLimitValue() +
1006 .05 * coordinateSystem.yValueRange();
1007 var yRange = coordinateSystem.yUpperLimitValue() - yMin;
1008
1009 // Use the largest scale that fits 3 or more graduations.
1010 // We allow scales of [...,500, 250, 100, 50, 25, 10,...].
1011 var scale = 5000000000;
1012 while (scale) {
1013 if (Math.floor(yRange / scale) > 2) break; // 5s.
1014 scale /= 2;
1015 if (Math.floor(yRange / scale) > 2) break; // 2.5s.
1016 scale /= 2.5;
1017 if (Math.floor(yRange / scale) > 2) break; // 1s.
1018 scale /= 2;
1019 }
1020
1021 var graduationPosition = yMin + (scale - yMin % scale);
1022 var graduationDivs = [];
1023 while (graduationPosition < coordinateSystem.yUpperLimitValue() ||
1024 yRange == 0) {
1025 var graduation = document.createElement('div');
1026 var canvasPosition;
1027 if (yRange == 0) {
1028 // Center the graduation vertically.
1029 canvasPosition = this.canvasElement_.offsetHeight / 2;
1030 } else {
1031 canvasPosition = coordinateSystem.yPixel(graduationPosition);
1032 }
1033 if (this.unitsYOther_) {
1034 graduation.style.borderTop = '1px dashed ' +
1035 this.makeColorTransparent(colorIndex, 0.4)
1036 } else {
1037 graduation.style.borderTop = '1px dashed rgba(0,0,0,.08)';
1038 }
1039 graduation.style.position = 'absolute';
1040 graduation.style.left = this.canvasElement_.offsetLeft + 'px';
1041 graduation.style.top = canvasPosition + this.canvasElement_.offsetTop +
1042 'px';
1043 graduation.style.width = this.canvasElement_.offsetWidth -
1044 this.canvasElement_.offsetLeft + 'px';
1045 graduation.style.paddingLeft = '4px';
1046 if (this.unitsYOther_)
1047 graduation.style.color = this.makeColorTransparent(colorIndex, 0.9)
1048 else
1049 graduation.style.color = 'rgba(0,0,0,.4)';
1050 graduation.style.fontSize = '9px';
1051 graduation.style.paddingTop = '0';
1052 graduation.style.zIndex = '-1';
1053 if (isRightSide)
1054 graduation.style.textAlign = 'right';
1055 if (yRange == 0)
1056 graduation.innerHTML = addCommas(yMin);
1057 else
1058 graduation.innerHTML = addCommas(graduationPosition);
1059 graduationDivs.push(graduation);
1060 if (yRange == 0)
1061 break;
1062 graduationPosition += scale;
1063 }
1064 return graduationDivs;
1065 };
1066
1067 /**
1068 * Generates and returns a div object representing the horizontal line that
1069 * follows the mouse pointer around the plot.
1070 *
1071 * @return {Object} A DOM Element object representing the div.
1072 */
1073 Plotter.prototype.ruler_ = function() {
1074 var ruler = document.createElement('div');
1075 ruler.setAttribute('class', 'plot-ruler');
1076 ruler.style.borderBottom = '1px dotted black';
1077 ruler.style.position = 'absolute';
1078 ruler.style.left = '-2px';
1079 ruler.style.top = '-2px';
1080 ruler.style.width = '0px';
1081 ruler.style.height = '0px';
1082 return ruler;
1083 };
1084
1085 /**
1086 * Generates and returns a canvas object representing the plot itself.
1087 *
1088 * @return {Object} A DOM Element object representing the canvas.
1089 */
1090 Plotter.prototype.canvas_ = function() {
1091 var canvas = document.createElement('canvas');
1092 canvas.setAttribute('id', '_canvas');
1093 canvas.setAttribute('class', 'plot');
1094 canvas.setAttribute('width', this.coordinates.widthMax);
1095 canvas.setAttribute('height', this.coordinates.heightMax);
1096 canvas.plotter = this;
1097 return canvas;
1098 };
1099
1100 /**
1101 * Generates and returns a div object representing the coordinate information
1102 * displayed directly underneath a graph.
1103 *
1104 * @return {Object} A DOM Element object representing the div.
1105 */
1106 Plotter.prototype.coordinates_ = function() {
1107 var coordinatesDiv = document.createElement('div');
1108 var table_html = '<table border=0 width="100%"';
1109 if (this.is_lookout_) {
1110 table_html += ' style="font-size:0.8em"';
1111 }
1112 table_html += '><tbody><tr>';
1113 table_html += '<td><span class="legend_item"></span>' +
1114 '<span class="plot-coordinates"><i>move mouse over graph</i></span></td>';
1115 table_html += '<td align="right">x-axis is ' + this.unitsX_ + '</td>';
1116 table_html += '</tr><tr>';
1117 table_html += '<td><span class="legend_item"></span>' +
1118 '<span class="plot-coordinates"></span></td>';
1119
1120 if (!this.is_lookout_) {
1121 table_html += '<td align="right" style="color: ' + HorizontalMarker.COLOR +
1122 '"><i>Shift-click to place baseline.</i></td>';
1123 }
1124 table_html += '</tr></tbody></table>';
1125 coordinatesDiv.innerHTML = table_html;
1126
1127 var trs = coordinatesDiv.querySelectorAll('tr');
1128 this.infoBox_ = {rows: []};
1129 this.infoBox_.rows.push({
1130 label: trs[0].querySelector('span.legend_item'),
1131 content: trs[0].querySelector('span.plot-coordinates')});
1132 if (this.dataDescriptions_.length > 1 || this.eventName_) {
1133 this.infoBox_.rows.push({
1134 label: trs[1].querySelector('span.legend_item'),
1135 content: trs[1].querySelector('span.plot-coordinates')});
1136 }
1137
1138 this.baselineDeltasTd_ = trs[1].childNodes[1];
1139
1140 // Add a summary of legends in case of stacked graphs.
1141 if (this.stackedGraph_ || this.stackedGraphOther_) {
1142 var legendPane = document.createElement('div');
1143 legendPane.style.fontSize = '80%';
1144 coordinatesDiv.appendChild(legendPane);
1145
1146 if (this.graphsOtherStartIndex_) {
1147 legendPane.appendChild(
1148 this.createLegendsSummaryElement_(
1149 this.dataDescriptions_.slice(0, this.graphsOtherStartIndex_),
1150 0));
1151 legendPane.appendChild(
1152 this.createLegendsSummaryElement_(
1153 this.dataDescriptions_.slice(this.graphsOtherStartIndex_),
1154 this.graphsOtherStartIndex_));
1155 } else {
1156 legendPane.appendChild(
1157 this.createLegendsSummaryElement_(this.dataDescriptions_, 0));
1158 }
1159 }
1160
1161 return coordinatesDiv;
1162 };
1163
1164 /**
1165 * Creates and returns a DOM element which shows a summary of legends.
1166 *
1167 * @param {!Array.<string>} legendTexts An array of legend texts.
1168 * @param {number} colorIndexOffset Offset index for color. i-th legend text
1169 * has an indicator in {@code (colorIndexOffset + i)}-th color
1170 * @return {!Element} An element which shows a summary of legends.
1171 */
1172 Plotter.prototype.createLegendsSummaryElement_ = function(legendTexts,
1173 colorIndexOffset) {
1174 var containerElem = document.createElement('div');
1175
1176 for (var i = 0, text; text = legendTexts[i]; ++i) {
1177 var colorIndicatorElem = document.createElement('div');
1178 colorIndicatorElem.style.display = 'inline-block';
1179 colorIndicatorElem.style.width = '1em';
1180 colorIndicatorElem.style.height = '1em';
1181 colorIndicatorElem.style.verticalAlign = 'text-bottom';
1182 colorIndicatorElem.style.margin = '0 0.24em 0 0';
1183 colorIndicatorElem.style.border = '1px solid #000';
1184 colorIndicatorElem.style.backgroundColor =
1185 this.getDataColor(colorIndexOffset + i);
1186 var legendTextElem = document.createElement('span');
1187 legendTextElem.textContent = text;
1188 var legendElem = document.createElement('span');
1189 legendElem.style.whiteSpace = 'nowrap';
1190 legendElem.appendChild(colorIndicatorElem);
1191 legendElem.appendChild(legendTextElem);
1192 legendElem.style.margin = '0 0.8em 0 0';
1193 containerElem.appendChild(legendElem);
1194 // Add a space to break lines if necessary.
1195 containerElem.appendChild(document.createTextNode(' '));
1196 }
1197
1198 return containerElem;
1199 };
OLDNEW
« no previous file with comments | « perf/dashboard/ui/endure_js/graph_utils.js ('k') | perf/dashboard/ui/endure_plotter.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698