//------------------------------------------------------------------------------ // substitution isFinite() for old version of JS function _isFinite(value) { if(isNaN(value)) return false; if(value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY) return false; return true; } //------------------------------------------------------------------------------ function round(value, decimals) { return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals); }; //------------------------------------------------------------------------------ function round1(value) { if (value !== 0) exponent = Math.floor(log10(Math.abs(value))); else return 0; if (exponent < 0) return round(value, Math.abs(exponent)); else return round(value, 0); }; //------------------------------------------------------------------------------ function round2(value, step) { if (value !== 0) exponent = Math.floor(log10(Math.abs(step))); else return 0; if (exponent < 0) return round(value, Math.abs(exponent)); else return round(value, 0); }; //------------------------------------------------------------------------------ function formatFloat(x, num_dig, isScientific) { return (isScientific ? x.toExponential(num_dig) : round(x, num_dig)).toString(); }; //------------------------------------------------------------------------------ function getColor(index) { var Color = ["aqua", "black", "blue", "fuchsia", "green", "gray", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "white", "yellow", "#00B4FF", "#FFA200", "#C89600", "#00E1FF", "#F000FF" ]; if (index < 0) return Color[0]; if (index < Color.length) return Color[index]; else return Color[index % Color.length]; }; //------------------------------------------------------------------------------ function getFillColor(index) { var Color = [ "rgb(25,20,151)", "rgb(6,50,173)", "rgb(11,69,187)", "rgb(12,93,197)", "rgb(11,110,199)", "rgb(14,140,204)", "rgb(4,153,173)", "rgb(10,179,150)", "rgb(21,176,121)", "rgb(6,175,78)", "rgb(92,191,20)", "rgb(185,212,15)", "rgb(220,222,2)", "rgb(245,212,2)", "rgb(251,201,11)", "rgb(231,176,18)", "rgb(250,157,16)", "rgb(244,122,4)", "rgb(247,108,0)", "rgb(244,83,2)", "rgb(248,54,36)", "rgb(240,33,55)", "rgb(232,11,62)", "rgb(216,8,52)" ]; if (index < 0) return Color[0]; if (index < Color.length) return Color[index]; else return Color[index % Color.length]; }; //------------------------------------------------------------------------------ function getBasicColor(index) { var Color = [ "rgb(0,0,0)", "rgb(255,0,0)", "rgb(0,0,255)", "rgb(0,255,0)", "rgb(255,0,255)", "rgb(0,255,255)", "rgb(128,0,0)", "rgb(0,128,0)", "rgb(0,0,128)", "rgb(0,128,128)", "rgb(128,0,128)", "rgb(128,128,128)" ]; if (index < 0) return Color[0]; if (index < Color.length) return Color[index]; else return Color[index % Color.length]; }; //------------------------------------------------------------------------------ function drawStar(cx, cy, spikes, outerRadius, innerRadius, ctx) { var rot = Math.PI / 2 * 3; var x = cx; var y = cy; var step = Math.PI / spikes; var i; ctx.strokeSyle = "#000"; ctx.beginPath(); ctx.moveTo(cx, cy - outerRadius); for (i = 0; i < spikes; i++) { x = cx + Math.cos(rot) * outerRadius; y = cy + Math.sin(rot) * outerRadius; ctx.lineTo(x, y); rot += step; x = cx + Math.cos(rot) * innerRadius; y = cy + Math.sin(rot) * innerRadius; ctx.lineTo(x, y); rot += step; } ctx.lineTo(cx, cy - outerRadius); ctx.closePath(); ctx.stroke(); }; //------------------------------------------------------------------------------ function polygon(ctx, x, y, radius, sides, startAngle, anticlockwise) { if (sides < 3) return; var a = (Math.PI * 2) / sides; a = anticlockwise ? -a : a; ctx.save(); ctx.translate(x, y); ctx.rotate(startAngle); ctx.beginPath(); ctx.moveTo(radius, 0); for (var i = 1; i < sides; i++) { ctx.lineTo(radius * Math.cos(a * i), radius * Math.sin(a * i)); } ctx.closePath(); ctx.restore(); ctx.stroke(); }; //------------------------------------------------------------------------------ function drawLine(x1, y1, x2, y2, context, strokeStyle, lineWidth, lineStyle) { if(lineStyle === undefined) lineStyle = "solid"; context.beginPath(); context.strokeStyle = strokeStyle; context.lineWidth = lineWidth; switch(lineStyle) { default: case 'solid': context.setLineDash([]); break; case 'dashed': context.setLineDash([4, 4]); break; case 'long_dashed': context.setLineDash([6, 3]); break; case 'dash_dotted': context.setLineDash([4, 2, 2, 2]); break; case 'dotted': context.setLineDash([2, 2]); break; } x1 = Math.floor(x1) + 0.5; x2 = Math.floor(x2) + 0.5; y1 = Math.floor(y1) + 0.5; y2 = Math.floor(y2) + 0.5; context.moveTo(x1, y1); context.lineTo(x2, y2); context.stroke(); context.setLineDash([]); }; //------------------------------------------------------------------------------ // * @param x, y the arraies of data points // * @param context - canvas for drawing // * @param strokeStyle - line color // * @param lineWidth - line width // * @param lineStyle - define the line style: solid, dashed, long_dashed, dash_dotted, dotted function drawLineByArray(x, y, context, strokeStyle, lineWidth, lineStyle) { if(lineStyle === undefined) lineStyle = 'solid'; // set properties for drawing switch(lineStyle) { default: case 'solid': context.setLineDash([]); break; case 'dashed': context.setLineDash([4, 4]); break; case 'long_dashed': context.setLineDash([6, 3]); break; case 'dash_dotted': context.setLineDash([4, 2, 2, 2]); break; case 'dotted': context.setLineDash([2, 2]); break; } context.strokeStyle = strokeStyle; context.lineWidth = lineWidth; // fill the path and stroke it context.beginPath(); context.moveTo(Math.floor(x[0])+0.5, Math.floor(y[0])+0.5); for(i = 1; i < x.length; i++ ) context.lineTo(Math.floor(x[i])+0.5, Math.floor(y[i])+0.5); context.stroke(); context.setLineDash([]);// restore to solid line style }; //------------------------------------------------------------------------------ function drawHorizontalDashedLine(xx0, xx, yy0, dashLength, context, strokeStyle, lineWidth) { n = 0; if (xx0 > xx) { tmp = xx0; xx0 = xx; xx = tmp; } while (true) { x1 = xx0 + dashLength * n; if (x1 > xx) break; x2 = xx0 + dashLength * (n + 1); if (x2 > xx) x2 = xx; drawLine(x1, yy0, x2, yy0, context, strokeStyle, lineWidth); n += 2; } }; //--------------------------------------------------------------------------- function drawVerticalDashedLine(xx0, yy0, yy, dashLength, context, strokeStyle, lineWidth) { n = 0; if (yy0 > yy) { tmp = yy0; yy0 = yy; yy = tmp; } while (true) { y1 = yy0 + dashLength * n; if (y1 > yy) break; y2 = yy0 + dashLength * (n + 1); if (y2 > yy) y2 = yy; drawLine(xx0, y1, xx0, y2, context, strokeStyle, lineWidth); n += 2; } }; //--------------------------------------------------------------------------- //Draws dashed line //You can use this as //context.dashedLine(0, 0, 200, 200, 4); CanvasRenderingContext2D.prototype.dashedLine = function (x1, y1, x2, y2, dashLen) { if (dashLen === undefined) dashLen = 2; x1 = Math.floor(x1) + 0.5; x2 = Math.floor(x2) + 0.5; y1 = Math.floor(y1) + 0.5; y2 = Math.floor(y2) + 0.5; //this['beginPath'](); //this.beginPath(); this.moveTo(x1, y1); var dX = x2 - x1; var dY = y2 - y1; var dashes = Math.floor(Math.sqrt(dX * dX + dY * dY) / dashLen); var dashX = dX / dashes; var dashY = dY / dashes; var q = 0; while (q++ < dashes) { //this['beginPath'](); x1 += dashX; y1 += dashY; //if (q % 2=== 0) this['beginPath'](); //if (q % 2=== 0) this.beginPath(); this[q % 2 === 0 ? 'moveTo' : 'lineTo'](x1, y1); // this[q % 2 === 0 ? 'moveTo' : 'lineTo'](Math.floor(x1)+0.5, Math.floor(y1)+0.5); } //if (q % 2=== 0) this['beginPath'](); //if (q % 2=== 0) this.beginPath(); this[q % 2 === 0 ? 'moveTo' : 'lineTo'](x2, y2); //beginPath(); // this[q % 2 === 0 ? 'moveTo' : 'lineTo'](Math.floor(x2)+0.5, Math.floor(y2)+0.5); }; //------------------------------------------------------------------------------ //Creates multidimensional array //You can use this as var myArray=createArray(5,4); function createArray(length) { var arr = new Array(length || 0), i = length; if (arguments.length > 1) { var args = Array.prototype.slice.call(arguments, 1); while (i--) arr[length - 1 - i] = createArray.apply(this, args); } return arr; }; //------------------------------------------------------------------------------ var ln10 = Math.log(10); //------------------------------------------------------------------------------ var log10 = function (x) { return Math.log(x) / ln10; }; //------------------------------------------------------------------------------ var calcStepSize = function (range, targetSteps) { // calculate an initial guess at step size var tempStep = range / targetSteps; //alert(tempStep); //alert(range); // get the magnitude of the step size var mag = Math.floor(Math.log(tempStep) / ln10); var magPow = Math.pow(10, mag); // calculate most significant digit of the new step size var magMsd = Math.round(tempStep / magPow + 0.5); // promote the MSD to either 1, 2, or 5 if (magMsd > 5.0) magMsd = 10.0; else if (magMsd > 2.0) magMsd = 5.0; else if (magMsd > 1.0) magMsd = 2.0; //console.log(magMsd); //{let ss=0;} return magMsd * magPow; }; //------------------------------------------------------------------------------ function NiceScale(min, max, type) { this.minPoint; this.maxPoint; this.maxTicks = 8; this.tickSpacing; this.range; this.niceMin; this.niceMax; var self = this; //------------------------------------------------------------------ // default actions this.minPoint = min; this.maxPoint = max; calculate(); //------------------------------------------------------------------ /** * Returns a "nice" number approximately equal to range Rounds * the number if round = true Takes the ceiling if round = false. * * @param range the data range * @param round whether to round the result * @return a "nice" number to be used for the data range */ function niceNum(range, round) { this.exponent; /** exponent of range */ this.fraction; /** fractional part of range */ this.niceFraction; /** nice, rounded fraction */ this.exponent = Math.floor(log10(range)); this.fraction = range / Math.pow(10, this.exponent); if(type)// angular axis [0;180] deg { if(round) { if(this.fraction < 2) this.niceFraction = 1.5; //this.niceFraction = 1; else if(this.fraction < 4) this.niceFraction = 3; else if(this.fraction < 7) this.niceFraction = 6; else this.niceFraction = 9; } else { if(this.fraction <= 2) this.niceFraction = 1.5; //this.niceFraction = 1; else if(this.fraction <= 4) this.niceFraction = 3; else if(this.fraction <= 7) this.niceFraction = 6; else this.niceFraction = 9; } } else { if(round) { if(this.fraction < 1.5) this.niceFraction = 1; else if(this.fraction < 3) this.niceFraction = 2; else if(this.fraction < 5) this.niceFraction = 4; else if(this.fraction < 7) this.niceFraction = 5; else this.niceFraction = 10; } else { if(this.fraction <= 1) this.niceFraction = 1; else if(this.fraction <= 2) this.niceFraction = 2; else if(this.fraction <= 4) this.niceFraction = 4; else if(this.fraction <= 5) this.niceFraction = 5; else this.niceFraction = 10; } } return this.niceFraction * Math.pow(10, this.exponent); }; //------------------------------------------------------------------ /** * Calculate and update values for tick spacing and nice * minimum and maximum data points on the axis. */ function calculate() { self.range = niceNum(self.maxPoint - self.minPoint, false); self.tickSpacing = niceNum(self.range / (self.maxTicks - 1), true); self.niceMin = Math.floor(self.minPoint / self.tickSpacing) * self.tickSpacing; self.niceMax = Math.ceil(self.maxPoint / self.tickSpacing) * self.tickSpacing; // the case when firs or last points coincide with min or max tics if(Math.abs(self.niceMin-self.minPoint) < 1.e-5) { if(Math.abs(self.minPoint) > 1.e-6) // if data starts with 0, left min tic at 0 self.niceMin -= self.tickSpacing; } if(Math.abs(self.niceMax-self.maxPoint) < 1.e-5) self.niceMax += self.tickSpacing; if(type)// axis corresponds to the angular variable [0;180] deg { if (self.niceMin < 0) self.niceMin = 0; if (self.niceMax > 180) self.niceMax = 180; } }; //------------------------------------------------------------------ /** * Sets the minimum and maximum data points for the axis. * * @param minPoint the minimum data point on the axis * @param maxPoint the maximum data point on the axis */ this.setMinMaxPoints = function (minPoint, maxPoint) { this.minPoint = minPoint; this.maxPoint = maxPoint; calculate(); }; //------------------------------------------------------------------ /** * Sets maximum number of tick marks we're comfortable with * * @param maxTicks the maximum number of tick marks for the axis */ this.setMaxTicks = function (maxTicks) { this.maxTicks = maxTicks; calculate(); }; }; //------------------------------------------------------------------------------ /** * Creates data object for drawing on Chart * * @param nPointsP the number of data points * @param nColumnsP the number of array columns (2 or 4); if nColumnsP=2, errors are not drawn * @param aaP the name of data array * @param colorP the color of symbol interior and/or line * @param symbolP the symbol type; if symbolP=0, symbols are not drawn; symbolP=1,2...9 correspond to different symbol types: 1 - circle,2 - square, 3 - diamond, 4 - star, 5 - triangle up, 6 - triangle down, 7 - triangle left, 8 - triangle right, 9 - pentagon * @param symbolSizeP the size (diameter) of symbol * @param lineP the thickness of line; if lineP=0, line is not drawn * @param errorLineWidthP the thickness of error bars * @param legendStrP the legend string shown on the right * @param isDataLogScaleP if true, data is plotted in logarithmic scale * @param lineStyleP line style for drawing - solid, dashed and so on * @param putLegendNearCurveP the legend will be repeated near the last point */ function ChartData(nPointsP, nColumnsP, aaP, colorP, symbolP, symbolSizeP, lineP, errorLineWidthP, legendStrP, isDataLogScaleP, lineStyleP, putLegendNearCurveP, skipForLimitsP) { this.nPoints = nPointsP; this.nColumns = nColumnsP; this.aa; this.color = colorP; this.symbol = symbolP; this.symbolSize = symbolSizeP; this.line = lineP; this.errorLineWidth = errorLineWidthP; this.aaLog = createArray(nPointsP, nColumnsP); this.aaLin = createArray(nPointsP, nColumnsP); this.isLogScaleAvailable = true; this.legendStr = legendStrP; this.isDataLogScale = isDataLogScaleP; this.isMinMax = true; this.isVisible = true; if(lineStyleP === undefined) this.lineStyle = "solid"; else this.lineStyle = lineStyleP; this.putLegendNearCurve = (putLegendNearCurveP === undefined)?false:putLegendNearCurveP; this.skipForLimits = (skipForLimitsP === undefined)?false:skipForLimitsP; for (i = 0; i < nPointsP; i++) { for (j = 0; j < nColumnsP; j++) { this.aaLin[i][j] = aaP[i][j]; } } if (this.isDataLogScale) { for (i = 0; i < nPointsP; i++) { if (this.aaLin[i][1] > 0) this.aaLog[i][1] = Math.log(this.aaLin[i][1]) / ln10; else { this.isLogScaleAvailable = false; this.aaLog[i][1] = Number.NaN; console.log("Logarithmic scale: some points will not be shown since their values are negative."); return; } this.aaLog[i][0] = this.aaLin[i][0]; // error bars check if (nColumnsP > 2) { if(this.aaLog[i][1] != Number.NaN) { this.aaLog[i][2] = Math.log(this.aaLin[i][1] + this.aaLin[i][2]) / ln10 - Math.log(this.aaLin[i][1]) / ln10; if (this.aaLin[i][1] - this.aaLin[i][3] > 0) this.aaLog[i][3] = Math.log(this.aaLin[i][1]) / ln10 - Math.log(this.aaLin[i][1] - this.aaLin[i][3]) / ln10; else this.aaLog[i][3] = Number.NaN; } else { this.aaLog[i][2] = Number.NaN; this.aaLog[i][3] = Number.NaN; } } } } this.toShow = function (show) { if(show == null) skip = true; this.isVisible = show; }; this.toSkipForLimits = function(skip) { if(skip == null) skip = true; this.skipForLimits = skip; }; this.synchronize = function(target) { target.nPoints = this.nPoints; target.nColumns = this.nColumns; target.color = this.color; target.symbol = this.symbol; target.symbolSize = this.symbolSize; target.line = this.line; target.errorLineWidth = this.errorLineWidth; target.isLogScaleAvailable = this.isLogScaleAvailable; target.legendStr = this.legendStr; target.isDataLogScale = this.isDataLogScale; target.isMinMax = this.isMinMax; target.isVisible = this.isVisible; target.lineStyle = this.lineStyle; target.putLegendNearCurve = this.putLegendNearCurve; target.skipForLimits = this.skipForLimits; target.aaLog = createArray(this.nPoints, this.nColumns); target.aaLin = createArray(this.nPoints, this.nColumns); for (i = 0; i < this.nPoints; i++) { for (j = 0; j < this.nColumns; j++) { target.aaLin[i][j] = this.aaLin[i][j]; target.aaLog[i][j] = this.aaLog[i][j]; } } }; }; //------------------------------------------------------------------------------ /** * Creates chart object for drawing data * * @param canvasName the name of canvas * @param tickLabelFontP the font of tick labels * @param tickLabelColorP the color of tick labels * @param axisLabelFontP the font of axis labels * @param axisLabelColorP the color of axis labels * @param xAxisLabelP the X-axis label string * @param yAxisLabelP the Y-axis label string * @param lineWidthP the thickness of axis lines * @param drawGridP if true, gridlines are drawn * @param gridColorP the color of gridlines * @param bgColorP the color of background * @param majorTickLengthP the length of major ticks; the length of minor ticks is majorTickLengthP/2 * @param isLogScaleP if true, Y-axis is plotted in logarithmic scale * @param isTopP if true, Y-axis label is shown on top * @param isRightP if true, the legend is shown */ function Chart(canvasNameP, tickLabelFontP, tickLabelColorP, axisLabelFontP, axisLabelColorP, xAxisLabelP, yAxisLabelP, lineWidthP, drawGridP, gridColorP, bgColorP, majorTickLengthP, isLogScaleP, isTopP, isRightP) { var current_document = document; var chartThis = this; var canvasName = canvasNameP; var canvas = current_document.getElementById(canvasNameP); var context = canvas.getContext('2d'); var lineLegend = 30; this.arrayOfData = new Array(); //----------------------------------------------------------------------------- // create two fonts for tic label and legend: for main text(100%) and for indexes(80%) this.tickLabelFont; this.tickLabelFont_Index; this.getTickLabelFont = function () { return tickLabelFont; }; this.setTickLabelFont = function (newVal) { this.tickLabelFont = newVal; var size = 10, findex = "", ftext = newVal.split(' '); for(i=0;i 180) X1 = 180; if (this.xAxisType && X2 > 180) X2 = 180; this.setXYLimits(Math.min(X1, X2), Math.max(X1, X2), Math.min(Y1, Y2), Math.max(Y1, Y2)); this.draw(); }; this.isFirstRun = true; this.restoreInitialScale = function () { //alert("restore initial"+this.xMin0); this.setXYLimits(this.xMin0, this.xMax0, this.yMin0, this.yMax0); this.areXYLimitsSet = false; this.draw(); }; this.restorePreviousScale = function () { //alert("restore previous"+this.xMinZ); this.setXYLimits(this.xMinZ, this.xMaxZ, this.yMinZ, this.yMaxZ); //alert("restore previous"+this.xmin); this.areXYLimitsSet = true; this.draw(); }; var xmin, xmax, ymin, ymax; var niceXmin, niceXmax, niceYmin, niceYmax; var xtickSpacing, ytickSpacing; var ymaxSet, xmaxSet; this.UserRecommendedYMin; this.UserRecommendedYMax; this.UserRecommendedXMin; this.UserRecommendedXMax; this.isUserRecommendedYMin = false; this.isUserRecommendedYMax = false; this.isUserRecommendedXMin = false; this.isUserRecommendedXMax = false; this.areXYNLimitsSet = false; this.areXNLimitsSet = false; this.areYNLimitsSet = false; this.areXYLimitsSet = false; this.areXLimitsSet = false; this.areYLimitsSet = false; this.isYmaxSet = false; this.isXmaxSet = false; this.nXTicks; this.nYTicks; this.setdrawGrid = function (drawGridP) { this.drawGrid = drawGridP; }; //------------------------------------------------------------------------------ this.drawSymbol = function (fillStyleColor, strokeStyleColor, errorLineWidth, centerX, centerY, symbolSize, symbolType) { context.fillStyle = fillStyleColor; context.strokeStyle = strokeStyleColor; context.lineWidth = errorLineWidth; switch (symbolType) { //circle case 1: { context.beginPath(); context.arc(centerX, centerY, symbolSize, 0, 2 * Math.PI); context.closePath(); context.stroke(); break; } //square case 2: { context.beginPath(); context.rect(centerX - symbolSize, centerY - symbolSize, 2 * symbolSize, 2 * symbolSize); context.closePath(); context.stroke(); break; } //diamond case 3: { context.beginPath(); context.moveTo(centerX - symbolSize, centerY); context.lineTo(centerX, centerY - symbolSize); context.lineTo(centerX + symbolSize, centerY); context.lineTo(centerX, centerY + symbolSize); context.closePath(); context.stroke(); break; } //star case 4: { drawStar(centerX, centerY, 5, symbolSize, symbolSize / 2, context); break; } //triangle up case 5: { context.beginPath(); context.moveTo(centerX, centerY - 1.5 * symbolSize); context.lineTo(centerX + symbolSize, centerY + 0.5 * symbolSize); context.lineTo(centerX - symbolSize, centerY + 0.5 * symbolSize); context.closePath(); context.stroke(); break; } //triangle down case 6: { context.beginPath(); context.moveTo(centerX, centerY + 1.5 * symbolSize); context.lineTo(centerX + symbolSize, centerY - 0.5 * symbolSize); context.lineTo(centerX - symbolSize, centerY - 0.5 * symbolSize); context.closePath(); context.stroke(); break; } //triangle left case 7: { context.save(); context.translate(centerX, centerY); context.rotate(Math.PI / 2); context.beginPath(); context.moveTo(0, 1.5 * symbolSize); context.lineTo(symbolSize, -0.5 * symbolSize); context.lineTo(-symbolSize, -0.5 * symbolSize); context.closePath(); context.stroke(); context.restore(); break; } //triangle right case 8: { context.save(); context.translate(centerX, centerY); context.rotate(-Math.PI / 2); context.beginPath(); context.moveTo(0, 1.5 * symbolSize); context.lineTo(symbolSize, -0.5 * symbolSize); context.lineTo(-symbolSize, -0.5 * symbolSize); context.closePath(); context.stroke(); context.restore(); break; } //pentagon case 9: { polygon(context, centerX, centerY, symbolSize, 5, -Math.PI / 2, false); break; } //default default: { break; } } //fill symbols context.fill(); }; //------------------------------------------------------------------------------ this.draw = function () { var h = canvas.height;//important!! var w = canvas.width;//important!! context.beginPath(); context.fillStyle = this.bgColor; context.fillRect(1, 1, w - 2, h - 2); //-------------------------------------------------------------------------- var anyVisible = false; for (j = 0; j < this.arrayOfData.length; j++) if(this.arrayOfData[j].isVisible) {anyVisible = true; break;} if(this.arrayOfData.length == 0 || !anyVisible) { context.strokeStyle = "black"; context.rect(0.5, 0.5, w - 1, h - 1); var exclam = "No data to draw"; context.textAlign = "center"; context.textBaseline = "top"; context.font = this.tickLabelFont; context.fillStyle = this.tickLabelColor; context.fillText(exclam, w/2, h/2); context.stroke(); return; } //-------------------------------------------------------------------------- // prepare array of formated-strings for legend var arrLegend_FS = []; for (j = 0; j < this.arrayOfData.length; j++) arrLegend_FS[j] = new FormatedString(this.arrayOfData[j].legendStr, context, this.tickLabelFont, this.tickLabelFont_Index); //-------------------------------------------------------------------------- for (j = 0; j < this.arrayOfData.length; j++) { if (this.isLogScale) this.arrayOfData[j].aa = this.arrayOfData[j].aaLog; else this.arrayOfData[j].aa = this.arrayOfData[j].aaLin; } if (this.areXYLimitsSet === false && this.areXYNLimitsSet === false) { if (this.areXLimitsSet === false && this.areXNLimitsSet === false) { this.xmin = Number.POSITIVE_INFINITY; this.xmax = Number.NEGATIVE_INFINITY; for (j = 0; j < this.arrayOfData.length; j++) { if (!this.arrayOfData[j].isVisible) continue; if (this.arrayOfData[j].skipForLimits) continue; if (this.arrayOfData[j].isMinMax === false) continue; for (i = 0; i < this.arrayOfData[j].nPoints; i++) { if (this.arrayOfData[j].aa[i][0] < this.xmin) this.xmin = this.arrayOfData[j].aa[i][0]; if (this.arrayOfData[j].aa[i][0] > this.xmax) this.xmax = this.arrayOfData[j].aa[i][0]; } } if(this.isUserRecommendedXMin) { this.xmin = this.UserRecommendedXMin; this.isUserRecommendedXMin = false; } if(this.isUserRecommendedXMax) { this.xmax = this.UserRecommendedXMax; this.isUserRecommendedXMax = false; } if (this.xmin === this.xmax) { if (this.xmin === 0) xnumScale = new NiceScale(-1, 1, this.xAxisType); else if (this.xmin > 0) xnumScale = new NiceScale(0, 2 * this.xmax, this.xAxisType); else xnumScale = new NiceScale(2 * this.xmax, 0, this.xAxisType); xMin = xnumScale.niceMin; xMax = xnumScale.niceMax; xtickSpacing = xnumScale.tickSpacing; this.setXNLimits(xMin, xMax, Math.round((xMax - xMin) / xtickSpacing)); } else { xnumScale = new NiceScale(this.xmin, this.xmax, this.xAxisType); xMin = xnumScale.niceMin; xMax = xnumScale.niceMax; xtickSpacing = xnumScale.tickSpacing; this.setXNLimits(xMin, xMax, Math.round((xMax - xMin) / xtickSpacing)); } } if (this.areYLimitsSet === false && this.areYNLimitsSet === false) { this.ymin = Number.POSITIVE_INFINITY; this.ymax = Number.NEGATIVE_INFINITY; for (j = 0; j < this.arrayOfData.length; j++) { if (!this.arrayOfData[j].isVisible) continue; if (this.arrayOfData[j].skipForLimits) continue; if (this.arrayOfData[j].isMinMax === false) continue; for (i = 0; i < this.arrayOfData[j].nPoints; i++) { if(!_isFinite(this.arrayOfData[j].aa[i][1])) continue; y = +this.arrayOfData[j].aa[i][1]; if(this.arrayOfData[j].nColumns > 2) { if(_isFinite(this.arrayOfData[j].aa[i][3])) { // check if errorM >= 99% of value (i.e. the value is measured upper limits) // then do not take into account this error-bar for getting minmal limits const val = Math.abs(this.arrayOfData[j].aa[i][1] - this.arrayOfData[j].aa[i][3])*100.0/this.arrayOfData[j].aa[i][1]; if(+val > 1.0) y -= this.arrayOfData[j].aa[i][3]; } } if (y < this.ymin) this.ymin = y; y = +this.arrayOfData[j].aa[i][1] + ((this.arrayOfData[j].nColumns > 2) ? this.arrayOfData[j].aa[i][2] : 0.0); if (y > this.ymax) this.ymax = y; } } if(this.isUserRecommendedYMin) { this.ymin = this.UserRecommendedYMin; this.isUserRecommendedYMin = false; } if(this.isUserRecommendedYMax) { this.ymax = this.UserRecommendedYMax; this.isUserRecommendedYMax = false; } if (this.ymin === this.ymax) { if (this.isLogScale === false) { if (this.ymin === 0) ynumScale = new NiceScale(-1, 1, 0); else if (this.ymin > 0) ynumScale = new NiceScale(0, 2 * this.ymax, 0); else ynumScale = new NiceScale(2 * this.ymax, 0, 0); yMin = ynumScale.niceMin; yMax = ynumScale.niceMax; ytickSpacing = ynumScale.tickSpacing; this.setYNLimits(yMin, yMax, Math.round((yMax - yMin) / ytickSpacing)); } else { this.ymin = Math.floor(this.ymin); this.ymax = Math.ceil(this.ymax); if (this.ymin === this.ymax) { this.ymin = this.ymin - 1; this.ymax = this.ymax + 1; } this.setYNLimits(this.ymin, this.ymax, (this.ymax - this.ymin)); } } else { if (this.isLogScale === false) { ynumScale = new NiceScale(this.ymin, this.ymax, 0); yMin = ynumScale.niceMin; yMax = ynumScale.niceMax; ytickSpacing = ynumScale.tickSpacing; this.setYNLimits(yMin, yMax, (yMax - yMin) / ytickSpacing); } else { this.ymin = Math.floor(this.ymin); this.ymax = Math.ceil(this.ymax); this.setYNLimits(this.ymin, this.ymax, (this.ymax - this.ymin)); } } } } if (this.areXYLimitsSet === true || this.areXLimitsSet === true) { xnumScale = new NiceScale(this.xmin, this.xmax, this.xAxisType); xMin = this.xmin; xMax = this.xmax; xtickSpacing = xnumScale.tickSpacing; // this.setXNLimits(xMin, xMax, ((xMax - xMin) / calcStepSize((xMax - xMin), 6))); this.setXNLimits(xMin, xMax, ((xMax - xMin) / xtickSpacing)); this.niceXmin = xnumScale.niceMin; this.niceXmax = xnumScale.niceMax; this.xtickSpacing = xtickSpacing; } if (this.areXYLimitsSet === true || this.areYLimitsSet === true) { ynumScale = new NiceScale(this.ymin, this.ymax, 0); yMin = this.ymin; yMax = this.ymax; ytickSpacing = ynumScale.tickSpacing; //this.setYNLimits(yMin, yMax, ((yMax - yMin) / calcStepSize((yMax - yMin), 6))); this.setYNLimits(yMin, yMax, ((yMax - yMin) / ytickSpacing)); this.niceYmin = ynumScale.niceMin; this.niceYmax = ynumScale.niceMax; if (this.isLogScale === false) this.ytickSpacing = ytickSpacing; else this.ytickSpacing = 1; //this.ytickSpacing = ytickSpacing; } if (this.isYmaxSet === true && this.ymax > this.ymaxSet && this.ymaxSet > this.ymin) this.ymax = this.ymaxSet; if (this.isXmaxSet === true && this.xmax > this.xmaxSet && this.xmaxSet > this.xmin) this.xmax = this.xmaxSet; //------------------------------------------------------------------------------ var top = 20, left = 120, bottom = 80, right = 20; var maxStrWidth = 0; this.legendWidth = 0; this.legendHeight = this.majorTickLength; if (this.isTop) top = 3 * this.majorTickLength + 0.25 * this.majorTickLength + this.yAxisLabel_FS.bounding.height; else top = 3*this.majorTickLength; if (this.isRight && this.isLegendVisible) { context.font = this.tickLabelFont; for (j = 0; j < this.arrayOfData.length; j++) { if (!this.arrayOfData[j].isVisible) continue; if (this.arrayOfData[j].putLegendNearCurve) continue; if (this.arrayOfData[j].legendStr.length !== 0) { if(maxStrWidth < arrLegend_FS[j].bounding.width) maxStrWidth = arrLegend_FS[j].bounding.width; this.legendHeight += arrLegend_FS[j].bounding.height + this.majorTickLength; } } right = 4 * this.majorTickLength + maxStrWidth + lineLegend; } else right = 3 * this.majorTickLength; right = 3 * this.majorTickLength; this.legendWidth = 2 * this.majorTickLength + maxStrWidth + lineLegend; //---------------------------------------- //temporary workaround if (this.xmax === 180) right = 3 * this.majorTickLength; //---------------------------------------- bottom = parseInt(this.tickLabelFont) + this.xAxisLabel_FS.bounding.height + 3 * this.majorTickLength; //---------------------------------------- var maxWidth = Number.NEGATIVE_INFINITY, Xwidth = this.xmax - this.xmin, Yheight = this.ymax - this.ymin; context.font = this.tickLabelFont; var wd10 = context.measureText('10').width; // for (i = 0; this.niceYmin + i * this.ytickSpacing <= this.ymax; i++) { if (this.niceYmin + i * this.ytickSpacing >= this.ymin) { var wd; if (this.isLogScale) { context.font = this.tickLabelFont_Index; var yy = Math.round(this.ymin + i * Yheight / this.nYTicks); var metrics2 = context.measureText(yy.toString()); wd = wd10 + metrics2.width; } else { context.font = this.tickLabelFont; var tt = round2(this.niceYmin + i * this.ytickSpacing, this.ytickSpacing), metrics = context.measureText(tt); wd = metrics.width; } if (wd > maxWidth) maxWidth = wd; } } if (this.isTop) left = maxWidth + 1 * this.majorTickLength + 0.25 * this.majorTickLength; else left = maxWidth + 3 * this.majorTickLength + 0.25 * this.majorTickLength + this.yAxisLabel_FS.bounding.height; var width = w - left - right, height = h - top - bottom; width = Math.round(width); height = Math.round(height); this.fWidth = width; this.fHeight = height; this.fTop = top; this.fLeft = left; //------------------------------------------------------------------------------ var Xscale = Xwidth / width, Yscale = Yheight / height; this.fXscale = Xscale; this.fYscale = Yscale; //------------------------------------------------------------------------------ if (this.drawGrid) { //vertictal grid lines for (i = 0; this.niceXmin + i * this.xtickSpacing <= this.xmax; i++) { if (this.niceXmin + i * this.xtickSpacing >= this.xmin) { x1 = left + (i * this.xtickSpacing - (this.xmin - this.niceXmin)) / Xscale; drawVerticalDashedLine(x1, top + height, top, 4, context, this.gridColor, this.lineWidth); } } //horizontal grid lines for (i = 0; this.niceYmin + i * this.ytickSpacing <= this.ymax; i++) { if (this.niceYmin + i * this.ytickSpacing >= this.ymin) { y1 = top + height - (i * this.ytickSpacing - (this.ymin - this.niceYmin)) / Yscale; drawHorizontalDashedLine(left, left + width, y1, 4, context, this.gridColor, this.lineWidth); } } } //------------------------------------------------------------------------------ for (j = 0; j < this.arrayOfData.length; j++) { if (!this.arrayOfData[j].isVisible) continue; context.fillStyle = this.arrayOfData[j].color; context.strokeStyle = this.arrayOfData[j].color; //draw line if (this.arrayOfData[j].line > 0) { var xx = [], yy = [], _x, _y; for (i = 0; i < this.arrayOfData[j].nPoints; i++) { if(_isFinite(this.arrayOfData[j].aa[i][1])) { _x = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale; _y = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin) / Yscale; xx.push(_x); yy.push(_y); } } drawLineByArray(xx, yy, context, this.arrayOfData[j].color, this.arrayOfData[j].line, this.arrayOfData[j].lineStyle); } for (i = 0; i < this.arrayOfData[j].nPoints; i++) { if(!_isFinite(this.arrayOfData[j].aa[i][1])) continue; //draw symbols if (this.arrayOfData[j].symbol > 0) { //draw plus errors context.strokeStyle = "black"; //if (this.arrayOfData[j].nColumns === 4) if (this.arrayOfData[j].nColumns >= 4) { x1 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale; y1 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin) / Yscale; x2 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale; y2 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin + this.arrayOfData[j].aa[i][2]) / Yscale; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); x1 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale - this.arrayOfData[j].symbolSize; y1 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin + this.arrayOfData[j].aa[i][2]) / Yscale; x2 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale + this.arrayOfData[j].symbolSize; y2 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin + this.arrayOfData[j].aa[i][2]) / Yscale; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); //draw minus errors if(_isFinite(this.arrayOfData[j].aa[i][3])) { x1 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale; y1 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin) / Yscale; x2 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale; y2 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin - this.arrayOfData[j].aa[i][3]) / Yscale; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); x1 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale - this.arrayOfData[j].symbolSize; y1 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin - this.arrayOfData[j].aa[i][3]) / Yscale; x2 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale + this.arrayOfData[j].symbolSize; y2 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin - this.arrayOfData[j].aa[i][3]) / Yscale; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); } else { x1 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale; y1 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin) / Yscale; x2 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale; //y2 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin - this.arrayOfData[j].aa[i][3]) / Yscale; y2 = height + top; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); x1 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale - this.arrayOfData[j].symbolSize; //y1 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin - this.arrayOfData[j].aa[i][3]) / Yscale; //y1 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin - this.arrayOfData[j].aa[i][3]) / Yscale - this.arrayOfData[j].symbolSize; //y1 = height + top - 2*this.arrayOfData[j].symbolSize; y1 = height + top - 1.5*this.arrayOfData[j].symbolSize; //x2 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale + this.arrayOfData[j].symbolSize; x2 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale; //y2 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin - this.arrayOfData[j].aa[i][3]) / Yscale; y2 = height + top; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); x1 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale + this.arrayOfData[j].symbolSize; //y1 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin - this.arrayOfData[j].aa[i][3]) / Yscale; //y1 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin - this.arrayOfData[j].aa[i][3]) / Yscale - this.arrayOfData[j].symbolSize; //y1 = height + top - 2*this.arrayOfData[j].symbolSize; y1 = height + top - 1.5*this.arrayOfData[j].symbolSize; //x2 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale + this.arrayOfData[j].symbolSize; x2 = left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale; //y2 = height + top - (this.arrayOfData[j].aa[i][1] - this.ymin - this.arrayOfData[j].aa[i][3]) / Yscale; y2 = height + top; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); } } //draw symbols this.drawSymbol(this.arrayOfData[j].color, "black", this.arrayOfData[j].errorLineWidth, left + (this.arrayOfData[j].aa[i][0] - this.xmin) / Xscale, height + top - (this.arrayOfData[j].aa[i][1] - this.ymin) / Yscale, this.arrayOfData[j].symbolSize, this.arrayOfData[j].symbol); } } //------------------------------------------------------------------------------ if(this.arrayOfData[j].putLegendNearCurve) { context.font = this.tickLabelFont; context.textAlign = "right"; context.textBaseline = "bottom"; var str = this.arrayOfData[j].legendStr; x1 = left + width - this.majorTickLength - 3; y1 = top + height - 3 - (this.arrayOfData[j].aa[this.arrayOfData[j].nPoints-1][1] - this.ymin) / Yscale; context.fillStyle = this.arrayOfData[j].color; context.fillText(str, x1, y1); } } //------------------------------------------------------------------------------ //this ensures lines do not go out of axis limits context.fillStyle = this.bgColor; context.beginPath(); context.fillRect(1, 1, w - 2, top - 1); context.beginPath(); context.fillRect(1, h - bottom + 1, w - 2, bottom - 2); context.beginPath(); context.fillRect(1, 1, left - 2, h - 2); context.beginPath(); context.fillRect(w - right, 1, right - 1, h - 2); context.beginPath();//here!!! context.lineWidth = this.lineWidth; context.rect(0.5, 0.5, w - 1, h - 1); context.stroke(); context.strokeStyle = "black"; context.rect(Math.floor(left) + 0.5, Math.floor(top) + 0.5, width, height); context.stroke(); //------------------------------------------------------------------------------ for (i = 0; this.niceXmin + i * this.xtickSpacing <= this.xmax; i++) { if (this.niceXmin + i * this.xtickSpacing >= this.xmin) { //horizontal bottom major ticks x1 = left + (i * this.xtickSpacing - (this.xmin - this.niceXmin)) / Xscale; y1 = top + height; x2 = x1; y2 = top + height + this.majorTickLength; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); //horizontal top major ticks y1 = top; y2 = top - this.majorTickLength; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); //horizontal bottom major tick labels context.textAlign = "center"; context.textBaseline = "top"; context.font = this.tickLabelFont; context.fillStyle = this.tickLabelColor; context.fillText( round2(this.niceXmin + i * this.xtickSpacing, this.xtickSpacing / 2.), x1, top + height + 1.5*this.majorTickLength); } if (this.niceXmin + i * this.xtickSpacing + 0.5 * this.xtickSpacing >= this.xmin && this.niceXmin + i * this.xtickSpacing + 0.5 * this.xtickSpacing <= this.xmax) { //horizontal bottom minor ticks x1 = left + (i * this.xtickSpacing + 0.5 * this.xtickSpacing - (this.xmin - this.niceXmin)) / Xscale; y1 = top + height; x2 = x1; y2 = top + height + this.majorTickLength / 2; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); //horizontal top minor ticks y1 = top; y2 = top - this.majorTickLength / 2; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); } } //------------------------------------------------------------------------------ for (i = 0; this.niceYmin + i * this.ytickSpacing <= this.ymax; i++) { if (this.niceYmin + i * this.ytickSpacing >= this.ymin) { //vertical right major ticks x1 = left + width; y1 = top + height - (i * this.ytickSpacing - (this.ymin - this.niceYmin)) / Yscale; x2 = left + width + this.majorTickLength; y2 = y1; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); //vertical left major ticks x1 = left; x2 = left - this.majorTickLength; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); //vertical left major tick labels context.textAlign = "right"; context.textBaseline = "middle"; context.font = this.tickLabelFont; context.fillStyle = this.tickLabelColor; if (this.isLogScale) { //------------------------------- context.textAlign = "right"; context.textBaseline = "middle"; context.fillStyle = 'black'; //------------------------------- var str1 = '10'; var metrics1 = context.measureText(str1); var width1 = metrics1.width; //------------------------------- context.textAlign = "left"; context.textBaseline = "middle"; context.fillStyle = 'black'; var fontSize1 = parseInt(context.font); var fontSize2 = Math.round(2 * fontSize1 / 3); var fontSizeDiff = fontSize1 - fontSize2; var strF = context.font.split(' ', 2); context.font = fontSize2.toString() + 'px ' + strF[1]; //------------------------------- //var yy = Math.round(this.ymin + i * Yheight / this.nYTicks); var yy = Math.round(i * this.ytickSpacing + this.niceYmin); var str2 = yy.toString(); var metrics2 = context.measureText(str2); var width2 = metrics2.width; //------------------------------- context.fillText(str2, left - 1.5*this.majorTickLength - width2, top + height - (i * this.ytickSpacing - (this.ymin - this.niceYmin)) / Yscale - fontSizeDiff); //------------------------------- context.textAlign = "right"; context.textBaseline = "middle"; context.font = this.tickLabelFont; context.fillStyle = this.tickLabelColor; context.fillText(str1, left - 1.5*this.majorTickLength - width2, top + height - (i * this.ytickSpacing - (this.ymin - this.niceYmin)) / Yscale); } else { //context.fillText(this.niceYmin + i * this.ytickSpacing, left - this.majorTickLength - 0.25 * this.majorTickLength, top + height - (i * this.ytickSpacing - (this.ymin - this.niceYmin)) / Yscale); //context.fillText(round1(this.niceYmin + i * this.ytickSpacing), left - this.majorTickLength - 0.25 * this.majorTickLength, top + height - (i * this.ytickSpacing - (this.ymin - this.niceYmin)) / Yscale); //context.fillText(round2(this.niceYmin + i * this.ytickSpacing, this.ytickSpacing), left - this.majorTickLength - 0.25 * this.majorTickLength, top + height - (i * this.ytickSpacing - (this.ymin - this.niceYmin)) / Yscale); context.fillText( round2(this.niceYmin + i * this.ytickSpacing, this.ytickSpacing / 2), left - 1.5*this.majorTickLength, top + height - (i * this.ytickSpacing - (this.ymin - this.niceYmin)) / Yscale); } } //minor ticks if (this.isLogScale) { for (j = 2; j < 10; j++) { if (this.niceYmin + i * this.ytickSpacing + this.ytickSpacing * Math.log(j) / ln10 >= this.ymin && this.niceYmin + i * this.ytickSpacing + this.ytickSpacing * Math.log(j) / ln10 <= this.ymax) { //vertical right minor ticks x1 = left + width; y1 = top + height - (i * this.ytickSpacing - (this.ymin - this.niceYmin) + this.ytickSpacing * Math.log(j) / ln10) / Yscale; x2 = left + width + this.majorTickLength / 2; y2 = y1; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); //vertical left minor ticks x1 = left; x2 = left - this.majorTickLength / 2; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); } } } else { if (this.niceYmin + i * this.ytickSpacing + 0.5 * this.ytickSpacing >= this.ymin && this.niceYmin + i * this.ytickSpacing + 0.5 * this.ytickSpacing <= this.ymax) { //vertical right minor ticks x1 = left + width; y1 = top + height - (i * this.ytickSpacing + 0.5 * this.ytickSpacing - (this.ymin - this.niceYmin)) / Yscale; x2 = left + width + this.majorTickLength / 2; y2 = y1; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); //vertical left minor ticks x1 = left; x2 = left - this.majorTickLength / 2; drawLine(x1, y1, x2, y2, context, "black", this.lineWidth); } } } //------------------------------------------------------------------------------ //set Y-axis label if (this.isTop) { context.fillStyle = this.axisLabelColor; this.yAxisLabel_FS.draw(left, top - 2 * this.majorTickLength); } else { context.save(); context.fillStyle = this.axisLabelColor; var _label = new FormatedString(this.yAxisLabel, context, this.axisLabelFont, this.axisLabelFont); context.translate( left - 2 * this.majorTickLength - 0.25 * this.majorTickLength - maxWidth, top + 0.5 * (height + this.yAxisLabel_FS.bounding.width)); context.rotate(-Math.PI / 2); this.yAxisLabel_FS.draw(0,0); context.restore(); } //------------------------------------------------------------------------------ //set X-axis label context.fillStyle = this.axisLabelColor; context.textAlign = "left"; context.textBaseline = this.xAxisLabel_FS.baseline; this.xAxisLabel_FS.draw(left + 0.5 * (width - this.xAxisLabel_FS.bounding.width), top + height + bottom - this.majorTickLength); //------------------------------------------------------------------------------ //legend if (this.isRight && this.isLegendVisible) { var nLegengStr = 0; for (j = 0; j < this.arrayOfData.length; j++) { if (!this.arrayOfData[j].isVisible) continue; if (this.arrayOfData[j].putLegendNearCurve) continue; if (this.arrayOfData[j].legendStr.length !== 0) { nLegengStr++; } } if (nLegengStr > 0) { context.rect(left + width - this.legendWidth - 2 * this.majorTickLength, this.isLegendOnTop ? top + this.majorTickLength : top - this.majorTickLength + height - this.legendHeight, this.legendWidth + this.majorTickLength, this.legendHeight); context.strokeStyle = 'black'; context.stroke(); context.fillStyle = this.bgColor; context.fill(); this.isLegendOnTop ? y1 = top + 1*this.majorTickLength : y1 = top + 1*this.majorTickLength + height - 2*this.majorTickLength - this.legendHeight; var jjj=0; for (j = 0; j < this.arrayOfData.length; j++) { if (!this.arrayOfData[j].isVisible) continue; if (this.arrayOfData[j].putLegendNearCurve) continue; if (this.arrayOfData[j].legendStr.length !== 0) { context.fillStyle = this.arrayOfData[j].color; context.strokeStyle = this.arrayOfData[j].color; //-------------------------------------------- x1 = left + width - this.legendWidth - this.majorTickLength; if(jjj > 0) y1 += arrLegend_FS[j-1].bounding.height/2; y1 += arrLegend_FS[j].bounding.height/2 + this.majorTickLength; x2 = x1 + lineLegend; y2 = y1; if (this.arrayOfData[j].line > 0) drawLine(x1, y2, x2, y2, context, this.arrayOfData[j].color, this.arrayOfData[j].line, this.arrayOfData[j].lineStyle); if (this.arrayOfData[j].symbol > 0) this.drawSymbol(this.arrayOfData[j].color, "black", this.arrayOfData[j].errorLineWidth, x1 + lineLegend / 2, y2, this.arrayOfData[j].symbolSize, this.arrayOfData[j].symbol); context.textAlign = "left"; context.textBaseline = "middle"; context.fillStyle = this.tickLabelColor; arrLegend_FS[j].draw(x1 + lineLegend + this.majorTickLength, y1); jjj++; //-------------------------------------------- } } } } //reset limits to enable redraw with different parameters this.areXYNLimitsSet = false; this.areXNLimitsSet = false; this.areYNLimitsSet = false; this.areXYLimitsSet = false; this.areXLimitsSet = false; this.areYLimitsSet = false; if (this.isFirstRun) { this.xMin0 = this.xmin; this.xMax0 = this.xmax; this.yMin0 = this.ymin; this.yMax0 = this.ymax; this.xMinZ = this.xmin; this.xMaxZ = this.xmax; this.yMinZ = this.ymin; this.yMaxZ = this.ymax; this.isFirstRun = false; } }; //------------------------------------------------------------------------------ //set xMin,xMax,yMin,yMax this.setXYNLimits = function (xMinP, xMaxP, nXTicksP, yMinP, yMaxP, nYTicksP) { this.xmin = xMinP; this.xmax = xMaxP; this.ymin = yMinP; this.ymax = yMaxP; this.nXTicks = nXTicksP; this.nYTicks = nYTicksP; this.areXYNLimitsSet = true; this.niceXmin = this.xmin; this.niceXmax = this.xmax; this.xtickSpacing = ((xMaxP - xMinP) / nXTicksP); this.niceYmin = this.ymin; this.niceYmax = this.ymax; if (this.isLogScale === false) this.ytickSpacing = ((yMaxP - yMinP) / nYTicksP); else this.ytickSpacing = 1; }; //------------------------------------------------------------------------------ this.setXNLimits = function (xMinP, xMaxP, nXTicksP) { this.xmin = xMinP; this.xmax = xMaxP; this.nXTicks = nXTicksP; this.areXNLimitsSet = true; this.niceXmin = this.xmin; this.niceXmax = this.xmax; this.xtickSpacing = ((xMaxP - xMinP) / nXTicksP); }; //------------------------------------------------------------------------------ this.setYNLimits = function (yMinP, yMaxP, nYTicksP) { this.ymin = yMinP; this.ymax = yMaxP; this.nYTicks = nYTicksP; this.areYNLimitsSet = true; this.niceYmin = this.ymin; this.niceYmax = this.ymax; if (this.isLogScale === false) this.ytickSpacing = ((yMaxP - yMinP) / nYTicksP); else this.ytickSpacing = 1; }; //------------------------------------------------------------------------------ this.setXYLimits = function (xMinP, xMaxP, yMinP, yMaxP) { this.xmin = xMinP; this.xmax = xMaxP; this.ymin = yMinP; this.ymax = yMaxP; this.areXYLimitsSet = true; }; //------------------------------------------------------------------------------ this.setXLimits = function (xMinP, xMaxP) { this.xmin = xMinP; this.xmax = xMaxP; this.areXLimitsSet = true; }; //------------------------------------------------------------------------------ this.setYLimits = function (yMinP, yMaxP) { this.ymin = yMinP; this.ymax = yMaxP; this.areYLimitsSet = true; }; //------------------------------------------------------------------------------ this.setYmax = function (yMaxP) { this.ymaxSet = yMaxP; this.isYmaxSet = true; }; //------------------------------------------------------------------------------ this.setXmax = function (xMaxP) { this.xmaxSet = xMaxP; this.isXmaxSet = true; }; //------------------------------------------------------------------------------ this.setUserYmin = function (yMinP) { this.UserRecommendedYMin = (this.isLogScale===false)?yMinP:((yMinP>0)?log10(yMinP):-15); this.isUserRecommendedYMin = true; }; this.setUserYmax = function (yMaxP) { this.UserRecommendedYMax = (this.isLogScale===false)?yMaxP:((yMaxP>0)?log10(yMaxP):-14); this.isUserRecommendedYMax = true; }; //------------------------------------------------------------------------------ this.setUserXmin = function (xMinP) { this.UserRecommendedXMin = xMinP; this.isUserRecommendedXMin = true; }; this.setUserXmax = function (xMaxP) { this.UserRecommendedXMax = xMaxP; this.isUserRecommendedXMax = true; }; //------------------------------------------------------------------------------ //add new data this.addData = function (ChartData) { this.arrayOfData.push(ChartData); }; //------------------------------------------------------------------------------ this.deleteAllData = function () { while (this.arrayOfData.length > 0) this.arrayOfData.pop(); }; //------------------------------------------------------------------------------ this.replaceData = function (num, dat) { this.arrayOfData[num] = dat; }; //------------------------------------------------------------------------------ this.getVisibleData = function (sig_p, sig_m, separator, num_digX, isScientificX, num_digY, isScientificY) { sig_p = typeof sig_p !== 'undefined' ? sig_p : true; sig_m = typeof sig_m !== 'undefined' ? sig_m : true; separator = typeof separator !== 'undefined' ? separator : "\t"; num_digX = typeof num_digX !== 'undefined' ? num_digX : 4; isScientificX = typeof isScientificX !== 'undefined' ? isScientificX : false; num_digY = typeof num_digY !== 'undefined' ? num_digY : 4; isScientificY = typeof isScientificY !== 'undefined' ? isScientificY : false; var result = ""; for (j = 0; j < this.arrayOfData.length; j++) { if (!this.arrayOfData[j].isVisible) continue; if ((this.arrayOfData[j].line > 0) || (this.arrayOfData[j].symbol > 0)) { if (this.arrayOfData[j].legendStr.length > 0) result += this.arrayOfData[j].legendStr + "\n"; for (i = 0; i < this.arrayOfData[j].nPoints; i++) { if ((this.arrayOfData[j].nColumns < 4) && ((sig_p === true) || (sig_m === true))) { console.log("No errors in data!"); break; } result += formatFloat(this.arrayOfData[j].aaLin[i][0], num_digX, isScientificX) + separator + formatFloat(this.arrayOfData[j].aaLin[i][1], num_digY, isScientificY); if ((sig_p === true) || (sig_m === true)) result += separator; else { result += "\n"; continue; } if (sig_p === true) result += formatFloat(this.arrayOfData[j].aaLin[i][2], num_digY, isScientificY); if (sig_m === true) result += separator; else { result += "\n"; continue; } result += formatFloat(this.arrayOfData[j].aaLin[i][3], num_digY, isScientificY) + "\n"; } } } return result; }; //------------------------------------------------------------------------------ // this.getAllData = function (sig_p, sig_m, separator, num_dig, isScientific) this.getAllData = function (sig_p, sig_m, separator, num_digX, isScientificX, num_digY, isScientificY) { sig_p = typeof sig_p !== 'undefined' ? sig_p : true; sig_m = typeof sig_m !== 'undefined' ? sig_m : true; separator = typeof separator !== 'undefined' ? separator : "\t"; num_digX = typeof num_digX !== 'undefined' ? num_digX : 4; isScientificX = typeof isScientificX !== 'undefined' ? isScientificX : false; num_digY = typeof num_digY !== 'undefined' ? num_digY : 4; isScientificY = typeof isScientificY !== 'undefined' ? isScientificY : false; var result = ""; for (j = 0; j < this.arrayOfData.length; j++) { //if ( (this.arrayOfData[j].line > 0) || (this.arrayOfData[j].symbol > 0 ) ) { if (this.arrayOfData[j].legendStr.length > 0) result += this.arrayOfData[j].legendStr + "\n"; for (i = 0; i < this.arrayOfData[j].nPoints; i++) { if ((this.arrayOfData[j].nColumns < 4) && ((sig_p === true) || (sig_m === true))) { console.log("No errors in data!"); break; } result += formatFloat(1.0*this.arrayOfData[j].aaLin[i][0], num_digX, isScientificX) + separator + formatFloat(1.0*this.arrayOfData[j].aaLin[i][1], num_digY, isScientificY); /* result += 1.0*this.arrayOfData[j].aaLin[i][0] + separator + 1.0*this.arrayOfData[j].aaLin[i][1]; */ if ((sig_p === true) || (sig_m === true)) result += separator; else { result += "\n"; continue; } if (sig_p === true) result += formatFloat(1.0*this.arrayOfData[j].aaLin[i][2], num_digY, isScientificY); // result += 1.0*this.arrayOfData[j].aaLin[i][2]; if (sig_m === true) result += separator; else { result += "\n"; continue; } //result += formatFloat(1.0*this.arrayOfData[j].aaLin[i][3], num_digY, isScientificY) + "\n"; result += formatFloat(1.0*this.arrayOfData[j].aaLin[i][3], num_digY, isScientificY); //result += 1.0*this.arrayOfData[j].aaLin[i][3]; if(this.arrayOfData[j].nColumns > 4) { result += separator; result += this.arrayOfData[j].aaLin[i][4]; //result += this.arrayOfData[j].aa[i][4]; } result += "\n"; } } } return result; }; //------------------------------------------------------------------------------ this.getSingleData = function (j, sig_p, sig_m, separator, num_digX, isScientificX, num_digY, isScientificY) { j = typeof j !== 'undefined' ? j : 4; sig_p = typeof sig_p !== 'undefined' ? sig_p : true; sig_m = typeof sig_m !== 'undefined' ? sig_m : true; separator = typeof separator !== 'undefined' ? separator : "\t"; num_digX = typeof num_digX !== 'undefined' ? num_digX : 4; isScientificX = typeof isScientificX !== 'undefined' ? isScientificX : false; num_digY = typeof num_digY !== 'undefined' ? num_digY : 4; isScientificY = typeof isScientificY !== 'undefined' ? isScientificY : false; var result = ""; //for (j = 0; j < this.arrayOfData.length; j++) { //if ( (this.arrayOfData[j].line > 0) || (this.arrayOfData[j].symbol > 0 ) ) { if (this.arrayOfData[j].legendStr.length > 0) result += this.arrayOfData[j].legendStr + "\n"; for (i = 0; i < this.arrayOfData[j].nPoints; i++) { if ((this.arrayOfData[j].nColumns < 4) && ((sig_p === true) || (sig_m === true))) { console.log("No errors in data!"); break; } result += formatFloat(this.arrayOfData[j].aaLin[i][0], num_digX, isScientificX) + separator + formatFloat(this.arrayOfData[j].aaLin[i][1], num_digY, isScientificY); if ((sig_p === true) || (sig_m === true)) result += separator; else { result += "\n"; continue; } if (sig_p === true) result += formatFloat(this.arrayOfData[j].aaLin[i][2], num_digY, isScientificY); if (sig_m === true) result += separator; else { result += "\n"; continue; } result += formatFloat(this.arrayOfData[j].aaLin[i][3], num_digY, isScientificY) + "\n"; } } } return result; }; //------------------------------------------------------------------------------ //set canvas this.setCanvas = function (canvasNameP) { canvasName = canvasNameP; canvas = current_document.getElementById(canvasNameP); context = canvas.getContext('2d'); this.setXAxisLabel(this.xAxisLabel); this.setYAxisLabel(this.yAxisLabel); }; //------------------------------------------------------------------------------ //set Document this.setDocument = function (doc) { current_document = doc; }; //------------------------------------------------------------------------------ //set bind html element this.setElements = function (doc, canvasNameP) { current_document = doc; canvas = current_document.getElementById(canvasNameP); context = canvas.getContext('2d'); this.setXAxisLabel(this.xAxisLabel); this.setYAxisLabel(this.yAxisLabel); }; //------------------------------------------------------------------------------ this.getClone = function(canvasNameP) { var clone_chart = new Chart(canvasNameP, this.tickLabelFont, this.tickLabelColor, this.axisLabelFont, this.axisLabelColor, this.xAxisLabel, this.yAxisLabel, this.lineWidth, this.drawGrid, this.gridColor, this.bgColor, this.majorTickLength, this.isLogScale, this.isTop, this.isRight); for (j = 0; j < clone_chart.arrayOfData.length; j++) { //this.arrayOfData[j].synchronize(clone_chart.arrayOfData[j]); this.addData(clone_chart.arrayOfData[j]); console.log("clone: "+j); } return clone_chart; }; //------------------------------------------------------------------------------ this.synchronizeWith = function(source, fullSynch) { if(fullSynch == true) { this.tickLabelFont = source.tickLabelFontP; this.tickLabelColor = source.tickLabelColorP; this.setAxisLabelFont(source.axisLabelFontP); this.axisLabelColor = source.axisLabelColorP; this.gridColor = source.gridColorP; this.bgColor = source.bgColorP; this.majorTickLength = source.majorTickLengthP; this.nXTicks = source.nXTicks; this.nYTicks = source.nYTicks; this.isFirstRun = source.isFirstRun; } this.setXAxisLabel(source.xAxisLabelP); this.setYAxisLabel(source.yAxisLabelP); this.lineWidth = source.lineWidthP; this.drawGrid = source.drawGridP; this.isLogScale = source.isLogScaleP; this.isTop = source.isTopP; this.isRight = source.isRightP; this.xAxisType = source.xAxisType; this.isLegendOnTop = source.isLegendOnTop; this.isLegendVisible = source.isLegendVisible; this.UserRecommendedYMin = source.UserRecommendedYMin; this.UserRecommendedYMax = source.UserRecommendedYMax; this.UserRecommendedXMin = source.UserRecommendedXMin; this.UserRecommendedXMax = source.UserRecommendedXMax; this.isUserRecommendedYMin = source.isUserRecommendedYMin; this.isUserRecommendedYMax = source.isUserRecommendedYMax; this.isUserRecommendedXMin = source.isUserRecommendedXMin; this.isUserRecommendedXMax = source.isUserRecommendedXMax; this.areXYNLimitsSet = source.areXYNLimitsSet; this.areXNLimitsSet = source.areXNLimitsSet; this.areYNLimitsSet = source.areYNLimitsSet; this.areXYLimitsSet = source.areXYLimitsSet; this.areXLimitsSet = source.areXLimitsSet; this.areYLimitsSet = source.areYLimitsSet; this.isYmaxSet = source.isYmaxSet; this.isXmaxSet = source.isXmaxSet; /*for (j = 0; j < source.arrayOfData.length; j++) { var obj = source.arrayOfData[j]; console.log("j="+j,obj.nPoints, obj.nColumns, obj.aaLin, obj.color, obj.symbol, obj.symbolSize, obj.line, obj.errorLineWidth, obj.legendStr, obj.isDataLogScale); var data = new ChartData(obj.nPoints, obj.nColumns, obj.aaLin, obj.color, obj.symbol, obj.symbolSize, obj.line, obj.errorLineWidth, obj.legendStr, obj.isDataLogScale); //this.addData(data); }*/ /*for (j = 0; j < source.arrayOfData.length; j++) { source.arrayOfData[j].synchronize(this.arrayOfData[j]); }*/ }; }; //------------------------------------------------------------------------------ // @param {Object} obj Любой объект // @returns {Object} Возвращает копию объекта function clone(obj) { if (obj === null || typeof(obj) !== 'object' || 'isActiveClone' in obj) { return obj; } var temp = obj.constructor(); // changed for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { obj['isActiveClone'] = null; temp[key] = clone(obj[key]); delete obj['isActiveClone']; } } return temp; }; //--------------------------------------------------------------------- // _string - parameter containing the formated string to draw on canvas // context - context object from the user canvas // font_main - font for main line // font_index - font fot indexes // // example: _string = "a\\_i\\^{2} + b\\_i\\^{2} = ∫\\u{0}\o{∞}f(x)dx" // // font content: [font style][font weight][font size][font face] // font style: normal; italic; oblique // font weight: normal; bold; bolder; lighter; 100-900 // font size: size in px, e.g. 24px // font face: name of the font, e.g. Arial, Times //--------------------------------------------------------------------- function FormatedString(_string, context, font_main, font_index) { this.bounding = { xBase: 0, yBase: 0, left: 0, top: 0, width: 0, height: 0 }; this.debuging = false; this.baseline = 'bottom'; // top, hanging, middle, alphabetic, ideographic, bottom this.hasIndex = false; var json_text = [], Shift = 0.5, h_main = 0, h_sup_index = 0, h_sub_index = 0, h_over_index = 0, h_under_index = 0, descent = 0.2,// 20% of total font height gapShift = 0.5, gap = 0; context.textAlign = "left"; context.textBaseline = this.baseline; //-------------------------------------------------------------------------- this.getFontHeigth = function(font) { if (!('fontBoundingBoxAscent' in TextMetrics.prototype)) { var size = 13, ftext = font.split(' '); for(i=0;i i_prev) { str = string.substring(i_prev, i); json_text.push({text:str, property:"", x:0, y:0, width:0, height:0}); } // 2. what is symbol's type type = char_array[++i]; if(char_array[++i] == '{') //multi symbol { i++; i_prev = i; while(char_array[i] != '}') i++; str = string.substring(i_prev, i); json_text.push({text: str, property: type, x:0, y:0, width:0, height:0}); } else // single symbol { json_text.push({text: char_array[i], property: type, x:0, y:0, width:0, height:0}); } // 3. save last position i_prev = ++i; } else i++; } //---------------------------------------------------------------------- // last substring of the main type if(i_prev != i) { str = string.substring(i_prev, i); json_text.push({text: str, property: "", x:0, y:0, width:0, height:0}); } if(this.debuging) console.log("analyze 1: "+JSON.stringify(json_text)); //---------------------------------------------------------------------- // get bounding var tm, w = 0, h = 0; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - context.font = font_index; tm = context.measureText("."); gap = gapShift*tm.width; h_main = 0; h_sup_index = 0; h_sub_index = 0; h_over_index = 0; h_under_index = 0; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // get joint height for(i = 0;i < json_text.length; i++) if(json_text[i].property == '') { context.font = font_main; h_main = Math.max(h_main, this.getTextHeigth(json_text[i].text, font_main)); } else { context.font = font_index; if(json_text[i].property == '^') h_sup_index = Math.max(h_sup_index, this.getTextHeigth(json_text[i].text, font_index)); else if (json_text[i].property == 'o') h_over_index = Math.max(h_over_index, this.getTextHeigth(json_text[i].text, font_index)); else if (json_text[i].property == '_') h_sub_index = Math.max(h_sub_index, this.getTextHeigth(json_text[i].text, font_index)); else if (json_text[i].property == 'u') h_under_index = Math.max(h_under_index, this.getTextHeigth(json_text[i].text, font_index)); } h = h_main + Math.max(Shift*h_sup_index, h_over_index + ((h_over_index>0)?gap:0)) + Math.max(Shift*h_sub_index, h_under_index + ((h_over_index>0)?gap:0)); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // get joint width for(i = 0;i < json_text.length; i++) { if(json_text[i].property == '') context.font = font_main; else context.font = font_index; tm = context.measureText(json_text[i].text); json_text[i].width = tm.width; json_text[i].height = this.getTextHeigth(json_text[i].text, context.font); if(json_text[i].property == 'o' && json_text[i].property == 'u') continue; if(i>0) if(json_text[i].property != "" && json_text[i-1].property != "") if(json_text[i].property != json_text[i-1].property) { dw = json_text[i].width - json_text[i-1].width; w += (dw>0?dw:0); continue; } w += json_text[i].width + gap; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - this.bounding.width = w; this.bounding.height = h; this.bounding.left = 0; this.bounding.top = 0; this.bounding.xBase = 0; this.bounding.yBase = h - Math.max(Shift*h_sub_index, h_under_index+gap); // if(this.debuging) console.log("analyze 2: "+JSON.stringify(json_text)); }; //------------------------------------------------------------------------------- this.draw = function(_x, _y) { var tm, x = _x, y = _y, x0, y0, dx = 0, dy = 0, w_main = 0, h_main = 0, newSymbol = true; // get h_main - maximal height of main substring for(i = 0;i < json_text.length; i++) if(json_text[i].property == "") if(h_main < json_text[i].height) h_main = json_text[i].height; // get bounding this.bounding.xBase = x; this.bounding.yBase = _y + ((h_sub_index>0)?descent*h_main:0) - Math.max((1-Shift)*h_sub_index, h_under_index+gap); this.bounding.left = x; this.bounding.top = _y - this.bounding.height; // start to draw for(i = 0;i < json_text.length; i++) { switch(json_text[i].property) { case "": x0 = x; y0 = this.bounding.yBase + descent*h_main ; newSymbol = true; w_main = json_text[i].width; x += json_text[i].width + gap; break; case "^": x0 = x; y0 = this.bounding.yBase - (1-descent)*h_main + 0.5*json_text[i].height; if(!newSymbol && json_text[i].property != json_text[i-1].property) x0 = json_text[i-1].x; if(newSymbol) x += json_text[i].width + gap; else { dx = (json_text[i].width-json_text[i-1].width); x += (dx>0?dx:0); } newSymbol = false; break; case "_": x0 = x; y0 = this.bounding.yBase + 0.5*json_text[i].height; if(!newSymbol && json_text[i].property != json_text[i-1].property) x0 = json_text[i-1].x; if(newSymbol) x += json_text[i].width + gap; else { dx = (json_text[i].width-json_text[i-1].width); x += (dx>0?dx:0); } newSymbol = false; break; case "o": x0 = json_text[i-(newSymbol?1:2)].x + json_text[i-(newSymbol?1:2)].width - (newSymbol?1:2)*gap - json_text[i].width/2; y0 = this.bounding.yBase - h_main - gap; newSymbol = true; break; case "u": x0 = json_text[i-(newSymbol?1:2)].x + json_text[i-(newSymbol?1:2)].width - (newSymbol?1:2)*gap - json_text[i].width/2; y0 = this.bounding.yBase + json_text[i].height + gap; newSymbol = true; break; } if(json_text[i].property != '') context.font = font_index; else context.font = font_main; context.beginPath(); json_text[i].x = x0; json_text[i].y = y0; context.fillText(json_text[i].text, json_text[i].x, json_text[i].y); } if(this.debuging) console.log("draw: "+JSON.stringify(json_text)); }; //---------------------------------------------------------------------- // first job this.analyze(_string); }