// js var plotPath; var plotPathNum; var plotPointNum; function drawGrid(left, top, horizSpacing, vertSpacing, vertLines, horizLines, lineThickness, colour, opt_ctx) { var ctx3; if (typeof opt_ctx !== 'undefined') {ctx3 = opt_ctx} else {ctx3 = ctx} if (!lineThickness) {lineThickness = 3} if (!colour) {colour = '#999'} ctx3.strokeStyle = colour; ctx3.lineWidth = lineThickness; ctx3.beginPath(); for (i = 0; i < horizLines + 1; i++) { ctx3.moveTo(left + i * horizSpacing, top); ctx3.lineTo(left + i * horizSpacing, top + vertLines * vertSpacing); } for (i = 0; i < vertLines + 1; i++) { ctx3.moveTo(left, top + i * vertSpacing); ctx3.lineTo(left + horizLines * horizSpacing, top + i * vertSpacing); } ctx3.closePath(); ctx3.stroke(); } function drawGrid3(context,contextLeft,contextTop,gridDetails,opt_fontSize,opt_minorColor,opt_majorColor,opt_xAxisColor,opt_yAxisColor,opt_borderColor,opt_originColor,opt_backColor,opt_showGrid,opt_showScales,opt_showLabels) { var mode = gridDetails.angleMode || 'deg'; /******************************** if mode == "rad" ... xMin, xMax, xMinorStep, xMajorStep are all multiples of pi, given as [num,denom], which will be simplified when text is drawn ********************************/ var hoursMode = boolean(gridDetails.hoursMode,false); /******************************** if hoursMode == true ... x-values will be converted to (eg. 12:00); ********************************/ var left = gridDetails.left - contextLeft; // use dimensions relative to background canvas (1200x700) var top = gridDetails.top - contextTop; // rather than the context supplied var width = gridDetails.width; var height = gridDetails.height; if (mode == 'rad') { if (typeof gridDetails.xMin == 'number') { var xMin = Math.PI * gridDetails.xMin; } else { var xMin = Math.PI * gridDetails.xMin[0] / gridDetails.xMin[1]; } if (typeof gridDetails.xMax == 'number') { var xMax = Math.PI * gridDetails.xMax; } else { var xMax = Math.PI * gridDetails.xMax[0] / gridDetails.xMax[1]; } if (typeof gridDetails.xMinorStep == 'number') { var xMinorStep = Math.PI * gridDetails.xMinorStep; } else { var xMinorStep = Math.PI * gridDetails.xMinorStep[0] / gridDetails.xMinorStep[1]; } if (typeof gridDetails.xMajorStep == 'number') { var xMajorStep = Math.PI * gridDetails.xMajorStep; } else { var xMajorStep = Math.PI * gridDetails.xMajorStep[0] / gridDetails.xMajorStep[1]; } } else { var xMin = gridDetails.xMin; var xMax = gridDetails.xMax; var xMinorStep = gridDetails.xMinorStep; var xMajorStep = gridDetails.xMajorStep; var xScaleStep = gridDetails.xScaleStep || xMajorStep; } if (xMin >= xMax) { console.log('Error: xMin >= xMax'); return; } var yMin = gridDetails.yMin; var yMax = gridDetails.yMax; var yMinorStep = gridDetails.yMinorStep; var yMajorStep = gridDetails.yMajorStep; var yScaleStep = gridDetails.yScaleStep || yMajorStep; if (yMin >= yMax) { console.log('Error: yMin >= yMax'); return; } var showGrid = boolean(gridDetails.showGrid,boolean(opt_showGrid,true)); var showScales = boolean(gridDetails.showScales,boolean(opt_showScales,true)); var showXScale = boolean(gridDetails.showXScale,showScales); var showYScale = boolean(gridDetails.showYScale,showScales); var showLabels = boolean(gridDetails.showLabels,boolean(opt_showLabels,true)); var showAxes = boolean(gridDetails.showAxes,true); var showBorder = boolean(gridDetails.showBorder,showAxes); var originStyle = gridDetails.originStyle || 'circle'; // 'circle', 'numbers' or 'none' var xScaleOffset = gridDetails.xScaleOffset || 4; var yScaleOffset = gridDetails.yScaleOffset || 0; var minorWidth = gridDetails.minorWidth || 1; var majorWidth = gridDetails.majorWidth || gridDetails.thickness || 2.4; var labelPos = [left,top,left+width,top+height]; var sf = gridDetails.sf || 1; // scale factor for text, origin, lineWidths // work out the spacing for minor and major steps var xMinorSpacing = (width * xMinorStep) / (xMax - xMin); var xMajorSpacing = (width * xMajorStep) / (xMax - xMin); var yMinorSpacing = (height * yMinorStep) / (yMax - yMin); var yMajorSpacing = (height * yMajorStep) / (yMax - yMin); var gridFontSize = gridDetails.fontSize || opt_fontSize || 24*sf; var backColor = gridDetails.backColor || opt_backColor || mainCanvasFillStyle || '#FFC'; var invertedBackColor = getShades(backColor,true); if (['#FFF','#fff','#FFFFFF','#ffffff'].indexOf(backColor) == -1) { var minorColor = gridDetails.minorColor || opt_minorColor || /*invertedBackColor[10] || */'#999'; } else { var minorColor = gridDetails.minorColor || opt_minorColor || invertedBackColor[9] || '#999'; // slightly darker if white background - for printing } var majorColor = gridDetails.majorColor || gridDetails.color || opt_majorColor || /*invertedBackColor[9] || */'#999'; var originColor = gridDetails.axesColor || opt_originColor || /*invertedBackColor[4] || */'#666'; var xAxisColor = gridDetails.axesColor || opt_xAxisColor || /*invertedBackColor[2] || */'#000'; var yAxisColor = gridDetails.axesColor || opt_yAxisColor || /*invertedBackColor[2] || */'#000'; var borderColor = gridDetails.axesColor || opt_borderColor || /*invertedBackColor[2] || */'#000'; var xScaleColor = gridDetails.xScaleColor || gridDetails.scaleColor || xAxisColor; var yScaleColor = gridDetails.yScaleColor || gridDetails.scaleColor || yAxisColor; var dots = boolean(gridDetails.dots,false); var dotsColor = def([gridDetails.dotsColor,majorColor]); var dotsRadius = def([gridDetails.dotsRadius,3])*sf; var labelFontSize = gridFontSize * 1.375; var xAxisLabel = gridDetails.xAxisLabel || ['x']; var yAxisLabel = gridDetails.yAxisLabel || ['y']; if (typeof xAxisLabel == 'string') xAxisLabel = [xAxisLabel]; if (typeof yAxisLabel == 'string') yAxisLabel = [yAxisLabel]; // work out the coordinates of the origin var x0 = left - (xMin * width) / (xMax - xMin); var y0 = top + (yMax * height) / (yMax - yMin); // work out the actual display position of the origin (ie. at the edge if it is off the grid) var x0DisplayPos; var y0DisplayPos; if (x0 >= left && x0 <= left + width) { x0DisplayPos = x0; } else { if (x0 < left) {x0DisplayPos = left}; if (x0 > left + width) {x0DisplayPos = left + width}; } if (y0 >= top && y0 <= top + height) { y0DisplayPos = y0; } else { if (y0 < top) {y0DisplayPos = top}; if (y0 > top + height) {y0DisplayPos = top + height}; } if (showGrid == true) { // draw minor grid lines context.strokeStyle = minorColor; context.lineWidth = minorWidth*sf; context.beginPath(); // draws positive xMinor lines var xAxisPoint = x0 + xMinorSpacing; while (xAxisPoint < left + width) { if (xAxisPoint > left) { context.moveTo(xAxisPoint, top); context.lineTo(xAxisPoint, top + height); } xAxisPoint += xMinorSpacing; } // draws negative xMinor lines var xAxisPoint = x0 - xMinorSpacing; while (xAxisPoint > left) { if (xAxisPoint < left + width) { context.moveTo(xAxisPoint, top); context.lineTo(xAxisPoint, top + height); } xAxisPoint -= xMinorSpacing; } // draws positive yMinor lines var yAxisPoint = y0 - yMinorSpacing; while (yAxisPoint > top) { if (yAxisPoint < top + height) { context.moveTo(left, yAxisPoint); context.lineTo(left + width, yAxisPoint); } yAxisPoint -= yMinorSpacing; } // draws negative yMinor lines var yAxisPoint = y0 + yMinorSpacing; while (yAxisPoint < top + height) { if (yAxisPoint > top) { context.moveTo(left, yAxisPoint); context.lineTo(left + width, yAxisPoint); } yAxisPoint += yMinorSpacing; } context.closePath(); context.stroke(); // draw major lines context.strokeStyle = majorColor; context.lineWidth = majorWidth*sf; context.beginPath(); // draws positive xMajor lines if (showAxes == true) { var xAxisPoint = x0 + xMajorSpacing; } else { var xAxisPoint = x0; } while (xAxisPoint <= left + width) { if (xAxisPoint > left) { context.moveTo(xAxisPoint, top); context.lineTo(xAxisPoint, top + height); } xAxisPoint += xMajorSpacing; } // draws negative xMajor lines var xAxisPoint = x0 - xMajorSpacing; while (xAxisPoint >= left) { if (xAxisPoint < left + width) { context.moveTo(xAxisPoint, top); context.lineTo(xAxisPoint, top + height); } xAxisPoint -= xMajorSpacing; } // draws positive yMajor lines if (showAxes == true) { var yAxisPoint = y0 - yMajorSpacing; } else { var yAxisPoint = y0; } while (yAxisPoint >= top) { if (yAxisPoint < top + height) { context.moveTo(left, yAxisPoint); context.lineTo(left + width, yAxisPoint); } yAxisPoint -= yMajorSpacing; } // draws negative yMajor lines var yAxisPoint = y0 + yMajorSpacing; while (yAxisPoint <= top + height) { if (yAxisPoint > top) { context.moveTo(left, yAxisPoint); context.lineTo(left + width, yAxisPoint); } yAxisPoint += yMajorSpacing; } context.closePath(); context.stroke(); } if (dots == true) { context.fillStyle = dotsColor; var xMin2 = Math.floor(Math.abs(xMin)/xMajorStep)*xMajorStep*(xMin/Math.abs(xMin)); var xMax2 = Math.floor(Math.abs(xMax)/xMajorStep)*xMajorStep*(xMax/Math.abs(xMax)); var yMin2 = Math.floor(Math.abs(yMin)/yMajorStep)*yMajorStep*(yMin/Math.abs(yMin)); var yMax2 = Math.floor(Math.abs(yMax)/yMajorStep)*yMajorStep*(yMax/Math.abs(yMax)); for (var x = xMin2; x <= xMax2; x += xMajorStep) { var xPos = getPosOfCoordX2(x,left,width,xMin,xMax); for (var y = yMin2; y <= yMax2; y += yMajorStep) { var yPos = getPosOfCoordY2(y,top,height,yMin,yMax); context.beginPath(); context.arc(xPos,yPos,dotsRadius,0,2*Math.PI); context.fill(); } } } if (showLabels == true) { if (!un(gridDetails.axisLabels)) { if (!un(draw) && !un(draw.hiddenCanvas)) { var ctx2 = draw.hiddenCanvas.ctx; } else { if (!un(window.hiddenCanvas)) window.hiddenCanvas = newctx({vis:false}); var ctx2 = hiddenCanvas.ctx; } var xDist = gridDetails.axisLabels[0].dist || 30; var measureTextX = text({ctx:ctx2,text:['<>'].concat(gridDetails.axisLabels[0].text),rect:[0,0,gridDetails.width,100],measureOnly:true}).tightRect; var xLabelRect = [gridDetails.left+gridDetails.width-measureTextX[2],gridDetails.top+gridDetails.height+xDist,measureTextX[2],measureTextX[3]]; text({ctx:context,text:['<>'].concat(gridDetails.axisLabels[0].text),rect:xLabelRect,align:[0,0]}); var yDist = gridDetails.axisLabels[1].dist || 50; var measureTextY = text({ctx:ctx2,text:['<>'].concat(gridDetails.axisLabels[1].text),rect:[0,0,gridDetails.height,100],measureOnly:true}).tightRect; var yLabelRect = [gridDetails.left-yDist-measureTextY[3],gridDetails.top,measureTextY[3],measureTextY[2]]; context.save(); context.translate(yLabelRect[0],yLabelRect[1]+measureTextY[2]); context.rotate(-Math.PI/2); text({ctx:context,text:['<>'].concat(gridDetails.axisLabels[1].text),rect:[0,0,measureTextY[2],measureTextY[3]],align:[0,0]}); context.restore(); } else { yAxisLabel2 = yAxisLabel.slice(0); yAxisLabel2.unshift('<><><><>'); /*var yLabel = text({ context:context, left:x0DisplayPos+5-300, top:top-labelFontSize-15, width:600, textArray:yAxisLabel });*/ var yLabel = drawMathsText(context, yAxisLabel2, labelFontSize, x0DisplayPos+5, top-labelFontSize+5, true, [], 'center', 'middle', yAxisColor); if (arraysEqual(yAxisLabel,['y'])) { yLabel.tightRect[3] = (3/4) * yLabel.tightRect[3]; yLabel.tightRect[1] += (1/3) * yLabel.tightRect[3]; } /*context.save(); context.lineWidth = 1; context.strokeStyle = '#393'; context.strokeRect(yLabel.tightRect[0],yLabel.tightRect[1],yLabel.tightRect[2],yLabel.tightRect[3]); context.restore();*/ labelPos[0] = Math.min(labelPos[0],yLabel.tightRect[0]); labelPos[1] = Math.min(labelPos[1],yLabel.tightRect[1]); labelPos[2] = Math.max(labelPos[2],yLabel.tightRect[0]+yLabel.tightRect[2]); labelPos[3] = Math.max(labelPos[3],yLabel.tightRect[1]+yLabel.tightRect[3]); xAxisLabel2 = xAxisLabel.slice(); xAxisLabel2.unshift('<><><><>'); /*xLabel = text({ context:context, left:left+width+10, top:y0DisplayPos-labelFontSize, width:300, textArray:xAxisLabel });*/ var xLabel = drawMathsText(context, xAxisLabel2, labelFontSize, left+width+10, y0DisplayPos-labelFontSize, true, [], 'left', 'top', xAxisColor); if (arraysEqual(xAxisLabel,['x'])) { xLabel.tightRect[3] = (3/4) * xLabel.tightRect[3]; xLabel.tightRect[1] += (1/3) * xLabel.tightRect[3]; } /*context.save(); context.lineWidth = 1; context.strokeStyle = '#393'; context.strokeRect(xLabel.tightRect[0],xLabel.tightRect[1],xLabel.tightRect[2],xLabel.tightRect[3]); context.restore();*/ labelPos[0] = Math.min(labelPos[0],xLabel.tightRect[0]); labelPos[1] = Math.min(labelPos[1],xLabel.tightRect[1]); labelPos[2] = Math.max(labelPos[2],xLabel.tightRect[0]+yLabel.tightRect[2]); labelPos[3] = Math.max(labelPos[3],xLabel.tightRect[1]+yLabel.tightRect[3]); } } if (showBorder == true) { // draw a black rectangular border context.strokeStyle = borderColor; context.lineWidth = 4*sf; context.strokeRect(left, top, width, height); } if (showScales == true) { if (originStyle == 'circle' && x0 >= left && x0 <= left + width && y0 >= top && y0 <= top + height) { context.textAlign = 'center'; context.textBaseline = "middle"; context.strokeStyle = originColor; context.lineWidth = 2*sf; context.beginPath(); context.arc(x0,y0,10*(gridFontSize/(24*sf))*sf,0,2*Math.PI); context.closePath(); context.stroke(); labelPos[0] = Math.min(labelPos[0],x0-10); labelPos[1] = Math.min(labelPos[1],y0-10); labelPos[2] = Math.max(labelPos[2],x0+10); labelPos[3] = Math.max(labelPos[3],y0+10); } if (showXScale == true) { // draw axes numbers context.font = gridFontSize+'px Arial'; context.textAlign = "center"; context.textBaseline = "top"; if (mode == "rad") { // draw positive x axis numbers as multiple of pi var xAxisPoint = x0 + xMajorSpacing; var major = 1; while (roundToNearest(xAxisPoint,0.001) <= roundToNearest(left+width,0.001)) { if (xAxisPoint >= left) { if (typeof gridDetails.xMajorStep == 'number') { var frac = {num:gridDetails.xMajorStep*major,denom:1}; } else { var frac = {num:gridDetails.xMajorStep[0]*major,denom:gridDetails.xMajorStep[1]}; } var axisValue = multOfPiText(frac); var params = {ctx:context,textArray:axisValue,font:"algebra",fontSize:gridFontSize,left:xAxisPoint-50,width:100,top:y0DisplayPos+xScaleOffset-4,height:50,minTightWidth:1,minTightHeight:1,align:'center',vertAlign:'top',color:xScaleColor,box:{type:'tight',borderColor:backColor,borderWidth:0.01,color:backColor,padding:1}}; var labelText = text(params); /*context.save(); context.lineWidth = 1; context.strokeStyle = '#F00'; context.strokeRect(labelText.tightRect[0],labelText.tightRect[1],labelText.tightRect[2],labelText.tightRect[3]); context.restore();*/ labelPos[0] = Math.min(labelPos[0],labelText.tightRect[0]); labelPos[1] = Math.min(labelPos[1],labelText.tightRect[1]); labelPos[2] = Math.max(labelPos[2],labelText.tightRect[0]+labelText.tightRect[2]); labelPos[3] = Math.max(labelPos[3],labelText.tightRect[1]+labelText.tightRect[3]); } major += 1; xAxisPoint += xMajorSpacing; } // draw negative x axis numbers as multiple of pi xAxisPoint = x0 - xMajorSpacing; major = -1; while (roundToNearest(xAxisPoint,0.001) >= roundToNearest(left,0.001)) { if (xAxisPoint < left + width) { if (typeof gridDetails.xMajorStep == 'number') { var frac = {num:gridDetails.xMajorStep*major,denom:1}; } else { var frac = {num:gridDetails.xMajorStep[0]*major,denom:gridDetails.xMajorStep[1]}; } var axisValue = multOfPiText(frac); var params = {ctx:context,textArray:axisValue,font:"algebra",fontSize:gridFontSize,left:xAxisPoint-50,width:100,top:y0DisplayPos+xScaleOffset-4,height:50,minTightWidth:1,minTightHeight:1,align:'center',vertAlign:'top',color:xScaleColor,box:{type:'tight',borderColor:backColor,borderWidth:0.01,color:backColor,padding:1}}; var labelText = text(params); /*context.save(); context.lineWidth = 1; context.strokeStyle = '#F00'; context.strokeRect(labelText.tightRect[0],labelText.tightRect[1],labelText.tightRect[2],labelText.tightRect[3]); context.restore();*/ labelPos[0] = Math.min(labelPos[0],labelText.tightRect[0]); labelPos[1] = Math.min(labelPos[1],labelText.tightRect[1]); labelPos[2] = Math.max(labelPos[2],labelText.tightRect[0]+labelText.tightRect[2]); labelPos[3] = Math.max(labelPos[3],labelText.tightRect[1]+labelText.tightRect[3]); } major -= 1; xAxisPoint -= xMajorSpacing; } } else if (hoursMode == true) { // positive x numbers var xAxisPoint = x0 + xMajorSpacing; var major = 1; //var placeValue = Math.pow(10,Math.floor(Math.log(xMajorStep)/Math.log(10))); while (roundToNearest(xAxisPoint,0.001) <= roundToNearest(left+width,0.001)) { if (xAxisPoint >= left) { var value = convertToHoursMins(major*xMajorStep); var axisValue = [String(value)]; var textWidth = context.measureText(String(axisValue)).width; context.fillStyle = backColor; context.fillRect(xAxisPoint - textWidth / 2, y0DisplayPos + xScaleOffset-1, textWidth, gridFontSize * 1.1); var labelText = drawMathsText(context, axisValue, gridFontSize, xAxisPoint, y0DisplayPos + xScaleOffset + 0.5 * gridFontSize, true, [], 'center', 'middle', xScaleColor); /*context.save(); context.lineWidth = 1; context.strokeStyle = '#393'; context.strokeRect(labelText.tightRect[0],labelText.tightRect[1],labelText.tightRect[2],labelText.tightRect[3]); context.restore();*/ labelPos[0] = Math.min(labelPos[0],labelText.tightRect[0]); labelPos[1] = Math.min(labelPos[1],labelText.tightRect[1]); labelPos[2] = Math.max(labelPos[2],labelText.tightRect[0]+labelText.tightRect[2]); labelPos[3] = Math.max(labelPos[3],labelText.tightRect[1]+labelText.tightRect[3]); } major += 1; xAxisPoint += xMajorSpacing; } } else { var xScaleSpacing = (width * xScaleStep) / (xMax - xMin); // positive x numbers var xAxisPoint = x0 + xScaleSpacing; var major = 1; var placeValue = Math.pow(10,Math.floor(Math.log(xScaleStep)/Math.log(10))); while (roundToNearest(xAxisPoint,0.001) <= roundToNearest(left+width,0.001)) { if (xAxisPoint >= left) { var value = roundToNearest(major*xScaleStep,placeValue); var axisValue = [String(value)]; var textWidth = context.measureText(String(axisValue)).width; context.fillStyle = backColor; context.fillRect(xAxisPoint - textWidth / 2, y0DisplayPos + xScaleOffset-1, textWidth, gridFontSize * 1.1); var labelText = drawMathsText(context, axisValue, gridFontSize, xAxisPoint, y0DisplayPos + xScaleOffset + 0.5 * gridFontSize, true, [], 'center', 'middle', xScaleColor); /*context.save(); context.lineWidth = 1; context.strokeStyle = '#393'; context.strokeRect(labelText.tightRect[0],labelText.tightRect[1],labelText.tightRect[2],labelText.tightRect[3]); context.restore();*/ labelPos[0] = Math.min(labelPos[0],labelText.tightRect[0]); labelPos[1] = Math.min(labelPos[1],labelText.tightRect[1]); labelPos[2] = Math.max(labelPos[2],labelText.tightRect[0]+labelText.tightRect[2]); labelPos[3] = Math.max(labelPos[3],labelText.tightRect[1]+labelText.tightRect[3]); } major += 1; xAxisPoint += xScaleSpacing; } // negative x numbers var xAxisPoint = x0 - xScaleSpacing; var major = -1; while (roundToNearest(xAxisPoint,0.001) >= roundToNearest(left,0.001)) { if (xAxisPoint < left + width) { var value = roundToNearest(major*xScaleStep,placeValue); var axisValue = [String(value)]; var textWidth = context.measureText(String(axisValue)).width; context.fillStyle = backColor; context.fillRect(xAxisPoint - textWidth / 2, y0DisplayPos + xScaleOffset-1, textWidth, gridFontSize * 1.1); var labelText = drawMathsText(context, axisValue, gridFontSize, xAxisPoint, y0DisplayPos + xScaleOffset + 0.5 * gridFontSize, true, [], 'center', 'middle', xScaleColor); /*context.save(); context.lineWidth = 1; context.strokeStyle = '#393'; context.strokeRect(labelText.tightRect[0],labelText.tightRect[1],labelText.tightRect[2],labelText.tightRect[3]); context.restore();*/ labelPos[0] = Math.min(labelPos[0],labelText.tightRect[0]); labelPos[1] = Math.min(labelPos[1],labelText.tightRect[1]); labelPos[2] = Math.max(labelPos[2],labelText.tightRect[0]+labelText.tightRect[2]); labelPos[3] = Math.max(labelPos[3],labelText.tightRect[1]+labelText.tightRect[3]); } major -= 1; xAxisPoint -= xScaleSpacing; } } } if (showYScale == true) { context.textBaseline = "middle"; context.textAlign = "right"; if (!un(gridDetails.yScaleColor)) { context.fillStyle = gridDetails.yScaleColor; } else { context.fillStyle = yAxisColor; } context.font = gridFontSize+"px Arial"; var yScaleSpacing = (height * yScaleStep) / (yMax - yMin); // positive y numbers var yAxisPoint = y0 - yScaleSpacing; var major = 1; var placeValue = Math.pow(10,Math.floor(Math.log(yScaleStep)/Math.log(10))); while (roundToNearest(yAxisPoint,0.001) >= roundToNearest(top,0.001)) { if (yAxisPoint <= top + height) { var axisValue = Number(roundSF(major*yScaleStep, 5)); var textWidth = context.measureText(String(axisValue)).width context.fillStyle = backColor; context.fillRect(x0DisplayPos - textWidth - 11*sf - yScaleOffset, yAxisPoint - gridFontSize * 0.5, textWidth + 3*sf, gridFontSize); var labelText = drawMathsText(context, String(axisValue), gridFontSize, x0DisplayPos - 10*sf - yScaleOffset, yAxisPoint - 2, true, [], 'right', 'middle', yScaleColor); /*context.save(); context.lineWidth = 1; context.strokeStyle = '#393'; context.strokeRect(labelText.tightRect[0],labelText.tightRect[1],labelText.tightRect[2],labelText.tightRect[3]); context.restore();*/ labelPos[0] = Math.min(labelPos[0],labelText.tightRect[0]); labelPos[1] = Math.min(labelPos[1],labelText.tightRect[1]); labelPos[2] = Math.max(labelPos[2],labelText.tightRect[0]+labelText.tightRect[2]); labelPos[3] = Math.max(labelPos[3],labelText.tightRect[1]+labelText.tightRect[3]); } major += 1; yAxisPoint -= yScaleSpacing; } // negative y numbers var yAxisPoint = y0 + yScaleSpacing; var major = -1; while (roundToNearest(yAxisPoint,0.001) <= roundToNearest(top+height,0.001)) { if (yAxisPoint >= top) { var axisValue = Number(roundSF(major*yScaleStep, 5)); var textWidth = context.measureText(String(axisValue)).width context.fillStyle = backColor; context.fillRect(x0DisplayPos - textWidth - 11*sf - yScaleOffset, yAxisPoint - gridFontSize * 0.5, textWidth + 3*sf, gridFontSize); var labelText = drawMathsText(context, String(axisValue), gridFontSize, x0DisplayPos - 10*sf - yScaleOffset, yAxisPoint - 2, true, [], 'right', 'middle', yScaleColor); /*context.save(); context.lineWidth = 1; context.strokeStyle = '#393'; context.strokeRect(labelText.tightRect[0],labelText.tightRect[1],labelText.tightRect[2],labelText.tightRect[3]); context.restore();*/ labelPos[0] = Math.min(labelPos[0],labelText.tightRect[0]); labelPos[1] = Math.min(labelPos[1],labelText.tightRect[1]); labelPos[2] = Math.max(labelPos[2],labelText.tightRect[0]+labelText.tightRect[2]); labelPos[3] = Math.max(labelPos[3],labelText.tightRect[1]+labelText.tightRect[3]); } major -= 1; yAxisPoint += yScaleSpacing; } } } if (originStyle == 'numbers') { if (showXScale == true && x0 >= left && x0 <= left+width) { var params = {ctx:context,textArray:["0"],font:"algebra",fontSize:gridFontSize,left:x0-50,width:100,top:y0DisplayPos+xScaleOffset-4,height:50,minTightWidth:1,minTightHeight:1,align:'center',vertAlign:'top',color:xScaleColor,box:{type:'tight',borderColor:backColor,borderWidth:0.01,color:backColor,padding:1}}; var labelText = text(params); labelPos[0] = Math.min(labelPos[0],labelText.tightRect[0]); labelPos[1] = Math.min(labelPos[1],labelText.tightRect[1]); labelPos[2] = Math.max(labelPos[2],labelText.tightRect[0]+labelText.tightRect[2]); labelPos[3] = Math.max(labelPos[3],labelText.tightRect[1]+labelText.tightRect[3]); } if (showYScale == true && y0 >= top && y0 <= top+height) { context.textBaseline = "middle"; context.textAlign = "right"; context.fillStyle = yAxisColor; context.font = gridFontSize+"px Arial"; var textWidth = context.measureText("0").width; context.fillStyle = backColor; context.fillRect(x0DisplayPos - textWidth - 11*sf - yScaleOffset, y0 - gridFontSize * 0.5, textWidth + 3*sf, gridFontSize); var labelText = drawMathsText(context, "0", gridFontSize, x0DisplayPos - 10*sf - yScaleOffset, y0 - 2, true, [], 'right', 'middle', yScaleColor); labelPos[0] = Math.min(labelPos[0],labelText.tightRect[0]); labelPos[1] = Math.min(labelPos[1],labelText.tightRect[1]); labelPos[2] = Math.max(labelPos[2],labelText.tightRect[0]+labelText.tightRect[2]); labelPos[3] = Math.max(labelPos[3],labelText.tightRect[1]+labelText.tightRect[3]); } } if (!un(gridDetails.plot)) { for (var c = 0; c < gridDetails.plot.length; c++) { var plot = gridDetails.plot[c]; if (plot.type == 'histogram') { drawHistogram(context,gridDetails,plot); /* eg. sel().originStyle = 'numbers'; sel().xMin = 0; sel().xMax = 35; sel().yMin = 0; sel().yMax = 2; sel().yMajorStep = 0.5; sel().yMinorStep = 0.1; sel().plot = [{type:'histogram',fillStyle:'#CFF',chartData:[{min:0,max:10,freq:5},{min:10,max:15,freq:8},{min:15,max:20,freq:7,fillStyle:'#FCF'},{min:20,max:35,freq:8}]}]; sel().axisLabels = [{text:['<>Time (s)']},{text:['<>Frequency'+br+'Density']}]; */ } else if (plot.type == 'cFreq') { context.lineWidth = plot.lineWidth || 3; context.strokeStyle = plot.strokeStyle || '#000'; drawSmoothCurve(context,gridDetails,plot.chartData,0.5,16,true); drawScatter(context,gridDetails,plot); /* eg. sel().originStyle = 'numbers'; sel().xMin = 0; sel().xMax = 140; sel().yMin = 0; sel().yMax = 70; sel().xMajorStep = 20; sel().xMinorStep = 10; sel().yMajorStep = 10; sel().yMinorStep = 5; sel().plot = [{type:'cFreq',pointStyle:'circle',chartData:[[0,0],[25,20],[50,43],[75,48],[100,56],[125,62]]}]; sel().axisLabels = [{text:['<>Time (s)']},{text:['<>Cumulative Frequency']}]; */ } else if (plot.type == 'scatter') { drawScatter(context,gridDetails,plot); /* eg. sel().originStyle = 'numbers'; sel().xMin = 0; sel().xMax = 140; sel().yMin = 0; sel().yMax = 70; sel().xMajorStep = 20; sel().xMinorStep = 10; sel().yMajorStep = 10; sel().yMinorStep = 5; sel().plot = [{type:'scatter',pointStyle:'circle',chartData:[[25,20],[50,43],[75,48],[100,56],[125,62]]}]; sel().axisLabels = [{text:['<>Weight (kg)']},{text:['<>Height (cm)']}]; */ } else if (plot.type == 'freqPolygon') { var coords = convertCoordsGridToCanvas(0,0,gridDetails,plot.chartData); context.lineWidth = plot.lineWidth || 3; context.strokeStyle = plot.strokeStyle || '#000'; context.beginPath(); context.moveTo(coords[0][0],coords[0][1]); for (var c2 = 1; c2 < coords.length; c2++) context.lineTo(coords[c2][0],coords[c2][1]); context.stroke(); drawScatter(context,gridDetails,plot); /* eg. sel().originStyle = 'numbers'; sel().xMin = 0; sel().xMax = 140; sel().yMin = 0; sel().yMax = 70; sel().xMajorStep = 20; sel().xMinorStep = 10; sel().yMajorStep = 10; sel().yMinorStep = 5; sel().plot = [{type:'freqPolygon',pointStyle:'circle',chartData:[[25,20],[50,43],[75,48],[100,56],[125,62]]}]; sel().axisLabels = [{text:['<>Time (days)']},{text:['<>Height (cm)']}]; */ } else if (plot.type == 'smoothCurve') { context.lineWidth = plot.lineWidth || 3; context.strokeStyle = plot.strokeStyle || '#000'; drawSmoothCurve(context,gridDetails,plot.chartData,0.5,16,false); drawScatter(context,gridDetails,plot); /* eg. sel().originStyle = 'numbers'; sel().xMin = 0; sel().xMax = 140; sel().yMin = 0; sel().yMax = 70; sel().xMajorStep = 20; sel().xMinorStep = 10; sel().yMajorStep = 10; sel().yMinorStep = 5; sel().plot = [{type:'freqPolygon',pointStyle:'circle',chartData:[[25,20],[50,43],[75,48],[100,56],[125,62]]}]; sel().axisLabels = [{text:['<>Time (days)']},{text:['<>Height (cm)']}]; */ } } } if (showAxes == true) { // draw axes context.beginPath(); context.strokeStyle = xAxisColor; context.lineWidth = 3*sf; // if neccesary, draw x-Axis if (y0 >= top && y0 <= top + height) { context.moveTo(left, y0); context.lineTo(left + width-10*sf, y0); } context.closePath(); context.stroke(); context.beginPath(); context.strokeStyle = yAxisColor; context.lineWidth = 3*sf; // if neccesary, draw y-Axis if (x0 >= left && x0 <= left + width) { context.moveTo(x0, top+10*sf); context.lineTo(x0, top + height); } context.closePath(); context.stroke(); // draw an arrow at the top of the y-axis context.fillStyle = yAxisColor; context.strokeStyle = yAxisColor; context.beginPath(); context.moveTo(x0DisplayPos, top + 2*sf); context.lineTo(x0DisplayPos - 5*sf, top + 12*sf); context.lineTo(x0DisplayPos + 5*sf, top + 12*sf); context.lineTo(x0DisplayPos, top + 2*sf); context.closePath(); context.stroke(); context.fill(); // draw an arrow at the right of the x-axis context.fillStyle = xAxisColor; context.strokeStyle = xAxisColor; context.beginPath(); context.moveTo(left + width - 2*sf, y0DisplayPos); context.lineTo(left + width - 12*sf, y0DisplayPos - 5*sf); context.lineTo(left + width - 12*sf, y0DisplayPos + 5*sf); context.lineTo(left + width - 2*sf, y0DisplayPos); context.closePath(); context.stroke(); context.fill(); } labelPos[0] = Math.min(labelPos[0],x0DisplayPos-5); labelPos[1] = Math.min(labelPos[1],y0DisplayPos-5); labelPos[2] = Math.max(labelPos[2],x0DisplayPos+5); labelPos[3] = Math.max(labelPos[3],y0DisplayPos+5); if (!un(yLabelRect) && !un(xLabelRect)) { labelPos[0] = Math.min(labelPos[0],yLabelRect[0]); labelPos[3] = Math.max(labelPos[3],xLabelRect[1]+xLabelRect[3]); } var labelBorder = [labelPos[0],labelPos[1],labelPos[2]-labelPos[0],labelPos[3]-labelPos[1],labelPos[2],labelPos[3]]; /* context.save(); context.lineWidth = 3; context.strokeStyle = '#F00'; context.strokeRect(labelBorder[0],labelBorder[1],labelBorder[2],labelBorder[3]); context.restore(); //*/ return {labelBorder:labelBorder}; } function drawHistogram(ctx,gridDetails,chart) { var xStep = gridDetails.width / (gridDetails.xMax - gridDetails.xMin); var yStep = gridDetails.height / gridDetails.yMax; var lineWidth = chart.lineWidth || 3; var strokeStyle = chart.strokeStyle || '#000'; var fillStyle = chart.fillStyle || '#FCF'; var data = chart.chartData; for (var d = 0; d < data.length; d++) { var fd = data[d].freq / (data[d].max - data[d].min); var l = gridDetails.left + xStep * (data[d].min - gridDetails.xMin); var t = gridDetails.top + gridDetails.height - yStep * fd; var w = xStep * (data[d].max - data[d].min); var h = yStep * fd; ctx.fillStyle = data[d].fillStyle || fillStyle; ctx.strokeStyle = data[d].strokeStyle || strokeStyle; ctx.lineWidth = data[d].lineWidth || lineWidth; ctx.fillRect(l,t,w,h); ctx.strokeRect(l,t,w,h); } //text({ctx:ctx,textArray:['<><>'+xAxisLabel],rect:[gridDetails.left+gridDetails.width-200,gridDetails.top+gridDetails.height+40,200,500],align:[0,-1]}); } function drawScatter(ctx,gridDetails,chart) { var lineWidth = chart.lineWidth || 3; var strokeStyle = chart.strokeStyle || chart.color || '#000'; var fillStyle = chart.fillStyle || strokeStyle; var pointStyle = chart.pointStyle || 'cross'; // cross, circle or none var pointSize = chart.pointSize || 8; var meta = chart.meta || []; var coords = convertCoordsGridToCanvas(0,0,gridDetails,chart.chartData); for (var d = 0; d < chart.chartData.length; d++) { var meta2 = meta[d] || {}; var point = meta2.pointStyle || pointStyle; var size = meta2.pointSize || pointSize; ctx.fillStyle = meta2.fillStyle || fillStyle; ctx.strokeStyle = meta2.strokeStyle || strokeStyle; ctx.lineWidth = meta2.lineWidth || lineWidth; var x = coords[d][0]; var y = coords[d][1]; switch (point) { case 'cross': ctx.beginPath(); ctx.moveTo(x-size,y-size); ctx.lineTo(x+size,y+size); ctx.moveTo(x-size,y+size); ctx.lineTo(x+size,y-size); ctx.stroke(); break; case 'circle': ctx.beginPath(); ctx.moveTo(x,y); ctx.arc(x,y,size,0,2*Math.PI); ctx.fill(); break; } } } function convertToHoursMins(num) { var hours = Math.floor(num); var mins = String(roundToNearest((num-hours)*60,2)); if (mins.length == 1) mins = "0"+mins; return String(hours)+":"+mins; } function gridLabelPos(ctx,gridDetails,obj) { //eg. gridLabelPos(j523ctx1,gridDetails,{x:4,y:6,lineWidth:2,lineColor:'#00F',lineDash:[4,4],labelX:true,labelXColor:'#00F',labelY:true,,labelYColor:'#00F'}); ctx.save(); ctx.lineWidth = obj.lineWidth || 2; ctx.strokeStyle = obj.lineColor || '#00F'; var dash = obj.lineDash || []; if (typeof ctx.setLineDash !== 'function') ctx.setLineDash = function(){}; ctx.setLineDash(dash); var x0 = getPosOfCoordX2(0,gridDetails.left,gridDetails.width,gridDetails.xMin,gridDetails.xMax); if (x0 < gridDetails.left) x0 = gridDetails.left; if (x0 > gridDetails.left + gridDetails.width) x0 = gridDetails.left + gridDetails.width; var y0 = getPosOfCoordY2(0,gridDetails.top,gridDetails.height,gridDetails.yMin,gridDetails.yMax); if (y0 < gridDetails.top) y0 = gridDetails.top; if (y0 > gridDetails.top + gridDetails.height) y0 = gridDetails.top + gridDetails.height; var x1 = getPosOfCoordX2(obj.x,gridDetails.left,gridDetails.width,gridDetails.xMin,gridDetails.xMax); var y1 = getPosOfCoordY2(obj.y,gridDetails.top,gridDetails.height,gridDetails.yMin,gridDetails.yMax); if (boolean(obj.line,true) == true) { ctx.beginPath(); ctx.moveTo(x1,y0); ctx.lineTo(x1,y1); ctx.lineTo(x0,y1); ctx.stroke(); } ctx.setLineDash([]); var sf = gridDetails.sf || 1; var gridFontSize = obj.fontSize || gridDetails.fontSize || 24*sf; var backColor = gridDetails.backColor || mainCanvasFillStyle || '#FFC'; var invertedBackColor = getShades('#000',true); var originColor = gridDetails.axesColor || invertedBackColor[4] || '#666'; var xAxisColor = gridDetails.axesColor || invertedBackColor[0] || '#000'; var yAxisColor = gridDetails.axesColor || invertedBackColor[0] || '#000'; if (obj.labelX === true) { var xColor = obj.labelXColor || '#00F'; text({ctx:ctx,textArray:['<>'+String(obj.x)],font:"algebra",fontSize:gridFontSize,left:x1-50,width:100,top:y0,height:50,minTightWidth:1,minTightHeight:1,align:'center',vertAlign:'top',box:{type:'tight',borderColor:backColor,borderWidth:0.01,color:backColor,padding:1}}); } else if (typeof obj.labelX == 'object') { var xColor = obj.labelXColor || '#00F'; obj.labelX.unshift('<>'); text({ctx:ctx,textArray:obj.labelX,font:"algebra",fontSize:gridFontSize,left:x1-50,width:100,top:y0,height:50,minTightWidth:1,minTightHeight:1,align:'center',vertAlign:'top',box:{type:'tight',borderColor:backColor,borderWidth:0.01,color:backColor,padding:1}}); } if (obj.labelY === true) { var yColor = obj.labelYColor || '#00F'; text({ctx:ctx,textArray:['<>'+String(obj.y)],font:"algebra",fontSize:gridFontSize,left:x0-100-10*sf,width:100,top:y1-25,height:50,minTightWidth:1,minTightHeight:1,align:'right',vertAlign:'middle',box:{type:'tight',borderColor:backColor,borderWidth:0.01,color:backColor,padding:1}}); } else if (typeof obj.labelY == 'object') { var yColor = obj.labelYColor || '#00F'; obj.labelY.unshift('<>'); text({ctx:ctx,textArray:obj.labelY,font:"algebra",fontSize:gridFontSize,left:x1-50,width:100,top:y0,height:50,minTightWidth:1,minTightHeight:1,align:'center',vertAlign:'top',box:{type:'tight',borderColor:backColor,borderWidth:0.01,color:backColor,padding:1}}); } if (boolean(gridDetails.showScales,true) == true && boolean(gridDetails.showOrigin,true) == true) { // redraw origin if (x0 >= gridDetails.left && x0 <= gridDetails.left + gridDetails.width && y0 >= gridDetails.top && y0 <= gridDetails.top + gridDetails.height) { ctx.strokeStyle = originColor; ctx.lineWidth = 2*sf; ctx.beginPath(); ctx.arc(x0,y0,10*sf,0,2*Math.PI); ctx.stroke(); } } // redraw axes ctx.beginPath(); ctx.strokeStyle = xAxisColor; ctx.lineWidth = 3*sf; // if neccesary, draw x-Axis if (y0 >= gridDetails.top && y0 <= gridDetails.top + gridDetails.height) { ctx.moveTo(gridDetails.left, y0); ctx.lineTo(gridDetails.left + gridDetails.width-10*sf, y0); } ctx.closePath(); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = yAxisColor; ctx.lineWidth = 3*sf; // if neccesary, draw y-Axis if (x0 >= gridDetails.left && x0 <= gridDetails.left + gridDetails.width) { ctx.moveTo(x0, gridDetails.top+10*sf); ctx.lineTo(x0, gridDetails.top + gridDetails.height); } ctx.closePath(); ctx.stroke(); // draw an arrow at the gridDetails.top of the y-axis ctx.fillStyle = yAxisColor; ctx.strokeStyle = yAxisColor; ctx.beginPath(); ctx.moveTo(x0, gridDetails.top + 2*sf); ctx.lineTo(x0 - 5*sf, gridDetails.top + 12*sf); ctx.lineTo(x0 + 5*sf, gridDetails.top + 12*sf); ctx.lineTo(x0, gridDetails.top + 2*sf); ctx.closePath(); ctx.stroke(); ctx.fill(); // draw an arrow at the right of the x-axis ctx.fillStyle = xAxisColor; ctx.strokeStyle = xAxisColor; ctx.beginPath(); ctx.moveTo(gridDetails.left + gridDetails.width - 2*sf, y0); ctx.lineTo(gridDetails.left + gridDetails.width - 12*sf, y0 - 5*sf); ctx.lineTo(gridDetails.left + gridDetails.width - 12*sf, y0 + 5*sf); ctx.lineTo(gridDetails.left + gridDetails.width - 2*sf, y0); ctx.closePath(); ctx.stroke(); ctx.fill(); ctx.restore(); } function drawGridRadians(context,contextLeft,contextTop,gridDetails,opt_fontSize,opt_minorColor,opt_majorColor,opt_xAxisColor,opt_yAxisColor){ var left = gridDetails.left - contextLeft; // use dimensions relative to background canvas (1200x700) var top = gridDetails.top - contextTop; // rather than the context supplied var width = gridDetails.width; var height = gridDetails.height; var xMin = gridDetails.xMin; // as multiple of pi var xMax = gridDetails.xMax; // as multiple of pi var yMin = gridDetails.yMin; var yMax = gridDetails.yMax; var xMinorStep = gridDetails.xMinorStep; // as fractional multiple of pi {num:1,denom:2} var xMajorStep = gridDetails.xMajorStep; // as fractional multiple of pi {num:1,denom:6} var yMinorStep = gridDetails.yMinorStep; var yMajorStep = gridDetails.yMajorStep; // work out the spacing for minor and major steps var xMinorSpacing = (width * (xMinorStep.num / xMinorStep.denom) * Math.PI) / (xMax - xMin); var xMajorSpacing = (width * (xMajorStep.num / xMajorStep.denom) * Math.PI) / (xMax - xMin); var yMinorSpacing = (height * yMinorStep) / (yMax - yMin); var yMajorSpacing = (height * yMajorStep) / (yMax - yMin); var fontSize = opt_fontSize || 24; var minorColor = opt_minorColor || '#DDD' var majorColor = opt_majorColor || '#AAA' var xAxisColor = opt_xAxisColor || '#000'; var yAxisColor = opt_yAxisColor || '#000'; // work out the coordinates of the origin var x0 = left - (xMin * width) / (xMax - xMin); var y0 = top + (yMax * height) / (yMax - yMin); // work out the actual display position of the origin (ie. at the edge if it is off the grid) var x0DisplayPos; var y0DisplayPos; if (x0 >= left && x0 <= left + width) { x0DisplayPos = x0; } else { if (x0 < left) {x0DisplayPos = left}; if (x0 > left + width) {x0DisplayPos = left + width}; } if (y0 >= top && y0 <= top + height) { y0DisplayPos = y0; } else { if (y0 < top) {y0DisplayPos = top}; if (y0 > top + height) {y0DisplayPos = top + height}; } // start to draw gridlines context.clearRect(0, 0, 1200, 700); // draw minor grid lines context.strokeStyle = minorColor; context.lineWidth = 1.2; context.beginPath(); // draws positive xMinor lines var xAxisPoint = x0 + xMinorSpacing; while (xAxisPoint <= left + width) { context.moveTo(xAxisPoint, top); context.lineTo(xAxisPoint, top + height); xAxisPoint += xMinorSpacing; } // draws negative xMinor lines var xAxisPoint = x0 - xMinorSpacing; while (xAxisPoint >= left) { context.moveTo(xAxisPoint, top); context.lineTo(xAxisPoint, top + height); xAxisPoint -= xMinorSpacing; } // draws positive yMinor lines var yAxisPoint = y0 - yMinorSpacing; while (yAxisPoint >= top) { context.moveTo(left, yAxisPoint); context.lineTo(left + width, yAxisPoint); yAxisPoint -= yMinorSpacing; } // draws negative yMinor lines var yAxisPoint = y0 + yMinorSpacing; while (yAxisPoint <= top + height) { context.moveTo(left, yAxisPoint); context.lineTo(left + width, yAxisPoint); yAxisPoint += yMinorSpacing; } context.closePath(); context.stroke(); // draw major lines context.strokeStyle = majorColor; context.lineWidth = 2; context.beginPath(); // draws positive xMajor lines var xAxisPoint = x0 + xMajorSpacing; while (xAxisPoint <= left + width) { context.moveTo(xAxisPoint, top); context.lineTo(xAxisPoint, top + height); xAxisPoint += xMajorSpacing; } // draws negative xMajor lines var xAxisPoint = x0 - xMajorSpacing; while (xAxisPoint >= left) { context.moveTo(xAxisPoint, top); context.lineTo(xAxisPoint, top + height); xAxisPoint -= xMajorSpacing; } // draws positive yMajor lines var yAxisPoint = y0 - yMajorSpacing; while (yAxisPoint >= top) { context.moveTo(left, yAxisPoint); context.lineTo(left + width, yAxisPoint); yAxisPoint -= yMajorSpacing; } // draws negative yMajor lines var yAxisPoint = y0 + yMajorSpacing; while (yAxisPoint <= top + height) { context.moveTo(left, yAxisPoint); context.lineTo(left + width, yAxisPoint); yAxisPoint += yMajorSpacing; } context.closePath(); context.stroke(); // draw axes context.beginPath(); context.strokeStyle = xAxisColor; context.lineWidth = 3; // if neccesary, draw x-Axis if (y0 > top && y0 < top + height) { context.moveTo(left, y0); context.lineTo(left + width, y0); } context.closePath(); context.stroke(); context.beginPath(); context.strokeStyle = yAxisColor; context.lineWidth = 3; // if neccesary, draw y-Axis if (x0 > left && x0 < left + width) { context.moveTo(x0, top); context.lineTo(x0, top + height); } context.closePath(); context.stroke(); var labelFontSize = fontSize * 1.375; // draw an arrow and label at the top of the y-axis context.fillStyle = yAxisColor; context.strokeStyle = yAxisColor; context.beginPath(); context.moveTo(x0DisplayPos, top + 2); context.lineTo(x0DisplayPos - 5, top + 12); context.lineTo(x0DisplayPos + 5, top + 12); context.lineTo(x0DisplayPos, top + 2); context.closePath(); context.stroke(); context.fill(); context.textAlign = 'center'; context.textBaseline = "bottom"; wrapText(context, 'y', x0DisplayPos + 5, top - 15, 50, 40, 'italic '+labelFontSize+'px Times New Roman', '#000'); // draw an arrow and label at the right of the x-axis context.fillStyle = xAxisColor; context.strokeStyle = xAxisColor; context.beginPath(); context.moveTo(left + width - 2, y0DisplayPos); context.lineTo(left + width - 12, y0DisplayPos - 5); context.lineTo(left + width - 12, y0DisplayPos + 5); context.lineTo(left + width - 2, y0DisplayPos); context.closePath(); context.stroke(); context.fill(); context.textAlign = 'left'; context.textBaseline = "middle"; wrapText(context, 'x', left + width + 15, y0DisplayPos - 5, 50, 40, 'italic '+labelFontSize+'px Times New Roman', '#000'); // draw a black rectangular border context.strokeStyle = '#000'; context.lineWidth = 4; context.strokeRect(left, top, width, height); // mark the origin, if it is visible if (x0 >= left && x0 <= left + width && y0 >= top && y0 <= top + height) { context.textAlign = 'center'; context.textBaseline = "middle"; context.strokeStyle = '#666'; context.lineWidth = 2; context.beginPath(); context.arc(x0,y0,10,0,2*Math.PI); context.closePath(); context.stroke(); } // draw axes numbers context.font = '24px Arial'; context.textAlign = "center"; context.textBaseline = "top"; // draw positive x axis numbers as multiple of pi var xAxisPoint = x0 + xMajorSpacing; var major = 1; while (xAxisPoint <= left + width) { var frac = {num: major*xMajorStep.num, denom:xMajorStep.denom}; var axisValue = multOfPiText(frac); context.clearRect(xAxisPoint - 2, y0DisplayPos + 3, 4, fontSize * 1.15); drawMathsText(context, axisValue, 0.8 * fontSize, xAxisPoint, y0DisplayPos + 1 + 0.7 * fontSize, true, [], 'center', 'middle', '#000') major += 1; xAxisPoint += xMajorSpacing; } // draw negative x axis numbers as multiple of pi xAxisPoint = x0 - xMajorSpacing; major = -1; while (xAxisPoint >= left) { var frac = {num: major*-1*xMajorStep.num, denom:xMajorStep.denom} var axisValue = multOfPiText(frac); context.clearRect(xAxisPoint - 2, y0DisplayPos + 3, 4, fontSize * 1.15); drawMathsText(context, axisValue, 0.8 * fontSize, xAxisPoint, y0DisplayPos + 1 + 0.7 * fontSize, true, [], 'center', 'middle', '#000') major -= 1; xAxisPoint -= xMajorSpacing; } context.textBaseline = "middle"; context.textAlign = "right"; context.fillStyle = '#000'; // positive y numbers var yAxisPoint = y0 - yMajorSpacing; var major = 1; while (yAxisPoint >= top) { var axisValue = Number(roundSF(major*yMajorStep, 5)); var textWidth = context.measureText(String(axisValue)).width context.clearRect(x0DisplayPos - textWidth - 11, yAxisPoint - fontSize * 0.5, textWidth + 3, fontSize); wrapText(context, String(axisValue), x0DisplayPos - 2, yAxisPoint - 2, 50, 40, fontSize + 'px Arial') major += 1; yAxisPoint -= yMajorSpacing; } // negative y numbers var yAxisPoint = y0 + yMajorSpacing; var major = -1; while (yAxisPoint <= top + height) { var axisValue = Number(roundSF(major*yMajorStep, 5)); var textWidth = context.measureText(String(axisValue)).width context.clearRect(x0DisplayPos - textWidth - 11, yAxisPoint - fontSize * 0.5, textWidth + 3, fontSize); wrapText(context, String(axisValue), x0DisplayPos - 2, yAxisPoint - 2, 50, 40, fontSize + 'px Arial') major -= 1; yAxisPoint += yMajorSpacing; } } function multOfPiText(frac) { // frac is an object with num and denom properties var newNum, newDenom, hcf2; var returnArray = []; if (frac.num == 0) return ["0"]; if (frac.num < 0) { frac.num = frac.num * -1; if (frac.denom < 0) { frac.denom = frac.denom * -1; } else { returnArray.push("-") } } else { if (frac.denom < 0) { frac.denom = frac.denom * -1; returnArray.push("-") } } hcf2 = hcf(frac.num, frac.denom); newNum = frac.num / hcf2; newDenom = frac.denom / hcf2; if (newDenom == 1) { if (newNum == 1) { returnArray.push(String.fromCharCode(0x03C0)); } else { returnArray.push(String(newNum) + String.fromCharCode(0x03C0)); } } else { if (newNum == 1) { newNum = String.fromCharCode(0x03C0); } else { newNum = String(newNum) + String.fromCharCode(0x03C0); } returnArray.push(['frac', [newNum], [String(newDenom)]]) } return returnArray; } function drawCoord(context,contextLeft,contextTop,gridDetails,x,y,opt_color,opt_size,opt_lineWidth) { if (typeof gridDetails.sf !== 'undefined') { var sf = gridDetails.sf; } else { var sf = 1; } var mode = gridDetails.angleMode || 'deg'; var left = gridDetails.left - contextLeft; // use dimensions relative to background canvas (1200x700) var top = gridDetails.top - contextTop; // rather than the context supplied if (y < gridDetails.yMin || y > gridDetails.yMax) return; if (mode == 'deg') { if (x < gridDetails.xMin || x > gridDetails.xMax) return; var xPos = getPosOfCoordX2(x, left, gridDetails.width, gridDetails.xMin, gridDetails.xMax); } else { if (x < Math.PI*gridDetails.xMin[0]/gridDetails.xMin[1] || x > Math.PI*gridDetails.xMax[0]/gridDetails.xMax[1]) return; var xPos = getPosOfCoordX2(x, left, gridDetails.width, Math.PI*gridDetails.xMin[0]/gridDetails.xMin[1], Math.PI*gridDetails.xMax[0]/gridDetails.xMax[1]); } var yPos = getPosOfCoordY2(y, top, gridDetails.height, gridDetails.yMin, gridDetails.yMax); var color = opt_color || '#00F'; var size = gridDetails.pointSize*sf || opt_size*sf || 7*sf; context.lineCap = 'round'; context.lineJoin = 'round'; context.save(); context.strokeStyle = color; context.lineWidth = gridDetails.pointWidth*sf || opt_lineWidth*sf || 1.5*sf; context.beginPath(); context.moveTo(xPos - size, yPos - size); context.lineTo(xPos + size, yPos + size); context.moveTo(xPos - size, yPos + size); context.lineTo(xPos + size, yPos - size); context.closePath(); context.stroke(); context.restore(); } function fillPolygonOnGrid(context,contextLeft,contextTop,gridDetails,vertices,opt_color,opt_alpha) { var left = gridDetails.left - contextLeft; // use dimensions relative to background canvas (1200x700) var top = gridDetails.top - contextTop; // rather than the context supplied var color = opt_color || '#00F'; var colorRGB = hexToRgb(color); var alpha = opt_alpha || 0.2; context.save(); context.fillStyle = "rgba("+colorRGB.r+","+colorRGB.g+","+colorRGB.b+","+alpha+")"; context.beginPath(); var xPos = getPosOfCoordX2(vertices[0][0],left,gridDetails.width,gridDetails.xMin,gridDetails.xMax); var yPos = getPosOfCoordY2(vertices[0][1],top,gridDetails.height,gridDetails.yMin,gridDetails.yMax); context.moveTo(xPos,yPos); for (var i = 1; i < vertices.length; i++) { var xPos = getPosOfCoordX2(vertices[i][0],left,gridDetails.width,gridDetails.xMin,gridDetails.xMax); var yPos = getPosOfCoordY2(vertices[i][1],top,gridDetails.height,gridDetails.yMin,gridDetails.yMax); context.lineTo(xPos,yPos); } context.fill(); context.restore(); } function getCoordAtMousePos(gridDetails,mode) { var mode2 = mode || gridDetails.angleMode || 'deg'; if (mode2 == 'deg') { var xCoord = getCoordX2(mouse.x, gridDetails.left, gridDetails.width, gridDetails.xMin, gridDetails.xMax); } else { var xCoord = getCoordX2(mouse.x, gridDetails.left, gridDetails.width, Math.PI*gridDetails.xMin[0]/gridDetails.xMin[1], Math.PI*gridDetails.xMax[0]/gridDetails.xMax[1]); } var yCoord = getCoordY2(mouse.y, gridDetails.top, gridDetails.height, gridDetails.yMin, gridDetails.yMax); return [xCoord, yCoord]; } function getCoordX2(xPos, left, width, xMin, xMax) { return (xMin + (xMax - xMin) * (xPos - left) / width); } function getCoordY2(yPos, top, height, yMin, yMax) { return (yMax - (yMax - yMin) * (yPos - top) / height); } function getPosOfCoordX2(xCoord, left, width, xMin, xMax) { return (left + (xCoord - xMin) * width / (xMax - xMin)); } function getPosOfCoordY2(yCoord, top, height, yMin, yMax) { return (top + (yMax - yCoord) * height / (yMax - yMin)); } function getPosOfCoord(pos,gridDetails) { // given ([x,y],gridDetails) return [ getPosOfCoordX2(pos[0],gridDetails.left,gridDetails.width,gridDetails.xMin,gridDetails.xMax), getPosOfCoordY2(pos[1],gridDetails.top,gridDetails.height,gridDetails.yMin,gridDetails.yMax) ]; } function drawLine(context,contextLeft,contextTop,gridDetails,x1,y1,x2,y2,opt_color,opt_thickness,opt_showPoints,opt_lineSegment,opt_dash,opt_dashWidth,opt_dashGapWidth) { if (typeof gridDetails.sf !== 'undefined') { var sf = gridDetails.sf; } else { var sf = 1; } var mode = gridDetails.angleMode || 'deg'; var left = gridDetails.left - contextLeft; // use dimensions relative to background canvas (1200x700) var top = gridDetails.top - contextTop; // rather than the context supplied var width = gridDetails.width; var height = gridDetails.height; var yMin = gridDetails.yMin; var yMax = gridDetails.yMax; if (mode == 'deg' || typeof gridDetails.xMin == 'number') { var xMin = gridDetails.xMin; var xMax = gridDetails.xMax; } else { var xMin = Math.PI*gridDetails.xMin[0]/gridDetails.xMin[1]; var xMax = Math.PI*gridDetails.xMax[0]/gridDetails.xMax[1]; } var color = opt_color || '#00F'; var thickness = gridDetails.lineWidth*sf || opt_thickness*sf || 3*sf; var showPoints = boolean(opt_showPoints, false); var lineSegment = boolean(opt_lineSegment, false); var dash = boolean(opt_dash, false); var dashWidth = def([opt_dashWidth*sf,15*sf]); var dashGapWidth = def([opt_dashGapWidth*sf,5*sf]); var x1Pos = getPosOfCoordX2(x1, left, width, xMin, xMax); var y1Pos = getPosOfCoordY2(y1, top, height, yMin, yMax); var x2Pos = getPosOfCoordX2(x2, left, width, xMin, xMax); var y2Pos = getPosOfCoordY2(y2, top, height, yMin, yMax); // check if the line is visible on the grid var x1vis = 0; var y1vis = 0; var x2vis = 0; var y2vis = 0; if (x1 < xMin) x1vis = -1; if (x1 > xMax) x1vis = 1; if (y1 < yMin) y1vis = -1; if (y1 > yMax) y1vis = 1; if (x2 < xMin) x2vis = -1; if (x2 > xMax) x2vis = 1; if (y2 < yMin) y2vis = -1; if (y2 > yMax) y2vis = 1; if ((x1vis == -1 && x2vis == -1) || (x1vis == 1 && x2vis == 1) || (y1vis == -1 && y2vis == -1) || (y1vis == 1 && y2vis == 1)) return; // check if the line (treated as infinite) intersects at least one of the four edges of the grid if (intersects2(x1,y1,x2,y2,xMin,yMin,xMax,yMin) == false && intersects2(x1,y1,x2,y2,xMin,yMin,xMin,yMax) == false && intersects2(x1,y1,x2,y2,xMax,yMin,xMax,yMax) == false && intersects2(x1,y1,x2,y2,xMin,yMax,xMax,yMax) == false) { return; } context.save(); if (typeof context.setLineDash !== 'function') context.setLineDash = function(){}; context.setLineDash([]); context.lineCap = 'round'; context.lineJoin = 'round'; if (showPoints == true) { // plot the two points if they are visible on the grid if (x1 >= xMin && x1 <= xMax && y1 >= yMin && y1 <= yMax) { context.save(); context.strokeStyle = color; context.lineWidth = thickness * 0.7; context.beginPath(); context.moveTo(x1Pos - 8, y1Pos - 8); context.lineTo(x1Pos + 8, y1Pos + 8); context.moveTo(x1Pos - 8, y1Pos + 8); context.lineTo(x1Pos + 8, y1Pos - 8); context.closePath(); context.stroke(); context.restore(); } if (x2 >= xMin && x2 <= xMax && y2 >= yMin && y2 <= yMax) { context.save(); context.strokeStyle = color; context.lineWidth = thickness * 0.7; context.beginPath(); context.moveTo(x2Pos - 8, y2Pos - 8); context.lineTo(x2Pos + 8, y2Pos + 8); context.moveTo(x2Pos - 8, y2Pos + 8); context.lineTo(x2Pos + 8, y2Pos - 8); context.closePath(); context.stroke(); context.restore(); } } // if it is a line segment if (lineSegment == true) { if (x1Pos == x2Pos) { // special case: vertical line, infinite gradient if (y1Pos < top) {y1Pos = top}; if (y1Pos > (top + height)) {y1Pos = top + height}; if (y2Pos < top) {y2Pos = top}; if (y2Pos > (top + height)) {y2Pos = top + height}; } else { // all of this is for truncating lines to the edge of the grid! var grad = (y2Pos - y1Pos) / (x2Pos - x1Pos); if (x1vis == -1) { if (y1vis == 1) { if ((x1Pos + (top - y1Pos) / grad) <= left) { y1Pos += grad * (left - x1Pos); x1Pos = left; } else { x1Pos += (top - y1Pos) / grad; y1Pos = top; } } if (y1vis == 0) { y1Pos += grad * (left - x1Pos); x1Pos = left; } if (y1vis == -1) { if ((x1Pos + (top + height - y1Pos) / grad) <= left) { y1Pos += grad * (left - x1Pos); x1Pos = left; } else { x1Pos += (top + height - y1Pos) / grad; y1Pos = top + height; } } } if (x1vis == 0) { if (y1vis == 1) { x1Pos += (top - y1Pos) / grad; y1Pos = top; } if (y1vis == -1) { x1Pos += (top + height - y1Pos) / grad; y1Pos = top + height; } } if (x1vis == 1) { if (y1vis == 1) { if ((x1Pos - (y1Pos - top) / grad) >= (left + width)) { y1Pos -= grad * (x1Pos - (left + width)); x1Pos = left + width; } else { x1Pos -= (y1Pos - top) / grad; y1Pos = top; } } if (y1vis == 0) { y1Pos -= grad * (x1Pos - (left + width)); x1Pos = left + width; } if (y1vis == -1) { if ((x1Pos - (y1Pos - (top + height)) / grad) >= left + width) { y1Pos -= grad * (x1Pos - (left + width)); x1Pos = left + width; } else { x1Pos -= (y1Pos - (top + height)) / grad; y1Pos = top + height; } } } if (x2vis == -1) { if (y2vis == 1) { if ((x2Pos + (top - y2Pos) / grad) <= left) { y2Pos += grad * (left - x2Pos); x2Pos = left; } else { x2Pos += (top - y2Pos) / grad; y2Pos = top; } } if (y2vis == 0) { y2Pos += grad * (left - x2Pos); x2Pos = left; } if (y2vis == -1) { if ((x2Pos + (top + height - y2Pos) / grad) <= left) { y2Pos += grad * (left - x2Pos); x2Pos = left; } else { x2Pos += (top + height - y2Pos) / grad; y2Pos = top + height; } } } if (x2vis == 0) { if (y2vis == 1) { x2Pos += (top - y2Pos) / grad; y2Pos = top; } if (y2vis == -1) { x2Pos += (top + height - y2Pos) / grad; y2Pos = top + height; } } if (x2vis == 1) { if (y2vis == 1) { if ((x2Pos - (y2Pos - top) / grad) >= (left + width)) { y2Pos -= grad * (x2Pos - (left + width)); x2Pos = left + width; } else { x2Pos -= (y2Pos - top) / grad; y2Pos = top; } } if (y2vis == 0) { y2Pos -= grad * (x2Pos - (left + width)); x2Pos = left + width; } if (y2vis == -1) { if ((x2Pos - (y2Pos - (top + height)) / grad) >= left + width) { y2Pos -= grad * (x2Pos - (left + width)); x2Pos = left + width; } else { x2Pos -= (y2Pos - (top + height)) / grad; y2Pos = top + height; } } } } } else { //infinite lines if (x1Pos == x2Pos) { // special case: vertical line, infinite gradient y1Pos = top; y2Pos = top + height; } else { /*var grad = (y2Pos - y1Pos) / (x2Pos - x1Pos); // old version var xPos = []; var yPos = []; // where does the line meet the left boundary? xPos[0] = left; yPos[0] = y1Pos + (left - x1Pos) * grad; // where does the line meet the right boundary? xPos[1] = left + width; yPos[1] = y1Pos + (left + width - x1Pos) * grad; // where does the line meet the top boundary? yPos[2] = top; xPos[2] = x1Pos - (y1Pos - top) / grad; // where does the line meet the bottom boundary? yPos[3] = top + height; xPos[3] = x1Pos - (y1Pos - (top + height)) / grad; var onEdge = [false, false, false, false]; if (yPos[0] >= top && yPos[0] <= top + height) onEdge[0] = true; if (yPos[1] >= top && yPos[1] <= top + height) onEdge[1] = true; if (xPos[2] > left && xPos[2] < left + width) onEdge[2] = true; if (xPos[3] > left && xPos[3] < left + width) onEdge[3] = true; console.log(onEdge); var done = []; var second = false; for (var i = 0; i < onEdge.length; i++) { if (onEdge[i] == true) { if (second == false) { x1Pos = xPos[i]; y1Pos = yPos[i]; second = true; } else { x2Pos = xPos[i]; y2Pos = yPos[i]; } } }*/ var intersectionPoints = []; var polygon = [[left,top],[left+width,top],[left+width,top+height],[left,top+height]]; for (var p = 0; p < 4; p++) { // check if line goes through corner points var point = polygon[p]; if (isPointOnLine(point, [x1Pos,y1Pos], [x2Pos,y2Pos])) { intersectionPoints.push(point); } } for (var p = 0; p < 4; p++) { var line = [polygon[p],polygon[(p+1)%4]]; if (intersects2(x1Pos, y1Pos, x2Pos, y2Pos, line[0][0], line[0][1], line[1][0], line[1][1]) === true) { var point = intersection(x1Pos, y1Pos, x2Pos, y2Pos, line[0][0], line[0][1], line[1][0], line[1][1]); var found = false; for (var i = 0; i < intersectionPoints.length; i++) { if (intersectionPoints[i][0] === point[0] && intersectionPoints[i][1] === point[1]) { found = true; break; } } if (found === false) intersectionPoints.push(point); } } x1Pos = intersectionPoints[0][0]; y1Pos = intersectionPoints[0][1]; x2Pos = intersectionPoints[1][0]; y2Pos = intersectionPoints[1][1]; } } if (dash == true) context.setLineDash([dashWidth,dashGapWidth]); context.strokeStyle = color; context.lineWidth = thickness*sf; if (lineSegment == true) context.lineJoin = 'round'; context.beginPath(); context.moveTo(x1Pos, y1Pos); context.lineTo(x2Pos, y2Pos); context.stroke(); context.setLineDash([]); context.restore(); return [[x1Pos,y1Pos],[x2Pos,y2Pos]]; } function calcFunc(gridDetails,funcString,opt_drawDensity,opt_domainMin,opt_domainMax) { var drawDensity = opt_drawDensity || 1; // work out points every ? canvas pixel(s) var angleMode = gridDetails.angleMode || 'deg'; var domainMin = opt_domainMin || 'none'; var domainMax = opt_domainMax || 'none'; var domainMinPassed = false; if (domainMin == 'none') {domainMinPassed == true}; var domainMaxPassed = false; var left = gridDetails.left; var top = gridDetails.top; var width = gridDetails.width; var height = gridDetails.height; var yMin = gridDetails.yMin; var yMax = gridDetails.yMax; if (angleMode == 'deg' || typeof gridDetails.xMin == 'number') { var xMin = gridDetails.xMin; var xMax = gridDetails.xMax; } else { var xMin = Math.PI*gridDetails.xMin[0]/gridDetails.xMin[1]; var xMax = Math.PI*gridDetails.xMax[0]/gridDetails.xMax[1] } if (/[\^]/.test(funcString) == true) return; var funcPoints = []; for (var xPos = 0; xPos <= width + drawDensity; xPos = xPos + drawDensity) { var x = xMin + (xPos / width) * (xMax - xMin); try { eval('var y = '+funcString+';'); } catch(err) { valid = false; return; } var yPos = (top + height) - height * ((y - yMin) / (yMax - yMin)); var thisPoint = [left+xPos, yPos, x, y] // AND: inDomain?, plotHighLowOk? if ((domainMin == 'none' || x >= domainMin) && (domainMax == 'none' || x <= domainMax)) { thisPoint.push(true); } else { thisPoint.push(false); } if (yPos < top) { thisPoint.push('high'); } else { if (yPos > (top + height)) { thisPoint.push('low'); } else { thisPoint.push('ok'); } } funcPoints.push(thisPoint); } return funcPoints; } function calcFunc2(gridDetails,func,opt_drawDensity,opt_domainMin,opt_domainMax) { var drawDensity = opt_drawDensity || 1; // work out points every ? canvas pixel(s) var angleMode = gridDetails.angleMode || 'deg'; var domainMin = opt_domainMin || 'none'; var domainMax = opt_domainMax || 'none'; var domainMinPassed = false; if (domainMin == 'none') {domainMinPassed == true}; var domainMaxPassed = false; var left = gridDetails.left; var top = gridDetails.top; var width = gridDetails.width; var height = gridDetails.height; var yMin = gridDetails.yMin; var yMax = gridDetails.yMax; if (angleMode == 'deg' || typeof gridDetails.xMin == 'number') { var xMin = gridDetails.xMin; var xMax = gridDetails.xMax; } else { var xMin = Math.PI*gridDetails.xMin[0]/gridDetails.xMin[1]; var xMax = Math.PI*gridDetails.xMax[0]/gridDetails.xMax[1] } var funcPoints = []; for (var xPos = 0; xPos <= width + drawDensity; xPos = xPos + drawDensity) { var x = xMin + (xPos / width) * (xMax - xMin); try { y = func(x); } catch(err) { valid = false; continue; } if (y === false) continue; var yPos = (top + height) - height * ((y - yMin) / (yMax - yMin)); var thisPoint = [left+xPos, yPos, x, y] // AND: inDomain?, plotHighLowOk? if ((domainMin == 'none' || x >= domainMin) && (domainMax == 'none' || x <= domainMax)) { thisPoint.push(true); } else { thisPoint.push(false); } if (yPos < top) { thisPoint.push('high'); } else { if (yPos > (top + height)) { thisPoint.push('low'); } else { thisPoint.push('ok'); } } funcPoints.push(thisPoint); } return funcPoints; } function drawFunc(context,contextLeft,contextTop,gridDetails,funcPoints,opt_color,opt_thickness,opt_asymptotes) { var top = gridDetails.top; var height = gridDetails.height; var path = [[]]; var prevPlot = 'start'; var pathNum = 0; var asymptotes = opt_asymptotes; // eg. [[90,1,-1]] - asymptote at 90, positive on left, negative on right // work out the path(s) of coordinates to be joined // this takes account of the edge of the grid and doesn't go above or below for (var pos = 0; pos < funcPoints.length; pos++) { if (funcPoints[pos][4] == false) { // if not in domain prevPlot = funcPoints[pos][5]; continue; } var currPlot = funcPoints[pos][5]; // check if an asymptote is being crossed var asymp = -1; if (typeof asymptotes == 'object' && pos > 0) { for (var i = 0; i < asymptotes.length; i++) { if (funcPoints[pos-1][2] <= asymptotes[i][0] && funcPoints[pos][2] >= asymptotes[i][0]) { asymp = i; } } } if (asymp > -1) { // if so, push top or bottom point into array if (asymptotes[asymp][1] >= 0 && prevPlot == 'ok') { path[pathNum].push([funcPoints[pos][0], top - contextTop]); } else if (asymptotes[asymp][1] < 0 && prevPlot == 'ok') { path[pathNum].push([funcPoints[pos][0], top + height - contextTop]); } // set prevPlot to high or low if (asymptotes[asymp][2] >= 0) { prevPlot = 'high'; } else { prevPlot = 'low'; } // skip to next x value continue; } if (currPlot == 'high') { if (prevPlot == 'ok') { path[pathNum].push([funcPoints[pos-1][0] - contextLeft + (funcPoints[pos][0] - funcPoints[pos-1][0]) * (funcPoints[pos-1][1] - top) / (funcPoints[pos-1][1] - funcPoints[pos][1]), top - contextTop]); } } else { if (currPlot == 'low') { if (prevPlot == 'ok') { path[pathNum].push([funcPoints[pos-1][0] - contextLeft + (funcPoints[pos][0] - funcPoints[pos-1][0]) * ((top + height) - funcPoints[pos-1][1]) / (funcPoints[pos][1] - funcPoints[pos-1][1]), top + height - contextTop]); } } else { if (prevPlot == 'ok') { path[pathNum].push([funcPoints[pos][0] - contextLeft, funcPoints[pos][1] - contextTop]); } else { if (prevPlot == 'high') { pathNum++; path[pathNum] = []; path[pathNum].push([funcPoints[pos-1][0] - contextLeft + (funcPoints[pos][0] - funcPoints[pos-1][0]) * (top - funcPoints[pos-1][1]) / (funcPoints[pos][1] - funcPoints[pos-1][1]), top - contextTop]); path[pathNum].push([funcPoints[pos][0] - contextLeft, funcPoints[pos][1] - contextTop]); } else { if (prevPlot == 'low') { pathNum++; path[pathNum] = []; path[pathNum].push([funcPoints[pos-1][0] + (funcPoints[pos][0] - funcPoints[pos-1][0]) * (funcPoints[pos-1][1] - (top + height)) / (funcPoints[pos-1][1] - funcPoints[pos][1]), top + height - contextTop]); path[pathNum].push([funcPoints[pos][0] - contextLeft, funcPoints[pos][1] - contextTop]); } else { path[pathNum].push([funcPoints[pos][0] - contextLeft, funcPoints[pos][1] - contextTop]); } } } } } prevPlot = currPlot; } // draw the path(s) context.strokeStyle = def([opt_color,gridDetails.funcColor,'#00F']); context.lineWidth = def([opt_thickness,gridDetails.funcWidth,gridDetails.lineWidth,3]); context.lineCap = 'round'; context.beginPath(); for (var pathNum = 0; pathNum < path.length; pathNum++) { if (path[pathNum].length < 2) continue; context.moveTo(path[pathNum][0][0],path[pathNum][0][1]); for (var point = 1; point < path[pathNum].length; point++) { context.lineTo(path[pathNum][point][0],path[pathNum][point][1]); } } context.stroke(); return path; } function calcFuncImplicitGridPos(gridDetails,func,increment) { function getLines(SW,SE,NW,NE) { // returns 0=S,1=W,2=N,3=E if (SW < 0) { if (SE < 0) { if (NE < 0) { if (NW < 0) { //return []; // .. // .. return []; } else { //return []; // .. // x. return [0,1]; } } else { if (NW < 0) { //return []; // .. // .x return [0,3]; } else { //return []; // .. // xx return [1,3]; } } } else { if (NE < 0) { if (NW < 0) { //return []; // .x // .. return [2,3]; } else { //return []; // .x // x. return [0,1,2,3]; } } else { if (NW < 0) { //return []; // .x // .x return [0,2]; } else { //return []; // .x // xx return [1,2]; } } } } else { if (SE < 0) { if (NE < 0) { if (NW < 0) { //return []; // x. // .. return [1,2]; } else { //return []; // x. // x. return [0,2]; } } else { if (NW < 0) { //return []; // x. // .x return [1,2,0,3]; } else { //return []; // x. // xx return [2,3]; } } } else { if (NE < 0) { if (NW < 0) { //return []; // .. // xx return [1,3]; } else { //return []; // xx // x. return [0,3]; } } else { if (NW < 0) { //return []; // xx // .x return [0,1]; } else { //return []; // xx // xx return []; } } } } } var yMin = gridDetails.yMin; var yMax = gridDetails.yMax; var xMin = gridDetails.xMin; var xMax = gridDetails.xMax; if (un(increment)) increment = Math.max(xMax-xMin,yMax-yMin)/500; var values = []; var pos = []; for (var y = yMin; y <= yMax; y += increment) { y = Math.min(yMax,y); var row = []; for (var x = xMin; x <= xMax; x += increment) { x = Math.min(xMax,x); row.push({x:x,y:y,value:func(x,y)}); if (x === xMax) break; } values.push(row); if (y === yMax) break; } for (var r = 0; r < values.length-1; r++) { for (var c = 0; c < values[r].length-1; c++) { var NW = values[r][c].value; var NE = values[r][c+1].value; var SW = values[r+1][c].value; var SE = values[r+1][c+1].value; var lines = getLines(SW,SE,NW,NE); while (lines.length > 1) { var pos2 = []; for (var i = 0; i < 2; i++) { if (lines[i] === 0) { //0=S,1=W,2=N,3=E var x = (values[r][c].x+values[r][c+1].x)/2; var y = values[r][c].y; } else if (lines[i] === 1) { var x = values[r][c].x; var y = (values[r][c].y+values[r+1][c].y)/2; } else if (lines[i] === 2) { var x = (values[r][c].x+values[r][c+1].x)/2; var y = values[r+1][c].y; } else { var x = values[r][c+1].x; var y = (values[r][c].y+values[r+1][c].y)/2; } pos2.push([x,y]); } pos.push(pos2); lines.shift(); lines.shift(); } } } return pos; } function calcFuncImplicitCanvasPos(gridDetails, funcPos) { var canvasPos = []; for (var p = 0; p < funcPos.length; p++) { var pos = funcPos[p]; canvasPos.push([getPosOfCoord(pos[0],gridDetails),getPosOfCoord(pos[1],gridDetails)]); } return canvasPos; } function drawFuncImplicit(ctx, canvasPos, color, lineWidth) { if (un(color)) color = '#00F'; if (un(lineWidth)) lineWidth = 5; ctx.save(); ctx.strokeStyle = color; ctx.lineWidth = lineWidth; ctx.beginPath(); for (var p = 0; p < canvasPos.length; p++) { var pos = canvasPos[p]; ctx.moveTo(pos[0][0],pos[0][1]); ctx.lineTo(pos[1][0],pos[1][1]); } ctx.stroke(); ctx.restore(); } function plotFunc(ctx,gridDetails,func,density,color) { function getLines(SW,SE,NW,NE) { // returns 0=S,1=W,2=N,3=E if (SW < 0) { if (SE < 0) { if (NE < 0) { if (NW < 0) { //return []; // .. // .. return []; } else { //return []; // .. // x. return [0,1]; } } else { if (NW < 0) { //return []; // .. // .x return [0,3]; } else { //return []; // .. // xx return [1,3]; } } } else { if (NE < 0) { if (NW < 0) { //return []; // .x // .. return [2,3]; } else { //return []; // .x // x. return [0,1,2,3]; } } else { if (NW < 0) { //return []; // .x // .x return [0,2]; } else { //return []; // .x // xx return [1,2]; } } } } else { if (SE < 0) { if (NE < 0) { if (NW < 0) { //return []; // x. // .. return [1,2]; } else { //return []; // x. // x. return [0,2]; } } else { if (NW < 0) { //return []; // x. // .x return [1,2,0,3]; } else { //return []; // x. // xx return [2,3]; } } } else { if (NE < 0) { if (NW < 0) { //return []; // .. // xx return [1,3]; } else { //return []; // xx // x. return [0,3]; } } else { if (NW < 0) { //return []; // xx // .x return [0,1]; } else { //return []; // xx // xx return []; } } } } } if (typeof density == 'undefined') density = 5; // density is the width of each square var angleMode = gridDetails.angleMode || 'deg'; var yMin = gridDetails.yMin; var yMax = gridDetails.yMax; if (angleMode == 'deg' || typeof gridDetails.xMin == 'number') { var xMin = gridDetails.xMin; var xMax = gridDetails.xMax; } else { var xMin = Math.PI*gridDetails.xMin[0]/gridDetails.xMin[1]; var xMax = Math.PI*gridDetails.xMax[0]/gridDetails.xMax[1] } if (typeof color == 'undefined') color = '#00F'; ctx.strokeStyle = color; ctx.beginPath(); var xInc = (xMax - xMin) / (gridDetails.width / density); var yInc = (yMax - yMin) / (gridDetails.height / density); var yPos = gridDetails.top + gridDetails.height; for (var y = yMin; y < yMax; y += yInc) { var xPos = gridDetails.left; for (var x = xMin; x < xMax; x += xInc) { var x2 = Math.min(x+xInc,xMax); var y2 = Math.min(y+yInc,yMax); var NW = func(x,y); var NE = func(x2,y); var SW = func(x,y2); var SE = func(x2,y2); var lines = getLines(SW,SE,NW,NE); while (lines.length > 1) { var pos = []; for (var i = 0; i < 2; i++) { var xPos2 = Math.min(xPos+density,gridDetails.left+gridDetails.width); var yPos2 = Math.max(yPos-density,gridDetails.top); var px = [xPos,xPos,xPos,xPos2][lines[i]]; var py = [yPos,yPos,yPos2,yPos][lines[i]]; var pf = [NW,NW,SW,NE][lines[i]]; var qx = [xPos2,xPos,xPos2,xPos2][lines[i]]; var qy = [yPos,yPos2,yPos2,yPos2][lines[i]]; var qf = [NE,SW,SE,SE][lines[i]]; pos[i] = [px,py]; if (px !== qx) pos[i][0] = px + density * ((0 - pf) / (qf - pf)); if (py !== qy) pos[i][1] = py - density * ((0 - pf) / (qf - pf)); } ctx.moveTo(pos[0][0],pos[0][1]); ctx.lineTo(pos[1][0],pos[1][1]); lines.shift(); lines.shift(); } xPos += density; } yPos -= density; } ctx.stroke(); } function slowPlotFunc(context,contextLeft,contextTop,gridDetails,funcPoints,plotTime,opt_color,opt_thickness) { var top = gridDetails.top; var height = gridDetails.height; var prevPlot = 'start'; var path = [[]]; var pathNum = 0; // work out the path(s) of coordinates to be joined // this takes account of the edge of the grid and doesn't go above or below for (var pos = 0; pos < funcPoints.length; pos++) { if (funcPoints[pos][4] == false) { // if not in domain prevPlot = funcPoints[pos][5]; continue; } var currPlot = funcPoints[pos][5]; if (currPlot == 'high') { if (prevPlot == 'ok') { path[pathNum].push([funcPoints[pos-1][0] - contextLeft + (funcPoints[pos][0] - funcPoints[pos-1][0]) * (funcPoints[pos-1][1] - top) / (funcPoints[pos-1][1] - funcPoints[pos][1]), top - contextTop]); } } else { if (currPlot == 'low') { if (prevPlot == 'ok') { path[pathNum].push([funcPoints[pos-1][0] - contextLeft + (funcPoints[pos][0] - funcPoints[pos-1][0]) * ((top + height) - funcPoints[pos-1][1]) / (funcPoints[pos][1] - funcPoints[pos-1][1]), top + height - contextTop]); } } else { if (prevPlot == 'ok') { path[pathNum].push([funcPoints[pos][0] - contextLeft, funcPoints[pos][1] - contextTop]); } else { if (prevPlot == 'high') { if (pathNum > 0) { pathNum++; path[pathNum] = []; } path[pathNum].push([funcPoints[pos-1][0] - contextLeft + (funcPoints[pos][0] - funcPoints[pos-1][0]) * (top - funcPoints[pos-1][1]) / (funcPoints[pos][1] - funcPoints[pos-1][1]), top - contextTop]); path[pathNum].push([funcPoints[pos][0] - contextLeft, funcPoints[pos][1] - contextTop]); } else { if (prevPlot == 'low') { if (pathNum > 0) { pathNum++; path[pathNum] = []; } path[pathNum].push([funcPoints[pos-1][0] + (funcPoints[pos][0] - funcPoints[pos-1][0]) * (funcPoints[pos-1][1] - (top + height)) / (funcPoints[pos-1][1] - funcPoints[pos][1]), top + height - contextTop]); path[pathNum].push([funcPoints[pos][0] - contextLeft, funcPoints[pos][1] - contextTop]); } else { path[pathNum].push([funcPoints[pos][0] - contextLeft, funcPoints[pos][1] - contextTop]); } } } } } prevPlot = currPlot; } // draw the path(s) context.strokeStyle = opt_color || '#00F'; context.lineWidth = opt_thickness || 3; context.lineCap = 'round'; context.beginPath(); var numOfFrames = 0; for (var pathNum = 0; pathNum < path.length; pathNum++) { numOfFrames += path[pathNum].length; } var frameTime = plotTime / numOfFrames; var plotPath = path; var plotPathNum = 0; var plotPointNum = 0; var slowPlotInterval = setCorrectingInterval(function() { if (plotPointNum == 0) { context.beginPath(); context.moveTo(plotPath[plotPathNum][plotPointNum][0],plotPath[plotPathNum][plotPointNum][1]); } else { context.lineTo(plotPath[plotPathNum][plotPointNum][0],plotPath[plotPathNum][plotPointNum][1]); context.stroke(); } if (plotPointNum == plotPath[plotPathNum].length - 1) { if (plotPathNum == plotPath.length - 1) { clearCorrectingInterval(slowPlotInterval); } else { plotPathNum++; plotPointNum = 0; } } else { plotPointNum++; } }, frameTime); } function convertToRadians(funcString) { // for a trig function in degrees, /180*Math.Pi // find instances of trig functions for (var char = 0; char < funcString.length; char++) { if (funcString.slice(char).indexOf('Math.sin(') == 0 || funcString.slice(char).indexOf('Math.cos(') == 0 || funcString.slice(char).indexOf('Math.tan(') == 0) { var bracketCount = 1; var charCount = 9; do { if (funcString.slice(char).charAt(charCount) == '(') bracketCount++; if (funcString.slice(char).charAt(charCount) == ')') bracketCount--; charCount++; } while (bracketCount > 0 && char + charCount <= funcString.length) var inner = funcString.slice(char).slice(9, charCount - 1); if (inner.indexOf('Math.PI') == -1) { inner = "(" + inner + ")*Math.PI/180"; funcString = funcString.slice(0,char+9) + inner + funcString.slice(char+charCount-1); } } // INVERSE TRIG FUNCTIONS NOT DONE YET!! } return funcString } function calcAreaUnderFunc(gridDetails,funcString,min,max,opt_drawDensity) { if (typeof min !== 'number') min = gridDetails.xMin; if (typeof max !== 'number') max = gridDetails.xMax; var drawDensity = opt_drawDensity || 1; // work out points every ? canvas pixel(s) var minPassed = false; if (min <= gridDetails.min) {minPassed == true}; var maxPassed = false; var left = gridDetails.left; var top = gridDetails.top; var width = gridDetails.width; var height = gridDetails.height; var xMin = gridDetails.xMin; var xMax = gridDetails.xMax; var yMin = gridDetails.yMin; var yMax = gridDetails.yMax; if (/[\^]/.test(funcString) == true) return; var funcPoints = [[getPosOfCoordX2(min,left,width,xMin,xMax),getPosOfCoordY2(0,top,height,yMin,yMax)]]; for (var xPos = getPosOfCoordX2(min,left,width,xMin,xMax)-left; xPos <= getPosOfCoordX2(max,left,width,xMin,xMax)-left; xPos = xPos + drawDensity) { var x = xMin + (xPos / width) * (xMax - xMin); try { eval('var y = '+funcString+';'); } catch(err) { valid = false; return; } var yPos = Math.min(Math.max((top + height) - height * ((y - yMin) / (yMax - yMin)), top), top + height); funcPoints.push([left+xPos, yPos]); } funcPoints.push( [getPosOfCoordX2(max,left,width,xMin,xMax),getPosOfCoordY2(0,top,height,yMin,yMax)], [getPosOfCoordX2(min,left,width,xMin,xMax),getPosOfCoordY2(0,top,height,yMin,yMax)] ); return funcPoints; } function drawFuncArea(context,contextLeft,contextTop,gridDetails,funcAreaPoints,boundaryLines,opt_color,opt_opacity) { var boundaryLines = boolean(boundaryLines,true); var color = opt_color || '#00F'; var opacity = opt_opacity || 0.5; var colorRGB = hexToRgb(color); context.save(); context.fillStyle = "rgba("+colorRGB.r+","+colorRGB.g+","+colorRGB.b+","+opacity+")"; context.beginPath(); context.moveTo(funcAreaPoints[0][0],funcAreaPoints[0][1]); for (var i = 0; i < funcAreaPoints.length; i++) { context.lineTo(funcAreaPoints[i][0],funcAreaPoints[i][1]); } context.fill(); if (boundaryLines = true) { context.beginPath(); context.strokeStyle = color; context.lineWidth = 3; context.moveTo(funcAreaPoints[0][0],funcAreaPoints[0][1]); context.lineTo(funcAreaPoints[1][0],funcAreaPoints[1][1]); context.moveTo(funcAreaPoints[funcAreaPoints.length-3][0],funcAreaPoints[funcAreaPoints.length-3][1]); context.lineTo(funcAreaPoints[funcAreaPoints.length-2][0],funcAreaPoints[funcAreaPoints.length-2][1]); context.stroke(); } context.restore(); } function convertCoordsGridToCanvas(contextLeft,contextTop,gridDetails,pointsArray) { var left = gridDetails.left - contextLeft; // use dimensions relative to background canvas (1200x700) var top = gridDetails.top - contextTop; // rather than the context supplied var width = gridDetails.width; var height = gridDetails.height; var xMin = gridDetails.xMin; var xMax = gridDetails.xMax; var yMin = gridDetails.yMin; var yMax = gridDetails.yMax; var returnArray = []; for (var i = 0; i < pointsArray.length; i++) { returnArray[i] = [getPosOfCoordX2(pointsArray[i][0],left,width,xMin,xMax),getPosOfCoordY2(pointsArray[i][1],top,height,yMin,yMax)]; } return returnArray; } function drawSmoothCurve(ctx,gridDetails,pointsArray,tension,numOfPoints,forceUpwards) { var gridPoints = convertCoordsGridToCanvas(0,0,gridDetails,pointsArray); var pts = getSmoothCurvePoints(gridPoints,tension,false,numOfPoints,forceUpwards); ctx.beginPath(); ctx.moveTo(pts[0], pts[1]); for (i = 2; i < pts.length - 1; i += 2) { ctx.lineTo(pts[i], pts[i+1]); } ctx.stroke(); } function getSmoothCurvePoints(pts, tension, isClosed, numOfSegments, forceUpwards) { // flatten pts array var pts2 = []; for (var i = 0; i < pts.length; i++) { for (var j = 0; j < pts[i].length; j++) { pts2.push(pts[i][j]); } } // use input value if provided, or use a default value tension = (typeof tension != 'undefined') ? tension : 0.5; isClosed = isClosed ? isClosed : false; numOfSegments = numOfSegments ? numOfSegments : 16; forceUpwards = boolean(forceUpwards,false); var _pts = [], res = [], // clone array x, y, // our x,y coords t1x, t2x, t1y, t2y, // tension vectors c1, c2, c3, c4, // cardinal points st, t, i; // steps based on num. of segments // clone array so we don't change the original // _pts = pts2.slice(0); // The algorithm require a previous and next point to the actual point array. // Check if we will draw closed or open curve. // If closed, copy end points to beginning and first points to end // If open, duplicate first points to befinning, end points to end if (isClosed) { _pts.unshift(pts2[pts2.length - 1]); _pts.unshift(pts2[pts2.length - 2]); _pts.unshift(pts2[pts2.length - 1]); _pts.unshift(pts2[pts2.length - 2]); _pts.push(pts2[0]); _pts.push(pts2[1]); } else { _pts.unshift(pts2[1]); //copy 1. point and insert at beginning _pts.unshift(pts2[0]); _pts.push(pts2[pts2.length - 2]); //copy last point and append _pts.push(pts2[pts2.length - 1]); } // ok, lets start.. // 1. loop goes through point array // 2. loop goes through each segment between the 2 pts + 1e point before and after for (i=2; i < (_pts.length - 4); i+=2) { for (t=0; t <= numOfSegments; t++) { // calc tension vectors t1x = (_pts[i+2] - _pts[i-2]) * tension; t2x = (_pts[i+4] - _pts[i]) * tension; t1y = (_pts[i+3] - _pts[i-1]) * tension; t2y = (_pts[i+5] - _pts[i+1]) * tension; // calc step st = t / numOfSegments; // calc cardinals c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1; c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2); c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st; c4 = Math.pow(st, 3) - Math.pow(st, 2); // calc x and y cords with common control vectors x = c1 * _pts[i] + c2 * _pts[i+2] + c3 * t1x + c4 * t2x; y = c1 * _pts[i+1] + c2 * _pts[i+3] + c3 * t1y + c4 * t2y; if (forceUpwards == true) { if ((y < _pts[i+1] && y < _pts[i+3]) || (y > _pts[i+1] && y > _pts[i+3])) { y = (_pts[i+1] + _pts[i+3]) / 2; } if ((x < _pts[i] && x < _pts[i+2]) || (x > _pts[i] && x > _pts[i+2])) { x = (_pts[i] + _pts[i+2]) / 2; } } //store points in array res.push(x); res.push(y); } } return res; } function drawBoxPlot(ctx,contextLeft,contextTop,gridDetails,data,yMiddle,yHeight,options) { var left = gridDetails.left - contextLeft; // use dimensions relative to background canvas (1200x700) var top = gridDetails.top - contextTop; // rather than the context supplied if (typeof options == 'undefined') options = {}; ctx.save(); ctx.strokeStyle = options.color || '#000'; ctx.lineWidth = options.lineWidth || 4; ctx.lineJoin = 'round'; ctx.lineCap = 'round'; if (typeof ctx.setLineDash == 'undefined') ctx.setLineDash = function(){}; ctx.setLineDash([]); var xPos = []; for (var i = 0; i < data.length; i++) { xPos[i] = getPosOfCoordX2(data[i],left,gridDetails.width,gridDetails.xMin,gridDetails.xMax); } var yPos = [ getPosOfCoordY2(yMiddle - 0.5 * yHeight,top,gridDetails.height,gridDetails.yMin,gridDetails.yMax), getPosOfCoordY2(yMiddle - 0.3 * yHeight,top,gridDetails.height,gridDetails.yMin,gridDetails.yMax), getPosOfCoordY2(yMiddle,top,gridDetails.height,gridDetails.yMin,gridDetails.yMax), getPosOfCoordY2(yMiddle + 0.3 * yHeight,top,gridDetails.height,gridDetails.yMin,gridDetails.yMax), getPosOfCoordY2(yMiddle + 0.5 * yHeight,top,gridDetails.height,gridDetails.yMin,gridDetails.yMax) ]; // fill if (boolean(options.rangeOnly,false) == false && boolean(options.dashesOnly,false) == false && boolean(options.fill,false) == true) { ctx.fillStyle = options.fillColor || '#FCF'; ctx.fillRect(xPos[1],yPos[0],xPos[3]-xPos[1],yPos[4]-yPos[0]); } ctx.beginPath(); // horizLines if (boolean(options.dashesOnly,false) == false) { if (boolean(options.rangeOnly,false) == true) { ctx.moveTo(xPos[0],yPos[2]); ctx.lineTo(xPos[4],yPos[2]); } else { ctx.moveTo(xPos[0],yPos[2]); ctx.lineTo(xPos[1],yPos[2]); ctx.moveTo(xPos[1],yPos[0]); ctx.lineTo(xPos[3],yPos[0]); ctx.moveTo(xPos[1],yPos[4]); ctx.lineTo(xPos[3],yPos[4]); ctx.moveTo(xPos[3],yPos[2]); ctx.lineTo(xPos[4],yPos[2]); } } // vertLines if (boolean(options.rangeOnly,false) == true) { ctx.moveTo(xPos[0],yPos[1]); ctx.lineTo(xPos[0],yPos[3]); ctx.moveTo(xPos[4],yPos[1]); ctx.lineTo(xPos[4],yPos[3]); ctx.stroke(); } else { ctx.moveTo(xPos[0],yPos[1]); ctx.lineTo(xPos[0],yPos[3]); ctx.moveTo(xPos[1],yPos[0]); ctx.lineTo(xPos[1],yPos[4]); ctx.moveTo(xPos[3],yPos[0]); ctx.lineTo(xPos[3],yPos[4]); ctx.moveTo(xPos[4],yPos[1]); ctx.lineTo(xPos[4],yPos[3]); ctx.stroke(); ctx.setLineDash([8,5]); ctx.lineWidth = 0.6 * ctx.lineWidth; ctx.beginPath(); ctx.moveTo(xPos[2],yPos[0]); ctx.lineTo(xPos[2],yPos[4]); ctx.stroke(); } ctx.restore(); }