MDL-62801 themes: Remove old mustache caches when new one generated
[moodle.git] / lib / amd / src / chart_base.js
blob3c718e15633d3ee192a703778f9f11d3e78248bf
1 // This file is part of Moodle - http://moodle.org/
2 //
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.
7 //
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/>.
16 /**
17  * Chart base.
18  *
19  * @package    core
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
23  */
24 define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) {
26     /**
27      * Chart base.
28      *
29      * The constructor of a chart must never take any argument.
30      *
31      * {@link module:core/chart_base#_setDefault} to set the defaults on instantiation.
32      *
33      * @alias module:core/chart_base
34      * @class
35      */
36     function Base() {
37         this._series = [];
38         this._labels = [];
39         this._xaxes = [];
40         this._yaxes = [];
42         this._setDefaults();
43     }
45     /**
46      * The series constituting this chart.
47      *
48      * @protected
49      * @type {module:core/chart_series[]}
50      */
51     Base.prototype._series = null;
53     /**
54      * The labels of the X axis when categorised.
55      *
56      * @protected
57      * @type {String[]}
58      */
59     Base.prototype._labels = null;
61     /**
62      * The title of the chart.
63      *
64      * @protected
65      * @type {String}
66      */
67     Base.prototype._title = null;
69     /**
70      * The X axes.
71      *
72      * @protected
73      * @type {module:core/chart_axis[]}
74      */
75     Base.prototype._xaxes = null;
77     /**
78      * The Y axes.
79      *
80      * @protected
81      * @type {module:core/chart_axis[]}
82      */
83     Base.prototype._yaxes = null;
85     /**
86      * Colours to pick from when automatically assigning them.
87      *
88      * @const
89      * @type {String[]}
90      */
91     Base.prototype.COLORSET = ['#f3c300', '#875692', '#f38400', '#a1caf1', '#be0032', '#c2b280', '#7f180d', '#008856',
92             '#e68fac', '#0067a5'];
94     /**
95      * Set of colours defined by setting $CFG->chart_colorset to be picked when automatically assigning them.
96      *
97      * @type {String[]}
98      * @protected
99      */
100     Base.prototype._configColorSet = null;
102     /**
103      * The type of chart.
104      *
105      * @abstract
106      * @type {String}
107      * @const
108      */
109     Base.prototype.TYPE = null;
111     /**
112      * Add a series to the chart.
113      *
114      * This will automatically assign a color to the series if it does not have one.
115      *
116      * @param {module:core/chart_series} series The series to add.
117      */
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]);
126         }
127     };
129     /**
130      * Create a new instance of a chart from serialised data.
131      *
132      * the serialised attributes they offer and support.
133      *
134      * @static
135      * @method create
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}
139      */
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));
149         });
150         data.axes.x.forEach(function(axisData, i) {
151             Chart.setXAxis(Axis.prototype.create(axisData), i);
152         });
153         data.axes.y.forEach(function(axisData, i) {
154             Chart.setYAxis(Axis.prototype.create(axisData), i);
155         });
156         return Chart;
157     };
159     /**
160      * Get an axis.
161      *
162      * @private
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}
167      */
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),
171             axis;
173         index = typeof index === 'undefined' ? 0 : index;
174         createIfNotExists = typeof createIfNotExists === 'undefined' ? false : createIfNotExists;
175         axis = axes[index];
177         if (typeof axis === 'undefined') {
178             if (!createIfNotExists) {
179                 throw new Error('Unknown axis.');
180             }
181             axis = new Axis();
182             setAxis(axis, index);
183         }
185         return axis;
186     };
188     /**
189      * Get colours defined by setting.
190      *
191      * @return {String[]}
192      */
193     Base.prototype.getConfigColorSet = function() {
194         return this._configColorSet;
195     };
197     /**
198      * Get the labels of the X axis.
199      *
200      * @return {String[]}
201      */
202     Base.prototype.getLabels = function() {
203         return this._labels;
204     };
206     /**
207      * Get the series.
208      *
209      * @return {module:core/chart_series[]}
210      */
211     Base.prototype.getSeries = function() {
212         return this._series;
213     };
215     /**
216      * Get the title of the chart.
217      *
218      * @return {String}
219      */
220     Base.prototype.getTitle = function() {
221         return this._title;
222     };
224     /**
225      * Get the type of chart.
226      *
227      * @see module:core/chart_base#TYPE
228      * @return {String}
229      */
230     Base.prototype.getType = function() {
231         if (!this.TYPE) {
232             throw new Error('The TYPE property has not been set.');
233         }
234         return this.TYPE;
235     };
237     /**
238      * Get the X axes.
239      *
240      * @return {module:core/chart_axis[]}
241      */
242     Base.prototype.getXAxes = function() {
243         return this._xaxes;
244     };
246     /**
247      * Get an X axis.
248      *
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}
252      */
253     Base.prototype.getXAxis = function(index, createIfNotExists) {
254         return this.__getAxis('x', index, createIfNotExists);
255     };
257     /**
258      * Get the Y axes.
259      *
260      * @return {module:core/chart_axis[]}
261      */
262     Base.prototype.getYAxes = function() {
263         return this._yaxes;
264     };
266     /**
267      * Get an Y axis.
268      *
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}
272      */
273     Base.prototype.getYAxis = function(index, createIfNotExists) {
274         return this.__getAxis('y', index, createIfNotExists);
275     };
277     /**
278      * Set colours defined by setting.
279      *
280      * @param {String[]} colorset An array of css colours.
281      * @protected
282      */
283     Base.prototype.setConfigColorSet = function(colorset) {
284         this._configColorSet = colorset;
285     };
287     /**
288      * Set the defaults for this chart type.
289      *
290      * Child classes can extend this to set defaults values on instantiation.
291      *
292      * emphasize and self-document the defaults values set by the chart type.
293      *
294      * @protected
295      */
296     Base.prototype._setDefaults = function() {
297         // For the children to extend.
298     };
300     /**
301      * Set the labels of the X axis.
302      *
303      * This requires for each series to contain strictly as many values as there
304      * are labels.
305      *
306      * @param {String[]} labels The labels.
307      */
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.');
311         }
312         this._labels = labels;
313     };
315     /**
316      * Set the title of the chart.
317      *
318      * @param {String} title The title.
319      */
320     Base.prototype.setTitle = function(title) {
321         this._title = title;
322     };
324     /**
325      * Set an X axis.
326      *
327      * Note that this will override any predefined axis without warning.
328      *
329      * @param {module:core/chart_axis} axis The axis.
330      * @param {Number} [index=0] The index of the axis.
331      */
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;
336     };
338     /**
339      * Set a Y axis.
340      *
341      * Note that this will override any predefined axis without warning.
342      *
343      * @param {module:core/chart_axis} axis The axis.
344      * @param {Number} [index=0] The index of the axis.
345      */
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;
350     };
352     /**
353      * Validate an axis.
354      *
355      * @protected
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.
359      */
360     Base.prototype._validateAxis = function(xy, axis, index) {
361         index = typeof index === 'undefined' ? 0 : index;
362         if (index > 0) {
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);
366             }
367         }
368     };
370     /**
371      * Validate a series.
372      *
373      * @protected
374      * @param {module:core/chart_series} series The series to validate.
375      */
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.');
382         }
383     };
385     return Base;