
/*================================================================================

 XYGraph.js : v1.0

 by J. Gebelein, Last Updated 11.05.04

 Contact: info@Structura.info

 All rights reserved. Copyright 2004 Structura.info


 This script generates an XY Graph using Vector Markup Language (VML) and returns
 an html string for display via javascript.

 As an engineer, I found a severe lack of useful graphing programs for scientific
 functions and data sets.  I wrote this program to satisfy my own needs, and hope
 that it will be useful to you.

 Use of this code is free for all non-commercial websites.  If you find this code
 to be worthy, I will gladly accept PayPal donations to info@Structura.info.  For
 commercial use I will ask for a modest one-time licensing fee.

 Please leave this header intact if you intend on sharing this code.

==================================================================================

"XYGraph" object documentation

Initialize: 
	var MyGraph = new XYGraph();
	MyGraph.xmax = 10; // set properties as desired
		
Properties: 
	See constructor script with inline comments below.
	
Methods:
	MyGraph.Plot(XYLine [, XYLine_1, ...]) // returns html code for display

Notes:

 Input x,y coordinate pairs using the "XYLine" object found in this script.

 The only required input is the x and y data, all formatting and other
 parameters either have default values defined in this script, or are
 automatically calculated as required to best display the data.

 Multiple XYLine objects may be passed to the Plot function for graphing.

 Extreme values +/-999E+99 and "NaN" are clipped from the data set.

 Unlike standard XY graphs, lines are drawn point to point in any direction
 without limitation.  This allows step functions, circles, shapes, etc.

 This script does not optimize data for better resolution, it is up to the
 programmer to input the data spacing as desired.

 Generating smooth output requires increasing the number of data points at the
 expense of computation time.  Generally, 1000 points or less is adequate.
 
 Formatting for text may be modified using CSS.

 Formatting for the axes and lines may be modified using VML styles, for more 
 information on VML go to the W3C definition page: http://www.w3.org/TR/NOTE-VML

==================================================================================

"XYLine" object documentation

Initialize: 
	var MyLine = new XYLine();
	MyLine.x = [1, 2, 3, 4]; // set x and y data points
	MyLine.label = "plot 1"; // set properties as desired
		
Properties: 
	See constructor script with inline comments below.

================================================================================*/


function XYLine() {

	// Arrays for holding x and y coordinate values

	this.x = new Array();
	this.y = new Array();
	
	// Assign VML compliant properties for the line.
	// Note that non-primary colors must be in #hex or rbg(r,g,b) format.

	this.VMLstroke = "weight='1pt'; color='#004040'; dashstyle='solid';";
	this.drawline=true;	// set to true or false

	// Assign a label for the line

	this.label = "Member Service development";	// displayed when mouse is over line

	// Assign a VML shapetype for plotting data points, see definitions at bottom of script.
	// Using the 'none' shapetype plots invisible points and allows coordinates to be
	// shown when the mouse is over the point.  Set 'drawpoints' to false to turn off
	// the points completely and speed up graphing for extensive data sets.  The graph script
	// automatically turns off points if the data set has more than 1000 points.

	this.VMLpointshapetype="none";	// [ diamond, square, triangle, circle, x, none ]
	this.drawpoints=false;	 	// set to true or false

	// Assign VML properties for the points

	this.pointsize=5;	 	// shape display size in "pt"
	this.pointfillcolor="#008080";	// point fill color
	this.pointstrokecolor="black";	// point line color
}




function XYGraph() {

  // Data Properties

	// The max and min values define the upper and lower axis values to display.
	// If not specified they will automatically fit to the data limits.

	this.xmax=null;
	this.xmin=null;
	this.ymax=null; 
	this.ymin=null;

	// Graph titles

	this.title=null;
	this.xaxis="No. bets";
	this.yaxis="Acc. profit";

	// Tic scale spacing, if not specified it will be fit to the data.

	this.xscale=null;
	this.yscale=null;

	// Value where the axes cross.  Default is at 0,0
	// Set to "Number.NEGATIVE_INFINITY" to align with the minimum axis value.
	// Set to "Number.POSITIVE_INFINITY" to align with the maximum axis value.

	this.xint=0;
	this.yint=0;

  // Style Properties

	this.gheight=120;	// Plotting height in "pt"
	this.gwidth=180;	// Plotting width in "pt"
	this.pad_top=10;	// Internal padding margins in "pt"
	this.pad_bottom=10;
	this.pad_left=10;
	this.pad_right=10;

	this.ticsize=5; 	// Tic size in "pt", set to "0" to turn off
	this.ticspaceavg=30;	// Average auto tic spacing in "pt"
	this.xticloc="auto";	// x-axis labels "top", "bottom", "auto" or "none"
	this.yticloc="auto";	// y axis labels "right", "left", "auto" or "none"

	this.VMLminorxaxisstroke = "weight='0.5pt'; color='#D3D3D3'; dashstyle='dash';";
	this.VMLminoryaxisstroke = "weight='0.5pt'; color='#D3D3D3'; dashstyle='dash';";
	this.VMLmajoraxisstroke = "weight='1pt'; color='black';";
	this.VMLbackgroundfill = "color='white'";
	this.VMLframestroke = "color='white'";

	this.CSSticfont = "font: 8pt 'Arial';";
	this.CSStitlefont = "font: 10pt 'Arial'; font-weight: bold;";  // font sizes must be set in "pt"
	this.CSSxaxisfont = "font: 8pt 'Arial'; font-weight: bold;";
	this.CSSyaxisfont = "font: 8pt 'Arial'; font-weight: bold;";
	this.VMLyaxisfontcolor = "black";  // must specify y-axis title font color since it is VML object

}




XYGraph.prototype.Plot = function (XYLine) {

// fix incorrect input
	if (this.xmax < this.xmin && this.xmax) {temp=this.xmax; this.xmax=this.xmin; this.xmin=temp;} 
	if (this.ymax < this.ymin && this.ymax) {temp=this.ymax; this.ymax=this.ymin; this.ymin=temp;}
	if (this.xint < this.xmin) {this.xint=this.xmin;} 
	if (this.yint < this.ymin) {this.yint=this.ymin;}
	if (this.xint > this.xmax) {this.xint=this.xmax;}
	if (this.yint > this.ymax) {this.yint=this.ymax;}

// Parse input to determine x,y data limits and clip extreme values
	lines = arguments; 
	xmax = Number.NEGATIVE_INFINITY; xmin = Number.POSITIVE_INFINITY;
	ymax = Number.NEGATIVE_INFINITY; ymin = Number.POSITIVE_INFINITY;
	clipxmax = (this.xmax ? Number(this.xmax) : 999E+99); 
	clipxmin = (this.xmin ? Number(this.xmin) : -999E+99);
	clipymax = (this.ymax ? Number(this.ymax) : 999E+99); 
	clipymin = (this.ymin ? Number(this.ymin) : -999E+99);
	clipped=false;

  for (n=0; n<lines.length; n++) {
	j=0; tempx = new Array(); tempy = new Array();
	linelen = (lines[n].y.length > lines[n].x.length ? lines[n].x.length : lines[n].y.length);
	for (i=0; i<linelen; i++) {  
		if ((lines[n].x[i] <= clipxmax)&&(lines[n].x[i] >= clipxmin)&&(lines[n].y[i] <= clipymax)&&(lines[n].y[i] >= clipymin)&&(i<=1000)) {
			if (xmax < lines[n].x[i]) {xmax = lines[n].x[i]};
			if (xmin > lines[n].x[i]) {xmin = lines[n].x[i]};
			if (ymax < lines[n].y[i]) {ymax = lines[n].y[i]};
			if (ymin > lines[n].y[i]) {ymin = lines[n].y[i]};
			tempx[j]=lines[n].x[i]; 
			tempy[j]=lines[n].y[i];
			j++;
		}
		else if (isNaN(lines[n].x[i]) || isNaN(lines[n].y[i])) {clipped=true;}
		else if (((lines[n].x[i+1] <= clipxmax)&&(lines[n].x[i+1] >= clipxmin)&&(lines[n].y[i+1] <= clipymax)&&(lines[n].y[i+1] >= clipymin)&&(i<=1000))) {
			lastxy = this.Findedge(lines[n].x[i+1],lines[n].x[i],lines[n].y[i+1],lines[n].y[i],clipxmax,clipxmin,clipymax,clipymin);
			if (Math.abs(lastxy[0]) < 999E+99 && Math.abs(lastxy[1]) < 999E+99) {
				tempx[j]=lastxy[0]; tempy[j]=lastxy[1]; j++;
			}
			clipped=true; 
		}
		else if (((lines[n].x[i-1] <= clipxmax)&&(lines[n].x[i-1] >= clipxmin)&&(lines[n].y[i-1] <= clipymax)&&(lines[n].y[i-1] >= clipymin))&&(i<=1000)) {
			lastxy = this.Findedge(lines[n].x[i-1],lines[n].x[i],lines[n].y[i-1],lines[n].y[i],clipxmax,clipxmin,clipymax,clipymin);
			if (Math.abs(lastxy[0]) < 999E+99 && Math.abs(lastxy[1]) < 999E+99) {
				tempx[j]=lastxy[0]; tempy[j]=lastxy[1]; j++;
			}
			if (i+1 != linelen) {
			lines.length += 1;
			lines[(lines.length-1)] = new Array();
			lines[(lines.length-1)].VMLstroke = lines[n].VMLstroke;
			lines[(lines.length-1)].drawline = lines[n].drawline;
			lines[(lines.length-1)].label = lines[n].label;
			lines[(lines.length-1)].VMLpointshapetype = lines[n].VMLpointshapetype;
			lines[(lines.length-1)].pointsize = lines[n].pointsize;
			lines[(lines.length-1)].pointfillcolor = lines[n].pointfillcolor;
			lines[(lines.length-1)].pointstrokecolor = lines[n].pointstrokecolor;
			lines[(lines.length-1)].drawpoints = lines[n].drawpoints;
			lines[(lines.length-1)].x=lines[n].x.slice(i);
			lines[(lines.length-1)].y=lines[n].y.slice(i); 
			lines[n].x=tempx; lines[n].y=tempy;
			clipped=true;

			break; 
			}
		}
		else if (i > 1000) {
			lines[n].drawpoints = false;
			lines.length += 1;
			lines[(lines.length-1)] = new Array();
			lines[(lines.length-1)].VMLstroke = lines[n].VMLstroke;
			lines[(lines.length-1)].drawline = lines[n].drawline;
			lines[(lines.length-1)].label = lines[n].label;
			lines[(lines.length-1)].drawpoints = false;
			lines[(lines.length-1)].x=lines[n].x.slice(i-1);
			lines[(lines.length-1)].y=lines[n].y.slice(i-1); 
			lines[n].x=tempx; lines[n].y=tempy;

			break; 
		}
		else {clipped=true;}
	}
	lines[n].x=tempx; lines[n].y=tempy;
  }
	xmax = (this.xmax ? Number(this.xmax) : xmax); 
	xmin = (this.xmin ? Number(this.xmin) : xmin);
	ymax = (this.ymax ? Number(this.ymax) : ymax); 
	ymin = (this.ymin ? Number(this.ymin) : ymin);

	if (this.xint == Number.NEGATIVE_INFINITY) {this.xint = xmin;}
	if (this.xint == Number.POSITIVE_INFINITY) {this.xint = xmax;}
	if (this.yint == Number.NEGATIVE_INFINITY) {this.yint = ymin;}
	if (this.yint == Number.POSITIVE_INFINITY) {this.yint = ymax;}

// Intialize data

	xscale=Number(this.xscale); yscale=Number(this.yscale);
	xint=Number(this.xint); yint=Number(this.yint);

	gheight=Number(this.gheight); gwidth=Number(this.gwidth);
	ticsize=Number(this.ticsize);

	xticloc=this.xticloc; yticloc=this.yticloc;

// Initialize parameters
	gxpt=100;
	pad_t=gxpt*this.pad_top; pad_b=gxpt*this.pad_bottom; // padding
	pad_l=gxpt*this.pad_left; pad_r=gxpt*this.pad_right; 
	gwt=Math.abs(Math.round(gwidth*gxpt)); // total graph width;
	ght=Math.abs(Math.round(gheight*gxpt)); // total graph height;

	gstyle='position:absolute; width='+gwt+'; height='+ght; // repetitive string constant
	GXstyle=this.CSSticfont+'position:absolute;';
	GYstyle=this.CSSticfont+'position:absolute;';
	GYLstyle=this.CSSticfont+'position:absolute; text-align:right; width:'; // finished later
	

// fix auto scale x axis
	if (xint < xmin) {xmin=xint;}
	if (xint > xmax) {xmax=xint;}

// x auto tic scale
     if (xscale <= 0) {
	xticmax=(gwidth-(pad_r+pad_l)/gxpt)/this.ticspaceavg;
	ticdivision=[0.1,0.2,0.25,0.5];
	divpow=0;
	i=0;
	  while ((xmax-xmin)/(ticdivision[i]*Math.pow(10,divpow)) > xticmax) { 
	    i++; 
	    if (!(i % ticdivision.length)) {divpow++; i=0;}
	    if (divpow>1) {xticmax=(gwidth-(pad_r+pad_l)/gxpt)/(Number(this.ticspaceavg)+5);}
	  }
	if (i==0 && divpow==0) {
	  i=ticdivision.length-1; divpow=-1; xticmax=(gwidth-(pad_r+pad_l)/gxpt)/(Number(this.ticspaceavg)+10);
	  while ((xmax-xmin)/(ticdivision[i]*Math.pow(10,divpow)) < xticmax) { 
	    i--; 
	    if (i==-1) {divpow--; i=ticdivision.length-1; xticmax=(gwidth-(pad_r+pad_l)/gxpt)/(Number(this.ticspaceavg)+30);}
	  }
	}
	xscale=ticdivision[i]*Math.pow(10,divpow);
     }


// fix auto scale y axis
	if (yint < ymin) {ymin = yint;}
	if (yint > ymax) {ymax = yint;}

// y auto tic scale
     if (yscale <= 0) {
	yticmax=(gheight-(pad_t+pad_b)/gxpt)/this.ticspaceavg;
	ticdivision=[0.1,0.2,0.25,0.5];
	divpow=0;
	i=0;
	  while ((ymax-ymin)/(ticdivision[i]*Math.pow(10,divpow)) > yticmax) { 
	    i++; 
	    if (!(i % ticdivision.length)) {divpow++; i=0;}
	    if (divpow>1) {yticmax=(gwidth-(pad_t+pad_b)/gxpt)/(Number(this.ticspaceavg)+5);}
	  }
	if (i==0 && divpow==0) {
	  i=ticdivision.length-1; divpow=-1; yticmax=(gheight-(pad_t+pad_b)/gxpt)/(this.ticspaceavg+10);
	  while ((ymax-ymin)/(ticdivision[i]*Math.pow(10,divpow)) < yticmax) { 
	    i--; 
	    if (i==-1) {divpow--; i=ticdivision.length-1; yticmax=(gheight-(pad_t+pad_b)/gxpt)/(this.ticspaceavg+30);}
	  }
	}
	yscale=ticdivision[i]*Math.pow(10,divpow);
     }

// fix auto scale y axis
	if (!clipped) {
		ymin = (ymin%yscale ? ymin-ymin%yscale-yscale : ymin);
		ymax = (ymax%yscale ? ymax-ymax%yscale+yscale : ymax);
	}



// Determine x tic labels
	if (xticloc!="none") {
	xticlabels = new Array(); xticcharnum=1;
	numxticleft = Math.floor((xint-xmin)/xscale); 
	numxtic = numxticleft+Math.floor((xmax-xint)/xscale)+1;
	for (i=0; i<numxtic; i++) {
		xticlabel=(i-numxticleft)*xscale+xint;  
		negstr=""; expstr=0;
		if (xticlabel < 0) {xticlabel*=-1; negstr="-";}
		switch (true) {	
		case (xticlabel > 99999) : 
			while (xticlabel>=1000) {xticlabel/=1000; expstr++;}
			xticlabel=String(xticlabel).slice(0,4);
			xticlabels[i]=negstr+xticlabel+"E+"+(expstr*3);
			break;
		case (xticlabel < 0.001 && xticlabel!=0) : 
			while (xticlabel<=0.001) {xticlabel*=1000; expstr++;}
			xticlabel=(Math.round(xticlabel*Math.pow(10,4)))/Math.pow(10,4);
			xticlabels[i]=negstr+xticlabel+"E-"+(expstr*3);
			break;
		default:
			xticlabel=(Math.round(xticlabel*Math.pow(10,3)))/Math.pow(10,3);
			xticlabels[i]=negstr+String(xticlabel).slice(0,6);
			break;
		} 
		xticcharnum=Math.max(xticcharnum,String(xticlabels[i]).length);
	}}
	xticcharnumlast=String(xticlabels[i-1]).length;

// Determine y tic labels
	if (yticloc!="none") {
	yticlabels = new Array(); yticcharnum=0;
	numyticbot = Math.floor((yint-ymin)/yscale);
	numytic = numyticbot+Math.floor((ymax-yint)/yscale)+1;
	for (i=0; i<numytic; i++) {
		yticlabel=(i-numyticbot)*yscale+yint;
		negstr=""; expstr=0;
		if (yticlabel < 0) {yticlabel*=-1; negstr="-";}
		switch (true) { 
		case (yticlabel > 99999) : 
			while (yticlabel>=1000) {yticlabel/=1000; expstr++;}
			yticlabel=String(yticlabel).slice(0,4);
			yticlabels[i]=negstr+yticlabel+"E+"+(expstr*3);
			break;
		case (yticlabel < 0.001 && yticlabel!=0) : 
			while (yticlabel<=0.001) {yticlabel*=1000; expstr++;}
			yticlabel=(Math.round(yticlabel*Math.pow(10,4)))/Math.pow(10,4);
			yticlabels[i]=negstr+yticlabel+"E-"+(expstr*3);
			break;
		default:
			yticlabel=(Math.round(yticlabel*Math.pow(10,3)))/Math.pow(10,3);
			yticlabels[i]=negstr+String(yticlabel).slice(0,6);
			break;
		} 
		yticcharnum=Math.max(yticcharnum,String(yticlabels[i]).length);
	}}


// Determine required extra padding and auto axis location
	tic_pt=Number((this.CSSticfont.slice(0,this.CSSticfont.indexOf("pt"))).slice(-2));
	GYLstyle+=tic_pt*(yticcharnum+1)*0.5+"pt;";
	if (yticloc!="none") {
	  if (!numxticleft) {
		if (yticloc=="auto") {yticloc="left";}
		if (yticloc!="right") {
			pad_l+=0.75*yticcharnum*tic_pt*gxpt;
			if (this.yaxis) {pad_l+=0.5*this.pad_left*gxpt;}
		}
	  }
	  if (numxticleft == numxtic-1) {
		if (yticloc=="auto") {yticloc="right";}
		if (yticloc!="left") {pad_r+=0.75*yticcharnum*tic_pt*gxpt;}
	  }
	}

	if (xticloc!="none") {
	  if (!numyticbot) {
		if (xticloc=="auto") {xticloc="bottom";}
		if (xticloc!="top") {pad_b+=0.75*tic_pt*gxpt;}
	  }
	  if (numyticbot == numytic-1) {
		if (xticloc=="auto") {xticloc="top";}
		if (xticloc!="bottom") {pad_t+=0.75*tic_pt*gxpt;}
	  }
	if (!((numxticleft == numxtic-1) && (yticloc=="right"))) {pad_r+=0.25*xticcharnumlast*tic_pt*gxpt;}
	}
	if (this.title) {
		title_pt=Number((this.CSStitlefont.slice(0,this.CSStitlefont.indexOf("pt"))).slice(-2));
		pad_t+=1.25*title_pt*gxpt;
		if (xticloc=="top") pad_t+=0.75*tic_pt*gxpt;}
	if (this.xaxis) {
		xaxis_pt=Number((this.CSSxaxisfont.slice(0,this.CSSxaxisfont.indexOf("pt"))).slice(-2));
		pad_b-=0.25*pad_b;
		pad_b+=xaxis_pt*gxpt;
		if (xticloc=="bottom") pad_b+=0.75*tic_pt*gxpt;}
	if (this.yaxis) {
		yaxis_pt=Number((this.CSSyaxisfont.slice(0,this.CSSyaxisfont.indexOf("pt"))).slice(-2));
		pad_l-=0.25*pad_l;
		pad_l+=yaxis_pt*gxpt;}


	gw=gwt-pad_l-pad_r;
	gh=ght-pad_t-pad_b;

	xscl=gw/(xmax-xmin);
	yscl=gh/(ymax-ymin);

	gxmin=pad_l;
	gxmax=gw+pad_l;
	gxint=(xint-xmin)*xscl+pad_l;
	gymin=gh+pad_t;
	gymax=pad_t;
	gyint=(ymax-yint)*yscl+pad_t;
	gytic=yscale*yscl;
	gxtic=xscale*xscl;
	gticsize=Math.abs(Math.round(ticsize*gxpt));

	gstr='<v:group style="antialias:true; width='+gwidth+'pt; height='+gheight+'pt" coordsize="'+gwt+','+ght+'" coordorigin="0,0">';
	gstr+='<v:rect style="'+gstyle+'" ><v:stroke '+this.VMLframestroke+' /><v:fill '+this.VMLbackgroundfill+' /></v:rect>';

// draw x-axis
	gstr+='<v:line from="'+gxmin+','+Math.round(gyint)+'" to="'+gxmax+','+Math.round(gyint)+'" ><v:stroke '+this.VMLmajoraxisstroke+' /></v:line>';
	
// draw y-axis
	gstr+='<v:line from="'+Math.round(gxint)+','+gymin+'" to="'+Math.round(gxint)+','+gymax+'" ><v:stroke '+this.VMLmajoraxisstroke+' /></v:line>';

// draw minor x-axis
	yticmin=gyint+numyticbot*gytic;
	for (i=0; i<numytic; i++) {
	  curint=Math.round(yticmin-gytic*i);
	  if (curint!=Math.round(gyint)) {gstr+='<v:line from="'+gxmin+','+curint+'" to="'+gxmax+','+curint+'" ><v:stroke '+this.VMLminorxaxisstroke+' /></v:line>';}
	}

// draw minor y-axis
	xticmin=gxint-numxticleft*gxtic;
	for (i=0; i<numxtic; i++) {
	  curint=Math.round(gxtic*i+xticmin);
	  if (curint!=Math.round(gxint)) {gstr+='<v:line from="'+curint+','+gymin+'" to="'+curint+','+gymax+'" ><v:stroke '+this.VMLminoryaxisstroke+' /></v:line>';}
	}

// draw x-axis tics
	gstr+='<v:shape style="'+gstyle+'"><v:path v="';
	for (i=0; i<numxtic; i++) { gstr+='m '+Math.round(xticmin+i*gxtic)+','+Math.round(gyint)+' r 0,'+((xticloc=="top" ? -1 : 1)*gticsize)+' x ';}
	gstr+='e" /><v:stroke '+this.VMLmajoraxisstroke+' /><v:fill on="false" /></v:shape>';

// draw y-axis tics
	gstr+='<v:shape style="'+gstyle+'"><v:path v="';
	for (i=0; i<numytic; i++) { gstr+='m '+Math.round(gxint)+','+Math.round(yticmin-i*gytic)+' r '+((yticloc=="right" ? 1 : -1)*gticsize)+',0 x ';}
	gstr+='e" /><v:stroke '+this.VMLmajoraxisstroke+' /><v:fill on="false" /></v:shape>';

// draw titles
	if (this.title) {
	gstr+='<span style="'+this.CSStitlefont+' position:absolute; text-align:center; top: '+0.5*this.pad_top;
	gstr+='pt; left: '+(0.5*gwt/gxpt-this.title.length*title_pt*0.25)+'pt;">'+this.title+'</span>';
	}
	if (this.xaxis) {
	gstr+='<span style="'+this.CSSxaxisfont+' position:absolute; text-align:center; top: '+((gymin+0.5*(pad_b-xaxis_pt*gxpt))/gxpt+(xticloc=="bottom" ? 0.75*tic_pt:0));
	gstr+='pt; left: '+(0.5*gwt/gxpt-this.xaxis.length*xaxis_pt*0.25)+'pt;">'+this.xaxis+'</span>';
	}
	if (this.yaxis) { 
	gstr+='<v:shape style="'+gstyle;
	gstr+='" path="M '+((0.25*this.pad_left+0.5*yaxis_pt)*gxpt)+','+gymin+' L '+((0.25*this.pad_left+0.5*yaxis_pt)*gxpt)+','+gymax+'" fillcolor="'+this.VMLyaxisfontcolor+'">';
	gstr+='<v:stroke on="false" /><v:path textpathok="true" />';
	gstr+='<v:textpath on="true" style="'+this.CSSyaxisfont+'" string="'+this.yaxis+'" /></v:shape>';
	}


// draw lines
  for (n=0; n<lines.length; n++) {
  if (lines[n].drawline && lines[n].x.length>1) {
	gstr+='<v:polyline points="';
	for (i=0; i<lines[n].x.length; i++) {gstr+= Math.round(gxmin+(lines[n].x[i]-xmin)*xscl)+" "+Math.round(gymin-(lines[n].y[i]-ymin)*yscl)+" ";}
	gstr+='" title="'+lines[n].label+'" ><v:stroke '+lines[n].VMLstroke+' /><v:fill on="false" /></v:polyline>';
  }}

// draw points
  for (n=0; n<lines.length; n++) {
  if (lines[n].drawpoints && lines[n].x.length>0) {
	gstr+=this.VMLpointshape(lines[n].VMLpointshapetype);
	for (i=0; i<lines[n].x.length; i++) {;
		gstr+='<v:shape type="#'+(lines[n].VMLpointshapetype).toLowerCase()+'" style="width:'+lines[n].pointsize*gxpt+' ;height:'+lines[n].pointsize*gxpt+' ';
		gstr+=';top:'+Math.round(gymin-0.5*lines[n].pointsize*gxpt-(lines[n].y[i]-ymin)*yscl)+';left:'+Math.round(gxmin-0.5*lines[n].pointsize*gxpt+(lines[n].x[i]-xmin)*xscl);
		gstr+='" title="'+lines[n].x[i]+','+lines[n].y[i]+'" fillcolor="'+lines[n].pointfillcolor+'"';
		gstr+=' strokecolor="'+lines[n].pointstrokecolor+'" />';
	}
  }}


// draw x-axis labels
	if (xticloc!="none") {
	for (i=0; i<numxtic; i++) { 
		  if (xticloc=="top") {
			gstr+='<span style="'+GXstyle+' top: '+((gyint-gticsize*1.25)/gxpt-8)+'pt; left: '+((xticmin+i*gxtic-0.5*gticsize)/gxpt)+'pt;">';
		  }
		  else {
			gstr+='<span style="'+GXstyle+' top: '+((gyint+gticsize*1.25)/gxpt)+'pt; left: '+((xticmin+i*gxtic-0.5*gticsize)/gxpt)+'pt;">';
		  }
		gstr+=xticlabels[i]+'</span>';
	}}

// draw y-axis labels
	if (yticloc!="none") {
	for (i=0; i<numytic; i++) { 
		  if (yticloc=="right") {
		  	gstr+='<span style="'+GYstyle+' top: '+((yticmin-i*gytic-gticsize)/gxpt)+'pt; left: '+((gxint+gticsize*1.5)/gxpt)+'pt;">';
		  }
		  else {
		  	gstr+='<span style="'+GYLstyle+' top: '+((yticmin-i*gytic-gticsize)/gxpt)+'pt; left: '+((gxint-gticsize)/gxpt-0.5*(yticcharnum+1)*tic_pt)+'pt;">';
		  }
		  gstr+=yticlabels[i]+'</span>';
	}}


// close and return output 
	gstr+='</v:group>';
	return gstr;

} // end function




XYGraph.prototype.Findedge = function (x1,x2,y1,y2,xmax,xmin,ymax,ymin) {

	x=0; y=0;
    if (!isNaN(x2)) {
	if (!isFinite(x2)) {
		switch (x2) {
			case Number.POSITIVE_INFINITY: x2 = 999E+99; break;
			case Number.NEGATIVE_INFINITY: x2 = -999E+99; break;
		}
	}
	if (!isFinite(y2)) {
		switch (y2) {
			case Number.POSITIVE_INFINITY: y2 = 999E+99; break;
			case Number.NEGATIVE_INFINITY: y2 = -999E+99; break;
		}
	}

	angle = Math.atan2(y2-y1,x2-x1);
	angle += (angle > 0 ? 0 : 2*Math.PI);

	slope = (y2-y1)/(x2-x1);
	Mxx = Math.atan2(ymax-y1,xmax-x1); Mxx += (Mxx > 0 ? 0 : 2*Math.PI);
	Mnx = Math.atan2(ymax-y1,xmin-x1); Mnx += (Mnx > 0 ? 0 : 2*Math.PI);
	Mnn = Math.atan2(ymin-y1,xmin-x1); Mnn += (Mnn > 0 ? 0 : 2*Math.PI);
	Mxn = Math.atan2(ymin-y1,xmax-x1); Mxn += (Mxn > 0 ? 0 : 2*Math.PI);

	switch (true) {
		case (angle>=Mxx && angle<Mnx) : 
			y = ymax;
			x = (ymax-y1)/slope+x1;
			break;
		case (angle>=Mnx && angle<Mnn) :
			x = xmin;
			y = (xmin-x1)*slope+y1;
			break;
		case (angle>=Mnn && angle<Mxn) :
			y = ymin;
			x = (ymin-y1)/slope+x1;
			break;
		case (angle>=Mxn || angle<Mxx) :
			x = xmax;
			y = (xmax-x1)*slope+y1;
			break;
	}
     }

	return [x,y]; 
} // end function



// point shapetype definitions, these can be modified and expanded

XYGraph.prototype.VMLpointshape = function (shapename) {
	switch (shapename.toLowerCase()) {
	
	case "diamond" :
		return '<v:shapetype id="diamond" coordsize="500,500" path=" m 250 500 l 500 250 250 0 0 250 x e" />';
	case "square" :
		return '<v:shapetype id="square" coordsize="350,350" path=" m 0 0 l 0 350 350 350 350 0 x e" />';
	case "triangle" :
		return '<v:shapetype id="triangle" coordsize="400,400" path=" m 200 0 l 400 400 0 400 x e" />';
	case "circle" :
		return '<v:shapetype id="circle" coordsize="350,350" path=" m 0 175 l 23 262 88 327 175 350 262 327 327 262 350 175 327 88 262 23 175 0 88 23 23 88 x e" />';
	case "x" :
		return '<v:shapetype id="x" coordsize="350,350" path=" m 0 0 l 350 350 e m 0 350 l 350 0 e" />';
	case "none" :
		return '<v:shapetype id="none" coordsize="350,350" filled="false" stroked="false" path=" m 0 0 l 0 350 350 350 350 0 x e" />';
	}
} // end function








