
/*
** jsGraph
**
** Developed by Chris Doyle
**
** www.chrisAdoyle.com/jsGraph
**
** Licensed under Creative Commons Attribution 3.0 United States License
**
**
*/


function viz_test() {
	var chart = new Chart({
		backgroundColor: 	"#BBB"
		,limitXY: [-1.0, -1.0, 6.0, 5.0]
	});
	var lColor = "g";
	var line  = new Line([0, 0], [1, 3], {color:lColor});
	chart.addMarking(line);
	var line  = new Line([1, 3], [2, 1], {color:lColor});
	chart.addMarking(line);
	var line  = new Line([2, 1], [5, 4], {color:lColor});
	chart.addMarking(line);
	var label = new Label("Major Recession", [-0.4, 3.3], {backgroundColor: "#FFF", border: "none"});
	chart.addMarking(label);
	var label = new Label("A brighter future?", [2.9, 4.3], {backgroundColor: "#FFF", border: "none"});
	chart.addMarking(label);
	var chartNode = chart.toDOM();
	var docNode = document.getElementById('jsGraphExample');
	docNode.appendChild(chartNode);
}

function Chart(options)
{
	var defaults = {
		 backgroundColor: 	"#FFF"
		,border: 			"solid 1px black"
		,limitXY:			[-1.0 ,-1.0 , 5.0 , 5.0 ]	//	minX, minY, maxX, maxY
		,tickXY:			[ 2.0 , 1.0 , 2.0 , 0.5 ]	// majorX, minorX, majorY, minorY
	}
	setInstanceVariables(this, options || {}, defaults);
	
	this.width				= 300;
	this.height				= 300;
	
	this.minX = this.limitXY[0];
	this.minY = this.limitXY[1];
	this.maxX = this.limitXY[2];
	this.maxY = this.limitXY[3];
	
	this.scale					= this.width / (this.maxX - this.minX);	// How many pixels per unit length?
	
	this.markings				= [];
}

function setInstanceVariables(obj, options, defaults)
{
	for (var i in defaults) {
		obj[i] = options[i] || defaults[i];
	}
}

function Chart_toDOM()
{
	this.displayNode = document.createElement('div');
	this.displayNode.style.height		= this.height + 'px';
	this.displayNode.style.width		= this.width  + 'px';
	this.displayNode.style.background	= this.backgroundColor;
	this.displayNode.style.border		= this.border;
	this.displayNode.style.position		= 'relative';
	this.displayNode.style.overflow		= 'hidden';
	
	// Draw all of our lines and whatnot
	//
	var mark;
	var mLength = this.markings.length;
	for (i = 0; i < mLength; i++) {
		mark = this.markings[i];
		mark.toDOM(this);
		this.displayNode.appendChild(mark.displayNode);
	}
	
	// Add tick marks
	//
	for (var i = 0; i < this.tickXY.length; i++) {
		if (this.tickXY[i] != null) {
			this.toDOM_ticks(this.tickXY[i], i);	// Including the counter lets us know whether it was major/minor X/Y
		}
	}
	
	return this.displayNode;
}
Chart.prototype.toDOM = Chart_toDOM;

function Chart_toDOM_ticks(spacing, type)
{
	// Find the leftmost/bottommost multiple of spacing
	// Also specify tick dimensions and which need placement
	//
	var tickWidth;
	var tickHeight;
	var position;
	var positionCorrection;
	var firstTick;
	var bigDim = 8;
	var medDim = 3;
	var smDim  = 1;
	switch (type) {
		
		// Major X
		//
		case 0:
			tickWidth 	= smDim;
			tickHeight 	= bigDim;
			position	= "bottom:0px;left:"
			firstTick	= Math.floor(this.minX / spacing) * spacing;
			positionCorrection = this.minX;
			break;
		
		// Minor X
		//
		case 1:
			tickWidth 	= smDim;
			tickHeight	= medDim;
			position = "bottom:0px;left:"
			firstTick	= Math.floor(this.minX / spacing) * spacing;
			positionCorrection = this.minX;
			break;
		
		// Major Y
		//
		case 2:
			tickHeight 	= smDim;
			tickWidth	= bigDim;
			position = "left:0px;bottom:"
			firstTick	= Math.floor(this.minY / spacing) * spacing;
			positionCorrection = this.minY;
			break;
		
		// Minor Y
		//
		case 3:
			tickHeight 	= smDim;
			tickWidth	= medDim;
			position = "left:0px;bottom:"
			firstTick	= Math.floor(this.minY / spacing) * spacing;
			positionCorrection = this.minY;
			break;
	}
	
	var tickHTML = [];
	var i = firstTick;
	while (i <= this.maxX) {
		tickHTML.push("<div style='background:black;height:");
		tickHTML.push(tickHeight);
		tickHTML.push("px;width:");
		tickHTML.push(tickWidth);
		tickHTML.push("px;position:absolute;");
		tickHTML.push(position);
		tickHTML.push((i - positionCorrection ) * this.scale);
		tickHTML.push("px;'></div>");
		i += spacing;
	}
	
	// Put it in the DOM
	//
	this.displayNode.innerHTML += tickHTML.join('');
}
Chart.prototype.toDOM_ticks = Chart_toDOM_ticks;

function Chart_addMarking(marking)
{
	this.markings.push(marking);
}
Chart.prototype.addMarking = Chart_addMarking;

function Line(pointOneXY, pointTwoXY, options)
{
	if (pointOneXY[0] == pointTwoXY[0] && pointOneXY[1] == pointTwoXY[1]) return;  // Can't be the same
	
	var defaults = {
		color:   	"green"
	};
	setInstanceVariables(this, options || {}, defaults);
	
	// Start is always to the left of End.  If x coordinate is the same, Start is always above End.
	//
	if (pointOneXY[0] < pointTwoXY[0]
	|| pointOneXY[0] == pointTwoXY[0] && pointOneXY[1] > pointTwoXY[1]) {
		this.startXY	= pointOneXY;
		this.endXY		= pointTwoXY;
	} else {
		this.startXY	= pointTwoXY;
		this.endXY		= pointOneXY;
	}
	
	this.imageURL = "point"
	switch (this.color) {
		case "green":
		case "g":
			this.imageURL += "G";
			break;
		case "blue":
		case "b":
			this.imageURL += "B";
			break;
		default:
			this.imageURL += "G";
			break;
	}
	this.imageURL += ".png"
	this.imageWidth = 6;
}

function Line_toDOM(chart)
{
	if (!chart) return;
	
	var startX	= this.startXY[0]	* 1.0;	// Make them all floats
	var startY	= this.startXY[1]	* 1.0;
	var endX	= this.endXY[0]		* 1.0;
	var endY	= this.endXY[1]		* 1.0;
	
	var rise	= endY - startY;
	var run		= endX - startX;	// Always positive
	var slope	= rise / run;
	
	var scale	= chart.scale;		// How many pixels per unit length?
	
	// console.log('Rise', rise, ', run', run, ', slope', slope, ', scale', scale);
	
	this.displayNode				= document.createElement('div');
	this.displayNode.style.width	= scale * run;
	this.displayNode.style.height	= scale * Math.abs(rise);
	// this.displayNode.style.background = 'green';
	this.displayNode.style.position	= 'absolute';
	this.displayNode.style.bottom	= (startY - chart.minY) * scale;
	this.displayNode.style.left		= (startX - chart.minX) * scale;
	
	// We need to populate the longer dimension with divs, then move them appropriately along the shorter dimension
	//
	if (rise > run) {
		// console.log("bigger rise");
		var position			= "left:0px;top:";
		var move				= "left";
		var divsRequired 		= scale * rise;
		var correctionFactor 	= 1/slope;
	} else {
		// console.log("bigger run");
		var position 			= "bottom:0px;left:";
		var move	 			= "bottom";
		var divsRequired 		= scale * run;
		var correctionFactor 	= slope;
	}
	
	var i = 0;
	var lineDivs = [];
	var xyCorrection = this.imageWidth / 2.0;
	while (i < divsRequired) {
		lineDivs.push("<div style='position:absolute;");
		lineDivs.push(position);
		lineDivs.push(i - xyCorrection);
		lineDivs.push("px;height:6px;width:6px;background:url(");
		lineDivs.push(this.imageURL);
		lineDivs.push(")'></div>");
		i++;
	}

	this.displayNode.innerHTML = lineDivs.join('');

	// Go back through and set the other position - the one we need to calculate
	//
	if (rise > run) {
		i = 0;
		j = divsRequired;
		while (i < divsRequired) {
			this.displayNode.childNodes[i].style[move] = Math.floor(j * correctionFactor) - xyCorrection;
			i++;
			j--;
		}
	} else {
		i = 0;
		while (i < divsRequired) {
			this.displayNode.childNodes[i].style[move] = Math.floor(i * correctionFactor) - xyCorrection;
			i++;
		}
	}
	
	return this.displayNode;
}
Line.prototype.toDOM = Line_toDOM;

function Label(labelText, position, options)
{
	this.labelText = labelText;
	this.positionX = position[0];
	this.positionY = position[1];
	
	var defaults = {
		border: "solid 1px black"
		,backgroundColor: "#FFF"
	};
	setInstanceVariables(this, options || {}, defaults);
}

function Label_toDOM(chart)
{
	if (!this.positionX || !this.positionY) return;	// Need some positioning info
	
	var padding = 2;
	
	this.displayNode				= document.createElement('div');
	this.displayNode.innerHTML		= this.labelText;
	this.displayNode.style.padding 	= padding + "px";
	this.displayNode.style.background = this.backgroundColor;
	this.displayNode.style.border 	= this.border;
	this.displayNode.style.position = 'absolute';
	this.displayNode.style.bottom 	= (this.positionY - chart.minY) * chart.scale;
	this.displayNode.style.left 	= (this.positionX - chart.minX) * chart.scale;
	this.displayNode.style.whiteSpace = "nowrap";
	this.displayNode.style.fontFamily = "Verdana, Arial";
	this.displayNode.style.fontSize = "9pt";
	this.displayNode.style.fontWeight = "bold";
	
	// Need to measure this div to generate the corners
	//
	var measDiv 					= document.createElement('span');
	measDiv.innerHTML 				= this.labelText;
	measDiv.style.visibility 		= "hidden";
	measDiv.style.whiteSpace 		= "nowrap";
	measDiv.style.fontFamily 		= "Verdana, Arial";
	measDiv.style.fontSize 			= "9pt";
	measDiv.style.fontWeight 		= "bold";
	document.body.appendChild(measDiv);
	this.displayNode.style.height	= measDiv.offsetHeight;
	this.displayNode.style.width	= measDiv.offsetWidth;
	
	// Round the corners
	//
	roundBorder(this.displayNode, 4, measDiv.offsetHeight + padding*2, measDiv.offsetWidth + padding*2, "white");
	
	// Done measuring
	//
	document.body.removeChild(measDiv);
	
	return this.displayNode;
}
Label.prototype.toDOM = Label_toDOM;





function roundBorder(div, size, height, width, color)
{
	var div;
	var size;
	var color;
	
	//   left, top, height, width
	var top = [0, -size, size, width, 'top'];
	var bottom = [0, height, size, width, 'bottom'];
	var left = [-size, 0, height, size, 'left'];
	var right = [width, 0, height, size, 'right'];
	var topleft = ['_tl.png', -size, -size, size, size];
	var topright = ['_tr.png', width, -size, size, size];
	var bottomleft = ['_bl.png', -size, height, size, size];
	var bottomright = ['_br.png', width, height, size, size];

	var borderHTML = [];

	var squares = [top, left, bottom, right];
	var style;
	for (var i=0; i < 4; i++) {
		style = squares[i];
		borderHTML.push('<div id="');
		borderHTML.push(style[4]);
		borderHTML.push('" style="position:absolute;left:');
		borderHTML.push(style[0]);
		borderHTML.push('px;top:');
		borderHTML.push(style[1]);
		borderHTML.push('px;height:');
		borderHTML.push(style[2]);
		borderHTML.push('px;width:');
		borderHTML.push(style[3]);
		borderHTML.push('px;background:');
		borderHTML.push(color);
		borderHTML.push(';"></div>');
	}

	var corners = [topleft, topright, bottomleft, bottomright];
	var style;
	for (var i=0; i < 4; i++) {
		style = corners[i];
		borderHTML.push('<img src="');
		borderHTML.push(color);
		borderHTML.push(style[0]);
		borderHTML.push('" style="position:absolute;left:');
		borderHTML.push(style[1]);
		borderHTML.push('px;top:');
		borderHTML.push(style[2]);
		borderHTML.push('px;" height="');
		borderHTML.push(style[3]);
		borderHTML.push('" width="');
		borderHTML.push(style[4]);
		borderHTML.push('">');
	}

	div.innerHTML += borderHTML.join('');

}
