diff options
Diffstat (limited to 'timings/js/plotter.js')
-rw-r--r-- | timings/js/plotter.js | 336 |
1 files changed, 336 insertions, 0 deletions
diff --git a/timings/js/plotter.js b/timings/js/plotter.js new file mode 100644 index 0000000..86fb230 --- /dev/null +++ b/timings/js/plotter.js @@ -0,0 +1,336 @@ +/* + Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. + Use of this source code is governed by a BSD-style license that can be + found in the LICENSE file. +*/ + +// Collection of classes used to plot data in a <canvas>. Create a Plotter() +// to generate a plot. + +// vertical marker for columns +function Marker(color) { + var m = document.createElement("DIV"); + m.setAttribute("class", "plot-cursor"); + m.style.backgroundColor = color; + m.style.opacity = "0.3"; + m.style.position = "absolute"; + m.style.left = "-2px"; + m.style.top = "-2px"; + m.style.width = "0px"; + m.style.height = "0px"; + return m; +} + +/** + * HorizontalMarker class + * Create a horizontal marker at the indicated mouse location. + * @constructor + * + * @param canvasRect {Object} The canvas bounds (in client coords). + * @param clientY {Number} The vertical mouse click location that spawned + * the marker, in the client coordinate space. + * @param yValue {Number} The plotted value corresponding to the clientY + * click location. + */ +function HorizontalMarker(canvasRect, clientY, yValue) { + // Add a horizontal line to the graph. + var m = document.createElement("DIV"); + m.setAttribute("class", "plot-baseline"); + m.style.backgroundColor = HorizontalMarker.COLOR; + m.style.opacity = "0.3"; + m.style.position = "absolute"; + m.style.left = canvasRect.offsetLeft; + var h = HorizontalMarker.HEIGHT; + m.style.top = (clientY - h/2).toFixed(0) + "px"; + m.style.width = canvasRect.width + "px"; + m.style.height = h + "px"; + this.markerDiv_ = m; + + this.value = yValue; +} + +HorizontalMarker.HEIGHT = 5; +HorizontalMarker.COLOR = "rgb(0,100,100)"; + +// Remove the horizontal line from the graph. +HorizontalMarker.prototype.remove_ = function() { + this.markerDiv_.parentNode.removeChild(this.markerDiv_); +} + +/** + * Plotter class + * @constructor + * + * Draws a chart using CANVAS element. Takes array of lines to draw with + * deviations values for each data sample. + * + * @param {Array} clNumbers list of clNumbers for each data sample. + * @param {Array} plotData list of arrays that represent individual lines. + * The line itself is an Array of value and stdd. + * @param {Array} dataDescription list of data description for each line + * in plotData. + * @units {string} units name of measurement used to describe plotted data. + * + * Example of the plotData: + * [ + * [line 1 data], + * [line 2 data] + * ]. + * Line data looks like [[point one], [point two]]. + * And individual points are [value, deviation value] + */ +function Plotter(clNumbers, plotData, dataDescription, units, resultNode) { + this.clNumbers_ = clNumbers; + this.plotData_ = plotData; + this.dataDescription_ = dataDescription; + this.resultNode_ = resultNode; + this.units_ = units; + this.coordinates = new Coordinates(plotData); + + // A color palette that's unambigous for normal and color-deficient viewers. + // Values are (red, green, blue) on a scale of 255. + // Taken from http://jfly.iam.u-tokyo.ac.jp/html/manuals/pdf/color_blind.pdf + this.colors = [[0, 114, 178], // blue + [230, 159, 0], // orange + [0, 158, 115], // green + [204, 121, 167], // purplish pink + [86, 180, 233], // sky blue + [213, 94, 0], // dark orange + [0, 0, 0], // black + [240, 228, 66] // yellow + ]; +} + +/** + * Does the actual plotting. + */ +Plotter.prototype.plot = function() { + var canvas = this.canvas(); + this.coordinates_div_ = this.coordinates_(); + this.ruler_div_ = this.ruler(); + // marker for the result-point that the mouse is currently over + this.cursor_div_ = new Marker("rgb(100,80,240)"); + // marker for the result-point for which details are shown + this.marker_div_ = new Marker("rgb(100,100,100)"); + var ctx = canvas.getContext("2d"); + for (var i = 0; i < this.plotData_.length; i++) + this.plotLine_(ctx, this.nextColor(i), this.plotData_[i]); + + this.resultNode_.appendChild(canvas); + this.resultNode_.appendChild(this.coordinates_div_); + + this.resultNode_.appendChild(this.ruler_div_); + this.resultNode_.appendChild(this.cursor_div_); + this.resultNode_.appendChild(this.marker_div_); + this.attachEventListeners(canvas); + this.canvasRectangle = { + "offsetLeft": canvas.offsetLeft, + "offsetTop": canvas.offsetTop, + "width": canvas.offsetWidth, + "height": canvas.offsetHeight + }; +}; + +Plotter.prototype.drawDeviationBar_ = function(context, strokeStyles, x, y, + deviationValue) { + context.strokeStyle = strokeStyles; + context.lineWidth = 1.0; + context.beginPath(); + context.moveTo(x, (y + deviationValue)); + context.lineTo(x, (y - deviationValue)); + context.moveTo(x, (y - deviationValue)); + context.closePath(); + context.stroke(); +}; + +Plotter.prototype.plotLine_ = function(ctx, strokeStyles, data) { + ctx.strokeStyle = strokeStyles; + ctx.lineWidth = 2.0; + ctx.beginPath(); + var initial = true; + var deviationData = []; + for (var i = 0; i < data.length; i++) { + var x = this.coordinates.xPoints(i); + var value = data[i][0]; + var stdd = data[i][1]; + var y = 0.0; + var err = 0.0; + if (isNaN(value)) { + // Re-set 'initial' if we're at a gap in the data. + initial = true; + } else { + y = this.coordinates.yPoints(value); + // We assume that the stdd will only be NaN (missing) when the value is. + if (parseFloat(value) != 0.0) + err = y * parseFloat(stdd) / parseFloat(value); + if (initial) + initial = false; + else + ctx.lineTo(x, y); + } + + ctx.moveTo(x, y); + deviationData.push([x, y, err]) + } + ctx.closePath(); + ctx.stroke(); + + for (var i = 0; i < deviationData.length; i++) { + this.drawDeviationBar_(ctx, strokeStyles, deviationData[i][0], + deviationData[i][1], deviationData[i][2]); + } +}; + +Plotter.prototype.attachEventListeners = function(canvas) { + var self = this; + canvas.parentNode.addEventListener( + "mousemove", function(evt) { self.onMouseMove_(evt); }, false); + this.cursor_div_.addEventListener( + "click", function(evt) { self.onMouseClick_(evt); }, false); +}; + +Plotter.prototype.updateRuler_ = function(evt) { + var r = this.ruler_div_; + r.style.left = this.canvasRectangle.offsetLeft + "px"; + + r.style.top = this.canvasRectangle.offsetTop + "px"; + r.style.width = this.canvasRectangle.width + "px"; + var h = evt.clientY - this.canvasRectangle.offsetTop; + if (h > this.canvasRectangle.height) + h = this.canvasRectangle.height; + r.style.height = h + "px"; +}; + +Plotter.prototype.updateCursor_ = function() { + var c = this.cursor_div_; + c.style.top = this.canvasRectangle.offsetTop + "px"; + c.style.height = this.canvasRectangle.height + "px"; + var w = this.canvasRectangle.width / this.clNumbers_.length; + var x = (this.canvasRectangle.offsetLeft + + w * this.current_index_).toFixed(0); + c.style.left = x + "px"; + c.style.width = w + "px"; +}; + + +Plotter.prototype.onMouseMove_ = function(evt) { + var canvas = evt.currentTarget.firstChild; + var positionX = evt.clientX - this.canvasRectangle.offsetLeft; + var positionY = evt.clientY - this.canvasRectangle.offsetTop; + + this.current_index_ = this.coordinates.dataSampleIndex(positionX); + var yValue = this.coordinates.yValue(positionY); + + this.coordinates_td_.innerHTML = + "r" + this.clNumbers_[this.current_index_] + ": " + + this.plotData_[0][this.current_index_][0].toFixed(2) + " " + + this.units_ + " +/- " + + this.plotData_[0][this.current_index_][1].toFixed(2) + " " + + yValue.toFixed(2) + " " + this.units_; + + // If there is a horizontal marker, also display deltas relative to it. + if (this.horizontal_marker_) { + var baseline = this.horizontal_marker_.value; + var delta = yValue - baseline + var fraction = delta / baseline; // allow division by 0 + + var deltaStr = (delta >= 0 ? "+" : "") + delta.toFixed(0) + " " + + this.units_; + var percentStr = (fraction >= 0 ? "+" : "") + + (fraction * 100).toFixed(3) + "%"; + + this.baseline_deltas_td_.innerHTML = deltaStr + ": " + percentStr; + } + + this.updateRuler_(evt); + this.updateCursor_(); +}; + +Plotter.prototype.onMouseClick_ = function(evt) { + // Shift-click controls the horizontal reference line. + if (evt.shiftKey) { + if (this.horizontal_marker_) { + this.horizontal_marker_.remove_(); + } + + var canvasY = evt.clientY - this.canvasRectangle.offsetTop; + this.horizontal_marker_ = new HorizontalMarker(this.canvasRectangle, + evt.clientY, this.coordinates.yValue(canvasY)); + + // Insert before cursor node, otherwise it catches clicks. + this.cursor_div_.parentNode.insertBefore( + this.horizontal_marker_.markerDiv_, this.cursor_div_); + } else { + var index = this.current_index_; + var m = this.marker_div_; + var c = this.cursor_div_; + m.style.top = c.style.top; + m.style.left = c.style.left; + m.style.width = c.style.width; + m.style.height = c.style.height; + if ("onclick" in this) { + var this_x = this.clNumbers_[index]; + var prev_x = index > 0 ? (parseInt(this.clNumbers_[index-1]) + 1) : + this_x; + this.onclick(prev_x, this_x); + } + } +}; + +Plotter.prototype.canvas = function() { + var canvas = document.createElement("CANVAS"); + canvas.setAttribute("id", "_canvas"); + canvas.setAttribute("class", "plot"); + canvas.setAttribute("width", this.coordinates.widthMax); + canvas.setAttribute("height", this.coordinates.heightMax); + return canvas; +}; + +Plotter.prototype.ruler = function() { + ruler = document.createElement("DIV"); + ruler.setAttribute("class", "plot-ruler"); + ruler.style.borderBottom = "1px dotted black"; + ruler.style.position = "absolute"; + ruler.style.left = "-2px"; + ruler.style.top = "-2px"; + ruler.style.width = "0px"; + ruler.style.height = "0px"; + return ruler; +}; + +Plotter.prototype.coordinates_ = function() { + var coordinatesDiv = document.createElement("DIV"); + var table_html = + "<table border=0 width='100%'><tbody><tr>" + + "<td colspan=2 class='legend'>Legend: "; + for (var i = 0; i < this.dataDescription_.length; i++) { + if (i > 0) + table_html += ", "; + table_html += "<span class='legend_item' style='color:" + + this.nextColor(i) + "'>" + this.dataDescription_[i] + "</span>"; + } + table_html += "</td></tr><tr>" + + "<td class='plot-coordinates'><i>move mouse over graph</i></td>" + + "<td align=right style='color: " + HorizontalMarker.COLOR + + "'><i>Shift-click to place baseline</i></td>" + + "</tr></tbody></table>"; + coordinatesDiv.innerHTML = table_html; + + var tr = coordinatesDiv.firstChild.firstChild.childNodes[1]; + this.coordinates_td_ = tr.childNodes[0]; + this.baseline_deltas_td_ = tr.childNodes[1]; + + return coordinatesDiv; +}; + +Plotter.prototype.nextColor = function(i) { + var index = i % this.colors.length; + return "rgb(" + this.colors[index][0] + "," + + this.colors[index][1] + "," + + this.colors[index][2] + ")"; +}; + +Plotter.prototype.log = function(val) { + document.getElementById('log').appendChild( + document.createTextNode(val + '\n')); +}; |