# Screen2D

#####

declaration
Screen2D

Two dimensional plotting

    var Screen2D = (function (_super) {
    __extends(Screen2D, _super);
    function Screen2D(id, options) {
        if (typeof options === "undefined") { options = {}; }
        var _this = this;
        _super.call(this, id, options);
        this.type = 'screen2D';
        var defaults = {
            axes: {
                color: 0x000000,
                lineColor: 0x000000,
                textColor: 0x000000,

label: true
label: false
label: {
x: true,
y: false
}

    label: {
                    fontSize: 12,
                    font: 'Helvetica',
                    x: true,
                    y: true
                },
                x: true,
                y: true
            },
            grid: {
                // angle: Math.PI / 8,
                type: 'cartesian',
                lineColor: 0xcccccc,
                lineWidth: 4,
                dash: [],
                dashOffset: 0,
                //tick: {x: 1, y: 1, r: 1}
                x: { tick: 1, lineColor: 0xcccccc, lineWidth: 4, dash: [], dashOffset: 0 },
                y: { tick: 1, lineColor: 0xcccccc, lineWidth: 4, dash: [], dashOffset: 0 },
                r: { tick: 1, lineColor: 0xcccccc, lineWidth: 4, dash: [], dashOffset: 0 },
                angle: { tick: Math.PI / 8, lineColor: 0xcccccc, lineWidth: 4, dash: [], dashOffset: 0 }
            },
            interaction: {
                allowPan: true,
                allowZoom: true,
                zoomSpeed: 1
            },
            background: 0xffffff,
            lookAt: { x: 0, y: 0 },
            range: { x: 1, y: 1 },
            figcaption: '',
            renderer: 'Canvas',
            transformation: new MathLib.Matrix([
                [Math.min(this.height, this.width) / 2, 0, this.width / 2],
                [0, -Math.min(this.height, this.width) / 2, this.height / 2],
                [0, 0, 1]])
        }, opts = MathLib.extendObject(defaults, options), element, transformation = opts.transformation, that = this;

        this.options = opts;
        this.renderer = MathLib[opts.renderer];

        this.circle = function () {
            var args = [];
            for (var _i = 0; _i < (arguments.length - 0); _i++) {
                args[_i] = arguments[_i + 0];
            }
            return _this.renderer.circle.apply(_this.layer.main, args);
        };
        this.line = function () {
            var args = [];
            for (var _i = 0; _i < (arguments.length - 0); _i++) {
                args[_i] = arguments[_i + 0];
            }
            return _this.renderer.line.apply(_this.layer.main, args);
        };
        this.path = function () {
            var args = [];
            for (var _i = 0; _i < (arguments.length - 0); _i++) {
                args[_i] = arguments[_i + 0];
            }
            return _this.renderer.path.apply(_this.layer.main, args);
        };

        // Should the pixel method default to the main layer or to the back layer?
        this.pixel = function () {
            var args = [];
            for (var _i = 0; _i < (arguments.length - 0); _i++) {
                args[_i] = arguments[_i + 0];
            }
            return _this.renderer.pixel.apply(_this.layer.main, args);
        };
        this.point = function () {
            var args = [];
            for (var _i = 0; _i < (arguments.length - 0); _i++) {
                args[_i] = arguments[_i + 0];
            }
            return _this.renderer.point.apply(_this.layer.main, args);
        };
        this.text = function () {
            var args = [];
            for (var _i = 0; _i < (arguments.length - 0); _i++) {
                args[_i] = arguments[_i + 0];
            }
            return _this.renderer.text.apply(_this.layer.main, args);
        };

        // Remove the warning message.
        this.wrapper.innerHTML = '';

        this.container.classList.add('MathLib_screen2D');

        // This is just a dummy method for the following few lines.
        // The real applyTransformation method is specified after the creation of the layers.
        this.applyTransformation = function () {
        };

        // The interaction methods
        this.translation = {};
        this.scale = {};
        this.transformation = transformation;

        Object.defineProperty(this.translation, 'x', {
            get: function () {
                return that.transformation[0][2];
            },
            set: function (x) {
                that.transformation[0][2] = x;
                that.applyTransformation();
            }
        });

        Object.defineProperty(this.translation, 'y', {
            get: function () {
                return that.transformation[1][2];
            },
            set: function (y) {
                that.transformation[1][2] = y;
                that.applyTransformation();
            }
        });

        Object.defineProperty(this.scale, 'x', {
            get: function () {
                return that.transformation[0][0];
            },
            set: function (x) {
                that.transformation[0][0] = x;
                that.applyTransformation();
            }
        });

        Object.defineProperty(this.scale, 'y', {
            get: function () {
                return that.transformation[1][1];
            },
            set: function (y) {
                that.transformation[1][1] = y;
                that.applyTransformation();
            }
        });

        this.lookAt = {};
        this.range = {};
        Object.defineProperty(this.lookAt, 'x', {
            get: function () {
                return (that.width / 2 - that.transformation[0][2]) / that.transformation[0][0];
            },
            set: function (x) {
                that.transformation[0][2] = that.width / 2 - x * that.transformation[0][0];
                that.applyTransformation();
            }
        });

        Object.defineProperty(this.lookAt, 'y', {
            get: function () {
                return (that.height / 2 - that.transformation[1][2]) / that.transformation[1][1];
            },
            set: function (y) {
                that.transformation[1][2] = that.height / 2 - y * that.transformation[1][1];
                that.applyTransformation();
            }
        });

        Object.defineProperty(this.range, 'x', {
            get: function () {
                return that.width / (2 * that.transformation[0][0]);
            },
            set: function (x) {
                that.transformation[0][0] = 0.5 * that.width / x;
                that.applyTransformation();
            }
        });

        Object.defineProperty(this.range, 'y', {
            get: function () {
                return -that.height / (2 * that.transformation[1][1]);
            },
            set: function (y) {
                that.transformation[1][1] = -0.5 * that.height / y;
                that.applyTransformation();
            }
        });

        this.range.x = opts.range.x;
        this.range.y = opts.range.y;
        this.lookAt.x = opts.lookAt.x;
        this.lookAt.y = opts.lookAt.y;

        // Create the SVG element which contains the layers
        if (opts.renderer === 'SVG') {
            // Create the canvas
            element = document.createElementNS('http://www.w3.org/2000/svg', 'svg');

            // Safari does not support .classList on SVG elements
            // This feature has be in webkit since [08/02/12](http://trac.webkit.org/changeset/124499)

element.classList.add('MathLib_screen');

    element.className.baseVal = 'MathLib_screen';
            element.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
            element.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
            element.setAttribute('height', this.height + 'px');
            element.setAttribute('width', this.width + 'px');
            element.setAttribute('version', '1.1');

            element.setAttribute('stroke', '#000000');
            element.setAttribute('stroke-opacity', '1');
            element.setAttribute('fill', 'transparent');

            this.element = element;
            this.wrapper.appendChild(element);

            //			if ('background' in options) {
            var background = document.createElementNS('http://www.w3.org/2000/svg', 'rect');

            background.setAttribute('x', '0px');
            background.setAttribute('y', '0px');
            background.setAttribute('width', this.width + 'px');
            background.setAttribute('height', this.height + 'px');
            background.setAttribute('stroke', 'transparent');
            background.setAttribute('fill', 'background' in options ? MathLib.colorConvert(options.background) : 'white');
            background.setAttribute('fill-opacity', '1');
            this.element.appendChild(background);
            //			}
        }

        // Create the Layers
        // =================
        this.layer = [];
        this.layer.back = new MathLib.Layer(this, 'back', 0);
        this.layer.grid = new MathLib.Layer(this, 'grid', 1);
        this.layer.axes = new MathLib.Layer(this, 'axes', 2);
        this.layer.main = new MathLib.Layer(this, 'main', 3);

        this.wrapper.addEventListener('mouseup', function (evt) {
            return _this.onmouseup(evt);
        }, false);
        this.wrapper.addEventListener('mousedown', function (evt) {
            return _this.onmousedown(evt);
        }, false);
        this.wrapper.addEventListener('mousemove', function (evt) {
            return _this.onmousemove(evt);
        }, false);
        this.wrapper.addEventListener('mousewheel', function (evt) {
            return _this.onmousewheel(evt);
        }, false);

        // For Firefox: [Bug report for the missing onmousewheel method](https://bugzilla.mozilla.org/show_bug.cgi?id=111647)
        this.wrapper.addEventListener('DOMMouseScroll', function (evt) {
            return _this.onmousewheel(evt);
        }, false);

        this.applyTransformation = this.renderer.applyTransformation;

        this.draw = function (x, options) {
            if (typeof options === "undefined") { options = {}; }
            var _this = this;
            // Clear and redraw the screen
            if (arguments.length === 0) {
                this.layer.forEach(function (l) {
                    l.clear().draw();
                });
            } else if (x.type === 'circle') {
                this.circle(x, options);
            } else if (x.type === 'line') {
                this.line(x, options);
            } else if (Array.isArray(x)) {
                x.forEach(function (y) {
                    return _this[y.type](y, options);
                });
            }
        };

        if (this.options.contextMenu) {
            var gridType = opts.grid.type ? opts.grid.type : 'none';
            this.contextMenu.querySelectorAll('.MathLib_grid_type[value=' + gridType + ']')[0].checked = true;
        }

        this.draw();
    }

# drawAxes

#####

method
Screen2D.prototype.drawAxes() → Screen2D

Draws the axes.

    Screen2D.prototype.drawAxes = function () {
        var _this = this;
        var line = function () {
            var args = [];
            for (var _i = 0; _i < (arguments.length - 0); _i++) {
                args[_i] = arguments[_i + 0];
            }
            return _this.renderer.line.apply(_this.layer.axes, args);
        }, text = function () {
            var args = [];
            for (var _i = 0; _i < (arguments.length - 0); _i++) {
                args[_i] = arguments[_i + 0];
            }
            return _this.renderer.text.apply(_this.layer.axes, args);
        }, options = {
            lineColor: MathLib.colorConvert(this.options.axes.color),
            'stroke-width': -1 / this.transformation[1][1]
        }, textOptions = {
            font: this.options.axes && 'label' in this.options.axes ? this.options.axes.label.font : '',
            fontSize: this.options.axes && 'label' in this.options.axes ? this.options.axes.label.fontSize : '',
            //				fontSize: this.options.axes.label.fontSize,
            strokeStyle: MathLib.colorConvert(this.options.axes.textColor),
            fillStyle: MathLib.colorConvert(this.options.axes.textColor)
        }, top = (-this.translation.y) / this.scale.y, bottom = (this.height - this.translation.y) / this.scale.y, left = (-this.translation.x) / this.scale.x, right = (this.width - this.translation.x) / this.scale.x, lengthX = 10 / this.transformation[0][0], lengthY = -10 / this.transformation[1][1], yExp = 1 - Math.floor(Math.log(-this.transformation[1][1]) / Math.LN10 - 0.3), xExp = 1 - Math.floor(Math.log(this.transformation[0][0]) / Math.LN10 - 0.3), yTick = Math.pow(10, yExp), xTick = Math.pow(10, xExp), i;

        if (!this.options.axes) {
            return this;
        }

        // The axes
        if (this.options.axes.x) {
            line([[left, 0], [right, 0]], options, true);
        }
        if (this.options.axes.y) {
            line([[0, bottom], [0, top]], options, true);
        }

        // The ticks on the axes
        // The x axes
        if (this.options.axes.x) {
            for (i = left; i <= right; i += yTick) {
                line([[i, -lengthY], [i, lengthY]], options, true);
            }
        }

        // The y axes
        if (this.options.axes.y) {
            for (i = bottom; i <= top; i += xTick) {
                line([[-lengthX, i], [lengthX, i]], options, true);
            }
        }

        // The labels
        // The x axes
        // .toFixed() is necessary to display 0.3 as "0.3" and not as "0.30000000000000004".
        // .toFixed expects arguments between 0 and 20.
        var xLen = Math.max(0, Math.min(20, -xExp)), yLen = Math.max(0, Math.min(20, -yExp));

        if (this.options.axes.label) {
            if (this.options.axes.x) {
                for (i = -yTick; i >= left; i -= yTick) {
                    text(i.toFixed(yLen), i, -2 * lengthY, textOptions, true);
                }
                for (i = yTick; i <= right; i += yTick) {
                    text(i.toFixed(yLen), i, -2 * lengthY, textOptions, true);
                }
            }

            // The y axes
            if (this.options.axes.y) {
                for (i = -xTick; i >= bottom; i -= xTick) {
                    text(i.toFixed(xLen), -2 * lengthX, i, textOptions, true);
                }
                for (i = xTick; i <= top; i += xTick) {
                    text(i.toFixed(xLen), -2 * lengthX, i, textOptions, true);
                }
            } else {
                text(0..toFixed(yLen), 0, -2 * lengthY, textOptions, true);
            }
        }

        return this;
    };

# drawGrid

#####

method
Screen2D.prototype.drawGrid() → Screen2D

Draws the grid.

    Screen2D.prototype.drawGrid = function () {
        var _this = this;
        if (!this.options.grid) {
            return this;
        }

        var i, ii, line = function () {
            var args = [];
            for (var _i = 0; _i < (arguments.length - 0); _i++) {
                args[_i] = arguments[_i + 0];
            }
            return _this.renderer.line.apply(_this.layer.grid, args);
        }, circle = function () {
            var args = [];
            for (var _i = 0; _i < (arguments.length - 0); _i++) {
                args[_i] = arguments[_i + 0];
            }
            return _this.renderer.circle.apply(_this.layer.grid, args);
        }, top = (-this.translation.y) / this.scale.y, bottom = (this.height - this.translation.y) / this.scale.y, left = (-this.translation.x) / this.scale.x, right = (this.width - this.translation.x) / this.scale.x, yTick = Math.pow(10, 1 - Math.floor(Math.log(-this.transformation[1][1]) / Math.LN10 - 0.3)), xTick = Math.pow(10, 1 - Math.floor(Math.log(this.transformation[0][0]) / Math.LN10 - 0.3));

        if (this.options.grid.type === 'cartesian') {
            // The vertical lines
            if (this.options.grid.x) {
                for (i = left - (left % xTick); i <= right; i += xTick) {
                    line([[i, bottom], [i, top]], MathLib.extendObject(this.options.grid, this.options.grid.x), true);
                }
            }

            // The horizontal lines
            if (this.options.grid.y) {
                for (i = bottom - (bottom % yTick); i <= top; i += yTick) {
                    line([[left, i], [right, i]], MathLib.extendObject(this.options.grid, this.options.grid.y), true);
                }
            }
            // Test for logarithmic plots

or (i = left - (left % this.axes.tick.x); i <= right; i += this.axes.tick.x) {
for (var j = 1; j <= 10; j++ ) {
this.line([[i * Math.log(10) + Math.log(j), bottom], [i * Math.log(10) + Math.log(j), top]], options);
}
}

    } else if (this.options.grid.type === 'polar') {
            var max = Math.sqrt(Math.max(top * top, bottom * bottom) + Math.max(left * left, right * right)), min = 0;

            if (this.options.grid.angle) {
                for (i = 0, ii = 2 * Math.PI; i < ii; i += this.options.grid.angle.tick) {
                    line([[0, 0], [max * Math.cos(i), max * Math.sin(i)]], MathLib.extendObject(this.options.grid, this.options.grid.angle), true);
                }
            }

            if (this.options.grid.r) {
                for (i = min; i <= max; i += Math.min(xTick, yTick)) {
                    circle(new MathLib.Circle([0, 0, 1], i), MathLib.extendObject(this.options.grid, this.options.grid.r), true);
                }
            }
        }

        return this;
    };

# getEventPoint

#####

method
Screen2D.prototype.getEventPoint() → Point

Option name | Type | Description
--- | --- | ---
evt | event | The event object

Creates a point based on the coordinates of an event.

    Screen2D.prototype.getEventPoint = function (evt) {
        var x, y;
        if (evt.offsetX) {
            x = evt.offsetX;
            y = evt.offsetY;
        } else {
            x = evt.layerX;
            y = evt.layerY;
        }
        return new MathLib.Point([x, y, 1]);
    };

# getLineEndPoints

#####

method
Screen2D.prototype.getLineEndPoints() → array

Option name | Type | Description
--- | --- | ---
l | Line array | The Line to calculate the end points to

Calculates the both endpoints for the line
for drawing purposes

    Screen2D.prototype.getLineEndPoints = function (l) {
        if (l.type === 'line') {
            var top = (-this.translation.y) / this.scale.y, bottom = (this.height - this.translation.y) / this.scale.y, left = (-this.translation.x) / this.scale.x, right = (this.width - this.translation.x) / this.scale.x, lineRight = -(l[2] + l[0] * right) / l[1], lineTop = -(l[2] + l[1] * top) / l[0], lineLeft = -(l[2] + l[0] * left) / l[1], lineBottom = -(l[2] + l[1] * bottom) / l[0], points = [];

            if (lineRight <= top && lineRight >= bottom) {
                points.push([right, lineRight]);
            }
            if (lineLeft <= top && lineLeft >= bottom) {
                points.push([left, lineLeft]);
            }
            if (lineTop < right && lineTop > left) {
                points.push([lineTop, top]);
            }
            if (lineBottom < right && lineBottom > left) {
                points.push([lineBottom, bottom]);
            }
            return points;
        } else {
            return l;
        }
    };

# onmousedown

#####

method
Screen2D.prototype.onmousedown()

Option name | Type | Description
--- | --- | ---
evt | event | The event object

Handles the mousedown event

    Screen2D.prototype.onmousedown = function (evt) {
        // Only start the action if the left mouse button was clicked
        if (evt.button !== 0) {
            return;
        }

        if (evt.preventDefault) {
            evt.preventDefault();
        }

        evt.returnValue = false;

        // Pan mode
        if (this.options.interaction.allowPan && !this.options.interaction.type) {
            this.options.interaction.type = 'pan';
            this.options.interaction.startPoint = this.getEventPoint(evt);
            this.options.interaction.startTransformation = this.transformation.copy();
        }
    };

# onmousemove

#####

method
Screen2D.prototype.onmousemove()

Option name | Type | Description
--- | --- | ---
evt | event | The event object

Handles the mousemove event

    Screen2D.prototype.onmousemove = function (evt) {
        var p;

        if (evt.preventDefault) {
            evt.preventDefault();
        }

        evt.returnValue = false;

        // Pan mode
        if (this.options.interaction.type === 'pan') {
            p = this.getEventPoint(evt).minus(this.options.interaction.startPoint);
            this.translation.x = this.options.interaction.startTransformation[0][2] + p[0];
            this.translation.y = this.options.interaction.startTransformation[1][2] + p[1];
            this.draw();
        }
    };

# onmouseup

#####

method
Screen2D.prototype.onmouseup()

Option name | Type | Description
--- | --- | ---
evt | event | The event object

Handles the mouseup event

    Screen2D.prototype.onmouseup = function (evt) {
        if (evt.preventDefault) {
            evt.preventDefault();
        }

        evt.returnValue = false;

        // Go back to normal mode
        if (this.options.interaction.type === 'pan') {
            delete this.options.interaction.type;
            delete this.options.interaction.startPoint;
            delete this.options.interaction.startTransformation;
        }
    };

# onmousewheel

#####

method
Screen2D.prototype.onmousewheel()

Option name | Type | Description
--- | --- | ---
evt | event | The event object

Handles the mousewheel event

    Screen2D.prototype.onmousewheel = function (evt) {
        var delta, s, p, z;

        if (this.options.interaction.allowZoom) {
            if (evt.preventDefault) {
                evt.preventDefault();
            }
            evt.returnValue = false;

            // Chrome/Safari
            if (evt.wheelDelta) {
                delta = evt.wheelDelta / 360;
            } else {
                delta = evt.detail / -9;
            }

            // The amount of zoom is determined by the zoom speed
            // and the amount how much the scrollwheel has been moved
            z = Math.pow(1 + this.options.interaction.zoomSpeed, delta);

            // Transform the (computer-)screen coordinates of the mouse to the internal coordinates
            p = this.transformation.inverse().times(this.getEventPoint(evt));

            // Compute new scale matrix in current mouse position
            s = new MathLib.Matrix([[z, 0, p[0] - p[0] * z], [0, z, p[1] - p[1] * z], [0, 0, 1]]);

            this.transformation = this.transformation.times(s);

            this.applyTransformation();
            this.draw();
        }
    };

# resize

#####

method
Screen2D.prototype.resize() → Screen2D

Option name | Type | Description
--- | --- | ---
width | number | The new width
height | number | The new height

Adjust the rendering if the screen is resized

    Screen2D.prototype.resize = function (width, height) {
        this.height = height;
        this.width = width;

        if (this.options.renderer === 'Canvas') {
            this.layer.back.element.width = width;
            this.layer.back.element.height = height;
            this.layer.back.ctx.fillStyle = 'rgba(255, 255, 255, 0)';

            this.layer.grid.element.width = width;
            this.layer.grid.element.height = height;
            this.layer.grid.ctx.fillStyle = 'rgba(255, 255, 255, 0)';
            this.layer.grid.ctx.strokeStyle = MathLib.colorConvert(this.options.grid.color) || '#cccccc';

            this.layer.axes.element.width = width;
            this.layer.axes.element.height = height;
            this.layer.axes.ctx.fillStyle = 'rgba(255, 255, 255, 0)';
            this.layer.axes.ctx.strokeStyle = MathLib.colorConvert(this.options.axes.color) || '#000000';

            this.layer.main.element.width = width;
            this.layer.main.element.height = height;
            this.layer.main.ctx.fillStyle = 'rgba(255, 255, 255, 0)';
        } else if (this.options.renderer === 'SVG') {
            this.element.setAttribute('width', width + 'px');
            this.element.setAttribute('height', height + 'px');
        }

        this.applyTransformation();
        this.draw();

        return this;
    };
    return Screen2D;
        })(MathLib.Screen);
        MathLib.Screen2D = Screen2D;
    })(MathLib || (MathLib = {}));