/* * Point class. A GPoint is composed of two coordinates (x, y) and a text label */ function GPoint() { var x, y, label; if (arguments.length === 3) { x = arguments[0]; y = arguments[1]; label = arguments[2]; } else if (arguments.length === 2 && arguments[0] instanceof p5.Vector) { x = arguments[0].x; y = arguments[0].y; label = arguments[1]; } else if (arguments.length === 2) { x = arguments[0]; y = arguments[1]; label = ""; } else if (arguments.length === 1 && arguments[0] instanceof GPoint) { x = arguments[0].getX(); y = arguments[0].getY(); label = arguments[0].getLabel(); } else if (arguments.length === 1 && arguments[0] instanceof p5.Vector) { x = arguments[0].x; y = arguments[0].y; label = ""; } else if (arguments.length === 0) { x = 0; y = 0; label = ""; } else { throw new Error("GPoint constructor: signature not supported"); } this.x = x; this.y = y; this.label = label; this.valid = this.isValidNumber(this.x) && this.isValidNumber(this.y); } GPoint.prototype.isValidNumber = function(number) { return !isNaN(number) && isFinite(number); }; GPoint.prototype.set = function() { var x, y, label; if (arguments.length === 3) { x = arguments[0]; y = arguments[1]; label = arguments[2]; } else if (arguments.length === 2 && arguments[0] instanceof p5.Vector) { x = arguments[0].x; y = arguments[0].y; label = arguments[1]; } else if (arguments.length === 2) { x = arguments[0]; y = arguments[1]; label = ""; } else if (arguments.length === 1 && arguments[0] instanceof GPoint) { x = arguments[0].getX(); y = arguments[0].getY(); label = arguments[0].getLabel(); } else if (arguments.length === 1 && arguments[0] instanceof p5.Vector) { x = arguments[0].x; y = arguments[0].y; label = ""; } else { throw new Error("GPoint.set(): signature not supported"); } this.x = x; this.y = y; this.label = label; this.valid = this.isValidNumber(this.x) && this.isValidNumber(this.y); }; GPoint.prototype.setX = function(x) { this.x = x; this.valid = this.isValidNumber(this.x) && this.isValidNumber(this.y); }; GPoint.prototype.setY = function(y) { this.y = y; this.valid = this.isValidNumber(this.x) && this.isValidNumber(this.y); }; GPoint.prototype.setLabel = function(label) { this.label = label; }; GPoint.prototype.setXY = function() { var x, y; if (arguments.length === 2) { x = arguments[0]; y = arguments[1]; } else if (arguments.length === 1 && arguments[0] instanceof GPoint) { x = arguments[0].getX(); y = arguments[0].getY(); } else if (arguments.length === 1 && arguments[0] instanceof p5.Vector) { x = arguments[0].x; y = arguments[0].y; } else { throw new Error("GPoint.setXY(): signature not supported"); } this.x = x; this.y = y; this.valid = this.isValidNumber(this.x) && this.isValidNumber(this.y); }; GPoint.prototype.getX = function() { return this.x; }; GPoint.prototype.getY = function() { return this.y; }; GPoint.prototype.getLabel = function() { return this.label; }; GPoint.prototype.getValid = function() { return this.valid; }; GPoint.prototype.isValid = function() { return this.valid; }; /* * Title class. */ function GTitle(parent, dim) { // The parent processing object this.parent = parent; // General properties this.dim = dim.slice(); this.relativePos = 0.5; this.plotPos = this.relativePos * this.dim[0]; this.offset = 10; // Text properties this.text = ""; this.textAlignment = this.parent.CENTER; this.fontName = "Helvetica"; this.fontColor = this.parent.color(100); this.fontStyle = this.parent.BOLD; this.fontSize = 13; } GTitle.prototype.draw = function() { this.parent.push(); this.parent.textFont(this.fontName); this.parent.textStyle(this.fontStyle); this.parent.textSize(this.fontSize); this.parent.fill(this.fontColor); this.parent.noStroke(); this.parent.textAlign(this.textAlignment, this.parent.BOTTOM); this.parent.text(this.text, this.plotPos, -this.offset - this.dim[1]); // There seems to be a bug in p5.js this.parent.textStyle(this.parent.NORMAL); this.parent.pop(); }; GTitle.prototype.setDim = function() { var xDim, yDim; if (arguments.length === 2) { xDim = arguments[0]; yDim = arguments[1]; } else if (arguments.length === 1) { xDim = arguments[0][0]; yDim = arguments[0][1]; } else { throw new Error("GTitle.setDim(): signature not supported"); } if (xDim > 0 && yDim > 0) { this.dim[0] = xDim; this.dim[1] = yDim; this.plotPos = this.relativePos * this.dim[0]; } }; GTitle.prototype.setRelativePos = function(relativePos) { this.relativePos = relativePos; this.plotPos = this.relativePos * this.dim[0]; }; GTitle.prototype.setOffset = function(offset) { this.offset = offset; }; GTitle.prototype.setText = function(text) { this.text = text; }; GTitle.prototype.setTextAlignment = function(textAlignment) { if (textAlignment === this.parent.CENTER || textAlignment === this.parent.LEFT || textAlignment === this.parent.RIGHT) { this.textAlignment = textAlignment; } }; GTitle.prototype.setFontName = function(fontName) { this.fontName = fontName; }; GTitle.prototype.setFontColor = function(fontColor) { this.fontColor = fontColor; }; GTitle.prototype.setFontStyle = function(fontStyle) { this.fontStyle = fontStyle; }; GTitle.prototype.setFontSize = function(fontSize) { if (fontSize > 0) { this.fontSize = fontSize; } }; GTitle.prototype.setFontProperties = function(fontName, fontColor, fontSize) { if (fontSize > 0) { this.fontName = fontName; this.fontColor = fontColor; this.fontSize = fontSize; } }; /* * Axis label class. */ function GAxisLabel(parent, type, dim) { // The parent processing object this.parent = parent; // General properties this.type = (type === this.parent.BOTTOM || type === this.parent.TOP || type === this.parent.LEFT || type === this.parent.RIGHT) ? type : this.parent.BOTTOM; this.dim = dim.slice(); this.relativePos = 0.5; this.plotPos = (this.type === this.parent.BOTTOM || this.type === this.parent.TOP) ? this.relativePos * this.dim[0] : -this.relativePos * this.dim[1]; this.offset = 35; this.rotate = (this.type === this.parent.BOTTOM || this.type === this.parent.TOP) ? false : true; // Text properties this.text = ""; this.textAlignment = this.parent.CENTER; this.fontName = "Helvetica"; this.fontColor = this.parent.color(0); this.fontSize = 13; } GAxisLabel.prototype.draw = function() { switch (this.type) { case this.parent.BOTTOM: this.drawAsXLabel(); break; case this.parent.LEFT: this.drawAsYLabel(); break; case this.parent.TOP: this.drawAsTopLabel(); break; case this.parent.RIGHT: this.drawAsRightLabel(); break; } }; GAxisLabel.prototype.drawAsXLabel = function() { this.parent.push(); this.parent.textFont(this.fontName); this.parent.textSize(this.fontSize); this.parent.fill(this.fontColor); this.parent.noStroke(); if (this.rotate) { this.parent.textAlign(this.parent.RIGHT, this.parent.CENTER); this.parent.translate(this.plotPos, this.offset); this.parent.rotate(-0.5 * Math.PI); this.parent.text(this.text, 0, 0); } else { this.parent.textAlign(this.textAlignment, this.parent.TOP); this.parent.text(this.text, this.plotPos, this.offset); } this.parent.pop(); }; GAxisLabel.prototype.drawAsYLabel = function() { this.parent.push(); this.parent.textFont(this.fontName); this.parent.textSize(this.fontSize); this.parent.fill(this.fontColor); this.parent.noStroke(); if (this.rotate) { this.parent.textAlign(this.textAlignment, this.parent.BOTTOM); this.parent.translate(-this.offset, this.plotPos); this.parent.rotate(-0.5 * Math.PI); this.parent.text(this.text, 0, 0); } else { this.parent.textAlign(this.parent.RIGHT, this.parent.CENTER); this.parent.text(this.text, -this.offset, this.plotPos); } this.parent.pop(); }; GAxisLabel.prototype.drawAsTopLabel = function() { this.parent.push(); this.parent.textFont(this.fontName); this.parent.textSize(this.fontSize); this.parent.fill(this.fontColor); this.parent.noStroke(); if (this.rotate) { this.parent.textAlign(this.parent.LEFT, this.parent.CENTER); this.parent.translate(this.plotPos, -this.offset - this.dim[1]); this.parent.rotate(-0.5 * Math.PI); this.parent.text(this.text, 0, 0); } else { this.parent.textAlign(this.textAlignment, this.parent.BOTTOM); this.parent.text(this.text, this.plotPos, -this.offset - this.dim[1]); } this.parent.pop(); }; GAxisLabel.prototype.drawAsRightLabel = function() { this.parent.push(); this.parent.textFont(this.fontName); this.parent.textSize(this.fontSize); this.parent.fill(this.fontColor); this.parent.noStroke(); if (this.rotate) { this.parent.textAlign(this.textAlignment, this.parent.TOP); this.parent.translate(this.offset + this.dim[0], this.plotPos); this.parent.rotate(-0.5 * Math.PI); this.parent.text(this.text, 0, 0); } else { this.parent.textAlign(this.parent.LEFT, this.parent.CENTER); this.parent.text(this.text, this.offset + this.dim[0], this.plotPos); } this.parent.pop(); }; GAxisLabel.prototype.setDim = function() { var xDim, yDim; if (arguments.length === 2) { xDim = arguments[0]; yDim = arguments[1]; } else if (arguments.length === 1) { xDim = arguments[0][0]; yDim = arguments[0][1]; } else { throw new Error("GAxisLabel.setDim(): signature not supported"); } if (xDim > 0 && yDim > 0) { this.dim[0] = xDim; this.dim[1] = yDim; this.plotPos = (this.type === this.parent.BOTTOM || this.type === this.parent.TOP) ? this.relativePos * this.dim[0] : -this.relativePos * this.dim[1]; } }; GAxisLabel.prototype.setRelativePos = function(relativePos) { this.relativePos = relativePos; this.plotPos = (this.type === this.parent.BOTTOM || this.type === this.parent.TOP) ? this.relativePos * this.dim[0] : -this.relativePos * this.dim[1]; }; GAxisLabel.prototype.setOffset = function(offset) { this.offset = offset; }; GAxisLabel.prototype.setRotate = function(rotate) { this.rotate = rotate; }; GAxisLabel.prototype.setText = function(text) { this.text = text; }; GAxisLabel.prototype.setTextAlignment = function(textAlignment) { if (textAlignment === this.parent.CENTER || textAlignment === this.parent.LEFT || textAlignment === this.parent.RIGHT) { this.textAlignment = textAlignment; } }; GAxisLabel.prototype.setFontName = function(fontName) { this.fontName = fontName; }; GAxisLabel.prototype.setFontColor = function(fontColor) { this.fontColor = fontColor; }; GAxisLabel.prototype.setFontSize = function(fontSize) { if (fontSize > 0) { this.fontSize = fontSize; } }; GAxisLabel.prototype.setFontProperties = function(fontName, fontColor, fontSize) { if (fontSize > 0) { this.fontName = fontName; this.fontColor = fontColor; this.fontSize = fontSize; } }; /* * Axis class. */ function GAxis(parent, type, dim, lim, log) { // The parent processing object this.parent = parent; // General properties this.type = (type === this.parent.BOTTOM || type === this.parent.TOP || type === this.parent.LEFT || type === this.parent.RIGHT) ? type : this.parent.BOTTOM; this.dim = dim.slice(); this.lim = lim.slice(); this.log = log; // Do some sanity checks if (this.log && (this.lim[0] <= 0 || this.lim[1] <= 0)) { console.log("The limits are negative. This is not allowed in logarithmic scale."); console.log("Will set them to (0.1, 10)"); if (this.lim[1] > this.lim[0]) { this.lim[0] = 0.1; this.lim[1] = 10; } else { this.lim[0] = 10; this.lim[1] = 0.1; } } // Format properties this.offset = 5; this.lineColor = this.parent.color(0); this.lineWidth = 1; // Ticks properties this.nTicks = 5; this.ticksSeparation = -1; this.ticks = []; this.plotTicks = []; this.ticksInside = []; this.tickLabels = []; this.fixedTicks = false; this.tickLength = 3; this.smallTickLength = 2; this.expTickLabels = false; this.rotateTickLabels = (this.type === this.parent.BOTTOM || this.type === this.parent.TOP) ? false : true; this.drawTickLabels = (this.type === this.parent.BOTTOM || this.type === this.parent.LEFT) ? true : false; this.tickLabelOffset = 7; this.ticksPrecision = undefined; // Label properties this.lab = new GAxisLabel(this.parent, this.type, this.dim); this.drawAxisLabel = true; // Text properties this.fontName = "Helvetica"; this.fontColor = this.parent.color(0); this.fontSize = 11; // Update the arrays this.updateTicks(); this.updatePlotTicks(); this.updateTicksInside(); this.updateTickLabels(); } GAxis.prototype.obtainSigDigits = function(number) { return Math.round(-Math.log(0.5 * Math.abs(number)) / Math.LN10); }; GAxis.prototype.roundPlus = function(number, sigDigits) { // Old way of doing it // var bd = new BigDecimal(number); // roundedNumber = parseFloat(bd.setScale(sigDigits, RoundingMode.HALF_UP()).longValue().toFixed(sigDigits)); number = Math.round(number * Math.pow(10, sigDigits)) / Math.pow(10, sigDigits); if (sigDigits <= 0) { number = Math.round(number); } return number; }; GAxis.prototype.adaptSize = function(a, n) { if (n < a.length) { a.splice(n, Number.MAX_VALUE); } }; GAxis.prototype.updateTicks = function() { if (this.log) { this.obtainLogarithmicTicks(); } else { this.obtainLinearTicks(); } }; GAxis.prototype.obtainLogarithmicTicks = function() { // Get the exponents of the first and last ticks in increasing order var firstExp, lastExp; if (this.lim[1] > this.lim[0]) { firstExp = Math.floor(Math.log(this.lim[0]) / Math.LN10); lastExp = Math.ceil(Math.log(this.lim[1]) / Math.LN10); } else { firstExp = Math.floor(Math.log(this.lim[1]) / Math.LN10); lastExp = Math.ceil(Math.log(this.lim[0]) / Math.LN10); } // Calculate the ticks var n = (lastExp - firstExp) * 9 + 1; this.adaptSize(this.ticks, n); for (var exp = firstExp; exp < lastExp; exp++) { var base = this.roundPlus(Math.exp(exp * Math.LN10), -exp); for (var i = 0; i < 9; i++) { this.ticks[(exp - firstExp) * 9 + i] = (i + 1) * base; } } this.ticks[this.ticks.length - 1] = this.roundPlus(Math.exp(lastExp * Math.LN10), -exp); // Change the ticks order if necessary if (this.lim[1] < this.lim[0]) { this.ticks.reverse(); } }; GAxis.prototype.obtainLinearTicks = function() { // Obtain the required precision for the ticks var step = 0; var nSteps = 0; var sigDigits = 0; if (this.ticksSeparation > 0) { step = (this.lim[1] > this.lim[0]) ? this.ticksSeparation : -this.ticksSeparation; sigDigits = this.obtainSigDigits(step); while (this.roundPlus(step, sigDigits) - step !== 0) { sigDigits++; } nSteps = Math.floor((this.lim[1] - this.lim[0]) / step); } else if (this.nTicks > 0) { step = (this.lim[1] - this.lim[0]) / this.nTicks; sigDigits = this.obtainSigDigits(step); step = this.roundPlus(step, sigDigits); if (step === 0 || Math.abs(step) > Math.abs(this.lim[1] - this.lim[0])) { sigDigits++; step = this.roundPlus((this.lim[1] - this.lim[0]) / this.nTicks, sigDigits); } nSteps = Math.floor((this.lim[1] - this.lim[0]) / step); } // Calculate the linear ticks if (nSteps > 0) { // Obtain the first tick var firstTick = this.lim[0] + ((this.lim[1] - this.lim[0]) - nSteps * step) / 2; // Subtract some steps to be sure we have all firstTick = this.roundPlus(firstTick - 2 * step, sigDigits); while ((this.lim[1] - firstTick) * (this.lim[0] - firstTick) > 0) { firstTick = this.roundPlus(firstTick + step, sigDigits); } // Calculate the rest of the ticks var n = Math.floor(Math.abs((this.lim[1] - firstTick) / step)) + 1; this.adaptSize(this.ticks, n); this.ticks[0] = firstTick; for (var i = 1; i < n; i++) { this.ticks[i] = this.roundPlus(this.ticks[i - 1] + step, sigDigits); } // Save the ticks precision this.ticksPrecision = sigDigits; } else { this.ticks = []; } }; GAxis.prototype.updatePlotTicks = function() { var scaleFactor, i; var n = this.ticks.length; this.adaptSize(this.plotTicks, n); if (this.log) { if (this.type === this.parent.BOTTOM || this.type === this.parent.TOP) { scaleFactor = this.dim[0] / Math.log(this.lim[1] / this.lim[0]); } else { scaleFactor = -this.dim[1] / Math.log(this.lim[1] / this.lim[0]); } for ( i = 0; i < n; i++) { this.plotTicks[i] = Math.log(this.ticks[i] / this.lim[0]) * scaleFactor; } } else { if (this.type === this.parent.BOTTOM || this.type === this.parent.TOP) { scaleFactor = this.dim[0] / (this.lim[1] - this.lim[0]); } else { scaleFactor = -this.dim[1] / (this.lim[1] - this.lim[0]); } for ( i = 0; i < n; i++) { this.plotTicks[i] = (this.ticks[i] - this.lim[0]) * scaleFactor; } } }; GAxis.prototype.updateTicksInside = function() { var i; var n = this.ticks.length; this.adaptSize(this.ticksInside, n); if (this.type === this.parent.BOTTOM || this.type === this.parent.TOP) { for ( i = 0; i < n; i++) { this.ticksInside[i] = (this.plotTicks[i] >= 0) && (this.plotTicks[i] <= this.dim[0]); } } else { for ( i = 0; i < n; i++) { this.ticksInside[i] = (-this.plotTicks[i] >= 0) && (-this.plotTicks[i] <= this.dim[1]); } } }; GAxis.prototype.updateTickLabels = function() { var tick, logValue, isExactLogValue, i; var n = this.ticks.length; this.adaptSize(this.tickLabels, n); if (this.log) { for ( i = 0; i < n; i++) { tick = this.ticks[i]; if (tick > 0) { logValue = Math.log(tick) / Math.LN10; isExactLogValue = Math.abs(logValue - Math.round(logValue)) < 0.0001; if (isExactLogValue) { logValue = Math.round(logValue); if (this.expTickLabels) { this.tickLabels[i] = "1e" + logValue; } else { if (logValue > -3.1 && logValue < 3.1) { this.tickLabels[i] = (logValue >= 0) ? "" + Math.round(tick) : "" + tick; } else { this.tickLabels[i] = "1e" + logValue; } } } else { this.tickLabels[i] = ""; } } else { this.tickLabels[i] = ""; } } } else { for ( i = 0; i < n; i++) { tick = this.ticks[i]; if (tick % 1 === 0) { this.tickLabels[i] = "" + Math.round(tick); } else if ( typeof this.ticksPrecision !== "undefined" && this.ticksPrecision >= 0) { this.tickLabels[i] = "" + parseFloat(tick).toFixed(this.ticksPrecision); } else { this.tickLabels[i] = "" + tick; } } } }; GAxis.prototype.moveLim = function(newLim) { if (newLim[1] !== newLim[0]) { // Check that the new limit makes sense if (this.log && (newLim[0] <= 0 || newLim[1] <= 0)) { console.log("The limits are negative. This is not allowed in logarithmic scale."); } else { this.lim[0] = newLim[0]; this.lim[1] = newLim[1]; // Calculate the new ticks if they are not fixed if (!this.fixedTicks) { var n = this.ticks.length; if (this.log) { this.obtainLogarithmicTicks(); } else if (n > 0) { // Obtain the ticks precision and the tick separation var step = 0; var sigDigits = 0; if (this.ticksSeparation > 0) { step = (this.lim[1] > this.lim[0]) ? this.ticksSeparation : -this.ticksSeparation; sigDigits = this.obtainSigDigits(step); while (this.roundPlus(step, sigDigits) - step !== 0) { sigDigits++; } } else { step = (n === 1) ? this.lim[1] - this.lim[0] : this.ticks[1] - this.ticks[0]; sigDigits = this.obtainSigDigits(step); step = this.roundPlus(step, sigDigits); if (step === 0 || Math.abs(step) > Math.abs(this.lim[1] - this.lim[0])) { sigDigits++; step = (n === 1) ? this.lim[1] - this.lim[0] : this.ticks[1] - this.ticks[0]; step = this.roundPlus(step, sigDigits); } step = (this.lim[1] > this.lim[0]) ? Math.abs(step) : -Math.abs(step); } // Obtain the first tick var firstTick = this.ticks[0] + step * Math.ceil((this.lim[0] - this.ticks[0]) / step); firstTick = this.roundPlus(firstTick, sigDigits); if ((this.lim[1] - firstTick) * (this.lim[0] - firstTick) > 0) { firstTick = this.ticks[0] + step * Math.floor((this.lim[0] - this.ticks[0]) / step); firstTick = this.roundPlus(firstTick, sigDigits); } // Calculate the rest of the ticks n = Math.floor(Math.abs((this.lim[1] - firstTick) / step)) + 1; this.adaptSize(this.ticks, n); this.ticks[0] = firstTick; for (var i = 1; i < n; i++) { this.ticks[i] = this.roundPlus(this.ticks[i - 1] + step, sigDigits); } // A sanity check if (this.ticksPrecision !== sigDigits) { console.log("There is a problem in the axis ticks precision calculation"); } } // Obtain the new tick labels this.updateTickLabels(); } // Update the rest of the arrays this.updatePlotTicks(); this.updateTicksInside(); } } }; GAxis.prototype.draw = function() { switch (this.type) { case this.parent.BOTTOM: this.drawAsXAxis(); break; case this.parent.LEFT: this.drawAsYAxis(); break; case this.parent.TOP: this.drawAsTopAxis(); break; case this.parent.RIGHT: this.drawAsRightAxis(); break; } if (this.drawAxisLabel) { this.lab.draw(); } }; GAxis.prototype.drawAsXAxis = function() { var i; // Draw the ticks this.parent.push(); this.parent.stroke(this.lineColor); this.parent.strokeWeight(this.lineWidth); this.parent.strokeCap(this.parent.SQUARE); this.parent.line(0, this.offset, this.dim[0], this.offset); for ( i = 0; i < this.plotTicks.length; i++) { if (this.ticksInside[i]) { if (this.log && this.tickLabels[i] === "") { this.parent.line(this.plotTicks[i], this.offset, this.plotTicks[i], this.offset + this.smallTickLength); } else { this.parent.line(this.plotTicks[i], this.offset, this.plotTicks[i], this.offset + this.tickLength); } } } this.parent.pop(); // Draw the tick labels if (this.drawTickLabels) { this.parent.push(); this.parent.textFont(this.fontName); this.parent.textSize(this.fontSize); this.parent.fill(this.fontColor); this.parent.noStroke(); if (this.rotateTickLabels) { var halfPI = 0.5 * Math.PI; this.parent.textAlign(this.parent.RIGHT, this.parent.CENTER); for ( i = 0; i < this.plotTicks.length; i++) { if (this.ticksInside[i] && this.tickLabels[i] !== "") { this.parent.push(); this.parent.translate(this.plotTicks[i], this.offset + this.tickLabelOffset); this.parent.rotate(-halfPI); this.parent.text(this.tickLabels[i], 0, 0); this.parent.pop(); } } } else { this.parent.textAlign(this.parent.CENTER, this.parent.TOP); for ( i = 0; i < this.plotTicks.length; i++) { if (this.ticksInside[i] && this.tickLabels[i] !== "") { this.parent.text(this.tickLabels[i], this.plotTicks[i], this.offset + this.tickLabelOffset); } } } this.parent.pop(); } }; GAxis.prototype.drawAsYAxis = function() { var i; // Draw the ticks this.parent.push(); this.parent.stroke(this.lineColor); this.parent.strokeWeight(this.lineWidth); this.parent.strokeCap(this.parent.SQUARE); this.parent.line(-this.offset, 0, -this.offset, -this.dim[1]); for ( i = 0; i < this.plotTicks.length; i++) { if (this.ticksInside[i]) { if (this.log && this.tickLabels[i] === "") { this.parent.line(-this.offset, this.plotTicks[i], -this.offset - this.smallTickLength, this.plotTicks[i]); } else { this.parent.line(-this.offset, this.plotTicks[i], -this.offset - this.tickLength, this.plotTicks[i]); } } } this.parent.pop(); // Draw the tick labels if (this.drawTickLabels) { this.parent.push(); this.parent.textFont(this.fontName); this.parent.textSize(this.fontSize); this.parent.fill(this.fontColor); this.parent.noStroke(); if (this.rotateTickLabels) { var halfPI = 0.5 * Math.PI; this.parent.textAlign(this.parent.CENTER, this.parent.BOTTOM); for ( i = 0; i < this.plotTicks.length; i++) { if (this.ticksInside[i] && this.tickLabels[i] !== "") { this.parent.push(); this.parent.translate(-this.offset - this.tickLabelOffset, this.plotTicks[i]); this.parent.rotate(-halfPI); this.parent.text(this.tickLabels[i], 0, 0); this.parent.pop(); } } } else { this.parent.textAlign(this.parent.RIGHT, this.parent.CENTER); for ( i = 0; i < this.plotTicks.length; i++) { if (this.ticksInside[i] && this.tickLabels[i] !== "") { this.parent.text(this.tickLabels[i], -this.offset - this.tickLabelOffset, this.plotTicks[i]); } } } this.parent.pop(); } }; GAxis.prototype.drawAsTopAxis = function() { var i; // Draw the ticks this.parent.push(); this.parent.stroke(this.lineColor); this.parent.strokeWeight(this.lineWidth); this.parent.strokeCap(this.parent.SQUARE); this.parent.translate(0, -this.dim[1]); this.parent.line(0, -this.offset, this.dim[0], -this.offset); for ( i = 0; i < this.plotTicks.length; i++) { if (this.ticksInside[i]) { if (this.log && this.tickLabels[i] === "") { this.parent.line(this.plotTicks[i], -this.offset, this.plotTicks[i], -this.offset - this.smallTickLength); } else { this.parent.line(this.plotTicks[i], -this.offset, this.plotTicks[i], -this.offset - this.tickLength); } } } this.parent.pop(); // Draw the tick labels if (this.drawTickLabels) { this.parent.push(); this.parent.textFont(this.fontName); this.parent.textSize(this.fontSize); this.parent.fill(this.fontColor); this.parent.noStroke(); this.parent.translate(0, -this.dim[1]); if (this.rotateTickLabels) { var halfPI = 0.5 * Math.PI; this.parent.textAlign(this.parent.LEFT, this.parent.CENTER); for ( i = 0; i < this.plotTicks.length; i++) { if (this.ticksInside[i] && this.tickLabels[i] !== "") { this.parent.push(); this.parent.translate(this.plotTicks[i], -this.offset - this.tickLabelOffset); this.parent.rotate(-halfPI); this.parent.text(this.tickLabels[i], 0, 0); this.parent.pop(); } } } else { this.parent.textAlign(this.parent.CENTER, this.parent.BOTTOM); for ( i = 0; i < this.plotTicks.length; i++) { if (this.ticksInside[i] && this.tickLabels[i] !== "") { this.parent.text(this.tickLabels[i], this.plotTicks[i], -this.offset - this.tickLabelOffset); } } } this.parent.pop(); } }; GAxis.prototype.drawAsRightAxis = function() { var i; // Draw the ticks this.parent.push(); this.parent.stroke(this.lineColor); this.parent.strokeWeight(this.lineWidth); this.parent.strokeCap(this.parent.SQUARE); this.parent.translate(this.dim[0], 0); this.parent.line(this.offset, 0, this.offset, -this.dim[1]); for ( i = 0; i < this.plotTicks.length; i++) { if (this.ticksInside[i]) { if (this.log && this.tickLabels[i] === "") { this.parent.line(this.offset, this.plotTicks[i], this.offset + this.smallTickLength, this.plotTicks[i]); } else { this.parent.line(this.offset, this.plotTicks[i], this.offset + this.tickLength, this.plotTicks[i]); } } } this.parent.pop(); // Draw the tick labels if (this.drawTickLabels) { this.parent.push(); this.parent.textFont(this.fontName); this.parent.textSize(this.fontSize); this.parent.fill(this.fontColor); this.parent.noStroke(); this.parent.translate(this.dim[0], 0); if (this.rotateTickLabels) { var halfPI = 0.5 * Math.PI; this.parent.textAlign(this.parent.CENTER, this.parent.TOP); for ( i = 0; i < this.plotTicks.length; i++) { if (this.ticksInside[i] && this.tickLabels[i] !== "") { this.parent.push(); this.parent.translate(this.offset + this.tickLabelOffset, this.plotTicks[i]); this.parent.rotate(-halfPI); this.parent.text(this.tickLabels[i], 0, 0); this.parent.pop(); } } } else { this.parent.textAlign(this.parent.LEFT, this.parent.CENTER); for ( i = 0; i < this.plotTicks.length; i++) { if (this.ticksInside[i] && this.tickLabels[i] !== "") { this.parent.text(this.tickLabels[i], this.offset + this.tickLabelOffset, this.plotTicks[i]); } } } this.parent.pop(); } }; GAxis.prototype.setDim = function() { var xDim, yDim; if (arguments.length === 2) { xDim = arguments[0]; yDim = arguments[1]; } else if (arguments.length === 1) { xDim = arguments[0][0]; yDim = arguments[0][1]; } else { throw new Error("GAxis.setDim(): signature not supported"); } if (xDim > 0 && yDim > 0) { this.dim[0] = xDim; this.dim[1] = yDim; this.updatePlotTicks(); this.lab.setDim(this.dim); } }; GAxis.prototype.setLim = function(lim) { if (lim[1] !== lim[0]) { // Make sure the new limits makes sense if (this.log && (lim[0] <= 0 || lim[1] <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); } else { this.lim[0] = lim[0]; this.lim[1] = lim[1]; if (!this.fixedTicks) { this.updateTicks(); this.updateTickLabels(); } this.updatePlotTicks(); this.updateTicksInside(); } } }; GAxis.prototype.setLimAndLog = function(lim, log) { if (lim[1] !== lim[0]) { // Make sure the new limits makes sense if (log && (lim[0] <= 0 || lim[1] <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); } else { this.lim[0] = lim[0]; this.lim[1] = lim[1]; this.log = log; if (!this.fixedTicks) { this.updateTicks(); this.updateTickLabels(); } this.updatePlotTicks(); this.updateTicksInside(); } } }; GAxis.prototype.setLog = function(log) { if (log !== this.log) { this.log = log; // Check if the old limits still make sense if (this.log && (this.lim[0] <= 0 || this.lim[1] <= 0)) { console.log("The limits are negative. This is not allowed in logarithmic scale."); console.log("Will set them to (0.1, 10)"); if (this.lim[1] > this.lim[0]) { this.lim[0] = 0.1; this.lim[1] = 10; } else { this.lim[0] = 10; this.lim[1] = 0.1; } } if (!this.fixedTicks) { this.updateTicks(); this.updateTickLabels(); } this.updatePlotTicks(); this.updateTicksInside(); } }; GAxis.prototype.setOffset = function(offset) { this.offset = offset; }; GAxis.prototype.setLineColor = function(lineColor) { this.lineColor = lineColor; }; GAxis.prototype.setLineWidth = function(lineWidth) { if (lineWidth > 0) { this.lineWidth = lineWidth; } }; GAxis.prototype.setNTicks = function(nTicks) { if (nTicks >= 0) { this.nTicks = nTicks; this.ticksSeparation = -1; if (!this.log) { this.fixedTicks = false; this.updateTicks(); this.updatePlotTicks(); this.updateTicksInside(); this.updateTickLabels(); } } }; GAxis.prototype.setTicksSeparation = function(ticksSeparation) { this.ticksSeparation = ticksSeparation; if (!this.log) { this.fixedTicks = false; this.updateTicks(); this.updatePlotTicks(); this.updateTicksInside(); this.updateTickLabels(); } }; GAxis.prototype.setTicks = function(ticks) { var n = ticks.length; this.adaptSize(this.ticks, n); for (var i = 0; i < n; i++) { this.ticks[i] = ticks[i]; } this.fixedTicks = true; // Set the tick precision to undefined this.ticksPrecision = undefined; this.updatePlotTicks(); this.updateTicksInside(); this.updateTickLabels(); }; GAxis.prototype.setTickLabels = function(tickLabels) { if (tickLabels.length === this.tickLabels.length) { for (var i = 0; i < this.tickLabels.length; i++) { this.tickLabels[i] = tickLabels[i]; } this.fixedTicks = true; // Set the tick precision to undefined this.ticksPrecision = undefined; } }; GAxis.prototype.setFixedTicks = function(fixedTicks) { if (fixedTicks !== this.fixedTicks) { this.fixedTicks = fixedTicks; if (!this.fixedTicks) { this.updateTicks(); this.updatePlotTicks(); this.updateTicksInside(); this.updateTickLabels(); } } }; GAxis.prototype.setTickLength = function(tickLength) { this.tickLength = tickLength; }; GAxis.prototype.setSmallTickLength = function(smallTickLength) { this.smallTickLength = smallTickLength; }; GAxis.prototype.setExpTickLabels = function(expTickLabels) { if (expTickLabels !== this.expTickLabels) { this.expTickLabels = expTickLabels; this.updateTickLabels(); } }; GAxis.prototype.setRotateTickLabels = function(rotateTickLabels) { this.rotateTickLabels = rotateTickLabels; }; GAxis.prototype.setDrawTickLabels = function(drawTickLabels) { this.drawTickLabels = drawTickLabels; }; GAxis.prototype.setTickLabelOffset = function(tickLabelOffset) { this.tickLabelOffset = tickLabelOffset; }; GAxis.prototype.setDrawAxisLabel = function(drawAxisLabel) { this.drawAxisLabel = drawAxisLabel; }; GAxis.prototype.setAxisLabelText = function(axisLabelText) { this.lab.setText(axisLabelText); }; GAxis.prototype.setFontName = function(fontName) { this.fontName = fontName; }; GAxis.prototype.setFontColor = function(fontColor) { this.fontColor = fontColor; }; GAxis.prototype.setFontSize = function(fontSize) { if (fontSize > 0) { this.fontSize = fontSize; } }; GAxis.prototype.setFontProperties = function(fontName, fontColor, fontSize) { if (fontSize > 0) { this.fontName = fontName; this.fontColor = fontColor; this.fontSize = fontSize; } }; GAxis.prototype.setAllFontProperties = function(fontName, fontColor, fontSize) { this.setFontProperties(fontName, fontColor, fontSize); this.lab.setFontProperties(fontName, fontColor, fontSize); }; GAxis.prototype.getTicks = function() { if (this.fixedTicks) { return this.ticks.slice(); } else { // Return only the ticks that are visible var validTicks = []; var counter = 0; for (var i = 0; i < this.ticksInside.length; i++) { if (this.ticksInside[i]) { validTicks[counter] = this.ticks[i]; counter++; } } return validTicks; } }; GAxis.prototype.getTicksRef = function() { return this.ticks; }; GAxis.prototype.getPlotTicks = function() { if (this.fixedTicks) { return this.plotTicks.slice(); } else { var validPlotTicks = []; var counter = 0; for (var i = 0; i < this.ticksInside.length; i++) { if (this.ticksInside[i]) { validPlotTicks[counter] = this.plotTicks[i]; counter++; } } return validPlotTicks; } }; GAxis.prototype.getPlotTicksRef = function() { return this.plotTicks; }; GAxis.prototype.getAxisLabel = function() { return this.lab; }; /* * Histogram class. */ function GHistogram(parent, type, dim, plotPoints) { // The parent processing object this.parent = parent; // General properties this.type = (type === GPlot.VERTICAL || type === GPlot.HORIZONTAL) ? type : GPlot.VERTICAL; this.dim = dim.slice(); this.plotPoints = []; // Copy the plot points for (var i = 0; i < plotPoints.length; i++) { this.plotPoints[i] = new GPoint(plotPoints[i]); } this.visible = true; this.separations = [2]; this.bgColors = [this.parent.color(150, 150, 255)]; this.lineColors = [this.parent.color(100, 100, 255)]; this.lineWidths = [1]; this.differences = []; this.leftSides = []; this.rightSides = []; this.updateArrays(); // Labels properties this.labelsOffset = 8; this.drawLabels = false; this.rotateLabels = false; this.fontName = "Helvetica"; this.fontColor = this.parent.color(0); this.fontSize = 11; } GHistogram.prototype.updateArrays = function() { var i; var nPoints = this.plotPoints.length; // Remove unused points if (this.differences.length > nPoints) { this.differences.splice(nPoints, Number.MAX_VALUE); this.leftSides.splice(nPoints, Number.MAX_VALUE); this.rightSides.splice(nPoints, Number.MAX_VALUE); } // Update the arrays if (nPoints === 1) { this.leftSides[0] = (this.type === GPlot.VERTICAL) ? 0.2 * this.dim[0] : 0.2 * this.dim[1]; this.rightSides[0] = this.leftSides[0]; } else if (nPoints > 1) { // Calculate the differences between consecutive points for ( i = 0; i < nPoints - 1; i++) { if (this.plotPoints[i].isValid() && this.plotPoints[i + 1].isValid()) { var separation = this.separations[i % this.separations.length]; var diff; if (this.type === GPlot.VERTICAL) { diff = this.plotPoints[i + 1].getX() - this.plotPoints[i].getX(); } else { diff = this.plotPoints[i + 1].getY() - this.plotPoints[i].getY(); } if (diff > 0) { this.differences[i] = (diff - separation) / 2; } else { this.differences[i] = (diff + separation) / 2; } } else { this.differences[i] = 0; } } // Fill the leftSides and rightSides arrays this.leftSides[0] = this.differences[0]; this.rightSides[0] = this.differences[0]; for ( i = 1; i < nPoints - 1; i++) { this.leftSides[i] = this.differences[i - 1]; this.rightSides[i] = this.differences[i]; } this.leftSides[nPoints - 1] = this.differences[nPoints - 2]; this.rightSides[nPoints - 1] = this.differences[nPoints - 2]; } }; GHistogram.prototype.draw = function(plotBasePoint) { if (this.visible) { // Calculate the baseline for the histogram var baseline = 0; if (plotBasePoint.isValid()) { baseline = (this.type === GPlot.VERTICAL) ? plotBasePoint.getY() : plotBasePoint.getX(); } // Draw the rectangles var point, x1, x2, y1, y2, lw; var nPoints = this.plotPoints.length; this.parent.push(); this.parent.rectMode(this.parent.CORNERS); this.parent.strokeCap(this.parent.SQUARE); for (var i = 0; i < nPoints; i++) { point = this.plotPoints[i]; if (point.isValid()) { // Obtain the corners if (this.type === GPlot.VERTICAL) { x1 = point.getX() - this.leftSides[i]; x2 = point.getX() + this.rightSides[i]; y1 = point.getY(); y2 = baseline; } else { x1 = baseline; x2 = point.getX(); y1 = point.getY() - this.leftSides[i]; y2 = point.getY() + this.rightSides[i]; } if (x1 < 0) { x1 = 0; } else if (x1 > this.dim[0]) { x1 = this.dim[0]; } if (-y1 < 0) { y1 = 0; } else if (-y1 > this.dim[1]) { y1 = -this.dim[1]; } if (x2 < 0) { x2 = 0; } else if (x2 > this.dim[0]) { x2 = this.dim[0]; } if (-y2 < 0) { y2 = 0; } else if (-y2 > this.dim[1]) { y2 = -this.dim[1]; } // Draw the rectangle lw = this.lineWidths[i % this.lineWidths.length]; this.parent.fill(this.bgColors[i % this.bgColors.length]); this.parent.stroke(this.lineColors[i % this.lineColors.length]); this.parent.strokeWeight(lw); if (Math.abs(x2 - x1) > 2 * lw && Math.abs(y2 - y1) > 2 * lw) { this.parent.rect(x1, y1, x2, y2); } else if ((this.type === GPlot.VERTICAL && x2 !== x1 && !(y1 === y2 && (y1 === 0 || y1 === -this.dim[1]))) || (this.type === GPlot.HORIZONTAL && y2 !== y1 && !(x1 === x2 && (x1 === 0 || x1 === this.dim[0])))) { this.parent.rect(x1, y1, x2, y2); this.parent.line(x1, y1, x1, y2); this.parent.line(x2, y1, x2, y2); this.parent.line(x1, y1, x2, y1); this.parent.line(x1, y2, x2, y2); } } } this.parent.pop(); // Draw the labels if (this.drawLabels) { this.drawHistLabels(); } } }; GHistogram.prototype.drawHistLabels = function() { var point, i; var nPoints = this.plotPoints.length; var halfPI = 0.5 * Math.PI; this.parent.push(); this.parent.textFont(this.fontName); this.parent.textSize(this.fontSize); this.parent.fill(this.fontColor); this.parent.noStroke(); if (this.type === GPlot.VERTICAL) { if (this.rotateLabels) { this.parent.textAlign(this.parent.RIGHT, this.parent.CENTER); for ( i = 0; i < nPoints; i++) { point = this.plotPoints[i]; if (point.isValid() && point.getX() >= 0 && point.getX() <= this.dim[0]) { this.parent.push(); this.parent.translate(point.getX(), this.labelsOffset); this.parent.rotate(-halfPI); this.parent.text(point.getLabel(), 0, 0); this.parent.pop(); } } } else { this.parent.textAlign(this.parent.CENTER, this.parent.TOP); for ( i = 0; i < nPoints; i++) { point = this.plotPoints[i]; if (point.isValid() && point.getX() >= 0 && point.getX() <= this.dim[0]) { this.parent.text(point.getLabel(), point.getX(), this.labelsOffset); } } } } else { if (this.rotateLabels) { this.parent.textAlign(this.parent.CENTER, this.parent.BOTTOM); for ( i = 0; i < nPoints; i++) { point = this.plotPoints[i]; if (point.isValid() && -point.getY() >= 0 && -point.getY() <= this.dim[1]) { this.parent.push(); this.parent.translate(-this.labelsOffset, point.getY()); this.parent.rotate(-halfPI); this.parent.text(point.getLabel(), 0, 0); this.parent.pop(); } } } else { this.parent.textAlign(this.parent.RIGHT, this.parent.CENTER); for ( i = 0; i < nPoints; i++) { point = this.plotPoints[i]; if (point.isValid() && -point.getY() >= 0 && -point.getY() <= this.dim[1]) { this.parent.text(point.getLabel(), -this.labelsOffset, point.getY()); } } } } this.parent.pop(); }; GHistogram.prototype.setType = function(type) { if (type !== this.type && (type === GPlot.VERTICAL || type === GPlot.HORIZONTAL)) { this.type = type; this.updateArrays(); } }; GHistogram.prototype.setDim = function() { var xDim, yDim; if (arguments.length === 2) { xDim = arguments[0]; yDim = arguments[1]; } else if (arguments.length === 1) { xDim = arguments[0][0]; yDim = arguments[0][1]; } else { throw new Error("GHistogram.setDim(): signature not supported"); } if (xDim > 0 && yDim > 0) { this.dim[0] = xDim; this.dim[1] = yDim; this.updateArrays(); } }; GHistogram.prototype.setPlotPoints = function(plotPoints) { var i; var nPoints = plotPoints.length; if (this.plotPoints.length === nPoints) { for ( i = 0; i < nPoints; i++) { this.plotPoints[i].set(plotPoints[i]); } } else if (this.plotPoints.length > nPoints) { for ( i = 0; i < nPoints; i++) { this.plotPoints[i].set(plotPoints[i]); } this.plotPoints.splice(nPoints, Number.MAX_VALUE); } else { for ( i = 0; i < this.plotPoints.length; i++) { this.plotPoints[i].set(plotPoints[i]); } for ( i = this.plotPoints.lengh; i < nPoints; i++) { this.plotPoints[i] = new GPoint(plotPoints[i]); } } this.updateArrays(); }; GHistogram.prototype.setPlotPoint = function(index, plotPoint) { if (index < this.plotPoints.length) { this.plotPoints[index].set(plotPoint); } else if (index === this.plotPoints.length) { this.plotPoints[index] = new GPoint(plotPoint); } else { throw new Error("GHistogram.setPlotPoint(): the index position is outside the array size"); } this.updateArrays(); }; GHistogram.prototype.addPlotPoint = function() { if (arguments.length === 2) { this.plotPoints.push(new GPoint(arguments[0], arguments[1])); } else if (arguments.length === 1) { this.plotPoints.push(new GPoint(arguments[0])); } else { throw new Error("GHistogram.addPlotPoint(): signature not supported"); } this.updateArrays(); }; GHistogram.prototype.removePlotPoint = function(index) { if (index < this.plotPoints.length) { this.plotPoints.splice(index, 1); } else { throw new Error("GHistogram.removePlotPoint(): the index position is outside the array size"); } this.updateArrays(); }; GHistogram.prototype.setSeparations = function(separations) { this.separations = separations.slice(); this.updateArrays(); }; GHistogram.prototype.setBgColors = function(bgColors) { this.bgColors = bgColors.slice(); }; GHistogram.prototype.setLineColors = function(lineColors) { this.lineColors = lineColors.slice(); }; GHistogram.prototype.setLineWidths = function(lineWidths) { this.lineWidths = lineWidths.slice(); }; GHistogram.prototype.setVisible = function(visible) { this.visible = visible; }; GHistogram.prototype.setLabelsOffset = function(labelsOffset) { this.labelsOffset = labelsOffset; }; GHistogram.prototype.setDrawLabels = function(drawLabels) { this.drawLabels = drawLabels; }; GHistogram.prototype.setRotateLabels = function(rotateLabels) { this.rotateLabels = rotateLabels; }; GHistogram.prototype.setFontName = function(fontName) { this.fontName = fontName; }; GHistogram.prototype.setFontColor = function(fontColor) { this.fontColor = fontColor; }; GHistogram.prototype.setFontSize = function(fontSize) { if (fontSize > 0) { this.fontSize = fontSize; } }; GHistogram.prototype.setFontProperties = function(fontName, fontColor, fontSize) { if (fontSize > 0) { this.fontName = fontName; this.fontColor = fontColor; this.fontSize = fontSize; } }; /* * Layer class. A GLayer usually contains an array of points and a histogram */ function GLayer(parent, id, dim, xLim, yLim, xLog, yLog) { // The parent processing object this.parent = parent; // General properties this.id = id; this.dim = dim.slice(); this.xLim = xLim.slice(); this.yLim = yLim.slice(); this.xLog = xLog; this.yLog = yLog; // Do some sanity checks if (this.xLog && (this.xLim[0] <= 0 || this.xLim[1] <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); console.log("Will set horizontal limits to (0.1, 10)"); this.xLim[0] = 0.1; this.xLim[1] = 10; } if (this.yLog && (this.yLim[0] <= 0 || this.yLim[1] <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); console.log("Will set vertical limits to (0.1, 10)"); this.yLim[0] = 0.1; this.yLim[1] = 10; } // Points properties this.points = []; this.plotPoints = []; this.inside = []; this.pointColors = [this.parent.color(255, 0, 0, 150)]; this.pointSizes = [7]; // Line properties this.lineColor = this.parent.color(0, 150); this.lineWidth = 1; // Histogram properties this.hist = undefined; this.histBasePoint = new GPoint(0, 0); // Labels properties this.labelBgColor = this.parent.color(255, 200); this.labelSeparation = [7, 7]; this.fontName = "Helvetica"; this.fontColor = this.parent.color(0); this.fontSize = 11; // Helper variable this.cuts = [[0, 0], [0, 0], [0, 0], [0, 0]]; } GLayer.prototype.isValidNumber = function(number) { return !isNaN(number) && isFinite(number); }; GLayer.prototype.isId = function(someId) { return this.id === someId; }; GLayer.prototype.valueToXPlot = function(x) { if (this.xLog) { return this.dim[0] * Math.log(x / this.xLim[0]) / Math.log(this.xLim[1] / this.xLim[0]); } else { return this.dim[0] * (x - this.xLim[0]) / (this.xLim[1] - this.xLim[0]); } }; GLayer.prototype.valueToYPlot = function(y) { if (this.yLog) { return -this.dim[1] * Math.log(y / this.yLim[0]) / Math.log(this.yLim[1] / this.yLim[0]); } else { return -this.dim[1] * (y - this.yLim[0]) / (this.yLim[1] - this.yLim[0]); } }; GLayer.prototype.valueToPlot = function() { if (arguments.length === 2) { return [this.valueToXPlot(arguments[0]), this.valueToYPlot(arguments[1])]; } else if (arguments.length === 1 && arguments[0] instanceof GPoint) { return new GPoint(this.valueToXPlot(arguments[0].getX()), this.valueToYPlot(arguments[0].getY()), arguments[0].getLabel()); } else if (arguments.length === 1 && arguments[0] instanceof Array && arguments[0][0] instanceof GPoint) { var xScalingFactor, yScalingFactor, point, xPlot, yPlot, i; var nPoints = arguments[0].length; var plotPts = []; // Go case by case. More code, but it's faster if (this.xLog && this.yLog) { xScalingFactor = this.dim[0] / Math.log(this.xLim[1] / this.xLim[0]); yScalingFactor = -this.dim[1] / Math.log(this.yLim[1] / this.yLim[0]); for ( i = 0; i < nPoints; i++) { point = arguments[0][i]; xPlot = Math.log(point.getX() / this.xLim[0]) * xScalingFactor; yPlot = Math.log(point.getY() / this.yLim[0]) * yScalingFactor; plotPts[i] = new GPoint(xPlot, yPlot, point.getLabel()); } } else if (this.xLog) { xScalingFactor = this.dim[0] / Math.log(this.xLim[1] / this.xLim[0]); yScalingFactor = -this.dim[1] / (this.yLim[1] - this.yLim[0]); for ( i = 0; i < nPoints; i++) { point = arguments[0][i]; xPlot = Math.log(point.getX() / this.xLim[0]) * xScalingFactor; yPlot = (point.getY() - this.yLim[0]) * yScalingFactor; plotPts[i] = new GPoint(xPlot, yPlot, point.getLabel()); } } else if (this.yLog) { xScalingFactor = this.dim[0] / (this.xLim[1] - this.xLim[0]); yScalingFactor = -this.dim[1] / Math.log(this.yLim[1] / this.yLim[0]); for ( i = 0; i < nPoints; i++) { point = arguments[0][i]; xPlot = (point.getX() - this.xLim[0]) * xScalingFactor; yPlot = Math.log(point.getY() / this.yLim[0]) * yScalingFactor; plotPts[i] = new GPoint(xPlot, yPlot, point.getLabel()); } } else { xScalingFactor = this.dim[0] / (this.xLim[1] - this.xLim[0]); yScalingFactor = -this.dim[1] / (this.yLim[1] - this.yLim[0]); for ( i = 0; i < nPoints; i++) { point = arguments[0][i]; xPlot = (point.getX() - this.xLim[0]) * xScalingFactor; yPlot = (point.getY() - this.yLim[0]) * yScalingFactor; plotPts[i] = new GPoint(xPlot, yPlot, point.getLabel()); } } return plotPts; } else { throw new Error("GLayer.valueToPlot(): signature not supported"); } }; GLayer.prototype.updatePlotPoints = function() { var xScalingFactor, yScalingFactor, point, xPlot, yPlot, i; var nPoints = this.points.length; // Update the plotPoints array size if necessary if (this.plotPoints.length < nPoints) { for ( i = this.plotPoints.length; i < nPoints; i++) { this.plotPoints[i] = new GPoint(); } } else if (this.plotPoints.length > nPoints) { this.plotPoints.splice(nPoints, Number.MAX_VALUE); } // Go case by case. More code, but it should be faster if (this.xLog && this.yLog) { xScalingFactor = this.dim[0] / Math.log(this.xLim[1] / this.xLim[0]); yScalingFactor = -this.dim[1] / Math.log(this.yLim[1] / this.yLim[0]); for ( i = 0; i < nPoints; i++) { point = this.points[i]; xPlot = Math.log(point.getX() / this.xLim[0]) * xScalingFactor; yPlot = Math.log(point.getY() / this.yLim[0]) * yScalingFactor; this.plotPoints[i].set(xPlot, yPlot, point.getLabel()); } } else if (this.xLog) { xScalingFactor = this.dim[0] / Math.log(this.xLim[1] / this.xLim[0]); yScalingFactor = -this.dim[1] / (this.yLim[1] - this.yLim[0]); for ( i = 0; i < nPoints; i++) { point = this.points[i]; xPlot = Math.log(point.getX() / this.xLim[0]) * xScalingFactor; yPlot = (point.getY() - this.yLim[0]) * yScalingFactor; this.plotPoints[i].set(xPlot, yPlot, point.getLabel()); } } else if (this.yLog) { xScalingFactor = this.dim[0] / (this.xLim[1] - this.xLim[0]); yScalingFactor = -this.dim[1] / Math.log(this.yLim[1] / this.yLim[0]); for ( i = 0; i < nPoints; i++) { point = this.points[i]; xPlot = (point.getX() - this.xLim[0]) * xScalingFactor; yPlot = Math.log(point.getY() / this.yLim[0]) * yScalingFactor; this.plotPoints[i].set(xPlot, yPlot, point.getLabel()); } } else { xScalingFactor = this.dim[0] / (this.xLim[1] - this.xLim[0]); yScalingFactor = -this.dim[1] / (this.yLim[1] - this.yLim[0]); for ( i = 0; i < nPoints; i++) { point = this.points[i]; xPlot = (point.getX() - this.xLim[0]) * xScalingFactor; yPlot = (point.getY() - this.yLim[0]) * yScalingFactor; this.plotPoints[i].set(xPlot, yPlot, point.getLabel()); } } }; GLayer.prototype.xPlotToValue = function(xPlot) { if (this.xLog) { return Math.exp(Math.log(this.xLim[0]) + Math.log(this.xLim[1] / this.xLim[0]) * xPlot / this.dim[0]); } else { return this.xLim[0] + (this.xLim[1] - this.xLim[0]) * xPlot / this.dim[0]; } }; GLayer.prototype.yPlotToValue = function(yPlot) { if (this.yLog) { return Math.exp(Math.log(this.yLim[0]) - Math.log(this.yLim[1] / this.yLim[0]) * yPlot / this.dim[1]); } else { return this.yLim[0] - (this.yLim[1] - this.yLim[0]) * yPlot / this.dim[1]; } }; GLayer.prototype.plotToValue = function(xPlot, yPlot) { return [this.xPlotToValue(xPlot), this.yPlotToValue(yPlot)]; }; GLayer.prototype.isInside = function() { var xPlot, yPlot, valid; if (arguments.length === 2) { xPlot = arguments[0]; yPlot = arguments[1]; valid = this.isValidNumber(xPlot) && this.isValidNumber(yPlot); } else if (arguments.length === 1 && arguments[0] instanceof GPoint) { xPlot = arguments[0].getX(); yPlot = arguments[0].getY(); valid = arguments[0].isValid(); } else { throw new Error("GLayer.isInside(): signature not supported"); } return (valid) ? (xPlot >= 0) && (xPlot <= this.dim[0]) && (-yPlot >= 0) && (-yPlot <= this.dim[1]) : false; }; GLayer.prototype.updateInsideList = function() { var point; var nPoints = this.plotPoints.length; for (var i = 0; i < nPoints; i++) { point = this.plotPoints[i]; this.inside[i] = (point.isValid()) ? (point.getX() >= 0) && (point.getX() <= this.dim[0]) && (-point.getY() >= 0) && (-point.getY() <= this.dim[1]) : false; } // Remove the unused elements if (this.inside.length > nPoints) { this.inside.splice(nPoints, Number.MAX_VALUE); } }; GLayer.prototype.getPointIndexAtPlotPos = function(xPlot, yPlot) { var pointIndex; if (this.isInside(xPlot, yPlot)) { var point, distSq; var minDistSq = 25; var nPoints = this.plotPoints.length; for (var i = 0; i < nPoints; i++) { if (this.inside[i]) { point = this.plotPoints[i]; distSq = Math.pow(point.getX() - xPlot, 2) + Math.pow(point.getY() - yPlot, 2); if (distSq < minDistSq) { minDistSq = distSq; pointIndex = i; } } } } return pointIndex; }; GLayer.prototype.getPointAtPlotPos = function(xPlot, yPlot) { return this.points[this.getPointIndexAtPlotPos(xPlot, yPlot)]; }; GLayer.prototype.obtainBoxIntersections = function(plotPoint1, plotPoint2) { var nCuts = 0; if (plotPoint1.isValid() && plotPoint2.isValid()) { var x1 = plotPoint1.getX(); var y1 = plotPoint1.getY(); var x2 = plotPoint2.getX(); var y2 = plotPoint2.getY(); var inside1 = this.isInside(x1, y1); var inside2 = this.isInside(x2, y2); // Check if the line between the two points could cut the box borders var dontCut = (inside1 && inside2) || (x1 < 0 && x2 < 0) || (x1 > this.dim[0] && x2 > this.dim[0]) || (-y1 < 0 && -y2 < 0) || (-y1 > this.dim[1] && -y2 > this.dim[1]); if (!dontCut) { // Obtain the axis cuts of the line that cross the two points var deltaX = x2 - x1; var deltaY = y2 - y1; if (deltaX === 0) { nCuts = 2; this.cuts[0][0] = x1; this.cuts[0][1] = 0; this.cuts[1][0] = x1; this.cuts[1][1] = -this.dim[1]; } else if (deltaY === 0) { nCuts = 2; this.cuts[0][0] = 0; this.cuts[0][1] = y1; this.cuts[1][0] = this.dim[0]; this.cuts[1][1] = y1; } else { // Obtain the straight line (y = yCut + slope*x) that // crosses the two points var slope = deltaY / deltaX; var yCut = y1 - slope * x1; // Calculate the axis cuts of that line nCuts = 4; this.cuts[0][0] = -yCut / slope; this.cuts[0][1] = 0; this.cuts[1][0] = (-this.dim[1] - yCut) / slope; this.cuts[1][1] = -this.dim[1]; this.cuts[2][0] = 0; this.cuts[2][1] = yCut; this.cuts[3][0] = this.dim[0]; this.cuts[3][1] = yCut + slope * this.dim[0]; } // Select only the cuts that fall inside the box and are located // between the two points nCuts = this.getValidCuts(this.cuts, nCuts, plotPoint1, plotPoint2); // Make sure we have the correct number of cuts if (inside1 || inside2) { // One of the points is inside. We should have one cut only if (nCuts !== 1) { var pointInside = (inside1) ? plotPoint1 : plotPoint2; // If too many cuts if (nCuts > 1) { nCuts = this.removeDuplicatedCuts(this.cuts, nCuts, 0); if (nCuts > 1) { nCuts = this.removePointFromCuts(this.cuts, nCuts, pointInside, 0); // In case of rounding number errors if (nCuts > 1) { nCuts = this.removeDuplicatedCuts(this.cuts, nCuts, 0.001); if (nCuts > 1) { nCuts = this.removePointFromCuts(this.cuts, nCuts, pointInside, 0.001); } } } } // If the cut is missing, then it must be equal to the point // inside if (nCuts === 0) { nCuts = 1; this.cuts[0][0] = pointInside.getX(); this.cuts[1][0] = pointInside.getY(); } } } else { // Both points are outside. We should have either two cuts or // none if (nCuts > 2) { nCuts = this.removeDuplicatedCuts(this.cuts, nCuts, 0); // In case of rounding number errors if (nCuts > 2) { nCuts = this.removeDuplicatedCuts(this.cuts, nCuts, 0.001); } } // If we have two cuts, order them (the closest to the first // point goes first) if (nCuts === 2) { if ((Math.pow(this.cuts[0][0] - x1, 2) + Math.pow(this.cuts[0][1] - y1), 2) > (Math.pow(this.cuts[1][0] - x1, 2) + Math.pow(this.cuts[1][1] - y1, 2))) { this.cuts[2][0] = this.cuts[0][0]; this.cuts[2][1] = this.cuts[0][1]; this.cuts[0][0] = this.cuts[1][0]; this.cuts[0][1] = this.cuts[1][1]; this.cuts[1][0] = this.cuts[2][0]; this.cuts[1][1] = this.cuts[2][1]; } } // If one cut is missing, add the same one twice if (nCuts === 1) { nCuts = 2; this.cuts[1][0] = this.cuts[0][0]; this.cuts[1][1] = this.cuts[0][1]; } } // Some sanity checks if ((inside1 || inside2) && nCuts !== 1) { console.log("There should be one cut!!!"); } else if (!inside1 && !inside2 && nCuts !== 0 && nCuts !== 2) { console.log("There should be either 0 or 2 cuts!!! " + nCuts + " were found"); } } } return nCuts; }; GLayer.prototype.getValidCuts = function(cuts, nCuts, plotPoint1, plotPoint2) { var x1 = plotPoint1.getX(); var y1 = plotPoint1.getY(); var x2 = plotPoint2.getX(); var y2 = plotPoint2.getY(); var deltaX = Math.abs(x2 - x1); var deltaY = Math.abs(y2 - y1); var counter = 0; for (var i = 0; i < nCuts; i++) { // Check that the cut is inside the inner plotting area if (this.isInside(cuts[i][0], cuts[i][1])) { // Check that the cut falls between the two points if (Math.abs(cuts[i][0] - x1) <= deltaX && Math.abs(cuts[i][1] - y1) <= deltaY && Math.abs(cuts[i][0] - x2) <= deltaX && Math.abs(cuts[i][1] - y2) <= deltaY) { cuts[counter][0] = cuts[i][0]; cuts[counter][1] = cuts[i][1]; counter++; } } } return counter; }; GLayer.prototype.removeDuplicatedCuts = function(cuts, nCuts, tolerance) { var repeated; var counter = 0; for (var i = 0; i < nCuts; i++) { repeated = false; for (var j = 0; j < counter; j++) { if (Math.abs(cuts[j][0] - cuts[i][0]) <= tolerance && Math.abs(cuts[j][1] - cuts[i][1]) <= tolerance) { repeated = true; break; } } if (!repeated) { cuts[counter][0] = cuts[i][0]; cuts[counter][1] = cuts[i][1]; counter++; } } return counter; }; GLayer.prototype.removePointFromCuts = function(cuts, nCuts, plotPoint, tolerance) { var x = plotPoint.getX(); var y = plotPoint.getY(); var counter = 0; for (var i = 0; i < nCuts; i++) { if (Math.abs(cuts[i][0] - x) > tolerance || Math.abs(cuts[i][1] - y) > tolerance) { cuts[counter][0] = cuts[i][0]; cuts[counter][1] = cuts[i][1]; counter++; } } return counter; }; GLayer.prototype.startHistogram = function(histType) { this.hist = new GHistogram(this.parent, histType, this.dim, this.plotPoints); }; GLayer.prototype.drawPoints = function() { var nPoints, i; if (arguments.length === 0) { nPoints = this.plotPoints.length; var nColors = this.pointColors.length; var nSizes = this.pointSizes.length; this.parent.push(); this.parent.ellipseMode(this.parent.CENTER); this.parent.stroke(50); if (nColors === 1 && nSizes === 1) { this.parent.fill(this.pointColors[0]); for ( i = 0; i < nPoints; i++) { if (this.inside[i]) { this.parent.ellipse(this.plotPoints[i].getX(), this.plotPoints[i].getY(), this.pointSizes[0], this.pointSizes[0]); } } } else if (nColors === 1) { this.parent.fill(this.pointColors[0]); for ( i = 0; i < nPoints; i++) { if (this.inside[i]) { this.parent.fill(0); this.parent.noStroke(); this.parent.ellipse(this.plotPoints[i].getX(), this.plotPoints[i].getY(),4,4); this.parent.stroke(100); this.parent.fill(this.pointColors[0]); this.parent.ellipse(this.plotPoints[i].getX(), this.plotPoints[i].getY(), this.pointSizes[i % nSizes], this.pointSizes[i % nSizes]); } } } else if (nSizes === 1) { for ( i = 0; i < nPoints; i++) { if (this.inside[i]) { this.parent.fill(this.pointColors[i % nColors]); this.parent.ellipse(this.plotPoints[i].getX(), this.plotPoints[i].getY(), this.pointSizes[0], this.pointSizes[0]); } } } else { for ( i = 0; i < nPoints; i++) { if (this.inside[i]) { this.parent.fill(this.pointColors[i % nColors]); this.parent.ellipse(this.plotPoints[i].getX(), this.plotPoints[i].getY(), this.pointSizes[i % nSizes], this.pointSizes[i % nSizes]); } } } this.parent.pop(); } else if (arguments.length === 1 && arguments[0] instanceof p5.Image) { nPoints = this.plotPoints.length; this.parent.push(); this.parent.imageMode(this.parent.CENTER); for ( i = 0; i < nPoints; i++) { if (this.inside[i]) { this.parent.image(arguments[0], this.plotPoints[i].getX(), this.plotPoints[i].getY()); } } this.parent.pop(); } else { throw new Error("GLayer.drawPoints(): signature not supported"); } }; GLayer.prototype.drawPoint = function() { var point, pointColor, pointSize, pointImg; if (arguments.length === 3) { point = arguments[0]; pointColor = arguments[1]; pointSize = arguments[2]; } else if (arguments.length === 2 && arguments[1] instanceof p5.Image) { point = arguments[0]; pointImg = arguments[1]; } else if (arguments.length === 1) { point = arguments[0]; pointColor = this.pointColors[0]; pointSize = this.pointSizes[0]; } else { throw new Error("GLayer.drawPoint(): signature not supported"); } var xPlot = this.valueToXPlot(point.getX()); var yPlot = this.valueToYPlot(point.getY()); if (this.isInside(xPlot, yPlot)) { this.parent.push(); if ( typeof pointImg !== "undefined") { this.parent.imageMode(this.parent.CENTER); this.parent.image(pointImg, xPlot, yPlot); } else { this.parent.ellipseMode(this.parent.CENTER); this.parent.fill(pointColor); this.parent.noStroke(); this.parent.ellipse(xPlot, yPlot, pointSize, pointSize); } this.parent.pop(); } }; GLayer.prototype.drawLines = function() { var nPoints = this.plotPoints.length; this.parent.push(); this.parent.noFill(); this.parent.stroke(this.lineColor); this.parent.strokeWeight(this.lineWidth); this.parent.strokeCap(this.parent.SQUARE); for (var i = 0; i < nPoints - 1; i++) { if (this.inside[i] && this.inside[i + 1]) { this.parent.line(this.plotPoints[i].getX(), this.plotPoints[i].getY(), this.plotPoints[i + 1].getX(), this.plotPoints[i + 1].getY()); } else if (this.plotPoints[i].isValid() && this.plotPoints[i + 1].isValid()) { // At least one of the points is outside the inner region. // Obtain the valid line box intersections var nCuts = this.obtainBoxIntersections(this.plotPoints[i], this.plotPoints[i + 1]); if (this.inside[i]) { this.parent.line(this.plotPoints[i].getX(), this.plotPoints[i].getY(), this.cuts[0][0], this.cuts[0][1]); } else if (this.inside[i + 1]) { this.parent.line(this.cuts[0][0], this.cuts[0][1], this.plotPoints[i + 1].getX(), this.plotPoints[i + 1].getY()); } else if (nCuts >= 2) { this.parent.line(this.cuts[0][0], this.cuts[0][1], this.cuts[1][0], this.cuts[1][1]); } } } this.parent.pop(); }; GLayer.prototype.drawLine = function() { var point1, point2, lc, lw, slope, ycut; if (arguments.length === 4 && arguments[0] instanceof GPoint) { point1 = arguments[0]; point2 = arguments[1]; lc = arguments[2]; lw = arguments[3]; } else if (arguments.length === 4) { slope = arguments[0]; ycut = arguments[1]; lc = arguments[2]; lw = arguments[3]; } else if (arguments.length === 2 && arguments[0] instanceof GPoint) { point1 = arguments[0]; point2 = arguments[1]; lc = this.lineColor; lw = this.lineWidth; } else if (arguments.length === 2) { slope = arguments[0]; ycut = arguments[1]; lc = this.lineColor; lw = this.lineWidth; } else { throw new Error("GLayer.drawLine(): signature not supported"); } if ( typeof slope !== "undefined") { if (this.xLog && this.yLog) { point1 = new GPoint(this.xLim[0], Math.pow(10, slope * Math.log(this.xLim[0]) / Math.LN10 + yCut)); point2 = new GPoint(this.xLim[1], Math.pow(10, slope * Math.log(this.xLim[1]) / Math.LN10 + yCut)); } else if (this.xLog) { point1 = new GPoint(this.xLim[0], slope * Math.log(this.xLim[0]) / Math.LN10 + yCut); point2 = new GPoint(this.xLim[1], slope * Math.log(this.xLim[1]) / Math.LN10 + yCut); } else if (this.yLog) { point1 = new GPoint(this.xLim[0], Math.pow(10, slope * this.xLim[0] + yCut)); point2 = new GPoint(this.xLim[1], Math.pow(10, slope * this.xLim[1] + yCut)); } else { point1 = new GPoint(this.xLim[0], slope * this.xLim[0] + yCut); point2 = new GPoint(this.xLim[1], slope * this.xLim[1] + yCut); } } var plotPoint1 = this.valueToPlot(point1); var plotPoint2 = this.valueToPlot(point2); if (plotPoint1.isValid() && plotPoint2.isValid()) { var inside1 = this.isInside(plotPoint1); var inside2 = this.isInside(plotPoint2); this.parent.push(); this.parent.noFill(); this.parent.stroke(lc); this.parent.strokeWeight(lw); this.parent.strokeCap(this.parent.SQUARE); if (inside1 && inside2) { this.parent.line(plotPoint1.getX(), plotPoint1.getY(), plotPoint2.getX(), plotPoint2.getY()); } else { // At least one of the points is outside the inner region. // Obtain the valid line box intersections var nCuts = this.obtainBoxIntersections(plotPoint1, plotPoint2); if (inside1) { this.parent.line(plotPoint1.getX(), plotPoint1.getY(), this.cuts[0][0], this.cuts[0][1]); } else if (inside2) { this.parent.line(this.cuts[0][0], this.cuts[0][1], plotPoint2.getX(), plotPoint2.getY()); } else if (nCuts >= 2) { this.parent.line(this.cuts[0][0], this.cuts[0][1], this.cuts[1][0], this.cuts[1][1]); } } this.parent.pop(); } }; GLayer.prototype.drawHorizontalLine = function() { var value, lc, lw; if (arguments.length === 3) { value = arguments[0]; lc = arguments[1]; lw = arguments[2]; } else if (arguments.length === 1) { value = arguments[0]; lc = this.lineColor; lw = this.lineWidth; } else { throw new Error("GLayer.drawHorizontalLine(): signature not supported"); } var yPlot = this.valueToYPlot(value); if (this.isValidNumber(yPlot) && -yPlot >= 0 && -yPlot <= this.dim[1]) { this.parent.push(); this.parent.noFill(); this.parent.stroke(lc); this.parent.strokeWeight(lw); this.parent.strokeCap(this.parent.SQUARE); this.parent.line(0, yPlot, this.dim[0], yPlot); this.parent.pop(); } }; GLayer.prototype.drawVerticalLine = function() { var value, lc, lw; if (arguments.length === 3) { value = arguments[0]; lc = arguments[1]; lw = arguments[2]; } else if (arguments.length === 1) { value = arguments[0]; lc = this.lineColor; lw = this.lineWidth; } else { throw new Error("GLayer.drawVerticalLine(): signature not supported"); } var xPlot = this.valueToXPlot(value); if (this.isValidNumber(xPlot) && xPlot >= 0 && xPlot <= this.dim[0]) { this.parent.push(); this.parent.noFill(); this.parent.stroke(lc); this.parent.strokeWeight(lw); this.parent.strokeCap(this.parent.SQUARE); this.parent.line(xPlot, 0, xPlot, -this.dim[1]); this.parent.pop(); } }; GLayer.prototype.drawFilledContour = function(contourType, referenceValue) { // Get the points that compose the shape var shapePoints; if (contourType === GPlot.HORIZONTAL) { shapePoints = this.getHorizontalShape(referenceValue); } else if (contourType === GPlot.VERTICAL) { shapePoints = this.getVerticalShape(referenceValue); } // Draw the shape if ( typeof shapePoints !== "undefined" && shapePoints.length > 0) { this.parent.push(); this.parent.fill(this.lineColor); this.parent.noStroke(); this.parent.beginShape(); for (var i = 0; i < shapePoints.length; i++) { if (shapePoints[i].isValid()) { this.parent.vertex(shapePoints[i].getX(), shapePoints[i].getY()); } } this.parent.endShape(this.parent.CLOSE); this.parent.pop(); } }; GLayer.prototype.getHorizontalShape = function(referenceValue) { // Collect the points and cuts inside the box var point, addedPoints, nextIndex; var nPoints = this.plotPoints.length; var shapePoints = []; var indexFirstPoint = -1; var indexLastPoint = -1; for (var i = 0; i < nPoints; i++) { point = this.plotPoints[i]; if (point.isValid()) { addedPoints = false; // Add the point if it's inside the box if (this.inside[i]) { shapePoints.push(new GPoint(point.getX(), point.getY(), "normal point")); addedPoints = true; } else if (point.getX() >= 0 && point.getX() <= this.dim[0]) { // If it's outside, add the projection of the point on the // horizontal axes if (-point.getY() < 0) { shapePoints.push(new GPoint(point.getX(), 0, "projection")); addedPoints = true; } else { shapePoints.push(new GPoint(point.getX(), -this.dim[1], "projection")); addedPoints = true; } } // Add the box cuts if there is any nextIndex = i + 1; while (nextIndex < nPoints - 1 && !this.plotPoints[nextIndex].isValid()) { nextIndex++; } if (nextIndex < nPoints && this.plotPoints[nextIndex].isValid()) { var nCuts = this.obtainBoxIntersections(point, this.plotPoints[nextIndex]); for (var j = 0; j < nCuts; j++) { shapePoints.push(new GPoint(this.cuts[j][0], this.cuts[j][1], "cut")); addedPoints = true; } } if (addedPoints) { if (indexFirstPoint < 0) { indexFirstPoint = i; } indexLastPoint = i; } } } // Continue if there are points in the shape if (shapePoints.length > 0) { // Calculate the starting point var startPoint = new GPoint(shapePoints[0]); if (startPoint.getX() !== 0 && startPoint.getX() !== this.dim[0]) { if (startPoint.getLabel() === "cut") { if (this.plotPoints[indexFirstPoint].getX() < 0) { startPoint.setX(0); startPoint.setLabel("extreme"); } else { startPoint.setX(this.dim[0]); startPoint.setLabel("extreme"); } } else if (indexFirstPoint !== 0) { // Get the previous valid point var prevIndex = indexFirstPoint - 1; while (prevIndex > 0 && !this.plotPoints[prevIndex].isValid()) { prevIndex--; } if (this.plotPoints[prevIndex].isValid()) { if (this.plotPoints[prevIndex].getX() < 0) { startPoint.setX(0); startPoint.setLabel("extreme"); } else { startPoint.setX(this.dim[0]); startPoint.setLabel("extreme"); } } } } // Calculate the end point var endPoint = new GPoint(shapePoints[shapePoints.length - 1]); if (endPoint.getX() !== 0 && endPoint.getX() !== this.dim[0] && indexLastPoint !== nPoints - 1) { nextIndex = indexLastPoint + 1; while (nextIndex < nPoints - 1 && !this.plotPoints[nextIndex].isValid()) { nextIndex++; } if (this.plotPoints[nextIndex].isValid()) { if (this.plotPoints[nextIndex].getX() < 0) { endPoint.setX(0); endPoint.setLabel("extreme"); } else { endPoint.setX(this.dim[0]); endPoint.setLabel("extreme"); } } } // Add the end point if it's a new extreme if (endPoint.getLabel() === "extreme") { shapePoints.push(endPoint); } // Add the reference connections if (this.yLog && referenceValue <= 0) { referenceValue = Math.min(this.yLim[0], this.yLim[1]); } var plotReference = this.valueToPlot(1, referenceValue); if (-plotReference[1] < 0) { shapePoints.push(new GPoint(endPoint.getX(), 0)); shapePoints.push(new GPoint(startPoint.getX(), 0)); } else if (-plotReference[1] > this.dim[1]) { shapePoints.push(new GPoint(endPoint.getX(), -this.dim[1])); shapePoints.push(new GPoint(startPoint.getX(), -this.dim[1])); } else { shapePoints.push(new GPoint(endPoint.getX(), plotReference[1])); shapePoints.push(new GPoint(startPoint.getX(), plotReference[1])); } // Add the starting point if it's a new extreme if (startPoint.getLabel() === "extreme") { shapePoints.push(startPoint); } } return shapePoints; }; GLayer.prototype.getVerticalShape = function(referenceValue) { // Collect the points and cuts inside the box var point, addedPoints, nextIndex; var nPoints = this.plotPoints.length; var shapePoints = []; var indexFirstPoint = -1; var indexLastPoint = -1; for (var i = 0; i < nPoints; i++) { point = this.plotPoints[i]; if (point.isValid()) { addedPoints = false; // Add the point if it's inside the box if (this.inside[i]) { shapePoints.push(new GPoint(point.getX(), point.getY(), "normal point")); addedPoints = true; } else if (-point.getY() >= 0 && -point.getY() <= this.dim[1]) { // If it's outside, add the projection of the point on the // vertical axes if (point.getX() < 0) { shapePoints.push(new GPoint(0, point.getY(), "projection")); addedPoints = true; } else { shapePoints.push(new GPoint(this.dim[0], point.getY(), "projection")); addedPoints = true; } } // Add the box cuts if there is any nextIndex = i + 1; while (nextIndex < nPoints - 1 && !this.plotPoints[nextIndex].isValid()) { nextIndex++; } if (nextIndex < nPoints && this.plotPoints[nextIndex].isValid()) { var nCuts = this.obtainBoxIntersections(point, this.plotPoints[nextIndex]); for (var j = 0; j < nCuts; j++) { shapePoints.push(new GPoint(this.cuts[j][0], this.cuts[j][1], "cut")); addedPoints = true; } } if (addedPoints) { if (indexFirstPoint < 0) { indexFirstPoint = i; } indexLastPoint = i; } } } // Continue if there are points in the shape if (shapePoints.length > 0) { // Calculate the starting point var startPoint = new GPoint(shapePoints[0]); if (startPoint.getY() !== 0 && startPoint.getY() !== -this.dim[1]) { if (startPoint.getLabel() === "cut") { if (-this.plotPoints[indexFirstPoint].getY() < 0) { startPoint.setY(0); startPoint.setLabel("extreme"); } else { startPoint.setY(-this.dim[1]); startPoint.setLabel("extreme"); } } else if (indexFirstPoint !== 0) { // Get the previous valid point var prevIndex = indexFirstPoint - 1; while (prevIndex > 0 && !this.plotPoints[prevIndex].isValid()) { prevIndex--; } if (this.plotPoints[prevIndex].isValid()) { if (-this.plotPoints[prevIndex].getY() < 0) { startPoint.setY(0); startPoint.setLabel("extreme"); } else { startPoint.setY(-this.dim[1]); startPoint.setLabel("extreme"); } } } } // Calculate the end point var endPoint = new GPoint(shapePoints[shapePoints.length - 1]); if (endPoint.getY() !== 0 && endPoint.getY() !== -this.dim[1] && indexLastPoint !== nPoints - 1) { nextIndex = indexLastPoint + 1; while (nextIndex < nPoints - 1 && !this.plotPoints[nextIndex].isValid()) { nextIndex++; } if (this.plotPoints[nextIndex].isValid()) { if (-this.plotPoints[nextIndex].getY() < 0) { endPoint.setY(0); endPoint.setLabel("extreme"); } else { endPoint.setY(-this.dim[1]); endPoint.setLabel("extreme"); } } } // Add the end point if it's a new extreme if (endPoint.getLabel() === "extreme") { shapePoints.push(endPoint); } // Add the reference connections if (this.xLog && referenceValue <= 0) { referenceValue = Math.min(this.xLim[0], this.xLim[1]); } var plotReference = this.valueToPlot(referenceValue, 1); if (plotReference[0] < 0) { shapePoints.push(new GPoint(0, endPoint.getY())); shapePoints.push(new GPoint(0, startPoint.getY())); } else if (plotReference[0] > this.dim[0]) { shapePoints.push(new GPoint(this.dim[0], endPoint.getY())); shapePoints.push(new GPoint(this.dim[0], startPoint.getY())); } else { shapePoints.push(new GPoint(plotReference[0], endPoint.getY())); shapePoints.push(new GPoint(plotReference[0], startPoint.getY())); } // Add the starting point if it's a new extreme if (startPoint.getLabel() === "extreme") { shapePoints.push(startPoint); } } return shapePoints; }; GLayer.prototype.drawAllLabels = function() { for (var n = 0; n < this.points.length; n++) { this.drawLabel(this.points[n]); } } GLayer.prototype.drawLabel = function(point) { var xPlot = this.valueToXPlot(point.getX()); var yPlot = this.valueToYPlot(point.getY()); if (this.isValidNumber(xPlot) && this.isValidNumber(yPlot)) { var xLabelPos = xPlot + this.labelSeparation[0]; var yLabelPos = yPlot - 0*this.labelSeparation[1] + 6; var delta = this.fontSize / 4; this.parent.push(); this.parent.rectMode(this.parent.CORNER); this.parent.noStroke(); this.parent.textFont(this.fontName); this.parent.textSize(this.fontSize); this.parent.textAlign(this.parent.LEFT, this.parent.BOTTOM); // Draw the background this.parent.fill(this.labelBgColor); this.parent.rect(xLabelPos - delta, yLabelPos - this.fontSize - delta, this.parent.textWidth(point.getLabel()) + 2 * delta, this.fontSize + 2 * delta); // Draw the text this.parent.fill(this.fontColor); this.parent.text(point.getLabel(), xLabelPos, yLabelPos); this.parent.pop(); } }; GLayer.prototype.drawLabelAtPlotPos = function(xPlot, yPlot) { var point = this.getPointAtPlotPos(xPlot, yPlot); if ( typeof point !== "undefined") { this.drawLabel(point); } }; GLayer.prototype.drawHistogram = function() { if ( typeof this.hist !== "undefined") { this.hist.draw(this.valueToPlot(this.histBasePoint)); } }; GLayer.prototype.drawPolygon = function(polygonPoints, polygonColor) { var i; if (polygonPoints.length > 2) { // Remove the polygon invalid points var plotPolygonPoints = this.valueToPlot(polygonPoints); var counter = 0; for ( i = 0; i < plotPolygonPoints.length; i++) { if (plotPolygonPoints[i].isValid()) { plotPolygonPoints[counter] = plotPolygonPoints[i]; counter++; } } plotPolygonPoints.splice(counter, Number.MAX_VALUE); // Create a temporal array with the points inside the plotting area // and the valid box cuts var point; var nPoints = plotPolygonPoints.length; var tmp = []; for ( i = 0; i < nPoints; i++) { point = plotPolygonPoints[i]; if (this.isInside(point)) { tmp.push(new GPoint(point.getX(), point.getY(), "normal point")); } // Obtain the cuts with the next point var nextIndex = (i + 1 < nPoints) ? i + 1 : 0; var nCuts = this.obtainBoxIntersections(point, plotPolygonPoints[nextIndex]); if (nCuts === 1) { tmp.push(new GPoint(this.cuts[0][0], this.cuts[0][1], "single cut")); } else if (nCuts > 1) { tmp.push(new GPoint(this.cuts[0][0], this.cuts[0][1], "double cut")); tmp.push(new GPoint(this.cuts[1][0], this.cuts[1][1], "double cut")); } } // Final version of the polygon nPoints = tmp.length; var croppedPoly = []; for ( i = 0; i < nPoints; i++) { // Add the point croppedPoly.push(tmp[i]); // Add new points in case we have two consecutive cuts, one of // them is single, and they are in consecutive axes var next = (i + 1 < nPoints) ? i + 1 : 0; var label = tmp[i].getLabel(); var nextLabel = tmp[next].getLabel(); var cond = (label === "single cut" && nextLabel === "single cut") || (label === "single cut" && nextLabel === "double cut") || (label === "double cut" && nextLabel === "single cut"); if (cond) { var x1 = tmp[i].getX(); var y1 = tmp[i].getY(); var x2 = tmp[next].getX(); var y2 = tmp[next].getY(); var deltaX = Math.abs(x2 - x1); var deltaY = Math.abs(y2 - y1); // Check that they come from consecutive axes if (deltaX > 0 && deltaY > 0 && deltaX !== this.dim[0] && deltaY !== this.dim[1]) { var x = (x1 === 0 || x1 === this.dim[0]) ? x1 : x2; var y = (y1 === 0 || y1 === -this.dim[1]) ? y1 : y2; croppedPoly.push(new GPoint(x, y, "special cut")); } } } // Draw the cropped polygon if (croppedPoly.length > 2) { this.parent.push(); this.parent.fill(polygonColor); this.parent.noStroke(); this.parent.beginShape(); for ( i = 0; i < croppedPoly.length; i++) { this.parent.vertex(croppedPoly[i].getX(), croppedPoly[i].getY()); } this.parent.endShape(this.parent.CLOSE); this.parent.pop(); } } }; GLayer.prototype.drawAnnotation = function(text, x, y, horAlign, verAlign) { var xPlot = this.valueToXPlot(x); var yPlot = this.valueToYPlot(y); if (this.isValidNumber(xPlot) && this.isValidNumber(yPlot) && this.isInside(xPlot, yPlot)) { if (horAlign !== this.parent.CENTER && horAlign !== this.parent.RIGHT && horAlign !== this.parent.LEFT) { horAlign = this.parent.LEFT; } if (verAlign !== this.parent.CENTER && verAlign !== this.parent.TOP && verAlign !== this.parent.BOTTOM) { verAlign = this.parent.CENTER; } // A trick to really center the text vertically if (verAlign === this.parent.CENTER) { verAlign = this.parent.BOTTOM; yPlot += this.fontSize / 2; } this.parent.push(); this.parent.textFont(this.fontName); this.parent.textSize(this.fontSize); this.parent.fill(this.fontColor); this.parent.textAlign(horAlign, verAlign); this.parent.text(text, xPlot, yPlot); this.parent.pop(); } }; GLayer.prototype.setDim = function() { var xDim, yDim; if (arguments.length === 2) { xDim = arguments[0]; yDim = arguments[1]; } else if (arguments.length === 1) { xDim = arguments[0][0]; yDim = arguments[0][1]; } else { throw new Error("GLayer.setDim(): signature not supported"); } if (xDim > 0 && yDim > 0) { this.dim[0] = xDim; this.dim[1] = yDim; this.updatePlotPoints(); if ( typeof this.hist !== "undefined") { this.hist.setDim(this.dim); this.hist.setPlotPoints(this.plotPoints); } } }; GLayer.prototype.setXLim = function() { var xMin, xMax; if (arguments.length === 2) { xMin = arguments[0]; xMax = arguments[1]; } else if (arguments.length === 1) { xMin = arguments[0][0]; xMax = arguments[0][1]; } else { throw new Error("GLayer.setXLim(): signature not supported"); } if (xMin !== xMax && this.isValidNumber(xMin) && this.isValidNumber(xMax)) { // Make sure the new limits makes sense if (this.xLog && (xMin <= 0 || xMax <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); } else { this.xLim[0] = xMin; this.xLim[1] = xMax; this.updatePlotPoints(); this.updateInsideList(); if ( typeof this.hist !== "undefined") { this.hist.setPlotPoints(this.plotPoints); } } } }; GLayer.prototype.setYLim = function() { var yMin, yMax; if (arguments.length === 2) { yMin = arguments[0]; yMax = arguments[1]; } else if (arguments.length === 1) { yMin = arguments[0][0]; yMax = arguments[0][1]; } else { throw new Error("GLayer.setYLim(): signature not supported"); } if (yMin !== yMax && this.isValidNumber(yMin) && this.isValidNumber(yMax)) { // Make sure the new limits makes sense if (this.yLog && (yMin <= 0 || yMax <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); } else { this.yLim[0] = yMin; this.yLim[1] = yMax; this.updatePlotPoints(); this.updateInsideList(); if ( typeof this.hist !== "undefined") { this.hist.setPlotPoints(this.plotPoints); } } } }; GLayer.prototype.setXYLim = function() { var xMin, xMax, yMin, yMax; if (arguments.length === 4) { xMin = arguments[0]; xMax = arguments[1]; yMin = arguments[2]; yMax = arguments[3]; } else if (arguments.length === 2) { xMin = arguments[0][0]; xMax = arguments[0][1]; yMin = arguments[1][0]; yMax = arguments[1][1]; } else { throw new Error("GLayer.setXYLim(): signature not supported"); } if (xMin !== xMax && yMin !== yMax && this.isValidNumber(xMin) && this.isValidNumber(xMax) && this.isValidNumber(yMin) && this.isValidNumber(yMax)) { // Make sure the new limits make sense if (this.xLog && (xMin <= 0 || xMax <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); } else { this.xLim[0] = xMin; this.xLim[1] = xMax; } if (this.yLog && (yMin <= 0 || yMax <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); } else { this.yLim[0] = yMin; this.yLim[1] = yMax; } this.updatePlotPoints(); this.updateInsideList(); if ( typeof this.hist !== "undefined") { this.hist.setPlotPoints(this.plotPoints); } } }; GLayer.prototype.setLimAndLog = function() { var xMin, xMax, yMin, yMax, xLog, yLog; if (arguments.length === 6) { xMin = arguments[0]; xMax = arguments[1]; yMin = arguments[2]; yMax = arguments[3]; xLog = arguments[4]; yLog = arguments[5]; } else if (arguments.length === 4) { xMin = arguments[0][0]; xMax = arguments[0][1]; yMin = arguments[1][0]; yMax = arguments[1][1]; xLog = arguments[2]; yLog = arguments[3]; } else { throw new Error("GLayer.setLimAndLog(): signature not supported"); } if (xMin !== xMax && yMin !== yMax && this.isValidNumber(xMin) && this.isValidNumber(xMax) && this.isValidNumber(yMin) && this.isValidNumber(yMax)) { // Make sure the new limits make sense if (xLog && (xMin <= 0 || xMax <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); } else { this.xLim[0] = xMin; this.yLim[1] = xMax; this.xLog = xLog; } if (yLog && (yMin <= 0 || yMax <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); } else { this.yLim[0] = yMin; this.yLim[1] = yMax; this.yLog = yLog; } this.updatePlotPoints(); this.updateInsideList(); if ( typeof this.hist !== "undefined") { this.hist.setPlotPoints(this.plotPoints); } } }; GLayer.prototype.setXLog = function(xLog) { if (xLog !== this.xLog) { if (xLog && (this.xLim[0] <= 0 || this.xLim[1] <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); console.log("Will set horizontal limits to (0.1, 10)"); this.xLim[0] = 0.1; this.xLim[1] = 10; } this.xLog = xLog; this.updatePlotPoints(); this.updateInsideList(); if ( typeof this.hist !== "undefined") { this.hist.setPlotPoints(this.plotPoints); } } }; GLayer.prototype.setYLog = function(yLog) { if (yLog !== this.yLog) { if (yLog && (this.yLim[0] <= 0 || this.yLim[1] <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); console.log("Will set horizontal limits to (0.1, 10)"); this.yLim[0] = 0.1; this.yLim[1] = 10; } this.yLog = yLog; this.updatePlotPoints(); this.updateInsideList(); if ( typeof this.hist !== "undefined") { this.hist.setPlotPoints(this.plotPoints); } } }; GLayer.prototype.setPoints = function(points) { var i; var nPoints = points.length; if (this.points.length > nPoints) { this.points.splice(nPoints, Number.MAX_VALUE); } else { for ( i = this.points.length; i < nPoints; i++) { this.points[i] = new GPoint(); } } for ( i = 0; i < nPoints; i++) { this.points[i].set(points[i]); } this.updatePlotPoints(); this.updateInsideList(); if ( typeof this.hist !== "undefined") { this.hist.setPlotPoints(this.plotPoints); } }; GLayer.prototype.setPoint = function() { var index, x, y, label; var nPoints = this.points.length; if (arguments.length === 4) { index = arguments[0]; x = arguments[1]; y = arguments[2]; label = arguments[3]; } else if (arguments.length === 3) { index = arguments[0]; x = arguments[1]; y = arguments[2]; label = (index < nPoins) ? this.points[index].getLabel() : ""; } else if (arguments.length === 2) { index = arguments[0]; x = arguments[1].getX(); y = arguments[1].getY(); label = arguments[1].getLabel(); } else { throw new Error("GLayer.setPoint(): signature not supported"); } if (index < nPoints) { this.points[index].set(x, y, label); this.plotPoints[index].set(this.valueToXPlot(x), this.valueToYPlot(y), label); this.inside[index] = this.isInside(this.plotPoints[index]); } else if (index === nPoints) { this.points[index] = new GPoint(x, y, label); this.plotPoints[index] = new GPoint(this.valueToXPlot(x), this.valueToYPlot(y), label); this.inside[index] = this.isInside(this.plotPoints[index]); } else { throw new Error("GLayer.setPoint(): the index position is outside the array size"); } if ( typeof this.hist !== "undefined") { this.hist.setPlotPoint(index, this.plotPoints[index]); } }; GLayer.prototype.addPoint = function() { var x, y, label; if (arguments.length === 3) { x = arguments[0]; y = arguments[1]; label = arguments[2]; } else if (arguments.length === 2) { x = arguments[0]; y = arguments[1]; label = ""; } else if (arguments.length === 1) { x = arguments[0].getX(); y = arguments[0].getY(); label = arguments[0].getLabel(); } else { throw new Error("GLayer.addPoint(): signature not supported"); } this.points.push(new GPoint(x, y, label)); this.plotPoints.push(new GPoint(this.valueToXPlot(x), this.valueToYPlot(y), label)); this.inside.push(this.isInside(this.plotPoints[this.plotPoints.length - 1])); if ( typeof this.hist !== "undefined") { this.hist.addPlotPoint(this.plotPoints[this.plotPoints.length - 1]); } }; GLayer.prototype.addPoints = function(newPoints) { var newPoint; var nNewPoints = newPoints.length; for (var i = 0; i < nNewPoints; i++) { newPoint = newPoints[i]; this.points.push(new GPoint(newPoint)); this.plotPoints.push(new GPoint(this.valueToXPlot(newPoint.getX()), this.valueToYPlot(newPoint.getY()), newPoint.getLabel())); this.inside.push(this.isInside(this.plotPoints[this.plotPoints.length - 1])); } if ( typeof this.hist !== "undefined") { this.hist.setPlotPoints(this.plotPoints); } }; GLayer.prototype.addPointAtIndexPos = function() { var index, x, y, label; if (arguments.length === 4) { index = arguments[0]; x = arguments[1]; y = arguments[2]; label = arguments[3]; } else if (arguments.length === 3) { index = arguments[0]; x = arguments[1]; y = arguments[2]; label = ""; } else if (arguments.length === 2) { index = arguments[0]; x = arguments[1].getX(); y = arguments[1].getY(); label = arguments[1].getLabel(); } else { throw new Error("GLayer.addPointAtIndexPos(): signature not supported"); } if (index <= this.points.length) { this.points.splice(index, 0, new GPoint(x, y, label)); this.plotPoints.splice(index, 0, new GPoint(this.valueToXPlot(x), this.valueToYPlot(y), label)); this.inside.splice(index, 0, this.isInside(this.plotPoints[0])); if ( typeof this.hist !== "undefined") { this.hist.setPlotPoints(this.plotPoints); } } }; GLayer.prototype.removePoint = function(index) { if (index < this.points.length) { this.points.splice(index, 1); this.plotPoints.splice(index, 1); this.inside.splice(index, 1); if ( typeof this.hist !== "undefined") { this.hist.removePlotPoint(index); } } }; GLayer.prototype.setInside = function(inside) { if (inside.length === this.inside.length) { this.inside = inside.slice(); } }; GLayer.prototype.setPointColors = function(pointColors) { if (pointColors.length > 0) { this.pointColors = pointColors.slice(); } }; GLayer.prototype.setPointColor = function(pointColor) { this.pointColors = [pointColor]; }; GLayer.prototype.setPointSizes = function(pointSizes) { if (pointSizes.length > 0) { this.pointSizes = pointSizes.slice(); } }; GLayer.prototype.setPointSize = function(pointSize) { this.pointSizes = [pointSize]; }; GLayer.prototype.setLineColor = function(lineColor) { this.lineColor = lineColor; }; GLayer.prototype.setLineWidth = function(lineWidth) { if (lineWidth > 0) { this.lineWidth = lineWidth; } }; GLayer.prototype.setHistBasePoint = function(histBasePoint) { this.histBasePoint.set(histBasePoint); }; GLayer.prototype.setHistType = function(histType) { if ( typeof this.hist !== "undefined") { this.hist.setType(histType); } }; GLayer.prototype.setHistVisible = function(visible) { if ( typeof this.hist !== "undefined") { this.hist.setVisible(visible); } }; GLayer.prototype.setDrawHistLabels = function(drawHistLabels) { if ( typeof this.hist !== "undefined") { this.hist.setDrawLabels(drawHistLabels); } }; GLayer.prototype.setLabelBgColor = function(labelBgColor) { this.labelBgColor = labelBgColor; }; GLayer.prototype.setLabelSeparation = function(labelSeparation) { this.labelSeparation[0] = labelSeparation[0]; this.labelSeparation[1] = labelSeparation[1]; }; GLayer.prototype.setFontName = function(fontName) { this.fontName = fontName; }; GLayer.prototype.setFontColor = function(fontColor) { this.fontColor = fontColor; }; GLayer.prototype.setFontSize = function(fontSize) { if (fontSize > 0) { this.fontSize = fontSize; } }; GLayer.prototype.setFontProperties = function(fontName, fontColor, fontSize) { if (fontSize > 0) { this.fontName = fontName; this.fontColor = fontColor; this.fontSize = fontSize; } }; GLayer.prototype.setAllFontProperties = function(fontName, fontColor, fontSize) { this.setFontProperties(fontName, fontColor, fontSize); if ( typeof this.hist !== "undefined") { this.hist.setFontProperties(fontName, fontColor, fontSize); } }; GLayer.prototype.getId = function() { return this.id; }; GLayer.prototype.getDim = function() { return this.dim.slice(); }; GLayer.prototype.getXLim = function() { return this.xLim.slice(); }; GLayer.prototype.getYLim = function() { return this.yLim.slice(); }; GLayer.prototype.getXLog = function() { return this.xLog; }; GLayer.prototype.getYLog = function() { return this.yLog; }; GLayer.prototype.getPoints = function() { var points = []; for (var i = 0; i < this.points.length; i++) { points[i] = new GPoint(this.points[i]); } return points; }; GLayer.prototype.getPointsRef = function() { return this.points; }; GLayer.prototype.getPointColors = function() { return this.pointColors.slice(); }; GLayer.prototype.getPointSizes = function() { return this.pointSizes.slice(); }; GLayer.prototype.getLineColor = function() { return this.lineColor; }; GLayer.prototype.getLineWidth = function() { return this.lineWidth; }; GLayer.prototype.getHistogram = function() { return this.hist; }; /* * Plot class. It controls the rest of the graphical elements (layers, axes, * title, limits). */ function GPlot() { var parent, xPos, yPos, plotWidth, plotHeight; if (arguments.length === 5) { parent = arguments[0]; xPos = arguments[1]; yPos = arguments[2]; plotWidth = arguments[3]; plotHeight = arguments[4]; } else if (arguments.length === 3) { parent = arguments[0]; xPos = arguments[1]; yPos = arguments[2]; plotWidth = 450; plotHeight = 300; } else if (arguments.length === 1) { parent = arguments[0]; xPos = 0; yPos = 0; plotWidth = 450; plotHeight = 300; } else { throw new Error("GPlot constructor: signature not supported"); } // The parent processing object this.parent = parent; // General properties this.pos = [xPos, yPos]; this.outerDim = [plotWidth, plotHeight]; this.mar = [60, 70, 40, 30]; this.dim = [this.outerDim[0] - this.mar[1] - this.mar[3], this.outerDim[1] - this.mar[0] - this.mar[2]]; this.xLim = [0, 1]; this.yLim = [0, 1]; this.fixedXLim = false; this.fixedYLim = false; this.xLog = false; this.yLog = false; this.invertedXScale = false; this.invertedYScale = false; this.includeAllLayersInLim = true; this.expandLimFactor = 0.1; // Format properties this.bgColor = this.parent.color(255); this.boxBgColor = this.parent.color(245); this.boxLineColor = this.parent.color(210); this.boxLineWidth = 1; this.gridLineColor = this.parent.color(210); this.gridLineWidth = 1; // Layers this.mainLayer = new GLayer(this.parent, GPlot.MAINLAYERID, this.dim, this.xLim, this.yLim, this.xLog, this.yLog); this.layerList = []; // Axes and title this.xAxis = new GAxis(this.parent, this.parent.BOTTOM, this.dim, this.xLim, this.xLog); this.topAxis = new GAxis(this.parent, this.parent.TOP, this.dim, this.xLim, this.xLog); this.yAxis = new GAxis(this.parent, this.parent.LEFT, this.dim, this.yLim, this.yLog); this.rightAxis = new GAxis(this.parent, this.parent.RIGHT, this.dim, this.yLim, this.yLog); this.title = new GTitle(this.parent, this.dim); // Setup for the mouse events this.zoomingIsActive = false; this.zoomFactor = 1.3; this.increaseZoomButton = this.parent.LEFT; this.decreaseZoomButton = this.parent.RIGHT; this.increaseZoomKeyModifier = GPlot.NONE; this.decreaseZoomKeyModifier = GPlot.NONE; this.centeringIsActive = false; this.centeringButton = this.parent.LEFT; this.centeringKeyModifier = GPlot.NONE; this.panningIsActive = false; this.panningButton = this.parent.LEFT; this.panningKeyModifier = GPlot.NONE; this.panningReferencePoint = undefined; this.panningIntervalId = undefined; this.labelingIsActive = false; this.labelingButton = this.parent.LEFT; this.labelingKeyModifier = GPlot.NONE; this.mousePos = undefined; this.resetIsActive = false; this.resetButton = this.parent.RIGHT; this.resetKeyModifier = this.parent.CONTROL; this.xLimReset = undefined; this.yLimReset = undefined; // Add the event listeners window.addEventListener("click", this.clickEvent.bind(this), false); window.addEventListener("mousedown", this.mouseDownEvent.bind(this), false); window.addEventListener("touchstart", this.mouseDownEvent.bind(this), false); window.addEventListener("mouseup", this.mouseUpEvent.bind(this), false); window.addEventListener("touchend", this.mouseUpEvent.bind(this), false); window.addEventListener("wheel", this.wheelEvent.bind(this), false); } // Constants GPlot.MAINLAYERID = "main layer"; GPlot.VERTICAL = 0; GPlot.HORIZONTAL = 1; GPlot.BOTH = 2; GPlot.NONE = 0; GPlot.prototype.addLayer = function() { var id, layer; if (arguments.length === 2) { id = arguments[0]; layer = new GLayer(this.parent, id, this.dim, this.xLim, this.yLim, this.xLog, this.yLog); layer.setPoints(arguments[1]); } else if (arguments.length === 1) { id = arguments[0].getId(); layer = arguments[0]; } else { throw new Error("GPlot.addLayer(): signature not supported"); } // Check that it is the only layer with that id var sameId = false; if (this.mainLayer.isId(id)) { sameId = true; } else { for (var i = 0; i < this.layerList.length; i++) { if (this.layerList[i].isId(id)) { sameId = true; break; } } } // Add the layer to the list if (!sameId) { layer.setDim(this.dim); layer.setLimAndLog(this.xLim, this.yLim, this.xLog, this.yLog); this.layerList.push(layer); // Calculate and update the new plot limits if necessary if (this.includeAllLayersInLim) { this.updateLimits(); } } else { console.log("A layer with the same id exists. Please change the id and try to add it again."); } }; GPlot.prototype.removeLayer = function(id) { var index; for (var i = 0; i < this.layerList.length; i++) { if (this.layerList[i].isId(id)) { index = i; break; } } if ( typeof index !== "undefined") { this.layerList.splice(index, 1); // Calculate and update the new plot limits if necessary if (this.includeAllLayersInLim) { this.updateLimits(); } } else { console.log("Couldn't find a layer in the plot with id = " + id); } }; GPlot.prototype.getPlotPosAt = function(xScreen, yScreen) { var xPlot = xScreen - (this.pos[0] + this.mar[1]); var yPlot = yScreen - (this.pos[1] + this.mar[2] + this.dim[1]); return [xPlot, yPlot]; }; GPlot.prototype.getScreenPosAtValue = function(xValue, yValue) { var xScreen = this.mainLayer.valueToXPlot(xValue) + (this.pos[0] + this.mar[1]); var yScreen = this.mainLayer.valueToYPlot(yValue) + (this.pos[1] + this.mar[2] + this.dim[1]); return [xScreen, yScreen]; }; GPlot.prototype.getPointAt = function() { var xScreen, yScreen, layer; if (arguments.length === 3) { xScreen = arguments[0]; yScreen = arguments[1]; layer = this.getLayer(arguments[2]); } else if (arguments.length === 2) { xScreen = arguments[0]; yScreen = arguments[1]; layer = this.mainLayer; } else { throw new Error("GPlot.getPointAt(): signature not supported"); } var plotPos = this.getPlotPosAt(xScreen, yScreen); return layer.getPointAtPlotPos(plotPos[0], plotPos[1]); }; GPlot.prototype.addPointAt = function() { var xScreen, yScreen, layerId; if (arguments.length === 3) { xScreen = arguments[0]; yScreen = arguments[1]; layerId = arguments[2]; } else if (arguments.length === 2) { xScreen = arguments[0]; yScreen = arguments[1]; layerId = GPlot.MAINLAYERID; } else { throw new Error("GPlot.addPointAt(): signature not supported"); } var value = this.getValueAt(xScreen, yScreen); this.addPoint(value[0], value[1], "", layerId); }; GPlot.prototype.removePointAt = function() { var xScreen, yScreen, layerId; if (arguments.length === 3) { xScreen = arguments[0]; yScreen = arguments[1]; layerId = arguments[2]; } else if (arguments.length === 2) { xScreen = arguments[0]; yScreen = arguments[1]; layerId = GPlot.MAINLAYERID; } else { throw new Error("GPlot.removePointAt(): signature not supported"); } var plotPos = this.getPlotPosAt(xScreen, yScreen); var pointIndex = this.getLayer(layerId).getPointIndexAtPlotPos(plotPos[0], plotPos[1]); if ( typeof pointIndex !== "undefined") { this.removePoint(pointIndex, layerId); } }; GPlot.prototype.getValueAt = function(xScreen, yScreen) { var plotPos = this.getPlotPosAt(xScreen, yScreen); return this.mainLayer.plotToValue(plotPos[0], plotPos[1]); }; GPlot.prototype.getRelativePlotPosAt = function(xScreen, yScreen) { var plotPos = this.getPlotPosAt(xScreen, yScreen); return [plotPos[0] / this.dim[0], -plotPos[1] / this.dim[1]]; }; GPlot.prototype.isOverPlot = function() { var xScreen, yScreen; if (arguments.length === 2) { xScreen = arguments[0]; yScreen = arguments[1]; } else if (arguments.length === 0) { xScreen = this.parent.mouseX; yScreen = this.parent.mouseY; } else { throw new Error("GPlot.isOverPlot(): signature not supported"); } return (xScreen >= this.pos[0]) && (xScreen <= this.pos[0] + this.outerDim[0]) && (yScreen >= this.pos[1]) && (yScreen <= this.pos[1] + this.outerDim[1]); }; GPlot.prototype.isOverBox = function() { var xScreen, yScreen; if (arguments.length === 2) { xScreen = arguments[0]; yScreen = arguments[1]; } else if (arguments.length === 0) { xScreen = this.parent.mouseX; yScreen = this.parent.mouseY; } else { throw new Error("GPlot.isOverBox(): signature not supported"); } return (xScreen >= this.pos[0] + this.mar[1]) && (xScreen <= this.pos[0] + this.outerDim[0] - this.mar[3]) && (yScreen >= this.pos[1] + this.mar[2]) && (yScreen <= this.pos[1] + this.outerDim[1] - this.mar[0]); }; GPlot.prototype.updateLimits = function() { // Calculate the new limits and update the axes if needed if (!this.fixedXLim) { this.xLim = this.calculatePlotXLim(); this.xAxis.setLim(this.xLim); this.topAxis.setLim(this.xLim); } if (!this.fixedYLim) { this.yLim = this.calculatePlotYLim(); this.yAxis.setLim(this.yLim); this.rightAxis.setLim(this.yLim); } // Update the layers this.mainLayer.setXYLim(this.xLim, this.yLim); for (var i = 0; i < this.layerList.length; i++) { this.layerList[i].setXYLim(this.xLim, this.yLim); } }; GPlot.prototype.calculatePlotXLim = function() { // Find the limits for the main layer var lim = this.calculatePointsXLim(this.mainLayer.getPointsRef()); // Include the other layers in the limit calculation if necessary if (this.includeAllLayersInLim) { for (var i = 0; i < this.layerList.length; i++) { var newLim = this.calculatePointsXLim(this.layerList[i].getPointsRef()); if ( typeof newLim !== "undefined") { if ( typeof lim !== "undefined") { lim[0] = Math.min(lim[0], newLim[0]); lim[1] = Math.max(lim[1], newLim[1]); } else { lim = newLim; } } } } if ( typeof lim !== "undefined") { // Expand the axis limits a bit var delta = (lim[0] === 0) ? 0.1 : 0.1 * lim[0]; if (this.xLog) { if (lim[0] !== lim[1]) { delta = Math.exp(this.expandLimFactor * Math.log(lim[1] / lim[0])); } lim[0] = lim[0] / delta; lim[1] = lim[1] * delta; } else { if (lim[0] !== lim[1]) { delta = this.expandLimFactor * (lim[1] - lim[0]); } lim[0] = lim[0] - delta; lim[1] = lim[1] + delta; } } else { if (this.xLog && (this.xLim[0] <= 0 || this.xLim[1] <= 0)) { lim = [0.1, 10]; } else { lim = this.xLim.slice(); } } // Invert the limits if necessary if (this.invertedXScale && lim[0] < lim[1]) { lim = [lim[1], lim[0]]; } return lim; }; GPlot.prototype.calculatePlotYLim = function() { // Find the limits for the main layer var lim = this.calculatePointsYLim(this.mainLayer.getPointsRef()); // Include the other layers in the limit calculation if necessary if (this.includeAllLayersInLim) { for (var i = 0; i < this.layerList.length; i++) { var newLim = this.calculatePointsYLim(this.layerList[i].getPointsRef()); if ( typeof newLim !== "undefined") { if ( typeof lim !== "undefined") { lim[0] = Math.min(lim[0], newLim[0]); lim[1] = Math.max(lim[1], newLim[1]); } else { lim = newLim; } } } } if ( typeof lim !== "undefined") { // Expand the axis limits a bit var delta = (lim[0] === 0) ? 0.1 : 0.1 * lim[0]; if (this.yLog) { if (lim[0] !== lim[1]) { delta = Math.exp(this.expandLimFactor * Math.log(lim[1] / lim[0])); } lim[0] = lim[0] / delta; lim[1] = lim[1] * delta; } else { if (lim[0] !== lim[1]) { delta = this.expandLimFactor * (lim[1] - lim[0]); } lim[0] = lim[0] - delta; lim[1] = lim[1] + delta; } } else { if (this.yLog && (this.yLim[0] <= 0 || this.yLim[1] <= 0)) { lim = [0.1, 10]; } else { lim = this.yLim.slice(); } } // Invert the limits if necessary if (this.invertedYScale && lim[0] < lim[1]) { lim = [lim[1], lim[0]]; } return lim; }; GPlot.prototype.calculatePointsXLim = function(points) { // Find the points limits var lim = [Number.MAX_VALUE, -Number.MAX_VALUE]; for (var i = 0; i < points.length; i++) { if (points[i].isValid()) { // Use the point if it's inside, and it's not negative if // the scale is logarithmic var x = points[i].getX(); var y = points[i].getY(); var isInside = true; if (this.fixedYLim) { isInside = ((this.yLim[1] >= this.yLim[0]) && (y >= this.yLim[0]) && (y <= this.yLim[1])) || ((this.yLim[1] < this.yLim[0]) && (y <= this.yLim[0]) && (y >= this.yLim[1])); } if (isInside && !(this.xLog && x <= 0)) { if (x < lim[0]) { lim[0] = x; } if (x > lim[1]) { lim[1] = x; } } } } // Check that the new limits make sense if (lim[1] < lim[0]) { lim = undefined; } return lim; }; GPlot.prototype.calculatePointsYLim = function(points) { // Find the points limits var lim = [Number.MAX_VALUE, -Number.MAX_VALUE]; for (var i = 0; i < points.length; i++) { if (points[i].isValid()) { // Use the point if it's inside, and it's not negative if // the scale is logarithmic var x = points[i].getX(); var y = points[i].getY(); var isInside = true; if (this.fixedXLim) { isInside = ((this.xLim[1] >= this.xLim[0]) && (x >= this.xLim[0]) && (x <= this.xLim[1])) || ((this.xLim[1] < this.xLim[0]) && (x <= this.xLim[0]) && (x >= this.xLim[1])); } if (isInside && !(this.yLog && y <= 0)) { if (y < lim[0]) { lim[0] = y; } if (y > lim[1]) { lim[1] = y; } } } } // Check that the new limits make sense if (lim[1] < lim[0]) { lim = undefined; } return lim; }; GPlot.prototype.moveHorizontalAxesLim = function(delta) { // Obtain the new x limits var deltaLim; if (this.xLog) { deltaLim = Math.exp(Math.log(this.xLim[1] / this.xLim[0]) * delta / this.dim[0]); this.xLim[0] *= deltaLim; this.xLim[1] *= deltaLim; } else { deltaLim = (this.xLim[1] - this.xLim[0]) * delta / this.dim[0]; this.xLim[0] += deltaLim; this.xLim[1] += deltaLim; } // Fix the limits this.fixedXLim = true; // Move the horizontal axes this.xAxis.moveLim(this.xLim); this.topAxis.moveLim(this.xLim); // Update the plot limits this.updateLimits(); }; GPlot.prototype.moveVerticalAxesLim = function(delta) { // Obtain the new y limits var deltaLim; if (this.yLog) { deltaLim = Math.exp(Math.log(this.yLim[1] / this.yLim[0]) * delta / this.dim[1]); this.yLim[0] *= deltaLim; this.yLim[1] *= deltaLim; } else { deltaLim = (this.yLim[1] - this.yLim[0]) * delta / this.dim[1]; this.yLim[0] += deltaLim; this.yLim[1] += deltaLim; } // Fix the limits this.fixedYLim = true; // Move the vertical axes this.yAxis.moveLim(this.yLim); this.rightAxis.moveLim(this.yLim); // Update the plot limits this.updateLimits(); }; GPlot.prototype.centerAndZoom = function(factor, xValue, yValue) { // Calculate the new limits var deltaLim; if (this.xLog) { deltaLim = Math.exp(Math.log(this.xLim[1] / this.xLim[0]) / (2 * factor)); this.xLim = [xValue / deltaLim, xValue * deltaLim]; } else { deltaLim = (this.xLim[1] - this.xLim[0]) / (2 * factor); this.xLim = [xValue - deltaLim, xValue + deltaLim]; } if (this.yLog) { deltaLim = Math.exp(Math.log(this.yLim[1] / this.yLim[0]) / (2 * factor)); this.yLim = [yValue / deltaLim, yValue * deltaLim]; } else { deltaLim = (this.yLim[1] - this.yLim[0]) / (2 * factor); this.yLim = [yValue - deltaLim, yValue + deltaLim]; } // Fix the limits this.fixedXLim = true; this.fixedYLim = true; // Update the horizontal and vertical axes this.xAxis.setLim(this.xLim); this.topAxis.setLim(this.xLim); this.yAxis.setLim(this.yLim); this.rightAxis.setLim(yLim); // Update the plot limits (the layers, because the limits are fixed) this.updateLimits(); }; GPlot.prototype.zoom = function() { var factor, deltaLim, offset; if (arguments.length === 3) { factor = arguments[0]; var xScreen = arguments[1]; var yScreen = arguments[2]; var plotPos = this.getPlotPosAt(xScreen, yScreen); var value = this.mainLayer.plotToValue(plotPos[0], plotPos[1]); if (this.xLog) { deltaLim = Math.exp(Math.log(this.xLim[1] / this.xLim[0]) / (2 * factor)); offset = Math.exp((Math.log(this.xLim[1] / this.xLim[0]) / factor) * (0.5 - plotPos[0] / this.dim[0])); this.xLim = [value[0] * offset / deltaLim, value[0] * offset * deltaLim]; } else { deltaLim = (this.xLim[1] - this.xLim[0]) / (2 * factor); offset = 2 * deltaLim * (0.5 - plotPos[0] / this.dim[0]); this.xLim = [value[0] + offset - deltaLim, value[0] + offset + deltaLim]; } if (this.yLog) { deltaLim = Math.exp(Math.log(this.yLim[1] / this.yLim[0]) / (2 * factor)); offset = Math.exp((Math.log(this.yLim[1] / this.yLim[0]) / factor) * (0.5 + plotPos[1] / this.dim[1])); this.yLim = [value[1] * offset / deltaLim, value[1] * offset * deltaLim]; } else { deltaLim = (this.yLim[1] - this.yLim[0]) / (2 * factor); offset = 2 * deltaLim * (0.5 + plotPos[1] / this.dim[1]); this.yLim = [value[1] + offset - deltaLim, value[1] + offset + deltaLim]; } // Fix the limits this.fixedXLim = true; this.fixedYLim = true; // Update the horizontal and vertical axes this.xAxis.setLim(this.xLim); this.topAxis.setLim(this.xLim); this.yAxis.setLim(this.yLim); this.rightAxis.setLim(this.yLim); // Update the plot limits (the layers, because the limits are fixed) this.updateLimits(); } else if (arguments.length === 1) { factor = arguments[0]; var centerValue = this.mainLayer.plotToValue(this.dim[0] / 2, -this.dim[1] / 2); this.centerAndZoom(factor, centerValue[0], centerValue[1]); } else { throw new Error("GPlot.zoom(): signature not supported"); } }; GPlot.prototype.shiftPlotPos = function(valuePlotPos, newPlotPos) { // Calculate the new limits var deltaLim; var deltaXPlot = valuePlotPos[0] - newPlotPos[0]; var deltaYPlot = valuePlotPos[1] - newPlotPos[1]; if (this.xLog) { deltaLim = Math.exp(Math.log(this.xLim[1] / this.xLim[0]) * deltaXPlot / this.dim[0]); this.xLim = [this.xLim[0] * deltaLim, this.xLim[1] * deltaLim]; } else { deltaLim = (this.xLim[1] - this.xLim[0]) * deltaXPlot / this.dim[0]; this.xLim = [this.xLim[0] + deltaLim, this.xLim[1] + deltaLim]; } if (this.yLog) { deltaLim = Math.exp(-Math.log(this.yLim[1] / this.yLim[0]) * deltaYPlot / this.dim[1]); this.yLim = [this.yLim[0] * deltaLim, this.yLim[1] * deltaLim]; } else { deltaLim = -(this.yLim[1] - this.yLim[0]) * deltaYPlot / this.dim[1]; this.yLim = [this.yLim[0] + deltaLim, this.yLim[1] + deltaLim]; } // Fix the limits this.fixedXLim = true; this.fixedYLim = true; // Move the horizontal and vertical axes this.xAxis.moveLim(this.xLim); this.topAxis.moveLim(this.xLim); this.yAxis.moveLim(this.yLim); this.rightAxis.moveLim(this.yLim); // Update the plot limits (the layers, because the limits are fixed) this.updateLimits(); }; GPlot.prototype.align = function() { var xValue, yValue, xScreen, yScreen; if (arguments.length === 4) { xValue = arguments[0]; yValue = arguments[1]; xScreen = arguments[2]; yScreen = arguments[3]; } else if (arguments.length === 3) { xValue = arguments[0][0]; yValue = arguments[0][1]; xScreen = arguments[1]; yScreen = arguments[2]; } else { throw new Error("GPlot.align(): signature not supported"); } var valuePlotPos = this.mainLayer.valueToPlot(xValue, yValue); var newPlotPos = this.getPlotPosAt(xScreen, yScreen); this.shiftPlotPos(valuePlotPos, newPlotPos); }; GPlot.prototype.center = function(xScreen, yScreen) { var valuePlotPos = this.getPlotPosAt(xScreen, yScreen); var newPlotPos = [this.dim[0] / 2, -this.dim[1] / 2]; this.shiftPlotPos(valuePlotPos, newPlotPos); }; GPlot.prototype.startHistograms = function(histType) { this.mainLayer.startHistogram(histType); for (var i = 0; i < this.layerList.length; i++) { this.layerList[i].startHistogram(histType); } }; GPlot.prototype.defaultDraw = function() { this.beginDraw(); this.drawBackground(); this.drawBox(); this.drawXAxis(); this.drawYAxis(); this.drawTitle(); this.drawLines(); this.drawPoints(); this.endDraw(); }; GPlot.prototype.beginDraw = function() { this.parent.push(); this.parent.translate(this.pos[0] + this.mar[1], this.pos[1] + this.mar[2] + this.dim[1]); }; GPlot.prototype.endDraw = function() { this.parent.pop(); }; GPlot.prototype.drawBackground = function() { this.parent.push(); this.parent.rectMode(this.parent.CORNER); this.parent.fill(this.bgColor); this.parent.noStroke(); this.parent.rect(-this.mar[1], -this.mar[2] - this.dim[1], this.outerDim[0], this.outerDim[1]); this.parent.pop(); }; GPlot.prototype.drawBox = function() { this.parent.push(); this.parent.rectMode(this.parent.CORNER); this.parent.fill(this.boxBgColor); this.parent.stroke(this.boxLineColor); this.parent.strokeWeight(this.boxLineWidth); this.parent.strokeCap(this.parent.SQUARE); this.parent.rect(0, -this.dim[1], this.dim[0], this.dim[1]); this.parent.pop(); }; GPlot.prototype.drawXAxis = function() { this.xAxis.draw(); }; GPlot.prototype.drawYAxis = function() { this.yAxis.draw(); }; GPlot.prototype.drawTopAxis = function() { this.topAxis.draw(); }; GPlot.prototype.drawRightAxis = function() { this.rightAxis.draw(); }; GPlot.prototype.drawTitle = function() { this.title.draw(); }; GPlot.prototype.drawPoints = function() { var i; if (arguments.length === 1) { this.mainLayer.drawPoints(arguments[0]); for ( i = 0; i < this.layerList.length; i++) { this.layerList[0].drawPoints(arguments[0]); } } else if (arguments.length === 0) { this.mainLayer.drawPoints(); for ( i = 0; i < this.layerList.length; i++) { this.layerList[i].drawPoints(); } } else { throw new Error("GPlot.drawPoints(): signature not supported"); } }; GPlot.prototype.drawPoint = function() { if (arguments.length === 3) { this.mainLayer.drawPoint(arguments[0], arguments[1], arguments[2]); } else if (arguments.length === 2) { this.mainLayer.drawPoint(arguments[0], arguments[1]); } else if (arguments.length === 1) { this.mainLayer.drawPoint(arguments[0]); } else { throw new Error("GPlot.drawPoint(): signature not supported"); } }; GPlot.prototype.drawLines = function() { this.mainLayer.drawLines(); for (var i = 0; i < this.layerList.length; i++) { this.layerList[i].drawLines(); } }; GPlot.prototype.drawLine = function() { if (arguments.length === 4) { this.mainLayer.drawLine(arguments[0], arguments[1], arguments[2], arguments[3]); } else if (arguments.length === 2) { this.mainLayer.drawLine(arguments[0], arguments[1]); } else { throw new Error("GPlot.drawLine(): signature not supported"); } }; GPlot.prototype.drawHorizontalLine = function() { if (arguments.length === 3) { this.mainLayer.drawHorizontalLine(arguments[0], arguments[1], arguments[2]); } else if (arguments.length === 1) { this.mainLayer.drawHorizontalLine(arguments[0]); } else { throw new Error("GPlot.drawHorizontalLine(): signature not supported"); } }; GPlot.prototype.drawVerticalLine = function() { if (arguments.length === 3) { this.mainLayer.drawVerticalLine(arguments[0], arguments[1], arguments[2]); } else if (arguments.length === 1) { this.mainLayer.drawVerticalLine(arguments[0]); } else { throw new Error("GPlot.drawVerticalLine(): signature not supported"); } }; GPlot.prototype.drawFilledContours = function(contourType, referenceValue) { this.mainLayer.drawFilledContour(contourType, referenceValue); for (var i = 0; i < this.layerList.length; i++) { this.layerList[i].drawFilledContour(contourType, referenceValue); } }; GPlot.prototype.drawAllLabels = function() { this.mainLayer.drawAllLabels(); }; GPlot.prototype.drawLabel = function(point) { this.mainLayer.drawLabel(point); }; GPlot.prototype.drawLabelsAt = function(xScreen, yScreen) { var plotPos = this.getPlotPosAt(xScreen, yScreen); this.mainLayer.drawLabelAtPlotPos(plotPos[0], plotPos[1]); for (var i = 0; i < this.layerList.length; i++) { this.layerList[i].drawLabelAtPlotPos(plotPos[0], plotPos[1]); } }; GPlot.prototype.drawLabels = function() { if (this.labelingIsActive && typeof this.mousePos !== "undefined") { this.drawLabelsAt(this.mousePos[0], this.mousePos[1]); } }; GPlot.prototype.drawGridLines = function(gridType) { var i; this.parent.push(); this.parent.noFill(); this.parent.stroke(this.gridLineColor); this.parent.strokeWeight(this.gridLineWidth); this.parent.strokeCap(this.parent.SQUARE); if (gridType === GPlot.BOTH || gridType === GPlot.VERTICAL) { var xPlotTicks = this.xAxis.getPlotTicksRef(); for ( i = 0; i < xPlotTicks.length; i++) { if (xPlotTicks[i] >= 0 && xPlotTicks[i] <= this.dim[0]) { this.parent.line(xPlotTicks[i], 0, xPlotTicks[i], -this.dim[1]); } } } if (gridType === GPlot.BOTH || gridType === GPlot.HORIZONTAL) { var yPlotTicks = this.yAxis.getPlotTicksRef(); for ( i = 0; i < yPlotTicks.length; i++) { if (-yPlotTicks[i] >= 0 && -yPlotTicks[i] <= this.dim[1]) { this.parent.line(0, yPlotTicks[i], this.dim[0], yPlotTicks[i]); } } } this.parent.pop(); }; GPlot.prototype.drawHistograms = function() { this.mainLayer.drawHistogram(); for (var i = 0; i < this.layerList.length; i++) { this.layerList[i].drawHistogram(); } }; GPlot.prototype.drawPolygon = function(polygonPoints, polygonColor) { this.mainLayer.drawPolygon(polygonPoints, polygonColor); }; GPlot.prototype.drawAnnotation = function(text, x, y, horAlign, verAlign) { this.mainLayer.drawAnnotation(text, x, y, horAlign, verAlign); }; GPlot.prototype.drawLegend = function(text, xRelativePos, yRelativePos) { var rectSize = 14; this.parent.push(); this.parent.rectMode(this.parent.CENTER); this.parent.noStroke(); for (var i = 0; i < text.length; i++) { var plotPosition = [xRelativePos[i] * this.dim[0], -yRelativePos[i] * this.dim[1]]; var position = this.mainLayer.plotToValue(plotPosition[0] + rectSize, plotPosition[1]); if (i === 0) { this.parent.fill(this.mainLayer.getLineColor()); this.parent.rect(plotPosition[0], plotPosition[1], rectSize, rectSize); this.mainLayer.drawAnnotation(text[i], position[0], position[1], this.parent.LEFT, this.parent.CENTER); } else { this.parent.fill(this.layerList[i - 1].getLineColor()); this.parent.rect(plotPosition[0], plotPosition[1], rectSize, rectSize); this.layerList[i - i].drawAnnotation(text[i], position[0], position[1], this.parent.LEFT, this.parent.CENTER); } } this.parent.pop(); }; GPlot.prototype.setPos = function() { if (arguments.length === 2) { this.pos[0] = arguments[0]; this.pos[1] = arguments[1]; } else if (arguments.length === 1) { this.pos[0] = arguments[0][0]; this.pos[1] = arguments[0][1]; } else { throw new Error("GPlot.setPos(): signature not supported"); } }; GPlot.prototype.setOuterDim = function() { var xOuterDim, yOuterDim; if (arguments.length === 2) { xOuterDim = arguments[0]; yOuterDim = arguments[1]; } else if (arguments.length === 1) { xOuterDim = arguments[0][0]; yOuterDim = arguments[0][1]; } else { throw new Error("GPlot.setOuterDim(): signature not supported"); } if (xOuterDim > 0 && yOuterDim > 0) { // Make sure that the new plot dimensions are positive var xDim = xOuterDim - this.mar[1] - this.mar[3]; var yDim = yOuterDim - this.mar[0] - this.mar[2]; if (xDim > 0 && yDim > 0) { this.outerDim[0] = xOuterDim; this.outerDim[1] = yOuterDim; this.dim[0] = xDim; this.dim[1] = yDim; this.xAxis.setDim(this.dim); this.topAxis.setDim(this.dim); this.yAxis.setDim(this.dim); this.rightAxis.setDim(this.dim); this.title.setDim(this.dim); // Update the layers this.mainLayer.setDim(this.dim); for (var i = 0; i < this.layerList.lenght; i++) { this.layerList[i].setDim(this.dim); } } } }; GPlot.prototype.setMar = function() { var bottomMargin, leftMargin, topMargin, rightMargin; if (arguments.length === 4) { bottomMargin = arguments[0]; leftMargin = arguments[1]; topMargin = arguments[2]; rightMargin = arguments[3]; } else if (arguments.length === 1) { bottomMargin = arguments[0][0]; leftMargin = arguments[0][1]; topMargin = arguments[0][2]; rightMargin = arguments[0][3]; } else { throw new Error("GPlot.setMar(): signature not supported"); } var xOuterDim = this.dim[0] + leftMargin + rightMargin; var yOuterDim = this.dim[1] + bottomMargin + topMargin; if (xOuterDim > 0 && yOuterDim > 0) { this.mar[0] = bottomMargin; this.mar[1] = leftMargin; this.mar[2] = topMargin; this.mar[3] = rightMargin; this.outerDim[0] = xOuterDim; this.outerDim[1] = yOuterDim; } }; GPlot.prototype.setDim = function() { var xDim, yDim; if (arguments.length === 2) { xDim = arguments[0]; yDim = arguments[1]; } else if (arguments.length === 1) { xDim = arguments[0][0]; yDim = arguments[0][1]; } else { throw new Error("GPlot.setDim(): signature not supported"); } if (xDim > 0 && yDim > 0) { // Make sure that the new outer dimensions are positive var xOuterDim = xDim + this.mar[1] + this.mar[3]; var yOuterDim = yDim + this.mar[0] + this.mar[2]; if (xOuterDim > 0 && yOuterDim > 0) { this.outerDim[0] = xOuterDim; this.outerDim[1] = yOuterDim; this.dim[0] = xDim; this.dim[1] = yDim; this.xAxis.setDim(this.dim); this.topAxis.setDim(this.dim); this.yAxis.setDim(this.dim); this.rightAxis.setDim(this.dim); this.title.setDim(this.dim); // Update the layers this.mainLayer.setDim(this.dim); for (var i = 0; i < this.layerList.length; i++) { this.layerList[i].setDim(this.dim); } } } }; GPlot.prototype.setXLim = function() { var lowerLim, upperLim; if (arguments.length === 2) { lowerLim = arguments[0]; upperLim = arguments[1]; } else if (arguments.length === 1) { lowerLim = arguments[0][0]; upperLim = arguments[0][1]; } else { throw new Error("GPlot.setXLim(): signature not supported"); } if (lowerLim !== upperLim) { // Make sure the new limits makes sense if (this.xLog && (lowerLim <= 0 || upperLim <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); } else { this.xLim[0] = lowerLim; this.xLim[1] = upperLim; this.invertedXScale = this.xLim[0] > this.xLim[1]; // Fix the limits this.fixedXLim = true; // Update the axes this.xAxis.setLim(this.xLim); this.topAxis.setLim(this.xLim); // Update the plot limits this.updateLimits(); } } }; GPlot.prototype.setYLim = function() { var lowerLim, upperLim; if (arguments.length === 2) { lowerLim = arguments[0]; upperLim = arguments[1]; } else if (arguments.length === 1) { lowerLim = arguments[0][0]; upperLim = arguments[0][1]; } else { throw new Error("GPlot.setYLim(): signature not supported"); } if (lowerLim !== upperLim) { // Make sure the new limits makes sense if (this.yLog && (lowerLim <= 0 || upperLim <= 0)) { console.log("One of the limits is negative. This is not allowed in logarithmic scale."); } else { this.yLim[0] = lowerLim; this.yLim[1] = upperLim; this.invertedYScale = this.yLim[0] > this.yLim[1]; // Fix the limits this.fixedYLim = true; // Update the axes this.yAxis.setLim(this.yLim); this.rightAxis.setLim(this.yLim); // Update the plot limits this.updateLimits(); } } }; GPlot.prototype.setFixedXLim = function(fixedXLim) { this.fixedXLim = fixedXLim; // Update the plot limits this.updateLimits(); }; GPlot.prototype.setFixedYLim = function(fixedYLim) { this.fixedYLim = fixedYLim; // Update the plot limits this.updateLimits(); }; GPlot.prototype.setLogScale = function(logType) { var newXLog = this.xLog; var newYLog = this.yLog; if (logType === "xy" || logType === "yx") { newXLog = true; newYLog = true; } else if (logType === "x") { newXLog = true; newYLog = false; } else if (logType === "y") { newXLog = false; newYLog = true; } else if (logType === "") { newXLog = false; newYLog = false; } // Do something only if the scale changed if (newXLog !== this.xLog || newYLog !== this.yLog) { // Set the new log scales this.xLog = newXLog; this.yLog = newYLog; // Unfix the limits if the old ones don't make sense if (this.xLog && this.fixedXLim && (this.xLim[0] <= 0 || this.xLim[1] <= 0)) { this.fixedXLim = false; } if (this.yLog && this.fixedYLim && (this.yLim[0] <= 0 || this.yLim[1] <= 0)) { this.fixedYLim = false; } // Calculate the new limits if needed if (!this.fixedXLim) { this.xLim = this.calculatePlotXLim(); } if (!this.fixedYLim) { this.yLim = this.calculatePlotYLim(); } // Update the axes this.xAxis.setLimAndLog(this.xLim, this.xLog); this.topAxis.setLimAndLog(this.xLim, this.xLog); this.yAxis.setLimAndLog(this.yLim, this.yLog); this.rightAxis.setLimAndLog(this.yLim, this.yLog); // Update the layers this.mainLayer.setLimAndLog(this.xLim, this.yLim, this.xLog, this.yLog); for (var i = 0; i < this.layerList.length; i++) { this.layerList[i].setLimAndLog(this.xLim, this.yLim, this.xLog, this.yLog); } } }; GPlot.prototype.setInvertedXScale = function(invertedXScale) { if (invertedXScale !== this.invertedXScale) { this.invertedXScale = invertedXScale; var temp = this.xLim[0]; this.xLim[0] = this.xLim[1]; this.xLim[1] = temp; // Update the axes this.xAxis.setLim(this.xLim); this.topAxis.setLim(this.xLim); // Update the layers this.mainLayer.setXLim(this.xLim); for (var i = 0; i < this.layerList.length; i++) { this.layerList[i].setXLim(this.xLim); } } }; GPlot.prototype.invertXScale = function() { this.setInvertedXScale(!this.invertedXScale); }; GPlot.prototype.setInvertedYScale = function(invertedYScale) { if (invertedYScale !== this.invertedYScale) { this.invertedYScale = invertedYScale; var temp = this.yLim[0]; this.yLim[0] = this.yLim[1]; this.yLim[1] = temp; // Update the axes this.yAxis.setLim(this.yLim); this.rightAxis.setLim(this.yLim); // Update the layers this.mainLayer.setYLim(this.yLim); for (var i = 0; i < this.layerList.length; i++) { this.layerList[i].setYLim(this.yLim); } } }; GPlot.prototype.invertYScale = function() { this.setInvertedYScale(!this.invertedYScale); }; GPlot.prototype.setIncludeAllLayersInLim = function(includeAllLayers) { if (includeAllLayers !== this.includeAllLayersInLim) { this.includeAllLayersInLim = includeAllLayers; // Update the plot limits this.updateLimits(); } }; GPlot.prototype.setExpandLimFactor = function(expandFactor) { if (expandFactor >= 0 && expandFactor !== this.expandLimFactor) { this.expandLimFactor = expandFactor; // Update the plot limits this.updateLimits(); } }; GPlot.prototype.setBgColor = function(bgColor) { this.bgColor = bgColor; }; GPlot.prototype.setBoxBgColor = function(boxBgColor) { this.boxBgColor = boxBgColor; }; GPlot.prototype.setBoxLineColor = function(boxLineColor) { this.boxLineColor = boxLineColor; }; GPlot.prototype.setBoxLineWidth = function(boxLineWidth) { if (boxLineWidth > 0) { this.boxLineWidth = boxLineWidth; } }; GPlot.prototype.setGridLineColor = function(gridLineColor) { this.gridLineColor = gridLineColor; }; GPlot.prototype.setGridLineWidth = function(gridLineWidth) { if (gridLineWidth > 0) { this.gridLineWidth = gridLineWidth; } }; GPlot.prototype.setPoints = function() { if (arguments.length === 2) { this.getLayer(arguments[1]).setPoints(arguments[0]); } else if (arguments.length === 1) { this.mainLayer.setPoints(arguments[0]); } else { throw new Error("GPlot.setPoints(): signature not supported"); } this.updateLimits(); }; GPlot.prototype.setPoint = function() { if (arguments.length === 5) { this.getLayer(arguments[4]).setPoint(arguments[0], arguments[1], arguments[2], arguments[3]); } else if (arguments.length === 4) { this.mainLayer.setPoint(arguments[0], arguments[1], arguments[2], arguments[3]); } else if (arguments.length === 3 && arguments[1] instanceof GPoint) { this.getLayer(arguments[2]).setPoint(arguments[0], arguments[1]); } else if (arguments.length === 3) { this.mainLayer.setPoint(arguments[0], arguments[1], arguments[2]); } else if (arguments.length === 2) { this.mainLayer.setPoint(arguments[0], arguments[1]); } else { throw new Error("GPlot.setPoint(): signature not supported"); } this.updateLimits(); }; GPlot.prototype.addPoint = function() { if (arguments.length === 4) { this.getLayer(arguments[3]).addPoint(arguments[0], arguments[1], arguments[2]); } else if (arguments.length === 3) { this.mainLayer.addPoint(arguments[0], arguments[1], arguments[2]); } else if (arguments.length === 2 && arguments[0] instanceof GPoint) { this.getLayer(arguments[1]).addPoint(arguments[0]); } else if (arguments.length === 2) { this.mainLayer.addPoint(arguments[0], arguments[1]); } else if (arguments.length === 1) { this.mainLayer.addPoint(arguments[0]); } else { throw new Error("GPlot.addPoint(): signature not supported"); } this.updateLimits(); }; GPlot.prototype.addPoints = function() { if (arguments.length === 2) { this.getLayer(arguments[1]).addPoints(arguments[0]); } else if (arguments.length === 1) { this.mainLayer.addPoints(arguments[0]); } else { throw new Error("GPlot.addPoints(): signature not supported"); } this.updateLimits(); }; GPlot.prototype.removePoint = function() { if (arguments.length === 2) { this.getLayer(arguments[1]).removePoint(arguments[0]); } else if (arguments.length === 1) { this.mainLayer.removePoint(arguments[0]); } else { throw new Error("GPlot.removePoint(): signature not supported"); } this.updateLimits(); }; GPlot.prototype.addPointAtIndexPos = function() { if (arguments.length === 5) { this.getLayer(arguments[4]).addPointAtIndexPos(arguments[0], arguments[1], arguments[2], arguments[3]); } else if (arguments.length === 4) { this.mainLayer.addPointAtIndexPos(arguments[0], arguments[1], arguments[2], arguments[3]); } else if (arguments.length === 3 && arguments[1] instanceof GPoint) { this.getLayer(arguments[2]).addPointAtIndexPos(arguments[0], arguments[1]); } else if (arguments.length === 3) { this.mainLayer.addPointAtIndexPos(arguments[0], arguments[1], arguments[2]); } else if (arguments.length === 2) { this.mainLayer.addPointAtIndexPos(arguments[0], arguments[1]); } else { throw new Error("GPlot.addPointAtIndexPos(): signature not supported"); } this.updateLimits(); }; GPlot.prototype.setPointColors = function(pointColors) { this.mainLayer.setPointColors(pointColors); }; GPlot.prototype.setPointColor = function(pointColor) { this.mainLayer.setPointColor(pointColor); }; GPlot.prototype.setPointSizes = function(pointSizes) { this.mainLayer.setPointSizes(pointSizes); }; GPlot.prototype.setPointSize = function(pointSize) { this.mainLayer.setPointSize(pointSize); }; GPlot.prototype.setLineColor = function(lineColor) { this.mainLayer.setLineColor(lineColor); }; GPlot.prototype.setLineWidth = function(lineWidth) { this.mainLayer.setLineWidth(lineWidth); }; GPlot.prototype.setHistBasePoint = function(basePoint) { this.mainLayer.setHistBasePoint(basePoint); }; GPlot.prototype.setHistType = function(histType) { this.mainLayer.setHistType(histType); }; GPlot.prototype.setHistVisible = function(histVisible) { this.mainLayer.setHistVisible(histVisible); }; GPlot.prototype.setDrawHistLabels = function(drawHistLabels) { this.mainLayer.setDrawHistLabels(drawHistLabels); }; GPlot.prototype.setLabelBgColor = function(labelBgColor) { this.mainLayer.setLabelBgColor(labelBgColor); }; GPlot.prototype.setLabelSeparation = function(labelSeparation) { this.mainLayer.setLabelSeparation(labelSeparation); }; GPlot.prototype.setTitleText = function(text) { this.title.setText(text); }; GPlot.prototype.setAxesOffset = function(offset) { this.xAxis.setOffset(offset); this.topAxis.setOffset(offset); this.yAxis.setOffset(offset); this.rightAxis.setOffset(offset); }; GPlot.prototype.setTicksLength = function(tickLength) { this.xAxis.setTickLength(tickLength); this.topAxis.setTickLength(tickLength); this.yAxis.setTickLength(tickLength); this.rightAxis.setTickLength(tickLength); }; GPlot.prototype.setHorizontalAxesNTicks = function(nTicks) { this.xAxis.setNTicks(nTicks); this.topAxis.setNTicks(nTicks); }; GPlot.prototype.setHorizontalAxesTicksSeparation = function(ticksSeparation) { this.xAxis.setTicksSeparation(ticksSeparation); this.topAxis.setTicksSeparation(ticksSeparation); }; GPlot.prototype.setHorizontalAxesTicks = function(ticks) { this.xAxis.setTicks(ticks); this.topAxis.setTicks(ticks); }; GPlot.prototype.setVerticalAxesNTicks = function(nTicks) { this.yAxis.setNTicks(nTicks); this.rightAxis.setNTicks(nTicks); }; GPlot.prototype.setVerticalAxesTicksSeparation = function(ticksSeparation) { this.yAxis.setTicksSeparation(ticksSeparation); this.rightAxis.setTicksSeparation(ticksSeparation); }; GPlot.prototype.setVerticalAxesTicks = function(ticks) { this.yAxis.setTicks(ticks); this.rightAxis.setTicks(ticks); }; GPlot.prototype.setFontName = function(fontName) { this.maniLayer.setFontName(fontName); }; GPlot.prototype.setFontColor = function(fontColor) { this.maniLayer.setFontColor(fontColor); }; GPlot.prototype.setFontSize = function(fontSize) { this.maniLayer.setFontSize(fontSize); }; GPlot.prototype.setFontProperties = function(fontName, fontColor, fontSize) { this.maniLayer.setFontProperties(fontName, fontColor, fontSize); }; GPlot.prototype.setAllFontProperties = function(fontName, fontColor, fontSize) { this.xAxis.setAllFontProperties(fontName, fontColor, fontSize); this.topAxis.setAllFontProperties(fontName, fontColor, fontSize); this.yAxis.setAllFontProperties(fontName, fontColor, fontSize); this.rightAxis.setAllFontProperties(fontName, fontColor, fontSize); this.title.setFontProperties(fontName, fontColor, fontSize); this.mainLayer.setAllFontProperties(fontName, fontColor, fontSize); for (var i = 0; i < layerList.length; i++) { this.layerList[i].setAllFontProperties(fontName, fontColor, fontSize); } }; GPlot.prototype.getPos = function() { return this.pos.slice(); }; GPlot.prototype.getOuterDim = function() { return this.outerDim.slice(); }; GPlot.prototype.getMar = function() { return this.mar.slice(); }; GPlot.prototype.getDim = function() { return this.dim.slice(); }; GPlot.prototype.getXLim = function() { return this.xLim.slice(); }; GPlot.prototype.getYLim = function() { return this.yLim.slice(); }; GPlot.prototype.getFixedXLim = function() { return this.fixedXLim; }; GPlot.prototype.getFixedYLim = function() { return this.fixedYLim; }; GPlot.prototype.getXLog = function() { return this.xLog; }; GPlot.prototype.getYLog = function() { return this.yLog; }; GPlot.prototype.getInvertedXScale = function() { return this.invertedXScale; }; GPlot.prototype.getInvertedYScale = function() { return this.invertedYScale; }; GPlot.prototype.getMainLayer = function() { return this.mainLayer; }; GPlot.prototype.getLayer = function(id) { var layer; if (this.mainLayer.isId(id)) { layer = this.mainLayer; } else { for (var i = 0; i < this.layerList.length; i++) { if (this.layerList[i].isId(id)) { layer = this.layerList[i]; break; } } } if ( typeof layer === "undefined") { console.log("Couldn't find a layer in the plot with id = " + id); } return layer; }; GPlot.prototype.getXAxis = function() { return this.xAxis; }; GPlot.prototype.getYAxis = function() { return this.yAxis; }; GPlot.prototype.getTopAxis = function() { return this.topAxis; }; GPlot.prototype.getRightAxis = function() { return this.rightAxis; }; GPlot.prototype.getTitle = function() { return this.title; }; GPlot.prototype.getPoints = function() { if (arguments.length === 1) { return this.getLayer(arguments[0]).getPoints(); } else if (arguments.length === 0) { return this.mainLayer.getPoints(); } else { throw new Error("GPlot.getPoints(): signature not supported"); } }; GPlot.prototype.getPointsRef = function() { if (arguments.length === 1) { return this.getLayer(arguments[0]).getPointsRef(); } else if (arguments.length === 0) { return this.mainLayer.getPointsRef(); } else { throw new Error("GPlot.getPointsRef(): signature not supported"); } }; GPlot.prototype.getHistogram = function() { if (arguments.length === 1) { return this.getLayer(arguments[0]).getHistogram(); } else if (arguments.length === 0) { return this.mainLayer.getHistogram(); } else { throw new Error("GPlot.getHistogram(): signature not supported"); } }; GPlot.prototype.activateZooming = function() { var zoomFactor, increaseButton, decreaseButton, increaseKeyModifier, decreaseKeyModifier; if (arguments.length === 5) { zoomFactor = arguments[0]; increaseButton = arguments[1]; decreaseButton = arguments[2]; increaseKeyModifier = arguments[3]; decreaseKeyModifier = arguments[4]; } else if (arguments.length === 3) { zoomFactor = arguments[0]; increaseButton = arguments[1]; decreaseButton = arguments[2]; increaseKeyModifier = GPlot.NONE; decreaseKeyModifier = GPlot.NONE; } else if (arguments.length === 1) { zoomFactor = arguments[0]; increaseButton = this.parent.LEFT; decreaseButton = this.parent.RIGHT; increaseKeyModifier = GPlot.NONE; decreaseKeyModifier = GPlot.NONE; } else if (arguments.length === 0) { zoomFactor = 1.3; increaseButton = this.parent.LEFT; decreaseButton = this.parent.RIGHT; increaseKeyModifier = GPlot.NONE; decreaseKeyModifier = GPlot.NONE; } else { throw new Error("GPlot.activateZooming(): signature not supported"); } this.zoomingIsActive = true; if (zoomFactor > 0) { this.zoomFactor = zoomFactor; } if (increaseButton === this.parent.LEFT || increaseButton === this.parent.RIGHT || increaseButton === this.parent.CENTER) { this.increaseZoomButton = increaseButton; } if (decreaseButton === this.parent.LEFT || decreaseButton === this.parent.RIGHT || decreaseButton === this.parent.CENTER) { this.decreaseZoomButton = decreaseButton; } if (increaseKeyModifier === this.parent.SHIFT || increaseKeyModifier === this.parent.CONTROL || increaseKeyModifier === this.parent.ALT || increaseKeyModifier === GPlot.NONE) { this.increaseZoomKeyModifier = increaseKeyModifier; } if (decreaseKeyModifier === this.parent.SHIFT || decreaseKeyModifier === this.parent.CONTROL || decreaseKeyModifier === this.parent.ALT || decreaseKeyModifier === GPlot.NONE) { this.decreaseZoomKeyModifier = decreaseKeyModifier; } }; GPlot.prototype.deactivateZooming = function() { this.zoomingIsActive = false; }; GPlot.prototype.activateCentering = function() { var button, keyModifier; if (arguments.length === 2) { button = arguments[0]; keyModifier = arguments[1]; } else if (arguments.length === 1) { button = arguments[0]; keyModifier = GPlot.NONE; } else if (arguments.length === 0) { button = this.parent.LEFT; keyModifier = GPlot.NONE; } else { throw new Error("GPlot.activateCentering(): signature not supported"); } this.centeringIsActive = true; if (button === this.parent.LEFT || button === this.parent.RIGHT || button === this.parent.CENTER) { this.centeringButton = button; } if (keyModifier === this.parent.SHIFT || keyModifier === this.parent.CONTROL || keyModifier === this.parent.ALT || keyModifier === GPlot.NONE) { this.centeringKeyModifier = keyModifier; } }; GPlot.prototype.deactivateCentering = function() { this.centeringIsActive = false; }; GPlot.prototype.activatePanning = function() { var button, keyModifier; if (arguments.length === 2) { button = arguments[0]; keyModifier = arguments[1]; } else if (arguments.length === 1) { button = arguments[0]; keyModifier = GPlot.NONE; } else if (arguments.length === 0) { button = this.parent.LEFT; keyModifier = GPlot.NONE; } else { throw new Error("GPlot.activatePanning(): signature not supported"); } this.panningIsActive = true; if (button === this.parent.LEFT || button === this.parent.RIGHT || button === this.parent.CENTER) { this.panningButton = button; } if (keyModifier === this.parent.SHIFT || keyModifier === this.parent.CONTROL || keyModifier === this.parent.ALT || keyModifier === GPlot.NONE) { this.panningKeyModifier = keyModifier; } }; GPlot.prototype.deactivatePanning = function() { this.panningIsActive = false; this.panningReferencePoint = undefined; }; GPlot.prototype.activatePointLabels = function() { var button, keyModifier; if (arguments.length === 2) { button = arguments[0]; keyModifier = arguments[1]; } else if (arguments.length === 1) { button = arguments[0]; keyModifier = GPlot.NONE; } else if (arguments.length === 0) { button = this.parent.LEFT; keyModifier = GPlot.NONE; } else { throw new Error("GPlot.activatePointLabels(): signature not supported"); } this.labelingIsActive = true; if (button === this.parent.LEFT || button === this.parent.RIGHT || button === this.parent.CENTER) { this.labelingButton = button; } if (keyModifier === this.parent.SHIFT || keyModifier === this.parent.CONTROL || keyModifier === this.parent.ALT || keyModifier === GPlot.NONE) { this.labelingKeyModifier = keyModifier; } }; GPlot.prototype.deactivatePointLabels = function() { this.labelingIsActive = false; this.mousePos = undefined; }; GPlot.prototype.activateReset = function() { var button, keyModifier; if (arguments.length === 2) { button = arguments[0]; keyModifier = arguments[1]; } else if (arguments.length === 1) { button = arguments[0]; keyModifier = GPlot.NONE; } else if (arguments.length === 0) { button = this.parent.RIGHT; keyModifier = GPlot.NONE; } else { throw new Error("GPlot.activateReset(): signature not supported"); } this.resetIsActive = true; this.xLimReset = undefined; this.yLimReset = undefined; if (button === this.parent.LEFT || button === this.parent.RIGHT || button === this.parent.CENTER) { this.resetButton = button; } if (keyModifier === this.parent.SHIFT || keyModifier === this.parent.CONTROL || keyModifier === this.parent.ALT || keyModifier === GPlot.NONE) { this.resetKeyModifier = keyModifier; } }; GPlot.prototype.deactivateReset = function() { this.resetIsActive = false; this.xLimReset = undefined; this.yLimReset = undefined; }; GPlot.prototype.getButton = function(event) { var button; if (event.button === 0) { button = this.parent.LEFT; } else if (event.button === 1) { button = this.parent.CENTER; } else if (event.button === 2) { button = this.parent.RIGHT; } else if ( typeof event.button === "undefined") { button = this.parent.LEFT; } return button; }; GPlot.prototype.getModifier = function(event) { var modifier; if (event.altKey) { modifier = this.parent.ALT; } else if (event.ctrlKey) { modifier = this.parent.CONTROL; } else if (event.shiftKey) { modifier = this.parent.SHIFT; } else { modifier = GPlot.NONE; } return modifier; }; GPlot.prototype.saveResetLimits = function() { if ( typeof this.xLimReset === "undefined" || typeof this.yLimReset === "undefined") { this.xLimReset = this.xLim.slice(); this.yLimReset = this.yLim.slice(); } }; GPlot.prototype.panningFunction = function() { if ( typeof this.panningReferencePoint !== "undefined") { this.align(this.panningReferencePoint, this.parent.mouseX, this.parent.mouseY); } }; GPlot.prototype.clickEvent = function(event) { e = event || window.event; if (this.isOverBox()) { var button = this.getButton(e); var modifier = this.getModifier(e); if (this.zoomingIsActive) { if (button === this.increaseZoomButton && modifier === this.increaseZoomKeyModifier) { // Save the axes limits if (this.resetIsActive) { this.saveResetLimits(); } this.zoom(this.zoomFactor, this.parent.mouseX, this.parent.mouseY); } else if (button === this.decreaseZoomButton && modifier === this.decreaseZoomKeyModifier) { // Save the axes limits if (this.resetIsActive) { this.saveResetLimits(); } this.zoom(1 / this.zoomFactor, this.parent.mouseX, this.parent.mouseY); } } if (this.centeringIsActive && button === this.centeringButton && modifier === this.centeringKeyModifier) { // Save the axes limits if (this.resetIsActive) { this.saveResetLimits(); } this.center(this.parent.mouseX, this.parent.mouseY); } if (this.resetIsActive && button === this.resetButton && modifier === this.resetKeyModifier) { if ( typeof this.xLimReset !== "undefined" && typeof this.yLimReset !== "undefined") { this.setXLim(this.xLimReset); this.setYLim(this.yLimReset); this.xLimReset = undefined; this.yLimReset = undefined; } } } }; GPlot.prototype.mouseDownEvent = function(event) { e = event || window.event; if (this.isOverBox()) { var button = this.getButton(e); var modifier = this.getModifier(e); if (this.panningIsActive && button === this.panningButton && modifier === this.panningKeyModifier) { // Save the axes limits if (this.resetIsActive) { this.saveResetLimits(); } // Calculate the panning reference point this.panningReferencePoint = this.getValueAt(this.parent.mouseX, this.parent.mouseY); // Execute the panning function every 100ms this.panningIntervalId = setInterval(this.panningFunction.bind(this), 100); } if (this.labelingIsActive && button === this.labelingButton && modifier === this.labelingKeyModifier) { this.mousePos = [this.parent.mouseX, this.parent.mouseY]; } } }; GPlot.prototype.mouseUpEvent = function(event) { e = event || window.event; var button = this.getButton(e); var modifier = this.getModifier(e); if (this.panningIsActive && button === this.panningButton && typeof this.panningIntervalId !== "undefined") { // Stop executing the panning function clearInterval(this.panningIntervalId); // Reset the panning variables this.panningIntervalId = undefined; this.panningReferencePoint = undefined; } if (this.labelingIsActive && button === this.labelingButton) { this.mousePos = undefined; } if (button === this.parent.RIGHT) { // This is a right click! this.clickEvent(e); } }; GPlot.prototype.wheelEvent = function(event) { e = event || window.event; if (this.isOverBox()) { var deltaY = e.deltaY; var button = this.parent.CENTER; var modifier = this.getModifier(e); if (this.zoomingIsActive) { if (button === this.increaseZoomButton && modifier === this.increaseZoomKeyModifier && deltaY > 0) { // Save the axes limits if (this.resetIsActive) { this.saveResetLimits(); } this.zoom(this.zoomFactor, this.parent.mouseX, this.parent.mouseY); } else if (button === this.decreaseZoomButton && modifier === this.decreaseZoomKeyModifier && deltaY < 0) { // Save the axes limits if (this.resetIsActive) { this.saveResetLimits(); } this.zoom(1 / this.zoomFactor, this.parent.mouseX, this.parent.mouseY); } } } }; GPlot.prototype.preventDefaultEvent = function(event) { e = event || window.event; if (this.isOverBox()) { // Don't show the menu inside the plot area if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } } }; GPlot.prototype.preventWheelDefault = function() { window.addEventListener("wheel", this.preventDefaultEvent.bind(this), false); }; GPlot.prototype.preventRightClickDefault = function() { window.addEventListener("contextmenu", this.preventDefaultEvent.bind(this), false); };