/** * @license * File: sigplot.playback.js * * Copyright (c) 2012-2014, Michael Ihde, All rights reserved. * Copyright (c) 2012-2014, Axios Inc., All rights reserved. * * This file is part of SigPlot. * * SigPlot is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation; either version 3.0 of the License, or * (at your option) any later version. This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the * GNU Lesser General Public License along with SigPlot. */ /* global mx */ /* global m */ (function(sigplot, mx, m, undefined) { function calculate_stats(xpoint, ypoint, npts, xmin, xmax, ymin, ymax) { var stats = { npts: 0, mean: 0, m2: 0, variance: 0, stddev: 0, max: { x: null, y: null, idx: null }, min: { x: null, y: null, idx: null } }; // Use Knuth Online Algorithm var delta; for (var i = 0; i < npts; i++) { if (xmin && xpoint[i] < xmin) { continue; } if (xmax && xpoint[i] > xmax) { continue; } if (ymin && ypoint[i] < ymin) { continue; } if (ymax && ypoint[i] > ymax) { continue; } stats.npts += 1; delta = ypoint[i] - stats.mean; stats.mean = stats.mean + (delta / stats.npts); stats.m2 = stats.m2 + delta * (ypoint[i] - stats.mean); if ((stats.max.x === null) || (ypoint[i] > stats.max.y)) { stats.max = { x: xpoint[i], y: ypoint[i], idx: i }; } if ((stats.min.x === null) || (ypoint[i] < stats.min.y)) { stats.min = { x: xpoint[i], y: ypoint[i], idx: i }; } } if (stats.npts < 2) { stats.variance = 0; stats.stddev = 0; } else { stats.variance = stats.m2 / (stats.npts - 1); stats.stddev = Math.sqrt(stats.variance); } return stats; } /** * @constructor * @param options * @returns {sigplot.Statistics1DPlugin} */ sigplot.Statistics1DPlugin = function(options) { this.options = (options === undefined) ? {} : options; this.enabled = this.options.enabled || false; this.font = this.options.font; // Features off by default this.show_max = this.options.show_max || false; this.show_min = this.options.show_min || false; // Features on by default this.show_mean = this.options.show_mean || true; this.show_stddev = this.options.show_stddev || true; }; sigplot.Statistics1DPlugin.prototype = { init: function(plot) { this.plot = plot; this.statistics = {}; this.color = this.options.color || this.plot._Mx.xwfg; this.symbol = this.options.symbol || mx.L_CircleSymbol; this.radius = this.options.radius || 3; // Create event handlers var self = this; this.onlayeradd = function(evt) { self.statistics[evt.index] = { npts: null, mean: null, m2: null, variance: null, stddev: null, max: { x: null, y: null, idx: null }, min: { x: null, y: null, idx: null } }; }; this.plot.addListener("lyradd", this.onlayeradd); this.onlayerdel = function(evt) { delete self.statistics[evt.index]; }; this.plot.addListener("lyrdel", this.onlayerdel); this.onlayerdraw = function(evt) { var Mx = self.plot._Mx; if (evt.layer.xpoint && evt.layer.ypoint) { self.statistics[evt.index] = calculate_stats(evt.layer.xpoint, evt.layer.ypoint, evt.layer.npts, Mx.stk[Mx.level].xmin, Mx.stk[Mx.level].xmax, Mx.stk[Mx.level].ymin, Mx.stk[Mx.level].ymax); } }; this.plot.addListener("lyrdraw", this.onlayerdraw); this.onkeypress = function(evt) { if (evt.keyCode === 83 && evt.shiftKey) { // 'shift-s' self.enabled = !self.enabled; self.plot.refresh(); } }; this.plot.addListener("plotkeypress", this.onkeypress); this.onmtag = function(evt) { // No layers mean nothing to do if (self.plot._Gx.lyr.length === 0) { return; } // Only do statistics if the mtag is a box if (evt.w && evt.h) { var stats_text = ""; for (var layer_n = 0; layer_n < self.plot._Gx.lyr.length; layer_n++) { var xpoint = self.plot._Gx.lyr[layer_n].xpoint; var ypoint = self.plot._Gx.lyr[layer_n].ypoint; var npts = self.plot._Gx.lyr[layer_n].npts; if (xpoint && ypoint) { var stats = calculate_stats(xpoint, ypoint, npts, evt.x, evt.x + evt.w, evt.y - evt.h, evt.y); stats_text = stats_text + "\n" + "N: " + mx.format_g(stats.npts, 10, 3, true) + "\n" + "Min X: " + mx.format_g(evt.x, 10, 3, true) + "\n" + "Max X: " + mx.format_g(evt.x + evt.w, 10, 3, true) + "\n" + "Min Y: " + mx.format_g(stats.min.y, 10, 3, true) + "\n" + "Max Y: " + mx.format_g(stats.max.y, 10, 3, true) + "\n" + "\u03BC Y: " + mx.format_g(stats.mean, 10, 3, true) + "\n" + "\u03C3 Y: " + mx.format_g(stats.stddev, 10, 3, true); } } mx.message(self.plot._Mx, stats_text, null, evt.xpos, evt.ypos); } }; this.plot.addListener("mtag", this.onmtag); }, menu: function() { var self = this; return { text: "Statistics", menu: { title: "STATISTICS OPTIONS", items: [{ text: "Enable", style: "checkbox", checked: this.enabled, handler: function() { self.enabled = !self.enabled; } }, { text: "Features", style: "separator" }, { text: "Show Max", style: "checkbox", checked: this.show_max, handler: function() { self.show_max = !self.show_max; } }, { text: "Show Min", style: "checkbox", checked: this.show_min, handler: function() { self.show_min = !self.show_min; } }, { text: "Show Mean", style: "checkbox", checked: this.show_mean, handler: function() { self.show_mean = !self.show_mean; } }, { text: "Show Std. Dev.", style: "checkbox", checked: this.show_stddev, handler: function() { self.show_stddev = !self.show_stddev; } }] } }; }, refresh: function(canvas) { var Mx = this.plot._Mx; var self = this; var ctx = canvas.getContext("2d"); if (!this.enabled) { return; } mx.onCanvas(Mx, canvas, function() { for (var layer_n in self.statistics) { var stats = self.statistics[layer_n]; if (self.show_max) { if (stats.max.x !== null && stats.max.y !== null) { var pix = mx.real_to_pixel(Mx, stats.max.x, stats.max.y, false); if (!pix.clipped) { mx.draw_symbol(Mx, self.color, pix.x, pix.y, self.symbol, self.radius); } } } if (self.show_min) { if (stats.min.x !== null && stats.min.y !== null) { var pix = mx.real_to_pixel(Mx, stats.min.x, stats.min.y, false); if (!pix.clipped) { mx.draw_symbol(Mx, self.color, pix.x, pix.y, self.symbol, self.radius); } } } if (self.show_mean) { if (stats.mean !== null) { var pix = mx.real_to_pixel(Mx, null, stats.mean, false); if (!pix.clipped_y) { mx.draw_line(Mx, self.color, Mx.l, pix.y, Mx.r, pix.y); ctx.font = self.font || Mx.font.font; ctx.globalAlpha = 1; ctx.textBaseline = "alphabetic"; ctx.textAlign = "left"; ctx.fillStyle = self.color; var height = ctx.measureText("M").width; // approximation of height var text = mx.format_g(stats.mean, 6, 3, true); ctx.fillText("\u03BC=" + text, Mx.l + 5, pix.y - 5); } } } if (self.show_stddev) { if (stats.sqrt !== null) { var pix = mx.real_to_pixel(Mx, null, stats.mean + stats.stddev, false); if (!pix.clipped_y) { mx.draw_line(Mx, self.color, Mx.l, pix.y, Mx.r, pix.y, 1, { mode: "dashed", on: 3, off: 3 }); ctx.font = self.font || Mx.font.font; ctx.globalAlpha = 1; ctx.textBaseline = "alphabetic"; ctx.textAlign = "left"; ctx.fillStyle = self.color; var height = ctx.measureText("M").width; // approximation of height var text = mx.format_g(stats.stddev, 6, 3, true); ctx.fillText("\u03C3=+" + text, Mx.l + 5, pix.y + 5 + height); } var pix = mx.real_to_pixel(Mx, null, stats.mean - stats.stddev, false); if (!pix.clipped_y) { mx.draw_line(Mx, self.color, Mx.l, pix.y, Mx.r, pix.y, 1, { mode: "dashed", on: 3, off: 3 }); ctx.font = self.font || Mx.font.font; ctx.globalAlpha = 1; ctx.textBaseline = "alphabetic"; ctx.textAlign = "left"; ctx.fillStyle = self.color; var height = ctx.measureText("M").width; // approximation of height var text = mx.format_g(stats.stddev, 6, 3, true); ctx.fillText("\u03C3=-" + text, Mx.l + 5, pix.y - 5); } } } } }); }, dispose: function() { this.plot.addListener("lyradd", this.onlayerdraw); this.plot.addListener("lyrdel", this.onlayerdraw); this.plot.removeListener("lyrdraw", this.onlayerdraw); this.plot = undefined; this.boxes = undefined; this.statistics = {}; } }; }(window.sigplot = window.sigplot || {}, mx, m));