1 // This file is part of Moodle - http://moodle.org/
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 // GNU General Public License for more details.
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
20 * @copyright 2016 Frédéric Massart - FMCorz.net
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22 * @module core/chart_base
24 define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) {
29 * The constructor of a chart must never take any argument.
31 * {@link module:core/chart_base#_setDefault} to set the defaults on instantiation.
33 * @alias module:core/chart_base
46 * The series constituting this chart.
49 * @type {module:core/chart_series[]}
51 Base.prototype._series = null;
54 * The labels of the X axis when categorised.
59 Base.prototype._labels = null;
62 * The title of the chart.
67 Base.prototype._title = null;
73 * @type {module:core/chart_axis[]}
75 Base.prototype._xaxes = null;
81 * @type {module:core/chart_axis[]}
83 Base.prototype._yaxes = null;
86 * Colours to pick from when automatically assigning them.
91 Base.prototype.COLORSET = ['#f3c300', '#875692', '#f38400', '#a1caf1', '#be0032', '#c2b280', '#7f180d', '#008856',
92 '#e68fac', '#0067a5'];
95 * Set of colours defined by setting $CFG->chart_colorset to be picked when automatically assigning them.
100 Base.prototype._configColorSet = null;
109 Base.prototype.TYPE = null;
112 * Add a series to the chart.
114 * This will automatically assign a color to the series if it does not have one.
116 * @param {module:core/chart_series} series The series to add.
118 Base.prototype.addSeries = function(series) {
119 this._validateSeries(series);
120 this._series.push(series);
122 // Give a default color from the set.
123 if (series.getColor() === null) {
124 var configColorSet = this.getConfigColorSet() || Base.prototype.COLORSET;
125 series.setColor(configColorSet[this._series.length % configColorSet.length]);
130 * Create a new instance of a chart from serialised data.
132 * the serialised attributes they offer and support.
136 * @param {module:core/chart_base} Klass The class oject representing the type of chart to instantiate.
137 * @param {Object} data The data of the chart.
138 * @return {module:core/chart_base}
140 Base.prototype.create = function(Klass, data) {
141 // TODO Not convinced about the usage of Klass here but I can't figure out a way
142 // to have a reference to the class in the sub classes, in PHP I'd do new self().
143 var Chart = new Klass();
144 Chart.setConfigColorSet(data.config_colorset);
145 Chart.setLabels(data.labels);
146 Chart.setTitle(data.title);
147 data.series.forEach(function(seriesData) {
148 Chart.addSeries(Series.prototype.create(seriesData));
150 data.axes.x.forEach(function(axisData, i) {
151 Chart.setXAxis(Axis.prototype.create(axisData), i);
153 data.axes.y.forEach(function(axisData, i) {
154 Chart.setYAxis(Axis.prototype.create(axisData), i);
163 * @param {String} xy Accepts the values 'x' or 'y'.
164 * @param {Number} [index=0] The index of the axis of its type.
165 * @param {Bool} [createIfNotExists=false] When true, create an instance if it does not exist.
166 * @return {module:core/chart_axis}
168 Base.prototype.__getAxis = function(xy, index, createIfNotExists) {
169 var axes = xy === 'x' ? this._xaxes : this._yaxes,
170 setAxis = (xy === 'x' ? this.setXAxis : this.setYAxis).bind(this),
173 index = typeof index === 'undefined' ? 0 : index;
174 createIfNotExists = typeof createIfNotExists === 'undefined' ? false : createIfNotExists;
177 if (typeof axis === 'undefined') {
178 if (!createIfNotExists) {
179 throw new Error('Unknown axis.');
182 setAxis(axis, index);
189 * Get colours defined by setting.
193 Base.prototype.getConfigColorSet = function() {
194 return this._configColorSet;
198 * Get the labels of the X axis.
202 Base.prototype.getLabels = function() {
209 * @return {module:core/chart_series[]}
211 Base.prototype.getSeries = function() {
216 * Get the title of the chart.
220 Base.prototype.getTitle = function() {
225 * Get the type of chart.
227 * @see module:core/chart_base#TYPE
230 Base.prototype.getType = function() {
232 throw new Error('The TYPE property has not been set.');
240 * @return {module:core/chart_axis[]}
242 Base.prototype.getXAxes = function() {
249 * @param {Number} [index=0] The index of the axis.
250 * @param {Bool} [createIfNotExists=false] Create the instance of it does not exist at index.
251 * @return {module:core/chart_axis}
253 Base.prototype.getXAxis = function(index, createIfNotExists) {
254 return this.__getAxis('x', index, createIfNotExists);
260 * @return {module:core/chart_axis[]}
262 Base.prototype.getYAxes = function() {
269 * @param {Number} [index=0] The index of the axis.
270 * @param {Bool} [createIfNotExists=false] Create the instance of it does not exist at index.
271 * @return {module:core/chart_axis}
273 Base.prototype.getYAxis = function(index, createIfNotExists) {
274 return this.__getAxis('y', index, createIfNotExists);
278 * Set colours defined by setting.
280 * @param {String[]} colorset An array of css colours.
283 Base.prototype.setConfigColorSet = function(colorset) {
284 this._configColorSet = colorset;
288 * Set the defaults for this chart type.
290 * Child classes can extend this to set defaults values on instantiation.
292 * emphasize and self-document the defaults values set by the chart type.
296 Base.prototype._setDefaults = function() {
297 // For the children to extend.
301 * Set the labels of the X axis.
303 * This requires for each series to contain strictly as many values as there
306 * @param {String[]} labels The labels.
308 Base.prototype.setLabels = function(labels) {
309 if (labels.length && this._series.length && this._series[0].length != labels.length) {
310 throw new Error('Series must match label values.');
312 this._labels = labels;
316 * Set the title of the chart.
318 * @param {String} title The title.
320 Base.prototype.setTitle = function(title) {
327 * Note that this will override any predefined axis without warning.
329 * @param {module:core/chart_axis} axis The axis.
330 * @param {Number} [index=0] The index of the axis.
332 Base.prototype.setXAxis = function(axis, index) {
333 index = typeof index === 'undefined' ? 0 : index;
334 this._validateAxis('x', axis, index);
335 this._xaxes[index] = axis;
341 * Note that this will override any predefined axis without warning.
343 * @param {module:core/chart_axis} axis The axis.
344 * @param {Number} [index=0] The index of the axis.
346 Base.prototype.setYAxis = function(axis, index) {
347 index = typeof index === 'undefined' ? 0 : index;
348 this._validateAxis('y', axis, index);
349 this._yaxes[index] = axis;
356 * @param {String} xy X or Y axis.
357 * @param {module:core/chart_axis} axis The axis to validate.
358 * @param {Number} [index=0] The index of the axis.
360 Base.prototype._validateAxis = function(xy, axis, index) {
361 index = typeof index === 'undefined' ? 0 : index;
363 var axes = xy == 'x' ? this._xaxes : this._yaxes;
364 if (typeof axes[index - 1] === 'undefined') {
365 throw new Error('Missing ' + xy + ' axis at index lower than ' + index);
374 * @param {module:core/chart_series} series The series to validate.
376 Base.prototype._validateSeries = function(series) {
377 if (this._series.length && this._series[0].getCount() != series.getCount()) {
378 throw new Error('Series do not have an equal number of values.');
380 } else if (this._labels.length && this._labels.length != series.getCount()) {
381 throw new Error('Series must match label values.');