lkarch.org/tools/i2/_grid2.js
2022-11-03 19:43:40 -04:00

2576 lines
89 KiB
JavaScript

// 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:['<<fontSize:'+labelFontSize+'>>'].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:['<<fontSize:'+labelFontSize+'>>'].concat(gridDetails.axisLabels[0].text),rect:xLabelRect,align:[0,0]});
var yDist = gridDetails.axisLabels[1].dist || 50;
var measureTextY = text({ctx:ctx2,text:['<<fontSize:'+labelFontSize+'>>'].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:['<<fontSize:'+labelFontSize+'>>'].concat(gridDetails.axisLabels[1].text),rect:[0,0,measureTextY[2],measureTextY[3]],align:[0,0]});
context.restore();
} else {
yAxisLabel2 = yAxisLabel.slice(0);
yAxisLabel2.unshift('<<font:algebra>><<fontSize:'+labelFontSize+'>><<align:center>><<color:'+yAxisColor+'>>');
/*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('<<font:algebra>><<fontSize:'+labelFontSize+'>><<align:left>><<color:'+xAxisColor+'>>');
/*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:['<<fontSize:28>>Time (s)']},{text:['<<fontSize:28>>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:['<<fontSize:28>>Time (s)']},{text:['<<fontSize:28>>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:['<<fontSize:28>>Weight (kg)']},{text:['<<fontSize:28>>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:['<<fontSize:28>>Time (days)']},{text:['<<fontSize:28>>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:['<<fontSize:28>>Time (days)']},{text:['<<fontSize:28>>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:['<<fontSize:24>><<italic:true>>'+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:['<<color:'+xColor+'>>'+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('<<color:'+xColor+'>>');
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:['<<color:'+xColor+'>>'+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('<<color:'+yColor+'>>');
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();
}