///
///
///
declare var $;
class Graph {
style: Styles;
private _context: any;
private _xMin: number;
private _xMax: number;
private _yMin: number;
private _yMax: number;
private _xLength: number;
private _yLength: number;
private _width: number;
private _height: number;
private _pointWidth: number;
private _lineWidth: number;
private _axes: boolean;
private _gridLines: boolean;
private _tabs: boolean;
private _strokeStyle: string = "#000000";
private _fillStyle: string = "#000000";
private _scale: Scale;
private _maxTicks: number = 10;
private mouseDown: boolean = false;
public xZoom: boolean = true;
public yZoom: boolean = true;
private _trace: Expression;
private _shapes: Array;
constructor(canvas: HTMLCanvasElement, xMin : number = -10, xMax : number = 10, yMin: number = -10, yMax: number = 10,
axes: boolean = true, gridlines: boolean = true, tabs: boolean = true, style: Styles = new Styles()) {
this._context = canvas.getContext("2d");
this.style = style;
this._height = canvas.height;
this._width = canvas.width;
this._xMin = xMin;
this._xMax = xMax;
this._yMin = yMin;
this._yMax = yMax;
this._axes = axes;
this._gridLines = gridlines;
this._tabs = tabs;
this._scale = new Scale(this);
this._shapes = [];
this.drag();
this.zoom();
this.interpolate();
this.update();
}
interpolate() {
var graph: Graph = this;
$(canvas).mousemove(function(e) {
graph.update();
var x: number = e.pageX - $(this).parent().offset().left;
var origin: {x: number; y: number;} = new Point(0, 0).toCanvas(graph);
x = (x - origin.x) * graph.xResolution;
var fn: Expression = graph._trace;
if(typeof fn !== "undefined") {
var y: number = fn.f(x);
new Point(x, y, fn.color, 3).draw(graph);
var label = new Label(new Point(graph._xMin, graph._yMin), "(" + x + ", " + y + ")");
graph.context.textBaseline = "bottom";
label.color = fn.color;
label.draw(graph);
graph.context.textBaseline = "alphabetic";
}
});
}
public add(shape: Drawable) {
this._shapes.push(shape);
shape.add(this);
if(shape instanceof Expression) {
this.trace = shape;
}
this.update();
}
public remove(shape: Drawable) {
for (var i: number = this._shapes.length - 1; i >= 0; i--) {
if(this._shapes[i].equals(shape)) {
this._shapes.splice(i, 1);
}
}
shape.remove(this);
this.update();
}
update(recalculate: boolean = true) {
this._context.clearRect(0, 0, this._context.canvas.width, this._context.canvas.height);
if (recalculate) {
this._xLength = this._xMax - this._xMin;
this._yLength = this._yMax - this._yMin;
if (this._xLength <= 0) {
throw "xMax must be greater than or equal to xMin";
}
if (this._yLength <= 0) {
throw "yMax must be greater than or equal to yMin";
}
this._scale.scale();
}
if (this._gridLines) {
this.drawGridlines();
}
if (this._axes) {
this.drawAxes();
}
if (this._tabs) {
this.drawTabs();
}
this.drawLabels();
var graph: Graph = this;
this._shapes.forEach(function(shape) {
shape.draw(graph);
});
}
private drag(): void {
var context: CanvasRenderingContext2D = this._context;
var graph: Graph = this;
var canvas: HTMLCanvasElement = context.canvas;
var offset = $(canvas).offset();
var newX: number;
var newY: number;
var oldX: number;
var oldY: number;
$(canvas).mousedown(function(e) {
graph.mouseDown = true;
oldX = e.pageX - offset.left;
oldY = e.pageY - offset.top;
});
$(document).mouseup(function() {
graph.mouseDown = false;
});
$(canvas).mousemove(function(e) {
if (graph.mouseDown) {
var newX: number = e.pageX - offset.left;
var newY: number = e.pageY - offset.top;
var oldXLength: number = graph.xLength;
var oldYLength: number = graph.yLength;
var xChange = Number(((newX - oldX) * graph.xResolution).toPrecision(1));
var yChange = Number(((newY - oldY) * graph.yResolution).toPrecision(1));
var xMin = graph.xMin - xChange;
var xMax = graph.xMax - xChange;
var yMin = graph.yMin + yChange;
var yMax = graph.yMax + yChange;
graph.setWindow(xMin, xMax, yMin, yMax);
oldX = newX;
oldY = newY;
}
});
}
private zoom(): void {
var graph: Graph = this;
$(canvas).mousewheel(function(e) {
e.preventDefault();
e.stopPropagation();
var parentOffset = $(this).parent().offset();
var x: number = e.pageX - parentOffset.left;
var y: number = e.pageY - parentOffset.top;
var delta: number = e.deltaY;
var factor: number = 1 + delta / 1000;
var origin = new Point(0, 0).toCanvas(graph);
var xOffset = (x - origin.x) * graph.xResolution;
var yOffset = (y - origin.y) * graph.yResolution;
var xMin: number = (graph.xZoom) ? Number(((graph.xMin - xOffset) * factor + xOffset).toPrecision(21)) : graph.xMin;
var xMax: number = (graph.xZoom) ? Number(((graph.xMax - xOffset) * factor + xOffset).toPrecision(21)) : graph.xMax;
var yMin: number = (graph.yZoom) ? Number(((graph.yMin + yOffset) * factor - yOffset).toPrecision(21)) : graph.yMin;
var yMax: number = (graph.yZoom) ? Number(((graph.yMax + yOffset) * factor - yOffset).toPrecision(21)) : graph.yMax;
console.log()
graph.setWindow(xMin, xMax, yMin, yMax);
});
}
private drawAxes(): void {
var xAxis: Line = new Line(new Point(this._xMin, 0), new Point(this._xMax, 0), "black", 2);
var yAxis: Line = new Line(new Point(0, this._yMin), new Point(0, this._yMax), "black", 2);
xAxis.draw(this);
yAxis.draw(this);
}
private drawGridlines(): void {
var oldLine: string = this.style.line;
var xScale: number = this._scale.minorXScale;
var yScale: number = this._scale.minorYScale;
var x: number;
var y: number;
var color: string = this.style.minorGridLines;
var line: Line;
for (x = this._scale.minorXMin; x < this._scale.minorXMax; x += xScale) {
line = new Line(new Point(x, this._yMin), new Point(x, this._yMax), color);
line.draw(this);
}
for (y = this._scale.minorYMin; y < this._scale.minorYMax; y += yScale) {
line = new Line(new Point(this._xMin, y), new Point(this._xMax, y), color);
line.draw(this);
}
xScale = this._scale.majorXScale;
yScale = this._scale.majorYScale;
color = this.style.majorGridLines;
for (x = this._scale.majorXMin; x < this._scale.majorXMax; x += xScale) {
line = new Line(new Point(x, this._yMin), new Point(x, this._yMax), color);
line.draw(this);
}
for (y = this._scale.majorYMin; y < this._scale.majorYMax; y += yScale) {
line = new Line(new Point(this._xMin, y), new Point(this._xMax, y), color);
line.draw(this);
}
}
drawTabs(color: string = "black") {
this._context.strokeStyle = color;
var tabWidth: number;
var tabHeight: number;
var majorTabWidth: number = 8 * this.xResolution;
var majorTabHeight: number = 8 * this.yResolution;
var minorTabWidth: number = 4 * this.xResolution;
var minorTabHeight: number = 4 * this.yResolution;
this.style.lineWidth = 1;
var i: number;
var line: Line;
var x: number;
var y: number;
//Minor tabs
var xScale: number = this._scale.minorXScale;
var yScale: number = this._scale.minorYScale;
for (x = this._scale.minorXMin; x < this._scale.minorXMax; x += xScale) {
line = new Line(new Point(x, -minorTabHeight), new Point(x, minorTabHeight));
line.draw(this);
}
for (y = this._scale.minorYMin; y < this._scale.minorYMax; y += yScale) {
line = new Line(new Point(-minorTabWidth, y), new Point(minorTabWidth, y));
line.draw(this);
}
//Major tabs
var xScale: number = this._scale.majorXScale;
var yScale: number = this._scale.majorYScale;
for (x = this._scale.majorXMin; x < this._scale.majorXMax; x += xScale) {
line = new Line(new Point(x, -majorTabHeight), new Point(x, majorTabHeight));
line.draw(this);
}
for (y = this._scale.majorYMin; y < this._scale.majorYMax; y += yScale) {
line = new Line(new Point(-majorTabWidth, y), new Point(majorTabWidth, y));
line.draw(this);
}
}
drawLabels(): void {
var xScale = this._scale.majorXScale;
var point: Point;
var pixels: number = 20;
for (var i = this._scale.majorXMin; i < this._scale.majorXMax; i += xScale) {
//prevent it from plotting zero
if (Math.abs(i) > xScale / 2) {
if (this.yMin > 0) {
point = new Point(i, this.yMin + pixels * this.yResolution);
} else if (this.yMax < 0) {
point = new Point(i, this.yMax - pixels * this.yResolution);
} else if (xScale < -this.yMin) {
point = new Point(i, -this.yResolution * pixels);
} else {
point = new Point(i, this.yResolution * pixels);
}
var message: string = parseFloat(i.toPrecision(8)) .toString();
if (Math.log(Math.abs(i)) / Math.log(10) > 5) {
message = i.toExponential();
}
new Label(point, message, "start", true).draw(this);
}
}
var yScale: number = this._scale.majorYScale;
this.context.textAlign = "end";
pixels = 15;
for (var i = this._scale.majorYMin; i < this._scale.majorYMax; i += yScale) {
var align: string = "right";
if (Math.abs(i) > yScale / 2) {
if (this.xMin > 0) {
point = new Point(this.xMin + pixels * this.xResolution, i - 5 * this.yResolution);
align = "left";
} else if (this.xMax < 0) {
point = new Point(this.xMax - pixels * this.xResolution, i - 5 * this.yResolution);
} else if (yScale < -this.xMin) {
point = new Point(-this.yResolution * pixels, i - 5 * this.yResolution);
} else {
point = new Point(this.yResolution * pixels, i - 5 * this.yResolution);
align = "left";
}
var message: string = parseFloat(i.toPrecision(8)).toString();
if (Math.log(Math.abs(i)) / Math.log(10) > 5) {
message = i.toExponential();
}
new Label(point, message, align).draw(this);
}
}
}
get xMin(): number {
return this._xMin;
}
set xMin(value: number) {
this._xMin = value;
this.update();
}
get xMax(): number {
return this._xMax;
}
set xMax(value: number) {
this._xMax = value;
this.update();
}
get yMin(): number {
return this._yMin;
}
set yMin(value: number) {
this._yMin = value;
this.update();
}
get yMax(): number {
return this._yMax;
}
set yMax(value: number) {
this._yMax = value;
this.update();
}
get xLength(): number {
return this._xLength;
}
get yLength(): number {
return this._yLength;
}
get majorXScale(): number {
return this._scale.majorXScale;
}
set majorXScale(value: number) {
this._scale.majorXScale = value;
this.update(false);
}
get minorXScale(): number {
return this._scale.minorXScale;
}
set minorXScale(value: number) {
this._scale.minorXScale = value;
this.update(false);
}
get majorYScale(): number {
return this._scale.majorYScale;
}
set majorYScale(value: number) {
this._scale.majorYScale = value;
this.update(false);
}
get minorYScale(): number {
return this._scale.minorYScale;
}
set minorYScale(value: number) {
this._scale.minorXScale = value;
this.update(false);
}
get maxTicks() : number {
return this._maxTicks;
}
set maxTicks(v : number) {
this._maxTicks = v;
this.update();
}
get width(): number {
return this._width;
}
get height(): number {
return this._height;
}
get xResolution(): number {
return this._xLength / this._width;
}
get yResolution(): number {
return this._yLength / this.height;
}
get context(): CanvasRenderingContext2D {
return this._context;
}
public get trace() : Expression {
return this._trace;
}
public set trace(v : Expression) {
this._trace = v;
}
setWindow(xMin: number = -10, xMax: number = 10, yMin: number = -10, yMax: number = 10) {
this._xMin = xMin;
this._xMax = xMax;
this._yMin = yMin;
this._yMax = yMax;
this.update();
}
}