# Canvas

#####

property
MathLib.Canvas

The Canvas renderer for 2D plotting

    MathLib.Canvas = {

Applies the current transformations to the screen

    applyTransformation: function () {
    			var m = this.transformation;
    			this.layer.forEach(function (l) {
    				l.ctx.setTransform(m[0][0], m[1][0], m[0][1], m[1][1], m[0][2], m[1][2]);
    			});
    		},

Option name | Type | Description
--- | --- | ---
circle | Circle | The circle to be drawn
options | object | Optional drawing options
redraw | boolean | Indicates if the current draw call is happening during a redraw

Draws a circle on the screen.

    circle: function (circle, options, redraw) {
    			if (typeof options === 'undefined') { options = {}; }
    			if (typeof redraw === 'undefined') { redraw = false; }
    			var screen = this.screen, ctx = this.ctx, prop, opts;

    			ctx.save();
    			ctx.lineWidth = (options.lineWidth || 4) / (screen.scale.x - screen.scale.y);

    			// Set the drawing options
    			if (options) {
    				opts = MathLib.Canvas.convertOptions(options);
    				for (prop in opts) {
    					if (opts.hasOwnProperty(prop)) {
    						ctx[prop] = opts[prop];
    					}
    				}

    				if ('setLineDash' in ctx) {
    					ctx.setLineDash(('dash' in options ? options.dash : []));
    				}
    				if ('lineDashOffset' in ctx) {
    					ctx.lineDashOffset = ('dashOffset' in options ? options.dashOffset : 0);
    				}
    			}

    			// Draw the circle
    			ctx.beginPath();
    			ctx.arc(circle.center[0], circle.center[1], circle.radius, 0, 2 * Math.PI);
    			ctx.closePath();
    			ctx.fill();
    			ctx.stroke();
    			ctx.restore();

    			if (!redraw) {
    				this.stack.push({
    					type: 'circle',
    					object: circle,
    					options: options
    				});
    			}

    			return this;
    		},

Option name | Type | Description
--- | --- | ---
layer | Layer | The layer to be cleared

Clears a given Layer.

    clear: function (layer) {
    			var screen = layer.screen, left = -screen.translation.x / screen.scale.x, top = -screen.translation.y / screen.scale.y, width = screen.width / screen.scale.x, height = screen.height / screen.scale.y;

    			layer.ctx.clearRect(left, top, width, height);
    		},

Option name | Type | Description
--- | --- | ---
options | object | The drawing options

Converts the options to the Canvas options format

    convertOptions: function (options) {
    			var convertedOptions = {};
    			if ('fillColor' in options) {
    				convertedOptions.fillStyle = MathLib.colorConvert(options.fillColor);
    			}
    			else if ('color' in options) {
    				convertedOptions.fillStyle = MathLib.colorConvert(options.color);
    			}

    			if ('font' in options) {
    				convertedOptions.font = options.font;
    			}

    			if ('fontSize' in options) {
    				convertedOptions.fontSize = options.fontSize;
    			}

    			if ('lineColor' in options) {
    				convertedOptions.strokeStyle = MathLib.colorConvert(options.lineColor);
    			}
    			else if ('color' in options) {
    				convertedOptions.strokeStyle = MathLib.colorConvert(options.color);
    			}

    			return convertedOptions;
    		},

Option name | Type | Description
--- | --- | ---
line | Line | The line to be drawn
options | object | Optional drawing options
redraw | boolean | Indicates if the current draw call is happening during a redraw

Draws a line on the screen.

    line: function (line, options, redraw) {
    			if (typeof options === 'undefined') { options = {}; }
    			if (typeof redraw === 'undefined') { redraw = false; }
    			var screen = this.screen, points, ctx = this.ctx, prop, opts;

    			ctx.save();
    			ctx.lineWidth = (options.lineWidth || 4) / (screen.scale.x - screen.scale.y);

    			// Don't try to draw the line at infinity
    			if (line.type === 'line' && MathLib.isZero(line[0]) && MathLib.isZero(line[1])) {
    				return this;
    			}
    			else {
    				points = this.screen.getLineEndPoints(line);
    			}

    			// Set the drawing options
    			if (options) {
    				opts = MathLib.Canvas.convertOptions(options);
    				for (prop in opts) {
    					if (opts.hasOwnProperty(prop)) {
    						ctx[prop] = opts[prop];
    					}
    				}

    				if ('setLineDash' in ctx) {
    					ctx.setLineDash(('dash' in options ? options.dash : []));
    				}
    				if ('lineDashOffset' in ctx) {
    					ctx.lineDashOffset = ('dashOffset' in options ? options.dashOffset : 0);
    				}
    			}

    			// Draw the line
    			ctx.beginPath();
    			ctx.moveTo(points[0][0], points[0][1]);
    			ctx.lineTo(points[1][0], points[1][1]);
    			ctx.stroke();
    			ctx.closePath();
    			ctx.restore();

    			if (!redraw) {
    				this.stack.push({
    					type: 'line',
    					object: line,
    					options: options
    				});
    			}

    			return this;
    		},

Option name | Type | Description
--- | --- | ---
curve | Path | The path to be drawn
options | object | Optional drawing options
redraw | boolean | Indicates if the current draw call is happening during a redraw

Draws a path on the screen.

    path: function (curve, options, redraw) {
    			if (typeof options === 'undefined') { options = {}; }
    			if (typeof redraw === 'undefined') { redraw = false; }
    			var screen = this.screen, ctx = this.ctx, prop, opts, path, paths = [], x, y, i, fx, fxold, step = 2 / (screen.scale.x - screen.scale.y), from, to;

    			ctx.save();
    			ctx.lineWidth = (options.lineWidth || 4) / (screen.scale.x - screen.scale.y);

    			// Set the drawing options
    			if (options) {
    				opts = MathLib.Canvas.convertOptions(options);
    				for (prop in opts) {
    					if (opts.hasOwnProperty(prop)) {
    						ctx[prop] = opts[prop];
    					}
    				}

    				if ('setLineDash' in ctx) {
    					ctx.setLineDash(('dash' in options ? options.dash : []));
    				}
    				if ('lineDashOffset' in ctx) {
    					ctx.lineDashOffset = ('dashOffset' in options ? options.dashOffset : 0);
    				}
    			}

    			// If curve is a function f, the path will be (x, f(x))
    			if (typeof curve === 'function') {
    				path = [];
    				from = ('from' in options ? options.from : (-screen.translation.x) / screen.scale.x) - step;
    				to = ('to' in options ? options.to : (screen.width - screen.translation.x) / screen.scale.x) + step;

    				for (i = from; i <= to; i += step) {
    					fx = curve(i);

    					// Inline NaN test and disontinuity test
    					// Check if we are drawing a (nearly) vertical line, which should not be there.
    					// i.e the vertical lines at π/2 for the tangent function
    					// TODO: Find a better check if there is a discontinuity.
    					if (fx !== fx || (MathLib.abs((fxold - fx) / step) >= 1e2 && (fx - curve(i - step / 2)) * (fxold - curve(i - step / 2)) >= 0)) {
    						// Don't add empty subpaths
    						if (path.length) {
    							paths.push(path);
    							path = [];
    						}
    					}
    					else {
    						path.push([i, fx]);
    					}

    					fxold = fx;
    				}
    				if (path.length) {
    					paths.push(path);
    				}
    			}
    			else if (typeof curve[0] === 'function') {
    				path = [];
    				x = curve[0];
    				y = curve[1];
    				from = ('from' in options ? options.from : 0) - step;
    				to = ('to' in options ? options.to : 2 * Math.PI) + step;
    				for (i = from; i <= to; i += step) {
    					path.push([x(i), y(i)]);
    				}
    				paths.push(path);
    			}
    			else {
    				path = curve;
    			}

    			// Draw the path
    			// Till now I haven't found a way to stroke and fill the path in one go.
    			// The problem is basically, that moveTo creates a new subpath
    			// and every subpath is filled on its own.
    			if (options.fillColor || options.fillColor !== 'transparent') {
    				ctx.beginPath();
    				ctx.lineTo(from, 0);
    				paths.forEach(function (path) {
    					// The following line (and the line four lines afterwards) fixes the fill at holes in the path.
    					ctx.lineTo(path[0][0], 0);
    					path.forEach(function (x) {
    						ctx.lineTo(x[0], x[1]);
    					});
    					ctx.lineTo(path[path.length - 1][0], 0);
    				});
    				ctx.fill();
    				//		ctx.closePath();
    			}

    			if (options.lineColor || options.lineColor !== 'transparent') {
    				ctx.beginPath();
    				paths.forEach(function (path) {
    					ctx.moveTo(path[0][0], path[0][1]);
    					path.forEach(function (x) {
    						ctx.lineTo(x[0], x[1]);
    					});
    				});
    				ctx.stroke();
    				//		ctx.closePath();
    			}

    			ctx.restore();

    			if (!redraw) {
    				if (options.conic) {
    					this.stack.push({
    						type: 'conic',
    						object: options.conic,
    						options: options
    					});
    				}
    				else {
    					this.stack.push({
    						type: 'path',
    						object: curve,
    						options: options
    					});
    				}
    			}

    			return this;
    		},

Option name | Type | Description
--- | --- | ---
f | function | The pixel function
t | number | The top coordinate of the draw rectangle
r | number | The right coordinate of the draw rectangle
b | number | The bottom coordinate of the draw rectangle
l | number | The left coordinate of the draw rectangle
options | object | Optional drawing options
redraw | boolean | Indicates if the current draw call is happening during a redraw

Draws pixel on the screen.

    pixel: function (f, t, r, b, l, options, redraw) {
    			if (typeof options === 'undefined') { options = {}; }
    			if (typeof redraw === 'undefined') { redraw = false; }
    			var screen = this.screen, top = (-screen.translation.y) / screen.scale.y, bottom = (screen.height - screen.translation.y) / screen.scale.y, left = (-screen.translation.x) / screen.scale.x, right = (screen.width - screen.translation.x) / screen.scale.x, ctx = this.ctx, x, y, i;

    			t = Math.min(top, t);
    			r = Math.min(right, r);
    			b = Math.max(bottom, b);
    			l = Math.max(left, l);

    			var tPxl = Math.floor(-t * screen.scale.y), rPxl = Math.floor(r * screen.scale.x), bPxl = Math.floor(-b * screen.scale.y), lPxl = Math.floor(l * screen.scale.x), w = (rPxl - lPxl), h = (bPxl - tPxl), imgData = ctx.createImageData(w, h), pxl;

    			for (y = tPxl, i = 0; y > bPxl; y--) {
    				for (x = lPxl; x < rPxl; x++, i++) {
    					pxl = f(x / screen.scale.x, y / screen.scale.y);
    					imgData.data[4 * i] = pxl[0];
    					imgData.data[4 * i + 1] = pxl[1];
    					imgData.data[4 * i + 2] = pxl[2];
    					imgData.data[4 * i + 3] = pxl[3];
    				}
    			}

    			ctx.putImageData(imgData, (left - l) * screen.scale.x, (t - top) * screen.scale.y);

    			if (!redraw) {
    				this.stack.push({
    					type: 'pixel',
    					object: f,
    					t: t,
    					r: r,
    					b: b,
    					l: l,
    					options: options
    				});
    			}

    			return this;
    		},

Option name | Type | Description
--- | --- | ---
point | Point | The point to be drawn
options | object | Optional drawing options
redraw | boolean | Indicates if the current draw call is happening during a redraw

Draws a point on the screen.

    point: function (point, options, redraw) {
    			if (typeof options === 'undefined') { options = {}; }
    			if (typeof redraw === 'undefined') { redraw = false; }
    			var screen = this.screen, ctx = this.ctx, prop, opts, dist;

    			ctx.save();
    			ctx.lineWidth = (options.lineWidth || 4) / (screen.scale.x - screen.scale.y);

    			// Set the drawing options
    			if (options) {
    				opts = MathLib.Canvas.convertOptions(options);

    				if (!('fillColor' in options) && !('color' in options)) {
    					opts.fillStyle = 'black';
    				}

    				for (prop in opts) {
    					if (opts.hasOwnProperty(prop)) {
    						ctx[prop] = opts[prop];
    					}
    				}

    				if ('setLineDash' in ctx) {
    					ctx.setLineDash(('dash' in options ? options.dash : []));
    				}
    				if ('lineDashOffset' in ctx) {
    					ctx.lineDashOffset = ('dashOffset' in options ? options.dashOffset : 0);
    				}
    			}

    			// Draw the point
    			ctx.beginPath();
    			ctx.arc(point[0] / point[2], point[1] / point[2], (options.size || 10) / (screen.scale.x - screen.scale.y), 0, 2 * Math.PI);
    			ctx.closePath();
    			ctx.fill();
    			ctx.stroke();
    			ctx.restore();

    			if (options.label) {
    				dist = 1.75 * (options.size || 10) + 0.75 * (options.lineWidth || 4);
    				screen.text(options.label, point[0] / point[2] + dist / (screen.scale.x - screen.scale.y), point[1] / point[2] + dist / (screen.scale.x - screen.scale.y), options, true);
    			}

    			if (!redraw) {
    				this.stack.push({
    					type: 'point',
    					object: point,
    					options: options
    				});
    			}

    			return this;
    		},

Option name | Type | Description
--- | --- | ---
str | string | The string to be drawn
x | number | The x coordinate
y | number | The y coordinate
options | object | Optional drawing options

Writes text on the screen.

    text: function (str, x, y, options, redraw) {
    			if (typeof options === 'undefined') { options = {}; }
    			if (typeof redraw === 'undefined') { redraw = false; }
    			var defaults = {
    				font: 'Helvetica',
    				fontSize: 12,
    				//				lineWidth:  0.05,
    				textColor: 'rgba(0, 0, 0, 1)'
    			}, ctx, prop, opts;

    			ctx = this.ctx;

    			opts = MathLib.Canvas.convertOptions(MathLib.extendObject(defaults, options));

    			for (prop in opts) {
    				if (opts.hasOwnProperty(prop)) {
    					ctx[prop] = opts[prop];
    				}
    			}

    			ctx.fillStyle = MathLib.colorConvert(options.textColor || options.color || defaults.textColor);
    			ctx.strokeStyle = MathLib.colorConvert(options.textColor || options.color || defaults.textColor);

    			ctx.font = opts.fontSize + 'px ' + opts.font;
    			ctx.textAlign = 'center';
    			ctx.textBaseline = 'middle';

    			var tf = this.screen.transformation;

    			ctx.save();
    			ctx.transform(1 / tf[0][0], 0, 0, 1 / tf[1][1], 0, 0);
    			ctx.fillText(str, tf[0][0] * x, tf[1][1] * y);
    			ctx.restore();

    			if (!redraw) {
    				this.stack.push({
    					type: 'text',
    					object: str,
    					x: x,
    					y: y,
    					options: options
    				});
    			}

    			return this;
    		}
    	};
    })(MathLib || (MathLib = {}));