<PUBLIC:COMPONENT TAGNAME = "LineChart"/>

<PUBLIC:DEFAULTS viewLinkContent="true" contentEditable="false"/>

<!-- SERIES METHODS -->

<PUBLIC:METHOD NAME = "addSeries"/>
<PUBLIC:METHOD NAME = "removeSeries"/>
<PUBLIC:METHOD NAME = "DBGViewSource"/>
<PUBLIC:METHOD NAME = "DBGSetSize"/>

<!-- SIZE -->

<PUBLIC:PROPERTY NAME="max" GET="get_max" PUT="put_max"/>
<PUBLIC:PROPERTY NAME="min" GET="get_min" PUT="put_min"/>
<PUBLIC:PROPERTY NAME="width" GET="get_width" PUT="put_width"/>
<PUBLIC:PROPERTY NAME="height" GET="get_height" PUT="put_height"/>

<!-- GRIDLINES -->

<PUBLIC:PROPERTY NAME="gridSpacingY" GET="get_gridSpacingY" PUT="put_gridSpacingY"/>
<PUBLIC:PROPERTY NAME="gridVisibleX" GET="get_gridVisibleX" PUT="put_gridVisibleX"/>
<PUBLIC:PROPERTY NAME="gridVisibleY" GET="get_gridVisibleY" PUT="put_gridVisibleY"/>

<!-- POINTS -->

<PUBLIC:PROPERTY NAME="pointStyle" GET="get_pointStyle" PUT="put_pointStyle"/>

<HTML xmlns:v="urn:schemas-microsoft-com:vml">

<STYLE>
v\:* { behavior: url(#default#VML); }
</STYLE>

<SCRIPT LANGUAGE="JScript">

// Global "constants"

var LEFT_SPACING = 2;
var RIGHT_SPACING = 3;

// Globals for property values

var nWidth = 0;
var nHeight = 0;
var nMax = 0;
var nMin = 0;

var nGridSpacingY = 25;
var fGridVisibleX = true;
var fGridVisibleY = true;

var sPointStyle = "none";

// Globals used internally

var nScaleFactor = 1;
var nDataPoints = 0;
var oRedrawTimer = null;
var dctSeries = new ActiveXObject("Scripting.Dictionary");

function addSeries(sName, sDataPoints, sColor, sDescription)
{
	var aDataPoints;
	var i;
	var oNewSeries;
	var nPrevDataPoints = nDataPoints;

	// Validate incoming data before creating Series

	if ((sName == null) || (sName == ""))
	{
		// A name is required
		return false;
	}
	else if (dctSeries.Exists(sName))
	{
		// The series name has to be unique
		return false;
	}
	else if ((sDataPoints == null) || (sDataPoints == ""))
	{
		// Data points are required
		return false;
	}

	// Validate data points
	aDataPoints = sDataPoints.toString().split(",");

	// Need at least two points for a valid chart
	if (aDataPoints.length < 2)
	{
		return false;
	}

	for (i = 0; i < aDataPoints.length; i++)
	{
		if (isNaN(aDataPoints[i]))
		{
			return false;
		}
	}

	// Substitute in color if one is missing
	if ((sColor == null) || (sColor == ""))
	{
		sColor = "red";
	}

	// Make sure description is not null
	if (sDescription == null)
	{
		sDescription = "";
	}

	if (aDataPoints.length > nDataPoints)
	{
		nDataPoints = aDataPoints.length;
	}

	oNewSeries = new Series(sName, aDataPoints, sColor, sDescription);

	dctSeries.Add(sName, oNewSeries);

	setRedrawTimer();

	return true;
}

function removeSeries(sName)
{
	var i = 0;
	var aKeys = null;
	var nMostDataPoints = 0;

	if ((sName == null) || (sName == ""))
	{
		// A name is required
		return false;
	}
	else if (!dctSeries.Exists(sName))
	{
		// The series must exist
		return false;
	}

	dctSeries.Remove(sName);

	aKeys = (new VBArray(dctSeries.Keys())).toArray()
	for (i in aKeys)
	{
		if (dctSeries(aKeys[i]).dataPoints.length > nMostDataPoints)
		{
			nMostDataPoints = dctSeries(aKeys[i]).dataPoints.length;
		}
	}

	nDataPoints = nMostDataPoints;

	setRedrawTimer();

	return true;
}

function Series(sName, aDataPoints, sColor, sDescription)
{
	this.name = sName;
	this.dataPoints = aDataPoints;
	this.color = sColor;
	this.description = sDescription;
}

function DBGViewSource()
{
	alert(vChartGroup.outerHTML);
}

//***************************************************************************************************************************
//	DRAWING FUNCTIONS
//***************************************************************************************************************************

function redrawGrid(fRedrawX, fRedrawY)
{
	var nSpacing = 0;
	var nLeftOffset = 0;
	var oNewElement = null;
	var eGridLine = null;
	var i = 0;
	var nUnscaledY = 0;
	var sPath = "";

	if (fRedrawX)
	{
		nSpacing = 1 + ((nWidth - (LEFT_SPACING + RIGHT_SPACING) - (vChartBorder.strokeweight * 2 / 0.75) - nDataPoints) / (nDataPoints - 1));
		nLeftOffset = LEFT_SPACING + (vChartBorder.strokeweight / 0.75) + 1;

		for (i = 0; i < nDataPoints; i++)
		{
			sPath += "m " + parseInt(nLeftOffset + (i * nSpacing)) + ",1 l " + parseInt(nLeftOffset + (i * nSpacing)) + "," + parseInt(nHeight - 1) + " ";
		}
		vGridLineX.style.height = nHeight;
		vGridLineX.style.width = nWidth;
		vGridLineX.coordsize = nWidth + "," + nHeight;
		vGridLineX.path = sPath + "e";
		vGridLineX.style.visibility = (fGridVisibleX) ? "visible" : "hidden";
		sPath = "";
		i = 0;
	}

	if (fRedrawY)
	{
		nUnscaledY = (nMax - Math.abs(nMax % nGridSpacingY));
		while (nUnscaledY > nMin)
		{
			sPath += "m 1," + parseInt((nMax - nUnscaledY) * nScaleFactor) + " l " + parseInt(nWidth - 1) + "," + parseInt((nMax - nUnscaledY) * nScaleFactor) + " ";
			nUnscaledY -= nGridSpacingY;
			i++;
		}
		vGridLineY.style.height = nHeight;
		vGridLineY.style.width = nWidth;
		vGridLineY.coordsize = nWidth + "," + nHeight;
		vGridLineY.path = sPath + "e";
		vGridLineY.style.visibility = (fGridVisibleY) ? "visible" : "hidden";
	}
}

function redrawLines()
{
	// Redraw lines

	var aKeys = null;
	var sPath = "";
	var j = 0;
	var nUnscaledY = 0;
	var nScaledY = 0;
	var nSpacing = 1 + ((nWidth - (LEFT_SPACING + RIGHT_SPACING) - (vChartBorder.strokeweight * 2 / 0.75) - nDataPoints) / (nDataPoints - 1));
	var nLeftOffset = LEFT_SPACING + (vChartBorder.strokeweight / 0.75) + 1;
	var aElements = null;

	// Take out old series lines

	aElements = document.all.tags("shape");
	for (i = aElements.length - 1; i >= 0; i--)
	{
		if (aElements[i].id.match(/^vSeries\d*Line/))
		{
			vChartGroup.removeChild(aElements[i]);
		}
	}

	aKeys = (new VBArray(dctSeries.Keys())).toArray()
	for (j in aKeys)
	{
		// Go through data points, build the path string
		sPath = "";

		for (i = 0; i < dctSeries(aKeys[j]).dataPoints.length; i++)
		{
			nUnscaledY = dctSeries(aKeys[j]).dataPoints[i];
			nScaledY = parseInt((nMax - nUnscaledY) * nScaleFactor);

			if (nUnscaledY > nMax)
			{
				if (i > 0)
				{
					// Do we need to draw a line to ourselves?

					if (dctSeries(aKeys[j]).dataPoints[i - 1] < nMax)
					{
						sPath += "l " + parseInt(nLeftOffset + (nSpacing * (i - 1)) + (((nMax - dctSeries(aKeys[j]).dataPoints[i - 1]) * nSpacing)/(nUnscaledY - dctSeries(aKeys[j]).dataPoints[i - 1]))) + ",0 ";
					}
				}
				if (i <= dctSeries(aKeys[j]).dataPoints.length - 1)
				{
					// Do we need to position the cursor ahead of ourselves?

					if (dctSeries(aKeys[j]).dataPoints[i + 1] < nMax)
					{
						sPath += "m " + parseInt(nLeftOffset + (nSpacing * i) + (((nUnscaledY - nMax) * nSpacing)/(nUnscaledY - dctSeries(aKeys[j]).dataPoints[i + 1]))) + ",0 ";
					}
				}
			}
			else if (nUnscaledY < nMin)
			{
				if (i > 0)
				{
					// Do we need to draw a line to ourselves?

					if (dctSeries(aKeys[j]).dataPoints[i - 1] > nMin)
					{
						sPath += "l " + parseInt(nLeftOffset + (nSpacing * (i - 1)) + (((dctSeries(aKeys[j]).dataPoints[i - 1] - nMin) * nSpacing)/(dctSeries(aKeys[j]).dataPoints[i - 1] - nUnscaledY))) + "," + nHeight + " ";
					}
				}
				if (i <= dctSeries(aKeys[j]).dataPoints.length - 1)
				{
					// Do we need to position the cursor ahead of ourselves?

					if (dctSeries(aKeys[j]).dataPoints[i + 1] > nMin)
					{
						sPath += "m " + parseInt(nLeftOffset + (nSpacing * i) + (((nUnscaledY - nMin) * nSpacing)/(nUnscaledY - dctSeries(aKeys[j]).dataPoints[i + 1]))) + "," + nHeight + " ";
					}
				}

			}
			else
			{
				sPath += (i == 0) ? "m " : "l ";
				sPath += parseInt(nLeftOffset + (i * nSpacing)) + "," + parseInt(nScaledY) + " ";
			}
		}
		sPath += " e";

		oNewElement = document.createElement("v:shape");
		oNewElement.id = "vSeries" + j + "Line";
		oNewElement.style.width = "100%";
		oNewElement.style.height = "100%";
		oNewElement.style.zIndex = (j + 1);
		oNewElement.filled = "false";
		oNewElement.stroked = "true";
		oNewElement.strokecolor = dctSeries(aKeys[j]).color;
		oNewElement.strokeweight = "1px";
		oNewElement.path = sPath;
		vChartGroup.appendChild(oNewElement);
		oNewElement = null;
	}
}

function redrawPoints()
{
	// Redraw points

	var aKeys = null;
	var j = 0;
	var nUnscaledY = 0;
	var nScaledY = 0;
	var nSpacing = 1 + ((nWidth - (LEFT_SPACING + RIGHT_SPACING) - (vChartBorder.strokeweight * 2 / 0.75) - nDataPoints) / (nDataPoints - 1));
	var nLeftOffset = LEFT_SPACING + (vChartBorder.strokeweight / 0.75) + 1;
	var sHTML = "";

	var aElements = document.all.tags("rect");
	for (i = aElements.length - 1; i >= 0; i--)
	{
		if (aElements[i].id.match(/^vSeries\d*DataPoint/))
		{
			vChartGroup.removeChild(aElements[i]);
		}
	}
	var aElements = document.all.tags("oval");
	for (i = aElements.length - 1; i >= 0; i--)
	{
		if (aElements[i].id.match(/^vSeries\d*DataPoint/))
		{
			vChartGroup.removeChild(aElements[i]);
		}
	}

	if (sPointStyle != "none")
	{
		aKeys = (new VBArray(dctSeries.Keys())).toArray()
		for (j in aKeys)
		{
			// Go through data points and draw dots

			for (i = 0; i < dctSeries(aKeys[j]).dataPoints.length; i++)
			{
				nUnscaledY = dctSeries(aKeys[j]).dataPoints[i];
				nScaledY = (nMax - nUnscaledY) * nScaleFactor;

				if ((parseInt(nScaledY) > 2) && (parseInt(nScaledY) < nHeight - 2))
				{
					if (sPointStyle == "squares")
					{
						sHTML += "<v:rect ";
					}
					else
					{
						sHTML += "<v:oval ";
					}
					sHTML += "ID=\"vSeries" + j + "DataPoint" + i + "\" ";
					sHTML += "TITLE=\"" + parseInt(nUnscaledY) + "\" ";
					sHTML += "STYLE=\"HEIGHT:3px;WIDTH:3px;TOP:" + (parseInt(nScaledY) - 1) + ";";
					sHTML += "LEFT:" + (parseInt(nLeftOffset + (i * nSpacing)) - 1) + ";Z-INDEX:" + (j + 1) + "\" ";
					sHTML += "STROKED=\"false\" ";
					sHTML += "FILLCOLOR=\"" + dctSeries(aKeys[j]).color + "\"/>";
				}
			}
		}
		vChartGroup.insertAdjacentHTML("beforeEnd", sHTML);
	}
}

function setRedrawTimer()
{
	if (oRedrawTimer != null)
	{
		window.clearTimeout(oRedrawTimer);
		oRedrawTimer = null;
	}
	oRedrawTimer = window.setTimeout(onTimer, 0);
}

function onTimer()
{
	oRedrawTimer = null;
	drawChart();
}

function drawChart()
{
	var nScaledY = 0;

	vChartGroup.style.height = nHeight;
	vChartGroup.coordsize.y = nHeight;
	vChartGroup.style.width = nWidth;
	vChartGroup.coordsize.x = nWidth;

	redrawGrid(true, true);
	redrawLines();
	redrawPoints();
}

//***************************************************************************************************************************
//	SIZING FUNCTIONS
//***************************************************************************************************************************

function get_width()
{
	return nWidth;
}

function put_width(nNewWidth)
{
	if (!isNaN(nNewWidth) && (nNewWidth != nWidth))
	{
		nWidth = parseFloat(nNewWidth);
		if (document.readyState == "complete")
		{
			setRedrawTimer();
		}
		else
		{
			vChartGroup.style.width = nWidth;
			vChartGroup.coordsize.x = nWidth;
		}
	}
}

function get_height()
{
	return nHeight;
}

function put_height(nNewHeight)
{
	if (!isNaN(nNewHeight) && (nNewHeight != nHeight))
	{
		nHeight = parseFloat(nNewHeight);
		recalcScaleFactor();
		if (document.readyState == "complete")
		{
			setRedrawTimer();
		}
		else
		{
			vChartGroup.style.height = nHeight;
			vChartGroup.coordsize.y = nHeight;
		}
	}
}

function get_max()
{
	return nMax;
}

function put_max(nNewMax)
{
	if (!isNaN(nNewMax) && (nNewMax > nMin) && (nNewMax != nMax))
	{
		nMax = parseFloat(nNewMax);
		recalcScaleFactor();
		setRedrawTimer();
	}
	else
	{
		return false;
	}
}

function get_min()
{
	return nMin;
}

function put_min(nNewMin)
{
	if (!isNaN(nNewMin) && (nNewMin < nMax) && (nNewMin != nMin))
	{
		nMin = parseFloat(nNewMin);
		recalcScaleFactor();
		setRedrawTimer();
	}
	else
	{
		return false;
	}
}

function recalcScaleFactor()
{
	nScaleFactor = (nHeight / (nMax - nMin));
}

//***************************************************************************************************************************
//	GRID FUNCTIONS
//***************************************************************************************************************************

function get_gridSpacingY()
{
	return nGridSpacingY;
}

function put_gridSpacingY(nNewGridSpacingY)
{
	var oNewElement = null;
	var nUnscaledY = 0;
	var i;

	if ((!isNaN(nNewGridSpacingY)) && (nNewGridSpacingY > 0) && (nNewGridSpacingY != nGridSpacingY))
	{
		nGridSpacingY = parseFloat(nNewGridSpacingY);
		redrawGrid(false, true);
	}
}

function get_gridVisibleX()
{
	return fGridVisibleX;
}

function put_gridVisibleX(sNewGridVisibleX)
{
	var fNewGridVisibleX = ((sNewGridVisibleX.match(/^true$/i) != null) || (sNewGridVisibleX.match(/^yes$/i) != null));
	if (fNewGridVisibleX != fGridVisibleX)
	{
		fGridVisibleX = fNewGridVisibleX;
		vGridLineX.style.visibility = (fGridVisibleX) ? "visible" : "hidden";
	}
}

function get_gridVisibleY()
{
	return fGridVisibleY;
}

function put_gridVisibleY(sNewGridVisibleY)
{
	var fNewGridVisibleY = ((sNewGridVisibleY.match(/^true$/i) != null) || (sNewGridVisibleY.match(/^yes$/i) != null));
	if (fNewGridVisibleY != fGridVisibleY)
	{
		fGridVisibleY = fNewGridVisibleY;
		vGridLineY.style.visibility = (fGridVisibleY) ? "visible" : "hidden";
	}
}

//***************************************************************************************************************************
//	POINT FUNCTIONS
//***************************************************************************************************************************

function get_pointStyle()
{
	return sPointStyle;
}

function put_pointStyle(sNewPointStyle)
{
	if ((sNewPointStyle.match(/^dots$/i) != null) || (sNewPointStyle.match(/^circles$/i) != null) && (sPointStyle != "circles"))
	{
		sPointStyle = "circles";
		redrawPoints();
	}
	else if ((sNewPointStyle.match(/^squares$/i) != null) || (sNewPointStyle.match(/^boxes$/i) != null) && (sPointStyle != "squares"))
	{
		sPointStyle = "squares";
		redrawPoints();
	}
	else if ((sNewPointStyle.match(/^none$/i) != null) || (sNewPointStyle.match(/^false$/i) != null) && (sPointStyle != "none"))
	{
		sPointStyle = "none";
		redrawPoints();
	}
	else
	{
		return false;
	}
}

</SCRIPT>

<v:group
	ID="vChartGroup"
	STYLE="POSITION:relative;WIDTH:0px; HEIGHT:0px;"
	COORDSIZE="0,0">

<v:rect
	ID="vChartBorder"
	STYLE="LEFT:0; TOP:0; WIDTH:100%; HEIGHT:100%; Z-INDEX: 100"
	STROKECOLOR="black"
	STROKEWEIGHT="1px"
	FILLED="false"/>

<v:shape
	ID="vGridLineX"
	STYLE="WIDTH:0px; HEIGHT:0px; Z-INDEX:0; VISIBILITY:hidden"
	FILLED="false"
	STROKED="true";
	STROKECOLOR="#EEEEEE"
	STROKEWEIGHT="1px";
	/>

<v:shape
	ID="vGridLineY"
	STYLE="WIDTH:0px; HEIGHT:0px; Z-INDEX:0; VISIBILITY:hidden"
	FILLED="false"
	STROKED="true";
	STROKECOLOR="#EEEEEE"
	STROKEWEIGHT="1px";
	/>

</v:group>

</HTML>