MDL-55110 core: Add support for smooth lines
[moodle.git] / lib / amd / src / chart_output_chartjs.js
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.
12 //
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 output for chart.js.
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_output_chartjs
23  */
24 define([
25     'jquery',
26     'core/chartjs',
27     'core/chart_axis',
28     'core/chart_output_base',
29     'core/chart_line',
30     'core/chart_pie',
31     'core/chart_series'
32 ], function($, Chartjs, Axis, Base, Line, Pie, Series) {
34     /**
35      * Makes an axis ID.
36      *
37      * @param {String} xy Accepts 'x' and 'y'.
38      * @param {Number} index The axis index.
39      * @return {String}
40      */
41     var makeAxisId = function(xy, index) {
42         return 'axis-' + xy + '-' + index;
43     };
45     /**
46      * Chart output for Chart.js.
47      *
48      * @class
49      * @alias module:core/chart_output_chartjs
50      * @extends {module:core/chart_output_base}
51      */
52     function Output() {
53         Base.prototype.constructor.apply(this, arguments);
55         // Make sure that we've got a canvas tag.
56         this._canvas = this._node;
57         if (this._canvas.prop('tagName') != 'CANVAS') {
58             this._canvas = $('<canvas>');
59             this._node.append(this._canvas);
60         }
62         this._build();
63     }
64     Output.prototype = Object.create(Base.prototype);
66     /**
67      * Reference to the chart config object.
68      *
69      * @type {Object}
70      * @protected
71      */
72     Output.prototype._config = null;
74     /**
75      * Reference to the instance of chart.js.
76      *
77      * @type {Object}
78      * @protected
79      */
80     Output.prototype._chartjs = null;
82     /**
83      * Reference to the canvas node.
84      *
85      * @type {Jquery}
86      * @protected
87      */
88     Output.prototype._canvas = null;
90     /**
91      * Builds the config and the chart.
92      *
93      * @protected
94      */
95     Output.prototype._build = function() {
96         this._config = this._makeConfig();
97         this._chartjs = new Chartjs(this._canvas[0], this._config);
98     };
100     /**
101      * Make the axis config.
102      *
103      * @protected
104      * @param {module:core/chart_axis} axis The axis.
105      * @param {String} xy Accepts 'x' or 'y'.
106      * @param {Number} index The axis index.
107      * @return {Object} The axis config.
108      */
109     Output.prototype._makeAxisConfig = function(axis, xy, index) {
110         var scaleData = {
111             id: makeAxisId(xy, index)
112         };
114         if (axis.getPosition() !== Axis.prototype.POS_DEFAULT) {
115             scaleData.position = axis.getPosition();
116         }
118         if (axis.getLabel() !== null) {
119             scaleData.scaleLabel = {
120                 display: true,
121                 labelString: axis.getLabel()
122             };
123         }
125         if (axis.getStepSize() !== null) {
126             scaleData.ticks = scaleData.ticks || {};
127             scaleData.ticks.stepSize = axis.getStepSize();
128         }
130         if (axis.getMax() !== null) {
131             scaleData.ticks = scaleData.ticks || {};
132             scaleData.ticks.max = axis.getMax();
133         }
135         if (axis.getMin() !== null) {
136             scaleData.ticks = scaleData.ticks || {};
137             scaleData.ticks.min = axis.getMin();
138         }
140         return scaleData;
141     };
143     /**
144      * Make the config config.
145      *
146      * @protected
147      * @param {module:core/chart_axis} axis The axis.
148      * @return {Object} The axis config.
149      */
150     Output.prototype._makeConfig = function() {
151         var config = {
152             type: this._chart.getType(),
153             data: {
154                 labels: this._chart.getLabels(),
155                 datasets: this._makeDatasetsConfig()
156             },
157             options: {
158                 title: {
159                     display: this._chart.getTitle() !== null,
160                     text: this._chart.getTitle()
161                 }
162             }
163         };
165         this._chart.getXAxes().forEach(function(axis, i) {
166             var axisLabels = axis.getLabels();
168             config.options.scales = config.options.scales || {};
169             config.options.scales.xAxes = config.options.scales.xAxes || [];
170             config.options.scales.xAxes[i] = this._makeAxisConfig(axis, 'x', i);
172             if (axisLabels !== null) {
173                 config.options.scales.xAxes[i].ticks.callback = function(value, index) {
174                     return axisLabels[index] || '';
175                 };
176             }
177         }.bind(this));
179         this._chart.getYAxes().forEach(function(axis, i) {
180             var axisLabels = axis.getLabels();
182             config.options.scales = config.options.scales || {};
183             config.options.scales.yAxes = config.options.scales.yAxes || [];
184             config.options.scales.yAxes[i] = this._makeAxisConfig(axis, 'y', i);
186             if (axisLabels !== null) {
187                 config.options.scales.yAxes[i].ticks.callback = function(value) {
188                     return axisLabels[parseInt(value, 10)] || '';
189                 };
190             }
191         }.bind(this));
193         return config;
194     };
196     /**
197      * Get the datasets configurations.
198      *
199      * @protected
200      * @return {Object[]}
201      */
202     Output.prototype._makeDatasetsConfig = function() {
203         var sets = this._chart.getSeries().map(function(series) {
204             var colors = series.hasColoredValues() ? series.getColors() : series.getColor();
205             var dataset = {
206                 label: series.getLabel(),
207                 data: series.getValues(),
208                 type: series.getType(),
209                 fill: false,
210                 backgroundColor: colors,
211                 // Pie charts look better without borders.
212                 borderColor: this._chart.getType() == Pie.prototype.TYPE ? null : colors,
213                 lineTension: this._isSmooth(series) ? 0.3 : 0
214             };
216             if (series.getXAxis() !== null) {
217                 dataset.xAxisID = makeAxisId('x', series.getXAxis());
218             }
219             if (series.getYAxis() !== null) {
220                 dataset.yAxisID = makeAxisId('y', series.getYAxis());
221             }
223             return dataset;
224         }.bind(this));
225         return sets;
226     };
228     /**
229      * Verify if the chart line is smooth or not.
230      *
231      * @protected
232      * @param {module:core/chart_series} series The series.
233      * @returns {Bool}
234      */
235     Output.prototype._isSmooth = function(series) {
236         var smooth = false;
237         if (this._chart.getType() === Line.prototype.TYPE) {
238             smooth = series.getSmooth();
239             if (smooth === null) {
240                 smooth = this._chart.getSmooth();
241             }
242         } else if (series.getType() === Series.prototype.TYPE_LINE) {
243             smooth = series.getSmooth();
244         }
246         return smooth;
247     };
249     /** @override */
250     Output.prototype.update = function() {
251         $.extend(true, this._config, this._makeConfig());
252         this._chartjs.update();
253     };
255     return Output;
257 });