Source: sigplot.js

/**
 * @license
 * File: sigplot.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.
 */

/*jslint nomen: true, browser: true, devel: true */

/**
 * This namespace provides the implementation of the plotting functionality.
 *
 * @namespace sigplot
 */
window.sigplot = window.sigplot || {};

//Uses Immediately-invoked Function Expressions (IIFE)s for namespaces
//See:
//http://addyosmani.com/blog/essential-js-namespacing/ for details.
//http://www.ethangardner.com/articles/javascript-namespace-strategy-for-large-applications/
(function(sigplot, mx, m) {
    /* global setKeypressHandler */
    /* global getKeyCode */
    /* global BlueFileReader */
    /* global Spinner */

    /**
     * Text of the keypress help dialog.
     *
     * @memberOf sigplot
     * @private
     */
    var KEYPRESS_HELP = "Keypress Table:\n" + "--------------\n" + "?    - Main help box.\n" + "A    - Toggle display x,y readouts:\n" + "       (absc) -> (index) -> (1/absc) -> (time).\n" + "B    - Toggle LM Drag Mode:\n" + "       (box) -> (horizontal) -> (vertical).\n" + "C    - Toggle controls.\n" + "L    - Toggle legend.\n" + "M    - Pops up main menu\n" + "R    - Toggle display specs (x/y readout)\n" + "S    - Toggle display specs and axes.\n" + "T    - Popup box with timecode value at mouse.\n" + "X    - Popup box with X value at mouse.\n" + "Y    - Popup box with Y value at mouse.\n" + "F    - Toggle fullscreen.\n";

    /**
     * Text of the main help dialog.
     *
     * @memberOf sigplot
     * @private
     */
    var MAIN_HELP = "To zoom, press and drag the left mouse (LM) over the region of interest and release. " + "To unzoom, press right mouse (RM).  Press the middle mouse (MM) button or press the " + "key 'M' to bring up the menu.  Information about keypresses and what they do can be found" + "by selecting 'Keypress Info' from the main menu.";

    /**
     * Attempts basic checks to determine if the browser is compatible with
     * sigplot.
     *
     * @memberOf sigplot
     * @private
     */
    sigplot.browserIsCompatible = function browserIsCompatible() {
        // We need a Canvas
        var test_canvas = document.createElement('canvas');
        var hascanvas = (test_canvas.getContext) ? true : false;

        // We need ArrayBuffer
        var hasarraybuf = ("ArrayBuffer" in window);

        // File and FileReader are optional...and only
        // required if the user wants to plot local files
        return (hascanvas && hasarraybuf);
    };

    /**
     *
     *
     * @memberOf sigplot
     * @private
     */
    var PointArray = null;

    /**
     * True if we detected that we are on an iOS device
     *
     * @memberOf sigplot
     * @private
     */
    var iOS = (navigator.userAgent.match(/(iPad|iPhone|iPod)/i) ? true : false);
    if ((iOS) ||                                   // iOS doesn't support Float64
	(typeof Float64Array === 'undefined') ||   // If it's undefined it's obviously not supported
	(Float64Array.emulated) ||                 // If it's emulated, don't waste time on extra precision
	(!Float64Array.BYTES_PER_ELEMENT)) {       // If bytes per element isn't defined, it's a buggy implementation (i.e. PhantomJS)
        sigplot.PointArray = Float32Array;
    } else {
        sigplot.PointArray = Float64Array;
    }



    /**
     * Construct and render a plot.
     *
     * @constructor
     *
     * @example plot = new sigplot.Plot(document.getElementById('plot'), {});
     *
     * @param element
     *            a 'div' DOM elements
     * @param [options]
     *            alters the behavior of the plot.
     *
     * @param {String}
     *            options.cmode the plot rendering mode "IN" = Index, "AB" =
     *            Abscissa, "MA" = Magnitude, "PH" = Phase, "RE" = Real, "IM" =
     *            Imaginary, "LO" or "D1" = 10*log, "L2" or "D2" = 20*log, "RI"
     *            or "IR" = Real vs. Imaginary
     *
     * @param {String}
     *            options.phunits the phase units "D" = Degrees, "R" = Radians,
     *            "C" = Cycles
     *
     * @param {Boolean}
     *            options.cross display cross hairs on the plot
     *
     * @param {Boolean}
     *            options.nogrid hide the background grid
     *
     * @param {Boolean}
     *            options.legend hide the legned
     *
     * @param {Boolean}
     *            options.nopan disable panning on the plot
     *
     * @param {Boolean}
     *            options.nomenu disable the middle-click menu
     *
     * @param {Boolean}
     *            options.nospec hide all plot specification displays
     *
     * @param {Boolean}
     *            options.noxaxis hide the x-axis
     *
     * @param {Boolean}
     *            options.noyaxis hide the y-axis
     *
     * @param {Boolean}
     *            options.noreadout hide the plot readout area
     *
     * @param {Boolean}
     *            options.nodragdrop prevent file drag drop
     *
     * @param {Number}
     *            options.scroll_time_interval
     *
     * @param {Boolean}
     *            options.index use the data-index in the X axis
     *
     * @param {Number}
     *            options.autox auto-scaling settings for X axis
     *
     * @param {Number}
     *            options.xmin the minimum range to display on the X axis
     *
     * @param {Number}
     *            options.xmax the maximum range to display on the X axis
     *
     * @param {Number}
     *            options.xlab the units that X-axis uses (see m.UNITS)
     *
     * @param {Number}
     *            options.xdiv the number of divisions on the X axis
     *
     * @param {Number}
     *            options.xcnt configure the mtag mouse controls 0 = Off, 1
     *            (default) = LM Click, 2 = Continuous
     *
     * @param {String}
     *            options.rubberbox_mode controls the behavior of the rubberbox
     *            "zoom" (default) = zoom to the selected area "box" = trigger
     *            an mtag action on the selected area
     *
     * @param {Number}
     *            options.line the line type to draw 0 = None, 1 = Verticals, 2 =
     *            Horizontals, 3 (default) = Connecting
     *
     * @param {Number}
     *            options.autoy auto-scaling settings for Y axis
     *
     * @param {Number}
     *            options.ylab the units that Y-axis uses (see m.UNITS)
     *
     * @param {Number}
     *            options.ymin the minimum range to display on the Y axis
     *
     * @param {Number}
     *            options.ymax the maximum range to display on the Y axis
     *
     * @param {Number}
     *            options.ydiv the number of divisions on the Y axis
     *
     * @param {Boolean}
     *            options.yinv invert the y-axis
     *
     * @param {String}
     *            options.colors.fg the foreground color as a CSS color
     *
     * @param {String}
     *            options.colors.bg the background color as a CSS color
     *
     * @param {Boolean}
     *            options.xi invert the foreground/background colors
     *
     * @param {Boolean}
     *            options.forcelab
     *
     * @param {Boolean}
     *            options.all show all of the data on the plot instead of just
     *            one buffer
     *
     * @param {Boolean}
     *            options.expand auto-scale the plot based on all the data (when
     *            combined with the all option)
     *
     * @param {Number}
     *            options.origin 1 = x1:xmin, x2:xmax, y1:ymax, y2:ymin
     *            (default), 2 = x1:xmax, x2:xmin, y1:ymax, y2:ymin (x
     *            inverted), 3 = x1:xmax, x2:xmin, y1:ymin, y2:ymax (x & y
     *            inverted), 4 = x1:xmin, x2:xmax, y1:ymin, y2:ymax (y inverted)
     *
     * @param {Number}
     *            options.bufmax the buffer size to use
     *
     * @param {Boolean}
     *            options.nokeypress disable key press actions
     *
     * @param options.anno_type
     *            (Not implemented)
     *
     * @param options.pmt
     *            (Not implemented)
     *
     * @param options.xfmt
     *            (Not implemented)
     *
     * @param options.yfmt
     *            (Not implemented)
     *
     * @param options.nsec
     *            the number of sections to split the plot into (Not
     *            implemented)
     *
     * @returns {sigplot.Plot}
     */
    sigplot.Plot = function(element, options) {
        if (!sigplot.browserIsCompatible()) {
            throw "Browser is not compatible";
        }

        // Register with the Mx structure - Step #4
        this._Mx = mx.open(element);
        var Mx = this._Mx;

        this._Gx = new GX();
        this._Gx.parent = element;

        // Variable which stores state of mouse position relative to the canvas
        this.mouseOnCanvas = false;

        if (!options) {
            options = {};
        }

        plot_init(this, options);

        this._refresh(); // Draw immediately

        this.onmousemove = (function(plot) {
            return function(e) {
                var Mx = plot._Mx;
                var Gx = plot._Gx;

                var rect = e.target.getBoundingClientRect();
                var xpos = (e.offsetX === undefined) ? (e.pageX - rect.left - window.scrollX) : e.offsetX;
                var ypos = (e.offsetX === undefined) ? (e.pageY - rect.top - window.scrollY) : e.offsetY;

                // var xpos = (e.offsetX === undefined) ? e.layerX : e.offsetX;
                // var ypos = (e.offsetY === undefined) ? e.layerY : e.offsetY;
                var re = pixel_to_real(plot, xpos, ypos);
                Gx.retx = re.x;
                Gx.rety = re.y;

                if (Mx.widget) {
                    return;
                }
                display_specs(plot);

                var evt = document.createEvent('Event');
                evt.initEvent('mmove', true, true);
                evt.xpos = xpos;
                evt.ypos = ypos;
                evt.x = Gx.retx;
                evt.y = Gx.rety;
                var canceled = !mx.dispatchEvent(Mx, evt);
                if (canceled) {
                    return;
                }

                // The crosshair logic in websigplot is different
                // because we need to clear the previous position
                // of the line (via XOR) and then draw the new line
                //
                // The Mx.xpos and Mx.ypos may have already been
                // updated to their new location so we need to store
                // the crosshair position in the Gx structure
                if (Gx.cross) {
                    if (Mx.warpbox) {
                        // during zoom operations undraw the crosshairs
                        if (Gx.cross_xpos !== undefined) {
                            mx.rubberline(Mx, Gx.cross_xpos, Mx.t,
                                Gx.cross_xpos, Mx.b);
                        }
                        if (Gx.cross_ypos !== undefined) {
                            mx.rubberline(Mx, Mx.l, Gx.cross_ypos, Mx.r,
                                Gx.cross_ypos);
                        }
                        Gx.cross_xpos = undefined;
                        Gx.cross_ypos = undefined;
                    } else {
                        draw_crosshairs(plot);
                    }
                }

                if (Gx.cntrls === 2) {
                    var evt = document.createEvent('Event');
                    evt.initEvent('mtag', true, true);
                    evt.x = Gx.retx;
                    evt.y = Gx.rety;
                    mx.dispatchEvent(Mx, evt);
                }
            };
        }(this));

        this.ontouchmove = (function(plot) {
            return function(event) {
                event.preventDefault();
                plot.onmousemove(event);
            };
        }(this));

        this.throttledOnMouseMove = m.throttle(this._Gx.scroll_time_interval,
            this.onmousemove);

        mx.addEventListener(Mx, "mousemove", this.throttledOnMouseMove, false);
        // canvas.addEventListener('touchmove', this.ontouchmove, false);

        this.onmouseout = (function(plot) {
            return function(event) {
                var Gx = plot._Gx;
                var Mx = plot._Mx;
                if (plot.mouseOnCanvas) {
                    plot.mouseOnCanvas = false;

                    if (Gx.autohide_readout) {
                        display_specs(plot);
                    }
                    if (Gx.autohide_panbars) {
                        draw_panbars(plot);
                    }
                    if (Mx.prompt) {
                        Mx.prompt.input.enableBlur();
                    }
                }
            };
        }(this));
        mx.addEventListener(Mx, "mouseout", this.onmouseout, false);

        this.onmouseover = (function(plot) {
            return function(event) {
                var Gx = plot._Gx;
                var Mx = plot._Mx;
                plot.mouseOnCanvas = true;
                if (Gx.autohide_panbars) {
                    draw_panbars(plot);
                }
                if (Mx.prompt) {
                    Mx.prompt.input.disableBlur();
                }
            };
        }(this));
        mx.addEventListener(Mx, "mouseover", this.onmouseover, false);

        this.onmousedown = (function(plot) {
            return function(event) {
                event.preventDefault(); // mouse down on the canvas should never do a browser default action

                var Mx = plot._Mx;
                var Gx = plot._Gx;

                if (Mx.widget && (Mx.widget.type === "ONESHOT")) {
                    Mx.widget = null;
                    plot.refresh();
                }

                // Update Mx event fields
                mx.ifevent(Mx, event);

                var evt = document.createEvent('Event');
                evt.initEvent('mdown', true, true);
                evt.xpos = Mx.xpos;
                evt.ypos = Mx.ypos;
                evt.x = Gx.retx;
                evt.y = Gx.rety;
                evt.which = event.which;
                var canceled = !mx.dispatchEvent(Mx, evt);
                if (canceled) {
                    return false;
                }

                // Check if event occured in the pan region
                var inPan = inPanRegion(plot);

                // Event processing
                if (inPan.inPanRegion) { // Mouse position lies in a pan
                    // region
                    event.preventDefault();
                    if (inPan.command !== ' ') {
                        var scrollbar = null;
                        var position = null;
                        if (inPan.command === "XPAN") {
                            scrollbar = Mx.scrollbar_x;
                        } else if (inPan.command === "YPAN") {
                            scrollbar = Mx.scrollbar_y;
                        }

                        if (event.which === 2) {
                            position = {
                                x: Mx.xpos,
                                y: Mx.ypos
                            };
                            if ((scrollbar !== undefined) && (onScrollbar(position, scrollbar))) {
                                // Only show menu if on the scrollbar itself
                                sigplot_scrollScaleMenu(plot, inPan.command);
                            }
                        } else {
                            if (inPan.command !== ' ') {
                                position = {
                                    x: Mx.xpos,
                                    y: Mx.ypos
                                };
                                if (!onScrollbar(position, scrollbar) && event.which === 1) { // Left-clicking
                                    // not on a
                                    // scrollbar -
                                    // handle
                                    // typical pan
                                    pan(plot, inPan.command, 0, event); // Execute
                                    // the
                                    // first
                                    // pan
                                    var repeatPan = function() {
                                        if (!onScrollbar({
                                            "x": Mx.xpos,
                                            "y": Mx.ypos
                                        }, scrollbar)) {
                                            pan(plot, inPan.command, 0, event);
                                            // execute
                                            // a
                                            // pan
                                            // on
                                            // every
                                            // interval
                                        } else {
                                            // stop
                                            // panning
                                            // once you
                                            // hit the
                                            // scrollbar
                                            if (Gx.stillPanning) {
                                                window.clearInterval(Gx.stillPanning);
                                                Gx.repeatPanning = undefined;
                                            }
                                        }
                                    };

                                    // Make scrolling smooth, the longer initial prevents
                                    // a single click from counting twice
                                    Gx.stillPanning = window.setTimeout(
                                        function() {
                                            Gx.repeatPanning = window.setInterval(repeatPan, 50);
                                        }, 250);
                                }
                            }
                        }
                    }
                } else { // Mouse not in a pan region, handle other cases
                    if (event.which === 1) {
                        var lButtonPressed = coordsInRectangle(Mx.xpos,
                            Mx.ypos, Gx.legendBtnLocation.x,
                            Gx.legendBtnLocation.y,
                            Gx.legendBtnLocation.width,
                            Gx.legendBtnLocation.height);

                        if (lButtonPressed) {
                            plot.change_settings({
                                legend: !Gx.legend
                            }); // toggle the legend
                        } else {
                            // In normal sigplot a mark is not set when drawing a
                            // box
                            // but it seems useful to have the specs dx and dy
                            // show you
                            // how big your zoom box is...so this implementation
                            // sets the
                            // mark on mousedown....
                            //
                            // TODO - reset the marks to their original values
                            // after the zoom is complete
                            Gx.xmrk = Gx.retx;
                            Gx.ymrk = Gx.rety;
                            display_specs(plot);

                            // Styles for rubberbox
                            var zoom_style = {
                                opacity: 0,
                                return_value: "zoom"
                            };

                            var select_style = {
                                opacity: 0.4,
                                fill_color: Mx.hi,
                                return_value: "select"
                            };

                            if (Gx.default_rubberbox_action === "zoom") {
                                mx.rubberbox(Mx, rubberbox_cb(plot),
                                    Gx.default_rubberbox_mode, zoom_style,
                                    select_style);
                            } else if (Gx.default_rubberbox_action === "select") {
                                mx.rubberbox(Mx, rubberbox_cb(plot),
                                    Gx.default_rubberbox_mode,
                                    select_style, zoom_style);
                            } // otherwise rubber-box is considered disabled
                        }
                    } else if (event.which === 2) {
                        if (!Gx.nomenu) {
                            sigplot_mainmenu(plot);
                        }
                    } // else if (event.which === 3) {
                    // Nothing yet
                    //}
                }
                return false;
            };
        }(this));

        this.ontouchstart = (function(plot) {
            return function(event) {
                event.preventDefault();
                plot.onmousedown({
                    which: 1
                });
            };
        }(this));

        mx.addEventListener(Mx, "mousedown", this.onmousedown, false);
        // canvas.addEventListener("touchstart", this.ontouchstart, false);

        this.docMouseUp = (function(plot) {
            return function(event) {
                var Gx = plot._Gx;

                if (event.which === 1) {
                    // in general, you shouldn't put anything in here
                    // ...instead it should go into rubberbox_cb
                    Gx.panning = undefined;
                    plot._Mx.scrollbar_x.action = 0; // TODO Is this
                    // necessary?
                    plot._Mx.scrollbar_y.action = 0;
                } //else if (event.which === 2) {
                // nothing
                //} else if (event.which === 3) {
                // nothing
                //}
                if (Gx.stillPanning) {
                    window.clearTimeout(Gx.stillPanning);
                    Gx.stillPanning = undefined;
                }
                if (Gx.repeatPanning) { // Clear the panning interval on any
                    // mouse up in the document
                    window.clearInterval(Gx.repeatPanning);
                    Gx.repeatPanning = undefined;
                }
                return false;
            };
        }(this));
        document.addEventListener("mouseup", this.docMouseUp, false);

        this.mouseup = (function(plot) {
            return function(event) {
                event.preventDefault(); // mouse down on the canvas should never do a browser default action

                var Gx = plot._Gx;
                var Mx = plot._Mx;

                // Update Mx event fields
                mx.ifevent(plot._Mx, event);

                var evt = document.createEvent('Event');
                evt.initEvent('mup', true, true);
                evt.xpos = Mx.xpos;
                evt.ypos = Mx.ypos;
                evt.x = Gx.retx;
                evt.y = Gx.rety;
                evt.which = event.which;
                var canceled = !mx.dispatchEvent(Mx, evt);
                if (!canceled) {
                    if (event.which === 3) { // unzoom only happens on
                        // right-clicks on plot
                        // unzoom/expand
                        event.preventDefault();

                        plot.unzoom(1);
                        plot.refresh();
                    } else if (event.which === 2) {
                        if (Gx.nomenu) {
                            // Send an event so that a custom menu can be displayed
                            // if desired
                            var evt = document.createEvent('Event');
                            evt.initEvent('showmenu', true, true);
                            evt.x = event.x || event.clientX;
                            evt.y = event.y || event.clientY;
                            var cancelled = !mx.dispatchEvent(Mx, evt);

                            if (!cancelled) {
                                if (event.stopPropagation) {
                                    event.stopPropagation();
                                }
                                event.cancelBubble = true;
                                mx.removeEventListener(Mx, "mousedown", plot.onmousedown, false);

                                var emit_hidemenu = function() {
                                    try {
                                        var hideMenuEvt = document.createEvent('Event');
                                        hideMenuEvt.initEvent('hidemenu', true, true);
                                        if (mx.dispatchEvent(Mx, hideMenuEvt)) {
                                            mx.addEventListener(Mx, "mousedown", plot.onmousedown, false);
                                        }
                                    } finally {
                                        document.removeEventListener("mouseup", emit_hidemenu, false);
                                    }
                                };
                                document.addEventListener("mouseup", emit_hidemenu, false);
                            }
                        }
                    }
                }

            };
        }(this));

        this.ontouchend = (function(plot) {
            return function(event) {
                event.preventDefault();
                //
            };
        }(this));

        mx.addEventListener(Mx, "mouseup", this.mouseup, false);
        // canvas.addEventListener("touchstart", this.ontouchend, false);

        this.mouseclick = (function(plot) {
            return function(event) {
                event.preventDefault(); // alway prevent any default browser actions on the plot

                var Gx = plot._Gx;
                var Mx = plot._Mx;

                // Update Mx event fields
                mx.ifevent(plot._Mx, event);

                var evt = document.createEvent('Event');
                evt.initEvent('mclick', true, true);
                evt.xpos = Mx.xpos;
                evt.ypos = Mx.ypos;
                evt.x = Gx.retx;
                evt.y = Gx.rety;
                evt.which = event.which; // not always available on all browser
                if (mx.dispatchEvent(Mx, evt)) {
                    // currently there isn't a default for mouseclick to cancel
                }
                return false;
            };
        }(this));
        mx.addEventListener(Mx, "click", this.mouseclick, false);

        this.mousedblclick = (function(plot) {
            return function(event) {
                event.preventDefault(); // alway prevent any default browser actions on the plot

                var Gx = plot._Gx;
                var Mx = plot._Mx;

                // Update Mx event fields
                mx.ifevent(plot._Mx, event);

                var evt = document.createEvent('Event');
                evt.initEvent('mdblclick', true, true);
                evt.xpos = Mx.xpos;
                evt.ypos = Mx.ypos;
                evt.x = Gx.retx;
                evt.y = Gx.rety;
                evt.which = event.which; // not always available on all browser
                if (mx.dispatchEvent(Mx, evt)) {
                    // currently there isn't a default for mouseclick to cancel
                }
                return false;
            };
        }(this));
        mx.addEventListener(Mx, "dblclick", this.mousedblclick, false);

        // PANBAR DRAGGING mouse event handlers:
        this.dragMouseDownHandler = (function(plot) {
            return function(event) {
                var Mx = plot._Mx;
                var Gx = plot._Gx;

                // Check if event occured in the pan region
                var inPan = inPanRegion(plot);

                // Event processing
                if (inPan.inPanRegion) { // Mouse position lies in a pan
                    // region
                    event.preventDefault();
                    if (inPan.command !== ' ') {
                        var scrollbar;
                        if (inPan.command === "XPAN") {
                            scrollbar = Mx.scrollbar_x;
                        } else if (inPan.command === "YPAN") {
                            scrollbar = Mx.scrollbar_y;
                        }

                        var position = {
                            x: Mx.xpos,
                            y: Mx.ypos
                        };
                        if (scrollbar !== undefined && onScrollbar(position, scrollbar) && event.which === 1) { // On scrollbar, set up
                            // a DRAG
                            Gx.panning = {
                                axis: inPan.command,
                                xpos: event.screenX, // Use screen-relative
                                // values here instead
                                // of div/page-relative
                                // values
                                ypos: event.screenY,
                                xmin: Mx.stk[Mx.level].xmin,
                                xmax: Mx.stk[Mx.level].xmax,
                                ymin: Mx.stk[Mx.level].ymin,
                                ymax: Mx.stk[Mx.level].ymax
                            };
                        }
                    }
                }
            };
        }(this));
        window.addEventListener("mousedown", this.dragMouseDownHandler, false);

        this.dragMouseMoveHandler = (function(plot) {
            return function(e) {
                var Gx = plot._Gx;

                if (Gx.panning !== undefined) { // execute a scrollbar DRAG
                    try {
                        drag_scrollbar(plot, Gx.panning.axis, e);
                    } catch (err) {
                        console.log("Error: " + err); // TODO Eventually come
                        // up with better error
                        // handling here
                    }
                }
            };
        }(this));

        this.throttledDragOnMouseMove = m.throttle(this._Gx.scroll_time_interval,
            this.dragMouseMoveHandler);

        window.addEventListener("mousemove", this.throttledDragOnMouseMove,
            false);

        this.dragMouseUpHandler = (function(plot) {
            return function(event) {
                var Gx = plot._Gx;

                if (event.which === 1) {
                    Gx.panning = undefined; // Panbar dragging completed - clear
                    // the state variable
                }
            };
        }(this));
        window.addEventListener("mouseup", this.dragMouseUpHandler, false);

        // TODO this may need to be throttled or debounced
        this.onresize = (function(plot) {
            return function(event) {
                if (mx.checkresize(plot._Mx)) {
                    plot.refresh();
                }
            };
        }(this));

        // Mouse Wheel logic
        this.wheelHandler = (function(plot) {

            var Mx = plot._Mx;
            var Gx = plot._Gx;

            var throttledPan = m.throttle(100, function(inPan) {
                // Mouse wheel
                // event over a
                // panning
                // region

                var scrollbar;
                if (inPan.command === "XPAN") {
                    scrollbar = Mx.scrollbar_x;
                } else if (inPan.command === "YPAN") {
                    scrollbar = Mx.scrollbar_y;
                }

                // For now, vertical mouse scrolling is the only action that
                // will trigger a pan
                // Later, we can add horizontal mouse scrolling if we choose
                if (Gx.wheelscroll_mode_natural) { // Original Sig-Plot
                    // orientation
                    scrollbar.action = (event.deltaY < 0 ? mx.SB_WHEELDOWN : mx.SB_WHEELUP);
                } else { // Inverted/"un-natural" orientation
                    scrollbar.action = (event.deltaY < 0 ? mx.SB_WHEELUP : mx.SB_WHEELDOWN);
                }

                scrollbar.step = 0.1 * scrollbar.srange;
                scrollbar.page = 9 * scrollbar.step;

                // Execute wheel action on the scrollbar
                mx.scroll(Mx, scrollbar, mx.XW_COMMAND, undefined,
                    scrollbar);

                // Update the viewbox
                updateViewbox(plot, scrollbar.smin, scrollbar.smin + scrollbar.srange, inPan.command.slice(0, 1));
            });

            var throttledZoom = m.throttle(100, function() {
                var zoomperc = Gx.wheelZoomPercent || 0.2;
                if (Gx.wheelscroll_mode_natural) {
                    if (event.deltaY > 0) {
                        zoomperc = -1 * zoomperc;
                    }
                } else { // Inverted/"un-natural" orientation
                    if (event.deltaY < 0) {
                        zoomperc = -1 * zoomperc;
                    }
                }

                if (Gx.wheelZoom === "x") {
                    plot.percent_zoom(zoomperc, 1, true);
                } else if (Gx.wheelZoom === "y") {
                    plot.percent_zoom(1, zoomperc, true);
                } else {
                    plot.percent_zoom(zoomperc, zoomperc, true);
                }
            });

            return function(event) {
                // Update Mx event fields
                mx.ifevent(Mx, event);

                // Check if event occured in the pan region
                var inPan = inPanRegion(plot);

                // Event processing
                if (plot.mouseOnCanvas) {
                    event.preventDefault();

                    if (inPan.inPanRegion) {
                        throttledPan(inPan);
                    } else if (Gx.wheelZoom) {
                        throttledZoom();
                    }
                }
            };
        }(this));

        window.addWheelListener(window, this.wheelHandler, false);

        window.addEventListener("resize", this.onresize, false);

        // If multiple plots are in the same window, then it
        // may be desired to disable keypress behavior and implement
        // it at a higher-level...by default keypress behavior
        // is enabled and only works if the mouse if over the plot
        if (!options.nokeypress) {
            this.onkeypress = (function(plot) {
                return function(event) {
                    var Mx = plot._Mx;
                    var Gx = plot._Gx;
                    if (plot.mouseOnCanvas) {

                        if (Mx.widget && (Mx.widget.type === "MENU")) {
                            return; // The menu absorbs the keypress
                        }

                        if (Mx.widget && (Mx.widget.type === "ONESHOT")) {
                            Mx.widget = null;
                            plot.refresh();
                            return;
                        }

                        // Only respond to keypresses if the mouse is
                        // in the plot area....
                        var keyCode = getKeyCode(event);
                        if (keyCode === 97) { // 'a'
                            Gx.iabsc = (Gx.iabsc + 1) % 4;
                            // It's kinda up in the air if changing the 'specs'
                            // area should also change the plotting mode itself...
                            // on one hand, if you have multiple layers with different
                            // xdeta's then switching the specs area to index mode will
                            // give you only the index of the baselayer...on the other hand
                            // the use may only want to change the readout and not the x-axis
                            // or the plot...for now this is commented out to behave in the same
                            // manner as SIGPLOT.
                            //plot.change_settings({
                            //	index : Gx.iabsc === 1
                            //});
                            display_specs(plot);
                        } else if (keyCode === 108) { // 'l'
                            plot.change_settings({
                                legend: !Gx.legend
                            }); // toggle the legend
                        } else if (keyCode === 103) { // 'g'
                            plot.change_settings({
                                grid: !Gx.grid
                            }); // toggle the legend
                        } else if ((keyCode === 98) || (keyCode === 2)) { // 'b' and CTRL-'b'
                            if (Mx.warpbox) {
                                if (Mx.warpbox.mode === "box") {
                                    Mx.warpbox.mode = "horizontal";
                                } else if (Mx.warpbox.mode === "horizontal") {
                                    Mx.warpbox.mode = "vertical";
                                } else {
                                    Mx.warpbox.mode = "box";
                                }
                                mx.redraw_warpbox(Mx);
                            }
                        } else if (keyCode === 99) { // 'c'
                            plot.change_settings({
                                xcnt: -1 * Gx.cntrls
                            });
                        } else if (keyCode === 114) { // 'r'
                            plot.change_settings({
                                show_readout: !Gx.show_readout
                            });
                        } else if (keyCode === 115) { // 's'
                            plot.change_settings({
                                specs: !Gx.specs
                            });
                        } else if (keyCode === 120) { // 'x'
                            sigplot_show_x(plot);
                        } else if (keyCode === 121) { // 'y'
                            sigplot_show_y(plot);
                        } else if (keyCode === 122) { // 'z'
                            sigplot_show_z(plot);
                        } else if (keyCode === 116) { // 't'
                            sigplot_show_timecode(plot);
                        } else if (keyCode === 109) { // 'm'
                            sigplot_mainmenu(plot);
                        } else if (keyCode === 63) { // '?'
                            mx.message(Mx, MAIN_HELP);
                        } else if (keyCode === 102) { // 'f'
                            mx.fullscreen(Mx);
                            plot.refresh();
                        } else if ((keyCode === 9) && (event.ctrlKey)) { // ctrl-i
                            plot.change_settings({
                                invert: null
                            });
                        }
                    }
                };
            }(this));

            setKeypressHandler(this.onkeypress);
        }

        return this;
    };

    // Public methods

    sigplot.Plot.prototype = {

        /**
         * Add a plugin to the plot
         *
         * @param plugin
         *            the plugin object
         *
         * @param zorder
         *            the zorder for the plugin to render, all plugins render as
         *            overlays on top of the plot
         */
        add_plugin: function(plugin, zorder) {
            if (zorder === undefined) {
                zorder = Number.MAX_VALUE;
            }
            if (zorder <= 0) {
                throw "Invalid plugin zorder";
            }


            plugin.init(this);

            var canvas = document.createElement('canvas');
            canvas.width = this._Mx.canvas.width;
            canvas.height = this._Mx.canvas.height;

            this._Gx.plugins.push({
                impl: plugin,
                zorder: zorder,
                canvas: canvas
            });

            this._Gx.plugins.sort(function(a, b) {
                return (a.zorder - b.zorder);
            });


            this.refresh();
        },

        /**
         * Removes a plugin from the plot
         *
         * @param plugin
         *            the plugin object
         */
        remove_plugin: function(plugin) {
            for (var i = 0; i < this._Gx.plugins.length; i++) {
                if (this._Gx.plugins[i].impl === plugin) {
                    if (plugin.dispose) {
                        plugin.dispose();
                    }
                    if (this._Gx.plugins[i].canvas.parentNode) {
                        this._Gx.plugins[i].canvas.parentNode.removeElement(this._Gx.plugins[i].canvas);
                    }
                }
            }
            this._Gx.plugins.sort(function(a, b) {
                return (a.zorder - b.zorder);
            });

            this.refresh();
        },

        /**
         * Adds a listener to plot events.
         *
         * @param what
         *            the event to listen to mtag = a mouse 'tag' event has
         *            occurred, mmove = a mouse move event has occurred, mdown =
         *            a mouse down event has occurred, mup = a mouse up event
         *            has occurred, showmenu = showmenu even has occurred,
         *            sigplotexit = an exit plot event has occurred, reread = a
         *            reread event has occurred, file_deoverlayed = a file has
         *            been deoverlayed, file_overlayed = a file has been
         *            overlayed,
         *
         * @param callback
         */
        addListener: function(what, callback) {
            var Mx = this._Mx;
            mx.addEventListener(Mx, what, callback, false);
        },

        /**
         * Removes a listener to plot events.
         *
         * @param what
         *            the event that was listned to
         * @param callback
         */
        removeListener: function(what, callback) {
            var Mx = this._Mx;
            mx.removeEventListener(Mx, what, callback, false);
        },

        /**
         * Change one or more plot settings. For boolean types, passing null
         * will toggle the setting.
         *
         * @param settings
         *            the settings to change.
         *
         * @param {Boolean}
         *            settings.grid change grid visibility
         *
         * @param {Boolean}
         *            settings.index change index setting
         *
         * @param {Boolean}
         *            settings.all change the plot to show all data
         *
         * @param {Boolean}
         *            settings.show_x_axis
         *
         * @param {Boolean}
         *            settings.show_y_axis
         *
         * @param {Boolean}
         *            settings.show_readout
         *
         * @param {Boolean}
         *            settings.specs
         *
         * @param {String}
         *            settings.xcnt "leftmouse", "continuous", "disable",
         *            "enable"
         *
         * @param {Boolean}
         *            settings.legend
         *
         * @param {Boolean}
         *            settings.pan
         *
         * @param {Boolean}
         *            settings.cross
         *
         * @param {String}
         *            settings.rubberbox_action
         *
         * @param {String}
         *            settings.rubberbox_mode
         *
         * @param {String}
         *            settings.wheelscroll_mode_natural
         *
         * @param {String}
         *            settings.cmode
         *
         * @param {String}
         *            settings.phunits
         */
        change_settings: function(settings) {
            var Gx = this._Gx;
            var Mx = this._Mx;

            for (var i = 0; i < Gx.lyr.length; i++) {
                Gx.lyr[i].change_settings(settings);
            }

            if (settings.grid !== undefined) {
                if (settings.grid === null) {
                    Gx.grid = !Gx.grid;
                } else {
                    Gx.grid = settings.grid;
                }
            }

            if (settings.gridBackground !== undefined) {
                Gx.gridBackground = settings.gridBackground;
            }

            if (settings.gridStyle !== undefined) {
                Gx.gridStyle = settings.gridStyle;
            }

            if (settings.wheelZoom !== undefined) {
                Gx.wheelZoom = settings.wheelZoom;
            }

            if (settings.wheelZoomPercent !== undefined) {
                Gx.wheelZoomPercent = settings.wheelZoomPercent;
            }

            if (settings.autol !== undefined) {
                Gx.autol = settings.autol;
            }

            if ((settings.index !== undefined) && (settings.index !== Gx.index)) {
                if (settings.index === null) {
                    Gx.index = !Gx.index;
                } else {
                    Gx.index = settings.index;
                }

                // the original sigplot.for fails
                // to do this so that the specs area
                // has the correct setting.
                if ((Gx.index) && (Gx.iabsc !== 1)) {
                    Gx.iabsc = 1;
                } else if ((!Gx.index) && (Gx.iabsc === 1)) {
                    Gx.iabsc = 0;
                }

                var xmin;
                var xmax;
                scale_base(this, {
                    get_data: false
                }, xmin, xmax);

                // like sigplot, undo all zoom levels
                this.unzoom();
            }

            if (settings.all !== undefined) {
                if (settings.all === null) {
                    Gx.all = !Gx.all;
                } else {
                    Gx.all = settings.all;
                }
            }

            if (settings.show_x_axis !== undefined) {
                if (settings.show_x_axis === null) {
                    Gx.show_x_axis = !Gx.show_x_axis;
                } else {
                    Gx.show_x_axis = settings.show_x_axis;
                }
                Gx.specs = (Gx.show_x_axis || Gx.show_y_axis || Gx.show_readout);
            }

            if (settings.show_y_axis !== undefined) {
                if (settings.show_y_axis === null) {
                    Gx.show_y_axis = !Gx.show_y_axis;
                } else {
                    Gx.show_y_axis = settings.show_y_axis;
                }
                Gx.specs = (Gx.show_x_axis || Gx.show_y_axis || Gx.show_readout);
            }

            if (settings.show_readout !== undefined) {
                if (settings.show_readout === null) {
                    Gx.show_readout = !Gx.show_readout;
                } else {
                    Gx.show_readout = settings.show_readout;
                }
                Gx.specs = (Gx.show_x_axis || Gx.show_y_axis || Gx.show_readout);
            }

            if (settings.specs !== undefined) {
                if (settings.specs === null) {
                    Gx.specs = !Gx.specs;
                } else {
                    Gx.specs = settings.specs;
                }
                if (Gx.specs) {
                    Gx.show_x_axis = true;
                    Gx.show_y_axis = true;
                    Gx.show_readout = true;
                } else {
                    Gx.show_x_axis = false;
                    Gx.show_y_axis = false;
                    Gx.show_readout = false;
                }
            }

            if (settings.xcnt !== undefined) {
                if (settings.xcnt === "leftmouse") {
                    Gx.cntrls = 1;
                } else if (settings.xcnt === "continuous") {
                    Gx.cntrls = 2;
                } else if ((settings.xcnt === "disable") && (Gx.cntrls > 0)) {
                    Gx.cntrls = -1 * Gx.cntrls;
                } else if ((settings.xcnt === "enable") && (Gx.cntrls < 0)) {
                    Gx.cntrls = -1 * Gx.cntrls;
                } else {
                    Gx.cntrls = settings.xcnt;
                }
            }

            if (settings.legend !== undefined) {
                if (settings.legend === null) {
                    Gx.legend = !Gx.legend;
                } else {
                    Gx.legend = settings.legend;
                }
                draw_accessories(this, -1);

                var i = Gx.lbtn - 2;
                if (Gx.show_readout) {
                    Gx.legendBtnLocation = {
                        x: this._Mx.width - Gx.lbtn,
                        y: 2,
                        width: i,
                        height: i
                    };
                    mx.shadowbox(this._Mx, this._Mx.width - Gx.lbtn, 2, i, i,
                        1, -1, 'L');
                } else {
                    Gx.legendBtnLocation = {
                        x: this._Mx.width - Gx.lbtn,
                        y: 2,
                        width: i,
                        height: i
                    };
                    mx.shadowbox(this._Mx, this._Mx.width - Gx.lbtn, 2, i, i,
                        1, 1, 'L');
                }
                draw_accessories(this, 1);
            }

            if (settings.pan !== undefined) {
                if (settings.pan === null) {
                    Gx.pan = !Gx.pan;
                } else {
                    Gx.pan = settings.pan;
                }
            }

            if (settings.cross !== undefined) {
                if (settings.cross === null) { // catch null or undefined here
                    Gx.cross = !Gx.cross;
                } else {
                    Gx.cross = settings.cross;
                }
                if (!Gx.cross) {
                    if (Gx.cross_xpos !== undefined) {
                        mx.rubberline(Mx, Gx.cross_xpos, Mx.t, Gx.cross_xpos,
                            Mx.b);
                    }
                    if (Gx.cross_ypos !== undefined) {
                        mx.rubberline(Mx, Mx.l, Gx.cross_ypos, Mx.r,
                            Gx.cross_ypos);
                    }
                    Gx.cross_xpos = undefined;
                    Gx.cross_ypos = undefined;
                } else {
                    Gx.cross_xpos = undefined;
                    Gx.cross_ypos = undefined;
                    draw_crosshairs(this);
                }
            }

            if (settings.cmode !== undefined) {
                changemode(this, settings.cmode);
            }

            if (settings.phunits !== undefined) {
                changephunits(this, settings.phunits);
            }

            if (settings.rubberbox_action !== undefined) {
                Gx.default_rubberbox_action = settings.rubberbox_action;
            }

            if (settings.rubberbox_mode !== undefined) {
                Gx.default_rubberbox_mode = settings.rubberbox_mode;
            }

            if (settings.wheelscroll_mode_natural !== undefined) {
                Gx.wheelscroll_mode_natural = settings.wheelscroll_mode_natural;
            }

            if (settings.colors !== undefined) {
                if (!settings.colors.fg) {
                    settings.colors.fg = Mx.fg;
                }
                if (!settings.colors.bg) {
                    settings.colors.bg = Mx.bg;
                }
                mx.setbgfg(Mx, settings.colors.bg, settings.colors.fg, Mx.xi);
            }

            if (settings.cmap !== undefined) {
                if (settings.cmap === null) {
                    // default
                    if (Gx.cmode === 2) {
                        Gx.cmap = 2; // wheel
                    } else {
                        Gx.cmap = 1; // ramp
                    }
                } else {
                    Gx.cmap = settings.cmap; // TODO support string lookup
                }

                mx.colormap(Mx, m.Mc.colormap[Gx.cmap], Gx.ncolors);
            }

            if (settings.yinv !== undefined) {
                if (settings.yinv) {
                    Mx.origin = 4;
                } else {
                    Mx.origin = 1;
                }
            }

            if (settings.rasterSmoothing !== undefined) {
                if (settings.rasterSmoothing === null) {
                    Gx.rasterSmoothing = !Gx.rasterSmoothing;
                } else {
                    Gx.rasterSmoothing = settings.rasterSmoothing;
                }
            }

            if (settings.fillStyle !== undefined) {
                Gx.fillStyle = settings.fillStyle;
            }

            if (settings.invert !== undefined) {
                if (settings.invert === null) {
                    mx.invertbgfg(Mx);
                } else if (settings.invert === true) {
                    mx.setbgfg(this, "white", "black");
                } else {
                    mx.setbgfg(this, "black", "white");
                }
            }

            if (settings.nomenu !== undefined) {
                if (settings.nomenu === null) {
                    Gx.nomenu = !Gx.nomenu;
                } else {
                    Gx.nomenu = settings.nomenu;
                }
            }

            if (settings.ymin !== undefined) {
                updateViewbox(this, settings.ymin, Mx.stk[0].ymax, "Y");
            }

            if (settings.ymax !== undefined) {
                updateViewbox(this, Mx.stk[0].ymin, settings.ymax, "Y");
            }

            if (settings.xmin !== undefined) {
                updateViewbox(this, settings.xmin, Mx.stk[0].xmax, "X");
            }

            if (settings.xmax !== undefined) {
                updateViewbox(this, Mx.stk[0].xmin, settings.xmax, "X");
            }

            this.refresh();
            if (settings.pan !== undefined) { // refactor - new code to handle
                // disappearing specs
                display_specs(this);
            }
        },

        /**
         * Reread all files and refresh the plot.
         */
        reread: function() {
            var Gx = this._Gx;
            var oldLayerData = [];
            for (var k = 0; k < Gx.lyr.length; k++) { // make a copy of layer
                // data before
                // destroying Gx.lyr
                // with the deoverlay
                oldLayerData[k] = Gx.lyr[k];
            }

            var origHCB = Gx.HCB.slice();
            this.deoverlay();
            for (var i = 0; i < origHCB.length; i++) {
                this.overlay_bluefile(origHCB[i]);
            }

            // propagate old layer attributes to re-read layers
            for (var j = 0; j < Gx.lyr.length; j++) {
                // TODO Assumes indices of old Gx.lyr and new Gx.lyr will match
                // up correctly - should we instead use hcb and name to identify
                Gx.lyr[j].symbol = oldLayerData[j].symbol;
                Gx.lyr[j].radius = oldLayerData[j].radius;
                // TODO re-copy other things like line type???
            }
            this.refresh();

            // Notify listeners that a reread was performed
            var evt = document.createEvent('Event');
            evt.initEvent('reread', true, true);
            mx.dispatchEvent(this._Mx, evt);
        },

        /**
         * Placeholder for cleanup logic.
         */
        cleanup: function() {
            // TODO not sure what we really want to do here yet
        },

        /**
         * Reload data without adjusting other aspects about a plot
         *
         * @param {Number} n
         *            the layer to push data into 
         * @param {Number[]} data
         *            data to push
         * @param {Object} hdrmod 
         *            optional changes to the file header
         */
        reload: function(n, data, hdrmod) {
            var Mx = this._Mx;
            var Gx = this._Gx;
            if ((n < 0) || (n >= Gx.lyr.length)) { return; }

            if (Gx.lyr[n].reload === undefined) { return; }

            var xbnds = Gx.lyr[n].reload(data, hdrmod);

            if (Mx.level === 0) {
                // Unlike push(), always call scale_base
                // when reload is invoked
                scale_base(this, {
                    get_data: false
                }, xbnds.xmin, xbnds.xmax);
            }

            this.refresh();

        },

        rescale: function() {
            var Mx = this._Mx;

            if (Mx.level === 0) {
                scale_base(this, {
                    get_data: false
                }, undefined, undefined);
            }

            this.refresh();
        },

        /**
         * Push data into a layer that was created with overlay_pipe
         * 
         * @param {Number} n
         *            the layer to push data into 
         * @param {Number[]} data
         *            data to push
         * @param {Object} hdrmod 
         *            optional changes to the file header
         * @param {boolean} [sync=false]
         *            optional dispatch onpipewrite syncronously 
         */
        push: function(n, data, hdrmod, sync) {
            var Mx = this._Mx;
            var Gx = this._Gx;
            if ((n < 0) || (n >= Gx.lyr.length)) { return; }
            
            if (Gx.lyr[n].push === undefined) { return; }

            var rescale = Gx.lyr[n].push(data, hdrmod, sync);

            if ((Mx.level === 0) && rescale) {
                scale_base(this, {
                    get_data: false
                });
            }

            this.refresh();
        },

        /**
         * Create a plot layer backed by an array
         *
         * @param filname
         * @param data
         *            {Number[]} data to plot
         * @param overrides
         *            optional bluefile header overrides
         * @param layerType
         */
        overlay_array: function(data, overrides, layerOptions) {
	    m.log.debug("Overlay array");
            var hcb = m.initialize(data, overrides);
            return this.overlay_bluefile(hcb, layerOptions);
        },

        overlay_pipe: function(overrides, layerOptions) {
	    m.log.debug("Overlay pipe");
            if (!overrides) {
                overrides = {};
            }
            overrides.pipe = true;
            var hcb = m.initialize(null, overrides);
            //console.log("pipe filename: "+hcb.file_name);
            return this.overlay_bluefile(hcb, layerOptions);
        },

        overlay_websocket: function(wsurl, overrides, layerOptions) {
	    m.log.debug("Overlay websocket: " + wsurl);
            var ws = new WebSocket(wsurl, "plot-data");
            ws.binaryType = "arraybuffer";

            var plot = this;
            var hcb = m.initialize(null, overrides);
            hcb.ws = ws;

            var layer_n = this.overlay_bluefile(hcb, layerOptions);

            ws.onopen = function(evt) {};

            ws.onmessage = (function(theSocket) {
                return function(evt) {
                    if (evt.data instanceof ArrayBuffer) {
                        var data = hcb.createArray(evt.data);
                        plot.reload(layer_n, data);
                    } else if (typeof evt.data === "string") {
                        var Gx = plot._Gx;
                        var hdr = Gx.HCB[Gx.lyr[layer_n].hcb];

                        var newHdr = JSON.parse(evt.data);

                        for (var field in newHdr) {
                            hdr[field] = newHdr[field];
                        }
                        hcb.size = undefined; // trigger rescale
                    }
                };
            })(ws);

            return layer_n;
        },

        /**
         * Create a plot layer from an HREF that points to a BLUEFILE
         *
         * @param {String}
         *            href the url to the bluefile
         * @param [onload]
         *            callback to be called when the file has been loaded
         */
        overlay_href: function(href, onload, layerOptions) {
	    m.log.debug("Overlay href: " + href);
            try {
                this.show_spinner();

                var handleHeader = (function(plot, onload) {
                    return function(hcb) {
                        try {
                            if (!hcb) {
                                alert("Failed to load data");
                            } else {
                                var i = plot.overlay_bluefile(hcb, layerOptions);
                                if (onload) {
                                    onload(hcb, i);
                                }
                            }
                        } finally {
                            plot.hide_spinner();
                        }
                    };
                }(this, onload));

                var br = new BlueFileReader();
                br.read_http(href, handleHeader);
            } catch (error) {
                console.log(error);
                alert("Failed to load data");
                this.hide_spinner();
            }
        },

        show_spinner: function() {
            if (!this._Gx.spinner) {
                SPINNER_OPTS.color = this._Mx.xwfg;
                this._Gx.spinner = new Spinner(SPINNER_OPTS).spin(this._Gx.parent);
            }
        },

        hide_spinner: function() {
            if (this._Gx.spinner) {
                this._Gx.spinner.stop();
            }
            this._Gx.spinner = undefined;
        },

        add_layer: function(layer) {
            var Gx = this._Gx;
            var Mx = this._Mx;

            Gx.lyr.push(layer);

            // Notify listeners that a file was overlayed
            var evt = document.createEvent('Event');
            evt.initEvent('file_overlayed', true, true);
            evt.index = Gx.lyr.length - 1; // the new index of the layer
            evt.name = layer.name; // the name of the layer
            mx.dispatchEvent(Mx, evt);
        },

        get_layer: function(n) {
            var Gx = this._Gx;
            if ((n >= 0) && (n < Gx.lyr.length)) {
                return Gx.lyr[n];
            } else {
                return null;
            }
        },

        /**
         * Create a plot layer backed by a bluefile header
         *
         * @param hcb
         *            {BlueHeader} an opened BlueHeader file
         * @returns the index of the new layer
         */
        overlay_bluefile: function(hcb, layerOptions) {
	    m.log.debug("Overlay bluefile: " + hcb.file_name);
            var Mx = this._Mx;
            var Gx = this._Gx;
            var size = 0;

            layerOptions = layerOptions || {};

            var basefiles = (Gx.HCB.length === 0);

            Gx.HCB.push(hcb);

            if (Gx.HCB.length === 1) {
                basefile(this, true);
            }

            var newlayer = Gx.lyr.length;

            if (layerOptions.layerType === undefined) {
                if (hcb["class"] === 1) {
                    sigplot.Layer1D.overlay(this, hcb, layerOptions);
                } else if (hcb["class"] === 2) {
                    sigplot.Layer2D.overlay(this, hcb, layerOptions);
                }
            } else {
                layerOptions.layerType.overlay(this, hcb, layerOptions);
            }

            // TODO - do we want to alert like the XM plot did?
            //if (!Gx.all && size > Gx.bufmax && Gx.HCB.length == 1) {
            // alert("Plot truncated to buffer size. Use panning or /ALL
            // switch");
            //}
            // The original code has a bug here. Fixed by moving changemode
            // outside of
            // the !basefiles check.
            // You can recreate with SIGPLOT ,,, IR
            // And then loading a file.
            changemode(this, Gx.cmode);

            if (!basefiles) {
                for (var n = newlayer; n < Gx.lyr.length; n++) {
                    draw_layer(this, n);
                }
            } else {
                if (Gx.HCB.length === 0) { // TODO dead code that cannot be reached
                    basefile(this, false);
                } else {
                    Gx.basemode = Gx.cmode;
                    var xmin;
                    var xmax;
                    if ((Gx.autox && 1) === 0) {
                        xmin = Gx.xmin;
                    }
                    if ((Gx.autox && 2) === 0) {
                        xmax = Gx.xmin;
                    }
                    scale_base(this, {
                        get_data: true
                    }, xmin, xmax);
                    Mx.level = 0;
                    if ((Gx.autox && 1) !== 0) {
                        Gx.xmin = Mx.stk[0].xmin;
                    }
                    if ((Gx.autox && 2) !== 0) {
                        Gx.xmax = Mx.stk[0].xmax;
                    }
                    if ((Gx.autoy && 1) !== 0) {
                        Gx.ymin = Mx.stk[0].ymin;
                    }
                    if ((Gx.autoy && 2) !== 0) {
                        Gx.ymax = Mx.stk[0].ymax;
                    }
                    Mx.resize = true;
                    if (Gx.lyr[0].preferred_origin) {
                        Mx.origin = Gx.lyr[0].preferred_origin;
                    } else {
                        Mx.origin = 1;
                    }
                }
            }
            form_plotnote(this);
            this.refresh();

            return (Gx.HCB.length - 1);
        },

        /**
         * Load one or more files.
         *
         * @param {File[]}
         *            a list of files to plot
         */
        load_files: function(files, layerType) {
	    var onload = (function(plot) {
                return function(hdr) {
                    plot.overlay_bluefile(hdr, layerType);
                };
            })(this);

            for (var i = 0; i < files.length; i++) {
                var f = files[i];
                var br = new BlueFileReader();
                br.read(f, onload);
            }
        },

        /**
         * Remove layers.
         *
         * @param [index]
         *            the layer to remove, if not provided all layers are
         *            removed. Negative indices can be used to remove layers
         *            from the back of the layer stack.
         */
        deoverlay: function(index) {
            var Gx = this._Gx;
            var Mx = this._Mx;

            if (Gx.HCB.length > 0) {
                if (index === undefined) {
                    for (var n = Gx.HCB.length - 1; n >= 0; n--) {
                        this.remove_layer(n);
                    }
                } else if (index < 0) {
                    var n = Gx.HCB.length + index;
                    if (n < 0) {
                        return;
                    }
                    this.remove_layer(n);
                } else if (index < Gx.HCB.length) {
                    this.remove_layer(index);
                }
            }
            if (Gx.lyr.length === 0) {
                basefile(this, false);
                scale_base(this, {});
            }
        },

        /**
         * Remove a layer.
         *
         * @param index
         *            the layer to remove
         */
        remove_layer: function(index) {
            var Gx = this._Gx;

            var fileName = "";
            var HCB = null;

            if ((index >= 0) && (index < Gx.HCB.length)) {
                fileName = Gx.HCB[index].file_name;
                // TODO if (Gx.modsource > 0) {
                //	
                // }
                HCB = Gx.HCB[index];
                Gx.HCB[index] = null;
                for (var n = index; n < Gx.HCB.length - 1; n++) {
                    Gx.HCB[n] = Gx.HCB[n + 1];
                }
                Gx.HCB.length -= 1;
            }

            for (var n = Gx.lyr.length - 1; n >= 0; n--) {
                if (Gx.lyr[n].hcb === HCB) {
                    delete_layer(this, n);
                }
            }
            form_plotnote(this);
            this.refresh();

            // Notify listeners that a file has been deoverlayed
            var evt = document.createEvent('Event');
            evt.initEvent('file_deoverlayed', true, true);
            if (fileName !== "") {
                evt.fileName = fileName; // The fileName that was
	    }
            // de-overlayed
            mx.dispatchEvent(this._Mx, evt);
        },

        /**
         * Zoom onto a given pixel range.
         */
        pixel_zoom: function(x1, y1, x2, y2, continuous) {
            var r1 = pixel_to_real(this, x1, y1);
            var r2 = pixel_to_real(this, x2, y2);

            this.zoom(r1, r2, continuous);
        },

        percent_zoom: function(xperc, yperc, continuous) {
            var Mx = this._Mx;
            var Gx = this._Gx;

            var xadj = 0;
            if (Math.abs(xperc) < 1) {
                xadj = Math.abs(Mx.stk[Mx.level].xmax - Mx.stk[Mx.level].xmin);
                xadj = (xadj * xperc) / 2;
            }

            var yadj = 0;
            if (Math.abs(yperc) < 1) {
                yadj = Math.abs(Mx.stk[Mx.level].ymax - Mx.stk[Mx.level].ymin);
                yadj = (yadj * yperc) / 2;
            }

            var ul = {
                x: Math.max(Mx.stk[Mx.level].xmin + xadj, Gx.panxmin),
                y: Math.max(Mx.stk[Mx.level].ymin + yadj, Gx.panymin)
            };
            var lr = {
                x: Math.min(Mx.stk[Mx.level].xmax - xadj, Gx.panxmax),
                y: Math.min(Mx.stk[Mx.level].ymax - yadj, Gx.panymax)
            };

            this.zoom(ul, lr, continuous);
        },

        /**
         * Zoom onto a given region.
         *
         * @param ul
         *            the uppler left corner
         * @param {Number}
         *            ul.x the upper left x pos in real plot value
         * @param {Number}
         *            ul.y the upper left y pos in real plot values
         *
         * @param lr
         *            the lower right corner
         * @param {Number}
         *            lr the lower right x pos in real plot value
         * @param {Number}
         *            lr the lower right y pos in real plot values
         *
         * @param continuous
         *            enter continuous zoom mode.  This will create a
         *            new if you are on level 0, but stay on the same level
         *            otherwise
         */
        zoom: function(ul, lr, continuous) {
            var Mx = this._Mx;
            var Gx = this._Gx;

            if (Mx.level >= 9) { // currently only allow 10 zooms
                return;
            }

            if (ul.x === undefined) {
                ul.x = Mx.stk[Mx.level].xmin;
            }
            if (ul.y === undefined) {
                ul.y = Mx.stk[Mx.level].ymin;
            }
            if (lr.x === undefined) {
                lr.x = Mx.stk[Mx.level].xmax;
            }
            if (lr.y === undefined) {
                lr.y = Mx.stk[Mx.level].ymax;
            }

            if (lr.x < ul.x) {
                var xtmp = lr.x;
                lr.x = ul.x;
                ul.x = xtmp;
            }
            if (lr.y < ul.y) {
                var ytmp = lr.y;
                lr.y = ul.y;
                ul.y = ytmp;
            }

            var zstk = {};

            // xscl/yscl are reset in sigplot.refresh
            zstk.xscl = Mx.stk[Mx.level].xscl;
            zstk.yscl = Mx.stk[Mx.level].yscl;

            zstk.xmin = ul.x; // real world val at x1(origin=1,4) or
            // x2(origin=2,4)
            zstk.xmax = lr.x; // real world val at x2(origin=1,4) or
            // x1(origin=2,4)
            zstk.ymin = ul.y; // real world val at y2(origin=1,2) or
            // y1(origin=3,4)
            zstk.ymax = lr.y; // real world val at y1(origin=1,2) or
            // y2(origin=3,4)
            if (Gx.index) {
                zstk.xmin = Math.min(zstk.xmin / Gx.xdelta);
                zstk.xmax = Math.min(zstk.xmax / Gx.xdelta);
            }

            if (!continuous || (!Gx.inContinuousZoom)) {
                // We aren't yet in continuous zoom mode
                // so create a new level
                Mx.stk.push(zstk);
                Mx.level = Mx.stk.length - 1;
            } else {
                // Once in continuous zoom mode update the current level
                Mx.stk[Mx.level] = zstk;
            }
            Gx.inContinuousZoom = continuous;

            var evt = document.createEvent('Event');
            evt.initEvent('zoom', true, true);
            evt.level = Mx.level;
            evt.inContinuousZoom = Gx.inContinuousZoom;
            evt.xmin = Mx.stk[Mx.level].xmin;
            evt.ymin = Mx.stk[Mx.level].ymin;
            evt.xmax = Mx.stk[Mx.level].xmax;
            evt.ymax = Mx.stk[Mx.level].ymax;
            mx.dispatchEvent(Mx, evt);

            this.refresh();
        },

        /**
         * Unzoom one or more levels.
         *
         * @param [levels]
         *            the number of levels to unzoom, if not provided unzoom
         *            all.
         */
        unzoom: function(levels) {
            var Mx = this._Mx;
            var Gx = this._Gx;

            if (Mx.level === 0) {
                return;
            }

            if (!levels) {
                levels = Mx.stk.length;
            }

            while (levels > 0) {
                if (Mx.level === 0) {
                    break;
                }
                Mx.stk.pop();
                Mx.level = Mx.stk.length - 1;
                levels -= 1;
            }
            // After any unzooms you can no longer remain in
            // continuous zoom
            Gx.inContinuousZoom = false;

            // Send the event to listeners
            var evt = document.createEvent('Event');
            evt.initEvent('unzoom', true, true);
            evt.level = Mx.level;
            evt.xmin = Mx.stk[Mx.level].xmin;
            evt.ymin = Mx.stk[Mx.level].ymin;
            evt.xmax = Mx.stk[Mx.level].xmax;
            evt.ymax = Mx.stk[Mx.level].ymax;
            mx.dispatchEvent(Mx, evt);

            this.refresh();
        },

        /**
         * Register this plot to mimic zoom/unzoom of other plot
         *
         * @param other
         *     the other plot to mimic
         *
         * @param mask
         *     the set of event to respond to
         *
         * @param mask.zoom
         *     if true, respond to zoom events
         *
         * @param mask.xzoom
         *     if true, respond to zoom events on for the x-axis only
         *
         * @param mask.yzoom
         *     if true, respond to zoom events on for the y-axis only
         *
         * @param mask.unzoom
         *     if true, respond to unzoom events
         *
         */
        mimic: function(other, mask) {
            var self = this;

            if (!mask) {
                mask = {};
            }

            if (mask.zoom) {
                other.addListener("zoom", function(event) {
                    self.zoom({
                            x: event.xmin,
                            y: event.ymin
                        }, {
                            x: event.xmax,
                            y: event.ymax
                        },
                        event.inContinuousZoom);
                });
            } else if (mask.xzoom) {
                other.addListener("zoom", function(event) {
                    self.zoom({
                            x: event.xmin,
                            y: undefined
                        }, {
                            x: event.xmax,
                            y: undefined
                        },
                        event.inContinuousZoom);
                });
            } else if (mask.yzoom) {
                other.addListener("zoom", function(event) {
                    self.zoom({
                            x: undefined,
                            y: event.ymin
                        }, {
                            x: undefined,
                            y: event.ymax
                        },
                        event.inContinuousZoom);
                });
            }

            if (mask.unzoom) {
                other.addListener("unzoom", function(event) {
                    self.unzoom(1);
                });
            }

        },

        // TODO - do we want an unmimic?

        /**
         * Like refresh, but doesn't rerender data
         *
         */
        redraw: function() {
            var Gx = this._Gx;
            var Mx = this._Mx;
            var ctx = Mx.canvas.getContext("2d");

            if (!Gx.plotData.valid) {
                this.refresh();
            } else {
		ctx.drawImage(Gx.plotData,
			      Mx.l - 1,
			      Mx.t - 1,
			      (Mx.r - Mx.l) + 2,
			      (Mx.b - Mx.t) + 2,
			      Mx.l - 1,
			      Mx.t - 1,
			      (Mx.r - Mx.l) + 2,
			      (Mx.b - Mx.t) + 2
			     );

                draw_plugins(this);

                Gx.cross_xpos = undefined;
                Gx.cross_ypos = undefined;
                draw_crosshairs(this);
            }
        },

        /**
         * Refresh the entire plot
         */
        refresh: function() {
            var self = this;
            mx.render(this._Mx, function() {
                self._refresh();
            });
        },

        enable_listeners: function() {
            var Mx = this._Mx;
            mx.addEventListener(Mx, "mousedown", this.onmousedown, false);
            mx.addEventListener(Mx, "mousemove", this.throttledOnMouseMove, false);
            document.addEventListener("mouseup", this.docMouseUp, false);
            mx.addEventListener(Mx, "mouseup", this.mouseup, false);
            window.addEventListener("mousedown", this.dragMouseDownHandler, false);
            window.addEventListener("mousemove", this.throttledDragOnMouseMove, false);
            window.addEventListener("mouseup", this.dragMouseUpHandler, false);
            window.addEventListener("wheel", this.wheelHandler, false);
            window.addEventListener("mousewheel", this.wheelHandler, false);
            window.addEventListener("DOMMouseScroll", this.wheelHandler, false);
            window.addEventListener("keypress", this.onkeypress, false);
        },

        disable_listeners: function() {
            var Mx = this._Mx;

            mx.removeEventListener(Mx, "mousedown", this.onmousedown, false);
            mx.removeEventListener(Mx, "mousemove", this.throttledOnMouseMove,
                false);
            document.removeEventListener("mouseup", this.docMouseUp, false);
            mx.removeEventListener(Mx, "mouseup", this.mouseup, false);
            window.removeEventListener("mousedown", this.dragMouseDownHandler,
                false);
            window.removeEventListener("mousemove", this.throttledDragOnMouseMove,
                false);
            window.removeEventListener("mouseup", this.dragMouseUpHandler, false);
            window.removeEventListener("wheel", this.wheelHandler, false);
            window.removeEventListener("mousewheel", this.wheelHandler, false);
            window.removeEventListener("DOMMouseScroll", this.wheelHandler, false);
            window.removeEventListener("keypress", this.onkeypress, false);
        },

        checkresize: function() {
            if (mx.checkresize(this._Mx)) {
                this.refresh();
            }
        },

        _refresh: function() {
            var Mx = this._Mx;
            var Gx = this._Gx;
            var ctx = Mx.canvas.getContext("2d");
            var plugin_index = 0;

            if (Gx.hold) {
                return;
            }
            mx.set_font(Mx, Math.min(8, Mx.width / 64));
            Gx.pthk = Mx.text_w * 1.5;

            if (Gx.specs) {
                // Set left and right edges
                if (Gx.show_y_axis === true) {
                    Mx.l = Mx.text_w * 6;
                } else {
                    Mx.l = 1;
                }
                if (Gx.pan === true) {
                    Mx.r = Mx.width - (Gx.pthk + 2 * Mx.text_w);
                } else {
                    Mx.r = Mx.width - 2;
                }

                // Set top and bottom
                if (Gx.show_readout) {
                    Mx.t = Mx.text_h * 2;
                    if (Gx.show_x_axis) {
                        Mx.b = Mx.height - Mx.text_h * 4;
                    } else {
                        Mx.b = Mx.height - Mx.text_h * 3;
                    }
                } else {
                    if (Gx.pan) {
                        Mx.t = Gx.pthk + 2 * Mx.text_w;
                    } else {
                        Mx.t = 1;
                    }
                    if (Gx.show_x_axis) {
                        Mx.b = Mx.height - (Mx.text_h * 3) / 2;
                    } else {
                        Mx.b = Mx.height - 2;
                    }
                }

                // set left and right edges for X scrollbar
                if (Gx.show_readout) {
                    Gx.pl = Mx.text_w * 50;
                } else {
                    Gx.pl = Mx.text_w * 35;
                }
                Gx.pr = Math.max(Gx.pl + Mx.text_w * 9, Mx.r);

                // set top scrollbar edge for X scrollbar
                if (Gx.show_readout) {
                    if (Gx.show_x_axis) {
                        Gx.pt = Mx.b + Mx.text_h + (Mx.height - Mx.b - Mx.text_h - Gx.pthk) / 2;
                    } else {
                        Gx.pt = Mx.b + (Mx.height - Mx.b - Gx.pthk) / 2;
                    }
                } else {
                    Gx.pt = (Mx.t - Gx.pthk) / 2;
                }
                Gx.lbtn = Mx.text_h + Mx.text_w + 2;
            } else {
                if (Gx.pan) {
                    Mx.t = Gx.pthk + 2 * Mx.text_w;
                    Mx.r = Mx.width - (Gx.pthk + Mx.text_w);
                } else {
                    Mx.t = 1;
                    Mx.r = Mx.width - 2;
                }
                Mx.b = Mx.height - 2;
                Mx.l = 1;
                Gx.pl = Mx.l;
                Gx.pr = Mx.r;
                Gx.pt = (Mx.t - Gx.pthk) / 2;
                Gx.lbtn = 0;
            }

            // pan select ranges
            Gx.pyl = Mx.r + (Mx.width - Mx.r - Gx.pthk) / 2 + 1;

            // set virtual window size/pos/scaling for current level
            var k = Mx.level;
            Mx.stk[k].x1 = Mx.l;
            Mx.stk[k].y1 = Mx.t;
            Mx.stk[k].x2 = Mx.r;
            Mx.stk[k].y2 = Mx.b;
            Mx.stk[k].xscl = (Mx.stk[k].xmax - Mx.stk[k].xmin) / (Mx.r - Mx.l);
            Mx.stk[k].yscl = (Mx.stk[k].ymax - Mx.stk[k].ymin) / (Mx.b - Mx.t);

            // In case things have rescaled
            var re = pixel_to_real(this, Mx.xpos, Mx.ypos);
            Gx.retx = re.x;
            Gx.rety = re.y;

            // modify stack for section plotting
            //if (Gx.sections) {
            // TODO
            //}

            if (Gx.panning === 0 || Gx.panning !== 0) { // TODO Gx.panning !==
                // 0?? Does this work?
                Gx.plotData.valid = false;
                mx.clear_window(Mx);
            } //else if (!Gx.specs) {
            // TODO
            //} else if (Gx.panning === 1) {
            // TODO
            //} else {
            // TODO
            //}

            var xlab = Gx.xlab;
            var ylab = Gx.ylab;

            if (xlab === undefined) {
                xlab = 30;
            }
            if (Gx.index) {
                xlab = 0;
            }

            if (ylab === undefined) {
                var cx = ((Gx.lyr.length > 0) && Gx.lyr[0].cx);
                if (Gx.cmode === 1) {
                    ylab = 28;
                } else if (Gx.cmode === 2) {
                    ylab = Gx.plab;
                } else if ((Gx.cmode === 3) && (cx)) {
                    ylab = 21;
                } else if (Gx.cmode === 4) {
                    ylab = 22;
                } else if (Gx.cmode === 5) {
                    ylab = 22;
                    xlab = 21;
                } else if (Gx.cmode === 6) {
                    ylab = 26;
                } else if (Gx.cmode === 7) {
                    ylab = 27;
                } else {
                    ylab = 0;
                }
            }

            if (Gx.specs) {
                if (Gx.sections === 0) {
                    var drawaxis_flags = {
                        grid: Gx.grid
                    };
                    if (Gx.panning === 2) {
                        drawaxis_flags.noxtlab = true;
                    } // TODO Does this work??
                    if (!Gx.show_x_axis) {
                        drawaxis_flags.noxtics = true;
                        drawaxis_flags.noxtlab = true;
                        drawaxis_flags.noxplab = true;
                    }
                    if (!Gx.show_y_axis) {
                        drawaxis_flags.noytics = true;
                        drawaxis_flags.noytlab = true;
                        drawaxis_flags.noyplab = true;
                    }
                    if (Gx.specs && !Gx.show_readout && !Gx.pan) {
                        drawaxis_flags.noyplab = true;
                        drawaxis_flags.noxplab = true;
                    }
                    if (Gx.gridBackground) {
                        drawaxis_flags.fillStyle = Gx.gridBackground;
                    }
                    if (Gx.gridStyle) {
                        drawaxis_flags.gridStyle = Gx.gridStyle;
                    }
                    mx.drawaxis(Mx, Gx.xdiv, Gx.ydiv, xlab, ylab,
                        drawaxis_flags);
                } //else {
                // Not implemented yet
                //}

                var i = Gx.lbtn - 2;

                if (Gx.show_readout && Gx.pan) {
                    if (Gx.legend) {
                        Gx.legendBtnLocation = {
                            x: Mx.width - Gx.lbtn,
                            y: 2,
                            width: i,
                            height: i
                        };
                        mx.shadowbox(Mx, Mx.width - Gx.lbtn, 2, i, i, 1, -2,
                            'L');
                    } else {
                        Gx.legendBtnLocation = {
                            x: Mx.width - Gx.lbtn,
                            y: 2,
                            width: i,
                            height: i
                        };
                        mx
                            .shadowbox(Mx, Mx.width - Gx.lbtn, 2, i, i, 1,
                                2, 'L');
                    }
                    display_specs(this);
                }
            } else if (Gx.grid && Gx.sections >= 0) {
                var drawaxis_flags = {
                    grid: true,
                    noaxisbox: true,
                    noxtics: true,
                    noxtlab: true,
                    noxplab: true,
                    noytics: true,
                    noytlab: true,
                    noyplab: true
                };
                mx.drawaxis(Mx, Gx.xdiv, Gx.ydiv, xlab, ylab,
                    drawaxis_flags);
            }

            for (var n = 0; n < Gx.lyr.length; n++) {
                //if (Gx.sections !== 0) {
                // TODO
                //}
                draw_layer(this, n);
            }

            draw_accessories(this, 4);

            if ((Mx.r > Mx.l) && (Mx.b > Mx.t)) {
	      // Resize
              Gx.plotData.width = Mx.canvas.width;
              Gx.plotData.height = Mx.canvas.height;
	      Gx.plotData.getContext("2d").drawImage(Mx.canvas, 0, 0);
	      Gx.plotData.valid = true;
            }

            draw_plugins(this);


            Gx.cross_xpos = undefined;
            Gx.cross_ypos = undefined;
            draw_crosshairs(this);
        }
    };

    // /////////////////////////////////////////////////////////////////////////
    // Private methods and objects
    // /////////////////////////////////////////////////////////////////////////

    /**
     * Options used when displaying the spinner.
     *
     * @memberOf sigplot
     * @private
     */
    var SPINNER_OPTS = {
        lines: 13, // The number of lines to draw
        length: 7, // The length of each line
        width: 4, // The line thickness
        radius: 10, // The radius of the inner circle
        corners: 1, // Corner roundness (0..1)
        rotate: 0, // The rotation offset
        color: '#FFF', // #rgb or #rrggbb
        speed: 1, // Rounds per second
        trail: 60, // Afterglow percentage
        shadow: false, // Whether to render a shadow
        hwaccel: false, // Whether to use hardware acceleration
        className: 'spinner', // The CSS class to assign to the spinner
        zIndex: 2e9, // The z-index (defaults to 2000000000)
        top: 'auto', // Top position relative to parent in px
        left: 'auto' // Left position relative to parent in px
    };

    /**
     * Map integer cmode to string equivalent.
     *
     * @memberOf sigplot
     * @private
     */
    var cxm = ["Ma", "Ph", "Re", "Im", "IR", "Lo", "L2"];

    /**
     * Map integer abscissa mode to string equivalent.
     *
     * @memberOf sigplot
     * @private
     */
    var cam = ["(absc)", "(indx)", "(1/ab)", "(dydx)"];



    /**
     * This object holds the data associated with layers in the plot.
     *
     * @constructor
     * @memberOf sigplot
     * @private
     */
    function SIGPLOTLAYER() {

        this.xbuf = undefined; // raw (ArrayBuffer) of ABSC data
        this.ybuf = undefined; // raw (ArrayBuffer) of ORD data

        this.offset = 0.0;
        this.xstart = 0.0;
        this.xdelta = 0.0;
        this.imin = 0;
        this.xmin = 0.0;
        this.xmax = 0.0;
        this.name = "";
        this.cx = false;
        this.hcb = undefined; // index in Gx.HCB
        // xbufn = xbuf.byteLength
        // ybufn = ybuf.byteLength
        this.size = 0;

        this.display = true;
        this.color = 0;
        this.line = 3; // 0=none, 1-vertical, 2-horizontal, 3-connecting
        this.thick = 1; // negative for dashed
        this.symbol = 0;
        this.radius = 3;

        this.skip = 0; // number of elements between ord values
        this.xsub = 0;
        this.ysub = 0;
        this.xdata = false; // true if X data is data from file

        this.options = {};
    }

    /**
     * The graphics structure object used to hold state about the plot.
     *
     * @constructor
     * @memberOf sigplot
     * @private
     */
    function GX() {
        this.xptr = undefined; // xpoints as anything "array-like"...
        this.yptr = undefined; // ypoints as anything "array-like"...

        this.retx = 0.0; // absc coord. at mouse location
        this.rety = 0.0;
        this.xmrk = 0.0; // absc coord of mark
        this.ymrk = 0.0;
        this.aretx = 0.0; // absc coord. at mouse location
        this.arety = 0.0;

        this.xstart = 0.0;
        this.xdelta = 0.0;

        this.panxmin = 0.0;
        this.panxmax = 0.0;
        this.panymin = 0.0;
        this.panymax = 0.0;
        this.xmin = 0.0;
        this.xmax = 0.0;
        this.ymin = 0.0;
        this.ymax = 0.0;
        this.zmin = undefined;
        this.zmax = undefined;
        this.dbmin = 0.0;
        this.pxscl = 0.0;
        this.pyscl = 0.0;
        this.pmt = 0.0;

        this.note = "";
        // this.mouse unnecssary because we don't have res table
        this.format = "";

        this.pl = 0;
        this.pr = 0;
        this.pt = 0;
        this.pb = 0;
        this.px1 = 0; // specifies plotting field
        this.px2 = 0;
        this.py1 = 0;
        this.py2 = 0;

        this.pyl = 0;
        this.pthk = 0; // thickness of pan drag box

        this.modlayer = 0;
        this.modsource = 0;
        this.modified = false;
        this.modmode = 0;

        this.xdiv = 0;
        this.ydiv = 0;

        this.all = false;
        this.expand = false;
        this.cross = false;
        this.grid = true;
        this.gridBackground = undefined;
        this.index = false;
        this.pan = true;
        this.specs = true;
        this.legend = true;
        this.xdata = false;

        this.show_x_axis = true;
        this.show_y_axis = true;
        this.show_readout = true;
        this.hide_note = false;
        this.autohide_readout = false;
        this.autohide_panbars = false;
        this.panning = undefined;
        this.panmode = 0; // TODO Is this a good default value? Where is this
        // changed?
        this.hold = false;

        this.sections = 0; // number of plot sections, -1 for layers
        this.iysec = 0;
        this.nsec = 0; // actual number of sections
        this.isec = 0; // current sections

        this.xlab = undefined;
        this.ylab = undefined;

        this.default_rubberbox_action = "zoom";
        this.default_rubberbox_mode = "box";

        this.wheelscroll_mode_natural = true;
        this.scroll_time_interval = 10;

        this.repeatPanning = undefined;
        this.stillPanning = undefined; // TODO maybe merge this variable with
        // Gx.panning in future?

        this.autol = -1;

        this.lineSmoothing = false; // not implemented
        this.rasterSmoothing = false;

        this.wheelZoom = false;
        this.wheelZoomPercent = 0.2;
        this.inContinuousZoom = false;

        this.lyr = [];
        this.HCB = [];
        this.plugins = [];
                
        this.plotData = document.createElement("canvas");
	this.plotData.valid = false;
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function sigplot_show_x(plot) {
        var Gx = plot._Gx;
        var Mx = plot._Mx;

        var ls = Gx.aretx.toString();
        if (Gx.iabsc === 1) {
            mx.message(Mx, "INDEX = " + ls);
        } else if (Gx.iabsc === 2) {
            mx.message(Mx, "1/X = " + ls);
        } else {
            //if (Gx.xlab === 4) {
            // TODO
            //}
            mx.message(Mx, "X = " + ls);
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function sigplot_show_timecode(plot) {
        var Gx = plot._Gx;
        var Mx = plot._Mx;

        if (Gx.lyr.length > 0) {
            //var hcb = Gx.HCB[Gx.lyr[0].hcb];
            var hcb = Gx.lyr[0].hcb; // mmm-TODO-needs investigation
            if (hcb.xunits === 1) {
                mx.message(Mx, "Time = " + m.sec2tod(hcb.timecode + Gx.retx));
            } else {
                mx.message(Mx, "Time = UNK");
            }

        }

    }

    /**
     * @memberOf sigplot
     * @private
     */
    function sigplot_show_y(plot) {
        var Gx = plot._Gx;
        var Mx = plot._Mx;

        var ls = Gx.arety.toString();
        if (Gx.iabsc === 2) {
            mx.message(Mx, "1/Y = " + ls);
        } else {
            //if (Gx.xlab === 4) {
            // TODO
            //}
            mx.message(Mx, "Y = " + ls);
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function sigplot_show_z(plot) {
        var Gx = plot._Gx;
        var Mx = plot._Mx;

        if (Gx.zmin && Gx.zmax) { // at least one layer has a z dimension
            var msg = "";
            if (Gx.lyr.length === 1) {
                var msg = "Z = " + Gx.lyr[0].get_z(Gx.retx, Gx.rety).toString();
            } else {
                var msg = "TODO"; // TODO we need to think of what we want to display here
            }
            mx.message(Mx, msg);
        }
    }

    /**
     * Constructs a menu for updating the pan scale of the plot.
     *
     * @param plot
     *            The plot to work with.
     * @param command
     *            The scroll direction to use. Either "XPAN" or "YPAN" are
     *            acceptable.
     * @private
     */
    function sigplot_scrollScaleMenu(plot, command) {
        var Mx = plot._Mx;

        mx.removeEventListener(Mx, "mousedown", plot.onmousedown, false);

        mx.menu(Mx, {
            title: "SCROLLBAR",
            refresh: function() {
                plot.refresh();
            },
            finalize: function() {
                mx.addEventListener(Mx, "mousedown",
                    plot.onmousedown, false);
                plot.refresh();
            },
            items: [{
                text: "Expand Range",
                handler: function() {
                    middleClickScrollMenuAction(plot,
                        mx.SB_EXPAND, command);
                }
            }, {
                text: "Shrink Range",
                handler: function() {
                    middleClickScrollMenuAction(plot,
                        mx.SB_SHRINK, command);
                }
            }, {
                text: "Expand Full",
                handler: function() {
                    middleClickScrollMenuAction(plot,
                        mx.SB_FULL, command);
                }
            }]
        });
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function sigplot_mainmenu(plot) {
        var Gx = plot._Gx;
        var Mx = plot._Mx;

        // show mainmenu
        //

        mx.removeEventListener(Mx, "mousedown", plot.onmousedown, false);

        // Sub-menus
        var CONTROLS_MENU = {
            text: "Cntrls...",
            menu: {
                title: "CONTROLS OPTIONS",
                items: [{
                    text: "Continuous (Disabled)",
                    checked: Gx.cntrls === -2,
                    handler: function() {
                        plot.change_settings({
                            xcnt: -2
                        });
                    }
                }, {
                    text: "LM Click (Disabled)",
                    checked: Gx.cntrls === -1,
                    handler: function() {
                        plot.change_settings({
                            xcnt: -1
                        });
                    }
                }, {
                    text: "Off",
                    checked: Gx.cntrls === 0,
                    handler: function() {
                        plot.change_settings({
                            xcnt: 0
                        });
                    }
                }, {
                    text: "LM Click",
                    checked: Gx.cntrls === 1,
                    handler: function() {
                        plot.change_settings({
                            xcnt: 1
                        });
                    }
                }, {
                    text: "Continuous",
                    checked: Gx.cntrls === 2,
                    handler: function() {
                        plot.change_settings({
                            xcnt: 2
                        });
                    }
                }]
            }
        };

        var CXMODE_MENU = {
            text: "CX Mode...",
            menu: {
                title: "COMPLEX MODE",
                items: [{
                    text: "Magnitude",
                    checked: Gx.cmode === 1,
                    handler: function() {
                        plot.change_settings({
                            cmode: 1
                        });
                    }
                }, {
                    text: "Phase",
                    checked: Gx.cmode === 2,
                    handler: function() {
                        plot.change_settings({
                            cmode: 2
                        });
                    }
                }, {
                    text: "Real",
                    checked: Gx.cmode === 3,
                    handler: function() {
                        plot.change_settings({
                            cmode: 3
                        });
                    }
                }, {
                    text: "Imaginary",
                    checked: Gx.cmode === 4,
                    handler: function() {
                        plot.change_settings({
                            cmode: 4
                        });
                    }
                }, {
                    text: "IR: Imag/Real",
                    checked: Gx.cmode === 5,
                    handler: function() {
                        plot.change_settings({
                            cmode: 5
                        });
                    }
                }, {
                    text: "10*Log10",
                    checked: Gx.cmode === 6,
                    handler: function() {
                        plot.change_settings({
                            cmode: 6
                        });
                    }
                }, {
                    text: "20*Log10",
                    checked: Gx.cmode === 7,
                    handler: function() {
                        plot.change_settings({
                            cmode: 7
                        });
                    }
                }]
            }
        };

        var SCALING_MENU = {
            text: "Scaling...",
            menu: {
                title: "SCALING",
                items: [{
                    text: "Y Axis",
                    style: "separator"
                }, {
                    text: "Parameters...",
                    checked: (Gx.autoy === 0),
                    handler: function() {
                        Gx.autoy = 0;

                        var nextPrompt = function() {
                            setupPrompt(
                                plot,
                                "Y Axis Max:",
                                mx.floatValidator,
                                function(finalValue) {
                                    if (parseFloat(finalValue) !== Mx.stk[Mx.level].ymax) {
                                        // Only update if different
                                        // value
                                        if (finalValue === "") {
                                            finalValue = 0;
                                        }
                                        updateViewbox(
                                            plot,
                                            Mx.stk[Mx.level].ymin,
                                            parseFloat(finalValue),
                                            "Y");
                                    } else {
                                        plot.refresh();
                                    }
                                }, Mx.stk[Mx.level].ymax,
                                undefined, undefined, undefined);
                        };

                        setupPrompt(
                            plot,
                            "Y Axis Min:",
                            mx.floatValidator,
                            function(finalValue) {
                                if (parseFloat(finalValue) !== Mx.stk[Mx.level].ymin) {
                                    // Only update if different
                                    // value
                                    if (finalValue === "") {
                                        finalValue = 0;
                                    }
                                    updateViewbox(plot,
                                        parseFloat(finalValue),
                                        Mx.stk[Mx.level].ymax,
                                        "Y");
                                } else {
                                    plot.refresh();
                                }

                            }, Mx.stk[Mx.level].ymin, undefined,
                            undefined, nextPrompt);
                    }
                }, {
                    text: "Min Auto",
                    checked: (Gx.autoy === 1),
                    handler: function() {
                        Gx.autoy = 1;
                    }
                }, {
                    text: "Max Auto",
                    checked: (Gx.autoy === 2),
                    handler: function() {
                        Gx.autoy = 2;
                    }
                }, {
                    text: "Full Auto",
                    checked: (Gx.autoy === 3),
                    handler: function() {
                        Gx.autoy = 3;
                    }
                }, {
                    text: "X Axis",
                    style: "separator"
                }, {
                    text: "Parameters...",
                    checked: (Gx.autox === 0),
                    handler: function() {
                        Gx.autox = 0;

                        var nextPrompt = function() {
                            setupPrompt(
                                plot,
                                "X Axis Max:",
                                mx.floatValidator,
                                function(finalValue) {
                                    if (parseFloat(finalValue) !== Mx.stk[Mx.level].xmax) {
                                        // Only update if different
                                        // value
                                        if (finalValue === "") {
                                            finalValue = 0;
                                        }
                                        updateViewbox(
                                            plot,
                                            Mx.stk[Mx.level].xmin,
                                            parseFloat(finalValue),
                                            "X");
                                    } else {
                                        plot.refresh();
                                    }
                                }, Mx.stk[Mx.level].xmax,
                                undefined, undefined, undefined);
                        };

                        setupPrompt(
                            plot,
                            "X Axis Min:",
                            mx.floatValidator,
                            function(finalValue) {
                                if (parseFloat(finalValue) !== Mx.stk[Mx.level].xmin) {
                                    // Only update if different
                                    // value
                                    if (finalValue === "") {
                                        finalValue = 0;
                                    }
                                    updateViewbox(plot,
                                        parseFloat(finalValue),
                                        Mx.stk[Mx.level].xmax,
                                        "X");
                                } else {
                                    plot.refresh();
                                }
                            }, Mx.stk[Mx.level].xmin, undefined,
                            undefined, nextPrompt);
                    }
                }, {
                    text: "Min Auto",
                    checked: (Gx.autox === 1),
                    handler: function() {
                        Gx.autox = 1;
                    }
                }, {
                    text: "Max Auto",
                    checked: (Gx.autox === 2),
                    handler: function() {
                        Gx.autox = 2;
                    }
                }, {
                    text: "Full Auto",
                    checked: (Gx.autox === 3),
                    handler: function() {
                        Gx.autox = 3;
                    }
                }]
            }
        };

        var GRID_MENU = {
            text: "Grid",
            handler: function() {
                plot.change_settings({
                    grid: !Gx.grid
                });
            }
        };

        var SETTINGS_MENU = {
            text: "Settings...",
            menu: {
                title: "SETTINGS",
                items: [{
                    text: "ALL Mode",
                    checked: Gx.all,
                    style: "checkbox",
                    handler: function() {
                        plot.change_settings({
                            all: !Gx.all
                        });
                    }
                }, {
                    text: "Controls...",
                    menu: {
                        title: "CONTROLS OPTIONS",
                        items: [{
                            text: "Continuous (Disabled)",
                            checked: Gx.cntrls === -2,
                            handler: function() {
                                plot.change_settings({
                                    xcnt: -2
                                });
                            }
                        }, {
                            text: "LM Click (Disabled)",
                            checked: Gx.cntrls === -1,
                            handler: function() {
                                plot.change_settings({
                                    xcnt: -1
                                });
                            }
                        }, {
                            text: "Off",
                            checked: Gx.cntrls === 0,
                            handler: function() {
                                plot.change_settings({
                                    xcnt: 0
                                });
                            }
                        }, {
                            text: "LM Click",
                            checked: Gx.cntrls === 1,
                            handler: function() {
                                plot.change_settings({
                                    xcnt: 1
                                });
                            }
                        }, {
                            text: "Continuous",
                            checked: Gx.cntrls === 2,
                            handler: function() {
                                plot.change_settings({
                                    xcnt: 2
                                });
                            }
                        }]
                    }
                }, {
                    text: "Mouse...",
                    menu: {
                        title: "MOUSE OPTIONS",
                        items: [{
                            text: "LM Drag (Zoom)",
                            checked: Gx.default_rubberbox_action === "zoom",
                            handler: function() {
                                Gx.default_rubberbox_action = "zoom";
                            }
                        }, {
                            text: "LM Drag (Select)",
                            checked: Gx.default_rubberbox_action === "select",
                            handler: function() {
                                Gx.default_rubberbox_action = "select";
                            }
                        }, {
                            text: "LM Drag (Disabled)",
                            checked: Gx.default_rubberbox_action === undefined,
                            handler: function() {
                                Gx.default_rubberbox_action = undefined;
                            }
                        }, {
                            text: "Mode...",
                            menu: {
                                title: "MOUSE Mode",
                                items: [{
                                    text: "Box",
                                    checked: Gx.default_rubberbox_mode === "box",
                                    handler: function() {
                                        Gx.default_rubberbox_mode = "box";
                                    }

                                }, {
                                    text: "Horizontal",
                                    checked: Gx.default_rubberbox_mode === "horizontal",
                                    handler: function() {
                                        Gx.default_rubberbox_mode = "horizontal";
                                    }
                                }, {
                                    text: "Vertical",
                                    checked: Gx.default_rubberbox_mode === "vertical",
                                    handler: function() {
                                        Gx.default_rubberbox_mode = "vertical";
                                    }
                                }]
                            }
                        }, {
                            text: "CROSShairs...",
                            menu: {
                                title: "Crosshairs Mode",
                                items: [{
                                    text: "Off",
                                    checked: !Gx.cross,
                                    handler: function() {
                                        Gx.cross = false;
                                    }

                                }, {
                                    text: "On",
                                    checked: Gx.cross === true,
                                    handler: function() {
                                        Gx.cross = true;
                                    }
                                }, {
                                    text: "Horizontal",
                                    checked: Gx.cross === "horizontal",
                                    handler: function() {
                                        Gx.cross = "horizontal";
                                    }
                                }, {
                                    text: "Vertical",
                                    checked: Gx.cross === "vertical",
                                    handler: function() {
                                        Gx.cross = "vertical";
                                    }
                                }]
                            }
                        }, {
                            text: "Mousewheel Natural Mode",
                            checked: Gx.wheelscroll_mode_natural,
                            style: "checkbox",
                            handler: function() {
                                plot
                                    .change_settings({
                                        wheelscroll_mode_natural: !Gx.wheelscroll_mode_natural
                                    });
                            }
                        }]
                    }
                }, {
                    text: "CROSShairs",
                    checked: Gx.cross,
                    style: "checkbox",
                    handler: function() {
                        plot.change_settings({
                            cross: !Gx.cross
                        });
                    }
                }, {
                    text: "GRID",
                    checked: Gx.grid,
                    style: "checkbox",
                    handler: function() {
                        plot.change_settings({
                            grid: !Gx.grid
                        });
                    }
                }, {
                    text: "INDEX Mode",
                    checked: Gx.index,
                    style: "checkbox",
                    handler: function() {
                        plot.change_settings({
                            index: !Gx.index
                        });
                    }
                }, {
                    text: "LEGEND",
                    checked: Gx.legend,
                    style: "checkbox",
                    handler: function() {
                        plot.change_settings({
                            legend: !Gx.legend
                        });
                    }
                }, {
                    text: "PAN Scrollbars",
                    checked: Gx.pan,
                    style: "checkbox",
                    handler: function() {
                        plot.change_settings({
                            pan: !Gx.pan
                        });
                    }
                }, {
                    text: "PHase UNITS...",
                    menu: {
                        title: "PHASE UNITS",
                        items: [{
                            text: "Radians",
                            checked: Gx.plab === 23,
                            handler: function() {
                                plot.change_settings({
                                    phunits: 'R'
                                });
                            }

                        }, {
                            text: "Degrees",
                            checked: Gx.plab === 24,
                            handler: function() {
                                plot.change_settings({
                                    phunits: 'D'
                                });
                            }
                        }, {
                            text: "Cycles",
                            checked: Gx.plab === 25,
                            handler: function() {
                                plot.change_settings({
                                    phunits: 'C'
                                });
                            }
                        }]
                    }
                }, {
                    text: "SPECS",
                    checked: Gx.specs,
                    style: "checkbox",
                    handler: function() {
                        plot.change_settings({
                            specs: !Gx.specs
                        });
                    }
                }, {
                    text: "XDIVisions...",
                    handler: function() {
                        var validator = function(value) {
                            var isValid = mx.intValidator(value);
                            var maxXDIV = m.trunc(Mx.width / 2); // TODO
                            // Make value an option on the plot?
                            // Maybe still a little too high
                            // while dotted-line grids are
                            // being drawn?
                            if (isValid.valid && value > maxXDIV) {
                                return {
                                    valid: false,
                                    reason: "Exceeds maximum number of divisions (" + maxXDIV + ")."
                                };
                            } else {
                                return isValid;
                            }
                        };

                        setupPrompt(
                            plot,
                            "X Divisions:",
                            validator,
                            function(finalValue) {
                                if (parseFloat(finalValue) !== Gx.xdiv) { // Only
                                    // update if different value
                                    if (finalValue === "") {
                                        finalValue = 1;
                                    }
                                    Gx.xdiv = parseFloat(finalValue);
                                }
                                plot.refresh();

                            }, Gx.xdiv, undefined, undefined,
                            undefined);
                    }
                }, {
                    text: "XLABel...",
                    handler: function() {
                        var validator = function(value) {
                            console.log("The value is " + value);
                            var isValid = mx.intValidator(value);
                            return isValid;
                        };

                        setupPrompt(
                            plot,
                            "X Units:",
                            validator,
                            function(finalValue) {
                                if (parseFloat(finalValue) !== Gx.xlab) { // Only
                                    // update if different value
                                    if (finalValue < 0) {
                                        finalValue = 0;
                                    }
                                    Gx.xlab = parseFloat(finalValue);
                                }
                                plot.refresh();

                            }, Gx.xlab, undefined, undefined,
                            undefined);
                    }
                }, {
                    text: "YDIVisions...",
                    handler: function() {
                        var validator = function(value) {
                            var isValid = mx.intValidator(value);
                            var maxYDIV = m.trunc(Mx.height / 2); // TODO
                            // Make value an option on the plot?
                            // Maybe still a little too high
                            // while dotted-line grids are
                            // being drawn?
                            if (isValid.valid && value > maxYDIV) {
                                return {
                                    valid: false,
                                    reason: "Exceeds maximum number of divisions (" + maxYDIV + ")."
                                };
                            } else {
                                return isValid;
                            }
                        };

                        setupPrompt(
                            plot,
                            "Y Divisions:",
                            validator,
                            function(finalValue) {
                                if (parseFloat(finalValue) !== Gx.ydiv) {
                                    // Only update if different
                                    // value
                                    if (finalValue === "") {
                                        finalValue = 1;
                                    }
                                    Gx.ydiv = parseFloat(finalValue);
                                }
                                plot.refresh();

                            }, Gx.ydiv, undefined, undefined,
                            undefined);
                    }
                }, {
                    text: "YINVersion",
                    checked: (Mx.origin === 4),
                    style: "checkbox",
                    handler: function() {
                        plot.change_settings({
                            yinv: (Mx.origin !== 4)
                        });
                    }
                }, {
                    text: "YLABel...",
                    handler: function() {
                        var validator = function(value) {
                            var isValid = mx.intValidator(value);
                            return isValid;
                        };

                        setupPrompt(
                            plot,
                            "Y Units:",
                            validator,
                            function(finalValue) {
                                if (parseFloat(finalValue) !== Gx.ylab) { // Only
                                    // update if different value
                                    if (finalValue < 0) {
                                        finalValue = 0;
                                    }
                                    Gx.ylab = parseFloat(finalValue);
                                }
                                plot.refresh();

                            }, Gx.ylab, undefined, undefined,
                            undefined);
                    }
                }, {
                    text: "X-axis",
                    checked: Gx.show_x_axis,
                    style: "checkbox",
                    handler: function() {
                        plot.change_settings({
                            show_x_axis: !Gx.show_x_axis
                        });
                    }
                }, {
                    text: "Y-axis",
                    checked: Gx.show_y_axis,
                    style: "checkbox",
                    handler: function() {
                        plot.change_settings({
                            show_y_axis: !Gx.show_y_axis
                        });
                    }
                }, {
                    text: "Readout",
                    checked: Gx.show_readout,
                    style: "checkbox",
                    handler: function() {
                        plot.change_settings({
                            show_readout: !Gx.show_readout
                        });
                    }
                }, {
                    text: "Invert Colors",
                    checked: Mx.xi,
                    style: "checkbox",
                    handler: function() {
                        mx.invertbgfg(Mx);
                    }
                }]
            }
        };

        var COLORMAP_MENU = {
            text: "Colormap...",
            menu: {
                title: "COLORMAP",
                items: [{
                    text: "Greyscale",
                    checked: (Gx.cmap === 0),
                    handler: function() {
                        plot.change_settings({
                            cmap: 0
                        });
                    }
                }, {
                    text: "Ramp Colormap",
                    checked: (Gx.cmap === 1),
                    handler: function() {
                        plot.change_settings({
                            cmap: 1
                        });
                    }
                }, {
                    text: "Color Wheel",
                    checked: (Gx.cmap === 2),
                    handler: function() {
                        plot.change_settings({
                            cmap: 2
                        });
                    }
                }, {
                    text: "Spectrum",
                    checked: (Gx.cmap === 3),
                    handler: function() {
                        plot.change_settings({
                            cmap: 3
                        });
                    }
                }, {
                    text: "Sunset",
                    checked: (Gx.cmap === 4),
                    handler: function() {
                        plot.change_settings({
                            cmap: 4
                        });
                    }
                }]
            }
        };

        var traceoptionsmenu = function(index) {
            return {
                title: "TRACE OPTIONS",
                items: [{
                    text: "Dashed...",
                    handler: function() {
                        // Figure out the current thickness
                        var thk = 1;
                        if (index !== undefined) {
                            thk = Math.abs(plot._Gx.lyr[index].thick);
                        } else {
                            if (Gx.lyr.length === 0) { return; }

                            thk = Math.abs(plot._Gx.lyr[0].thick);
                            for (var i = 0; i < Gx.lyr.length; i++) {
                                if (thk !== Math.abs(plot._Gx.lyr[i].thick)) {
                                    thk = 1;
                                    break;
                                }
                            }
                        }
                        setupPrompt(
                            plot,
                            "Line thickness:",
                            mx.intValidator, function(finalValue) {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].line = 3;
                                    plot._Gx.lyr[index].thick = -1 * finalValue;
                                    plot._Gx.lyr[index].symbol = 0;
                                } else {
                                    for (var index = 0; index < Gx.lyr.length; index++) {
                                        plot._Gx.lyr[index].line = 3;
                                        plot._Gx.lyr[index].thick = -1 * finalValue;
                                        plot._Gx.lyr[index].symbol = 0;
                                    }
                                }
                            }, thk);
                    }
                }, {
                    text: "Dots...",
                    handler: function() {
                        // Figure out the current thickness
                        var radius = 3;
                        if (index !== undefined) {
                            radius = Math.abs(plot._Gx.lyr[index].radius);
                        } else {
                            if (Gx.lyr.length === 0) { return; }
                            for (var i = 0; i < Gx.lyr.length; i++) {
                                if (radius !== Math.abs(plot._Gx.lyr[i].radius)) {
                                    radius = 3;
                                    break;
                                }
                            }
                        }
                        setupPrompt(
                            plot,
                            "Radius/Shape:",
                            mx.intValidator, function(finalValue) {
                                var sym;
                                var rad;
                                if (finalValue < 0) {
                                    sym = 3; // square
                                    rad = Math.abs(finalValue);
                                } else if (finalValue > 0) {
                                    sym = 2; // circle
                                    rad = finalValue;
                                } else {
                                    sym = 1;
                                    rad = 0;
                                }
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].line = 0;
                                    plot._Gx.lyr[index].radius = rad;
                                    plot._Gx.lyr[index].symbol = sym;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].line = 0;
                                        plot._Gx.lyr[i].radius = rad;
                                        plot._Gx.lyr[i].symbol = sym;
                                    }
                                }
                            }, radius);
                    }
                }, {
                    text: "Solid...",
                    handler: function() {
                        // Figure out the current thickness
                        var thk = 1;
                        if (index !== undefined) {
                            thk = Math.abs(plot._Gx.lyr[index].thick);
                        } else {
                            if (Gx.lyr.length === 0) { return; }

                            thk = Math.abs(plot._Gx.lyr[0].thick);
                            for (var i = 0; i < Gx.lyr.length; i++) {
                                if (thk !== Math.abs(plot._Gx.lyr[i].thick)) {
                                    thk = 1;
                                    break;
                                }
                            }
                        }
                        setupPrompt(
                            plot,
                            "Line thickness:",
                            mx.intValidator, function(finalValue) {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].line = 3;
                                    plot._Gx.lyr[index].thick = finalValue;
                                    plot._Gx.lyr[index].symbol = 0;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].line = 3;
                                        plot._Gx.lyr[i].thick = finalValue;
                                        plot._Gx.lyr[i].symbol = 0;
                                    }
                                }
                            }, thk);
                    }
                }, {
                    text: "Toggle",
                    style: (index !== undefined) ? "checkbox" : undefined,
                    checked: (index !== undefined) ? plot._Gx.lyr[index].display : undefined,
                    handler: function() {
                        if (index !== undefined) {
                            plot._Gx.lyr[index].display = !plot._Gx.lyr[index].display;
                        } else {
                            for (var i = 0; i < Gx.lyr.length; i++) {
                                plot._Gx.lyr[i].display = !plot._Gx.lyr[i].display;
                            }
                        }
                    }
                }, {
                    text: "Symbols...",
                    menu: {
                        title: "SYMBOLS",
                        items: [{
                            text: "Retain Current"
                        }, {
                            text: "None",
                            checked: (index !== undefined) ? plot._Gx.lyr[index].symbol === 0 : undefined,
                            handler: function() {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].radius = 0;
                                    plot._Gx.lyr[index].symbol = 0;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].radius = 0;
                                        plot._Gx.lyr[i].symbol = 0;
                                    }
                                }
                            }
                        }, {
                            text: "Pixels",
                            checked: (index !== undefined) ? plot._Gx.lyr[index].symbol === 1 : undefined,
                            handler: function() {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].radius = 1;
                                    plot._Gx.lyr[index].symbol = 1;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].radius = 1;
                                        plot._Gx.lyr[i].symbol = 1;
                                    }
                                }
                            }
                        }, {
                            text: "Circles",
                            checked: (index !== undefined) ? plot._Gx.lyr[index].symbol === 2 : undefined,
                            handler: function() {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].radius = 4;
                                    plot._Gx.lyr[index].symbol = 2;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].radius = 4;
                                        plot._Gx.lyr[i].symbol = 2;
                                    }
                                }
                            }
                        }, {
                            text: "Squares",
                            checked: (index !== undefined) ? plot._Gx.lyr[index].symbol === 3 : undefined,
                            handler: function() {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].radius = 4;
                                    plot._Gx.lyr[index].symbol = 3;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].radius = 4;
                                        plot._Gx.lyr[i].symbol = 3;
                                    }
                                }
                            }
                        }, {
                            text: "Plusses",
                            checked: (index !== undefined) ? plot._Gx.lyr[index].symbol === 4 : undefined,
                            handler: function() {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].radius = 4;
                                    plot._Gx.lyr[index].symbol = 4;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].radius = 4;
                                        plot._Gx.lyr[i].symbol = 4;
                                    }
                                }
                            }
                        }, {
                            text: "X's",
                            checked: (index !== undefined) ? plot._Gx.lyr[index].symbol === 5 : undefined,
                            handler: function() {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].radius = 4;
                                    plot._Gx.lyr[index].symbol = 5;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].radius = 4;
                                        plot._Gx.lyr[i].symbol = 5;
                                    }
                                }
                            }
                        }, {
                            text: "Triangles",
                            checked: (index !== undefined) ? plot._Gx.lyr[index].symbol === 6 : undefined,
                            handler: function() {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].radius = 6;
                                    plot._Gx.lyr[index].symbol = 6;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].radius = 6;
                                        plot._Gx.lyr[i].symbol = 6;
                                    }
                                }
                            }
                        }, {
                            text: "Downward Triangles",
                            checked: (index !== undefined) ? plot._Gx.lyr[index].symbol === 7 : undefined,
                            handler: function() {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].radius = 6;
                                    plot._Gx.lyr[index].symbol = 7;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].radius = 6;
                                        plot._Gx.lyr[i].symbol = 7;
                                    }
                                }
                            }
                        }]
                    }
                }, {
                    text: "Line Type...",
                    menu: {
                        title: "LINE TYPE",
                        items: [{
                            text: "Retain Current"
                        }, {
                            text: "None",
                            checked: (index !== undefined) ? plot._Gx.lyr[index].line === 0 : undefined,
                            handler: function() {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].line = 0;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].line = 0;
                                    }
                                }
                            }
                        }, {
                            text: "Verticals",
                            checked: (index !== undefined) ? plot._Gx.lyr[index].line === 1 : undefined,
                            handler: function() {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].line = 1;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].line = 1;
                                    }
                                }
                            }
                        }, {
                            text: "Horizontals",
                            checked: (index !== undefined) ? plot._Gx.lyr[index].line === 2 : undefined,
                            handler: function() {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].line = 2;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].line = 2;
                                    }
                                }
                            }
                        }, {
                            text: "Connecting",
                            checked: (index !== undefined) ? plot._Gx.lyr[index].line === 3 : undefined,
                            handler: function() {
                                if (index !== undefined) {
                                    plot._Gx.lyr[index].line = 3;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].line = 3;
                                    }
                                }
                            }
                        }]
                    }
                }, {
                    text: "Thickness...",
                    handler: function() {
                        var thickness = 1;
                        if (index !== undefined) {
                            thickness = plot._Gx.lyr[index].thick;
                        }
                        setupPrompt(
                            plot,
                            "Thickness",
                            mx.intValidator,
                            function(finalValue) {
                                if (finalValue === "") {
                                    finalValue = 1;
                                }
                                finalValue = Math.max(0, finalValue);

                                if (index !== undefined) {
                                    plot._Gx.lyr[index].thick = finalValue;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].thick = finalValue;
                                    }
                                }
                            }, thickness, undefined,
                            undefined, undefined);
                    }
                }, {
                    text: "Opacity...",
                    handler: function() {
                        var opacity = 1.0;
                        if (index !== undefined) {
                            opacity = plot._Gx.lyr[index].opacity;
                        }
                        setupPrompt(
                            plot,
                            "Opacity:",
                            mx.floatValidator,
                            function(finalValue) {
                                if (finalValue === "") {
                                    finalValue = 1.0;
                                }
                                finalValue = Math.max(0, finalValue);
                                finalValue = Math.min(1, finalValue);

                                if (index !== undefined) {
                                    plot._Gx.lyr[index].opacity = finalValue;
                                } else {
                                    for (var i = 0; i < Gx.lyr.length; i++) {
                                        plot._Gx.lyr[i].opacity = finalValue;
                                    }
                                }
                            }, opacity, undefined,
                            undefined, undefined);
                    }
                }]
            };
        };

        var TRACES_MENU = {
            text: "Traces...",
            menu: function() {
                var Gx = plot._Gx;
                var tracemenu = {
                    title: "TRACE",
                    items: []
                };
                // Add the ALL option
                tracemenu.items.push({
                    text: "All",
                    menu: traceoptionsmenu()
                });
                // Add all the active layers
                for (var i = 0; i < Gx.lyr.length; i++) {
                    tracemenu.items.push({
                        text: Gx.lyr[i].name,
                        menu: traceoptionsmenu(i)
                    });
                }
                return tracemenu;
            }
        };

        var FILES_MENU = {
            text: "Files...",
            menu: {
                title: "FILES OPTIONS",
                items: [
                    // Overlay File... is disabled
                    // because it's actually very
                    // difficult to bring up a
                    // a file upload browse dialog
                    /*
                     * { text: "Overlay File...", handler: function() {
                     * plot.change_settings( {phunits: 'R'} ); } },
                     */
                    {
                        text: "Deoverlay File...",
                        menu: function() {
                            var Gx = plot._Gx;
                            var deoverlaymenu = {
                                title: "DEOVERLAY",
                                items: []
                            };
                            deoverlaymenu.items.push({
                                text: "Deoverlay All",
                                handler: function() {
                                    plot.deoverlay();
                                }
                            });
			    /* jshint -W083 */
			    /* TODO figure out how to not create functions within a loop */
                            for (var i = 0; i < Gx.lyr.length; i++) {
                                var handler = (function(index) {
                                    return function() {
                                        plot.deoverlay(index);
                                    };
                                }(i));

                                deoverlaymenu.items.push({
                                    text: Gx.lyr[i].name,
                                    handler: handler
                                });
                            }
			    /* jshint +W083 */
                            return deoverlaymenu;
                        }
                    }
                ]
            }
        };

        var PLUGINS_MENU = {
            text: "Plugins...",
            menu: {
                title: "PLUGINS",
                items: (function() { // Immediately
                    // Invoked
                    // Function
                    var result = [];
                    for (var i = 0; i < Gx.plugins.length; i++) {
                        var plugin = Gx.plugins[i];
                        if (plugin.impl.menu) {
                            if (typeof plugin.impl.menu === 'function') {
                                result.push(plugin.impl.menu());
                            } else {
                                result.push(plugin.impl.menu);
                            }
                        }
                    }
                    return result;
                }())
            }
        };

        var REFRESH_ITEM = {
            text: "Refresh" // no handler, just let the finalizer deal with
            // it
        };

        var KEYPRESSINFO_ITEM = {
            text: "Keypress Info",
            handler: function() {
                mx.message(Mx, KEYPRESS_HELP);
            }
        };

        var EXIT_ITEM = {
            text: "Exit",
            handler: function() {
                var evt = document.createEvent('Event');
                evt.initEvent('sigplotexit', true, true);
                mx.dispatchEvent(Mx, evt);
            }
        };

        // Main Menu
        var MAINMENU = {
            title: "SIG-PLOT",
            finalize: function() {
                if (!Mx.prompt) {
                    // A prompt may have been
                    // created by a menu handler
                    // - let it deal with
                    // eventListener re-setting
                    mx.addEventListener(Mx, "mousedown", plot.onmousedown,
                        false);
                }
                plot.refresh();
            },
            items: [REFRESH_ITEM, CONTROLS_MENU, CXMODE_MENU, SCALING_MENU,
                GRID_MENU, SETTINGS_MENU, COLORMAP_MENU, TRACES_MENU, FILES_MENU,
                PLUGINS_MENU, KEYPRESSINFO_ITEM, EXIT_ITEM
            ]
        };

        mx.menu(Mx, MAINMENU);
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function rubberbox_cb(plot) {
        return function(event, xo, yo, xl, yl, action) {
            var Gx = plot._Gx;
            var Mx = plot._Mx;

            var x = Math.min(xo, xl);
            var y = Math.min(yo, yl);
            var w = Math.abs(xl - xo);
            var h = Math.abs(yl - yo);

            if ((action === undefined) || (action === "zoom")) {
                if (event.which === 1) {
                    // On some browsers, a click will actually be sent as
                    // mousedown/mousemove/mouseup so
                    // don't make insanely small zooms...instead treat them as a
                    // click
                    if ((w < 2) && (h < 2)) {
                        var inCenter = inPanCenterRegion(plot);
                        if (inCenter.inCenterRegion) {
                            // console.log("!!!MOUSEUP in
                            // PAN_CENTER_REGION!!!");
                            // event.preventDefault(); // TODO Necessary?
                            if (inCenter.command !== ' ') {
                                pan(plot, inCenter.command, 0, event); // pan
                            }
                        } else if (Gx.cntrls === 1) {
                            var evt = document.createEvent('Event');
                            evt.initEvent('mtag', true, true);
                            evt.x = Gx.xmrk;
                            evt.y = Gx.ymrk;
                            evt.w = undefined;
                            evt.h = undefined;
                            mx.dispatchEvent(Mx, evt);
                        }
                        return;
                    }
                    plot.pixel_zoom(xo, yo, xl, yl);
                    plot.refresh();
                }
            } else if (action === "select") {
                var evt = document.createEvent('Event');
                evt.initEvent('mtag', true, true);
                var re = pixel_to_real(plot, x, y);
                var rwh = pixel_to_real(plot, x + w, y + h);
                evt.x = re.x;
                evt.y = re.y;
                evt.w = Math.abs(rwh.x - re.x);
                evt.h = Math.abs(rwh.y - re.y);
                mx.dispatchEvent(Mx, evt);
            }
        };
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function plot_init(plot, o) {
        var Mx = plot._Mx;
        var Gx = plot._Gx;

        // Equivalent to reading cmd line args
        Gx.xmin = o.xmin === undefined ? 0.0 : o.xmin;
        Gx.xmax = o.xmax === undefined ? 0.0 : o.xmax;
        var havexmin = (o.xmin !== undefined);
        var havexmax = (o.xmax !== undefined);
        var address = o.cmode === undefined ? "" : o.cmode.toUpperCase();
        var line = o.line === undefined ? 3 : o.line;
        Gx.ylab = o.ylab;
        Gx.ymin = o.ymin === undefined ? 0.0 : o.ymin;
        Gx.ymax = o.ymax === undefined ? 0.0 : o.ymax;
        var haveymin = (o.ymin !== undefined);
        var haveymax = (o.ymax !== undefined);

        if (o.colors !== undefined) {
            mx.setbgfg(Mx, o.colors.bg, o.colors.fg, Mx.xi);
        }

        if (o.xi !== undefined) {
            mx.invertbgfg(Mx);
        }

        Gx.forcelab = o.forcelab === undefined ? true : o.forcelab;

        Gx.all = o.all === undefined ? false : o.all;
        // By default, SIGPLOT auto-scales only on the first buffer size.
        // "expand" is a feature added for websigplot that when
        // combined with "all" will expand the y-range automaticall
        // to accomodate all of the samples
        Gx.expand = o.expand === undefined ? false : o.expand;

        // TODO Gx.mimic = M$GET_SWITCH ('MIMIC')
        Gx.xlab = o.xlab;
        Gx.segment = o.segment === undefined ? false : o.segment;
        Gx.plab = 24;

        var phunits = (o.phunits === undefined) ? 'D' : o.phunits;
        if (phunits[0] === 'R') {
            Gx.plab = 23;
        } else if (phunits[0] === 'C') {
            Gx.plab = 25;
        }
        Gx.xdiv = o.xdiv === undefined ? 5 : o.xdiv;
        Gx.ydiv = o.ydiv === undefined ? 5 : o.ydiv;

        Mx.origin = 1;
        if (o.yinv) {
            Mx.origin = 4;
        }
        Gx.pmt = o.pmt === undefined ? 1.0 : o.pmt;
        Gx.bufmax = o.bufmax === undefined ? 32768 : o.bufmax;
        Gx.sections = o.nsec === undefined ? 0 : o.nsec;
        Gx.anno_type = o.anno_type === undefined ? 0 : o.anno_type;

        Gx.xfmt = o.xfmt === undefined ? "" : o.xfmt;
        Gx.yfmt = o.yfmt === undefined ? "" : o.yfmt;

        // TODO Gx.xf.msgid = M$GET_SWITCH ('MSGID')
        // Gx.xf.msgmask = max (0, M$GET_SWITCH ('MASK'))

        Gx.index = o.index === undefined ? false : o.index;
        var imode = (Gx.index || (address.slice(0, 2) === "IN"));
        if (imode) {
            if (havexmin && (Gx.xmin === 1.0)) {
                havexmin = false;
            }
            if (havexmax && (Gx.xmin === 1.0)) {
                havexmax = false;
            }
        }

        Gx.yptr = undefined;
        Gx.xptr = undefined;
        Gx.pointbufsize = 0;
        Gx.xdata = false;
        Gx.note = "";
        Gx.hold = 0;

        m.vstype('D');

        if (!o.inputs) {
            basefile(plot, false);
        } else {
            // TODO load files
        }

        var cmode = address.slice(0, 2);
        if (cmode === "IN" || cmode === "AB" || cmode === "__") {
            cmode = address.slice(2, 4);
        }

        // TODO
        if ((Gx.lyr.length > 0) && (Gx.lyr[0].cx)) {
            Gx.cmode = 1;
        } else {
            Gx.cmode = 3;
        }

        if (cmode === "MA") {
            Gx.cmode = 1;
        }
        if (cmode === "PH") {
            Gx.cmode = 2;
        }
        if (cmode === "RE") {
            Gx.cmode = 3;
        }
        if (cmode === "IM") {
            Gx.cmode = 4;
        }
        if ((cmode === "LO") || (cmode === "D1")) {
            Gx.cmode = 6;
        }
        if ((cmode === "L2") || (cmode === "D2")) {
            Gx.cmode = 7;
        }
        if ((cmode === "RI") || (cmode === "IR")) {
            if (Gx.index) {
                alert("Imag/Real mode not permitted in INDEX mode");
            } else {
                Gx.cmode = 5;
            }
        }
        Gx.basemode = Gx.cmode;
        plot.change_settings({
            cmode: Gx.cmode
        });

        // if ( (Gx.forcelab) .and. (Gx.xlab .le. 0) .and.
        // & (Gx.ylab .le. 0) ) then
        // call M$WARNING
        // & ('/xlab or /ylab is missing with /forcelab usage')
        // Gx.forcelab = .false.
        // endif

        Gx.dbmin = 1.0e-20;
        if (Gx.cmode >= 6) {
            var dbscale = 10.0;
            if (Gx.cmode === 7) {
                dbscale = 20.0;
            }
            if (cmode[0] === "L") {
                if ((Gx.lyr.length > 0) && (Gx.lyr[0].cx)) {
                    Gx.ymin = Math.max(Gx.ymin, 1e-10);
                    Gx.ymax = Math.max(Gx.ymax, 1e-10);
                } else {
                    Gx.ymin = Math.max(Gx.ymin, 1e-20);
                    Gx.ymax = Math.max(Gx.ymax, 1e-20);
                }
            } else if ((Gx.lyr.length > 0) && (Gx.lyr[0].cx)) {
                Gx.ymin = Math.max(-18.0 * dbscale, Gx.ymin);
                Gx.ymax = Math.max(-18.0 * dbscale, Gx.ymax);
                Gx.dbmin = 1e-37;
            } else if (Math.min(Gx.ymin, Gx.ymax) < -20.0 * dbscale) {
                Gx.ymin = Math.max(-37.0 * dbscale, Gx.ymin);
                Gx.ymax = Math.max(-37.0 * dbscale, Gx.ymax);
                Gx.dbmin = Math.pow(10, Math.min(Gx.ymin, Gx.ymax) / dbscale);
            }
        }

        Mx.level = 0;
        if (imode && !Gx.index) {
            if (havexmin) {
                Gx.xmin = Gx.xstart + Gx.xdelta * (Gx.xmin - 1.0);
            }
            if (havexmin) {
                Gx.xmax = Gx.xstart + Gx.xdelta * (Gx.xmax - 1.0);
            }
        }
        Gx.autox = o.autox === undefined ? -1 : o.autox;
        if (Gx.autox < 0) {
            Gx.autox = 0;
            if (!havexmin) {
                Gx.autox += 1;
            }
            if (!havexmax) {
                Gx.autox += 2;
            }
        }
        Gx.autoy = o.autoy === undefined ? -1 : o.autoy;
        if (Gx.autoy < 0) {
            Gx.autoy = 0;
            if (!haveymin) {
                Gx.autoy += 1;
            }
            if (!haveymax) {
                Gx.autoy += 2;
            }
        }
        Gx.autol = o.autol === undefined ? -1 : o.autol;

        if (!havexmin) {
            Gx.xmin = undefined;
        }
        if (!havexmax) {
            Gx.xmax = undefined;
        }
        scale_base(plot, {
            get_data: true
        }, Gx.xmin, Gx.xmax, Gx.xlab, Gx.ylab);

        if (!havexmin) {
            Gx.xmin = Mx.stk[0].xmin;
        }
        if (!havexmax) {
            Gx.xmax = Mx.stk[0].xmax;
        }
        if (!haveymin) {
            Gx.ymin = Mx.stk[0].ymin;
        }
        if (!haveymax) {
            Gx.ymax = Mx.stk[0].ymax;
        }

        if (Gx.xmin > Gx.xmax) {
            Mx.stk[0].xmin = Gx.xmax;
            Gx.xmax = Gx.xmin;
            Gx.xmin = Mx.stk[0].xmin;
        }
        if (Gx.ymin > Gx.ymax) {
            Mx.stk[0].ymin = Gx.ymax;
            Gx.ymax = Gx.ymin;
            Gx.ymin = Mx.stk[0].ymin;
        }
        Mx.stk[0].xmin = Gx.xmin;
        Mx.stk[0].xmax = Gx.xmax;
        Mx.stk[0].ymin = Gx.ymin;
        Mx.stk[0].ymax = Gx.ymax;
        Gx.panxmin = Math.min(Gx.panxmin, Gx.xmin);
        Gx.panxmax = Math.max(Gx.panxmax, Gx.xmax);
        Gx.panymin = Math.min(Gx.panymin, Gx.ymin);
        Gx.panymax = Math.max(Gx.panymax, Gx.ymax);

        Gx.xmin = Mx.stk[0].xmin;
        Gx.ymin = Mx.stk[0].ymin;

        mx.set_font(Mx, Math.min(7, Mx.width / 64));

        Gx.ncolors = o.ncolors === undefined ? 16 : o.ncolors;
        Gx.cmap = o.xc === undefined ? -1 : o.xc;
        if (Gx.ncolors < 0) {
            Gx.ncolors = -1 * Gx.ncolors;
            Gx.cmap = Math.max(1, Gx.cmap);
        }
        if ((Gx.cmap < 1) || (Gx.cmap > 5)) {
            if (Gx.cmode === 2) {
                Gx.cmap = 2; // wheel
            } else {
                Gx.cmap = 1; // ramp
            }
        }

        mx.colormap(Mx, m.Mc.colormap[Gx.cmap], Gx.ncolors);


        // TODO setup annotate, boxes and points facilities

        // TODO initialize layer structure line types

        if (o.xcnt === "leftmouse") {
            Gx.cntrls = 1;
        } else if (o.xcnt === "continuous") {
            Gx.cntrls = 2;
        } else {
            Gx.cntrls = o.xcnt === undefined ? 1 : o.xcnt;
        }

        Gx.default_rubberbox_mode = o.rubberbox_mode === undefined ? "box" : o.rubberbox_mode;
        Gx.default_rubberbox_action = o.rubberbox_action === undefined ? "zoom" : o.rubberbox_action;

        Gx.cross = o.cross === undefined ? false : o.cross;
        Gx.grid = o.nogrid === undefined ? true : !o.nogrid;
        Gx.gridBackground = o.gridBackground;
        Gx.gridStyle = o.gridStyle;
        Gx.wheelZoom = o.wheelZoom;
        Gx.wheelZoomPercent = o.wheelZoomPercent;
        Gx.legend = o.legend === undefined ? false : o.legend;
        Gx.legendBtnLocation = {
            x: 0,
            y: 0,
            width: 0,
            height: 0
        };
        Gx.pan = o.nopan === undefined ? true : !o.nopan;
        Gx.nomenu = o.nomenu === undefined ? false : o.nomenu;

        // TODO Gx.lmap.ip = 0
        Gx.modmode = 0;
        Gx.modlayer = -1; // 0-based indexing instead of 1
        Gx.modsource = 0;
        Gx.modified = (o.mod && Gx.lyr.length > 0);
        // TODO Gx.marks(5) = 5
        Gx.nmark = 0;
        Gx.iabsc = 0;
        if (Gx.index) {
            Gx.iabsc = 1;
        }
        // TODO if (o.specs > 0) Gx.iabsc = M$SEARCH('IRS',c(1:1))
        Gx.specs = !o.nospecs;

        Gx.scroll_time_interval = o.scroll_time_interval === undefined ? Gx.scroll_time_interval : o.scroll_time_interval;

        Gx.autohide_readout = o.autohide_readout;
        Gx.autohide_panbars = o.autohide_panbars;
        if (Gx.specs) {
            Gx.show_x_axis = !o.noxaxis;
            Gx.show_y_axis = !o.noyaxis;
            Gx.show_readout = !o.noreadout;
            if (Gx.show_x_axis || Gx.show_y_axis || Gx.show_readout) {
                Gx.specs = true;
            } else {
                Gx.specs = false;
            }
        } else {
            Gx.show_x_axis = false;
            Gx.show_y_axis = false;
            Gx.show_readout = false;
        }
        Gx.hide_note = o.hide_note || false;

        Gx.xmrk = 0.0;
        Gx.ymrk = 0.0;

        if (!o.nodragdrop) {
            mx.addEventListener(Mx, "dragover", function(evt) {
                evt.preventDefault();
            }, false);

            mx.addEventListener(Mx, "drop", (function(plot) {
                return function(evt) {
                    var files = evt.dataTransfer.files;
                    if (files.length > 0) {
                        evt.preventDefault();
                        plot.load_files(files);
                    }
                };
            }(plot)), false);
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function basefile(plot, open) {
        var Gx = plot._Gx;
        var Mx = plot._Mx;
        // != BASEFILE(false)

        // unlike SIGPLOT, where if Gx.index == 1
        // then xstart = 1.0 and xdelta = 1.0...technically
        // Gx.retx is supposed to be the real X coordinate
        // and Gx.aretx is supposed to be the X coordinate in the
        // current abscissa mode
        if (open) {
            var hcb = Gx.HCB[0];
            Gx.xstart = hcb.xstart;
            Gx.xdelta = hcb.xdelta;
            Mx.origin = 1;
        } else {
            Gx.xstart = 0.0;
            Gx.xdelta = 1.0;
            Gx.autol = -1;
            Gx.zmin = undefined;
            Gx.zmax = undefined;
            Mx.origin = 1;
        }

        // if (!open) {
        // Gx.lay[0].cx = false;
        // }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function draw_accessories(plot, mode) {
        var Mx = plot._Mx;
        var Gx = plot._Gx;
        if (mode > 0) {
            if ((mode >= 4) && (Gx.show_readout) && (!Gx.hide_note)) {
                var ln = Gx.note.length;
                mx.text(Mx, Mx.width - Gx.lbtn - (ln + 1) * Mx.text_w,
                    Mx.text_h, Gx.note);
            }
            if (mode >= 4) {
                draw_panbars(plot);
            }
            if ((mode >= 1) && (Gx.legend)) {
                draw_legend(plot);
            }
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function draw_plugins(plot) {
        var Gx = plot._Gx;
        var ctx = plot._Mx.canvas.getContext("2d");
	var canvas;

        var plugin_index = 0;
        while (plugin_index < Gx.plugins.length) {
            var plugin = Gx.plugins[plugin_index].impl;
            if (plugin.refresh) {
                canvas = Gx.plugins[plugin_index].canvas;
                canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height);
                Gx.plugins[plugin_index].impl.refresh(canvas);
                ctx.drawImage(canvas, 0, 0);
            }
            plugin_index = plugin_index + 1;
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function draw_legend(plot) {
        var Mx = plot._Mx;
        var Gx = plot._Gx;
        var ctx = Mx.canvas.getContext("2d");

        var n = 0; // integer*4
        var ix = 0; // integer*4
        var iy = 0; // integer*4
        var ln = 0; // integer*4
        var tw = 0; // integer*4
        var xc = 0; // integer*4
        var yc = 0; // integer*4
        var xs = 0; // integer*4
        var ys = 0; // integer*4
        var thk = 0; // integer*4
        var ic = 0; // integer*4

        tw = Mx.text_w;
        xs = tw * 23;
        ys = (Gx.lyr.length + 1) * Mx.text_h;
        xc = Mx.r - xs;
        yc = Mx.t;

        var legendPos = {
            x: xc + 2,
            y: yc + 2,
            width: xs - 5,
            height: ys - 5
        }; // default legend size

        // Determine legend position and label offset based on label sizes
        var defLabelWidth = 98; // a magic number - default width of pixels
        var maxLabelWidth = 0;
        var labelOffset = 0;
        for (n = 0; n < Gx.lyr.length; n++) { // figure out maximum label
            // length
            var labelLength = ctx.measureText(Gx.lyr[n].name).width;
            if (labelLength > maxLabelWidth) {
                maxLabelWidth = labelLength;
            }
        }
        if (maxLabelWidth > defLabelWidth) {
            labelOffset = (maxLabelWidth - defLabelWidth);
            legendPos.width += labelOffset;
            legendPos.x -= labelOffset;
        }

        ctx.strokeStyle = Mx.fg; // Mx.xwfg swapped in for FGColor
        ctx.fillStyle = Mx.bg;
        ctx.fillRect(legendPos.x, legendPos.y, legendPos.width,
            legendPos.height); // Creating a filled box instead of using
        // clear_area
        ctx.strokeRect(legendPos.x, legendPos.y, legendPos.width,
            legendPos.height);

        for (n = 0; n < Gx.lyr.length; n++) {
            ix = xc + 4 * tw;
            iy = yc + n * Mx.text_h + Mx.text_h; // additional text_h to
            // account for 0-based
            // indexing
            if (n === Gx.modlayer) {
                mx.text(Mx, xc + tw - labelOffset, iy + Math.floor(Mx.text_w / 2), '**'); // Added text_w/2
                // offset
            }
            if (Gx.lyr[n].display) {
                ic = Gx.lyr[n].color;
                if (Gx.lyr[n].line > 0) {
                    thk = m.sign(Math.min(tw, Math.abs(Gx.lyr[n].thick)),
                        Gx.lyr[n].thick);
                    // added magic -3 offset to y coordinates to center lines
                    // with text
                    if (thk < 0 || thk === mx.L_dashed) {
                        mx.draw_line(Mx, ic, ix - labelOffset, iy - 3, (ix + tw * 2) - labelOffset, iy - 3, Math.abs(thk), {
                            mode: "dashed",
                            on: 4,
                            off: 4
                        });
                    } else {
                        mx.draw_line(Mx, ic, ix - labelOffset, iy - 3, (ix + tw * 2) - labelOffset, iy - 3, Math.abs(thk));
                    }
                }
                if (Gx.lyr[n].symbol > 0) {
                    // New logic here with 0.6*tw to help with legend symbol
                    // sizing
                    if (Gx.lyr[n].radius < 0) {
                        thk = -m.trunc(0.6 * tw);
                    } else {
                        thk = Math.min(Gx.lyr[n].radius, m.trunc(0.6 * tw));
                    }

                    mx.draw_symbol(Mx, ic, ix + tw - labelOffset, iy - 3,
                        Gx.lyr[n].symbol, thk);
                }
            }
            ix = ix + tw * 3;
            iy = iy + Mx.text_h * 0.3;
            mx.text(Mx, ix - labelOffset, iy, Gx.lyr[n].name);
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function form_plotnote(plot) {
        var Gx = plot._Gx;
        if (Gx.HCB.length === 0) {
            Gx.note = "";
        } else if (Gx.HCB[0].plotnote === undefined) {
            var files = [];
            for (var n = 0; n < Gx.HCB.length; n++) {
                if (Gx.HCB[n].file_name) {
                    files.push(Gx.HCB[n].file_name);
                }
            }
            Gx.note = files.join("|").toUpperCase();
        }
    }

    /**
     * Draws the specified layer.
     *
     * This is expected to be called after clearing the plot.
     *
     * @param {sigplot.Plot}
     *            the plot object
     * @param {Number}
     *            the layer to draw
     * @private
     * @memberOf sigplot
     */
    function draw_layer(plot, n) {
        var Mx = plot._Mx;
        var Gx = plot._Gx;

        if ((n >= Gx.lyr.length) || (!Gx.lyr[n].display) || (Gx.hold !== 0)) {
            return;
        }

        Gx.lyr[n].draw();
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function delete_layer(plot, n) {
        var Gx = plot._Gx;
        //if (n < Gx.modlayer) Gx.modlayer = Gx.modlayer - 1;
        //if (n < Gx.modsource) Gx.modsource = Gx.modsource - 1;
        var topbs;
        if (Gx.lyr[n].display) { topbs = n; }
        Gx.lyr[n].ybufn = 0;
        Gx.lyr[n].ybuf = null;
        if (n < Gx.lyr.length - 1) {
            var lyr = Gx.lyr[n];
            for (var i = n; i < Gx.lyr.length - 1; i++) {
                Gx.lyr[i] = Gx.lyr[i + 1];
            }
        }
        Gx.lyr.length -= 1;

        if (Gx.HCB.length > 0) {
            Gx.panxmin = 1.0;
            Gx.panxmax = -1.0;
            Gx.panymin = 1.0;
            Gx.panymax = -1.0;
        }
        //Gx.yptr = undefined;
        //Gx.xptr = undefined;
        //Gx.pointbufsize = 0;
        //Gx.xptr = undefined; // xpoints ArrayBuffer
        //Gx.yptr = undefined; // ypoints ArrayBuffer
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function draw_crosshairs(plot) {
        var Gx = plot._Gx;
        var Mx = plot._Mx;

        if (Gx.cross) {
            if ((Gx.cross === "vertical") || (Gx.cross === true)) {
                if ((Mx.xpos >= Mx.l) && (Mx.xpos <= Mx.r) && (Gx.cross_xpos !== Mx.xpos)) {
                    if (Gx.cross_xpos !== undefined) {
                        mx.rubberline(Mx, Gx.cross_xpos, Mx.t, Gx.cross_xpos, Mx.b);
                    }
                    mx.rubberline(Mx, Mx.xpos, Mx.t, Mx.xpos, Mx.b);
                    Gx.cross_xpos = Mx.xpos;
                }
            }
            if ((Gx.cross === "horizontal") || (Gx.cross === true)) {
                if ((Mx.ypos >= Mx.t) && (Mx.ypos <= Mx.b) && (Gx.cross_ypos !== Mx.ypos)) {
                    if (Gx.cross_ypos !== undefined) {
                        mx.rubberline(Mx, Mx.l, Gx.cross_ypos, Mx.r, Gx.cross_ypos);
                    }

                    mx.rubberline(Mx, Mx.l, Mx.ypos, Mx.r, Mx.ypos);
                    Gx.cross_ypos = Mx.ypos;
                }
            }
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function changephunits(plot, newphunits) {
        var Gx = plot._Gx;
        var Mx = plot._Mx;
        var newplab = Gx.plab;
        if (newphunits === 'R') {
            newplab = 23;
        } else if (newphunits === 'D') {
            newplab = 24;
        }
        if (newphunits === 'C') {
            newplab = 25;
        }
        if (newplab !== Gx.plab) {
            var phscale = [Math.PI, 180.0, 0.5];
            var dscl = phscale[newplab - 23] / phscale[Gx.plab - 23];
            Gx.plab = newplab;
            if (Gx.cmode === 2) {
                for (var i = 0; i <= Mx.level; i++) {
                    Mx.stk[i].ymin = Mx.stk[i].ymin * dscl;
                    Mx.stk[i].ymax = Mx.stk[i].ymax * dscl;
                    Mx.stk[i].yscl = Mx.stk[i].yscl * dscl;
                }

                Gx.panymin = Gx.panymin * dscl;
                Gx.panymax = Gx.panymax * dscl;
                plot.refresh();
            }
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function changemode(plot, newmode) {
        var Mx = plot._Mx;
        var Gx = plot._Gx;

        Gx.xdata = false;
        for (var n = 0; n < Gx.lyr.length; n++) {
            if (newmode === 5) {
                Gx.lyr[n].xdata = true;
            } else {
                Gx.lyr[n].xdata = false; // TODO (Gx.lyr(n).xsub > 0)
            }
            if (Gx.lyr[n].xdata) {
                Gx.xdata = true;
	    }
        }

        if (newmode === Gx.cmode) {
            return;
        } else if (newmode === 5 && Gx.index) {
            alert("Imag/Real mode not permitted in INDEX mode");
        } else if (Gx.lyr.length <= 0) {
            Gx.cmode = newmode;
            // The call to display specs isn't found in sigplot.for;
            // which causes a small bug where the plot mode is
            // changed but the mode label in the specs area
            // isn't updated.
            display_specs(plot);
        } else if (newmode > 0) {
            var oldmode = Gx.cmode;
            Gx.cmode = newmode;

            var autox = Gx.autox;
            var autoy = Gx.autoy;
            Gx.autox = 3;
            Gx.autoy = 3;

            if ((newmode === 5) || (oldmode === 5)) {
                Gx.panxmin = 1.0;
                Gx.panxmax = -1.0;
                Gx.panymin = 1.0;
                Gx.panymax = -1.0;
                Mx.level = 0;

                if (newmode === Gx.basemode) {
                    Mx.stk[0].xmin = Gx.xmin;
                    Mx.stk[0].xmax = Gx.xmax;
                    Mx.stk[0].ymin = Gx.ymin;
                    Mx.stk[0].ymax = Gx.ymax;
                } else if ((newmode === 5) || (Gx.basemode === 5)) {
                    scale_base(plot, {
                        get_data: true
                    });
                } else {
                    Mx.stk[0].xmin = Gx.xmin;
                    Mx.stk[0].xmax = Gx.xmax;
                    scale_base(plot, {
                        get_data: true
                    }, Gx.xmin, Gx.xmax);
                }
            } else {
                if (newmode === Gx.basemode) { // This is only correct if we
                    // didn't load a basefile
                    Gx.panymin = 1.0;
                    Gx.panymax = -1.0;
                    Mx.stk[0].ymin = Gx.ymin;
                    Mx.stk[0].ymax = Gx.ymax;
                } else {
                    scale_base(plot, {}, Mx.stk[Mx.level].xmin,
                        Mx.stk[Mx.level].xmax);
                }
                for (var n = 1; n <= Mx.level; n++) {
                    Mx.stk[n].ymin = Mx.stk[0].ymin;
                    Mx.stk[n].ymax = Mx.stk[0].ymax;
                }
            }
            Gx.autox = autox;
            Gx.autoy = autoy;
            plot.refresh();
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function draw_panbars(plot) {
        var k; // integer*4
        //var i1; // integer*4
        //var itext; // integer*4

        var Mx = plot._Mx;
        var Gx = plot._Gx;

        if ((!Gx.pan) || (Mx.widget)) {
            return;
	}

        k = Mx.level; // Y scrollbar

        var out = {
            ps: Mx.stk[k].ymin,
            pe: Mx.stk[k].ymax
        };
        var need_y_scrollbar = ((out.ps !== Gx.panymin) || (out.pe !== Gx.panymax));
        need_y_scrollbar = need_y_scrollbar && (Mx.level > 0);

        if (Gx.autohide_panbars && (!need_y_scrollbar || !plot.mouseOnCanvas) && !Gx.panning) {
            var ctx = Mx.canvas.getContext("2d");
            ctx.fillStyle = Mx.bg;
            ctx.fillRect(Gx.pyl, Mx.t, Gx.pyl + Gx.pthk, Mx.b - Mx.t);
        } else {
            var i1 = mx.scrollbar(Mx, 0, Gx.pyl, Gx.pyl + Gx.pthk, Mx.t, Mx.b, out,
                Gx.panymin, Gx.panymax, undefined, Mx.scrollbar_y);
            Mx.stk[k].ymin = out.ps;
            Mx.stk[k].ymax = out.pe;
        }

        if (Gx.pl < Mx.width) { // X scrollbar
            out = {
                ps: Mx.stk[k].xmin,
                pe: Mx.stk[k].xmax
            };
            var need_x_scrollbar = ((out.ps !== Gx.panxmin) || (out.pe !== Gx.panxmax));
            need_x_scrollbar = need_x_scrollbar && (!Gx.all || (Mx.level > 0));

            if (Gx.autohide_panbars && (!need_x_scrollbar || !plot.mouseOnCanvas) && !Gx.panning) {
                var ctx = Mx.canvas.getContext("2d");
                ctx.fillStyle = Mx.bg;
                ctx.fillRect(Gx.pl, Gx.pt - 1, Gx.pr - Gx.pl, Gx.pthk + 4);
            } else {
                var i1 = mx.scrollbar(Mx, 0, Gx.pl, Gx.pr, Gx.pt, Gx.pt + Gx.pthk, out,
                    Gx.panxmin, Gx.panxmax, undefined, Mx.scrollbar_x);
                Mx.stk[k].xmin = out.ps;
                Mx.stk[k].xmax = out.pe;
            }
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function pan(plot, action, flag, mouseEvent) {
        var Mx = plot._Mx;
        var Gx = plot._Gx;

        var i; // an integer*4
        var k; // an integer*4
        // var ih // an integer*4
        // var iw; // an integer*4
        // var imin; // an integer*4
        // var imax; // an integer*4
        var j; // an integer*4
        var xmin; // a real*8
        var xmax; // a real*8
        var xran; // a real*8
        // var xtemp; // a real*8
        var ymin; // a real*8
        var ymax; // a real*8
        var yran; // a real*8
        // var ytemp; // a real*8
        var warn; // a logical*4

        var scrollbarState;

        var sbx = new mx.SCROLLBAR(); // a SCROLLBAR
        var sby = new mx.SCROLLBAR(); // a SCROLLBAR

        var SIGPLOT_PAN = false;
        k = Mx.level;
        if (Gx.panmode > 0) {
            sbx.flag = 11;
            sby.flag = 11;
        } else {
            sbx.flag = -12;
            sby.flag = -12;
        }
        if (flag === 0) {
            sbx.action = 0;
            sby.action = 0;
        }

        warn = true;
        if (action.substring(0, 1) === 'Y') {
            ymin = Mx.stk[k].ymin;
            ymax = Mx.stk[k].ymax;
            yran = ymax - ymin;
            if (action === 'YPAN') {
                scrollbarState = Mx.scrollbar_y;

                var out = {
                    ps: ymin,
                    pe: ymax
                };
                i = mx
                    .scrollbar(Mx, sby, Gx.pyl, Gx.pyl + Gx.pthk, Mx.t,
                        Mx.b, out, Gx.panymin, Gx.panymax, mouseEvent,
                        scrollbarState);
                ymin = out.ps;
                ymax = out.pe;
                // TODO: Warn only if Scrollbar arrow is pressed and no
                // movement.
                if (sby.action !== 0) {
                    j = mx.scroll(Mx, sby, mx.XW_UPDATE, undefined,
                        scrollbarState);
                }
                warn = false;
            } else if (action === 'YCENTER') {
                // Orig code : ymin = ymin - yran * (Mx.ypos - (Mx.t + Mx.b) /
                // 2) / (Mx.b - Mx.t) // TODO Worry about any int division here?
                ymin = ymin - yran * (Mx.ypos - (Mx.t + Mx.b) / 2) / (Mx.b - Mx.t);
                ymax = ymin + yran;
                warn = false;
            }

            if (ymin !== Mx.stk[k].ymin || ymax !== Mx.stk[k].ymax) {
                Mx.stk[k].ymin = ymin;
                Mx.stk[k].ymax = ymax;
                if (Gx.cmode === Gx.basemode && Mx.level === 1) {
                    Gx.ymin = Math.min(Gx.ymin, ymin);
                    Gx.ymax = Math.max(Gx.ymax, ymax);
                }
                plot.refresh();
                // MSGDO(MSK_PANY, Mx.level); // just sets plotinfo.xmin and
                // xmax into the MQD for the menu
                SIGPLOT_PAN = true;
            }
            // TODO Later - Implement a messagebox status method
            // else if (warn) {
            // mx.message('All panned out', -1.0);
            // }
        } else {
            xmin = Mx.stk[k].xmin;
            xmax = Mx.stk[k].xmax;
            xran = xmax - xmin;
            if (action === 'XPAN') {
                scrollbarState = Mx.scrollbar_x;

                var out = {
                    ps: xmin,
                    pe: xmax
                };
                i = mx
                    .scrollbar(Mx, sbx, Gx.pl, Gx.pr, Gx.pt, Gx.pt + Gx.pthk, out, Gx.panxmin, Gx.panxmax,
                        mouseEvent, scrollbarState);
                xmin = out.ps;
                xmax = out.pe;
                // TODO: Warn only if Scrollbox arrow is pressed and no
                // movement.
                if (sbx.action !== 0) {
                    j = mx.scroll(Mx, sbx, mx.XW_UPDATE, undefined,
                        scrollbarState);
                }
                warn = false;
            } else if (action === 'XCENTER') {
                // Original code : xmin = xmin + xran * (Mx.xpos - (Mx.l + Gx.r)
                // / 2) / (Mx.r - Mx.l) // TODO Worry about any int division
                // here?
                xmin = xmin + xran * (Mx.xpos - (Mx.l + Mx.r) / 2) / (Mx.r - Mx.l);
                if (xmin !== Mx.stk[k].xmin) {
                    xmax = xmin + xran;
		}
                warn = false;
            }

            if (Mx.stk[k].xmin !== xmin || Mx.stk[k].xmax !== xmax) {
                Mx.stk[k].xmin = xmin;
                Mx.stk[k].xmax = xmax;
                if (!Gx.xdata && Mx.level === 1) {
                    Gx.xmin = Mx.stk[1].xmin;
                    Gx.xmax = Mx.stk[1].xmax;
                }
                plot.refresh();
                // MSGDO (MSK_PANX, Mx.level); // just sets plotinfo.xmin and
                // xmax into the MQD for the menu
                SIGPLOT_PAN = true;
            }
            // TODO Later - Implement a messagebox status method
            // else if (warn) {
            // mx.message('All panned out', -1.0);
            // }
        }

        return SIGPLOT_PAN;
    }

    /**
     * Direct method to handle the dragging of a scrollbar.
     *
     * @param plot
     *            The plot to work with.
     * @param scrollAction
     *            The scroll action being performed. Either "YPAN" or "XPAN" are
     *            accepted.
     * @param event
     *            The mouse move event.
     * @private
     * @memberOf sigplot
     */
    function drag_scrollbar(plot, scrollAction, event) {
        var Mx = plot._Mx;
        var Gx = plot._Gx;
        var min;
        var max;

        // ----- Retrieve appropriate SCROLLBAR -----
        var scrollbar;
        if (scrollAction === "XPAN") {
            scrollbar = plot._Mx.scrollbar_x;
        } else if (scrollAction === "YPAN") {
            scrollbar = plot._Mx.scrollbar_y;
        } else {
            throw "Unable to drag scrollbar - scrollAction is not 'XPAN' or 'YPAN'!!";
        }

        // ----- OLD SIGPLOT.PAN Logic -----
        scrollbar.flag = -12; // TODO Necessary?
        var k = Mx.level;
        if (scrollAction === "XPAN") {
            min = Mx.stk[k].xmin;
            max = Mx.stk[k].xmax;
        } else if (scrollAction === "YPAN") {
            min = Mx.stk[k].ymin;
            max = Mx.stk[k].ymax;
        } else {
            min = undefined;
            max = undefined;
        }

        // ----- MX.SCROLLBAR Logic -----
        var rangeOut = {
            "min": min,
            "max": max
        };
        drag_updateRange(Mx, Gx, scrollbar, scrollAction, rangeOut, event);
        min = rangeOut.min;
        max = rangeOut.max;

        // ----- UPDATE SCROLLBAR -----
        scrollbar.smin = min;
        scrollbar.srange = max - min;
        mx.redrawScrollbar(scrollbar, Mx, undefined);

        // ----- Update the viewbox -----
        updateViewbox(plot, scrollbar.smin, scrollbar.smin + scrollbar.srange,
            scrollAction.slice(0, 1));

        scrollbar.action = 0; // TODO New step - reset action of the scrollbar
        // after drag is done...
        plot.refresh();
    }

    /**
     * Method to update plot range based on a drag event. Takes the mouse offset
     * introduced by the drag and adds a scale factor.
     *
     * @param Gx
     *            The GX Context to work with.
     * @param scrollbar
     *            The Scrollbar to use.
     * @param scrollAction
     *            The scroll action being performed. Either "YPAN" or "XPAN" are
     *            accepted.
     * @param range
     *            The plot' min and max range values to update.
     * @param event
     *            The mouse move event.
     *
     * @private
     * @memberOf sigplot
     */
    function drag_updateRange(Mx, Gx, scrollbar, scrollAction, range, event) {
        scrollbar.action = mx.SB_DRAG;

        if (scrollAction === "YPAN") {
            var scaleFactor = Mx.scrollbar_y.trange / Mx.scrollbar_y.h;
            if (scrollbar.origin === 4) { // inverted y
                scaleFactor *= -1;
            }
            var mouseOffset = event.screenY - Gx.panning.ypos;
            var realOffset = mouseOffset * scaleFactor;

            if ((Gx.panning.ymin - realOffset) < Gx.panymin) { // At the left
                // edge
                range.max = Gx.panymin + (range.max - range.min);
                range.min = Gx.panymin;
            } else if ((Gx.panning.ymax - realOffset) > Gx.panymax) { // At
                // the
                // right
                // edge
                range.min = Gx.panymax - (range.max - range.min);
                range.max = Gx.panymax;
            } else {
                range.min = Gx.panning.ymin - realOffset;
                range.max = Gx.panning.ymax - realOffset;
            }
        } else if (scrollAction === "XPAN") {
            var scaleFactor = Mx.scrollbar_x.trange / Mx.scrollbar_x.w;
            if (scrollbar.origin === 3) { // inverted x
                scaleFactor *= -1;
            }
            var mouseOffset = event.screenX - Gx.panning.xpos;
            var realOffset = mouseOffset * scaleFactor;

            if ((Gx.panning.xmin + realOffset) < Gx.panxmin) { // At the left
                // edge
                range.max = Gx.panxmin + (range.max - range.min);
                range.min = Gx.panxmin;
            } else if ((Gx.panning.xmax + realOffset) > Gx.panxmax) { // At
                // the
                // right
                // edge
                range.min = Gx.panxmax - (range.max - range.min);
                range.max = Gx.panxmax;
            } else {
                range.min = Gx.panning.xmin + realOffset;
                range.max = Gx.panning.xmax + realOffset;
            }
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function setupPrompt(plot, promptText, isValid, onSuccess, inputValue,
        xpos, ypos, callback) {
        var Mx = plot._Mx;

        if (Mx.prompt) {
            throw "Prompt already exists! Can only have one prompt at a time!";
        }

        // Disable Mx keypress/mouse listeners
        mx.disableListeners(Mx);

        // Disable Plot keypress/mouse listeners
        plot.disable_listeners();

        // Add on to the onSuccess method with plot specifics
        var realOnSuccess = function(plot, onSuccess) {
            return function(value) {
                onSuccess(value);

                // Re-enable Mx keypress/mouse listeners
                mx.enableListeners(Mx);

                // Re-enable Plot keypress/mouse listeners
                plot.enable_listeners();

                plot.refresh();

                if (callback !== undefined) {
                    callback();
                }
            };
        };

        var refresh = function() { // TODO Refactor this setup method to be
            // more like mx.menu/main menu widget?
            plot.refresh();
        };

        // Create the prompt
        var errorMessageTimeout = 5000;

        try {
            mx.prompt(Mx, promptText, isValid, realOnSuccess(plot, onSuccess),
                refresh, inputValue, xpos, ypos, errorMessageTimeout);
        } catch (err) {
            console.log("ERROR: Failed to set up prompt due to: " + err);
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function enable_listeners(plot) {
        var Mx = plot._Mx;
        mx.addEventListener(Mx, "mousedown", plot.onmousedown, false);
        mx.addEventListener(Mx, "mousemove", plot.throttledOnMouseMove, false);
        document.addEventListener("mouseup", plot.docMouseUp, false);
        mx.addEventListener(Mx, "mouseup", plot.mouseup, false);
        window.addEventListener("mousedown", plot.dragMouseDownHandler, false);
        window.addEventListener("mousemove", plot.throttledDragOnMouseMove, false);
        window.addEventListener("mouseup", plot.dragMouseUpHandler, false);
        window.addEventListener("wheel", plot.wheelHandler, false);
        window.addEventListener("mousewheel", plot.wheelHandler, false);
        window.addEventListener("DOMMouseScroll", plot.wheelHandler, false);
        window.addEventListener("keypress", plot.onkeypress, false);
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function disable_listeners(plot) {
        var Mx = plot._Mx;

        mx.removeEventListener(Mx, "mousedown", plot.onmousedown, false);
        mx.removeEventListener(Mx, "mousemove", plot.throttledOnMouseMove,
            false);
        document.removeEventListener("mouseup", plot.docMouseUp, false);
        mx.removeEventListener(Mx, "mouseup", plot.mouseup, false);
        window.removeEventListener("mousedown", plot.dragMouseDownHandler,
            false);
        window.removeEventListener("mousemove", plot.throttledDragOnMouseMove,
            false);
        window.removeEventListener("mouseup", plot.dragMouseUpHandler, false);
        window.removeEventListener("wheel", plot.wheelHandler, false);
        window.removeEventListener("mousewheel", plot.wheelHandler, false);
        window.removeEventListener("DOMMouseScroll", plot.wheelHandler, false);
        window.removeEventListener("keypress", plot.onkeypress, false);
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function display_specs(plot) {
        var Mx = plot._Mx;
        var Gx = plot._Gx;

        var ctx = Mx.canvas.getContext("2d");

        // section logic
        if (Gx.sections !== 0) {
            // TODO
        } else {
            Gx.isec = 0;
        }

        // transform into realworld coordinates
        // is already done by the mousemove listener
        // adjust for abscissa mode
        Gx.aretx = Gx.retx;
        Gx.arety = Gx.rety;
        Gx.dretx = Gx.retx - Gx.xmrk;
        Gx.drety = Gx.rety - Gx.ymrk;

        if ((Gx.cmode === 5) && (Gx.iabsc === 1)) {
            Gx.iabsc = 2;
        } // R/I mode
        if (Gx.iabsc === 1) { // index
            Gx.aretx = Math.round((Gx.aretx - Gx.xstart) / Gx.xdelta);
            if (!Gx.index) {
                Gx.aretx += 1;
            }
            Gx.dretx = Math.round(Gx.dretx / Gx.xdelta);
        } else if (Gx.iabsc === 2) { // 1/absc
            if (Gx.aretx !== 0.0) {
                Gx.aretx = 1.0 / Gx.aretx;
	    }
            if (Gx.arety !== 0.0) {
                Gx.arety = 1.0 / Gx.arety;
	    }
            if (Gx.dretx !== 0.0) {
                Gx.dretx = 1.0 / Gx.dretx;
	    }
            if (Gx.drety !== 0.0) {
                Gx.drety = 1.0 / Gx.drety;
	    }
        }

        if ((!Gx.show_readout) || (Mx.widget)) {
            return;
        }

        // Clear the specs area
        ctx.fillStyle = Mx.bg;
        var iy = Math.floor(Mx.height - 2.5 * Mx.text_h);
        ctx.fillRect(Mx.text_w, iy, 49 * Mx.text_w, iy + 1.5 * Mx.text_h);

        iy = Math.floor(Mx.height - 0.5 * Mx.text_h);
        var k = Math.max(Gx.pr + Mx.text_w, Mx.width - Mx.text_w * 2);
        ctx.fillStyle = Mx.bg;
        ctx.fillRect(k, iy - Mx.text_h, Mx.text_w, Mx.text_h);

        if (Gx.autohide_readout && !plot.mouseOnCanvas && !Gx.panning) {
            return;
        }

        var chara = "y: " + mx.format_g(Gx.arety, 16, 9, true) + " dy: " + mx.format_g(Gx.drety, 16, 9) + " L=" + Mx.level + " " + cxm[Gx.cmode - 1];
        var charb = "x: " + mx.format_g(Gx.aretx, 16, 9, true) + " dx: " + mx.format_g(Gx.dretx, 16, 9) + " " + cam[Gx.iabsc];
        if (Gx.iabsc === 3) {
            if (Gx.dretx === 0.0) {
                chara = chara.substr(0, 20) + "sl: Inf             " + chara.substr(40, chara.length);
            } else {
                chara = chara.substr(0, 20) + "sl: " + mx.format_g(Gx.drety / Gx.dretx, 16, 9) + chara.substr(40, chara.length);
            }
        }

        iy = Math.floor(Mx.height - 1.5 * Mx.text_h);
        mx.text(Mx, Mx.text_w, iy, chara);
        iy = Math.floor(Mx.height - 0.5 * Mx.text_h);
        mx.text(Mx, Mx.text_w, iy, charb);

        // display controls indicator
        if (k < Mx.width) {
            if (Gx.cntrls > 0) {
                mx.text(Mx, k, iy, 'C');
            } else {
                mx.text(Mx, k, iy, ' ');
            }
        }

        // draw color bar
        var x = (49 * Mx.text_w) - 1;
        var y = Mx.height - Mx.text_h * 2.5;
        var w = Mx.text_w;
        var h = Mx.text_h * 2;

        mx.colorbar(Mx, x, y, w, h);
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function scale_base(plot, mode, xxmin, xxmax, xlab, ylab) {
        var Mx = plot._Mx;
        var Gx = plot._Gx;

        var load = (mode.get_data === true);

        Gx.panxmin = 1.0;
        Gx.panxmax = -1.0;
        Gx.panymin = 1.0;
        Gx.panymax = -1.0;
        var xmin = xxmin;
        var xmax = xxmax;
        var noxmin = (xmin === undefined);
        var noxmax = (xmax === undefined);
        if (Gx.lyr.length === 0) {
            Gx.panxmin = -1.0;
            Gx.panxmax = 1.0;
            Gx.panymin = -1.0;
            Gx.panymax = 1.0;
        } else {
            if (xlab === undefined) {
                Gx.xlab = Gx.lyr[0].xlab;
            }
            if (ylab === undefined) {
                Gx.ylab = Gx.lyr[0].ylab;
            }

            for (var n = 0; n < Gx.lyr.length; n++) {
                if (noxmin) {
                    xmin = Gx.lyr[n].xmin;
                }

                if (noxmax) {
                    xmax = Gx.lyr[n].xmax;
                }

                if (Gx.xlab !== Gx.lyr[n].xlab) {
                    Gx.xlab = 0; // If the layers aren't consistent use None
                }
                if (Gx.ylab !== Gx.lyr[n].ylab) {
                    Gx.ylab = 0; // If the layers aren't consistent use None
                }

                if (load) {
                    Gx.lyr[n].get_data(xmin, xmax);
                }

                if (Gx.autox > 0 || Gx.autoy > 0) {
                    while (xmin < xmax) {
                        // get_data fills in the layer xbuf/ybuf with data
                        Gx.lyr[n].get_data(xmin, xmax);

                        // sigplot_prep fills in Gx.xptr and Gx.yptr (both PointArray)
                        // with the data to be plotted
                        var npts = Gx.lyr[n].prep(xmin, xmax);

                        // If both All and Expand are provided we
                        // need to look at the entire file to auto-scale it
                        if (Gx.all && Gx.expand) {
                            if (Gx.lyr[n].size === 0) {
                                xmin = xmax;
                            } else {
                                if (Gx.index) {
                                    xmin = xmin + npts;
                                } else {
                                    if (Gx.lyr[n].xdelta >= 0) {
                                        xmin = xmin + (Gx.lyr[n].size * Gx.lyr[n].xdelta);
                                    } else {
                                        xmax = xmax + (Gx.lyr[n].size * Gx.lyr[n].xdelta);
                                    }
                                }
                            }
                        } else {
                            xmin = xmax;
                        }
                    }
                } else {
                    Gx.lyr[n].prep(1.0, -1.0);
                }
            }
        }

        var xran = Gx.panxmax - Gx.panxmin;
        if (xran < 0.0) {
            Gx.panxmax = Gx.panxmin;
            Gx.panxmin = Gx.panxmax + xran;
            xran = -xran;
        }
        if (xran <= 1.0e-20) {
            Gx.panxmin = Gx.panxmin - 1.0;
            Gx.panxmax = Gx.panxmax + 1.0;
        }

        if (((Gx.autox & 1) !== 0) && noxmin) {
            Mx.stk[0].xmin = Gx.panxmin;
        }
        if (((Gx.autox & 2) !== 0) && noxmax) {
            Mx.stk[0].xmax = Gx.panxmax;
            if (!(Gx.all || Gx.xdata)) {
                for (var n = 0; n < Gx.lyr.length; n++) {
                    xmax = Math.min(Gx.lyr[n].xmax, Mx.stk[0].xmax);
                    var dpts = Math.abs((xmax - Gx.lyr[n].xmin) / Gx.lyr[n].xdelta) - Gx.bufmax + 1.0;
                    if (dpts > 0) {
                        Mx.stk[0].xmax = xmax - dpts * Math.abs(Gx.lyr[n].xdelta);
                    }
                }
            }
        }

        if (((Gx.autoy & 1) !== 0)) {
            Mx.stk[0].ymin = Gx.panymin;
        }
        if (((Gx.autoy & 2) !== 0)) {
            Mx.stk[0].ymax = Gx.panymax;
        }
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function pixel_to_real(plot, xpos, ypos) {
        var Gx = plot._Gx;
        var Mx = plot._Mx;

        var ret = mx.pixel_to_real(Mx, xpos, ypos);
        if (Gx.index) {
            ret.x = ret.x * Gx.xdelta;
        }

        return ret;
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function coordsInRectangle(x, y, rect_x, rect_y, rect_width, rect_height) {
        return (x >= rect_x && x <= rect_x + rect_width && y >= rect_y && y <= rect_y + rect_height);
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function inPanRegion(plot) {
        var inPanRegion = false;
        var Gx = plot._Gx;
        var Mx = plot._Mx;
        var x = Mx.xpos;
        var y = Mx.ypos;

        var command = ' ';

        if (!plot.mouseOnCanvas) {
            return false;
        }

        if (Gx.pan && (x > Mx.r && y >= Mx.t && y <= Mx.b)) { // YPAN
            command = 'YPAN'; // Y scrollbar
            Mx.xpos = Gx.pyl + m.trunc(Gx.pthk / 2); // TODO do we want to
            // reset the xposition?

            inPanRegion = true;
        } else if (Gx.pan && (x >= Gx.pl && x <= Gx.pr) && ((Gx.show_readout && y > Gx.pt - 2) || (!Gx.show_readout && y <= Gx.pt + Gx.pthk + 2))) { // XPAN
            command = 'XPAN'; // X scrollbar
            Mx.ypos = Gx.pt + m.trunc(Gx.pthk / 2); // TODO Do we want to reset
            // the yposition?

            inPanRegion = true;
        }

        return {
            inPanRegion: inPanRegion,
            command: command
        };
    }

    /**
     * @memberOf sigplot
     * @private
     */
    function inPanCenterRegion(plot) {
        var inCenterRegion = false;
        var Mx = plot._Mx;
        var x = Mx.xpos;
        var y = Mx.ypos;
        var th = Mx.text_h;
        var tw = Mx.text_w;
        var command = ' ';

        if (x < Mx.l - tw && y <= Mx.b && y >= Mx.t) { // YCENTER
            // Mx.canvas.getContext("2d").strokeStyle = "blue";
            // Mx.canvas.getContext("2d").strokeRect(0, Mx.t, Mx.l - tw, Mx.b -
            // Mx.t);
            command = 'YCENTER';
            inCenterRegion = true;
        } else if (y > Mx.b + m.trunc(0.5 * tw) && y <= Mx.b + m.trunc(m.trunc(3 * th) / 2) && x >= Mx.l && x <= Mx.r) { // XCENTER
            // Mx.canvas.getContext("2d").strokeStyle = "red";
            // Mx.canvas.getContext("2d").strokeRect(Mx.l, Mx.b + m.trunc(.5 *
            // tw),
            // Mx.r - Mx.l, (Mx.b + m.trunc(m.trunc(3 * th) / 2)) - (Mx.b +
            // m.trunc(.5 * tw)));
            command = 'XCENTER';
            inCenterRegion = true;
        }

        return {
            inCenterRegion: inCenterRegion,
            command: command
        };
    }

    /**
     * Returns true if position is within the given scrollbar's area. Depends on
     * mx.scroll_real2pix method.
     *
     * @param position
     *            An object containing 'x' and 'y' pixel values that represent a
     *            position.
     * @param scrollbar
     *            The scrollbar object itself.
     *
     * @private
     * @memberOf sigplot
     */
    function onScrollbar(position, scrollbar) {
        var s1;
        var sw;

        /*
         * Compute s, the offset in pixels from the 'origin' of the scrollbar's
         * on-screen region.
         */
        var s;
        if (scrollbar.origin & 1) {
            s = position.x - scrollbar.x;
            if (scrollbar.origin & 2) {
                s = scrollbar.w - s;
	    }
        } else {
            s = position.y - scrollbar.y;
            if (scrollbar.origin <= 2) {
                s = scrollbar.h - s;
	    }
        }

        // Update s1 and sw values
        var scrollReal2PixOut = mx.scroll_real2pix(scrollbar);
        s1 = scrollReal2PixOut.s1;
        sw = scrollReal2PixOut.sw;

        // Determine if mouse is on scrollbar
        if (s >= s1 && s <= s1 + sw) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Performs the middle-click scroll-menu action specified on the plot.
     *
     * @param plot
     *            The plot to work with.
     * @param action
     *            The scrollbar action to perform.
     * @param direction
     *            The scroll direction to update. Acceptable directions are
     *            either "XPAN" or "YPAN".
     * @memberOf sigplot
     * @private
     */
    function middleClickScrollMenuAction(plot, action, direction) {
        var Mx = plot._Mx;

        // Determine the appropriate scrollbar to work with
        var scrollbar;
        if (direction === "XPAN") {
            scrollbar = Mx.scrollbar_x;
        } else if (direction === "YPAN") {
            scrollbar = Mx.scrollbar_y;
        }

        // Set scrollbar action
        scrollbar.action = action;
        scrollbar.step = 0.1 * scrollbar.srange;
        scrollbar.page = 9 * scrollbar.step;
        scrollbar.scale = 2.0;

        // Update min and range to the appropriate values based on action
        mx.scroll(Mx, scrollbar, mx.XW_COMMAND, undefined, scrollbar);

        // Update the viewbox based on new min and max values
        updateViewbox(plot, scrollbar.smin, scrollbar.smin + scrollbar.srange,
            direction.slice(0, 1));
    }

    /**
     * Updates a plot's viewbox along a given axis to the provided min and max
     * values.
     *
     * @param plot
     *            The plot to work with.
     * @param newMin
     *            The new minimum axis value to use
     * @param newMax
     *            The new maximum axis value to use
     * @param axis
     *            The axis to update. Acceptable axis values are "X" or "Y".
     * @memberOf sigplot
     * @private
     */
    function updateViewbox(plot, newMin, newMax, axis) {
        var Mx = plot._Mx;
        var Gx = plot._Gx;

        var k = Mx.level;

        if (axis === "X") {
            var xmin = newMin;
            var xmax = newMax;

            if (Mx.stk[k].xmin !== xmin || Mx.stk[k].xmax !== xmax) {
                Mx.stk[k].xmin = xmin;
                Mx.stk[k].xmax = xmax;
                if (!Gx.xdata && Mx.level === 1) {
                    Gx.xmin = Mx.stk[1].xmin;
                    Gx.xmax = Mx.stk[1].xmax;
                }
                plot.refresh();
            }
        } else if (axis === "Y") {
            var ymin = newMin;
            var ymax = newMax;

            if (ymin !== Mx.stk[k].ymin || ymax !== Mx.stk[k].ymax) {
                Mx.stk[k].ymin = ymin;
                Mx.stk[k].ymax = ymax;
                if (Gx.cmode === Gx.basemode && Mx.level === 1) {
                    Gx.ymin = Math.min(Gx.ymin, ymin);
                    Gx.ymax = Math.max(Gx.ymax, ymax);
                }
                plot.refresh();
            }
        }
    }

}(window.sigplot, window.mx, window.m));
DocStrap Copyright © 2012-2013 The contributors to the JSDoc3 and DocStrap projects.
Documentation generated by JSDoc 3.2.2 on Sun Nov 02 2014 20:42:48 GMT-0000 (GMT) using the DocStrap template.