/*
   Canvas-based function drawing script
   (c) David Lippman, part of www.imathas.com
   Quadratic inequality code contributed by Cam Joyce
   GNU 2 Licensed - see license in IMathAS distribution

   HTML should have <canvas id="canvas##"></canvas>
   and include a <script> tag that defines
   canvases[##] = [##,'background img',xmin,xmax,ymin,ymax,border,imgwidth,imgheight,defmode,dotline,locky];
   where
   	background img is filename is a specific directory (anyone else would need
   	  	to adjust the directory in the code)
   	xmin,xmax,ymin,ymax,border are based on the background image coordinates.
   	    	these are not used by the JS, but could be to convert from
   	    	pixel locations to graph coordinate locations
   	imgwidth, imgheight are the pixels of the canvas element
   	defmode is the default tool that should be selected (see below for modes)
   	dotline will, if true, add dots at the end of line segments in mode 0.
   		this is useful for drawing polygons
   	locky will, if true, only allow drawing along the center x-axis.
   		this is useful for numberline graphing

   Will automatically output to <input id="qn##" />

   JS can interact with the drawing item by calling:
      clearcanvas(##)
      settool(this,##,mode)
   where ## is the ## in the canvas id, and mode is one of:

   targets.mode
   	0:  set of line segments / freeform drawing
   	0.5: single line segment (basic tool)
   	0.7:  freeform drawing on mousedown only (no click-line)
   	1: solid dot
   	2: open dot
   tptypes
	5: line
	5.2:  ray (no arrow)
	5.3:  line segment
	5.4:  vector
	6: parabola
	6.1: horiz parabola
	6.3: cubic
	6.5: square root
	6.6: cube root
	7: circle (only works on square grids)
	7.2: ellipse
	7.4: vertical hyperbola
	7.5: horizontal hyperbola
	8: abs value
	8.2: linear/linear rational
	8.3: exponential (unshifted)
	8.4: log (unshifted)
	8.5: exponential (shifted)
	8.6: log (shifted)
	9: cosine
	9.1: sine
   ineqtypes
   	10: linear >= or <=
   	10.2: linear < or >
	10.3: quadratic <= or =>
	10.4: quadratic < or >
    10.5: abs < or >
    10.6: abs <= or >=
*/

var canvases = new Array();
var drawla = new Array();

var imathasDraw = (function($) {
var mouseisdown = false;
var targets = new Array();
var imgs = new Array();
var targetOuts = new Array();
var lines = new Array();
var dots = new Array();
var odots = new Array();
var tplines = new Array();
var tptypes = new Array();
var ineqlines = new Array();
var ineqtypes = new Array();
var curLine = null;
var drawstyle = [];
var drawlocky = [];
var curTPcurve = null;
var curIneqcurve = null; //inequalities
var ineqcolors = ["rgb(0,0,255)","rgb(255,0,0)","rgb(0,255,0)"];
var ineqacolors = ["rgba(0,0,255,.4)","rgba(255,0,0,.4)","rgba(0,255,0,.4)"];
var asymcolor = "rgb(0,200,0)";
var dragObj = null;
var oldpointpos = null;
var curTarget = null;
var curCursor = null;
var nocanvaswarning = false;
var hasTouch = false;
var didMultiTouch = false;
var clickmightbenewcurve = false;
var hasTouchTimer = null;
var tpModeN = {
	"5": 2, "5.2": 2, "5.3": 2, "5.4": 2,
	"6": 2, "6.1": 2, "6.2": 2, "6.3": 2, "6.5": 2, "6.6": 2,
	"7": 2, "7.2": 2, "7.4": 2, "7.5": 2,
	"8": 2, "8.2": 2, "8.3": 2, "8.4": 2, "8.5": 3, "8.6": 3,
	"9": 2, "9.1": 2,
	"10": 3, "10.2": 3, "10.3": 3, "10.4": 3};
var tpHasAsymp = { "7.4": 1, "7.5": 1, "8.2": 1, "8.5": 1, "8.6": 1};

function reset() {
	clearAllDrawListners();
	targets.length = 0;
	imgs.length = 0;
	targetOuts.length = 0;
	lines.length = 0;
	dots.length = 0;
	odots.length = 0;
	tplines.length = 0;
	tptypes.length = 0;
	ineqlines.length = 0;
	ineqtypes.length = 0;
	drawstyle.length = 0;
	drawlocky.length = 0;
	curTarget = null;
	curLine = null;
	dragObj = null;
	curTPcurve = null;
	curIneqcurve = null;
}

function clearcanvas(tarnum, overridedis) {
    if (targetOuts[tarnum] && targetOuts[tarnum].disabled && !overridedis) { return; }
    if (!lines.hasOwnProperty(tarnum)) { return; }
	lines[tarnum].length = 0;
	dots[tarnum].length = 0;
	odots[tarnum].length = 0;
	tplines[tarnum].length = 0;
	tptypes[tarnum].length = 0;
	ineqlines[tarnum].length = 0;
	ineqtypes[tarnum].length = 0;
    curTarget = tarnum;
    if (targets[tarnum].type == 'canvas') {
        drawTarget();
    }
	curTarget = null;
	curLine = null;
	dragObj = null;
}

function clearlastline(tarnum) {
	if (lines[tarnum].length>0) {
		lines[tarnum].length = lines[tarnum].length-1;
	}
	curTarget = tarnum;
	drawTarget();
	curTarget = null;
	curLine = null;
	dragObj = null;
}

function addA11yTarget(canvdata, thisdrawla, imgpath) {
	var tarnum = canvdata[0];
	var ansformats = canvdata[13].split(',');
    var vispreview = true;
    if (ansformats[ansformats.length - 1] == 'noprev') {
        vispreview = false;
        ansformats.pop();
    }
	var xmin = canvdata[2];
	var xmax = canvdata[3];
	var ymin = canvdata[4];
	var ymax = canvdata[5];
	if (ymin==ymax) { //numberlines
		ymin = -1; ymax = 1;
	}
	var imgborder = canvdata[6];
	var imgwidth = canvdata[7];
	var imgheight = canvdata[8];
	var tarel = document.getElementById("a11ydraw"+tarnum);
	if (tarel == null) {
		var canvel = document.getElementById("canvas"+tarnum);
		if (canvel && !vispreview) { // non-interactive previous attempt display
			var newdiv = $("<div>").append($("<p>", {text: _('Drawn Elements:')}));
			tarel = document.createElement("ul");
			tarel.setAttribute("id", "allydraw" + tarnum);
			newdiv.append(tarel);
			$(canvel).replaceWith(newdiv);
		}
	}
    targetOuts[tarnum] = document.getElementById('qn'+tarnum);
    var activedraw = !!document.getElementById("a11ydrawnew"+tarnum);
    if (targetOuts[tarnum].disabled) {
        if (activedraw) {
            document.getElementById("a11ydrawnew"+tarnum).disabled = true;
        }
        $(".a11ydrawadd[data-qn="+tarnum+"]").prop('disabled', true);
    }
	targets[tarnum] = {type:'a11y', el: tarel, vispreview: vispreview, xmin: xmin, xmax: xmax, ymin: ymin, ymax: ymax, imgborder: imgborder, imgwidth: imgwidth, imgheight: imgheight};
    if (vispreview) {
        targets[tarnum].canv = document.getElementById("canvas"+tarnum);
    } 
	targets[tarnum].pixperx = (imgwidth - 2*imgborder)/(xmax-xmin);
	targets[tarnum].pixpery = (ymin==ymax)?1:((imgheight - 2*imgborder)/(ymax-ymin));
	var afgroup;
	//massae ansformats array to account for default behaviors
	if (ansformats[0]=="inequality") {
		afgroup = ansformats.shift();
		if (ansformats.length==0) {
			ansformats = ["line"];
		} else if (ansformats[0]=="both") {
			ansformats = ["line","parab"];
		}
	} else if (ansformats[0]=="twopoint") {
		afgroup = ansformats.shift();
		if (ansformats.length==0) {
			ansformats = ["line","parab","abs","circle","dot"];
		}
	} else if (ansformats[0]=="numberline") {
		afgroup = "basic";
		ansformats.shift();
	} else {
		afgroup = "basic";
	}
	targets[tarnum].afgroup = afgroup;
	var types = {
		"inequality": {
			"line": [
				{"mode":10, "descr":_("Linear inequality with solid line"), inN: 3, "input":_("Enter 2 points on the line, then a third point in the shaded region")},
				{"mode":10.2, "descr":_("Linear inequality with dashed line"), inN: 3, "input":_("Enter 2 points on the line, then a third point in the shaded region")}
			],
			"parab": [
				{"mode":10.3, "descr":_("Parabolic inequality with solid line"), inN: 3, "input":_("Enter the vertex, then another point on the parabola, then a third point in the shaded region")},
				{"mode":10.4, "descr":_("Parabolic inequality with dashed line"), inN: 3, "input":_("Enter the vertex, then another point on the parabola, then a third point in the shaded region")}
            ],
            "abs": [
				{"mode":10.5, "descr":_("Absolute value inequality with solid line"), inN: 3, "input":_("Enter the corner point of the absolute value, then another point on the graph, then a third point in the shaded region")},
				{"mode":10.6, "descr":_("Absolute value inequality with dashed line"), inN: 3, "input":_("Enter the corner point of the absolute value, then another point on the graph, then a third point in the shaded region")}
			]
		},
		"twopoint": {
			"line": [{"mode":5, "descr":_("Line"), inN: 2, "input":_("Enter two points on the line")}],
			"lineseg": [{"mode":5.3, "descr":_("Line segment"), inN: 2, "input":_("Enter the starting and ending point of the line segment")}],
			"ray": [{"mode":5.2, "descr":_("Ray"), inN: 2, "input":_("Enter the starting point of the ray and another point on the ray")}],
			"parab": [{"mode":6, "descr":_("Parabola"), inN: 2, "input":_("Enter the vertex, then another point on the parabola")}],
			"horizparab": [{"mode":6.1, "descr":_("Parabola opening right or left"), inN: 2, "input":_("Enter the vertex, then another point on the parabola")}],
			"halfparab": [{"mode":6.2, "descr":_("Half Parabola"), inN: 2, "input":_("Enter the vertex, then another point on the half parabola")}],
			"cubic": [{"mode":6.3, "descr":_("Cubic"), inN: 2, "input":_("Enter the inflection point, then another point on the cubic")}],
			"cuberoot": [{"mode":6.6, "descr":_("Cube root"), inN: 2, "input":_("Enter the inflection point of the cube root, then another point on the graph")}],
			"abs": [{"mode":8, "descr":_("Absolute value"), inN: 2, "input":_("Enter the corner point of the absolute value, then another point on the graph")}],
			"rational": [{"mode":8.2, "descr":_("Rational"), inN: 2, "input":_("Enter the point where the vertical and horizontal asymptote cross, then a point on the graph")}],
			"exp": [{"mode":8.3, "descr":_("Exponential"), inN: 2, "input":_("Enter two points on the graph")}],
			"log": [{"mode":8.4, "descr":_("Logarithm"), inN: 2, "input":_("Enter two points on the graph")}],
			"genexp": [{"mode":8.5, "descr":_("Shifted Exponential"), inN: 3, "input":_("Enter a point on the asymptote, then enter two points on the graph")}],
			"genlog": [{"mode":8.6, "descr":_("Shifted Logarithm"), inN: 3, "input":_("Enter a point on the asymptote, then enter two points on the graph")}],
			"circle": [{"mode":7, "descr":_("Circle"), inN: 2, "input":_("Enter the center point of the circle, then a point on the graph")}],
			"ellipse": [{"mode":7.2, "descr":_("Ellipse"), inN: 2, "input":_("Enter the center point of the ellipse, then a point offset from the center by the horizontal radius and vertical radius")}],
			"hyperbola": [
				{"mode":7.4, "descr":_("Vertical hyperbola"), inN: 2, "input":_("Enter the center point of the hyperbola, then a point (x,y) where x is the x-coordinate of the co-vertex and y is the y-coordinate of the vertex")},
				{"mode":7.5, "descr":_("Horizontal hyperbola"), inN: 2, "input":_("Enter the center point of the hyperbola, then a point (x,y) where x is the x-coordinate of the vertex and y is the y-coordinate of the co-vertex")},
			],
			"dot": [{"mode":1, "descr":_("Solid dot"), inN: 1, "input":_("Enter the coordinates of the dot")}],
			"opendot": [{"mode":2, "descr":_("Open dot"), inN: 1, "input":_("Enter the coordinates of the dot")}],
			"trig": [
				{"mode":9, "descr":_("Cosine"), inN: 2, "input":_("Enter a point at the start of a phase, then a point half a phase further")},
				{"mode":9.1, "descr":_("Sine"), inN: 2, "input":_("Enter a point at the start of a phase, then a point a quarter phase further")}
			],
			"vector": [{"mode":5.4, "descr":_("Vector"), inN: 2, "input":_("Enter the starting and ending point of the vector")}],
		},
		"basic": {
			"line": [{"mode":0, "descr":_("Lines"), inN: "list", "input":_("Enter a list of points to connect with lines")}],
			"lineseg": [{"mode":0.5, "descr":_("Line segment"), inN: 2, "input":_("Enter the starting and ending point of the line segment")}],
			"freehand": [{"mode":0.7, "descr":_("Freehand"), inN: "list", "input":_("Enter a list of points to connect with lines")}],
			"dot": [{"mode":1, "descr":_("Solid dot"), inN: 1, "input":_("Enter the coordinates of the dot")}],
			"opendot": [{"mode":2, "descr":_("Open dot"), inN: 1, "input":_("Enter the coordinates of the dot")}],
			"polygon": [{"mode":0, "descr":_("Polygon"), inN: "list", "input":_("Enter a list of points for dots to connect with lines"), "dotline":1}],
			"closedpolygon": [{"mode":0, "descr":_("Polygon"), inN: "list", "input":_("Enter a list of points for dots to connect with lines"), "dotline":2}],
		}
	};

    if (lines[tarnum]==null) {lines[tarnum] = new Array();}
	if (dots[tarnum]==null) {dots[tarnum] = new Array();}
	if (odots[tarnum]==null) {odots[tarnum] = new Array();}
	if (tplines[tarnum]==null) {tplines[tarnum] = new Array();}
	if (tptypes[tarnum]==null) {tptypes[tarnum] = new Array();}
	if (ineqlines[tarnum]==null) {ineqlines[tarnum] = new Array();}
	if (ineqtypes[tarnum]==null) {ineqtypes[tarnum] = new Array();}

	var defmode, inputmodes = [], selects = [], input, moderef=[],op;
	for (var i=0;i<ansformats.length;i++) {
		if (types[afgroup].hasOwnProperty(ansformats[i])) {
			if (i==0) {
				defmode = types[afgroup][ansformats[i]][0].mode;
			}
			for (var j=0;j<types[afgroup][ansformats[i]].length;j++) {
				moderef[types[afgroup][ansformats[i]][j].mode] = types[afgroup][ansformats[i]][j];
				inputmodes.push(types[afgroup][ansformats[i]][j].mode);
				op = '<option value="'+types[afgroup][ansformats[i]][j].mode+'"';
				op += ' data-af="'+ansformats[i]+'">'+types[afgroup][ansformats[i]][j].descr+'</option>';
				selects.push(op);
			}
		}
    }
    if (activedraw) {
        document.getElementById("a11ydrawnew"+tarnum).innerHTML = selects;
    }
	targets[tarnum].defmode = defmode;
	targets[tarnum].inputmodes = inputmodes;
	targets[tarnum].selects = selects;
	targets[tarnum].moderef = moderef;

	//how can we restore what the student entered, if we're allowing
	//things like (2/3, 0)?  Either we convert that to a decimal, or
	// we have to store original typed answer.
	//maybe new drawla[5] for that purpose?
	//need to be able to get acccess to drawla from here
    //TODO:  Check if thisdrawla was defined
    if (activedraw || !vispreview) {
        if (thisdrawla == null && lines.hasOwnProperty(tarnum)) {
            for (var i=0;i<lines[tarnum].length;i++) {
                adda11ydraw(tarnum, 0, pixcoordstopointlist(lines[tarnum][i], tarnum));
            }
            for (var i=0;i<dots[tarnum].length;i++) {
                adda11ydraw(tarnum, 1, pixcoordstopointlist(dots[tarnum][i], tarnum));
            }
            for (var i=0;i<odots[tarnum].length;i++) {
                adda11ydraw(tarnum, 2, pixcoordstopointlist(odots[tarnum][i], tarnum));
            }
            for (var i=0;i<tplines[tarnum].length;i++) {
                adda11ydraw(tarnum, tptypes[tarnum][i], pixcoordstopointlist(tplines[tarnum][i][0], tarnum)+","+pixcoordstopointlist(tplines[tarnum][i][1], tarnum));
            }
            for (var i=0;i<ineqlines[tarnum].length;i++) {
                adda11ydraw(tarnum, ineqtypes[tarnum][i], pixcoordstopointlist(ineqlines[tarnum][i][0], tarnum)+","+pixcoordstopointlist(ineqlines[tarnum][i][1], tarnum)+","+pixcoordstopointlist(ineqlines[tarnum][i][2], tarnum));
            }
        } else if (thisdrawla != null) {
            for (var i=0;i<thisdrawla.length;i++) {
                //format:  mode, "entered answer"
                //but somehow have to preserve parens in the entered answer??
                adda11ydraw(tarnum, thisdrawla[i][0], thisdrawla[i][1].replace(/\[/g,"(").replace(/\]/g,")"));
            }
        }
    }
    if (vispreview) {
        initBackground(targets[tarnum].canv, tarnum, imgpath, imgwidth, imgheight, true);
    }

}

function adda11ydraw(tarnum,initmode,defval) {
	var thistarg = targets[tarnum];
	var mode = initmode || document.getElementById("a11ydrawnew"+tarnum).value;
	var val = defval || "";
    var afgroup = thistarg.afgroup;
    var n = thistarg.el.getElementsByTagName("li").length+1;
    var disabled = targetOuts[tarnum].disabled ? ' disabled ' : '';
    var numname = '<span class="sr-only draweln">'+_('Drawing element ') + n + '</span>';
    html = numname;
    html += thistarg.moderef[mode].descr + '.<br/>';
	html += '<label><span class="a11ydrawinstr"></span><br/>';
    html += '<input type="text" value="'+val+'" data-n="'+thistarg.moderef[mode].inN+'" ' + disabled;
    html += 'onblur="imathasDraw.updatea11ydraw(this)"/></label>';
	html += '<button type="button" class="imgbutton" onclick="imathasDraw.removea11ydraw(this)"' + disabled + '>';
	html += _("Remove")+' '+numname+'</button>';
	var li = $("<li>", {class:"a11ydrawrow", "data-mode":mode}).html(html);
	$(thistarg.el).append(li);
	li.find(".a11ydrawinstr").text(thistarg.moderef[mode].input);
	if (!defval) {
		li.find("input").focus();
	}
}

function removea11ydraw(el) {
    setariastatus(_('Removed drawing element'));
    var ul = $(el).closest("ul");
    $(el).parent().remove();
    ul.find("li").each(function(i,el) {
        var numname = _('Drawing element ') + (i+1);
        $(el).find(".draweln").html(numname);
    });
    ul.prev().focus();
	encodea11ydraw(ul.attr("id").substring(8));
}
function changea11ydraw(tarel, tarnum) {
	var curmode = $(tarel).val();
	var modedata = targets[tarnum].moderef[curmode];
	$(tarel).closest("li").find(".a11ydrawinstr").text(modedata.input);
	encodea11ydraw();
}
function updatea11ydraw(el) {
    var err = 0;
    var elval = el.value.trim();
    if (elval.charAt(0) != '(' || elval.slice(-1) != ')') {
        err |= 1;
    }
    var pts = elval.slice(1,-1).split(/\)\s*,\s*\(/);
    var inN = el.getAttribute('data-n');
    if (inN.match(/\d/) && pts.length != parseInt(inN)) {
        err |= 2;
    }
    for (var i=0; i<pts.length;i++) {
        var subpts = pts[i].split(/,/);
        if (subpts.length != 2) {
            err |= 1;
        }
        for (var j=0; j<subpts.length; j++) {
            if (subpts[j].trim().length == 0) {
                err |= 1;
            }
        }
    }
    if (el.parentNode.parentNode.lastChild.className == 'noticetext') {
        el.parentNode.parentNode.removeChild(el.parentNode.parentNode.lastChild);
    }
    if (err) {
        el.setAttribute('aria-invalid', true);
        var errspan = document.createElement("span");
        errspan.id = uniqid('a11ydrawerr');
        errspan.className = "noticetext"
        if ((err&1)==1) {
            errspan.innerHTML = '<br>'+_('Error: Invalid format for points. Give points as open parenthesis number comma number close parenthesis. Separate points with a comma.');
            setariastatus(_('Error: Invalid format for points'));
        } else {
            errspan.innerHTML = '<br>'+_('Error: Incorrect number of points. Expecting ') + inN;
            setariastatus(_('Error: Incorrect number of points'));
        }
        el.parentNode.parentNode.appendChild(errspan);
        el.setAttribute('aria-describedby', errspan.id);
    } else {
        el.removeAttribute('aria-invalid');
        el.removeAttribute('aria-describedby');
        setariastatus("");
    }
    var qn = parseInt($(el).closest('.a11ydraw').attr("id").substring(8));
	encodea11ydraw(qn);
}
function pixcoordstopointlist(vals,tarnum) {
	var thistarg = targets[tarnum];
	var x,y;
	x = (vals[0] - thistarg.imgborder)/thistarg.pixperx + thistarg.xmin;
	y = (thistarg.imgheight - vals[1] - thistarg.imgborder)/thistarg.pixpery + thistarg.ymin;
	x = Math.round(x*100)/100;
	y = Math.round(y*100)/100;
	return "("+x+","+y+")";
}
function encodea11ydraw(qn) {
    if (typeof qn == 'undefined') {
        qn = -1;
    }
    var oldCurTarget = curTarget;
	for (var tarnum in targets) {  
        if (qn > -1 && qn != tarnum) { continue; }
        var thistarg = targets[tarnum];
        if (thistarg.type != 'a11y') { continue; }
        if (qn > -1 && qn == tarnum) {
            curTarget = tarnum;
        }
        lines[tarnum].length = 0;
        dots[tarnum].length = 0;
        odots[tarnum].length = 0;
        tplines[tarnum].length = 0;
        tptypes[tarnum].length = 0;
        ineqlines[tarnum].length = 0;
        ineqtypes[tarnum].length = 0;
        
		var enclines = [];
		var encdots = [];
		var encodots = [];
		var enctplines = [];
		var enctpineq = [];
		var saveinput = [];
		var afgroup = targets[tarnum].afgroup;
		$("#a11ydraw"+tarnum).find(".a11ydrawrow").each(function(i,el) {
            var input = $(el).find("input").val();
            var mode = el.getAttribute('data-mode');
			saveinput.push("["+mode+',"'+input+'"]');
			input = input.replace(/[\(\)]/g,'').split(/\s*,\s*/);
			var outpts = [];
            var outptsraw = [];
			for (var i=1;i<input.length;i+=2) {
				try {
					input[i-1] = eval(prepWithMath(mathjs(input[i-1])));
				} catch(e) {
					input[i-1] = NaN;
				}
				try {
					input[i] = eval(prepWithMath(mathjs(input[i])));
				} catch(e) {
					input[i] = NaN;
				}
				input[i-1] = (input[i-1] - thistarg.xmin)*thistarg.pixperx + thistarg.imgborder;
				input[i] = thistarg.imgheight - (input[i] - thistarg.ymin)*thistarg.pixpery - thistarg.imgborder;
				outpts.push(Math.round(input[i-1])+','+Math.round(input[i]));
                outptsraw.push([input[i-1], input[i]]);
			}
			if (mode==1) {
				encdots.push('('+outpts.join('),(')+')');
                dots[tarnum].push(outptsraw);
			} else if (mode==2) {
				encodots.push('('+outpts.join('),(')+')');
                odots[tarnum].push(outptsraw);
			} else if (mode<1) {
				enclines.push('('+outpts.join('),(')+')');
                lines[tarnum].push(outptsraw);
			} else if (mode>=5 && mode<10 && outpts.length==2) {
				enctplines.push('('+mode+','+outpts.join(',')+')');
                tplines[tarnum].push(outptsraw);
                tptypes[tarnum].push(mode);
			} else if (mode>=10 && outpts.length==3) {
				enctpineq.push('('+mode+','+outpts.join(',')+')');
                ineqlines[tarnum].push(outptsraw);
                ineqtypes[tarnum].push(mode);
			}
		});
		targetOuts[tarnum].value = enclines.join(';')+';;'+encdots.join(',')+';;'+encodots.join(',')+';;'+enctplines.join(',')+';;'+enctpineq.join(',')+';;'+saveinput.join(',');
		$(targetOuts[tarnum]).trigger("input").trigger("change");
	}
    if (qn > -1 && targets[qn].vispreview) {
        drawTarget(null,null,true);
    }
    curTarget = oldCurTarget;
}

function addTarget(tarnum,target,imgpath,formel,xmin,xmax,ymin,ymax,imgborder,imgwidth,imgheight,defmode,dotline,locky,snaptogrid) {
	var tarel = document.getElementById(target);

	if ($(tarel).is(":hidden")) {
		$(tarel).on("mouseover.imathasdrawwait touchstart.imathasdrawwait", function() {
				$(tarel).off("mouseover.imathasdrawwait touchstart.imathasdrawwait");
				initCanvases(tarnum);
		});
	}
	tarel.style.userSelect = "none";
	tarel.style.webkitUserSelect = "none";
	tarel.style.MozUserSelect = "none";

	var tarpos = getPosition(tarel);

	targets[tarnum] = {type:'canvas', el: tarel, left: tarpos.x, top: tarpos.y, width: tarel.offsetWidth, height: tarel.offsetHeight, xmin: xmin, xmax: xmax, ymin: ymin, ymax: ymax, imgborder: imgborder, imgwidth: imgwidth, imgheight: imgheight, mode: defmode, dotline: dotline};
	if (typeof snaptogrid=="string" && snaptogrid.indexOf(":")!=-1) {
		snaptogrid = snaptogrid.split(":");
		targets[tarnum].snaptogridx = 1*snaptogrid[0];
		targets[tarnum].snaptogridy = 1*snaptogrid[1];
	} else {
		targets[tarnum].snaptogridx = 1*snaptogrid;
		targets[tarnum].snaptogridy = 1*snaptogrid;
	}
	targets[tarnum].pixperx = (imgwidth - 2*imgborder)/(xmax-xmin);
	targets[tarnum].pixpery = (ymin==ymax)?1:((imgheight - 2*imgborder)/(ymax-ymin));

    targetOuts[tarnum] = document.getElementById(formel);

	if (lines[tarnum]==null) {lines[tarnum] = new Array();}
	if (dots[tarnum]==null) {dots[tarnum] = new Array();}
	if (odots[tarnum]==null) {odots[tarnum] = new Array();}
	if (tplines[tarnum]==null) {tplines[tarnum] = new Array();}
	if (tptypes[tarnum]==null) {tptypes[tarnum] = new Array();}
	if (ineqlines[tarnum]==null) {ineqlines[tarnum] = new Array();}
	if (ineqtypes[tarnum]==null) {ineqtypes[tarnum] = new Array();}
	if (defmode>=5) {
		drawstyle[tarnum] = 1;
	} else {
		drawstyle[tarnum] = 0;
	}
    drawlocky[tarnum] = locky;
	initBackground(tarel, tarnum, imgpath, imgwidth, imgheight, false);
}

function initBackground(tarel, tarnum, imgpath, imgwidth, imgheight, skipencode) {
    if (imgpath.match(/initPicture/)) {
		if ($(tarel).closest('.drawcanvasholder').length == 0) {
			$(tarel).removeClass("drawcanvas").wrap($("<div>", {
				class: "drawcanvas",
				style: "position:relative;width:"+imgwidth+"px;height:"+imgheight+"px"
			})).wrap($("<div>", {
				class: "drawcanvasholder",
				style: "position:absolute;top:0;left:0;z-index:2"
			}));
			$(tarel).closest('.drawcanvas').prepend($('<div>', {
				class: "canvasbg",
				style: "position:absolute;top:0;left:0"
			}).append($("<embed>", {
				"data-nomag": 1,
				type: "image/svg+xml",
				align: "middle",
				width: imgwidth,
				height: imgheight,
				script: imgpath
			}).attr("width",imgwidth).attr("height",imgheight)));
		}
		imgs[tarnum] = null;
		var oldcurTarget = curTarget;
		curTarget = tarnum;
		drawTarget(null,null,skipencode);
		setTimeout(function() { window.drawPics($(tarel).closest('.drawcanvas')[0]);}, 30);
		curTarget = oldcurTarget;
	} else if (imgpath !== '') {
		imgs[tarnum] = new Image();
		imgs[tarnum].onload = function() {
			var oldcurTarget = curTarget;
			curTarget = tarnum;
			drawTarget(null,null,skipencode);
			curTarget = oldcurTarget;
		};
		imgs[tarnum].src = imgpath;
	} else {
		imgs[tarnum] = null;
		var oldcurTarget = curTarget;
		curTarget = tarnum;
		drawTarget(null,null,skipencode);
		curTarget = oldcurTarget;
	}
}

function settool(curel,tarnum,mode) {
	var mydiv = document.getElementById("drawtools"+tarnum);
	var mycel       = mydiv.getElementsByTagName("span");
	for (var i=0; i<mycel.length; i++) {
		mycel[i].className = '';
	}
	mycel       = mydiv.getElementsByTagName("img");
	for (var i=0; i<mycel.length; i++) {
		mycel[i].className = '';
	}
	curel.className = "sel";

	setDrawMode(tarnum,mode);
	curTarget = tarnum;
	drawTarget();
}
function setDrawMode(tarnum,mode) {
	targets[tarnum].mode = mode;
}
function setDotLine(tarnum,onoff) {
	targets[tarnum].dotline = onoff;
}

function drawTarget(x,y,skipencode) {
	try {
        if (targets[curTarget].canv) {
            var ctx = targets[curTarget].canv.getContext('2d');
        } else {
		    var ctx = targets[curTarget].el.getContext('2d');
        }
	} catch(e) {
		if (!nocanvaswarning) {
			nocanvaswarning = true;
			alert("Your browser does not support drawing answer entry.  Please try again using Internet Explorer 6+ (Windows), FireFox 1.5+ (Win/Mac), Safari 1.3+ (Mac), Opera 9+ (Win/Mac), or Camino (Mac)");
		}
	}

	ctx.fillStyle = "rgb(0,0,255)";
	ctx.lineWidth = 2;
	ctx.strokeStyle = "rgb(0,0,255)";
	ctx.clearRect(0,0,targets[curTarget].imgwidth,targets[curTarget].imgheight);
	if (imgs[curTarget] !== null) {
		ctx.drawImage(imgs[curTarget],0,0);
	}
	ctx.beginPath();
	for (var i=0;i<ineqlines[curTarget].length; i++) {
		var colornum = i%3;
		ctx.strokeStyle = ineqcolors[colornum];
		ctx.fillStyle = ineqcolors[colornum];
		//is an inequality
		var slope = null;
		var x2 = null; var x3 = null;
		var y2 = null; var y3 = null;
		if (ineqlines[curTarget][i].length==3) { //three points set
			x2 = ineqlines[curTarget][i][1][0];
			y2 = ineqlines[curTarget][i][1][1];
			x3 = ineqlines[curTarget][i][2][0];
			y3 = ineqlines[curTarget][i][2][1];
		} else if (ineqlines[curTarget][i].length==2) { //two points set
			x2 = ineqlines[curTarget][i][1][0];
			y2 = ineqlines[curTarget][i][1][1];
			x3 = x;
			y3 = y;
		} else if (curIneqcurve==i && x!= null && ineqlines[curTarget][i].length==1) {
			x2 = x;
			y2 = y;
		}
		if (x3 != null) { //third point is set or varying; plot dot at point and shade that side
			ctx.save();
			ctx.fillStyle = ineqacolors[colornum];
			//ctx.fillStyle = "rgba(0,0,255,0.4)";
			//ctx.fillStyle = "rgb(0,255,0)";
			ctx.beginPath();

			if(ineqtypes[curTarget][i] <= 10.2){//linear inequality
				if (x2!=ineqlines[curTarget][i][0][0]) {
					var slope = (y2 - ineqlines[curTarget][i][0][1])/(x2-ineqlines[curTarget][i][0][0]);
				}
				if (Math.abs(x2-ineqlines[curTarget][i][0][0])<1 || Math.abs(slope)>100) {
					ctx.moveTo(ineqlines[curTarget][i][0][0],0);
					ctx.lineTo(ineqlines[curTarget][i][0][0],targets[curTarget].imgheight);
					if (x3>x2) {//shade right
						ctx.lineTo(targets[curTarget].imgwidth,targets[curTarget].imgheight);
						ctx.lineTo(targets[curTarget].imgwidth,0);
						ctx.closePath();
					} else {
						ctx.lineTo(0,targets[curTarget].imgheight);
						ctx.lineTo(0,0);
						ctx.closePath();
					}
				} else {
					var yb = slope*(x3 - x2) + y2;
					var yleft = ineqlines[curTarget][i][0][1] - slope*ineqlines[curTarget][i][0][0];
					var yright = ineqlines[curTarget][i][0][1] + slope*(targets[curTarget].imgwidth-ineqlines[curTarget][i][0][0]);

					ctx.moveTo(0,yleft);
					ctx.lineTo(targets[curTarget].imgwidth,yright);
					if (y3 > yb) { //shade above
						ctx.lineTo(targets[curTarget].imgwidth,targets[curTarget].imgheight);
						ctx.lineTo(0,targets[curTarget].imgheight);
						ctx.closePath();
					} else { //shade below
						ctx.lineTo(targets[curTarget].imgwidth,0);
						ctx.lineTo(0,0);
						ctx.closePath();
					}
				}
			} else if (ineqtypes[curTarget][i] <= 10.4) {//quadratic inequality shading
				shadeParabola(ctx,ineqlines[curTarget][i][0][0],ineqlines[curTarget][i][0][1],x2,y2,x3,y3,targets[curTarget].imgwidth,targets[curTarget].imgheight);
			} else { //abs inequality shading
                if (Math.abs(x2-ineqlines[curTarget][i][0][0])>1) { // only draw if not vertical
                    var slope = (y2 - ineqlines[curTarget][i][0][1])/(x2-ineqlines[curTarget][i][0][0]);
                    if (x2<ineqlines[curTarget][i][0][0]) {  //want slope on right
						slope *= -1;
                    }
                    var yb = slope*Math.abs(x3 - ineqlines[curTarget][i][0][0]) + ineqlines[curTarget][i][0][1];
                    var yleft = ineqlines[curTarget][i][0][1] + slope*ineqlines[curTarget][i][0][0];
					var yright = ineqlines[curTarget][i][0][1] + slope*(targets[curTarget].imgwidth-ineqlines[curTarget][i][0][0]);
                    if (y3 > yb) { //shade above
                        ctx.moveTo(targets[curTarget].imgwidth,targets[curTarget].imgheight);
                        ctx.lineTo(0,targets[curTarget].imgheight);
                    } else {
                        ctx.moveTo(targets[curTarget].imgwidth,0);
                        ctx.lineTo(0,0);
                    }
                    ctx.lineTo(0,yleft);
					ctx.lineTo(ineqlines[curTarget][i][0][0],ineqlines[curTarget][i][0][1]);
					ctx.lineTo(targets[curTarget].imgwidth,yright);
                }
            }
			ctx.fill();
			ctx.restore();
		}
		ctx.beginPath();

		if (x2 != null) { //at least one point set
			if(ineqtypes[curTarget][i] <= 10.2){//linear inequality
				if (x2!=ineqlines[curTarget][i][0][0]) {
					var slope = (y2 - ineqlines[curTarget][i][0][1])/(x2-ineqlines[curTarget][i][0][0]);
				}
				if (Math.abs(x2-ineqlines[curTarget][i][0][0])<1 || Math.abs(slope)>100) { //vert line
					//document.getElementById("ans0-0").innerHTML = 'vert';
					if(ineqtypes[curTarget][i]==10.2) {
						if (dragObj != null && dragObj.mode>=10 && dragObj.mode<11 && dragObj.num==i && dragObj.subnum==0) {
							ctx.dashedLine(ineqlines[curTarget][i][1][0],ineqlines[curTarget][i][1][1],ineqlines[curTarget][i][1][0],targets[curTarget].imgheight);
							ctx.dashedLine(ineqlines[curTarget][i][1][0],ineqlines[curTarget][i][1][1],ineqlines[curTarget][i][1][0],0);
						} else {
							ctx.dashedLine(ineqlines[curTarget][i][0][0],ineqlines[curTarget][i][0][1],ineqlines[curTarget][i][0][0],targets[curTarget].imgheight);
							ctx.dashedLine(ineqlines[curTarget][i][0][0],ineqlines[curTarget][i][0][1],ineqlines[curTarget][i][0][0],0);
						}
					} else {
						ctx.moveTo(ineqlines[curTarget][i][0][0],0);
						ctx.lineTo(ineqlines[curTarget][i][0][0],targets[curTarget].imgheight);
						ctx.stroke();
					}
				} else {
					//document.getElementById("ans0-0").innerHTML = slope;
					var yleft = ineqlines[curTarget][i][0][1] - slope*ineqlines[curTarget][i][0][0];
					var yright = ineqlines[curTarget][i][0][1] + slope*(targets[curTarget].imgwidth-ineqlines[curTarget][i][0][0]);
					//TODO:  fix for very large slopes;
					if (ineqtypes[curTarget][i]==10.2) {
						if (dragObj != null && dragObj.mode>=10 && dragObj.mode<11 && dragObj.num==i && dragObj.subnum==0) {
							ctx.dashedLine(ineqlines[curTarget][i][1][0],ineqlines[curTarget][i][1][1],targets[curTarget].imgwidth,yright);
							ctx.dashedLine(ineqlines[curTarget][i][1][0],ineqlines[curTarget][i][1][1],0,yleft);
						} else {
							ctx.dashedLine(ineqlines[curTarget][i][0][0],ineqlines[curTarget][i][0][1],targets[curTarget].imgwidth,yright);
							ctx.dashedLine(ineqlines[curTarget][i][0][0],ineqlines[curTarget][i][0][1],0,yleft);
						}
					} else {
						ctx.moveTo(0,yleft);
						ctx.lineTo(targets[curTarget].imgwidth,yright);
						ctx.stroke();
					}
				}
			} else if (ineqtypes[curTarget][i] <= 10.4) {//quadratic inequality shading
				if (ineqtypes[curTarget][i]==10.3) {//solid parabola
					if (x2 != ineqlines[curTarget][i][0][0]) {
						if (y2==ineqlines[curTarget][i][0][1]) {
							ctx.moveTo(0,y2);
							ctx.lineTo(targets[curTarget].imgwidth,y2);
						} else {
							var stretch = (y2 - ineqlines[curTarget][i][0][1])/((x2 - ineqlines[curTarget][i][0][0])*(x2 - ineqlines[curTarget][i][0][0]));
							if (y2>ineqlines[curTarget][i][0][1]) {
								//crosses at y=imgheight
								var inta = Math.sqrt((targets[curTarget].imgheight - ineqlines[curTarget][i][0][1])/stretch)+ineqlines[curTarget][i][0][0];
								var intb = -1*Math.sqrt((targets[curTarget].imgheight - ineqlines[curTarget][i][0][1])/stretch)+ineqlines[curTarget][i][0][0];
								var cnty = ineqlines[curTarget][i][0][1] - (targets[curTarget].imgheight - ineqlines[curTarget][i][0][1]);
								var qy = targets[curTarget].imgheight;
							} else {
								var inta = Math.sqrt((0 - ineqlines[curTarget][i][0][1])/stretch)+ineqlines[curTarget][i][0][0];
								var intb = -1*Math.sqrt((0 - ineqlines[curTarget][i][0][1])/stretch)+ineqlines[curTarget][i][0][0];
								var cnty = 2*ineqlines[curTarget][i][0][1];
								var qy = 0;
							}
							var cp1x = inta + 2.0/3.0*(ineqlines[curTarget][i][0][0] - inta);
							var cp1y = qy + 2.0/3.0*(cnty - qy);
							var cp2x = cp1x + (intb - inta)/3.0;
							var cp2y = cp1y;
							ctx.moveTo(inta,qy);
							ctx.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,intb,qy);
						}
					}
					ctx.stroke();
				} else {//10.4, dashed parabola
					if(x2 != ineqlines[curTarget][i][0][0]){
						dashedParabola(ctx,ineqlines[curTarget][i][0][0],ineqlines[curTarget][i][0][1],x2,y2,targets[curTarget].imgwidth,targets[curTarget].imgheight);
					}
				}
			} else { //abs ineq
                if (x2!=ineqlines[curTarget][i][0][0]) {
                    var slope = (y2 - ineqlines[curTarget][i][0][1])/(x2-ineqlines[curTarget][i][0][0]);
                    if (x2<ineqlines[curTarget][i][0][0]) {  //want slope on right
						slope *= -1;
                    }
                    var yleft = ineqlines[curTarget][i][0][1] + slope*ineqlines[curTarget][i][0][0];
					var yright = ineqlines[curTarget][i][0][1] + slope*(targets[curTarget].imgwidth-ineqlines[curTarget][i][0][0]);

					if (ineqtypes[curTarget][i]==10.6) {
						if (dragObj != null && dragObj.mode>=10 && dragObj.mode<11 && dragObj.num==i && dragObj.subnum==0) {
							ctx.dashedLine(ineqlines[curTarget][i][0][0],ineqlines[curTarget][i][0][1],targets[curTarget].imgwidth,yright);
							ctx.dashedLine(ineqlines[curTarget][i][0][0],ineqlines[curTarget][i][0][1],0,yleft);
						} else {
							ctx.dashedLine(ineqlines[curTarget][i][0][0],ineqlines[curTarget][i][0][1],targets[curTarget].imgwidth,yright);
							ctx.dashedLine(ineqlines[curTarget][i][0][0],ineqlines[curTarget][i][0][1],0,yleft);
						}
					} else {
                        ctx.moveTo(0,yleft);
                        ctx.lineTo(ineqlines[curTarget][i][0][0],ineqlines[curTarget][i][0][1]);
						ctx.lineTo(targets[curTarget].imgwidth,yright);
						ctx.stroke();
					}
                }
            }
		}
        if (ineqtypes[curTarget][i] == targets[curTarget].mode) {
            ctx.beginPath();
            for (var j=0; j<ineqlines[curTarget][i].length; j++) {
                if (j==2) {
                    ctx.arc(ineqlines[curTarget][i][j][0],ineqlines[curTarget][i][j][1],4,0,Math.PI*2,true);
                    ctx.fill();
                } else {
                    ctx.fillRect(ineqlines[curTarget][i][j][0]-3,ineqlines[curTarget][i][j][1]-3,6,6);
                }
            }
            if (ineqlines[curTarget][i].length==1 && x2!=null) {
                ctx.fillRect(x2-3,y2-3,6,6);
            }
        }
		ctx.beginPath();
	}
	ctx.fillStyle = "rgb(0,0,255)";
	if (drawlocky[curTarget]==1) {
		ctx.lineWidth = 4;
	} else {
		ctx.lineWidth = 2;
	}
	ctx.strokeStyle = "rgb(0,0,255)";
	for (var i=0;i<tplines[curTarget].length; i++) {
		if (tptypes[curTarget][i]>=5 && tptypes[curTarget][i]<6) {//if a tpline
			var slope = null;
			var x2 = null;
			var y2 = null; var u, uperp;
			if (tplines[curTarget][i].length==2) {  //if two points set
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {  //one point set, use mouse pos for third
				x2 = x;
				y2 = y;
			}
			if (x2 != null) { //at least one point set
				if (tptypes[curTarget][i]==5.3 || tptypes[curTarget][i]==5.4) {
					ctx.moveTo(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1]);
					ctx.lineTo(x2,y2);
					if (tptypes[curTarget][i]==5.4) {
						var u = [x2 - tplines[curTarget][i][0][0], y2 - tplines[curTarget][i][0][1]];
						var d = Math.sqrt(u[0]*u[0]+u[1]*u[1]);
						if (d > 0.0001) {
						 	 u = [u[0]/d, u[1]/d];
						 	 uperp = [-u[1],u[0]];
						 	 ctx.moveTo(x2 - 15*u[0]-4*uperp[0],y2-15*u[1]-5*uperp[1]);
						 	 ctx.lineTo(x2,y2);
						 	 ctx.moveTo(x2 - 15*u[0]+4*uperp[0],y2-15*u[1]+5*uperp[1]);
						 	 ctx.lineTo(x2,y2);
						 }
					}
				} else {
					if (x2!=tplines[curTarget][i][0][0]) {
						var slope = (y2 - tplines[curTarget][i][0][1])/(x2-tplines[curTarget][i][0][0]);
					}
					if (Math.abs(x2-tplines[curTarget][i][0][0])<1 || Math.abs(slope)>100) { //vert line
						//document.getElementById("ans0-0").innerHTML = 'vert';
						if (tptypes[curTarget][i]==5.2) {
							ctx.moveTo(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1]);
							if (y2>tplines[curTarget][i][0][1]) {
								ctx.lineTo(tplines[curTarget][i][0][0],targets[curTarget].imgheight);
							} else {
								ctx.lineTo(tplines[curTarget][i][0][0],0);
							}
						} else {
							ctx.moveTo(tplines[curTarget][i][0][0],0);
							ctx.lineTo(tplines[curTarget][i][0][0],targets[curTarget].imgheight);
						}
					} else {
						var yleft = tplines[curTarget][i][0][1] - slope*tplines[curTarget][i][0][0];
						var yright = tplines[curTarget][i][0][1] + slope*(targets[curTarget].imgwidth-tplines[curTarget][i][0][0]);

						//document.getElementById("ans0-0").innerHTML = slope;
						var yleft = tplines[curTarget][i][0][1] - slope*tplines[curTarget][i][0][0];
						var yright = tplines[curTarget][i][0][1] + slope*(targets[curTarget].imgwidth-tplines[curTarget][i][0][0]);
						if (tptypes[curTarget][i]==5.2) {
							ctx.moveTo(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1]);
							if (x2>tplines[curTarget][i][0][0]) {
								ctx.lineTo(targets[curTarget].imgwidth,yright);
							} else {
								ctx.lineTo(0,yleft);
							}
						} else {
							//TODO:  fix for very large slopes;
							ctx.moveTo(0,yleft);
							ctx.lineTo(targets[curTarget].imgwidth,yright);
						}
					}
				}
			}
		} else if (tptypes[curTarget][i]==6 || tptypes[curTarget][i]==6.1 || tptypes[curTarget][i]==6.2) {//if a tp parabola
			var y2 = null;
			var x2 = null;
			if (tplines[curTarget][i].length==2) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {
				x2 = x;
				y2 = y;
			}
			if (x2 != null) {
				if (tptypes[curTarget][i]==6) {
					if (y2==tplines[curTarget][i][0][1]) {
						ctx.moveTo(0,y2);
						ctx.lineTo(targets[curTarget].imgwidth,y2);
					} else if (x2 == tplines[curTarget][i][0][0]) {
						ctx.moveTo(x2,tplines[curTarget][i][0][1]);
						if (y2>tplines[curTarget][i][0][1]) {
							ctx.lineTo(x2,targets[curTarget].imgheight);
						} else {
							ctx.lineTo(x2,0);
						}
					} else {
						var stretch = (y2 - tplines[curTarget][i][0][1])/((x2 - tplines[curTarget][i][0][0])*(x2 - tplines[curTarget][i][0][0]));
						if (y2>tplines[curTarget][i][0][1]) {
							//crosses at y=imgheight
							var inta = Math.sqrt((targets[curTarget].imgheight - tplines[curTarget][i][0][1])/stretch)+tplines[curTarget][i][0][0];
							var intb = -1*Math.sqrt((targets[curTarget].imgheight - tplines[curTarget][i][0][1])/stretch)+tplines[curTarget][i][0][0];
							var cnty = tplines[curTarget][i][0][1] - (targets[curTarget].imgheight - tplines[curTarget][i][0][1]);
							var qy = targets[curTarget].imgheight;
						} else {
							var inta = Math.sqrt((0 - tplines[curTarget][i][0][1])/stretch)+tplines[curTarget][i][0][0];
							var intb = -1*Math.sqrt((0 - tplines[curTarget][i][0][1])/stretch)+tplines[curTarget][i][0][0];
							var cnty = 2*tplines[curTarget][i][0][1];
							var qy = 0;
						}
						var cp1x = inta + 2.0/3.0*(tplines[curTarget][i][0][0] - inta);
						var cp1y = qy + 2.0/3.0*(cnty - qy);
						var cp2x = cp1x + (intb - inta)/3.0;
						var cp2y = cp1y;
						ctx.moveTo(inta,qy);
						ctx.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,intb,qy);
					}
				} else if (tptypes[curTarget][i]==6.1) {
					if (x2==tplines[curTarget][i][0][0]) {
						ctx.moveTo(x2,0);
						ctx.lineTo(x2,targets[curTarget].imgheight);
					} else if (y2 == tplines[curTarget][i][0][1]) {
						ctx.moveTo(tplines[curTarget][i][0][0],y2);
						if (x2>tplines[curTarget][i][0][0]) {
							ctx.lineTo(targets[curTarget].imgwidth,y2);
						} else {
							ctx.lineTo(0,y2);
						}
					} else {
						var stretch = (x2 - tplines[curTarget][i][0][0])/((y2 - tplines[curTarget][i][0][1])*(y2 - tplines[curTarget][i][0][1]));
						if (x2>tplines[curTarget][i][0][0]) {
							//crosses at x=imgwidth
							var inta = Math.sqrt((targets[curTarget].imgwidth - tplines[curTarget][i][0][0])/stretch)+tplines[curTarget][i][0][1];
							var intb = -1*Math.sqrt((targets[curTarget].imgwidth - tplines[curTarget][i][0][0])/stretch)+tplines[curTarget][i][0][1];
							var cntx = tplines[curTarget][i][0][0] - (targets[curTarget].imgwidth - tplines[curTarget][i][0][0]);
							var qx = targets[curTarget].imgwidth;
						} else {
							var inta = Math.sqrt((0 - tplines[curTarget][i][0][0])/stretch)+tplines[curTarget][i][0][1];
							var intb = -1*Math.sqrt((0 - tplines[curTarget][i][0][0])/stretch)+tplines[curTarget][i][0][1];
							var cntx = 2*tplines[curTarget][i][0][0];
							var qx = 0;
						}
						var cp1y = inta + 2.0/3.0*(tplines[curTarget][i][0][1] - inta);
						var cp1x = qx + 2.0/3.0*(cntx - qx);
						var cp2y = cp1y + (intb - inta)/3.0;
						var cp2x = cp1x;
						ctx.moveTo(qx,inta);
						ctx.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,qx,intb);
					}
				} else if (tptypes[curTarget][i]==6.2) {
					if (y2==tplines[curTarget][i][0][1]) {
						ctx.moveTo(tplines[curTarget][i][0][0],y2);
						ctx.lineTo(x2 < tplines[curTarget][i][0][0] ? 0 : targets[curTarget].imgwidth,y2);
					} else if (x2 == tplines[curTarget][i][0][0]) {
						ctx.moveTo(x2,tplines[curTarget][i][0][1]);
						if (y2>tplines[curTarget][i][0][1]) {
							ctx.lineTo(x2,targets[curTarget].imgheight);
						} else {
							ctx.lineTo(x2,0);
						}
					} else {
						var stretch = (y2 - tplines[curTarget][i][0][1])/((x2 - tplines[curTarget][i][0][0])*(x2 - tplines[curTarget][i][0][0]));
						var xm = (x2 < tplines[curTarget][i][0][0]) ? -1 : 1;
                        if (y2>tplines[curTarget][i][0][1]) {
							//crosses at y=imgheight
                            var xs = Math.sqrt((targets[curTarget].imgheight - tplines[curTarget][i][0][1])/stretch);
							var qy = targets[curTarget].imgheight;
						} else {
                            var xs = Math.sqrt((0 - tplines[curTarget][i][0][1])/stretch);
							var qy = 0;
						}
                        if (x2 < tplines[curTarget][i][0][0]) {
                            xs *= -1;
                        }
						var cp1x = tplines[curTarget][i][0][0] + 1.0/3.0*xs;
						var cp1y = tplines[curTarget][i][0][1];
						var cp2x = tplines[curTarget][i][0][0] + 2.0/3.0*xs;
						var cp2y = tplines[curTarget][i][0][1] + 1.0/3.0*stretch*xs*xs;
						ctx.moveTo(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1]);
						ctx.bezierCurveTo(cp1x,cp1y,cp2x,cp2y,tplines[curTarget][i][0][0] + xs,qy);
					}
                }
			}
		} else if (tptypes[curTarget][i]==6.5) {//if a tp sqrtt
			var y2 = null;
			var x2 = null;
			if (tplines[curTarget][i].length==2) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {
				x2 = x;
				y2 = y;
			}
			if (x2 != null && x2!=tplines[curTarget][i][0][0]) {
				if (y2==tplines[curTarget][i][0][1]) {
					ctx.moveTo(tplines[curTarget][i][0][0],y2);
					if (x2 > tplines[curTarget][i][0][0]) {
						ctx.lineTo(targets[curTarget].imgwidth,y2);
					} else {
						ctx.lineTo(0,y2);
					}
				} else {
					var flip = (x2 < tplines[curTarget][i][0][0])?-1:1;
					var stretch = (y2-tplines[curTarget][i][0][1])/Math.sqrt(flip*(x2-tplines[curTarget][i][0][0]));
					ctx.moveTo(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1]);
					curx = tplines[curTarget][i][0][0]; cury = tplines[curTarget][i][0][1];

					do {
						curx += flip*3;
						ctx.lineTo(curx, stretch*Math.sqrt(flip*(curx - tplines[curTarget][i][0][0])) + tplines[curTarget][i][0][1]);
					} while (curx > 0 && curx < targets[curTarget].imgwidth && cury > 0 && cury < targets[curTarget].imgheight);
				}
			}
		} else if (tptypes[curTarget][i]==6.3) {//if a tp cubic
			var y2 = null;
			var x2 = null;
			if (tplines[curTarget][i].length==2) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {
				x2 = x;
				y2 = y;
			}
			if (x2 != null && x2!=tplines[curTarget][i][0][0]) {
				if (y2==tplines[curTarget][i][0][1]) {
					ctx.moveTo(0,y2);
					ctx.lineTo(targets[curTarget].imgwidth,y2);
				} else {
					var stretch = (y2-tplines[curTarget][i][0][1])/safepow(x2-tplines[curTarget][i][0][0], 3);
					var cury = 0;
					for (var curx=0;curx < targets[curTarget].imgwidth+4;curx += 1) {
						cury = stretch*safepow(curx-tplines[curTarget][i][0][0],3) + tplines[curTarget][i][0][1];
						if (cury<-100) { cury = -100;}
						if (cury>targets[curTarget].imgheight+100) { cury=targets[curTarget].imgheight+100;}
						if (curx==0) {
							ctx.moveTo(curx,cury);
						} else {
							ctx.lineTo(curx,cury);
						}
					}
				}
			}
		} else if (tptypes[curTarget][i]==6.6) {//if a tp cube root
			var y2 = null;
			var x2 = null;
			if (tplines[curTarget][i].length==2) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {
				x2 = x;
				y2 = y;
			}
			if (x2 != null && x2!=tplines[curTarget][i][0][0]) {
				if (y2==tplines[curTarget][i][0][1]) {
					ctx.moveTo(0,y2);
					ctx.lineTo(targets[curTarget].imgwidth,y2);
				} else {
					var stretch = (y2-tplines[curTarget][i][0][1])/safepow(x2-tplines[curTarget][i][0][0], 1/3);
					var cury = 0;
					for (var curx=0;curx < targets[curTarget].imgwidth+4;curx += 1) {
						cury = stretch*safepow(curx-tplines[curTarget][i][0][0], 1/3) + tplines[curTarget][i][0][1];
						//if (curx==90) {console.log(originy+","+tplines[curTarget][i][0][0]+","+tplines[curTarget][i][0][1]+","+curx+","+cury)};
						if (cury<-100) { cury = -100;}
						if (cury>targets[curTarget].imgheight+100) { cury=targets[curTarget].imgheight+100;}
						if (curx==0) {
							ctx.moveTo(curx,cury);
						} else {
							ctx.lineTo(curx,cury);
						}
					}
				}
			}
		} else if (tptypes[curTarget][i]>=7 && tptypes[curTarget][i]<8) {//if a tp circle
			var y2 = null;
			var x2 = null;
			if (tplines[curTarget][i].length==2) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {
				x2 = x;
				y2 = y;
			}
			if (x2 != null && (x2!=tplines[curTarget][i][0][0] || y2!=tplines[curTarget][i][0][1])) {
				if (tptypes[curTarget][i]==7) { //is a tp circle
					var rad = Math.sqrt((x2-tplines[curTarget][i][0][0])*(x2-tplines[curTarget][i][0][0]) + (y2-tplines[curTarget][i][0][1])*(y2-tplines[curTarget][i][0][1]));
					ctx.arc(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1],rad,0,2*Math.PI,true);
				} else if (tptypes[curTarget][i]==7.2) { //if a tp ellipse
					var rx = Math.abs(x2-tplines[curTarget][i][0][0]);
					var ry = Math.abs(y2-tplines[curTarget][i][0][1]);
					if (curTPcurve==i || (dragObj != null && dragObj.num==i)) {
						ctx.strokeStyle = "rgb(0,255,255)";
						ctx.lineWidth = 1;
						ctx.dashedLine(x2,y2,x2-2*(x2-tplines[curTarget][i][0][0]),y2,5);
						ctx.dashedLine(x2,y2,x2,y2-2*(y2-tplines[curTarget][i][0][1]),5);
						ctx.dashedLine(x2,y2-2*(y2-tplines[curTarget][i][0][1]),x2-2*(x2-tplines[curTarget][i][0][0]),y2-2*(y2-tplines[curTarget][i][0][1]),5);
						ctx.dashedLine(x2-2*(x2-tplines[curTarget][i][0][0]),y2,x2-2*(x2-tplines[curTarget][i][0][0]),y2-2*(y2-tplines[curTarget][i][0][1]),5);
						ctx.lineWidth = 2;
					}
					ctx.strokeStyle = "rgb(0,0,255)";
					ctx.save(); // save state
					ctx.beginPath();
					ctx.translate(tplines[curTarget][i][0][0]-rx, tplines[curTarget][i][0][1]-ry);
					ctx.scale(rx, ry);
					ctx.arc(1, 1, 1, 0, 2 * Math.PI, false);
					ctx.restore(); // restore to original state
				} else if (tptypes[curTarget][i]==7.4) { //if a tp vert hyperbola
					var b = Math.abs(x2-tplines[curTarget][i][0][0]);
					var a = Math.abs(y2-tplines[curTarget][i][0][1]);
					var m = Math.abs(a/b);
					ctx.strokeStyle = asymcolor;
					ctx.dashedLine(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1],targets[curTarget].imgwidth,tplines[curTarget][i][0][1]+m*(targets[curTarget].imgwidth-tplines[curTarget][i][0][0]));
					ctx.dashedLine(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1],targets[curTarget].imgwidth,tplines[curTarget][i][0][1]-m*(targets[curTarget].imgwidth-tplines[curTarget][i][0][0]));
					ctx.dashedLine(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1],0,tplines[curTarget][i][0][1]-m*tplines[curTarget][i][0][0]);
					ctx.dashedLine(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1],0,tplines[curTarget][i][0][1]+m*tplines[curTarget][i][0][0]);
					if (curTPcurve==i || (dragObj != null && dragObj.num==i)) {
						ctx.strokeStyle = "rgb(0,255,255)";
						ctx.lineWidth = 1;
						ctx.dashedLine(x2,y2,x2-2*(x2-tplines[curTarget][i][0][0]),y2,5);
						ctx.dashedLine(x2,y2,x2,y2-2*(y2-tplines[curTarget][i][0][1]),5);
						ctx.dashedLine(x2,y2-2*(y2-tplines[curTarget][i][0][1]),x2-2*(x2-tplines[curTarget][i][0][0]),y2-2*(y2-tplines[curTarget][i][0][1]),5);
						ctx.dashedLine(x2-2*(x2-tplines[curTarget][i][0][0]),y2,x2-2*(x2-tplines[curTarget][i][0][0]),y2-2*(y2-tplines[curTarget][i][0][1]),5);
						ctx.lineWidth = 2;
					}
					ctx.beginPath();
					ctx.strokeStyle = "rgb(0,0,255)";
					for (var curx=0;curx < targets[curTarget].imgwidth+4;curx += 3) {
						cury = tplines[curTarget][i][0][1] + Math.sqrt((Math.pow(curx-tplines[curTarget][i][0][0], 2)/(b*b) + 1)*a*a);
						if (cury<-100) { cury = -100;}
						if (cury>targets[curTarget].imgheight+100) { cury=targets[curTarget].imgheight+100;}
						if (curx==0) {
							ctx.moveTo(curx,cury);
						} else {
							ctx.lineTo(curx,cury);
						}
					}
					ctx.stroke();
					ctx.beginPath();
					for (var curx=0;curx < targets[curTarget].imgwidth+4;curx += 3) {
						cury = tplines[curTarget][i][0][1] - Math.sqrt((Math.pow(curx-tplines[curTarget][i][0][0], 2)/(b*b) + 1)*a*a);
						if (cury<-100) { cury = -100;}
						if (cury>targets[curTarget].imgheight+100) { cury=targets[curTarget].imgheight+100;}
						if (curx==0) {
							ctx.moveTo(curx,cury);
						} else {
							ctx.lineTo(curx,cury);
						}
					}
				} else if (tptypes[curTarget][i]==7.5) { //if a tp horiz hyperbola
					var a = Math.abs(x2-tplines[curTarget][i][0][0]);
					var b = Math.abs(y2-tplines[curTarget][i][0][1]);
					var m = Math.abs(b/a);
					ctx.strokeStyle = asymcolor;
					ctx.dashedLine(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1],targets[curTarget].imgwidth,tplines[curTarget][i][0][1]+m*(targets[curTarget].imgwidth-tplines[curTarget][i][0][0]));
					ctx.dashedLine(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1],targets[curTarget].imgwidth,tplines[curTarget][i][0][1]-m*(targets[curTarget].imgwidth-tplines[curTarget][i][0][0]));
					ctx.dashedLine(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1],0,tplines[curTarget][i][0][1]-m*tplines[curTarget][i][0][0]);
					ctx.dashedLine(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1],0,tplines[curTarget][i][0][1]+m*tplines[curTarget][i][0][0]);
					if (curTPcurve==i || (dragObj != null && dragObj.num==i)) {
						ctx.strokeStyle = "rgb(0,255,255)";
						ctx.lineWidth = 1;
						ctx.dashedLine(x2,y2,x2-2*(x2-tplines[curTarget][i][0][0]),y2,5);
						ctx.dashedLine(x2,y2,x2,y2-2*(y2-tplines[curTarget][i][0][1]),5);
						ctx.dashedLine(x2,y2-2*(y2-tplines[curTarget][i][0][1]),x2-2*(x2-tplines[curTarget][i][0][0]),y2-2*(y2-tplines[curTarget][i][0][1]),5);
						ctx.dashedLine(x2-2*(x2-tplines[curTarget][i][0][0]),y2,x2-2*(x2-tplines[curTarget][i][0][0]),y2-2*(y2-tplines[curTarget][i][0][1]),5);
						ctx.lineWidth = 2;
					}
					ctx.beginPath();
					ctx.strokeStyle = "rgb(0,0,255)";
					for (var cury=0;cury < targets[curTarget].imgwidth+4;cury += 3) {
						curx = tplines[curTarget][i][0][0] + Math.sqrt((Math.pow(cury-tplines[curTarget][i][0][1], 2)/(b*b) + 1)*a*a);
						if (curx<-100) { curx = -100;}
						if (curx>targets[curTarget].imgwidth+100) { curx=targets[curTarget].imgwidth+100;}
						if (cury==0) {
							ctx.moveTo(curx,cury);
						} else {
							ctx.lineTo(curx,cury);
						}
					}
					ctx.stroke();
					ctx.beginPath();
					for (var cury=0;cury < targets[curTarget].imgwidth+4;cury += 3) {
						curx = tplines[curTarget][i][0][0] - Math.sqrt((Math.pow(cury-tplines[curTarget][i][0][1], 2)/(b*b) + 1)*a*a);
						if (curx<-100) { curx = -100;}
						if (curx>targets[curTarget].imgwidth+100) { curx=targets[curTarget].imgwidth+100;}
						if (cury==0) {
							ctx.moveTo(curx,cury);
						} else {
							ctx.lineTo(curx,cury);
						}
					}
				}
			}
		} else if (tptypes[curTarget][i]==8) {//if a tp absolute value
			var slope = null;
			var x2 = null;
			var y2 = null;
			if (tplines[curTarget][i].length==2) {  //if two points set
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {  //one point set, use mouse pos for third
				x2 = x;
				y2 = y;
			}
			if (x2 != null) { //at least one point set
				if (x2!=tplines[curTarget][i][0][0]) {
					var slope = (y2 - tplines[curTarget][i][0][1])/(x2-tplines[curTarget][i][0][0]);
					if (x2<tplines[curTarget][i][0][0]) {  //want slope on right
						slope *= -1;
					}
				}
				if (Math.abs(x2-tplines[curTarget][i][0][0])<1 || Math.abs(slope)>100) { //vert line
					ctx.moveTo(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1]);
					if (y2>tplines[curTarget][i][0][1]) {
						ctx.lineTo(tplines[curTarget][i][0][0],targets[curTarget].imgheight);
					} else {
						ctx.lineTo(tplines[curTarget][i][0][0],0);
					}
				} else {
					var yleft = tplines[curTarget][i][0][1] + slope*tplines[curTarget][i][0][0];
					var yright = tplines[curTarget][i][0][1] + slope*(targets[curTarget].imgwidth-tplines[curTarget][i][0][0]);
					ctx.moveTo(0,yleft);
					ctx.lineTo(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1]);
					ctx.lineTo(targets[curTarget].imgwidth,yright);

				}

			}
		} else if (tptypes[curTarget][i]==8.3) {//if a tp exponential (unshifted)
			var y2 = null;
			var x2 = null;
			if (tplines[curTarget][i].length==2) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {
				x2 = x;
				y2 = y;
			}
			if (x2 != null && x2!=tplines[curTarget][i][0][0]) {
				if (y2==tplines[curTarget][i][0][1]) {
					ctx.moveTo(0,y2);
					ctx.lineTo(targets[curTarget].imgwidth,y2);
				} else {
					// (x1, y1) (x2, y2)
					// b^(x2-x1) = y2/y1
					// a = y1/b^x1

					var originy = targets[curTarget].ymax*targets[curTarget].pixpery + targets[curTarget].imgborder;
					var adjy1 = originy - tplines[curTarget][i][0][1];
					var adjy2 = originy - y2;
					if (adjy1*adjy2>0 && x2 != tplines[curTarget][i][0][0]) {
						var expbase = safepow(adjy2/adjy1, 1/(x2-tplines[curTarget][i][0][0]));
						var stretch = adjy2/safepow(expbase,x2);
						ctx.moveTo(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1]);
						var cury = 0;
						for (var curx=0;curx < targets[curTarget].imgwidth+4;curx += 3) {
							cury = originy - stretch*safepow(expbase,curx);
							if (cury<-100) { cury = -100;}
							if (cury>targets[curTarget].imgheight+100) { cury=targets[curTarget].imgheight+100;}
							if (curx==0) {
								ctx.moveTo(curx,cury);
							} else {
								ctx.lineTo(curx,cury);
							}
						}
					}
				}
			}
		} else if (tptypes[curTarget][i]==8.4) {//if a tp log (unshifted)
			var y2 = null;
			var x2 = null;
			if (tplines[curTarget][i].length==2) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {
				x2 = x;
				y2 = y;
			}
			if (x2 != null && x2!=tplines[curTarget][i][0][0] && y2!=tplines[curTarget][i][0][1]) {
				// Treat as x = ab^y
				// (x1, y1) (x2, y2)
				// b^(y2-y1) = x2/x1
				// a = x1/b^y1

				var originx = -targets[curTarget].xmin*targets[curTarget].pixperx + targets[curTarget].imgborder;
				var adjx1 = originx - tplines[curTarget][i][0][0];
				var adjx2 = originx - x2;
				if (adjx1*adjx2>0 && y2 != tplines[curTarget][i][0][1]) {
					var expbase = safepow(adjx2/adjx1, 1/(y2-tplines[curTarget][i][0][1]));
					var stretch = adjx2/safepow(expbase,y2);
					ctx.moveTo(tplines[curTarget][i][0][0],tplines[curTarget][i][0][1]);
					var cury = 0;
					for (var cury=0;cury < targets[curTarget].imgheight+4;cury += 3) {
						curx = originx - stretch*safepow(expbase,cury);
						if (curx<-100) { curx = -100;}
						if (curx>targets[curTarget].imgwidth+100) { curx=targets[curTarget].imgwidth+100;}
						if (cury==0) {
							ctx.moveTo(curx,cury);
						} else {
							ctx.lineTo(curx,cury);
						}
					}
				}

			}
		} else if (tptypes[curTarget][i]==8.5) {//if a 3p exponential (shifted)
			var y2 = null;
			var x2 = null;
			var y3 = null;
			var x3 = null;
			if (tplines[curTarget][i].length==3) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
				x3 = tplines[curTarget][i][2][0];
				y3 = tplines[curTarget][i][2][1];
			} else if (tplines[curTarget][i].length==2) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
				x3 = x;
				y3 = y;
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {
				x2 = x;
				y2 = y;
			}
			var y1 = tplines[curTarget][i][0][1];
			ctx.strokeStyle = asymcolor;
			ctx.dashedLine(5,y1,targets[curTarget].imgwidth,y1);
			ctx.beginPath();
			ctx.strokeStyle = "rgb(0,0,255)";
			if (x3 != null && x3!=x2) {
				if (y3==y2) {
					ctx.moveTo(0,y3);
					ctx.lineTo(targets[curTarget].imgwidth,y3);
				} else {
					// (x2, y2-y1) (x3, y3-y1)
					// b^(x3-x2) = (y3-y1)/(y2-y1)
					// a = (y3-y1)/b^x3

					var adjy2 = y1 - y2;
					var adjy3 = y1 - y3;
					if (adjy2*adjy3>0 && x3 != x2) {
						var expbase = safepow(adjy3/adjy2, 1/(x3-x2));
						var stretch = adjy3/safepow(expbase,x3);
						ctx.moveTo(x2,y2);
						var cury = 0;
						for (var curx=0;curx < targets[curTarget].imgwidth+4;curx += 3) {
							cury = y1 - stretch*safepow(expbase,curx);
							if (cury<-100) { cury = -100;}
							if (cury>targets[curTarget].imgheight+100) { cury=targets[curTarget].imgheight+100;}
							if (curx==0) {
								ctx.moveTo(curx,cury);
							} else {
								ctx.lineTo(curx,cury);
							}
						}
					}
				}
			}
		} else if (tptypes[curTarget][i]==8.6) {//if a 3p log (shifted)
			var y2 = null;
			var x2 = null;
			var y3 = null;
			var x3 = null;
			if (tplines[curTarget][i].length==3) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
				x3 = tplines[curTarget][i][2][0];
				y3 = tplines[curTarget][i][2][1];
			} else if (tplines[curTarget][i].length==2) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
				x3 = x;
				y3 = y;
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {
				x2 = x;
				y2 = y;
			}
			var x1 = tplines[curTarget][i][0][0];
			ctx.strokeStyle = asymcolor;
			ctx.dashedLine(x1,5,x1,targets[curTarget].imgheight);
			ctx.beginPath();
			ctx.strokeStyle = "rgb(0,0,255)";
			if (x3 != null && x3!=x2 && y3!=y2) {
				// treat as x = ab^y + c
				// (x2-x1, y2) (x3-x1, y3)
				// b^(y3-y2) = (x3-x1)/(x2-x1)
				// a = (x2-x1)/b^y2

				var adjx2 = x2 - x1;
				var adjx3 = x3 - x1;
				if (adjx2*adjx3>0) {
					var expbase = safepow(adjx3/adjx2, 1/(y3-y2));
					var stretch = adjx3/safepow(expbase,y3);
					ctx.moveTo(x2,y2);
					var curx = 0;
					for (var cury=0;cury < targets[curTarget].imgheight+4;cury += 3) {
						curx = x1 + stretch*safepow(expbase,cury);
						if (curx<-100) { curx = -100;}
						if (curx>targets[curTarget].imgwidth+100) { curx=targets[curTarget].imgwidth+100;}
						if (cury==0) {
							ctx.moveTo(curx,cury);
						} else {
							ctx.lineTo(curx,cury);
						}
					}
				}
			}
		} else if (tptypes[curTarget][i]==8.2) {//if a tp linear/linear rational
			var y2 = null;
			var x2 = null;
			if (tplines[curTarget][i].length==2) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {
				x2 = x;
				y2 = y;
			}
			ctx.strokeStyle = asymcolor;
			ctx.dashedLine(5,tplines[curTarget][i][0][1],targets[curTarget].imgwidth,tplines[curTarget][i][0][1]);
			ctx.dashedLine(tplines[curTarget][i][0][0],5,tplines[curTarget][i][0][0],targets[curTarget].imgheight);
			ctx.beginPath();
			ctx.strokeStyle = "rgb(0,0,255)";
			if (x2 != null && x2!=tplines[curTarget][i][0][0] && y2!=tplines[curTarget][i][0][1]) {

				//y = c/(x-p) + k
				var stretch = (y2 - tplines[curTarget][i][0][1])*(x2 - tplines[curTarget][i][0][0]);

				for (var curx=tplines[curTarget][i][0][0]-1;curx>-4;curx -= 3) {
					cury = stretch/(curx - tplines[curTarget][i][0][0]) + tplines[curTarget][i][0][1];
					if (cury<-100) { cury = -100;}
					if (cury>targets[curTarget].imgheight+100) { cury=targets[curTarget].imgheight+100;}
					if (curx==tplines[curTarget][i][0][0]-1) {
						ctx.moveTo(curx,cury);
					} else {
						ctx.lineTo(curx,cury);
					}
				}
				for (var curx=tplines[curTarget][i][0][0]+1;curx<targets[curTarget].imgwidth+4;curx += 3) {
					cury = stretch/(curx - tplines[curTarget][i][0][0]) + tplines[curTarget][i][0][1];
					if (cury<-100) { cury = -100;}
					if (cury>targets[curTarget].imgheight+100) { cury=targets[curTarget].imgheight+100;}
					if (curx==tplines[curTarget][i][0][0]+1) {
						ctx.moveTo(curx,cury);
					} else {
						ctx.lineTo(curx,cury);
					}
				}
				ctx.stroke();
			}
		} else if (tptypes[curTarget][i]==9  || tptypes[curTarget][i]==9.1 ) {//if a tp sin/cos
			var y2 = null;
			var x2 = null;
			if (tplines[curTarget][i].length==2) {
				x2 = tplines[curTarget][i][1][0];
				y2 = tplines[curTarget][i][1][1];
			} else if (curTPcurve==i && x!=null && tplines[curTarget][i].length==1) {
				x2 = x;
				y2 = y;
			}
			if (x2 != null && x2!=tplines[curTarget][i][0][0]) {
				if (y2==tplines[curTarget][i][0][1]) {
					ctx.moveTo(0,y2);
					ctx.lineTo(targets[curTarget].imgwidth,y2);
				} else {
					if (tptypes[curTarget][i]==9) {
						var amp = -1*Math.abs(y2-tplines[curTarget][i][0][1])/2;
						var mid = (y2+tplines[curTarget][i][0][1])/2;
						var stretch = Math.PI/Math.abs(x2-tplines[curTarget][i][0][0]);
						var horizs = (y2 < tplines[curTarget][i][0][1])?x2:tplines[curTarget][i][0][0];
					} else if (tptypes[curTarget][i]==9.1) {
						var amp = -1*Math.abs(y2-tplines[curTarget][i][0][1]);
						var mid = tplines[curTarget][i][0][1];
						var stretch = 0.5*Math.PI/Math.abs(x2-tplines[curTarget][i][0][0]);
						var horizs = (y2 < tplines[curTarget][i][0][1])?x2:(x2+2*Math.abs(x2-tplines[curTarget][i][0][0]));
					}

					var cury = 0;
					for (var curx=0;curx < targets[curTarget].imgwidth+4;curx += 3) {
						cury = amp*Math.cos(stretch*(curx - horizs)) + mid;
						if (curx==0) {
							ctx.moveTo(curx,cury);
						} else {
							ctx.lineTo(curx,cury);
						}
					}
				}
			}
		}
		ctx.stroke();
		ctx.beginPath();
	}
	if (curTPcurve === null && tpHasAsymp.hasOwnProperty(targets[curTarget].mode)) { //has asymptotes
		ctx.strokeStyle = "rgb(200,200,200)";

		if (targets[curTarget].mode == 8.5) { //shifted exp, no current curve
			ctx.dashedLine(5,y,targets[curTarget].imgwidth,y);
		} else if (targets[curTarget].mode == 8.6) { //shifted log, no current curve
			ctx.dashedLine(x,5,x,targets[curTarget].imgwidth);
		} else if (targets[curTarget].mode == 8.2) { //rational
			ctx.dashedLine(5,y,targets[curTarget].imgwidth,y);
			ctx.dashedLine(x,5,x,targets[curTarget].imgheight);
		} else if (targets[curTarget].mode == 7.4 || targets[curTarget].mode == 7.5) { //hyperbola
			ctx.dashedLine(x,y,targets[curTarget].imgwidth,y+(targets[curTarget].imgwidth-x));
			ctx.dashedLine(x,y,targets[curTarget].imgwidth,y-(targets[curTarget].imgwidth-x));
			ctx.dashedLine(x,y,0,y-x);
			ctx.dashedLine(x,y,0,y+x);
		}
		ctx.beginPath();
		ctx.strokeStyle = "rgb(0,0,255)";
	}

	var linefirstx, linefirsty, linelastx, linelasty;
	ctx.fillStyle = 'rgba(0,0,255,.5)';
	for (var i=0;i<lines[curTarget].length; i++) {
		ctx.beginPath();
		for (var j=0;j<lines[curTarget][i].length; j++) {
			if (j==0) {
				ctx.moveTo(lines[curTarget][i][j][0],lines[curTarget][i][j][1]);
				linefirstx = lines[curTarget][i][j][0];
				linefirsty = lines[curTarget][i][j][1];
			} else {
				ctx.lineTo(lines[curTarget][i][j][0],lines[curTarget][i][j][1]);
				linelastx = lines[curTarget][i][j][0];
				linelasty = lines[curTarget][i][j][1];
			}
		}
		if (i==curLine && x!=null) {
			ctx.lineTo(x,y);
			linelastx = x;
			linelasty = y;
		}
		var arrowsize = targets[curTarget].imgwidth*.02;
		if (drawlocky[curTarget]==1 && linelastx>targets[curTarget].imgwidth*.98) {
			ctx.moveTo(linelastx,linelasty);
			ctx.lineTo(linelastx-arrowsize,linelasty+arrowsize);
			ctx.moveTo(linelastx,linelasty);
			ctx.lineTo(linelastx-arrowsize,linelasty-arrowsize);
		} else if (drawlocky[curTarget]==1 && linefirstx>targets[curTarget].imgwidth*.98) {
			ctx.moveTo(linefirstx,linefirsty);
			ctx.lineTo(linefirstx-arrowsize,linefirsty+arrowsize);
			ctx.moveTo(linefirstx,linefirsty);
			ctx.lineTo(linefirstx-arrowsize,linefirsty-arrowsize);
		}
		if (drawlocky[curTarget]==1 && linelastx<targets[curTarget].imgwidth*.02) {
			ctx.moveTo(linelastx,linelasty);
			ctx.lineTo(linelastx+arrowsize,linelasty+arrowsize);
			ctx.moveTo(linelastx,linelasty);
			ctx.lineTo(linelastx+arrowsize,linelasty-arrowsize);
		} else if (drawlocky[curTarget]==1 && linefirstx<targets[curTarget].imgwidth*.02) {
			ctx.moveTo(linefirstx,linefirsty);
			ctx.lineTo(linefirstx+arrowsize,linefirsty+arrowsize);
			ctx.moveTo(linefirstx,linefirsty);
			ctx.lineTo(linefirstx+arrowsize,linefirsty-arrowsize);
		}
		ctx.stroke();
		if (targets[curTarget].dotline>1) {
			var ml = lines[curTarget][i].length-1;
			if (ml<1) {continue;}
			if (Math.pow((linelastx - linefirstx),2)+Math.pow((linelasty - linefirsty),2)<25) {
				//ctx.fillStyle('rgba(0,0,255,.5)');
				ctx.moveTo(lines[curTarget][i][0][0],lines[curTarget][i][0][1]);
				for (var j=1;j<lines[curTarget][i].length; j++) {
					ctx.lineTo(lines[curTarget][i][j][0],lines[curTarget][i][j][1]);
				}
				ctx.closePath();
				ctx.fill();
			}
		}
	}
	ctx.fillStyle = 'rgb(0,0,255)';
	ctx.beginPath();
	for (var i=0; i<dots[curTarget].length; i++) {
		ctx.moveTo(dots[curTarget][i][0]+5,dots[curTarget][i][1]);
		ctx.arc(dots[curTarget][i][0],dots[curTarget][i][1],5,0,Math.PI*2,true);
	}
	ctx.fill();
	if (targets[curTarget].dotline>0) {
		ctx.beginPath();
		for (var i=0;i<lines[curTarget].length; i++) {
			for (var j=0;j<lines[curTarget][i].length; j++) {
				if ((j==0) || Math.pow((lines[curTarget][i][j][0] - lines[curTarget][i][j-1][0]),2)+Math.pow((lines[curTarget][i][j][1] - lines[curTarget][i][j-1][1]),2)>25) {
					ctx.moveTo(lines[curTarget][i][j][0]+5,lines[curTarget][i][j][1]);
					ctx.arc(lines[curTarget][i][j][0],lines[curTarget][i][j][1],5,0,Math.PI*2,true);
				}
			}
		}
		ctx.fill();
	}

	ctx.fillStyle = "rgb(255,255,255)";
	ctx.beginPath();
	for (var i=0; i<odots[curTarget].length; i++) {
		ctx.moveTo(odots[curTarget][i][0]+5,odots[curTarget][i][1]);
		ctx.arc(odots[curTarget][i][0],odots[curTarget][i][1],4,0,Math.PI*2,true);
	}
	if (targets[curTarget].mode<3) {
		ctx.fill();
	} else {
		ctx.stroke();
	}
	ctx.lineWidth = 2;
	ctx.beginPath();
	for (var i=0; i<odots[curTarget].length; i++) {
		ctx.moveTo(odots[curTarget][i][0]+5,odots[curTarget][i][1]);
		ctx.arc(odots[curTarget][i][0],odots[curTarget][i][1],4,0,Math.PI*2,true);
	}
	ctx.stroke();

	for (var i=0;i<tplines[curTarget].length; i++) {
		//draw control points
		if (tptypes[curTarget][i]==targets[curTarget].mode) {
			ctx.fillStyle = "rgb(255,0,0)";
			for (var j=0; j<tplines[curTarget][i].length; j++) {
				ctx.fillRect(tplines[curTarget][i][j][0]-3,tplines[curTarget][i][j][1]-3,6,6);
			}
			if (tplines[curTarget][i].length<tpModeN[targets[curTarget].mode] && x!=null && curTPcurve==i) {
				ctx.fillRect(x-3,y-3,6,6);
			}
			ctx.fillStyle = "rgb(0,0,255)";
		}
	}
    if (skipencode !== true) {
	    encodeDraw();
    }
	//targetOuts[curTarget].value =  php_serialize(lines[curTarget]) + ';;'+php_serialize(dots[curTarget])+ ';;'+php_serialize(odots[curTarget]);

}

function deleteCurve(curveType,num) {
	if (curveType==1) { //if dot, delete dot
		dots[curTarget].splice(num,1);
	} else if (curveType==2) { //if open dot, delete dot
		odots[curTarget].splice(num,1);
	} else if (curveType>=0 && curveType<1) {
		lines[curTarget].splice(num,1);
	} else if (curveType>=5 && curveType<10) { //if twopoint, delete line
		tplines[curTarget].splice(num,1);
		tptypes[curTarget].splice(num,1);
		curTPcurve = null;
	} else if (curveType>=10 && curveType<11) { //if ineq, delete ineq
		ineqlines[curTarget].splice(num,1);
		ineqtypes[curTarget].splice(num,1);
		curIneqcurve = null;
	}
	drawTarget();
}
function encodeDraw() {
	var out = '';
	var outline = [];
	for (var i=0;i<lines[curTarget].length; i++) {
		var prevlen = lines[curTarget][i].length;
		if (targets[curTarget].dotline == 0) {
			outline = pathsimplify(lines[curTarget][i]);
		} else {
			outline = lines[curTarget][i];
		}
		if (i!=0) {
			out += ';';
		}
		for (var j=0;j<outline.length; j++) {
			if (j!=0) {
				out += ',';
			}
			out +=	'('+outline[j][0]+','+outline[j][1]+')';

		}
	}
	out += ';;';
	for (var i=0; i<dots[curTarget].length; i++) {
		if (i!=0) {
			out += ',';
		}
		out += '('+dots[curTarget][i][0]+','+dots[curTarget][i][1]+')';
	}
	out += ';;';
	for (var i=0; i<odots[curTarget].length; i++) {
		if (i!=0) {
			out += ',';
		}
		out += '('+odots[curTarget][i][0]+','+odots[curTarget][i][1]+')';
	}
	out += ';;';
	var tplineout = [];
	var tpoutstr = '';
	for (var i=0; i<tplines[curTarget].length; i++) {
		if (tplines[curTarget][i].length==tpModeN[tptypes[curTarget][i]]) {
			tpoutstr = '('+tptypes[curTarget][i];
			for (var j=0; j<tplines[curTarget][i].length; j++) {
				tpoutstr += ','+tplines[curTarget][i][j][0]+','+tplines[curTarget][i][j][1];
			}
			tplineout.push(tpoutstr + ')');
		}
	}
	out += tplineout.join(",");
	out += ';;';
	var tpineqout = [];
	for (var i=0; i<ineqlines[curTarget].length; i++) {
		//if (i!=0) {
		//	out += ',';
		//}
		if (ineqlines[curTarget][i].length>2) {
			tpineqout.push('('+ineqtypes[curTarget][i]+','+ineqlines[curTarget][i][0][0]+','+ineqlines[curTarget][i][0][1]+','+ineqlines[curTarget][i][1][0]+','+ineqlines[curTarget][i][1][1]+','+ineqlines[curTarget][i][2][0]+','+ineqlines[curTarget][i][2][1]+')');
		}
	}
	out += tpineqout.join(",");
	if (targetOuts[curTarget].value != out) {
		var dochange = (targetOuts[curTarget].value != '' || out != ';;;;;;;;');
		targetOuts[curTarget].value = out;
		if (dochange) {
			$(targetOuts[curTarget]).trigger("input").trigger("change");
		}
	}
}
var clickcnt=0;
function drawMouseDown(ev) {
	clickcnt++;
	clearAllDrawListners();
	if (hasTouch && ev.originalEvent.touches.length>1) {
		//hasTouch = false;
		didMultiTouch = true;
		//$(".tips").html("multi mousedown");
		//$(document).on("mousemove.imathasdraw", drawMouseMove);
		$(document).on("touchend.imathasdraw", drawMouseUp);
		return true;  //bypass when multitouching to prevent interference with pinch zoom
	} else {
		//$(".tips").html("other mousedown");
	}
	if (hasTouch) {
		window.clearTimeout(hasTouchTimer);
		$(document).off("mousemove.imathasdraw");
		$(".drawcanvas").on("touchstart.imathasdraw", function(ev) { hasTouch=true; drawMouseDown(ev);});
		$(".drawcanvas").on("touchmove.imathasdraw", drawMouseMove);
		$(document).on("touchend.imathasdraw", drawMouseUp);
		$(document).on("touchcancel.imathasdraw", drawMouseUp);
	} else {
		$(document).on("mousemove.imathasdraw", drawMouseMove);
		$(document).on("mouseup.imathasdraw", drawMouseUp);
	}
	var mousePos = mouseCoords(ev);
	//see if mouse click is inside a target; if so, select it (unless currently in a line from another target)
	if (curTarget==null || (curLine==null && curTPcurve==null && curIneqcurve==null)) {
		for (i in targets) {
			if ($(targets[i].el).is(':hidden') || targets[i].type != 'canvas') {continue;} // skip hidden
			var tarelpos = getPosition(targets[i].el);
			if (tarelpos.x<mousePos.x && (tarelpos.x+targets[i].width>mousePos.x) &&
				tarelpos.y<mousePos.y && (tarelpos.y+targets[i].height>mousePos.y) &&
				ev.target == targets[i].el
			) {
				curTarget = i;
				break;
			}
		}
	}

    if (curTarget!=null) { //is a target currectly in action?
        if (targetOuts[curTarget].disabled) { return; }
		ev.preventDefault();  //prevent scrolling
		mouseisdown = true;
		var tarelpos = getPosition(targets[curTarget].el);
		//$(".tips").html(curTPcurve+","+clickcnt);
		var mouseOff = {x:(mousePos.x - tarelpos.x), y: (mousePos.y-tarelpos.y)};

		//are we inside target region?
		if (mouseOff.x>-1 && mouseOff.x<targets[curTarget].width && mouseOff.y>-1 && mouseOff.y<targets[curTarget].height) {
			/*  not necessary
			if( navigator.userAgent.match(/Android/i) ) {
				ev.preventDefault(); //prevent pinch-zoom too
			}
			*/
			if (targets[curTarget].snaptogridx > 0) {mouseOff = snaptogrid(mouseOff,curTarget);}
			if (drawlocky[curTarget]==1) {
				mouseOff.y = targets[curTarget].imgheight/2;
			}

			//see if current point

			var foundpt = findnearpoint(curTarget,mouseOff);
			if (foundpt!=null) {
				if (targets[curTarget].mode==-1) { //if in erase
					deleteCurve(foundpt[0], foundpt[1]);
					return;
				}
				if (curLine!=null && foundpt[0]<1 && curLine!=foundpt[1]) {
					foundpt = null;
				} else if (curTPcurve!=null & foundpt[0]>=5 && foundpt[0]<10 && curTPcurve!=foundpt[1]) {
					foundpt = null;
				} else if (curIneqcurve!=null && foundpt[0]>=10 && foundpt[0]<11 && curIneqcurve!=foundpt[1]) {
					foundpt = null;
				}
			}

			if (foundpt==null) { //not a current point
				setCursor('pendown');
				//targets[curTarget].el.style.cursor = 'url('+imasroot+'/img/pendown.cur), auto';
				if (targets[curTarget].mode==1) {//if in dot mode
					dots[curTarget].push([mouseOff.x,mouseOff.y]);
					dragObj = {mode: 1, num: dots[curTarget].length-1};
					//targets[curTarget].el.style.cursor = 'move';
				} else if (targets[curTarget].mode==2) {//if in open dot mode
					odots[curTarget].push([mouseOff.x,mouseOff.y]);
					dragObj = {mode: 2, num: odots[curTarget].length-1};
					//targets[curTarget].el.style.cursor = 'move';
				} else if (targets[curTarget].mode==0 || targets[curTarget].mode==0.7) { //in line mode
					if (curLine==null) { //start new line
						lines[curTarget].push([[mouseOff.x,mouseOff.y]]);
						curLine = lines[curTarget].length-1;
					} else {//in existing line
						lines[curTarget][curLine].push([mouseOff.x,mouseOff.y]);
					}
				} else if (targets[curTarget].mode==0.5) { //in single line mode
					if (curLine==null) { //start new line
						lines[curTarget].push([[mouseOff.x,mouseOff.y]]);
						curLine = lines[curTarget].length-1;
					} else {//in existing line
						lines[curTarget][curLine].push([mouseOff.x,mouseOff.y]);
						curLine = null;
						dragObj = null;
					}
				} else if (targets[curTarget].mode>=5 && targets[curTarget].mode<10) {//in twopoint mode
					if (curTPcurve==null) { //start new tpline
						tplines[curTarget].push([[mouseOff.x,mouseOff.y]]);
						curTPcurve = tplines[curTarget].length-1;
						tptypes[curTarget][curTPcurve] = targets[curTarget].mode;
						mouseisdown = false;
					} else {//in existing line
						tplines[curTarget][curTPcurve].push([mouseOff.x,mouseOff.y]);
						if (tplines[curTarget][curTPcurve].length==tpModeN[targets[curTarget].mode]) {
							//second point is set.  switch to drag and end line
							dragObj = {mode: targets[curTarget].mode, num: curTPcurve, subnum: (tpModeN[targets[curTarget].mode]-1)};
							curTPcurve = null;
						} else {
                            mouseisdown = false; // treat like click and release
                        }
					}
				} else if (targets[curTarget].mode>=10 && targets[curTarget].mode<11) {//in ineqline mode
					if (curIneqcurve==null) { //start new tpline
						ineqlines[curTarget].push([[mouseOff.x,mouseOff.y]]);
						curIneqcurve = ineqlines[curTarget].length-1;
						ineqtypes[curTarget][curIneqcurve] = targets[curTarget].mode;
						mouseisdown = false;
					} else {//in existing line
						ineqlines[curTarget][curIneqcurve].push([mouseOff.x,mouseOff.y]);
						if (ineqlines[curTarget][curIneqcurve].length==2) {
							dragObj = {mode: targets[curTarget].mode, num: curIneqcurve, subnum: 1};
						} else if (ineqlines[curTarget][curIneqcurve].length==3) {
							//second point is set.  switch to drag and end line
							dragObj = {mode: targets[curTarget].mode, num: curIneqcurve, subnum: 2};
							curIneqcurve = null;
						}
					}
				}
			} else { //clicked on current point
				if (foundpt[0]==0 || foundpt[0]==0.5) { //if point is on line
					if (curLine==null) {//not current in line
						setCursor('move');
						//targets[curTarget].el.style.cursor = 'move';
						//start dragging
						dragObj = {mode: targets[curTarget].mode, num: foundpt[1], subnum: foundpt[2]};
						oldpointpos = lines[curTarget][foundpt[1]][foundpt[2]];
						if (foundpt[2] == lines[curTarget][foundpt[1]].length-1 && targets[curTarget].mode==0) {
							//if last point in line, continue line too
							curLine = foundpt[1];
						} else if (foundpt[2] == 0 && targets[curTarget].mode==0) {
							curLine = foundpt[1];
							lines[curTarget][curLine].reverse();
							dragObj.subnum = lines[curTarget][curLine].length-1;
						}
						clickmightbenewcurve = true;
					} else { //already in line
						if (foundpt[1]==curLine && foundpt[2] == lines[curTarget][foundpt[1]].length-1) {
							//clicked last point; end line
							if (lines[curTarget][curLine].length<2) {
								lines[curTarget].splice(curLine,1);
							}
							curLine = null;
						} else if (targets[curTarget].dotline>1 && foundpt[1]==curLine && foundpt[2]==0) {
							//clicked on first point, and are in closed poly mode.  Set point to same as first point and end line
							lines[curTarget][curLine].push(lines[curTarget][curLine][0]);
							curLine = null;
						} else {
							//if (foundpt[1]!=curLine) { //so long as point is not on current line, add it
								setCursor('pendown');
								//targets[curTarget].el.style.cursor = 'url('+imasroot+'/img/pendown.cur), auto';
								lines[curTarget][curLine].push([mouseOff.x,mouseOff.y]);
							//}
						}
					}
				} else if (foundpt[0]==1) { //if point is a dot
					setCursor('move');
					//targets[curTarget].el.style.cursor = 'move';
					dragObj = {mode: 1, num: foundpt[1]};
				} else if (foundpt[0]==2) { //if point is a open dot
					setCursor('move');
					//targets[curTarget].el.style.cursor = 'move';
					dragObj = {mode: 2, num: foundpt[1]};
				} else if (foundpt[0]>=5 && foundpt[0]<10) { //if point is on twopoint
					if (curTPcurve == null) {
						setCursor('move');
						//targets[curTarget].el.style.cursor = 'move';
						//start dragging
						dragObj = {mode: foundpt[0], num: foundpt[1], subnum: foundpt[2]};
						oldpointpos = tplines[curTarget][foundpt[1]][foundpt[2]];
						clickmightbenewcurve = true;
						//curTPcurve = foundpt[1];
					}
				} else if (foundpt[0]>=10 && foundpt[0]<11) { //if point is on ineqline
					if (curIneqcurve == null) {
						setCursor('move');
						//targets[curTarget].el.style.cursor = 'move';
						//start dragging
						dragObj = {mode: foundpt[0], num: foundpt[1], subnum: foundpt[2]};
						oldpointpos = ineqlines[curTarget][foundpt[1]][foundpt[2]];
						//curIneqcurve = foundpt[1];
						clickmightbenewcurve = true;
					}
				}

			}
			drawTarget();
		} else {  //clicked outside currect target region
			if (curLine!=null) {
				if (lines[curTarget][curLine].length<2) {
					lines[curTarget].splice(curLine,1);
				}
				setCursor('pen');
				//targets[curTarget].el.style.cursor = 'url('+imasroot+'/img/pen.cur), auto';
			}
			if (curTPcurve != null) {
				if (tplines[curTarget][curTPcurve].length<tpModeN[targets[curTarget].mode]) {
					tplines[curTarget].splice(curTPcurve,1);
					tptypes[curTarget].splice(curTPcurve,1);
				}
				setCursor('pen');
				//targets[curTarget].el.style.cursor = 'url('+imasroot+'/img/pen.cur), auto';
			}
			if (curIneqcurve != null) {
				if (ineqlines[curTarget][curIneqcurve].length<3) {
					ineqlines[curTarget].splice(curIneqcurve,1);
					ineqtypes[curTarget].splice(curIneqcurve,1);
				}
				setCursor('pen');
				//targets[curTarget].el.style.cursor = 'url('+imasroot+'/img/pen.cur), auto';
			}
			curLine = null;
			curTPcurve = null;
			curIneqcurve = null;
			dragObj = null;
			drawTarget();
			curTarget = null;

		}
		//ev.preventDefault();
	}

}

function findnearpoint(thetarget,mouseOff) {
	if (hasTouch) {
		var chkdist = Math.max(15*window.innerWidth/screen.width,15);
		chkdist *= chkdist;
	} else if (targets[thetarget].mode==-1) {
		var chkdist = 50;
	} else {
		var chkdist = 25;
	}

	if (targets[thetarget].mode==0 || targets[thetarget].mode==0.5 || targets[thetarget].mode==-1) { //if in line mode
		for (var i=lines[thetarget].length-1; i>=0; i--) { //check lines
			for (var j=lines[thetarget][i].length-1; j>=0; j--) {
				var dist = Math.pow(lines[thetarget][i][j][0]-mouseOff.x,2) + Math.pow(lines[thetarget][i][j][1]-mouseOff.y,2);
				if (dist<chkdist) {
					return [targets[thetarget].mode==-1?0:targets[thetarget].mode,i,j];
				}
			}
		}
	}
	if (targets[thetarget].mode==1 || targets[thetarget].mode==-1) {
		for (var i=dots[thetarget].length-1; i>=0; i--) { //check dots
			if (Math.pow(dots[thetarget][i][0]-mouseOff.x,2) + Math.pow(dots[thetarget][i][1]-mouseOff.y,2)<chkdist) {
				return [1,i];
			}
		}
	}
	if (targets[thetarget].mode==2 || targets[thetarget].mode==-1) {
		for (var i=odots[thetarget].length-1; i>=0; i--) { //check opendots
			if (Math.pow(odots[thetarget][i][0]-mouseOff.x,2) + Math.pow(odots[thetarget][i][1]-mouseOff.y,2)<chkdist) {
				return [2,i];
			}
		}
	}
	if ((targets[thetarget].mode>=5 && targets[thetarget].mode<10) || targets[thetarget].mode==-1) { //if in tpline mode
		for (var i=tplines[thetarget].length-1; i>=0; i--) { //check lines
			for (var j=tplines[thetarget][i].length-1; j>=0; j--) {
				if (tptypes[thetarget][i]!=targets[thetarget].mode && targets[thetarget].mode!=-1) {
					continue;
				}

				var dist = Math.pow(tplines[thetarget][i][j][0]-mouseOff.x,2) + Math.pow(tplines[thetarget][i][j][1]-mouseOff.y,2);
				if (dist<chkdist) {
					return [tptypes[thetarget][i],i,j];
				}
			}
		}
	}
	if ((targets[thetarget].mode>=10 && targets[thetarget].mode<11) || targets[thetarget].mode==-1) { //if in ineqline mode
		for (var i=ineqlines[thetarget].length-1; i>=0; i--) { //check inqs
			for (var j=ineqlines[thetarget][i].length-1; j>=0; j--) {
				if (ineqtypes[thetarget][i]!=targets[thetarget].mode && targets[thetarget].mode!=-1) {
					continue;
				}
				var dist = Math.pow(ineqlines[thetarget][i][j][0]-mouseOff.x,2) + Math.pow(ineqlines[thetarget][i][j][1]-mouseOff.y,2);
				if (dist<chkdist) {
					return [ineqtypes[thetarget][i],i,j];
				}
			}
		}
	}
	return null;
}

var lastdrawmouseup = null;
function drawMouseUp(ev) {
	//$(".tips").html("mouseup" + curTarget + dragObj);
	var mousePos = mouseCoords(ev);
	mouseisdown = false;
	if (curTarget!=null) {
        if (targetOuts[curTarget].disabled) { return; }
		var tarelpos = getPosition(targets[curTarget].el);
		var mouseOff = {x:(mousePos.x - tarelpos.x), y: (mousePos.y-tarelpos.y)};
		if (targets[curTarget].snaptogridx > 0) {mouseOff = snaptogrid(mouseOff,curTarget);}
		var releaseInTarget = (mouseOff.x>-1 && mouseOff.x<targets[curTarget].width && mouseOff.y>-1 && mouseOff.y<targets[curTarget].height);
		if (clickmightbenewcurve==true) {
			if (targets[curTarget].mode>=5 && targets[curTarget].mode<10) {
				tplines[curTarget].push([[mouseOff.x,mouseOff.y]]);
				curTPcurve = tplines[curTarget].length-1;
				tptypes[curTarget][curTPcurve] = targets[curTarget].mode;
			} else if (targets[curTarget].mode>=10 && targets[curTarget].mode<11) {//in ineqline mode
				ineqlines[curTarget].push([[mouseOff.x,mouseOff.y]]);
				curIneqcurve = ineqlines[curTarget].length-1;
				ineqtypes[curTarget][curIneqcurve] = targets[curTarget].mode;
			}
		}
		if (lastdrawmouseup!=null && mousePos.x==lastdrawmouseup.x && mousePos.y==lastdrawmouseup.y) {
			//basically a double-click which IE can handle
			if (curLine!=null && dragObj==null) {
				if (lines[curTarget][curLine].length<2) {
					lines[curTarget].splice(curLine,1);
				}
				curLine = null;
				dragObj = null;
				drawTarget();
			}
		}
		if (curLine != null && dragObj==null) {
			if (targets[curTarget].mode==0.5 && lines[curTarget][curLine].length>1) {
				curLine = null;
				dragobj = null;
				drawTarget();
			} else if (targets[curTarget].mode==0.7) {
				curLine = null;
				dragobj = null;
				drawTarget();
			}
		}
		if (curTPcurve!=null && tplines[curTarget][curTPcurve].length<tpModeN[targets[curTarget].mode]) {
			if (didMultiTouch || !releaseInTarget) {
				tplines[curTarget].splice(curTPcurve,1);
				tptypes[curTarget].splice(curTPcurve,1);
				curTPcurve = null;
				drawTarget();
			} else if (findnearpoint(curTarget,mouseOff)==null) {
                tplines[curTarget][curTPcurve].push([mouseOff.x,mouseOff.y]);
                if (tplines[curTarget][curTPcurve].length == tpModeN[targets[curTarget].mode]) {
                    curTPcurve = null;
                }
				drawTarget();
			}
		}
		if (curIneqcurve!=null && ineqlines[curTarget][curIneqcurve].length==1) {
			if (didMultiTouch || !releaseInTarget) {
				ineqlines[curTarget].splice(curIneqcurve,1);
				ineqtypes[curTarget].splice(curIneqcurve,1);
				curIneqcurve = null;
				drawTarget();
			} else if (findnearpoint(curTarget,mouseOff)==null) {
				ineqlines[curTarget][curIneqcurve].push([mouseOff.x,mouseOff.y]);
				drawTarget();
			}
		}
		if (curLine==null && curTPcurve == null) {
			setCursor('move');
			//targets[curTarget].el.style.cursor = 'move';
		} else {
			setCursor('pen');
			//targets[curTarget].el.style.cursor = 'url('+imasroot+'/img/pen.cur), auto';
		}
		if (typeof ev != 'undefined') {
			ev.preventDefault();
		}
	}
	lastdrawmouseup = mousePos;
	if (curTarget!=null && dragObj!=null) { //is a target currectly in action, and dragging
		var tarelpos = getPosition(targets[curTarget].el);

		//are we inside target region?
		if (mouseOff.x>-1 && mouseOff.x<targets[curTarget].width && mouseOff.y>-1 && mouseOff.y<targets[curTarget].height) {
			if (dragObj.mode==0 && targets[curTarget].dotline>1) {
				if (dragObj.subnum==0 || dragObj.subnum==lines[curTarget][dragObj.num].length-1) {
					//first or last dot being moved
					if (Math.pow((lines[curTarget][dragObj.num][0][0] - lines[curTarget][dragObj.num][lines[curTarget][dragObj.num].length-1][0]),2)+Math.pow((lines[curTarget][dragObj.num][0][1] - lines[curTarget][dragObj.num][lines[curTarget][dragObj.num].length-1][1]),2)<25) {
						if (dragObj.subnum==0) {
							lines[curTarget][dragObj.num][lines[curTarget][dragObj.num].length-1][0] = lines[curTarget][dragObj.num][0][0];
							lines[curTarget][dragObj.num][lines[curTarget][dragObj.num].length-1][1] = lines[curTarget][dragObj.num][0][1];
						} else {
							lines[curTarget][dragObj.num][0][0] = lines[curTarget][dragObj.num][lines[curTarget][dragObj.num].length-1][0];
							lines[curTarget][dragObj.num][0][1] = lines[curTarget][dragObj.num][lines[curTarget][dragObj.num].length-1][1];
						}
						curLine = null;
						drawTarget();
					}
				}
			}

			dragObj = null;

		} else {
			if (drawlocky[curTarget]==1) {
				mouseOff.y = targets[curTarget].imgheight/2;
			}
			if (dragObj.mode==1) { //if dot, delete dot
				dots[curTarget].splice(dragObj.num,1);
			} else if (dragObj.mode==2) { //if open dot, delete dot
				odots[curTarget].splice(dragObj.num,1);
			} else if (dragObj.mode==0.5) {
				lines[curTarget].splice(dragObj.num,1);
			} else if (dragObj.mode==0) { //if line, return pt to orig pos
				lines[curTarget][dragObj.num][dragObj.subnum] = oldpointpos;
			} else if (dragObj.mode>=5 && dragObj.mode<10) { //if twopoint, delete line
				tplines[curTarget].splice(dragObj.num,1);
				tptypes[curTarget].splice(dragObj.num,1);
				//tplines[curTarget][dragObj.num][dragObj.subnum] = oldpointpos;
				curTPcurve = null;
			} else if (dragObj.mode>=10 && dragObj.mode<11) { //if ineq, delete ineq
				ineqlines[curTarget].splice(dragObj.num,1);
				ineqtypes[curTarget].splice(dragObj.num,1);
				//ineqlines[curTarget][dragObj.num][dragObj.subnum] = oldpointpos;
				curIneqcurve = null;
			}
			dragObj = null;
			drawTarget();
		}
	}
	didMultiTouch = false;
	if (hasTouch) {
		hasTouchTimer = window.setTimeout(function () {
			hasTouch = false;
			clearAllDrawListners();
			$(document).on("mousemove.imathasdraw", drawMouseMove);
			$(".drawcanvas").on("touchstart.imathasdraw", function(ev) { hasTouch=true; drawMouseDown(ev);});
			$(document).on("mousedown.imathasdraw", drawMouseDown);
		}, 350);
	} else {
		clearAllDrawListners();
		$(document).on("mousemove.imathasdraw", drawMouseMove);
		$(".drawcanvas").on("touchstart.imathasdraw", function(ev) { hasTouch=true; drawMouseDown(ev);});
		$(document).on("mousedown.imathasdraw", drawMouseDown);
	}
}

var mouseintarget = -1;
function drawMouseMove(ev) {
	var tempTarget = null;
	clickmightbenewcurve = false;
	var mousePos = mouseCoords(ev);
	//$(".tips").html("move"+didMultiTouch);
	//document.getElementById("ans0-0").innerHTML = dragObj + ';' + curTPcurve;
	//if (curTarget==null) {
		for (i in targets) {
			if ($(targets[i].el).is(':hidden') || targets[i].type != 'canvas') {continue;} // skip hidden
			var tarelpos = getPosition(targets[i].el);
			if (tarelpos.x<mousePos.x && (tarelpos.x+targets[i].width>mousePos.x) &&
				tarelpos.y<mousePos.y && (tarelpos.y+targets[i].height>mousePos.y) &&
				ev.target == targets[i].el
			) {
				tempTarget = i;
				break;
			}
		}
	//}

	if (tempTarget!=null) {
        if (targetOuts[tempTarget].disabled) { return; }
		mouseintarget = tempTarget;
		var tarelpos = getPosition(targets[tempTarget].el);
		var mouseOff = {x:(mousePos.x - tarelpos.x), y: (mousePos.y-tarelpos.y)};
		if (targets[tempTarget].snaptogridx > 0) {mouseOff = snaptogrid(mouseOff,tempTarget);}
		if (drawlocky[tempTarget]==1) {
			mouseOff.y = targets[tempTarget].imgheight/2;
		}
		if (mouseOff.x>-1 && mouseOff.x<targets[tempTarget].width && mouseOff.y>-1 && mouseOff.y<targets[tempTarget].height) {
			if (dragObj==null) {
				if (curLine==null) {
					var foundpt = findnearpoint(tempTarget,mouseOff);
					if (foundpt==null) {
						setCursor('pen', tempTarget);
						//targets[tempTarget].el.style.cursor = 'url('+imasroot+'/img/pen.cur), auto';
					} else {
						if (targets[tempTarget].mode != -1) {
							setCursor('move', tempTarget);
						}
						//targets[tempTarget].el.style.cursor = 'move';
					}
				}
			}
		}
	} else {
		if (mouseintarget != null && mouseintarget != -1) {
			if (curTarget != null) {
				drawTarget();
			} else {
				curTarget = mouseintarget;
				drawTarget();
				curTarget = null;
			}
			mouseintarget = -1;
		}
	}
	/*
	$("#curtarget").text(curTarget);
	$("#temptarget").text(tempTarget);
	$("#mouseintarget").text(mouseintarget);
	*/

	if (curTarget!=null) {
		if (ev.originalEvent.touches && ev.originalEvent.touches.length>1) {
			didMultiTouch = true;
			//$(".tips").html("multi mousemove");
			return true;  //bypass when multitouching to prevent interference with pinch zoom
		} else if (typeof ev != 'undefined') {
			ev.preventDefault();
		}
		var tarelpos = getPosition(targets[curTarget].el);
		var mouseOff = {x:(mousePos.x - tarelpos.x), y: (mousePos.y-tarelpos.y)};
		if (targets[curTarget].snaptogridx > 0) {mouseOff = snaptogrid(mouseOff,curTarget);}
		if (drawlocky[curTarget]==1) {
			mouseOff.y = targets[curTarget].imgheight/2;
		}
		//are we inside target region?
		if (mouseOff.x>-1 && mouseOff.x<targets[curTarget].width && mouseOff.y>-1 && mouseOff.y<targets[curTarget].height) {
			if (dragObj==null) { //notdragging{
				if (mouseisdown && targets[curTarget].mode==-1) { //erasing
					var foundpt = findnearpoint(curTarget,mouseOff);
					if (foundpt!=null) {
						deleteCurve(foundpt[0], foundpt[1]);
					}
					return;
				}
				if (curLine!=null) {
					if (mouseisdown && (targets[curTarget].mode==0 || targets[curTarget].mode==0.7)) {
						var last = lines[curTarget][curLine].length-1;
						var dist = Math.pow(lines[curTarget][curLine][last][0]-mouseOff.x,2) + Math.pow(lines[curTarget][curLine][last][1]-mouseOff.y,2);
						//add point to line
						if (dist>25) {
							lines[curTarget][curLine].push([mouseOff.x,mouseOff.y]);
							drawTarget();
						} else {
							drawTarget(mouseOff.x,mouseOff.y);
						}
					} else if (mouseisdown && targets[curTarget].mode==0.5) {
						var last = lines[curTarget][curLine].length-1;
						if (last==0) {
							var dist = Math.pow(lines[curTarget][curLine][last][0]-mouseOff.x,2) + Math.pow(lines[curTarget][curLine][last][1]-mouseOff.y,2);
							if (dist>25) {
								lines[curTarget][curLine].push([mouseOff.x,mouseOff.y]);
								drawTarget();
							} else {
								drawTarget(mouseOff.x,mouseOff.y);
							}
						} else {
							lines[curTarget][curLine][last] = [mouseOff.x,mouseOff.y];
							drawTarget();
						}

					} else {
						//draw temp line
						drawTarget(mouseOff.x,mouseOff.y);
					}
				} else if (curTPcurve!=null) {
					if (mouseisdown) {
						drawTarget();
					} else {
						drawTarget(mouseOff.x,mouseOff.y);
					}

				} else if (curIneqcurve!=null) {
					if (mouseisdown) {
						drawTarget();
					} else {
						drawTarget(mouseOff.x,mouseOff.y);
					}

				} else { //see if we're near a point
					var foundpt = findnearpoint(curTarget,mouseOff);
					if (foundpt==null) {
						setCursor('pen');
						//targets[curTarget].el.style.cursor = 'url('+imasroot+'/img/pen.cur), auto';

						if (tpHasAsymp.hasOwnProperty(targets[curTarget].mode) && tempTarget !== null) {
							drawTarget(mouseOff.x,mouseOff.y);
						}
					} else {
						drawTarget();
						if (targets[curTarget].mode != -1) {
							setCursor('move');
						}
						//targets[curTarget].el.style.cursor = 'move';
					}

				}
			} else { //dragging
				setCursor('move');
				//targets[curTarget].el.style.cursor = 'move';
				if (dragObj.mode==0 || dragObj.mode==0.5) {
					lines[curTarget][dragObj.num][dragObj.subnum] = [mouseOff.x,mouseOff.y];
				} else if (dragObj.mode==1) {
					dots[curTarget][dragObj.num] = [mouseOff.x,mouseOff.y];
				} else if (dragObj.mode==2) {
					odots[curTarget][dragObj.num] = [mouseOff.x,mouseOff.y];
				} else if (dragObj.mode>=5 && dragObj.mode<10) {
					tplines[curTarget][dragObj.num][dragObj.subnum] = [mouseOff.x,mouseOff.y];
				} else if (dragObj.mode>=10 && dragObj.mode<11) {
					ineqlines[curTarget][dragObj.num][dragObj.subnum] = [mouseOff.x,mouseOff.y];
				}
				drawTarget();
			}
			return false;
		} else if (curIneqcurve!=null && !mouseisdown) {
			drawTarget();
		}
	} else if (tempTarget != null && mouseintarget != -1) {
		curTarget = tempTarget;
		if (tpHasAsymp.hasOwnProperty(targets[curTarget].mode)) {
				drawTarget(mouseOff.x,mouseOff.y);
		}
		curTarget = null;
	}

}
function setCursor(cursor, target) {
	target = target || curTarget;

	if (targets[target].cursor != cursor) {
		if (cursor=='move') {
			targets[target].el.style.cursor = cursor;
		} else {
			targets[target].el.style.cursor = 'url('+staticroot+'/img/'+cursor+'.cur), auto';
		}
		targets[target].cursor = cursor;
	}
}
function mouseCoords(ev){

	ev = ev || window.event;

	if (hasTouch) {
		var touch = ev.originalEvent.changedTouches[0] || ev.originalEvent.touches[0];
		return {x:touch.pageX, y:touch.pageY};
	}

	if(ev.pageX || ev.pageY){
		return {x:ev.pageX, y:ev.pageY};
	}
	var dd = document.documentElement, db = document.body;
	if (dd && (dd.scrollTop || dd.scrollLeft)) {
		var SL = dd.scrollLeft;
		var ST = dd.scrollTop;
	} else if (db) {
		var SL = db.scrollLeft;
		var ST = db.scrollTop;
	} else {
		var SL = 0;
		var ST = 0;
	}

	return {
		x:ev.clientX + SL,
		y:ev.clientY + ST
	};

}

function snaptogrid(mousepos, curt) {
	var posx = mousepos.x - targets[curt].imgborder + targets[curt].xmin*targets[curt].pixperx;
	var posy = mousepos.y - targets[curt].imgborder - targets[curt].ymax*targets[curt].pixpery;
	posx = Math.round(Math.round(posx/(targets[curt].pixperx*targets[curt].snaptogridx))*targets[curt].pixperx*targets[curt].snaptogridx + targets[curt].imgborder  - targets[curt].xmin*targets[curt].pixperx);
	posy = Math.round(Math.round(posy/(targets[curt].pixpery*targets[curt].snaptogridy))*targets[curt].pixpery*targets[curt].snaptogridy + targets[curt].imgborder  + targets[curt].ymax*targets[curt].pixpery);
	return {x: posx, y: posy};
}

function getMouseOffset(target, ev){

	var docPos    = getPosition(target);
	var mousePos  = mouseCoords(ev);
	return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
}

function getPosition(e){
	if (e.className.match(/hidden/)) {
		return {x:-10000, y:-10000};
	}
	var pos = $(e).offset();
	return {x: pos.left, y: pos.top};
}


function clearAllDrawListners() {
	$(document).off("mousedown.imathasdraw").off("mousemove.imathasdraw").off("mouseup.imathasdraw");
	$(document).off("touchstart.imathasdraw").off("touchmove.imathasdraw").off("touchend.imathasdraw").off("touchcancel.imathasdraw");
	$(".drawcanvas").off("touchstart.imathasdraw").off("touchmove.imathasdraw").off("touchend.imathasdraw").off("touchcancel.imathasdraw");
}
function initCanvases(k) {
	clearAllDrawListners();
	$(document).on("mousemove.imathasdraw", drawMouseMove);
	$(".drawcanvas").on("touchstart.imathasdraw", function(ev) { hasTouch=true; drawMouseDown(ev);});
	$(document).on("mousedown.imathasdraw", drawMouseDown);

	try {

		CanvasRenderingContext2D.prototype.dashedLine = function(x1, y1, x2, y2, dashLen) {
		    if (dashLen == undefined) dashLen = 10;

		    this.beginPath();
		    this.moveTo(x1, y1);

		    var dX = x2 - x1;
		    var dY = y2 - y1;
		    var dashes = Math.sqrt(dX * dX + dY * dY) / dashLen;
		    var dashX = dX / dashes;
		    var dashY = dY / dashes;
		    dashes = Math.floor(dashes);

		    var q = 0;
		    while (q++ < dashes && y1>-1 && y1<targets[curTarget].imgheight+1 && x1>-1 && x1<targets[curTarget].imgwidth+1) {
		     x1 += dashX;
		     y1 += dashY;
		     this[q % 2 == 0 ? 'moveTo' : 'lineTo'](x1, y1);
		    }
		    this[q % 2 == 0 ? 'moveTo' : 'lineTo'](x2, y2);

		    this.stroke();
		    this.closePath();
		};
	} catch(e) { }
	for (var i in canvases) {
		if (typeof(k)=='undefined' || k==i) {
			var thisdrawla = null;
			if (drawla[i]!=null && drawla[i].length>2) {
				lines[canvases[i][0]] = drawla[i][0];
				dots[canvases[i][0]] = drawla[i][1];
				odots[canvases[i][0]] = drawla[i][2];
				tptypes[canvases[i][0]] = [];
				tplines[canvases[i][0]] = [];
				if (drawla[i].length>3 && drawla[i][3].length>0) {
					for (var j=0; j<drawla[i][3].length;j++) {
						tptypes[canvases[i][0]][j] = drawla[i][3][j][0];
						tplines[canvases[i][0]][j] = [];
						for (var ptidx=0; ptidx<tpModeN[tptypes[canvases[i][0]][j]]; ptidx++) {
							tplines[canvases[i][0]][j].push(drawla[i][3][j].slice(1+2*ptidx,3+2*ptidx));
						}
					}
				}
				ineqtypes[canvases[i][0]] = [];
				ineqlines[canvases[i][0]] = [];
				if (drawla[i].length>4 && drawla[i][4].length>0) {
					for (var j=0; j<drawla[i][4].length;j++) {
						//CHECK
						ineqtypes[canvases[i][0]][j] = drawla[i][4][j][0];
						ineqlines[canvases[i][0]][j] = [drawla[i][4][j].slice(1,3),drawla[i][4][j].slice(3,5),drawla[i][4][j].slice(5)];
					}
				}
				if (drawla[i].length>5) {
					thisdrawla = drawla[i][5];
				}
			} else if (drawla[i] != null && JSON.stringify(drawla[i])=='[[]]') {
                clearcanvas(canvases[i][0], true);
            } 
            var bgpath = '';
            if (canvases[i][1].match(/initPicture/)) {
                bgpath = canvases[i][1];
            } else if (canvases[i][1] !== '') {
                bgpath = imasroot+'/filter/graph/imgs/'+canvases[i][1];
            }
			if (canvases[i][13] !== '') {
				addA11yTarget(canvases[i], thisdrawla, bgpath);
			} else {
				addTarget(canvases[i][0],'canvas'+canvases[i][0],bgpath,'qn'+canvases[i][0],canvases[i][2],canvases[i][3],canvases[i][4],canvases[i][5],canvases[i][6],canvases[i][7],canvases[i][8],canvases[i][9],canvases[i][10],canvases[i][11],canvases[i][12]);
			}
		}
	}
}

if (typeof(initstack)!='undefined') {
	initstack.push(initCanvases);
} else {
	$(function() {
		initCanvases();
	});
}


/*
normal distribution slider code

For each normal grapher on the page, include this html, and push the id (number on the end of each id) to normslider.idnums
<div class="normgrapher">
<p>Shade: <select id="shaderegions0" onchange="chgnormtype(this.id.substring(12));"><option value="1L">Left of a value</option><option value="1R">Right of a value</option>
<option value="2B">Between two values</option><option value="2O">2 regions</option></select>. Click and drag and arrows to adjust the values.


<div style="position: relative; width: 500px; height:220px;padding:0px;background:#fff;">
<div style="position: absolute; left:0; top:0; height:200px; width:0px; background:#00f; z-index:-1" id="normleft0">&nbsp;</div>
<div style="position: absolute; right:0; top:0; height:200px; width:0px; background:#00f; z-index:-1" id="normright0">&nbsp;</div>
<img style="position: absolute; left:0; top:0;" src="img/normalcurve.gif"/>
<img style="position: absolute; top:142px;left:0px;cursor:pointer;" id="slid10" src="img/uppointer.gif"/>
<img style="position: absolute; top:142px;left:0px;cursor:pointer;" id="slid20" src="img/uppointer.gif"/>
<div style="position: absolute; top:170px;left:0px;" id="slid1txt0">-2.5</div>
<div style="position: absolute; top:170px;left:0px;" id="slid2txt0">1.6</div>
</div>

reads/writes values from "qn"+id

*/
var normslider = {idnums:[], curslider:{el: null, startpos: [0,0], outnode: null}};
function addnormslider(k, rendernow) {
	if (arraysearch(k,normslider.idnums)==-1) { //not in there yet.  First load
		normslider.idnums.push(k);
		if (rendernow === true) {
			slideronpageload(k);
		}
	} else {
		//resubmit.  Must be on embedded reload.  call pageload
		slideronpageload(k);
	}
}
function slideronpageload(k) {
	var el,id;
	for(var i=0; i<normslider.idnums.length; i++) {
		if (typeof(k)=='undefined' || k==normslider.idnums[i]) {
			id = normslider.idnums[i];
			initnormslider(id);
			$("#slid1"+id).on("touchstart.normslider mousedown.normslider", onsliderstart);
			$("#slid2"+id).on("touchstart.normslider mousedown.normslider", onsliderstart);
		}
	}
}
if (typeof(initstack)!='undefined') {
	initstack.push(slideronpageload);
}
function initnormslider(id) {
	var type, p1, p2, v;
	var str = document.getElementById("qn"+id).value;
	if (v = str.match(/\(-oo,([\-\d\.]+)\)U\(([\-\d\.]+),oo\)/)) {
		type = 3;
		p1 = v[1];
		p2 = v[2];
	} else if (v = str.match(/\(-oo,([\-\d\.]+)\)/)) {
		type = 0;
		p1 = v[1];
		p2 = 2.5;
	} else if (v = str.match(/\(([\-\d\.]+),oo\)/)) {
		type = 1;
		p1 = v[1];
		p2 = 2.5;
	} else if (v = str.match(/\(([\-\d\.]+),([\-\d\.]+)\)/)) {
		type = 2;
		p1 = v[1];
		p2 = v[2];
	} else {
		type = 0;
		p1 = -1.5;
		p2 = 2.5;
	}
	document.getElementById("slid1"+id).style.left = (p1*60+250-6)+"px";
	document.getElementById("slid2"+id).style.left = (p2*60+250-6)+"px";
	document.getElementById("shaderegions"+id).selectedIndex = type;
	//alert(document.getElementById("shaderegions").selectedIndex +","+ type + ","+p1+","+p2);
	normslider.curslider.type = document.getElementById("shaderegions"+id).value;
	chgnormtype(id);

}
function onsliderstart(ev) {
	ev = ev || window.event;
	if (ev.originalEvent.touches && ev.originalEvent.touches.length>0) {
		hasTouch = true;
		$(document).on("touchmove.normslider", onsliderchange);
		$(document).on("touchend.normslider", onsliderstop);
	} else {
		$(document).on("mousemove.normslider", onsliderchange);
		$(document).on("mouseup.normslider", onsliderstop);
		//normslider.curslider.el.parentNode.onmousemove = onsliderchange;
	}
	normslider.curslider.el = ev.target || ev.srcElement;
	normslider.curslider.id = normslider.curslider.el.id.substring(5);
	normslider.curslider.type = document.getElementById("shaderegions"+normslider.curslider.id).value;
	normslider.curslider.outnode = document.getElementById(normslider.outputid);
	normslider.curslider.startpos = getMouseOffset(normslider.curslider.el,ev);
	normslider.curslider.el.parentNode.style.cursor = 'pointer';
	var parentpos = getPosition(normslider.curslider.el.parentNode);
	if (ev.preventDefault) {ev.preventDefault()};
	return false;
}
function onsliderchange(ev) {
	var id = normslider.curslider.id;
	ev = ev || window.event;
	var curpos = getMouseOffset(normslider.curslider.el.parentNode,ev);
	if (curpos.x<5 || curpos.x>normslider.curslider.el.parentNode.offsetWidth-5 || curpos.y<5 || curpos.y>normslider.curslider.el.parentNode.offsetHeight-5) {
		return;
	}
	var posx =  Math.round((curpos.x-normslider.curslider.startpos.x - 10)/6)*6+10;
	normslider.curslider.el.style.left = posx + "px";

	normupdatevalues(id);

	if (ev.preventDefault) {ev.preventDefault()};
	return false;
}
function onsliderstop(ev) {
	if (normslider.curslider.el !== null) {
		hasTouch = false;
		$(document).off("touchmove.normslider touchend.normslider");
		$(document).off("mousemove.normslider mouseup.normslider");
		normslider.curslider.el.parentNode.style.cursor = '';
		normslider.curslider.el = null;
	}
}
function normupdatevalues(id) {

	var p1 = parseInt(document.getElementById("slid1"+id).style.left)+6;
	var p2 = parseInt(document.getElementById("slid2"+id).style.left)+6;
	var minp = Math.min(p1,p2);
	var maxp = Math.max(p1,p2);
	if (normslider.curslider.type=='2O') {
		document.getElementById("normleft"+id).style.width = (minp+1) + "px";
	} else if (normslider.curslider.type=='1L') {
		document.getElementById("normleft"+id).style.width = (p1+1) + "px";
	} else if (normslider.curslider.type=='2B') {
		document.getElementById("normleft"+id).style.left = (minp) + "px";
		document.getElementById("normleft"+id).style.width = (maxp-minp+1) + "px";
	}
	if (normslider.curslider.type=='2O') {
		document.getElementById("normright"+id).style.width = (500-maxp) + "px";
	} else if (normslider.curslider.type=='1R') {
		document.getElementById("normright"+id).style.width = (500-p1) + "px";
	}
	var lbl1val = (-4+(p1 - 10)/60).toFixed(1);
	var lbl2val =(-4+(p2 - 10)/60).toFixed(1);
	var lbl1 = document.getElementById("slid1txt"+id);
	lbl1.innerHTML = lbl1val;
	lbl1.style.left = (p1-6) + "px";
	var lbl2 = document.getElementById("slid2txt"+id);
	lbl2.innerHTML = lbl2val;
	lbl2.style.left = (p2-6) + "px";
	var minlbl = Math.min(lbl1val,lbl2val);
	var maxlbl = Math.max(lbl1val,lbl2val);
	if (normslider.curslider.type=='2O') {
		var outstr = "(-oo,"+minlbl+")U("+maxlbl+",oo)";
	} else if (normslider.curslider.type=='2B') {
		var outstr = "("+minlbl+","+maxlbl+")";
	} else if (normslider.curslider.type=='1L') {
		var outstr = "(-oo,"+lbl1val+")";
	} else if (normslider.curslider.type=='1R') {
		var outstr = "("+lbl1val+",oo)";
	}
    document.getElementById("qn"+id).value = outstr;
    $("#qn"+id).trigger("input").trigger("change");
}

function chgnormtype(id) {
	var type = document.getElementById("shaderegions"+id).value;
	normslider.curslider.type = type;
	var p1 = parseInt(document.getElementById("slid1"+id).style.left)+6;
	var p2 = parseInt(document.getElementById("slid2"+id).style.left)+6;
	var minp = Math.min(p1,p2);
	var maxp = Math.max(p1,p2);
	if (type == "1R" || type == "1L") {
		document.getElementById("slid2"+id).style.display = "none";
		document.getElementById("slid2txt"+id).style.display = "none";
		if (type=="1R") {
			document.getElementById("normleft"+id).style.display = "none";
			document.getElementById("normright"+id).style.display = "";
			document.getElementById("normright"+id).style.right = 0+"px";
			document.getElementById("normright"+id).style.width = (500-p1)+"px";
		} else {
			document.getElementById("normright"+id).style.display = "none";
			document.getElementById("normleft"+id).style.display = "";
			document.getElementById("normleft"+id).style.left = 0+"px";
			document.getElementById("normleft"+id).style.width = p1+"px";
		}
	} else if (type=='2O') {
		document.getElementById("slid2"+id).style.display = "";
		document.getElementById("slid2txt"+id).style.display = "";
		document.getElementById("normleft"+id).style.display = "";
		document.getElementById("normright"+id).style.display = "";
		document.getElementById("normright"+id).style.right = 0+"px";
		document.getElementById("normleft"+id).style.left = 0+"px";
		document.getElementById("normright"+id).style.width = (500-maxp)+"px";
		document.getElementById("normleft"+id).style.width = minp+"px";
	} else if (type=='2B') {
		document.getElementById("slid2"+id).style.display = "";
		document.getElementById("slid2txt"+id).style.display = "";
		document.getElementById("normleft"+id).style.display = "";
		document.getElementById("normright"+id).style.display = "none";
		document.getElementById("normleft"+id).style.left = minp+"px";
		document.getElementById("normleft"+id).style.width = (maxp-minp)+"px";
	}
	normupdatevalues(id);
}

//a parabola given two points,
//context to draw on - ctx
//the vertex - (Vx,Vy)
//another point on the parabola - (x,y)
//screen width - sw
//screen height - sh
function dashedParabola(ctx,Vx,Vy,x,y,sw,sh){

	if (y==Vy) {//horizontal line across screen
		ctx.moveTo(0,y);
		ctx.dashedLine(0,y,sw,y);
	}

	else{
		//find out where the parabola touches the edge of the screen
        	var a = (y-Vy)/((x-Vx)*(x-Vx));
		var b = -2*a*Vx;
	    	var shift = 0;
		if(y > Vy){//hits image height, not 0
			shift = sh;
		}
        	var c = a*Vx*Vx+Vy - shift;

        	//calculate control points
		var discr = Math.sqrt(b*b-4*a*c);
		c += shift;
        	var p0x = (-b - discr)/(2*a);
		var p0y = a*p0x*p0x + b*p0x + c;

		var p2x = (-b + discr)/(2*a);
		var p2y = p0y;

        	if(p2x < p0x){
            		var temp = p0x;
            		p0x = p2x;
            		p2x = temp;
        	}

        	var stroke = 8;//length of dashes
        	var strokeSqr = stroke*stroke;
		var xChange = p2x - p0x;
		var px = Vx;
        	var py = Vy;
        	var counter = 0;
		while(px > p0x-5 && px>-5) {

			if(counter%2 == 0)
                		ctx.moveTo(px,py);
            		else
                		ctx.lineTo(px,py);

            		//px -= Math.sqrt(strokeSqr/(1+Math.pow(2*a*(px-Vx),2)));
            		px -= Math.min(Math.sqrt(strokeSqr/(1+Math.pow(2*a*(px-Vx),2))), Math.sqrt(stroke/Math.abs(a)));
            		py = a*px*px+b*px+c;
            		counter++;
		}

        	px = Vx;
        	py = Vy;
        	counter = 0;
        	while(px < p2x+5 && px < sw+5){
           		if(counter%2 == 0)
                		ctx.moveTo(px,py);
            		else
                		ctx.lineTo(px,py);

            		//px += Math.sqrt(strokeSqr/(1+Math.pow(2*a*(px-Vx),2)));
            		px += Math.min(Math.sqrt(strokeSqr/(1+Math.pow(2*a*(px-Vx),2))), Math.sqrt(stroke/Math.abs(a)));
            		py = a*px*px+b*px+c;
            		counter++;
        	}

	}
	ctx.stroke();
}

//a parabola given two points,
//context to draw on - ctx
//the vertex - (Vx,Vy)
//another point on the parabola - (x,y)
//point to determine which side to shade (shX,shY)
//screen width - sw
//screen height - sh
function shadeParabola(ctx,Vx,Vy,x,y,shX,shY,sw,sh){
	ctx.beginPath();
	if (y==Vy) {//horizontal line across screen
		ctx.moveTo(0,y);
		ctx.lineTo(sw,y);
		if(shY < Vy){//top half
			ctx.lineTo(sw,0);
			ctx.lineTo(0,0);
		}
		else{
			ctx.lineTo(sw,sh);
			ctx.lineTo(0,sh);
		}
	}
	else {
		//find out where the parabola touches the edge of the screen
        	var a = (y-Vy)/((x-Vx)*(x-Vx));
		var b = -2*a*Vx;
	    	var shift = 0;
		if(y > Vy){//hits image height, not 0
			shift = sh;
		}
        	var c = a*Vx*Vx+Vy - shift;

        	//calculate control points
		var discr = Math.sqrt(b*b-4*a*c);
		c += shift;
        	var p0x = (-b - discr)/(2*a);
		var p0y = a*p0x*p0x + b*p0x + c;

		var p2x = (-b + discr)/(2*a);
		var p2y = p0y;

        	if(p2x < p0x){
            		var temp = p0x;
            		p0x = p2x;
            		p2x = temp;
        	}

		var m0 = 2*a*(p0x-Vx);
		var b0 = p0y - m0*p0x;
		var m2 = -m0;
		var b2 = p2y - m2*p2x;

		var p1x = (b0-b2)/(m2-m0);
		var p1y = m0*p1x+b0;//either line, but use p1x

		if(a<0 && shY < a*shX*shX+b*shX+c || a>0 && shY > a*shX*shX+b*shX+c){//shade inside parabola
			ctx.moveTo(p0x,p0y);
			ctx.quadraticCurveTo(p1x,p1y,p2x,p2y);
		}
		else{
			var otherY = sh;
			if(Math.abs(p0y-sh) < 2)
				otherY = 0;

			ctx.moveTo(0,p0y);
			ctx.lineTo(p0x,p0y);
			ctx.quadraticCurveTo(p1x,p1y,p2x,p2y);
			ctx.lineTo(sw,p2y);
			ctx.lineTo(sw,otherY);
			ctx.lineTo(0,otherY);
		}
	}
	ctx.closePath();
}

var drawexport = {
	reset:reset,
	initCanvases:initCanvases,
	clearcanvas:clearcanvas,
	clearlastline:clearlastline,
	settool:settool,
	addnormslider:addnormslider,
	chgnormtype:chgnormtype,
	adda11ydraw:adda11ydraw,
	changea11ydraw:changea11ydraw,
	encodea11ydraw:encodea11ydraw,
	updatea11ydraw:updatea11ydraw,
	removea11ydraw:removea11ydraw
};
return drawexport;
}(jQuery));


/*
 The following code is (c) 2017, Vladimir Agafonkin
 Simplify.js, a high-performance JS polyline simplification library
 mourner.github.io/simplify-js
*/

(function () { 'use strict';

// to suit your point format, run search/replace for '[0]' and '[1]';
// for 3D version, see 3d branch (configurability would draw significant performance overhead)

// square distance between 2 points
function getSqDist(p1, p2) {

    var dx = p1[0] - p2[0],
        dy = p1[1] - p2[1];

    return dx * dx + dy * dy;
}

// square distance from a point to a segment
function getSqSegDist(p, p1, p2) {

    var x = p1[0],
        y = p1[1],
        dx = p2[0] - x,
        dy = p2[1] - y;

    if (dx !== 0 || dy !== 0) {

        var t = ((p[0] - x) * dx + (p[1] - y) * dy) / (dx * dx + dy * dy);

        if (t > 1) {
            x = p2[0];
            y = p2[1];

        } else if (t > 0) {
            x += dx * t;
            y += dy * t;
        }
    }

    dx = p[0] - x;
    dy = p[1] - y;

    return dx * dx + dy * dy;
}
// rest of the code doesn't care about point format

// basic distance-based simplification
function simplifyRadialDist(points, sqTolerance) {

    var prevPoint = points[0],
        newPoints = [prevPoint],
        point;

    for (var i = 1, len = points.length; i < len; i++) {
        point = points[i];

        if (getSqDist(point, prevPoint) > sqTolerance) {
            newPoints.push(point);
            prevPoint = point;
        }
    }

    if (prevPoint !== point) newPoints.push(point);

    return newPoints;
}

function simplifyDPStep(points, first, last, sqTolerance, simplified) {
    var maxSqDist = sqTolerance,
        index;

    for (var i = first + 1; i < last; i++) {
        var sqDist = getSqSegDist(points[i], points[first], points[last]);

        if (sqDist > maxSqDist) {
            index = i;
            maxSqDist = sqDist;
        }
    }

    if (maxSqDist > sqTolerance) {
        if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified);
        simplified.push(points[index]);
        if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified);
    }
}

// simplification using Ramer-Douglas-Peucker algorithm
function simplifyDouglasPeucker(points, sqTolerance) {
    var last = points.length - 1;

    var simplified = [points[0]];
    simplifyDPStep(points, 0, last, sqTolerance, simplified);
    simplified.push(points[last]);

    return simplified;
}

// both algorithms combined for awesome performance
function simplify(points, tolerance, highestQuality) {

    if (points.length <= 2) return points;

    var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;

    points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
    points = simplifyDouglasPeucker(points, sqTolerance);

    return points;
}

window.pathsimplify = simplify;

})();
