From 601da0e693842212d8beed19a016e06d1bf4ba71 Mon Sep 17 00:00:00 2001 From: Frederic Massart Date: Mon, 4 Jul 2016 12:01:25 +0800 Subject: [PATCH] MDL-55108 core: Document the charting library Part of MDL-54987 epic. --- lib/amd/build/chart_builder.min.js | 2 +- lib/amd/build/chart_output.min.js | 2 +- lib/amd/build/chart_output_chartjs.min.js | 2 +- lib/amd/src/chart_axis.js | 170 ++++++++++++++++++- lib/amd/src/chart_bar.js | 7 + lib/amd/src/chart_base.js | 189 +++++++++++++++++++++- lib/amd/src/chart_builder.js | 21 ++- lib/amd/src/chart_line.js | 6 + lib/amd/src/chart_output.js | 8 +- lib/amd/src/chart_output_base.js | 22 +++ lib/amd/src/chart_output_chartjs.js | 82 ++++++++-- lib/amd/src/chart_pie.js | 14 ++ lib/amd/src/chart_series.js | 86 ++++++++++ lib/classes/chart_axis.php | 93 ++++++++++- lib/classes/chart_bar.php | 3 + lib/classes/chart_base.php | 112 ++++++++++++- lib/classes/chart_series.php | 52 ++++++ 17 files changed, 832 insertions(+), 39 deletions(-) diff --git a/lib/amd/build/chart_builder.min.js b/lib/amd/build/chart_builder.min.js index 1f58ab4f561..c746f317749 100644 --- a/lib/amd/build/chart_builder.min.js +++ b/lib/amd/build/chart_builder.min.js @@ -1 +1 @@ -define(["jquery"],function(a){return{make:function(b){var c=a.Deferred();return require(["core/chart_"+b.type],function(a){var d=a.prototype.create(a,b);c.resolve(d)}),c.promise()}}}); \ No newline at end of file +define(["jquery"],function(a){var b={make:function(b){var c=a.Deferred();return require(["core/chart_"+b.type],function(a){var d=a.prototype.create(a,b);c.resolve(d)}),c.promise()}};return b}); \ No newline at end of file diff --git a/lib/amd/build/chart_output.min.js b/lib/amd/build/chart_output.min.js index e72d12c9a42..65c0f11c853 100644 --- a/lib/amd/build/chart_output.min.js +++ b/lib/amd/build/chart_output.min.js @@ -1 +1 @@ -define(["core/chart_output_chartjs"],function(a){return a}); \ No newline at end of file +define(["core/chart_output_chartjs"],function(a){var b=a;return b}); \ No newline at end of file diff --git a/lib/amd/build/chart_output_chartjs.min.js b/lib/amd/build/chart_output_chartjs.min.js index ff3210f1d6f..0acc1c9b459 100644 --- a/lib/amd/build/chart_output_chartjs.min.js +++ b/lib/amd/build/chart_output_chartjs.min.js @@ -1 +1 @@ -define(["jquery","core/chartjs","core/chart_axis","core/chart_output_base"],function(a,b,c,d){function e(){d.prototype.constructor.apply(this,arguments),this._canvas=this._node,"CANVAS"!=this._canvas.prop("tagName")&&(this._canvas=a(""),this._node.append(this._canvas)),this._build()}return e.prototype=Object.create(d.prototype),e.prototype._config=null,e.prototype._chartjs=null,e.prototype._canvas=null,e.prototype.getDatasets=function(){var a=this._chart.getSeries().map(function(a){return{label:a.getLabel(),data:a.getValues(),type:a.getType(),fill:!1,borderColor:a.getColor(),backgroundColor:a.getColor()}});return a},e.prototype._build=function(){this._config=this._makeConfig(),this._chartjs=new b(this._canvas[0],this._config)},e.prototype._makeAxisConfig=function(a){var b={};return a.getPosition()!==c.prototype.POS_DEFAULT&&(b.position=a.getPosition()),null!==a.getLabel()&&(b.scaleLabel={display:!0,labelString:a.getLabel()}),null!==a.getStepSize()&&(b.ticks=b.ticks||{},b.ticks.stepSize=a.getStepSize()),null!==a.getMax()&&(b.ticks=b.ticks||{},b.ticks.max=a.getMax()),null!==a.getMin()&&(b.ticks=b.ticks||{},b.ticks.min=a.getMin()),b},e.prototype._makeConfig=function(){var a={type:this._chart.getType(),data:{labels:this._chart.getLabels(),datasets:this.getDatasets()},options:{title:{display:null!==this._chart.getTitle(),text:this._chart.getTitle()}}};return this._chart.getXAxes().forEach(function(b,c){a.options.scales=a.options.scales||{},a.options.scales.xAxes=a.options.scales.xAxes||[],a.options.scales.xAxes[c]=this._makeAxisConfig(b)}.bind(this)),this._chart.getYAxes().forEach(function(b,c){var d=b.getLabels();a.options.scales=a.options.scales||{},a.options.scales.yAxes=a.options.scales.yAxes||[],a.options.scales.yAxes[c]=this._makeAxisConfig(b),null!==d&&(a.options.scales.yAxes[c].ticks.callback=function(a){return d[parseInt(a,10)]||""})}.bind(this)),a},e.prototype.update=function(){a.extend(!0,this._config,this._makeConfig()),this._chartjs.update()},e}); \ No newline at end of file +define(["jquery","core/chartjs","core/chart_axis","core/chart_output_base"],function(a,b,c,d){function e(){d.prototype.constructor.apply(this,arguments),this._canvas=this._node,"CANVAS"!=this._canvas.prop("tagName")&&(this._canvas=a(""),this._node.append(this._canvas)),this._build()}return e.prototype=Object.create(d.prototype),e.prototype._config=null,e.prototype._chartjs=null,e.prototype._canvas=null,e.prototype._build=function(){this._config=this._makeConfig(),this._chartjs=new b(this._canvas[0],this._config)},e.prototype._makeAxisConfig=function(a){var b={};return a.getPosition()!==c.prototype.POS_DEFAULT&&(b.position=a.getPosition()),null!==a.getLabel()&&(b.scaleLabel={display:!0,labelString:a.getLabel()}),null!==a.getStepSize()&&(b.ticks=b.ticks||{},b.ticks.stepSize=a.getStepSize()),null!==a.getMax()&&(b.ticks=b.ticks||{},b.ticks.max=a.getMax()),null!==a.getMin()&&(b.ticks=b.ticks||{},b.ticks.min=a.getMin()),b},e.prototype._makeConfig=function(){var a={type:this._chart.getType(),data:{labels:this._chart.getLabels(),datasets:this._makeDatasetsConfig()},options:{title:{display:null!==this._chart.getTitle(),text:this._chart.getTitle()}}};return this._chart.getXAxes().forEach(function(b,c){a.options.scales=a.options.scales||{},a.options.scales.xAxes=a.options.scales.xAxes||[],a.options.scales.xAxes[c]=this._makeAxisConfig(b)}.bind(this)),this._chart.getYAxes().forEach(function(b,c){var d=b.getLabels();a.options.scales=a.options.scales||{},a.options.scales.yAxes=a.options.scales.yAxes||[],a.options.scales.yAxes[c]=this._makeAxisConfig(b),null!==d&&(a.options.scales.yAxes[c].ticks.callback=function(a){return d[parseInt(a,10)]||""})}.bind(this)),a},e.prototype._makeDatasetsConfig=function(){var a=this._chart.getSeries().map(function(a){return{label:a.getLabel(),data:a.getValues(),type:a.getType(),fill:!1,borderColor:a.getColor(),backgroundColor:a.getColor()}});return a},e.prototype.update=function(){a.extend(!0,this._config,this._makeConfig()),this._chartjs.update()},e}); \ No newline at end of file diff --git a/lib/amd/src/chart_axis.js b/lib/amd/src/chart_axis.js index 69db868c988..1ec5234a7eb 100644 --- a/lib/amd/src/chart_axis.js +++ b/lib/amd/src/chart_axis.js @@ -19,29 +19,102 @@ * @package core * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @module core/chart_axis */ define([], function() { /** - * Chart axis. + * Chart axis class. + * + * This is used to represent an axis, whether X or Y. + * + * @alias module:core/chart_axis + * @class */ function Axis() { // Please eslint no-empty-function. } + /** + * Default axis position. + * @const {Null} + */ Axis.prototype.POS_DEFAULT = null; + + /** + * Bottom axis position. + * @const {String} + */ Axis.prototype.POS_BOTTOM = 'bottom'; + + /** + * Left axis position. + * @const {String} + */ Axis.prototype.POS_LEFT = 'left'; + + /** + * Right axis position. + * @const {String} + */ Axis.prototype.POS_RIGHT = 'right'; + + /** + * Top axis position. + * @const {String} + */ Axis.prototype.POS_TOP = 'top'; + /** + * Label of the axis. + * @type {String} + * @protected + */ Axis.prototype._label = null; + + /** + * Labels of the ticks. + * @type {String[]} + * @protected + */ Axis.prototype._labels = null; + + /** + * Maximum value of the axis. + * @type {Number} + * @protected + */ Axis.prototype._max = null; + + /** + * Minimum value of the axis. + * @type {Number} + * @protected + */ Axis.prototype._min = null; + + /** + * Position of the axis. + * @type {String} + * @protected + */ Axis.prototype._position = null; + + /** + * Steps on the axis. + * @type {Number} + * @protected + */ Axis.prototype._stepSize = null; + /** + * Create a new instance of an axis from serialised data. + * + * @static + * @method create + * @param {Object} obj The data of the axis. + * @return {module:core/chart_axis} + */ Axis.prototype.create = function(obj) { var s = new Axis(); s.setPosition(obj.position); @@ -53,34 +126,90 @@ define([], function() { return s; }; + /** + * Get the label of the axis. + * + * @method getLabel + * @return {String} + */ Axis.prototype.getLabel = function() { return this._label; }; + /** + * Get the labels of the ticks of the axis. + * + * @method getLabels + * @return {String[]} + */ Axis.prototype.getLabels = function() { return this._labels; }; + /** + * Get the maximum value of the axis. + * + * @method getMax + * @return {Number} + */ Axis.prototype.getMax = function() { return this._max; }; + /** + * Get the minimum value of the axis. + * + * @method getMin + * @return {Number} + */ Axis.prototype.getMin = function() { return this._min; }; + /** + * Get the position of the axis. + * + * @method getPosition + * @return {String} + */ Axis.prototype.getPosition = function() { return this._position; }; + /** + * Get the step size of the axis. + * + * @method getStepSize + * @return {Number} + */ Axis.prototype.getStepSize = function() { return this._stepSize; }; + /** + * Set the label of the axis. + * + * @method setLabel + * @param {String} label The label. + */ Axis.prototype.setLabel = function(label) { this._label = label || null; }; + /** + * Set the labels of the values on the axis. + * + * This automatically sets the [_stepSize]{@link module:core/chart_axis#_stepSize}, + * [_min]{@link module:core/chart_axis#_min} and [_max]{@link module:core/chart_axis#_max} + * to define a scale from 0 to the number of labels when none of the previously + * mentioned values have been modified. + * + * You can use other values so long that your values in a series are mapped + * to the values represented by your _min, _max and _stepSize. + * + * @method setLabels + * @param {String[]} labels The labels. + */ Axis.prototype.setLabels = function(labels) { this._labels = labels || null; @@ -95,14 +224,45 @@ define([], function() { } }; + /** + * Set the maximum value on the axis. + * + * When this is not set (or set to null) it is left for the output + * library to best guess what should be used. + * + * @method setMax + * @param {Number} max The value. + */ Axis.prototype.setMax = function(max) { this._max = typeof max !== 'undefined' ? max : null; }; + /** + * Set the minimum value on the axis. + * + * When this is not set (or set to null) it is left for the output + * library to best guess what should be used. + * + * @method setMin + * @param {Number} min The value. + */ Axis.prototype.setMin = function(min) { this._min = typeof min !== 'undefined' ? min : null; }; + /** + * Set the position of the axis. + * + * This does not validate whether or not the constant used is valid + * as the axis itself is not aware whether it represents the X or Y axis. + * + * The output library has to have a fallback in case the values are incorrect. + * When this is not set to {@link module:core/chart_axis#POS_DEFAULT} it is up + * to the output library to choose what position fits best. + * + * @method setPosition + * @param {String} position The value. + */ Axis.prototype.setPosition = function(position) { if (position != this.POS_DEFAULT && position != this.POS_BOTTOM @@ -114,6 +274,14 @@ define([], function() { this._position = position; }; + /** + * Set the stepSize on the axis. + * + * This is used to determine where ticks are displayed on the axis between min and max. + * + * @method setStepSize + * @param {Number} stepSize The value. + */ Axis.prototype.setStepSize = function(stepSize) { if (typeof stepSize === 'undefined' || stepSize === null) { stepSize = null; diff --git a/lib/amd/src/chart_bar.js b/lib/amd/src/chart_bar.js index 4b6fdcc2ddb..d01faf47cf8 100644 --- a/lib/amd/src/chart_bar.js +++ b/lib/amd/src/chart_bar.js @@ -19,19 +19,26 @@ * @package core * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @module core/chart_bar */ define(['core/chart_base'], function(Base) { /** * Bar chart. + * + * @alias module:core/chart_bar + * @extends {module:core/chart_base} + * @class */ function Bar() { Base.prototype.constructor.apply(this, arguments); } Bar.prototype = Object.create(Base.prototype); + /** @override */ Bar.prototype.TYPE = 'bar'; + /** @override */ Bar.prototype._setDefaults = function() { Base.prototype._setDefaults.apply(this, arguments); var axis = this.getYAxis(0, true); diff --git a/lib/amd/src/chart_base.js b/lib/amd/src/chart_base.js index 1c9fc026d52..c99efcfb8cb 100644 --- a/lib/amd/src/chart_base.js +++ b/lib/amd/src/chart_base.js @@ -19,11 +19,19 @@ * @package core * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @module core/chart_base */ define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) { /** * Chart base. + * + * The constructor of a chart must never take any argument. + * + * {@link module:core/chart_base#_setDefault} to set the defaults on instantiation. + * + * @alias module:core/chart_base + * @class */ function Base() { this._series = []; @@ -33,25 +41,92 @@ define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) { this._setDefaults(); } + + /** + * The series constituting this chart. + * + * @protected + * @type {module:core/chart_series[]} + */ Base.prototype._series = null; + + /** + * The labels of the X axis when categorised. + * + * @protected + * @type {String[]} + */ Base.prototype._labels = null; + + /** + * The title of the chart. + * + * @protected + * @type {String} + */ Base.prototype._title = null; + + /** + * The X axes. + * + * @protected + * @type {module:core/chart_axis[]} + */ Base.prototype._xaxes = null; + + /** + * The Y axes. + * + * @protected + * @type {module:core/chart_axis[]} + */ Base.prototype._yaxes = null; + /** + * Colours to pick from when automatically assigning them. + * + * @const + * @type {String[]} + */ Base.prototype.COLORSET = ['red', 'green', 'blue', 'yellow', 'pink', 'orange']; + + /** + * The type of chart. + * + * @abstract + * @type {String} + * @const + */ Base.prototype.TYPE = null; - Base.prototype.addSeries = function(serie) { - this._validateSerie(serie); - this._series.push(serie); + /** + * Add a series to the chart. + * + * This will automatically assign a color to the series if it does not have one. + * + * @param {module:core/chart_series} series The series to add. + */ + Base.prototype.addSeries = function(series) { + this._validateSerie(series); + this._series.push(series); // Give a default color from the set. - if (serie.getColor() === null) { - serie.setColor(Base.prototype.COLORSET[this._series.length % Base.prototype.COLORSET.length]); + if (series.getColor() === null) { + series.setColor(Base.prototype.COLORSET[this._series.length % Base.prototype.COLORSET.length]); } }; + /** + * Create a new instance of a chart from serialised data. + * + * the serialised attributes they offer and support. + * + * @static + * @method create + * @param {module:core/chart_base} Klass The class oject representing the type of chart to instantiate. + * @param {Object} data The data of the chart. + * @return {module:core/chart_base} + */ Base.prototype.create = function(Klass, data) { // TODO Not convinced about the usage of Klass here but I can't figure out a way // to have a reference to the class in the sub classes, in PHP I'd do new self(). @@ -71,6 +146,15 @@ define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) { return Chart; }; + /** + * Get an axis. + * + * @private + * @param {String} xy Accepts the values 'x' or 'y'. + * @param {Number} [index=0] The index of the axis of its type. + * @param {Bool} [createIfNotExists=false] When true, create an instance if it does not exist. + * @return {module:core/chart_axis} + */ Base.prototype.__getAxis = function(xy, index, createIfNotExists) { var axes = xy === 'x' ? this._xaxes : this._yaxes, setAxis = (xy === 'x' ? this.setXAxis : this.setYAxis).bind(this), @@ -91,18 +175,39 @@ define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) { return axis; }; + /** + * Get the labels of the X axis. + * + * @return {String[]} + */ Base.prototype.getLabels = function() { return this._labels; }; + /** + * Get the series. + * + * @return {module:core/chart_series[]} + */ Base.prototype.getSeries = function() { return this._series; }; + /** + * Get the title of the chart. + * + * @return {String} + */ Base.prototype.getTitle = function() { return this._title; }; + /** + * Get the type of chart. + * + * @see module:core/chart_base#TYPE + * @return {String} + */ Base.prototype.getType = function() { if (!this.TYPE) { throw new Error('The TYPE property has not been set.'); @@ -110,26 +215,67 @@ define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) { return this.TYPE; }; + /** + * Get the X axes. + * + * @return {module:core/chart_axis[]} + */ Base.prototype.getXAxes = function() { return this._xaxes; }; + /** + * Get an X axis. + * + * @param {Number} [index=0] The index of the axis. + * @param {Bool} [createIfNotExists=false] Create the instance of it does not exist at index. + * @return {module:core/chart_axis} + */ Base.prototype.getXAxis = function(index, createIfNotExists) { return this.__getAxis('x', index, createIfNotExists); }; + /** + * Get the Y axes. + * + * @return {module:core/chart_axis[]} + */ Base.prototype.getYAxes = function() { return this._yaxes; }; + /** + * Get an Y axis. + * + * @param {Number} [index=0] The index of the axis. + * @param {Bool} [createIfNotExists=false] Create the instance of it does not exist at index. + * @return {module:core/chart_axis} + */ Base.prototype.getYAxis = function(index, createIfNotExists) { return this.__getAxis('y', index, createIfNotExists); }; + /** + * Set the defaults for this chart type. + * + * Child classes can extend this to set defaults values on instantiation. + * + * emphasize and self-document the defaults values set by the chart type. + * + * @protected + */ Base.prototype._setDefaults = function() { // For the children to extend. }; + /** + * Set the labels of the X axis. + * + * This requires for each series to contain strictly as many values as there + * are labels. + * + * @param {String[]} labels The labels. + */ Base.prototype.setLabels = function(labels) { if (labels.length && this._series.length && this._series[0].length != labels.length) { throw new Error('Series must match label values.'); @@ -137,25 +283,52 @@ define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) { this._labels = labels; }; + /** + * Set the title of the chart. + * + * @param {String} title The title. + */ Base.prototype.setTitle = function(title) { this._title = title; }; + /** + * Set an X axis. + * + * Note that this will override any predefined axis without warning. + * + * @param {module:core/chart_axis} axis The axis. + * @param {Number} [index=0] The index of the axis. + */ Base.prototype.setXAxis = function(axis, index) { index = typeof index === 'undefined' ? 0 : index; this._xaxes[index] = axis; }; + /** + * Set a Y axis. + * + * Note that this will override any predefined axis without warning. + * + * @param {module:core/chart_axis} axis The axis. + * @param {Number} [index=0] The index of the axis. + */ Base.prototype.setYAxis = function(axis, index) { index = typeof index === 'undefined' ? 0 : index; this._yaxes[index] = axis; }; - Base.prototype._validateSerie = function(serie) { - if (this._series.length && this._series[0].getCount() != serie.getCount()) { + /** + * Validate a series. + * + * @protected + * @param {module:core/chart_series} series The series to validate. + */ + Base.prototype._validateSerie = function(series) { + if (this._series.length && this._series[0].getCount() != series.getCount()) { throw new Error('Series do not have an equal number of values.'); - } else if (this._labels.length && this._labels.length != serie.getCount()) { + } else if (this._labels.length && this._labels.length != series.getCount()) { throw new Error('Series must match label values.'); } }; diff --git a/lib/amd/src/chart_builder.js b/lib/amd/src/chart_builder.js index 44553e25b39..5978c891c46 100644 --- a/lib/amd/src/chart_builder.js +++ b/lib/amd/src/chart_builder.js @@ -16,15 +16,28 @@ /** * Chart builder. * - * This takes data, most likely generated in PHP, and creates a chart instance. - * * @package core * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define(['jquery'], function($) { - return { + /** + * Chart builder. + * + * @exports core/chart_builder + */ + var module = { + + /** + * Make a chart instance. + * + * This takes data, most likely generated in PHP, and creates a chart instance from it + * deferring most of the logic to {@link module:core/chart_base.create}. + * + * @param {Object} data The data. + * @return {Promise} A promise resolved with the chart instance. + */ make: function(data) { var deferred = $.Deferred(); require(['core/chart_' + data.type], function(Klass) { @@ -35,4 +48,6 @@ define(['jquery'], function($) { } }; + return module; + }); diff --git a/lib/amd/src/chart_line.js b/lib/amd/src/chart_line.js index a4351c91614..ff30a86f8b8 100644 --- a/lib/amd/src/chart_line.js +++ b/lib/amd/src/chart_line.js @@ -19,17 +19,23 @@ * @package core * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @module core/chart_line */ define(['core/chart_base'], function(Base) { /** * Line chart. + * + * @alias module:core/chart_line + * @extends {module:core/chart_base} + * @class */ function Line() { Base.prototype.constructor.apply(this, arguments); } Line.prototype = Object.create(Base.prototype); + /** @override */ Line.prototype.TYPE = 'line'; return Line; diff --git a/lib/amd/src/chart_output.js b/lib/amd/src/chart_output.js index b7d947729f2..68488418bb3 100644 --- a/lib/amd/src/chart_output.js +++ b/lib/amd/src/chart_output.js @@ -24,6 +24,12 @@ */ define(['core/chart_output_chartjs'], function(Output) { - return Output; + /** + * @exports module:core/chart_output + * @extends {module:core/chart_output_chartjs} + */ + var defaultModule = Output; + + return defaultModule; }); diff --git a/lib/amd/src/chart_output_base.js b/lib/amd/src/chart_output_base.js index 91c6ae5c4cd..ac256e7bfa8 100644 --- a/lib/amd/src/chart_output_base.js +++ b/lib/amd/src/chart_output_base.js @@ -21,12 +21,24 @@ * @package core * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @module core/chart_output_base */ define(['jquery'], function($) { /** * Chart output base. * + * The constructor of an output class must instantly generate and display the + * chart. It is also the responsability of the output module to check that + * the node received is of the appropriate type, if not a new node can be + * added within. + * + * The output module has total control over the content of the node and can + * clear it or output anything to it at will. A node should not be shared by + * two simultaneous output modules. + * + * @class + * @alias module:core/chart_output_base * @param {Node} node The node to output with/in. * @param {Chart} chart A chart object. */ @@ -35,6 +47,16 @@ define(['jquery'], function($) { this._chart = chart; } + /** + * Update method. + * + * This is the public method through which an output instance in informed + * that the chart instance has been updated and they need to update the + * chart rendering. + * + * @abstract + * @return {Void} + */ Base.prototype.update = function() { throw new Error('Not supported.'); }; diff --git a/lib/amd/src/chart_output_chartjs.js b/lib/amd/src/chart_output_chartjs.js index 54daedddfa7..775589a5150 100644 --- a/lib/amd/src/chart_output_chartjs.js +++ b/lib/amd/src/chart_output_chartjs.js @@ -19,6 +19,7 @@ * @package core * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @module core/chart_output_chartjs */ define([ 'jquery', @@ -29,6 +30,10 @@ define([ /** * Chart output for Chart.js. + * + * @class + * @alias module:core/chart_output_chartjs + * @extends {module:core/chart_output_base} */ function Output() { Base.prototype.constructor.apply(this, arguments); @@ -42,32 +47,49 @@ define([ this._build(); } - Output.prototype = Object.create(Base.prototype); + /** + * Reference to the chart config object. + * + * @type {Object} + * @protected + */ Output.prototype._config = null; + + /** + * Reference to the instance of chart.js. + * + * @type {Object} + * @protected + */ Output.prototype._chartjs = null; + + /** + * Reference to the canvas node. + * + * @type {Jquery} + * @protected + */ Output.prototype._canvas = null; - Output.prototype.getDatasets = function() { - var sets = this._chart.getSeries().map(function(series) { - return { - label: series.getLabel(), - data: series.getValues(), - type: series.getType(), - fill: false, - borderColor: series.getColor(), - backgroundColor: series.getColor() - }; - }); - return sets; - }; - + /** + * Builds the config and the chart. + * + * @protected + */ Output.prototype._build = function() { this._config = this._makeConfig(); this._chartjs = new Chartjs(this._canvas[0], this._config); }; + /** + * Make the axis config. + * + * @protected + * @param {module:core/chart_axis} axis The axis. + * @return {Object} The axis config. + */ Output.prototype._makeAxisConfig = function(axis) { var scaleData = {}; @@ -100,12 +122,19 @@ define([ return scaleData; }; + /** + * Make the config config. + * + * @protected + * @param {module:core/chart_axis} axis The axis. + * @return {Object} The axis config. + */ Output.prototype._makeConfig = function() { var config = { type: this._chart.getType(), data: { labels: this._chart.getLabels(), - datasets: this.getDatasets() + datasets: this._makeDatasetsConfig() }, options: { title: { @@ -138,6 +167,27 @@ define([ return config; }; + /** + * Get the datasets configurations. + * + * @protected + * @return {Object[]} + */ + Output.prototype._makeDatasetsConfig = function() { + var sets = this._chart.getSeries().map(function(series) { + return { + label: series.getLabel(), + data: series.getValues(), + type: series.getType(), + fill: false, + borderColor: series.getColor(), + backgroundColor: series.getColor() + }; + }); + return sets; + }; + + /** @override */ Output.prototype.update = function() { $.extend(true, this._config, this._makeConfig()); this._chartjs.update(); diff --git a/lib/amd/src/chart_pie.js b/lib/amd/src/chart_pie.js index 8325520eafb..57756df9da2 100644 --- a/lib/amd/src/chart_pie.js +++ b/lib/amd/src/chart_pie.js @@ -19,19 +19,33 @@ * @package core * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @module core/chart_pie */ define(['core/chart_base'], function(Base) { /** * Pie chart. + * + * @class + * @alias module:core/chart_pie + * @extends {module:core/chart_base} */ function Pie() { Base.prototype.constructor.apply(this, arguments); } Pie.prototype = Object.create(Base.prototype); + /** @override */ Pie.prototype.TYPE = 'pie'; + /** + * Validate a series. + * + * Overrides parent implementation to validate that there is only + * one series per chart instance. + * + * @override + */ Pie.prototype._validateSerie = function() { if (this._series.length >= 1) { throw new Error('Pie charts only support one serie.'); diff --git a/lib/amd/src/chart_series.js b/lib/amd/src/chart_series.js index 9df3176db97..73f827a335c 100644 --- a/lib/amd/src/chart_series.js +++ b/lib/amd/src/chart_series.js @@ -19,12 +19,15 @@ * @package core * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @module core/chart_series */ define([], function() { /** * Chart data series. * + * @class + * @alias module:core/chart_series * @param {String} label The series label. * @param {Number[]} values The values. */ @@ -43,14 +46,62 @@ define([], function() { this._values = values; } + /** + * The default type of series. + * + * @type {Null} + * @const + */ Series.prototype.TYPE_DEFAULT = null; + + /** + * Type of series 'line'. + * + * @type {String} + * @const + */ Series.prototype.TYPE_LINE = 'line'; + /** + * The color of the series. + * + * @type {String} + * @protected + */ Series.prototype._color = null; + + /** + * The label of the series. + * + * @type {String} + * @protected + */ Series.prototype._label = null; + + /** + * The type of the series. + * + * @type {String} + * @protected + */ Series.prototype._type = Series.prototype.TYPE_DEFAULT; + + /** + * The values in the series. + * + * @type {Number[]} + * @protected + */ Series.prototype._values = null; + /** + * Create a new instance of a series from serialised data. + * + * @static + * @method create + * @param {Object} obj The data of the series. + * @return {module:core/chart_series} + */ Series.prototype.create = function(obj) { var s = new Series(obj.label, obj.values); s.setColor(obj.color); @@ -58,30 +109,65 @@ define([], function() { return s; }; + /** + * Get the color. + * + * @return {String} + */ Series.prototype.getColor = function() { return this._color; }; + /** + * Get the number of values in the series. + * + * @return {Number} + */ Series.prototype.getCount = function() { return this._values.length; }; + /** + * Get the series label. + * + * @return {String} + */ Series.prototype.getLabel = function() { return this._label; }; + /** + * Get the series type. + * + * @return {String} + */ Series.prototype.getType = function() { return this._type; }; + /** + * Get the series values. + * + * @return {Number[]} + */ Series.prototype.getValues = function() { return this._values; }; + /** + * Set the series color. + * + * @param {String} color A CSS-compatible color. + */ Series.prototype.setColor = function(color) { this._color = color || null; }; + /** + * Set the type of the series. + * + * @param {String} type A type constant value. + */ Series.prototype.setType = function(type) { if (type != this.TYPE_DEFAULT && type != this.TYPE_LINE) { throw new Error('Invalid serie type.'); diff --git a/lib/classes/chart_axis.php b/lib/classes/chart_axis.php index 58ee66742cf..1703472bad6 100644 --- a/lib/classes/chart_axis.php +++ b/lib/classes/chart_axis.php @@ -38,46 +38,97 @@ use renderable; */ class chart_axis implements JsonSerializable { + /** Default axis position. */ const POS_DEFAULT = null; + /** Bottom axis position. */ const POS_BOTTOM = 'bottom'; + /** Left axis position. */ const POS_LEFT = 'left'; + /** Right axis position. */ const POS_RIGHT = 'right'; + /** Top axis position. */ const POS_TOP = 'top'; + /** @var string The axis label. */ protected $label = null; + /** @var string[] The axis labels, tick values. */ protected $labels = null; + /** @var float The maximum tick value. */ protected $max = null; + /** @var float The minimum tick value. */ protected $min = null; + /** @var string The axis position. */ protected $position = self::POS_DEFAULT; + /** @var float The stepsize between ticks. */ protected $stepsize = null; + /** + * Constructor. + * + * Must not take any argument. + */ public function __construct() { } + /** + * Get the label. + * + * @return string + */ public function get_label() { return $this->label; } + /** + * Get the labels. + * + * @return string[] + */ public function get_labels() { return $this->labels; } + /** + * Get the max value. + * + * @return float + */ public function get_max() { return $this->max; } + /** + * Get the min value. + * + * @return float + */ public function get_min() { return $this->min; } + /** + * Get the axis position. + * + * @return string + */ public function get_position() { return $this->position; } + /** + * Get the step size. + * + * @return float + */ public function get_stepsize() { return $this->stepsize; } + /** + * Serialize the object. + * + * @return array + */ public function jsonSerialize() { return [ 'label' => $this->label, @@ -89,28 +140,58 @@ class chart_axis implements JsonSerializable { ]; } + /** + * Set the label. + * + * @param string $label The label. + */ public function set_label($label) { - return $this->label = $label; + $this->label = $label; } + /** + * Set the labels. + * + * @param string[] $labels The labels. + */ public function set_labels($labels) { - return $this->labels = $labels; + $this->labels = $labels; } + /** + * Set the max value. + * + * @param float $max The max value. + */ public function set_max($max) { - return $this->max = $max; + $this->max = $max; } + /** + * Set the min value. + * + * @param float $min The min value. + */ public function set_min($min) { - return $this->min = $min; + $this->min = $min; } + /** + * Set the position. + * + * @param string $position Use constant self::POS_*. + */ public function set_position($position) { - return $this->position = $position; + $this->position = $position; } + /** + * Set the step size. + * + * @param float $stepsize The step size. + */ public function set_stepsize($stepsize) { - return $this->stepsize = $stepsize; + $this->stepsize = $stepsize; } } diff --git a/lib/classes/chart_bar.php b/lib/classes/chart_bar.php index 46f895ee824..127563a20ce 100644 --- a/lib/classes/chart_bar.php +++ b/lib/classes/chart_bar.php @@ -34,6 +34,9 @@ defined('MOODLE_INTERNAL') || die(); */ class chart_bar extends chart_base { + /** + * Set the defaults. + */ protected function set_defaults() { parent::set_defaults(); $yaxis = $this->get_yaxis(0, true); diff --git a/lib/classes/chart_base.php b/lib/classes/chart_base.php index ce0967fa1bd..acac8a41ec4 100644 --- a/lib/classes/chart_base.php +++ b/lib/classes/chart_base.php @@ -38,20 +38,43 @@ use renderable; */ class chart_base implements JsonSerializable, renderable { + /** @var chart_series[] The series constituting this chart. */ protected $series = []; + /** @var string[] The labels for the X axis when categorised. */ protected $labels = []; + /** @var string The title of the chart. */ protected $title = null; + /** @var chart_axis[] The X axes. */ protected $xaxes = []; + /** @var chart_axis[] The Y axes. */ protected $yaxes = []; + /** + * Constructor. + * + * Must not take any argument. + * + * Most of the time you do not want to extend this, rather extend the + * method {@link self::set_defaults} to set the defaults on instantiation. + */ public function __construct() { $this->set_defaults(); } + /** + * Add a series to the chart. + * + * @param chart_series $serie The serie. + */ public function add_series(chart_series $serie) { $this->series[] = $serie; } + /** + * Serialize the object. + * + * @return array + */ public function jsonSerialize() { return [ 'type' => $this->get_type(), @@ -65,6 +88,14 @@ class chart_base implements JsonSerializable, renderable { ]; } + /** + * Get an axis. + * + * @param string $type Accepts values 'x' or 'y'. + * @param int $index The index of this axis. + * @param bool $createifnotexists Whether to create the axis if not found. + * @return chart_axis + */ private function get_axis($type, $index, $createifnotexists) { $isx = $type === 'x'; if ($isx) { @@ -89,55 +120,134 @@ class chart_base implements JsonSerializable, renderable { return $axis; } + /** + * Get the labels of the X axis. + * + * @return string[] + */ public function get_labels() { return $this->labels; } + /** + * Get the series. + * + * @return chart_series[] + */ public function get_series() { return $this->series; } + /** + * Get the title. + * + * @return string + */ public function get_title() { return $this->title; } + /** + * Get the chart type. + * + * @return string + */ public function get_type() { $classname = get_class($this); return substr($classname, strpos($classname, '_') + 1); } + /** + * Get the X axes. + * + * @return chart_axis[] + */ public function get_xaxes() { return $this->xaxes; } + /** + * Get an X axis. + * + * @param int $index The index of the axis. + * @param bool $createifnotexists When true, create an instance of the axis if none exist at this index yet. + * @return chart_axis + */ public function get_xaxis($index = 0, $createifnotexists = false) { return $this->get_axis('x', $index, $createifnotexists); } + /** + * Get the Y axes. + * + * @return chart_axis[] + */ public function get_yaxes() { return $this->yaxes; } + /** + * Get a Y axis. + * + * @param int $index The index of the axis. + * @param bool $createifnotexists When true, create an instance of the axis if none exist at this index yet. + * @return chart_axis + */ public function get_yaxis($index = 0, $createifnotexists = false) { return $this->get_axis('y', $index, $createifnotexists); } + /** + * Set the defaults for this chart type. + * + * Child classes can extend this to set default values on instantiation. + * + * In general the constructor could be used, but this method is here to + * emphasize and self-document the default values set by the chart type. + * + * @return void + */ protected function set_defaults() { - // For the child classes to extend. } + /** + * Set the chart labels. + * + * @param string[] $labels The labels. + */ public function set_labels(array $labels) { $this->labels = $labels; } + /** + * Set the title. + * + * @param string $title The title. + */ public function set_title($title) { $this->title = $title; } + /** + * Set an X axis. + * + * Note that this will override any predefined axis without warning. + * + * @param chart_axis $axis The axis. + * @param int $index The index of the axis. + */ public function set_xaxis(chart_axis $axis, $index = 0) { return $this->xaxes[$index] = $axis; } + /** + * Set an Y axis. + * + * Note that this will override any predefined axis without warning. + * + * @param chart_axis $axis The axis. + * @param int $index The index of the axis. + */ public function set_yaxis(chart_axis $axis, $index = 0) { return $this->yaxes[$index] = $axis; } diff --git a/lib/classes/chart_series.php b/lib/classes/chart_series.php index 2c5a93fcc9e..4ac93ef8d38 100644 --- a/lib/classes/chart_series.php +++ b/lib/classes/chart_series.php @@ -37,39 +37,81 @@ use JsonSerializable; */ class chart_series implements JsonSerializable { + /** Default type for a series. */ const TYPE_DEFAULT = null; + /** Series of type line. */ const TYPE_LINE = 'line'; + /** @var string Color of the series. */ protected $color; + /** @var string Label for this series. */ protected $label; + /** @var string Type of the series. */ protected $type = self::TYPE_DEFAULT; + /** @var float[] Values of the series. */ protected $values = []; + /** + * Constructor. + * + * @param string $label The label of the series. + * @param float[] $values The values of this series. + */ public function __construct($label, $values) { $this->values = $values; $this->label = $label; } + /** + * Get the color. + * + * @return string + */ public function get_color() { return $this->color; } + /** + * Get the number of values in this series. + * + * @return int + */ public function get_count() { return count($this->values); } + /** + * Get the label of the series. + * + * @return string + */ public function get_label() { return $this->label; } + /** + * Get the type of series. + * + * @return string + */ public function get_type() { return $this->type; } + /** + * Get the values of the series. + * + * @return [type] + */ public function get_values() { return $this->values; } + /** + * Serialize the object. + * + * @return array + */ public function jsonSerialize() { $data = [ 'label' => $this->label, @@ -80,10 +122,20 @@ class chart_series implements JsonSerializable { return $data; } + /** + * Set the color of the series. + * + * @param string $color CSS compatible color. + */ public function set_color($color) { $this->color = $color; } + /** + * Set the type of the series. + * + * @param string $type Constant value from self::TYPE_*. + */ public function set_type($type) { if (!in_array($type, [self::TYPE_DEFAULT, self::TYPE_LINE])) { throw new coding_exception('Invalid serie type.');