MDL-55108 core: Document the charting library

Part of MDL-54987 epic.
This commit is contained in:
Frederic Massart 2016-07-04 12:01:25 +08:00 committed by Dan Poltawski
parent 6ce35fe0e1
commit 601da0e693
17 changed files with 832 additions and 39 deletions

View file

@ -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()}}}); 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});

View file

@ -1 +1 @@
define(["core/chart_output_chartjs"],function(a){return a}); define(["core/chart_output_chartjs"],function(a){var b=a;return b});

View file

@ -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("<canvas>"),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}); 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("<canvas>"),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});

View file

@ -19,29 +19,102 @@
* @package core * @package core
* @copyright 2016 Frédéric Massart - FMCorz.net * @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @module core/chart_axis
*/ */
define([], function() { 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() { function Axis() {
// Please eslint no-empty-function. // Please eslint no-empty-function.
} }
/**
* Default axis position.
* @const {Null}
*/
Axis.prototype.POS_DEFAULT = null; Axis.prototype.POS_DEFAULT = null;
/**
* Bottom axis position.
* @const {String}
*/
Axis.prototype.POS_BOTTOM = 'bottom'; Axis.prototype.POS_BOTTOM = 'bottom';
/**
* Left axis position.
* @const {String}
*/
Axis.prototype.POS_LEFT = 'left'; Axis.prototype.POS_LEFT = 'left';
/**
* Right axis position.
* @const {String}
*/
Axis.prototype.POS_RIGHT = 'right'; Axis.prototype.POS_RIGHT = 'right';
/**
* Top axis position.
* @const {String}
*/
Axis.prototype.POS_TOP = 'top'; Axis.prototype.POS_TOP = 'top';
/**
* Label of the axis.
* @type {String}
* @protected
*/
Axis.prototype._label = null; Axis.prototype._label = null;
/**
* Labels of the ticks.
* @type {String[]}
* @protected
*/
Axis.prototype._labels = null; Axis.prototype._labels = null;
/**
* Maximum value of the axis.
* @type {Number}
* @protected
*/
Axis.prototype._max = null; Axis.prototype._max = null;
/**
* Minimum value of the axis.
* @type {Number}
* @protected
*/
Axis.prototype._min = null; Axis.prototype._min = null;
/**
* Position of the axis.
* @type {String}
* @protected
*/
Axis.prototype._position = null; Axis.prototype._position = null;
/**
* Steps on the axis.
* @type {Number}
* @protected
*/
Axis.prototype._stepSize = null; 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) { Axis.prototype.create = function(obj) {
var s = new Axis(); var s = new Axis();
s.setPosition(obj.position); s.setPosition(obj.position);
@ -53,34 +126,90 @@ define([], function() {
return s; return s;
}; };
/**
* Get the label of the axis.
*
* @method getLabel
* @return {String}
*/
Axis.prototype.getLabel = function() { Axis.prototype.getLabel = function() {
return this._label; return this._label;
}; };
/**
* Get the labels of the ticks of the axis.
*
* @method getLabels
* @return {String[]}
*/
Axis.prototype.getLabels = function() { Axis.prototype.getLabels = function() {
return this._labels; return this._labels;
}; };
/**
* Get the maximum value of the axis.
*
* @method getMax
* @return {Number}
*/
Axis.prototype.getMax = function() { Axis.prototype.getMax = function() {
return this._max; return this._max;
}; };
/**
* Get the minimum value of the axis.
*
* @method getMin
* @return {Number}
*/
Axis.prototype.getMin = function() { Axis.prototype.getMin = function() {
return this._min; return this._min;
}; };
/**
* Get the position of the axis.
*
* @method getPosition
* @return {String}
*/
Axis.prototype.getPosition = function() { Axis.prototype.getPosition = function() {
return this._position; return this._position;
}; };
/**
* Get the step size of the axis.
*
* @method getStepSize
* @return {Number}
*/
Axis.prototype.getStepSize = function() { Axis.prototype.getStepSize = function() {
return this._stepSize; return this._stepSize;
}; };
/**
* Set the label of the axis.
*
* @method setLabel
* @param {String} label The label.
*/
Axis.prototype.setLabel = function(label) { Axis.prototype.setLabel = function(label) {
this._label = label || null; 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) { Axis.prototype.setLabels = function(labels) {
this._labels = labels || null; 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) { Axis.prototype.setMax = function(max) {
this._max = typeof max !== 'undefined' ? max : null; 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) { Axis.prototype.setMin = function(min) {
this._min = typeof min !== 'undefined' ? min : null; 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) { Axis.prototype.setPosition = function(position) {
if (position != this.POS_DEFAULT if (position != this.POS_DEFAULT
&& position != this.POS_BOTTOM && position != this.POS_BOTTOM
@ -114,6 +274,14 @@ define([], function() {
this._position = position; 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) { Axis.prototype.setStepSize = function(stepSize) {
if (typeof stepSize === 'undefined' || stepSize === null) { if (typeof stepSize === 'undefined' || stepSize === null) {
stepSize = null; stepSize = null;

View file

@ -19,19 +19,26 @@
* @package core * @package core
* @copyright 2016 Frédéric Massart - FMCorz.net * @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @module core/chart_bar
*/ */
define(['core/chart_base'], function(Base) { define(['core/chart_base'], function(Base) {
/** /**
* Bar chart. * Bar chart.
*
* @alias module:core/chart_bar
* @extends {module:core/chart_base}
* @class
*/ */
function Bar() { function Bar() {
Base.prototype.constructor.apply(this, arguments); Base.prototype.constructor.apply(this, arguments);
} }
Bar.prototype = Object.create(Base.prototype); Bar.prototype = Object.create(Base.prototype);
/** @override */
Bar.prototype.TYPE = 'bar'; Bar.prototype.TYPE = 'bar';
/** @override */
Bar.prototype._setDefaults = function() { Bar.prototype._setDefaults = function() {
Base.prototype._setDefaults.apply(this, arguments); Base.prototype._setDefaults.apply(this, arguments);
var axis = this.getYAxis(0, true); var axis = this.getYAxis(0, true);

View file

@ -19,11 +19,19 @@
* @package core * @package core
* @copyright 2016 Frédéric Massart - FMCorz.net * @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @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) { define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) {
/** /**
* Chart base. * 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() { function Base() {
this._series = []; this._series = [];
@ -33,25 +41,92 @@ define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) {
this._setDefaults(); this._setDefaults();
} }
/**
* The series constituting this chart.
*
* @protected
* @type {module:core/chart_series[]}
*/
Base.prototype._series = null; Base.prototype._series = null;
/**
* The labels of the X axis when categorised.
*
* @protected
* @type {String[]}
*/
Base.prototype._labels = null; Base.prototype._labels = null;
/**
* The title of the chart.
*
* @protected
* @type {String}
*/
Base.prototype._title = null; Base.prototype._title = null;
/**
* The X axes.
*
* @protected
* @type {module:core/chart_axis[]}
*/
Base.prototype._xaxes = null; Base.prototype._xaxes = null;
/**
* The Y axes.
*
* @protected
* @type {module:core/chart_axis[]}
*/
Base.prototype._yaxes = null; Base.prototype._yaxes = null;
/**
* Colours to pick from when automatically assigning them.
*
* @const
* @type {String[]}
*/
Base.prototype.COLORSET = ['red', 'green', 'blue', 'yellow', 'pink', 'orange']; Base.prototype.COLORSET = ['red', 'green', 'blue', 'yellow', 'pink', 'orange'];
/**
* The type of chart.
*
* @abstract
* @type {String}
* @const
*/
Base.prototype.TYPE = null; Base.prototype.TYPE = null;
Base.prototype.addSeries = function(serie) { /**
this._validateSerie(serie); * Add a series to the chart.
this._series.push(serie); *
* 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. // Give a default color from the set.
if (serie.getColor() === null) { if (series.getColor() === null) {
serie.setColor(Base.prototype.COLORSET[this._series.length % Base.prototype.COLORSET.length]); 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) { Base.prototype.create = function(Klass, data) {
// TODO Not convinced about the usage of Klass here but I can't figure out a way // 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(). // 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; 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) { Base.prototype.__getAxis = function(xy, index, createIfNotExists) {
var axes = xy === 'x' ? this._xaxes : this._yaxes, var axes = xy === 'x' ? this._xaxes : this._yaxes,
setAxis = (xy === 'x' ? this.setXAxis : this.setYAxis).bind(this), 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; return axis;
}; };
/**
* Get the labels of the X axis.
*
* @return {String[]}
*/
Base.prototype.getLabels = function() { Base.prototype.getLabels = function() {
return this._labels; return this._labels;
}; };
/**
* Get the series.
*
* @return {module:core/chart_series[]}
*/
Base.prototype.getSeries = function() { Base.prototype.getSeries = function() {
return this._series; return this._series;
}; };
/**
* Get the title of the chart.
*
* @return {String}
*/
Base.prototype.getTitle = function() { Base.prototype.getTitle = function() {
return this._title; return this._title;
}; };
/**
* Get the type of chart.
*
* @see module:core/chart_base#TYPE
* @return {String}
*/
Base.prototype.getType = function() { Base.prototype.getType = function() {
if (!this.TYPE) { if (!this.TYPE) {
throw new Error('The TYPE property has not been set.'); 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; return this.TYPE;
}; };
/**
* Get the X axes.
*
* @return {module:core/chart_axis[]}
*/
Base.prototype.getXAxes = function() { Base.prototype.getXAxes = function() {
return this._xaxes; 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) { Base.prototype.getXAxis = function(index, createIfNotExists) {
return this.__getAxis('x', index, createIfNotExists); return this.__getAxis('x', index, createIfNotExists);
}; };
/**
* Get the Y axes.
*
* @return {module:core/chart_axis[]}
*/
Base.prototype.getYAxes = function() { Base.prototype.getYAxes = function() {
return this._yaxes; 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) { Base.prototype.getYAxis = function(index, createIfNotExists) {
return this.__getAxis('y', 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() { Base.prototype._setDefaults = function() {
// For the children to extend. // 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) { Base.prototype.setLabels = function(labels) {
if (labels.length && this._series.length && this._series[0].length != labels.length) { if (labels.length && this._series.length && this._series[0].length != labels.length) {
throw new Error('Series must match label values.'); 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; this._labels = labels;
}; };
/**
* Set the title of the chart.
*
* @param {String} title The title.
*/
Base.prototype.setTitle = function(title) { Base.prototype.setTitle = function(title) {
this._title = 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) { Base.prototype.setXAxis = function(axis, index) {
index = typeof index === 'undefined' ? 0 : index; index = typeof index === 'undefined' ? 0 : index;
this._xaxes[index] = axis; 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) { Base.prototype.setYAxis = function(axis, index) {
index = typeof index === 'undefined' ? 0 : index; index = typeof index === 'undefined' ? 0 : index;
this._yaxes[index] = axis; 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.'); 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.'); throw new Error('Series must match label values.');
} }
}; };

View file

@ -16,15 +16,28 @@
/** /**
* Chart builder. * Chart builder.
* *
* This takes data, most likely generated in PHP, and creates a chart instance.
*
* @package core * @package core
* @copyright 2016 Frédéric Massart - FMCorz.net * @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/ */
define(['jquery'], function($) { 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) { make: function(data) {
var deferred = $.Deferred(); var deferred = $.Deferred();
require(['core/chart_' + data.type], function(Klass) { require(['core/chart_' + data.type], function(Klass) {
@ -35,4 +48,6 @@ define(['jquery'], function($) {
} }
}; };
return module;
}); });

View file

@ -19,17 +19,23 @@
* @package core * @package core
* @copyright 2016 Frédéric Massart - FMCorz.net * @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @module core/chart_line
*/ */
define(['core/chart_base'], function(Base) { define(['core/chart_base'], function(Base) {
/** /**
* Line chart. * Line chart.
*
* @alias module:core/chart_line
* @extends {module:core/chart_base}
* @class
*/ */
function Line() { function Line() {
Base.prototype.constructor.apply(this, arguments); Base.prototype.constructor.apply(this, arguments);
} }
Line.prototype = Object.create(Base.prototype); Line.prototype = Object.create(Base.prototype);
/** @override */
Line.prototype.TYPE = 'line'; Line.prototype.TYPE = 'line';
return Line; return Line;

View file

@ -24,6 +24,12 @@
*/ */
define(['core/chart_output_chartjs'], function(Output) { 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;
}); });

View file

@ -21,12 +21,24 @@
* @package core * @package core
* @copyright 2016 Frédéric Massart - FMCorz.net * @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @module core/chart_output_base
*/ */
define(['jquery'], function($) { define(['jquery'], function($) {
/** /**
* Chart output base. * 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 {Node} node The node to output with/in.
* @param {Chart} chart A chart object. * @param {Chart} chart A chart object.
*/ */
@ -35,6 +47,16 @@ define(['jquery'], function($) {
this._chart = chart; 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() { Base.prototype.update = function() {
throw new Error('Not supported.'); throw new Error('Not supported.');
}; };

View file

@ -19,6 +19,7 @@
* @package core * @package core
* @copyright 2016 Frédéric Massart - FMCorz.net * @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @module core/chart_output_chartjs
*/ */
define([ define([
'jquery', 'jquery',
@ -29,6 +30,10 @@ define([
/** /**
* Chart output for Chart.js. * Chart output for Chart.js.
*
* @class
* @alias module:core/chart_output_chartjs
* @extends {module:core/chart_output_base}
*/ */
function Output() { function Output() {
Base.prototype.constructor.apply(this, arguments); Base.prototype.constructor.apply(this, arguments);
@ -42,32 +47,49 @@ define([
this._build(); this._build();
} }
Output.prototype = Object.create(Base.prototype); Output.prototype = Object.create(Base.prototype);
/**
* Reference to the chart config object.
*
* @type {Object}
* @protected
*/
Output.prototype._config = null; Output.prototype._config = null;
/**
* Reference to the instance of chart.js.
*
* @type {Object}
* @protected
*/
Output.prototype._chartjs = null; Output.prototype._chartjs = null;
/**
* Reference to the canvas node.
*
* @type {Jquery}
* @protected
*/
Output.prototype._canvas = null; Output.prototype._canvas = null;
Output.prototype.getDatasets = function() { /**
var sets = this._chart.getSeries().map(function(series) { * Builds the config and the chart.
return { *
label: series.getLabel(), * @protected
data: series.getValues(), */
type: series.getType(),
fill: false,
borderColor: series.getColor(),
backgroundColor: series.getColor()
};
});
return sets;
};
Output.prototype._build = function() { Output.prototype._build = function() {
this._config = this._makeConfig(); this._config = this._makeConfig();
this._chartjs = new Chartjs(this._canvas[0], this._config); 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) { Output.prototype._makeAxisConfig = function(axis) {
var scaleData = {}; var scaleData = {};
@ -100,12 +122,19 @@ define([
return scaleData; return scaleData;
}; };
/**
* Make the config config.
*
* @protected
* @param {module:core/chart_axis} axis The axis.
* @return {Object} The axis config.
*/
Output.prototype._makeConfig = function() { Output.prototype._makeConfig = function() {
var config = { var config = {
type: this._chart.getType(), type: this._chart.getType(),
data: { data: {
labels: this._chart.getLabels(), labels: this._chart.getLabels(),
datasets: this.getDatasets() datasets: this._makeDatasetsConfig()
}, },
options: { options: {
title: { title: {
@ -138,6 +167,27 @@ define([
return config; 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() { Output.prototype.update = function() {
$.extend(true, this._config, this._makeConfig()); $.extend(true, this._config, this._makeConfig());
this._chartjs.update(); this._chartjs.update();

View file

@ -19,19 +19,33 @@
* @package core * @package core
* @copyright 2016 Frédéric Massart - FMCorz.net * @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @module core/chart_pie
*/ */
define(['core/chart_base'], function(Base) { define(['core/chart_base'], function(Base) {
/** /**
* Pie chart. * Pie chart.
*
* @class
* @alias module:core/chart_pie
* @extends {module:core/chart_base}
*/ */
function Pie() { function Pie() {
Base.prototype.constructor.apply(this, arguments); Base.prototype.constructor.apply(this, arguments);
} }
Pie.prototype = Object.create(Base.prototype); Pie.prototype = Object.create(Base.prototype);
/** @override */
Pie.prototype.TYPE = 'pie'; 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() { Pie.prototype._validateSerie = function() {
if (this._series.length >= 1) { if (this._series.length >= 1) {
throw new Error('Pie charts only support one serie.'); throw new Error('Pie charts only support one serie.');

View file

@ -19,12 +19,15 @@
* @package core * @package core
* @copyright 2016 Frédéric Massart - FMCorz.net * @copyright 2016 Frédéric Massart - FMCorz.net
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @module core/chart_series
*/ */
define([], function() { define([], function() {
/** /**
* Chart data series. * Chart data series.
* *
* @class
* @alias module:core/chart_series
* @param {String} label The series label. * @param {String} label The series label.
* @param {Number[]} values The values. * @param {Number[]} values The values.
*/ */
@ -43,14 +46,62 @@ define([], function() {
this._values = values; this._values = values;
} }
/**
* The default type of series.
*
* @type {Null}
* @const
*/
Series.prototype.TYPE_DEFAULT = null; Series.prototype.TYPE_DEFAULT = null;
/**
* Type of series 'line'.
*
* @type {String}
* @const
*/
Series.prototype.TYPE_LINE = 'line'; Series.prototype.TYPE_LINE = 'line';
/**
* The color of the series.
*
* @type {String}
* @protected
*/
Series.prototype._color = null; Series.prototype._color = null;
/**
* The label of the series.
*
* @type {String}
* @protected
*/
Series.prototype._label = null; Series.prototype._label = null;
/**
* The type of the series.
*
* @type {String}
* @protected
*/
Series.prototype._type = Series.prototype.TYPE_DEFAULT; Series.prototype._type = Series.prototype.TYPE_DEFAULT;
/**
* The values in the series.
*
* @type {Number[]}
* @protected
*/
Series.prototype._values = null; 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) { Series.prototype.create = function(obj) {
var s = new Series(obj.label, obj.values); var s = new Series(obj.label, obj.values);
s.setColor(obj.color); s.setColor(obj.color);
@ -58,30 +109,65 @@ define([], function() {
return s; return s;
}; };
/**
* Get the color.
*
* @return {String}
*/
Series.prototype.getColor = function() { Series.prototype.getColor = function() {
return this._color; return this._color;
}; };
/**
* Get the number of values in the series.
*
* @return {Number}
*/
Series.prototype.getCount = function() { Series.prototype.getCount = function() {
return this._values.length; return this._values.length;
}; };
/**
* Get the series label.
*
* @return {String}
*/
Series.prototype.getLabel = function() { Series.prototype.getLabel = function() {
return this._label; return this._label;
}; };
/**
* Get the series type.
*
* @return {String}
*/
Series.prototype.getType = function() { Series.prototype.getType = function() {
return this._type; return this._type;
}; };
/**
* Get the series values.
*
* @return {Number[]}
*/
Series.prototype.getValues = function() { Series.prototype.getValues = function() {
return this._values; return this._values;
}; };
/**
* Set the series color.
*
* @param {String} color A CSS-compatible color.
*/
Series.prototype.setColor = function(color) { Series.prototype.setColor = function(color) {
this._color = color || null; this._color = color || null;
}; };
/**
* Set the type of the series.
*
* @param {String} type A type constant value.
*/
Series.prototype.setType = function(type) { Series.prototype.setType = function(type) {
if (type != this.TYPE_DEFAULT && type != this.TYPE_LINE) { if (type != this.TYPE_DEFAULT && type != this.TYPE_LINE) {
throw new Error('Invalid serie type.'); throw new Error('Invalid serie type.');

View file

@ -38,46 +38,97 @@ use renderable;
*/ */
class chart_axis implements JsonSerializable { class chart_axis implements JsonSerializable {
/** Default axis position. */
const POS_DEFAULT = null; const POS_DEFAULT = null;
/** Bottom axis position. */
const POS_BOTTOM = 'bottom'; const POS_BOTTOM = 'bottom';
/** Left axis position. */
const POS_LEFT = 'left'; const POS_LEFT = 'left';
/** Right axis position. */
const POS_RIGHT = 'right'; const POS_RIGHT = 'right';
/** Top axis position. */
const POS_TOP = 'top'; const POS_TOP = 'top';
/** @var string The axis label. */
protected $label = null; protected $label = null;
/** @var string[] The axis labels, tick values. */
protected $labels = null; protected $labels = null;
/** @var float The maximum tick value. */
protected $max = null; protected $max = null;
/** @var float The minimum tick value. */
protected $min = null; protected $min = null;
/** @var string The axis position. */
protected $position = self::POS_DEFAULT; protected $position = self::POS_DEFAULT;
/** @var float The stepsize between ticks. */
protected $stepsize = null; protected $stepsize = null;
/**
* Constructor.
*
* Must not take any argument.
*/
public function __construct() { public function __construct() {
} }
/**
* Get the label.
*
* @return string
*/
public function get_label() { public function get_label() {
return $this->label; return $this->label;
} }
/**
* Get the labels.
*
* @return string[]
*/
public function get_labels() { public function get_labels() {
return $this->labels; return $this->labels;
} }
/**
* Get the max value.
*
* @return float
*/
public function get_max() { public function get_max() {
return $this->max; return $this->max;
} }
/**
* Get the min value.
*
* @return float
*/
public function get_min() { public function get_min() {
return $this->min; return $this->min;
} }
/**
* Get the axis position.
*
* @return string
*/
public function get_position() { public function get_position() {
return $this->position; return $this->position;
} }
/**
* Get the step size.
*
* @return float
*/
public function get_stepsize() { public function get_stepsize() {
return $this->stepsize; return $this->stepsize;
} }
/**
* Serialize the object.
*
* @return array
*/
public function jsonSerialize() { public function jsonSerialize() {
return [ return [
'label' => $this->label, '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) { 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) { 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) { 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) { 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) { 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) { public function set_stepsize($stepsize) {
return $this->stepsize = $stepsize; $this->stepsize = $stepsize;
} }
} }

View file

@ -34,6 +34,9 @@ defined('MOODLE_INTERNAL') || die();
*/ */
class chart_bar extends chart_base { class chart_bar extends chart_base {
/**
* Set the defaults.
*/
protected function set_defaults() { protected function set_defaults() {
parent::set_defaults(); parent::set_defaults();
$yaxis = $this->get_yaxis(0, true); $yaxis = $this->get_yaxis(0, true);

View file

@ -38,20 +38,43 @@ use renderable;
*/ */
class chart_base implements JsonSerializable, renderable { class chart_base implements JsonSerializable, renderable {
/** @var chart_series[] The series constituting this chart. */
protected $series = []; protected $series = [];
/** @var string[] The labels for the X axis when categorised. */
protected $labels = []; protected $labels = [];
/** @var string The title of the chart. */
protected $title = null; protected $title = null;
/** @var chart_axis[] The X axes. */
protected $xaxes = []; protected $xaxes = [];
/** @var chart_axis[] The Y axes. */
protected $yaxes = []; 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() { public function __construct() {
$this->set_defaults(); $this->set_defaults();
} }
/**
* Add a series to the chart.
*
* @param chart_series $serie The serie.
*/
public function add_series(chart_series $serie) { public function add_series(chart_series $serie) {
$this->series[] = $serie; $this->series[] = $serie;
} }
/**
* Serialize the object.
*
* @return array
*/
public function jsonSerialize() { public function jsonSerialize() {
return [ return [
'type' => $this->get_type(), '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) { private function get_axis($type, $index, $createifnotexists) {
$isx = $type === 'x'; $isx = $type === 'x';
if ($isx) { if ($isx) {
@ -89,55 +120,134 @@ class chart_base implements JsonSerializable, renderable {
return $axis; return $axis;
} }
/**
* Get the labels of the X axis.
*
* @return string[]
*/
public function get_labels() { public function get_labels() {
return $this->labels; return $this->labels;
} }
/**
* Get the series.
*
* @return chart_series[]
*/
public function get_series() { public function get_series() {
return $this->series; return $this->series;
} }
/**
* Get the title.
*
* @return string
*/
public function get_title() { public function get_title() {
return $this->title; return $this->title;
} }
/**
* Get the chart type.
*
* @return string
*/
public function get_type() { public function get_type() {
$classname = get_class($this); $classname = get_class($this);
return substr($classname, strpos($classname, '_') + 1); return substr($classname, strpos($classname, '_') + 1);
} }
/**
* Get the X axes.
*
* @return chart_axis[]
*/
public function get_xaxes() { public function get_xaxes() {
return $this->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) { public function get_xaxis($index = 0, $createifnotexists = false) {
return $this->get_axis('x', $index, $createifnotexists); return $this->get_axis('x', $index, $createifnotexists);
} }
/**
* Get the Y axes.
*
* @return chart_axis[]
*/
public function get_yaxes() { public function get_yaxes() {
return $this->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) { public function get_yaxis($index = 0, $createifnotexists = false) {
return $this->get_axis('y', $index, $createifnotexists); 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() { 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) { public function set_labels(array $labels) {
$this->labels = $labels; $this->labels = $labels;
} }
/**
* Set the title.
*
* @param string $title The title.
*/
public function set_title($title) { public function set_title($title) {
$this->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) { public function set_xaxis(chart_axis $axis, $index = 0) {
return $this->xaxes[$index] = $axis; 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) { public function set_yaxis(chart_axis $axis, $index = 0) {
return $this->yaxes[$index] = $axis; return $this->yaxes[$index] = $axis;
} }

View file

@ -37,39 +37,81 @@ use JsonSerializable;
*/ */
class chart_series implements JsonSerializable { class chart_series implements JsonSerializable {
/** Default type for a series. */
const TYPE_DEFAULT = null; const TYPE_DEFAULT = null;
/** Series of type line. */
const TYPE_LINE = 'line'; const TYPE_LINE = 'line';
/** @var string Color of the series. */
protected $color; protected $color;
/** @var string Label for this series. */
protected $label; protected $label;
/** @var string Type of the series. */
protected $type = self::TYPE_DEFAULT; protected $type = self::TYPE_DEFAULT;
/** @var float[] Values of the series. */
protected $values = []; protected $values = [];
/**
* Constructor.
*
* @param string $label The label of the series.
* @param float[] $values The values of this series.
*/
public function __construct($label, $values) { public function __construct($label, $values) {
$this->values = $values; $this->values = $values;
$this->label = $label; $this->label = $label;
} }
/**
* Get the color.
*
* @return string
*/
public function get_color() { public function get_color() {
return $this->color; return $this->color;
} }
/**
* Get the number of values in this series.
*
* @return int
*/
public function get_count() { public function get_count() {
return count($this->values); return count($this->values);
} }
/**
* Get the label of the series.
*
* @return string
*/
public function get_label() { public function get_label() {
return $this->label; return $this->label;
} }
/**
* Get the type of series.
*
* @return string
*/
public function get_type() { public function get_type() {
return $this->type; return $this->type;
} }
/**
* Get the values of the series.
*
* @return [type]
*/
public function get_values() { public function get_values() {
return $this->values; return $this->values;
} }
/**
* Serialize the object.
*
* @return array
*/
public function jsonSerialize() { public function jsonSerialize() {
$data = [ $data = [
'label' => $this->label, 'label' => $this->label,
@ -80,10 +122,20 @@ class chart_series implements JsonSerializable {
return $data; return $data;
} }
/**
* Set the color of the series.
*
* @param string $color CSS compatible color.
*/
public function set_color($color) { public function set_color($color) {
$this->color = $color; $this->color = $color;
} }
/**
* Set the type of the series.
*
* @param string $type Constant value from self::TYPE_*.
*/
public function set_type($type) { public function set_type($type) {
if (!in_array($type, [self::TYPE_DEFAULT, self::TYPE_LINE])) { if (!in_array($type, [self::TYPE_DEFAULT, self::TYPE_LINE])) {
throw new coding_exception('Invalid serie type.'); throw new coding_exception('Invalid serie type.');