Full Code of jagracar/grafica.js for AI

master ebb91ab65605 cached
28 files
1.1 MB
324.4k tokens
52 symbols
1 requests
Download .txt
Showing preview only (1,110K chars total). Download the full file or copy to clipboard to get everything.
Repository: jagracar/grafica.js
Branch: master
Commit: ebb91ab65605
Files: 28
Total size: 1.1 MB

Directory structure:
gitextract_vmqybpgv/

├── .gitignore
├── .project
├── Gruntfile.js
├── LICENSE
├── README.md
├── examples/
│   ├── data/
│   │   └── OktoberfestVSGermanElections.csv
│   ├── defaultPlot.js
│   ├── examples.html
│   ├── exponentialTrend.js
│   ├── movingPoints.js
│   ├── multiplePanels.js
│   ├── multiplePlots.js
│   ├── oktoberfest.js
│   └── twoVerticalAxes.js
├── lib/
│   └── grafica.js
├── package.json
├── releases/
│   ├── grafica-0.1.0.js
│   ├── grafica-0.2.0.js
│   ├── grafica-0.3.0.js
│   ├── grafica-0.3.1.js
│   └── grafica.js
└── src/
    ├── GAxis.js
    ├── GAxisLabel.js
    ├── GHistogram.js
    ├── GLayer.js
    ├── GPlot.js
    ├── GPoint.js
    └── GTitle.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
node_modules


================================================
FILE: .project
================================================
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
	<name>grafica.js</name>
	<comment></comment>
	<projects>
	</projects>
	<buildSpec>
	</buildSpec>
	<natures>
	</natures>
</projectDescription>


================================================
FILE: Gruntfile.js
================================================
module.exports = function(grunt) {

	// Project configuration.
	grunt.initConfig({
		pkg : grunt.file.readJSON('package.json'),
		jshint : {
			files : ['Gruntfile.js', 'src/**/*.js', 'examples/**/*.js'],
		},
		concat : {
			options : {
				separator : ''
			},
			dist : {
				src : ['src/GPoint.js', 'src/GTitle.js', 'src/GAxisLabel.js', 'src/GAxis.js', 'src/GHistogram.js', 'src/GLayer.js', 'src/GPlot.js'],
				dest : 'lib/<%= pkg.name %>.js'
			}
		},
		uglify : {
			options : {
				banner : '/*! <%= pkg.name %>.js (<%= pkg.version %>): a library for p5.js. Author: <%= pkg.author %>, license: <%= pkg.license %>, <%= grunt.template.today("yyyy-mm-dd") %> */ '
			},
			build : {
				src : 'lib/<%= pkg.name %>.js',
				dest : 'lib/<%= pkg.name %>.min.js'
			}
		},
		copy : {
			main : {
				files : [{
					expand : true,
					cwd : 'lib/',
					src : ['**'],
					dest : 'releases/'
				}]
			}
		}
	});

	grunt.loadNpmTasks('grunt-contrib-jshint');
	grunt.loadNpmTasks('grunt-contrib-concat');
	grunt.loadNpmTasks('grunt-contrib-uglify');
	grunt.loadNpmTasks('grunt-contrib-copy');

	// Default task(s).
	grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'copy']);
};


================================================
FILE: LICENSE
================================================
                   GNU LESSER GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.


  This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.

  0. Additional Definitions.

  As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.

  "The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.

  An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.

  A "Combined Work" is a work produced by combining or linking an
Application with the Library.  The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".

  The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.

  The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.

  1. Exception to Section 3 of the GNU GPL.

  You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.

  2. Conveying Modified Versions.

  If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:

   a) under this License, provided that you make a good faith effort to
   ensure that, in the event an Application does not supply the
   function or data, the facility still operates, and performs
   whatever part of its purpose remains meaningful, or

   b) under the GNU GPL, with none of the additional permissions of
   this License applicable to that copy.

  3. Object Code Incorporating Material from Library Header Files.

  The object code form of an Application may incorporate material from
a header file that is part of the Library.  You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:

   a) Give prominent notice with each copy of the object code that the
   Library is used in it and that the Library and its use are
   covered by this License.

   b) Accompany the object code with a copy of the GNU GPL and this license
   document.

  4. Combined Works.

  You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:

   a) Give prominent notice with each copy of the Combined Work that
   the Library is used in it and that the Library and its use are
   covered by this License.

   b) Accompany the Combined Work with a copy of the GNU GPL and this license
   document.

   c) For a Combined Work that displays copyright notices during
   execution, include the copyright notice for the Library among
   these notices, as well as a reference directing the user to the
   copies of the GNU GPL and this license document.

   d) Do one of the following:

       0) Convey the Minimal Corresponding Source under the terms of this
       License, and the Corresponding Application Code in a form
       suitable for, and under terms that permit, the user to
       recombine or relink the Application with a modified version of
       the Linked Version to produce a modified Combined Work, in the
       manner specified by section 6 of the GNU GPL for conveying
       Corresponding Source.

       1) Use a suitable shared library mechanism for linking with the
       Library.  A suitable mechanism is one that (a) uses at run time
       a copy of the Library already present on the user's computer
       system, and (b) will operate properly with a modified version
       of the Library that is interface-compatible with the Linked
       Version.

   e) Provide Installation Information, but only if you would otherwise
   be required to provide such information under section 6 of the
   GNU GPL, and only to the extent that such information is
   necessary to install and execute a modified version of the
   Combined Work produced by recombining or relinking the
   Application with a modified version of the Linked Version. (If
   you use option 4d0, the Installation Information must accompany
   the Minimal Corresponding Source and Corresponding Application
   Code. If you use option 4d1, you must provide the Installation
   Information in the manner specified by section 6 of the GNU GPL
   for conveying Corresponding Source.)

  5. Combined Libraries.

  You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:

   a) Accompany the combined library with a copy of the same work based
   on the Library, uncombined with any other library facilities,
   conveyed under the terms of this License.

   b) Give prominent notice with the combined library that part of it
   is a work based on the Library, and explaining where to find the
   accompanying uncombined form of the same work.

  6. Revised Versions of the GNU Lesser General Public License.

  The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.

  Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.

  If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.



================================================
FILE: README.md
================================================
# grafica.js

grafica.js is a simple and configurable plotting library for [p5.js](http://p5js.org). It's the javaScript
version of the [grafica](https://github.com/jagracar/grafica) Processing library.
With it you can easily create 2D plots that will enjoy the full interactive capabilities of Processing in the web. 


## Main features

  * Make fancy scatter and linear plots that update in real time.
  * Display histograms in the vertical and horizontal directions.
  * Add several layers with different properties to the same plot.
  * It works both with linear and logarithmic scales.
  * Automatic axis tick determination.
  * Interactive zooming and panning. Make your data move!
  * Add labels to your points and display them with one click.
  * You can use images to represent your points.
  * Highly customizable. Defaults are nice, but you can tweak almost everything.
  * Processing coding style. If you are used to work with Processing (or p5.js), grafica.js will be very easy.
  * It comes with a good set of [examples](https://github.com/jagracar/grafica.js/tree/master/examples). Check them live at 
  [openprocessing](http://www.openprocessing.org/collection/4807) and [JSFiddle](http://jsfiddle.net/user/jagracar/fiddles/).
  * It's open source. grafica.js is under the GNU Lesser General Public License. You can find the complete source code [here](https://github.com/jagracar/grafica.js/tree/master/src).

## Installation

Download the [latest minified library release](https://raw.github.com/jagracar/grafica.js/master/releases/grafica.min.js) and follow the steps 
described [here](http://p5js.org/libraries/).


================================================
FILE: examples/data/OktoberfestVSGermanElections.csv
================================================
year,month,day,oktoberfest,bundestagswahl
2004,0,1,5,1
2004,1,1,4,1
2004,2,1,6,1
2004,3,1,5,1
2004,4,1,6,1
2004,5,1,8,1
2004,6,1,12,1
2004,7,1,26,0
2004,8,1,84,1
2004,9,1,38,1
2004,10,1,5,1
2004,11,1,4,1
2005,0,1,5,1
2005,1,1,5,1
2005,2,1,5,1
2005,3,1,6,1
2005,4,1,7,2
2005,5,1,9,3
2005,6,1,12,4
2005,7,1,23,16
2005,8,1,81,64
2005,9,1,39,5
2005,10,1,5,2
2005,11,1,4,1
2006,0,1,5,1
2006,1,1,4,1
2006,2,1,5,1
2006,3,1,5,1
2006,4,1,6,0
2006,5,1,7,1
2006,6,1,11,0
2006,7,1,25,0
2006,8,1,76,1
2006,9,1,38,1
2006,10,1,5,1
2006,11,1,3,0
2007,0,1,5,0
2007,1,1,5,0
2007,2,1,5,1
2007,3,1,6,0
2007,4,1,7,0
2007,5,1,9,1
2007,6,1,12,0
2007,7,1,24,0
2007,8,1,79,0
2007,9,1,46,1
2007,10,1,5,1
2007,11,1,3,0
2008,0,1,5,1
2008,1,1,5,1
2008,2,1,5,1
2008,3,1,6,0
2008,4,1,6,0
2008,5,1,7,0
2008,6,1,11,0
2008,7,1,22,0
2008,8,1,77,1
2008,9,1,44,1
2008,10,1,5,1
2008,11,1,3,1
2009,0,1,4,2
2009,1,1,5,1
2009,2,1,5,1
2009,3,1,5,1
2009,4,1,6,2
2009,5,1,8,3
2009,6,1,12,3
2009,7,1,24,12
2009,8,1,91,79
2009,9,1,53,5
2009,10,1,6,2
2009,11,1,4,1
2010,0,1,5,2
2010,1,1,5,1
2010,2,1,6,1
2010,3,1,6,1
2010,4,1,7,1
2010,5,1,8,1
2010,6,1,13,0
2010,7,1,27,0
2010,8,1,100,1
2010,9,1,59,1
2010,10,1,7,1
2010,11,1,4,0
2011,0,1,5,1
2011,1,1,5,1
2011,2,1,5,1
2011,3,1,5,0
2011,4,1,7,1
2011,5,1,8,0
2011,6,1,13,0
2011,7,1,26,0
2011,8,1,95,1
2011,9,1,64,1
2011,10,1,10,1
2011,11,1,6,0
2012,0,1,6,1
2012,1,1,5,1
2012,2,1,5,1
2012,3,1,6,1
2012,4,1,7,1
2012,5,1,8,0
2012,6,1,13,0
2012,7,1,28,0
2012,8,1,97,1
2012,9,1,69,1
2012,10,1,9,1
2012,11,1,5,1
2013,0,1,5,2
2013,1,1,5,1
2013,2,1,5,1
2013,3,1,6,1
2013,4,1,7,2
2013,5,1,8,2
2013,6,1,11,3
2013,7,1,27,16
2013,8,1,64,32


================================================
FILE: examples/defaultPlot.js
================================================
var defaultPlotSketch = function(p) {
	// Initial setup
	p.setup = function() {
		// Create the canvas
		var canvas = p.createCanvas(500, 350);
		p.background(150);

		// Prepare the points for the plot
		var points = [];
		var seed = 100 * p.random();

		for (var i = 0; i < 100; i++) {
			points[i] = new GPoint(i, 10 * p.noise(0.1 * i + seed));
		}

		// Create a new plot and set its position on the screen
		var plot = new GPlot(p);
		plot.setPos(25, 25);

		// Set the plot title and the axis labels
		plot.setPoints(points);
		plot.getXAxis().setAxisLabelText("x axis");
		plot.getYAxis().setAxisLabelText("y axis");
		plot.setTitleText("A very simple example");

		// Draw it!
		plot.defaultDraw();

		p.noLoop();
	};
};


================================================
FILE: examples/examples.html
================================================
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="keywords"
	content="processing, p5.js, P5js, grafica, grafica.js, javaScript, examples">
<meta name="description" content="grafica.js examples">
<meta name="author" content="Javier Graciá Carpio">
<title>grafica.js examples</title>
</head>

<body>
	<h1>grafica.js</h1>

	<p>Some basic examples showing the grafica.js library capabilities:
	</p>

	<ul>
		<li onclick="runSketch(defaultPlotSketch);">Default plot example
		</li>
		<li onclick="runSketch(multiplePlotsSketch);">Multiple plots
			example</li>
		<li onclick="runSketch(multiplePanelsSketch);">Multiple panels
			example</li>
		<li onclick="runSketch(exponentialTrendSketch);">Exponential
			trend example</li>
		<li onclick="runSketch(movingPointsSketch);">Moving points
			example</li>
		<li onclick="runSketch(oktoberfestSketch);">Oktoberfest example</li>
		<li onclick="runSketch(twoVerticalAxesSketch);">Two vertical axis
			example</li>
	</ul>

	<div id="sketchContainer"></div>

	<!-- JavaScript files -->
	<script
		src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.10/p5.min.js"></script>
	<script src="../lib/grafica.min.js"></script>
	<script src="defaultPlot.js"></script>
	<script src="multiplePlots.js"></script>
	<script src="multiplePanels.js"></script>
	<script src="exponentialTrend.js"></script>
	<script src="movingPoints.js"></script>
	<script src="oktoberfest.js"></script>
	<script src="twoVerticalAxes.js"></script>
	<script>
		// This is the sketch launcher
		var p5Sketch;

		function runSketch(sketch) {
			if (typeof p5Sketch !== 'undefined') {
				p5Sketch.remove();
			}

			p5Sketch = new p5(sketch, "sketchContainer");
		}
	</script>
</body>


================================================
FILE: examples/exponentialTrend.js
================================================
var exponentialTrendSketch = function(p) {
	// Global variables
	var plot, logScale;

	// Initial setup
	p.setup = function() {
		// Create the canvas
		var canvas = p.createCanvas(450, 450);

		// Prepare the points for the plot
		var points = [];

		for (var i = 0; i < 1000; i++) {
			var x = 10 + p.random(200);
			var y = 10 * p.exp(0.015 * x);
			var xErr = p.randomGaussian(0, 2);
			var yErr = p.randomGaussian(0, 2);
			points[i] = new GPoint(x + xErr, y + yErr);
		}

		// Create the plot
		plot = new GPlot(p);
		plot.setPos(25, 25);
		plot.setDim(300, 300);
		// or all in one go
		// plot = new GPlot(p, 25, 25, 300, 300);

		// Set the plot title and the axis labels
		plot.setTitleText("Exponential law");
		plot.getXAxis().setAxisLabelText("x");

		if (logScale) {
			plot.setLogScale("y");
			plot.getYAxis().setAxisLabelText("log y");
		} else {
			plot.setLogScale("");
			plot.getYAxis().setAxisLabelText("y");
		}

		// Add the points to the plot
		plot.setPoints(points);
		plot.setPointColor(p.color(100, 100, 255, 50));
	};

	// Execute the sketch
	p.draw = function() {
		// Clean the canvas
		p.background(150);

		// Draw the plot
		plot.beginDraw();
		plot.drawBackground();
		plot.drawBox();
		plot.drawXAxis();
		plot.drawYAxis();
		plot.drawTopAxis();
		plot.drawRightAxis();
		plot.drawTitle();
		plot.drawPoints();
		plot.endDraw();
	};

	p.mouseClicked = function() {
		if (plot.isOverBox(p.mouseX, p.mouseY)) {
			// Change the log scale
			logScale = !logScale;

			if (logScale) {
				plot.setLogScale("y");
				plot.getYAxis().setAxisLabelText("log y");
			} else {
				plot.setLogScale("");
				plot.getYAxis().setAxisLabelText("y");
			}
		}
	};
};


================================================
FILE: examples/movingPoints.js
================================================
var movingPointsSketch = function(p) {
	// Global variables
	var plot, i;
	var step = 0;
	var stepsPerCycle = 100;
	var lastStepTime = 0;
	var clockwise = true;
	var scale = 5;

	// Initial setup
	p.setup = function() {
		// Create the canvas
		var canvas = p.createCanvas(450, 450);

		// Prepare the first set of points
		var nPoints1 = stepsPerCycle / 10;
		var points1 = [];

		for ( i = 0; i < nPoints1; i++) {
			points1[i] = calculatePoint(step, stepsPerCycle, scale);
			step = (clockwise) ? step + 1 : step - 1;
		}

		lastStepTime = p.millis();

		// Prepare the second set of points
		var nPoints2 = stepsPerCycle + 1;
		var points2 = [];

		for ( i = 0; i < nPoints2; i++) {
			points2[i] = calculatePoint(i, stepsPerCycle, 0.9 * scale);
		}

		// Create the plot
		plot = new GPlot(p);
		plot.setPos(25, 25);
		plot.setDim(300, 300);
		// or all in one go
		// plot = new GPlot(p, 25, 25, 300, 300);

		// Set the plot limits (this will fix them)
		plot.setXLim(-1.2 * scale, 1.2 * scale);
		plot.setYLim(-1.2 * scale, 1.2 * scale);

		// Set the plot title and the axis labels
		plot.setTitleText("Clockwise movement");
		plot.getXAxis().setAxisLabelText("x axis");
		plot.getYAxis().setAxisLabelText("y axis");

		// Activate the panning effect
		plot.activatePanning();

		// Add the two set of points to the plot
		plot.setPoints(points1);
		plot.addLayer("surface", points2);

		// Change the second layer line color
		plot.getLayer("surface").setLineColor(p.color(100, 255, 100));
	};

	// Execute the sketch
	p.draw = function() {
		// Clean the canvas
		p.background(150);

		// Draw the plot
		plot.beginDraw();
		plot.drawBackground();
		plot.drawBox();
		plot.drawXAxis();
		plot.drawYAxis();
		plot.drawTopAxis();
		plot.drawRightAxis();
		plot.drawTitle();
		plot.getMainLayer().drawPoints();
		plot.getLayer("surface").drawFilledContour(GPlot.HORIZONTAL, 0);
		plot.endDraw();

		// Add and remove new points every 10th of a second
		if (p.millis() - lastStepTime > 100) {
			if (clockwise) {
				// Add the point at the end of the array
				plot.addPoint(calculatePoint(step, stepsPerCycle, scale));
				step++;

				// Remove the first point
				plot.removePoint(0);
			} else {
				// Add the point at the beginning of the array
				plot.addPointAtIndexPos(0, calculatePoint(step, stepsPerCycle, scale));
				step--;

				// Remove the last point
				plot.removePoint(plot.getPointsRef().length - 1);
			}

			lastStepTime = p.millis();
		}
	};

	p.mouseClicked = function() {
		if (plot.isOverBox(p.mouseX, p.mouseY)) {
			// Change the movement sense
			clockwise = !clockwise;

			if (clockwise) {
				step += plot.getPointsRef().length + 1;
				plot.setTitleText("Clockwise movement");
			} else {
				step -= plot.getPointsRef().length + 1;
				plot.setTitleText("Anti-clockwise movement");
			}
		}
	};

	function calculatePoint(i, n, rad) {
		var delta = 0.1 * p.cos(p.TWO_PI * 10 * i / n);
		var ang = p.TWO_PI * i / n;
		return new GPoint(rad * (1 + delta) * p.sin(ang), rad * (1 + delta) * p.cos(ang));
	}

};


================================================
FILE: examples/multiplePanels.js
================================================
var multiplePanelsSketch = function(p) {
	// Initial setup
	p.setup = function() {
		// Create the canvas
		var canvas = p.createCanvas(500, 500);

		var firstPlotPos = [0, 0];
		var panelDim = [200, 200];
		var margins = [60, 70, 40, 30];

		// Create four plots to represent the 4 panels
		var plot1 = new GPlot(p);
		plot1.setPos(firstPlotPos);
		plot1.setMar(0, margins[1], margins[2], 0);
		plot1.setDim(panelDim);
		plot1.setAxesOffset(0);
		plot1.setTicksLength(-4);
		plot1.getXAxis().setDrawTickLabels(false);

		var plot2 = new GPlot(p);
		plot2.setPos(firstPlotPos[0] + margins[1] + panelDim[0], firstPlotPos[1]);
		plot2.setMar(0, 0, margins[2], margins[3]);
		plot2.setDim(panelDim);
		plot2.setAxesOffset(0);
		plot2.setTicksLength(-4);
		plot2.getXAxis().setDrawTickLabels(false);
		plot2.getYAxis().setDrawTickLabels(false);

		var plot3 = new GPlot(p);
		plot3.setPos(firstPlotPos[0], firstPlotPos[1] + margins[2] + panelDim[1]);
		plot3.setMar(margins[0], margins[1], 0, 0);
		plot3.setDim(panelDim);
		plot3.setAxesOffset(0);
		plot3.setTicksLength(-4);

		var plot4 = new GPlot(p);
		plot4.setPos(firstPlotPos[0] + margins[1] + panelDim[0], firstPlotPos[1] + margins[2] + panelDim[1]);
		plot4.setMar(margins[0], 0, 0, margins[3]);
		plot4.setDim(panelDim);
		plot4.setAxesOffset(0);
		plot4.setTicksLength(-4);
		plot4.getYAxis().setDrawTickLabels(false);

		// Prepare the points for the four plots
		var nPoints = 21;
		var points1 = [];
		var points2 = [];
		var points3 = [];
		var points4 = [];

		for (var i = 0; i < nPoints; i++) {
			points1[i] = new GPoint(p.sin(p.TWO_PI * i / (nPoints - 1)), p.cos(p.TWO_PI * i / (nPoints - 1)));
			points2[i] = new GPoint(i, p.cos(p.TWO_PI * i / (nPoints - 1)));
			points3[i] = new GPoint(p.sin(p.TWO_PI * i / (nPoints - 1)), i);
			points4[i] = new GPoint(i, i);
		}

		// Set the points, the title and the axis labels
		plot1.setPoints(points1);
		plot1.getYAxis().setAxisLabelText("cos(i)");
		plot1.setTitleText("Plot with multiple panels");
		plot1.getTitle().setRelativePos(1);
		plot1.getTitle().setTextAlignment(p.CENTER);

		plot2.setPoints(points2);

		plot3.setPoints(points3);
		plot3.getXAxis().setAxisLabelText("sin(i)");
		plot3.getYAxis().setAxisLabelText("i");
		plot3.setInvertedYScale(true);

		plot4.setPoints(points4);
		plot4.getXAxis().setAxisLabelText("i");
		plot4.setInvertedYScale(true);

		// Draw the plots
		plot1.beginDraw();
		plot1.drawBox();
		plot1.drawXAxis();
		plot1.drawYAxis();
		plot1.drawTopAxis();
		plot1.drawRightAxis();
		plot1.drawTitle();
		plot1.drawPoints();
		plot1.drawLines();
		plot1.endDraw();

		plot2.beginDraw();
		plot2.drawBox();
		plot2.drawXAxis();
		plot2.drawYAxis();
		plot2.drawTopAxis();
		plot2.drawRightAxis();
		plot2.drawPoints();
		plot2.drawLines();
		plot2.endDraw();

		plot3.beginDraw();
		plot3.drawBox();
		plot3.drawXAxis();
		plot3.drawYAxis();
		plot3.drawTopAxis();
		plot3.drawRightAxis();
		plot3.drawPoints();
		plot3.drawLines();
		plot3.endDraw();

		plot4.beginDraw();
		plot4.drawBox();
		plot4.drawXAxis();
		plot4.drawYAxis();
		plot4.drawTopAxis();
		plot4.drawRightAxis();
		plot4.drawPoints();
		plot4.drawLines();
		plot4.endDraw();

		p.noLoop();
	};
};


================================================
FILE: examples/multiplePlots.js
================================================
var multiplePlotsSketch = function(p) {
	// Global variables
	var plot1, plot2, plot3, plot4, i, index;
	var polygonPoints, mug, star;
	var gaussianStack, gaussianCounter, uniformStack, uniformCounter;

	// Load the image before the sketch is run
	p.preload = function() {
		mug = p.loadImage("data/beermug.png");
		star = p.loadImage("data/star.png");
	};

	// Initial setup
	p.setup = function() {
		// Create the canvas
		var canvas = p.createCanvas(850, 650);

		// Obtain the points for the first plot
		var points1a = [];
		var points1b = [];
		var points1c = [];

		for ( i = 0; i < 500; i++) {
			points1a[i] = new GPoint(i, p.noise(0.1 * i) + 1, "point " + i);
			points1b[i] = new GPoint(i, p.noise(500 + 0.1 * i) + 0.5, "point " + i);
			points1c[i] = new GPoint(i, p.noise(1000 + 0.1 * i), "point " + i);
		}

		// Create a polygon to display inside the plot
		polygonPoints = [];
		polygonPoints.push(new GPoint(2, 0.15));
		polygonPoints.push(new GPoint(6, 0.12));
		polygonPoints.push(new GPoint(15, 0.3));
		polygonPoints.push(new GPoint(8, 0.6));
		polygonPoints.push(new GPoint(1.5, 0.5));

		// Setup for the first plot
		plot1 = new GPlot(p);
		plot1.setPos(0, 0);
		plot1.setXLim(1, 100);
		plot1.setYLim(0.1, 3);
		plot1.getXAxis().getAxisLabel().setText("Time");
		plot1.getYAxis().getAxisLabel().setText("noise (0.1 time)");
		plot1.getTitle().setText("Multiple layers plot");
		plot1.setLogScale("xy");
		plot1.setPoints(points1a);
		plot1.setLineColor(p.color(200, 200, 255));
		plot1.addLayer("layer 1", points1b);
		plot1.getLayer("layer 1").setLineColor(p.color(150, 150, 255));
		plot1.addLayer("layer 2", points1c);
		plot1.getLayer("layer 2").setLineColor(p.color(100, 100, 255));

		// Leave empty the points for the second plot. We will fill them in draw()
		var points2 = [];

		// Setup for the second plot
		plot2 = new GPlot(p);
		plot2.setPos(460, 0);
		plot2.setDim(250, 250);
		plot2.getXAxis().getAxisLabel().setText("mouseX");
		plot2.getYAxis().getAxisLabel().setText("-mouseY");
		plot2.getTitle().setText("Mouse position");
		plot2.setPoints(points2);

		// Obtain the points for the third plot
		gaussianStack = [];
		gaussianCounter = 0;

		for ( i = 0; i < 10; i++) {
			gaussianStack[i] = 0;
		}

		for ( i = 0; i < 20; i++) {
			index = p.int(p.randomGaussian(gaussianStack.length / 2, 1));

			if (index >= 0 && index < gaussianStack.length) {
				gaussianStack[index]++;
				gaussianCounter++;
			}
		}

		var points3 = [];

		for ( i = 0; i < gaussianStack.length; i++) {
			points3[i] = new GPoint(i + 0.5 - gaussianStack.length / 2, gaussianStack[i] / gaussianCounter, "H" + i);
		}

		// Setup for the third plot
		plot3 = new GPlot(p);
		plot3.setPos(0, 300);
		plot3.setDim(250, 250);
		plot3.setYLim(-0.02, 0.45);
		plot3.setXLim(-gaussianStack.length / 2, gaussianStack.length / 2);
		plot3.getYAxis().getAxisLabel().setText("Relative probability");
		plot3.getYAxis().getAxisLabel().setTextAlignment(p.RIGHT);
		plot3.getYAxis().getAxisLabel().setRelativePos(1);
		plot3.getTitle().setText("Gaussian distribution (" + gaussianCounter + " points)");
		plot3.getTitle().setTextAlignment(p.LEFT);
		plot3.getTitle().setRelativePos(0);
		plot3.setPoints(points3);
		plot3.startHistograms(GPlot.VERTICAL);
		plot3.getHistogram().setDrawLabels(true);
		plot3.getHistogram().setRotateLabels(true);
		plot3.getHistogram().setBgColors([p.color(0, 0, 255, 50), p.color(0, 0, 255, 100), p.color(0, 0, 255, 150), p.color(0, 0, 255, 200)]);

		// Obtain the points for the fourth plot
		uniformStack = [];
		uniformCounter = 0;

		for ( i = 0; i < 30; i++) {
			uniformStack[i] = 0;
		}

		for ( i = 0; i < 20; i++) {
			index = p.int(p.random(uniformStack.length));

			if (index >= 0 && index < uniformStack.length) {
				uniformStack[index]++;
				uniformCounter++;
			}
		}

		var points4 = [];

		for ( i = 0; i < uniformStack.length; i++) {
			points4[i] = new GPoint(i + 0.5 - uniformStack.length / 2, uniformStack[i] / uniformCounter, "point " + i);
		}

		// Setup for the fourth plot
		plot4 = new GPlot(p);
		plot4.setPos(370, 350);
		plot4.setYLim(-0.005, 0.1);
		plot4.getXAxis().getAxisLabel().setText("x variable");
		plot4.getYAxis().getAxisLabel().setText("Relative probability");
		plot4.getTitle().setText("Uniform distribution (" + uniformCounter + " points)");
		plot4.getTitle().setTextAlignment(p.LEFT);
		plot4.getTitle().setRelativePos(0.1);
		plot4.setPoints(points4);
		plot4.startHistograms(GPlot.VERTICAL);

		// Setup the mouse actions
		plot1.activatePanning(p.LEFT, p.CONTROL);
		plot1.activatePointLabels();
		plot2.activateCentering();
		plot2.activateZooming(1.3, p.CENTER, p.CENTER);
		plot2.preventWheelDefault();
		plot3.activateCentering();
		plot4.activateZooming();
		plot4.preventRightClickDefault();

		// Resize the mug image
		mug.resize(0.5 * mug.width, 0.5 * mug.height);
	};

	// Execute the sketch
	p.draw = function() {
		// Clean the canvas
		p.background(255);

		// Draw the first plot
		plot1.beginDraw();
		plot1.drawBackground();
		plot1.drawBox();
		plot1.drawXAxis();
		plot1.drawYAxis();
		plot1.drawTopAxis();
		plot1.drawRightAxis();
		plot1.drawTitle();
		plot1.drawFilledContours(GPlot.HORIZONTAL, 0.05);
		plot1.drawPoint(new GPoint(65, 1.5), mug);
		plot1.drawPolygon(polygonPoints, p.color(255, 200));
		plot1.drawLabels();
		plot1.endDraw();

		// Add a new point to the second plot if the mouse moves significantly
		var points2 = plot2.getPoints();

		if (points2.length === 0) {
			points2.push(new GPoint(p.mouseX, -p.mouseY, "(" + p.mouseX + " , " + p.mouseY + ")"));
			plot2.setPoints(points2);
		} else {
			var lastPoint = points2[points2.length - 1];

			if (!lastPoint.isValid() || Math.pow(lastPoint.getX() - p.mouseX, 2) + Math.pow(lastPoint.getY() + p.mouseY, 2) > 2500) {
				points2.push(new GPoint(p.mouseX, -p.mouseY, "(" + p.mouseX + " , " + -p.mouseY + ")"));
				plot2.setPoints(points2);
			}
		}

		// Reset the points if the user pressed the space bar
		if (p.keyIsPressed && p.key === ' ') {
			plot2.setPoints([]);
		}

		// Draw the second plot
		plot2.beginDraw();
		plot2.drawBackground();
		plot2.drawBox();
		plot2.drawXAxis();
		plot2.drawYAxis();
		plot2.drawTitle();
		plot2.drawGridLines(GPlot.BOTH);
		plot2.drawLines();
		plot2.drawPoints(star);
		plot2.endDraw();

		// Add one more point to the gaussian stack
		index = p.int(p.randomGaussian(gaussianStack.length / 2, 1));

		if (index >= 0 && index < gaussianStack.length) {
			gaussianStack[index]++;
			gaussianCounter++;

			var points3 = [];

			for ( i = 0; i < gaussianStack.length; i++) {
				points3[i] = new GPoint(i + 0.5 - gaussianStack.length / 2, gaussianStack[i] / gaussianCounter, "H" + i);
			}

			plot3.setPoints(points3);
			plot3.getTitle().setText("Gaussian distribution (" + gaussianCounter + " points)");
		}

		// Draw the third plot
		plot3.beginDraw();
		plot3.drawBackground();
		plot3.drawBox();
		plot3.drawYAxis();
		plot3.drawTitle();
		plot3.drawHistograms();
		plot3.endDraw();

		// Actions over the fourth plot (scrolling)
		if (plot4.isOverBox(p.mouseX, p.mouseY)) {
			// Get the cursor relative position inside the inner plot area
			var relativePos = plot4.getRelativePlotPosAt(p.mouseX, p.mouseY);

			// Move the x axis
			if (relativePos[0] < 0.2) {
				plot4.moveHorizontalAxesLim(2);
			} else if (relativePos[0] > 0.8) {
				plot4.moveHorizontalAxesLim(-2);
			}

			// Move the y axis
			if (relativePos[1] < 0.2) {
				plot4.moveVerticalAxesLim(2);
			} else if (relativePos[1] > 0.8) {
				plot4.moveVerticalAxesLim(-2);
			}

			// Change the inner area bg color
			plot4.setBoxBgColor(p.color(200, 100));
		} else {
			plot4.setBoxBgColor(p.color(200, 50));
		}

		// Add one more point to the uniform stack
		index = p.int(p.random(uniformStack.length));

		if (index >= 0 && index < uniformStack.length) {
			uniformStack[index]++;
			uniformCounter++;

			var points4 = [];

			for ( i = 0; i < uniformStack.length; i++) {
				points4[i] = new GPoint(i + 0.5 - uniformStack.length / 2, uniformStack[i] / uniformCounter, "point " + i);
			}

			plot4.setPoints(points4);
			plot4.getTitle().setText("Uniform distribution (" + uniformCounter + " points)");
		}

		// Draw the forth plot
		plot4.beginDraw();
		plot4.drawBackground();
		plot4.drawBox();
		plot4.drawXAxis();
		plot4.drawYAxis();
		plot4.drawTitle();
		plot4.drawHistograms();
		plot4.endDraw();
	};
};


================================================
FILE: examples/oktoberfest.js
================================================
var oktoberfestSketch = function(p) {
	// Global variables
	var table, plot;
	var monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
	var daysPerMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
	var daysPerMonthLeapYear = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

	// Load the table before the sketch is run
	p.preload = function() {
		// Load the Oktoberfest vs. Bundestagswahl (German elections day) Google
		// search history file (obtained from the Google trends page).
		// The csv file has the following format:
		// year,month,day,oktoberfest,bundestagswahl
		// 2004,0,1,5,1
		// ...
		table = p.loadTable("data/OktoberfestVSGermanElections.csv", "header");
	};

	// Initial setup
	p.setup = function() {
		// Create the canvas
		var canvas = p.createCanvas(800, 400);

		// Save the table data in two GPointsArrays
		var pointsOktoberfest = [];
		var pointsElections = [];

		for (var row = 0; row < table.getRowCount(); row++) {
			var data = table.getRow(row);
			var year = data.getNum("year");
			var month = data.getNum("month");
			var day = data.getNum("day");
			var date = getExactDate(year, month, day);
			var oktoberfestCount = data.getNum("oktoberfest");
			var electionsCount = data.getNum("bundestagswahl");

			pointsOktoberfest[row] = new GPoint(date, oktoberfestCount, monthNames[month]);
			pointsElections[row] = new GPoint(date, electionsCount, monthNames[month]);
		}

		// Create the plot
		plot = new GPlot(p);
		plot.setDim(700, 300);
		plot.setTitleText("Oktoberfest vs. Bundestagwahl Google search history");
		plot.getXAxis().setAxisLabelText("Year");
		plot.getYAxis().setAxisLabelText("Google normalized searches");
		plot.getXAxis().setNTicks(10);
		plot.setPoints(pointsOktoberfest);
		plot.setLineColor(p.color(100, 100, 100));
		plot.addLayer("German elections day", pointsElections);
		plot.getLayer("German elections day").setLineColor(p.color(255, 100, 255));
		plot.activatePointLabels();
	};

	// Execute the sketch
	p.draw = function() {
		// Clean the canvas
		p.background(255);

		// Draw the plot
		plot.beginDraw();
		plot.drawBox();
		plot.drawXAxis();
		plot.drawYAxis();
		plot.drawTitle();
		plot.drawGridLines(GPlot.VERTICAL);
		plot.drawFilledContours(GPlot.HORIZONTAL, 0);
		plot.drawLegend(["Oktoberfest", "Bundestagswahl"], [0.07, 0.22], [0.92, 0.92]);
		plot.drawLabels();
		plot.endDraw();
	};

	function getExactDate(year, month, day) {
		var leapYear = false;

		if (year % 400 === 0) {
			leapYear = true;
		} else if (year % 100 === 0) {
			leapYear = false;
		} else if (year % 4 === 0) {
			leapYear = true;
		}

		if (leapYear) {
			return year + (month + (day - 1) / daysPerMonthLeapYear[month]) / 12;
		} else {
			return year + (month + (day - 1) / daysPerMonth[month]) / 12;
		}
	}

};


================================================
FILE: examples/twoVerticalAxes.js
================================================
var twoVerticalAxesSketch = function(p) {
	// Global variables
	var plot1, plot2;

	// Initial setup
	p.setup = function() {
		// Create the canvas
		var canvas = p.createCanvas(450, 300);

		// Create the first plot
		plot1 = new GPlot(p);
		plot1.setPos(0, 0);
		plot1.setMar(60, 70, 40, 70);
		plot1.setDim(310, 200);
		plot1.setAxesOffset(4);
		plot1.setTicksLength(4);

		// Create the second plot with the same dimensions
		plot2 = new GPlot(p);
		plot2.setPos(plot1.getPos());
		plot2.setMar(plot1.getMar());
		plot2.setDim(plot1.getDim());
		plot2.setAxesOffset(4);
		plot2.setTicksLength(4);

		// Prepare the points
		var points = [];

		for (var i = 0; i < 50; i++) {
			points[i] = new GPoint(i, 30 + 10 * p.noise(i * 0.1));
		}

		// Set the points, the title and the axis labels
		plot1.setPoints(points);
		plot1.setTitleText("Temperature");
		plot1.getYAxis().setAxisLabelText("T (Celsius)");
		plot1.getXAxis().setAxisLabelText("Time (minutes)");

		plot2.getRightAxis().setAxisLabelText("T (Kelvin)");

		// Make the right axis of the second plot visible
		plot2.getRightAxis().setDrawTickLabels(true);

		// Activate the panning (only for the first plot)
		plot1.activatePanning();
	};

	// Execute the sketch
	p.draw = function() {
		// Clean the canvas
		p.background(150);

		// Draw the plot
		plot1.beginDraw();
		plot1.drawBackground();
		plot1.drawBox();
		plot1.drawXAxis();
		plot1.drawYAxis();
		plot1.drawTitle();
		plot1.drawPoints();
		plot1.drawLines();
		plot1.endDraw();

		// Change the second plot vertical scale from Celsius to Kelvin
		plot2.setYLim(celsiusToKelvin(plot1.getYLim()));

		// Draw only the right axis
		plot2.beginDraw();
		plot2.drawRightAxis();
		plot2.endDraw();
	};

	//
	// Transforms from degree Celsius to degree Kelvin
	//
	function celsiusToKelvin(celsius) {
		var kelvin = [];

		for (var i = 0; i < celsius.length; i++) {
			kelvin[i] = 273.15 + celsius[i];
		}

		return kelvin;
	}

};


================================================
FILE: lib/grafica.js
================================================
/*
 * 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;
		this.fixedTicks = false;

		if (!this.log) {
			this.updateTicks();
			this.updatePlotTicks();
			this.updateTicksInside();
			this.updateTickLabels();
		}
	}
};

GAxis.prototype.setTicksSeparation = function(ticksSeparation) {
	this.ticksSeparation = ticksSeparation;
	this.fixedTicks = false;

	if (!this.log) {
		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.length; 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 = Number.MAX_VALUE;
		var nPoints = this.plotPoints.length;
		var nSizes = this.pointSizes.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 < Math.max(Math.pow(this.pointSizes[i % nSizes] / 2.0, 2), 25)) {
					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[0][1] = 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.noStroke();

		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.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.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 - this.labelSeparation[1];
		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 < nPoints) ? 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[index]));

		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;
	this.parentElt = this.parent._renderer.elt;

	// 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
	this.clickListener = this.clickEvent.bind(this);
	this.wheelListener = this.wheelEvent.bind(this);
	this.mouseDownListener = this.mouseDownEvent.bind(this);
	this.mouseMoveListener = this.mouseMoveEvent.bind(this);
	this.mouseUpListener = this.mouseUpEvent.bind(this);
	this.touchStartListener = this.touchStartEvent.bind(this);
	this.touchMoveListener = this.touchMoveEvent.bind(this);
	this.touchEndListener = this.touchEndEvent.bind(this);
	this.parentElt.addEventListener("click", this.clickListener, false);
	this.parentElt.addEventListener("wheel", this.wheelListener, false);
	this.parentElt.addEventListener("mousedown", this.mouseDownListener, false);
	this.parentElt.addEventListener("touchstart", this.touchStartListener, 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;
	this.fixedYLim = 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.fixedXLim = true;
	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(this.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.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.mainLayer.setFontName(fontName);
};

GPlot.prototype.setFontColor = function(fontColor) {
	this.mainLayer.setFontColor(fontColor);
};

GPlot.prototype.setFontSize = function(fontSize) {
	this.mainLayer.setFontSize(fontSize);
};

GPlot.prototype.setFontProperties = function(fontName, fontColor, fontSize) {
	this.mainLayer.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 < this.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.clickEvent = function(event) {
	var 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.wheelEvent = function(event) {
	var 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) {
				e.preventDefault();

				// 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) {
				e.preventDefault();

				// Save the axes limits
				if (this.resetIsActive) {
					this.saveResetLimits();
				}

				this.zoom(1 / this.zoomFactor, this.parent.mouseX, this.parent.mouseY);
			}
		}
	}
};

GPlot.prototype.mouseDownEvent = function(event) {
	var e = event || window.event;

	if (this.isOverBox()) {
		var addListeners = false;
		var button = this.getButton(e);
		var modifier = this.getModifier(e);

		if (this.panningIsActive && button === this.panningButton && modifier === this.panningKeyModifier) {
			addListeners = true;

			// Save the axes limits
			if (this.resetIsActive) {
				this.saveResetLimits();
			}

			// Calculate the panning reference point
			this.panningReferencePoint = this.getValueAt(this.parent.mouseX, this.parent.mouseY);
		}

		if (this.labelingIsActive && button === this.labelingButton && modifier === this.labelingKeyModifier) {
			addListeners = true;
			this.mousePos = [ this.parent.mouseX, this.parent.mouseY ];
		}

		if (addListeners) {
			// Add the mousemove and mouseup event listeners
			document.addEventListener('mousemove', this.mouseMoveListener, false);
			document.addEventListener('mouseup', this.mouseUpListener, false);
		}
	}
};

GPlot.prototype.mouseMoveEvent = function(event) {
	var e = event || window.event;
	var button = this.getButton(e);
	var modifier = this.getModifier(e);
	event.preventDefault();

	if (this.panningIsActive && button === this.panningButton && modifier === this.panningKeyModifier) {
		this.align(this.panningReferencePoint, this.parent.mouseX, this.parent.mouseY);
	}

	if (this.labelingIsActive && button === this.labelingButton && modifier === this.labelingKeyModifier) {
		this.mousePos = [ this.parent.mouseX, this.parent.mouseY ];
	}
};

GPlot.prototype.mouseUpEvent = function(event) {
	var e = event || window.event;
	var button = this.getButton(e);

	// Remove the mousemove and mouseup event listeners
	document.removeEventListener('mousemove', this.mouseMoveListener, false);
	document.removeEventListener('mouseup', this.mouseUpListener, false);

	if (this.panningIsActive && button === this.panningButton) {
		// Reset the panning variables
		this.panningReferencePoint = undefined;
	}

	if (this.labelingIsActive && button === this.labelingButton) {
		this.mousePos = undefined;
	}
};

GPlot.prototype.touchStartEvent = function(event) {
	var e = event || window.event;
	this.parent._ontouchstart(e);

	if (this.isOverBox()) {
		var addListeners = false;

		if (this.panningIsActive) {
			addListeners = true;
			this.panningReferencePoint = this.getValueAt(this.parent.mouseX, this.parent.mouseY);
		}

		if (this.labelingIsActive) {
			addListeners = true;
			this.mousePos = [ this.parent.mouseX, this.parent.mouseY ];
		}

		if(this.zoomingIsActive && typeof e.touches !== "undefined" && e.touches.length === 2){
			addListeners = true;
			var dx = e.touches[ 0 ].pageX - e.touches[ 1 ].pageX;
			var dy = e.touches[ 0 ].pageY - e.touches[ 1 ].pageY;
			this.zoomStartDistance = Math.sqrt( dx * dx + dy * dy );
			this.zoomStartPosition = [this.parent.mouseX, this.parent.mouseY];
		}

		if (addListeners) {
			// Add the touchmove, touchend and touchcancel event listeners
			document.addEventListener('touchmove', this.touchMoveListener, {passive: false});
			document.addEventListener('touchend', this.touchEndListener, false);
			document.addEventListener('touchcancel', this.touchEndListener, false);
		}
	}
};

GPlot.prototype.touchMoveEvent = function(event) {
	var e = event || window.event;
	e.preventDefault();

	if (this.panningIsActive) {
		this.align(this.panningReferencePoint, this.parent.mouseX, this.parent.mouseY);
	}

	if (this.labelingIsActive) {
		this.mousePos = [ this.parent.mouseX, this.parent.mouseY ];
	}

	if(this.zoomingIsActive && typeof e.touches !== "undefined" && e.touches.length === 2){
		var dx = e.touches[ 0 ].pageX - e.touches[ 1 ].pageX;
		var dy = e.touches[ 0 ].pageY - e.touches[ 1 ].pageY;
		var distance = Math.sqrt( dx * dx + dy * dy );
		this.zoom(distance/this.zoomStartDistance, this.zoomStartPosition[0], this.zoomStartPosition[1]);
		this.zoomStartDistance = distance;
	}
};

GPlot.prototype.touchEndEvent = function(event) {
	// Remove the touchmove, touchend and touch cancel event listeners
	document.removeEventListener('touchmove', this.touchMoveListener, false);
	document.removeEventListener('touchend', this.touchEndListener, false);
	document.removeEventListener('touchcancel', this.touchEndListener, false);

	if (this.panningIsActive) {
		this.panningReferencePoint = undefined;
	}

	if (this.labelingIsActive) {
		this.mousePos = undefined;
	}

	if(this.zoomingIsActive){
		this.zoomStartDistance = undefined;
		this.zoomStartPosition = undefined;		
	}
};

GPlot.prototype.preventDefaultEvent = function(event) {
	var e = event || window.event;

	if (this.isOverBox()) {
		e.preventDefault();
	}
};

GPlot.prototype.contextMenuEvent = function(event) {
	var e = event || window.event;

	if (this.isOverBox()) {
		e.preventDefault();
		this.clickEvent(e);
	}
};

GPlot.prototype.preventWheelDefault = function() {
	this.parentElt.addEventListener("wheel", this.preventDefaultEvent.bind(this), false);
};

GPlot.prototype.preventRightClickDefault = function() {
	this.parentElt.addEventListener("contextmenu", this.contextMenuEvent.bind(this), false);
};


================================================
FILE: package.json
================================================
{
  "name": "grafica",
  "repository": {
    "type": "git",
    "url": "https://github.com/jagracar/grafica.js.git"
  },
  "version": "0.3.1",
  "description": "A simple and configurable plotting library for p5.js",
  "keywords": [
    "math",
    "p5.js",
    "visualization",
    "data"
  ],
  "bugs": {
    "url": "https://github.com/jagracar/grafica.js/issues"
  },
  "author": "Javier Graciá Carpio",
  "devDependencies": {
    "grunt": "^0.4.5",
    "grunt-contrib-concat": "^0.5.1",
    "grunt-contrib-copy": "^0.8.0",
    "grunt-contrib-jshint": "^0.11.1",
    "grunt-contrib-uglify": "^0.9.1",
    "grunt-contrib-watch": "^0.6.1"
  },
  "license": "LGPL-3.0"
}


================================================
FILE: releases/grafica-0.1.0.js
================================================
/*
 * 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.
Download .txt
gitextract_vmqybpgv/

├── .gitignore
├── .project
├── Gruntfile.js
├── LICENSE
├── README.md
├── examples/
│   ├── data/
│   │   └── OktoberfestVSGermanElections.csv
│   ├── defaultPlot.js
│   ├── examples.html
│   ├── exponentialTrend.js
│   ├── movingPoints.js
│   ├── multiplePanels.js
│   ├── multiplePlots.js
│   ├── oktoberfest.js
│   └── twoVerticalAxes.js
├── lib/
│   └── grafica.js
├── package.json
├── releases/
│   ├── grafica-0.1.0.js
│   ├── grafica-0.2.0.js
│   ├── grafica-0.3.0.js
│   ├── grafica-0.3.1.js
│   └── grafica.js
└── src/
    ├── GAxis.js
    ├── GAxisLabel.js
    ├── GHistogram.js
    ├── GLayer.js
    ├── GPlot.js
    ├── GPoint.js
    └── GTitle.js
Download .txt
SYMBOL INDEX (52 symbols across 16 files)

FILE: examples/movingPoints.js
  function calculatePoint (line 116) | function calculatePoint(i, n, rad) {

FILE: examples/oktoberfest.js
  function getExactDate (line 73) | function getExactDate(year, month, day) {

FILE: examples/twoVerticalAxes.js
  function celsiusToKelvin (line 76) | function celsiusToKelvin(celsius) {

FILE: lib/grafica.js
  function GPoint (line 4) | function GPoint() {
  function GTitle (line 135) | function GTitle(parent, dim) {
  function GAxisLabel (line 236) | function GAxisLabel(parent, type, dim) {
  function GAxis (line 420) | function GAxis(parent, type, dim, lim, log) {
  function GHistogram (line 1314) | function GHistogram(parent, type, dim, plotPoints) {
  function GLayer (line 1697) | function GLayer(parent, id, dim, xLim, yLim, xLog, yLog) {
  function GPlot (line 3435) | function GPlot() {

FILE: releases/grafica-0.1.0.js
  function GPoint (line 4) | function GPoint() {
  function GTitle (line 135) | function GTitle(parent, dim) {
  function GAxisLabel (line 236) | function GAxisLabel(parent, type, dim) {
  function GAxis (line 420) | function GAxis(parent, type, dim, lim, log) {
  function GHistogram (line 1314) | function GHistogram(parent, type, dim, plotPoints) {
  function GLayer (line 1697) | function GLayer(parent, id, dim, xLim, yLim, xLog, yLog) {
  function GPlot (line 3432) | function GPlot() {

FILE: releases/grafica-0.2.0.js
  function GPoint (line 4) | function GPoint() {
  function GTitle (line 135) | function GTitle(parent, dim) {
  function GAxisLabel (line 236) | function GAxisLabel(parent, type, dim) {
  function GAxis (line 420) | function GAxis(parent, type, dim, lim, log) {
  function GHistogram (line 1314) | function GHistogram(parent, type, dim, plotPoints) {
  function GLayer (line 1697) | function GLayer(parent, id, dim, xLim, yLim, xLog, yLog) {
  function GPlot (line 3435) | function GPlot() {

FILE: releases/grafica-0.3.0.js
  function GPoint (line 4) | function GPoint() {
  function GTitle (line 135) | function GTitle(parent, dim) {
  function GAxisLabel (line 236) | function GAxisLabel(parent, type, dim) {
  function GAxis (line 420) | function GAxis(parent, type, dim, lim, log) {
  function GHistogram (line 1314) | function GHistogram(parent, type, dim, plotPoints) {
  function GLayer (line 1697) | function GLayer(parent, id, dim, xLim, yLim, xLog, yLog) {
  function GPlot (line 3435) | function GPlot() {

FILE: releases/grafica-0.3.1.js
  function GPoint (line 4) | function GPoint() {
  function GTitle (line 135) | function GTitle(parent, dim) {
  function GAxisLabel (line 236) | function GAxisLabel(parent, type, dim) {
  function GAxis (line 420) | function GAxis(parent, type, dim, lim, log) {
  function GHistogram (line 1314) | function GHistogram(parent, type, dim, plotPoints) {
  function GLayer (line 1697) | function GLayer(parent, id, dim, xLim, yLim, xLog, yLog) {
  function GPlot (line 3435) | function GPlot() {

FILE: releases/grafica.js
  function GPoint (line 4) | function GPoint() {
  function GTitle (line 135) | function GTitle(parent, dim) {
  function GAxisLabel (line 236) | function GAxisLabel(parent, type, dim) {
  function GAxis (line 420) | function GAxis(parent, type, dim, lim, log) {
  function GHistogram (line 1314) | function GHistogram(parent, type, dim, plotPoints) {
  function GLayer (line 1697) | function GLayer(parent, id, dim, xLim, yLim, xLog, yLog) {
  function GPlot (line 3435) | function GPlot() {

FILE: src/GAxis.js
  function GAxis (line 4) | function GAxis(parent, type, dim, lim, log) {

FILE: src/GAxisLabel.js
  function GAxisLabel (line 4) | function GAxisLabel(parent, type, dim) {

FILE: src/GHistogram.js
  function GHistogram (line 4) | function GHistogram(parent, type, dim, plotPoints) {

FILE: src/GLayer.js
  function GLayer (line 4) | function GLayer(parent, id, dim, xLim, yLim, xLog, yLog) {

FILE: src/GPlot.js
  function GPlot (line 5) | function GPlot() {

FILE: src/GPoint.js
  function GPoint (line 4) | function GPoint() {

FILE: src/GTitle.js
  function GTitle (line 4) | function GTitle(parent, dim) {
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,209K chars).
[
  {
    "path": ".gitignore",
    "chars": 13,
    "preview": "node_modules\n"
  },
  {
    "path": ".project",
    "chars": 203,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<projectDescription>\n\t<name>grafica.js</name>\n\t<comment></comment>\n\t<projects>\n\t<"
  },
  {
    "path": "Gruntfile.js",
    "chars": 1191,
    "preview": "module.exports = function(grunt) {\n\n\t// Project configuration.\n\tgrunt.initConfig({\n\t\tpkg : grunt.file.readJSON('package."
  },
  {
    "path": "LICENSE",
    "chars": 7652,
    "preview": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007"
  },
  {
    "path": "README.md",
    "chars": 1635,
    "preview": "# grafica.js\n\ngrafica.js is a simple and configurable plotting library for [p5.js](http://p5js.org). It's the javaScript"
  },
  {
    "path": "examples/data/OktoberfestVSGermanElections.csv",
    "chars": 1628,
    "preview": "year,month,day,oktoberfest,bundestagswahl\n2004,0,1,5,1\n2004,1,1,4,1\n2004,2,1,6,1\n2004,3,1,5,1\n2004,4,1,6,1\n2004,5,1,8,1\n"
  },
  {
    "path": "examples/defaultPlot.js",
    "chars": 729,
    "preview": "var defaultPlotSketch = function(p) {\n\t// Initial setup\n\tp.setup = function() {\n\t\t// Create the canvas\n\t\tvar canvas = p."
  },
  {
    "path": "examples/examples.html",
    "chars": 1784,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initi"
  },
  {
    "path": "examples/exponentialTrend.js",
    "chars": 1689,
    "preview": "var exponentialTrendSketch = function(p) {\n\t// Global variables\n\tvar plot, logScale;\n\n\t// Initial setup\n\tp.setup = funct"
  },
  {
    "path": "examples/movingPoints.js",
    "chars": 3050,
    "preview": "var movingPointsSketch = function(p) {\n\t// Global variables\n\tvar plot, i;\n\tvar step = 0;\n\tvar stepsPerCycle = 100;\n\tvar "
  },
  {
    "path": "examples/multiplePanels.js",
    "chars": 3218,
    "preview": "var multiplePanelsSketch = function(p) {\n\t// Initial setup\n\tp.setup = function() {\n\t\t// Create the canvas\n\t\tvar canvas ="
  },
  {
    "path": "examples/multiplePlots.js",
    "chars": 8426,
    "preview": "var multiplePlotsSketch = function(p) {\n\t// Global variables\n\tvar plot1, plot2, plot3, plot4, i, index;\n\tvar polygonPoin"
  },
  {
    "path": "examples/oktoberfest.js",
    "chars": 2866,
    "preview": "var oktoberfestSketch = function(p) {\n\t// Global variables\n\tvar table, plot;\n\tvar monthNames = [\"January\", \"February\", \""
  },
  {
    "path": "examples/twoVerticalAxes.js",
    "chars": 1952,
    "preview": "var twoVerticalAxesSketch = function(p) {\n\t// Global variables\n\tvar plot1, plot2;\n\n\t// Initial setup\n\tp.setup = function"
  },
  {
    "path": "lib/grafica.js",
    "chars": 153445,
    "preview": "/*\n * Point class. A GPoint is composed of two coordinates (x, y) and a text label\n */\nfunction GPoint() {\n\tvar x, y, la"
  },
  {
    "path": "package.json",
    "chars": 670,
    "preview": "{\n  \"name\": \"grafica\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/jagracar/grafica.js.git\"\n  },"
  },
  {
    "path": "releases/grafica-0.1.0.js",
    "chars": 149864,
    "preview": "/*\n * Point class. A GPoint is composed of two coordinates (x, y) and a text label\n */\nfunction GPoint() {\n\tvar x, y, la"
  },
  {
    "path": "releases/grafica-0.2.0.js",
    "chars": 152503,
    "preview": "/*\n * Point class. A GPoint is composed of two coordinates (x, y) and a text label\n */\nfunction GPoint() {\n\tvar x, y, la"
  },
  {
    "path": "releases/grafica-0.3.0.js",
    "chars": 152510,
    "preview": "/*\n * Point class. A GPoint is composed of two coordinates (x, y) and a text label\n */\nfunction GPoint() {\n\tvar x, y, la"
  },
  {
    "path": "releases/grafica-0.3.1.js",
    "chars": 153445,
    "preview": "/*\n * Point class. A GPoint is composed of two coordinates (x, y) and a text label\n */\nfunction GPoint() {\n\tvar x, y, la"
  },
  {
    "path": "releases/grafica.js",
    "chars": 153445,
    "preview": "/*\n * Point class. A GPoint is composed of two coordinates (x, y) and a text label\n */\nfunction GPoint() {\n\tvar x, y, la"
  },
  {
    "path": "src/GAxis.js",
    "chars": 23437,
    "preview": "/*\n * Axis class. \n */\nfunction GAxis(parent, type, dim, lim, log) {\n\t// The parent processing object\n\tthis.parent = par"
  },
  {
    "path": "src/GAxisLabel.js",
    "chars": 5219,
    "preview": "/*\n * Axis label class.\n */\nfunction GAxisLabel(parent, type, dim) {\n\t// The parent processing object\n\tthis.parent = par"
  },
  {
    "path": "src/GHistogram.js",
    "chars": 10122,
    "preview": "/*\n * Histogram class.\n */\nfunction GHistogram(parent, type, dim, plotPoints) {\n\t// The parent processing object\n\tthis.p"
  },
  {
    "path": "src/GLayer.js",
    "chars": 50149,
    "preview": "/*\n * Layer class. A GLayer usually contains an array of points and a histogram\n */\nfunction GLayer(parent, id, dim, xLi"
  },
  {
    "path": "src/GPlot.js",
    "chars": 58904,
    "preview": "/*\n * Plot class. It controls the rest of the graphical elements (layers, axes,\n * title, limits).\n */\nfunction GPlot() "
  },
  {
    "path": "src/GPoint.js",
    "chars": 3174,
    "preview": "/*\n * Point class. A GPoint is composed of two coordinates (x, y) and a text label\n */\nfunction GPoint() {\n\tvar x, y, la"
  },
  {
    "path": "src/GTitle.js",
    "chars": 2440,
    "preview": "/*\n * Title class.\n */\nfunction GTitle(parent, dim) {\n\t// The parent processing object\n\tthis.parent = parent;\n\n\t// Gener"
  }
]

About this extraction

This page contains the full source code of the jagracar/grafica.js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (1.1 MB), approximately 324.4k tokens, and a symbol index with 52 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!