From 08501958d3a94bf33d435bb7c3559736b07f2188 Mon Sep 17 00:00:00 2001 From: Simey Lameze Date: Fri, 22 Jul 2016 14:45:39 +0800 Subject: [PATCH] MDL-55262 core: Add support for series values labels Part of MDL-54987 epic. --- lib/amd/build/chart_output_chartjs.min.js | 2 +- lib/amd/build/chart_output_htmltable.min.js | 2 +- lib/amd/build/chart_series.min.js | 2 +- lib/amd/src/chart_output_chartjs.js | 34 +++++++++++++++++ lib/amd/src/chart_output_htmltable.js | 5 +++ lib/amd/src/chart_series.js | 41 +++++++++++++++++++++ lib/classes/chart_series.php | 21 +++++++++++ 7 files changed, 104 insertions(+), 3 deletions(-) diff --git a/lib/amd/build/chart_output_chartjs.min.js b/lib/amd/build/chart_output_chartjs.min.js index 40abdb440d2..fdc8e84c423 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_bar","core/chart_output_base","core/chart_line","core/chart_pie","core/chart_series"],function(a,b,c,d,e,f,g,h){function i(){e.prototype.constructor.apply(this,arguments),this._canvas=this._node,"CANVAS"!=this._canvas.prop("tagName")&&(this._canvas=a(""),this._node.append(this._canvas)),this._build()}var j=function(a,b){return"axis-"+a+"-"+b};return i.prototype=Object.create(e.prototype),i.prototype._config=null,i.prototype._chartjs=null,i.prototype._canvas=null,i.prototype._build=function(){this._config=this._makeConfig(),this._chartjs=new b(this._canvas[0],this._config)},i.prototype._getChartType=function(){var a=this._chart.getType();return this._chart.getType()===d.prototype.TYPE&&this._chart.getHorizontal()===!0&&(a="horizontalBar"),a},i.prototype._makeAxisConfig=function(a,b,d){var e={id:j(b,d)};return a.getPosition()!==c.prototype.POS_DEFAULT&&(e.position=a.getPosition()),null!==a.getLabel()&&(e.scaleLabel={display:!0,labelString:a.getLabel()}),null!==a.getStepSize()&&(e.ticks=e.ticks||{},e.ticks.stepSize=a.getStepSize()),null!==a.getMax()&&(e.ticks=e.ticks||{},e.ticks.max=a.getMax()),null!==a.getMin()&&(e.ticks=e.ticks||{},e.ticks.min=a.getMin()),e},i.prototype._makeConfig=function(){var a={type:this._getChartType(),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){var d=b.getLabels();a.options.scales=a.options.scales||{},a.options.scales.xAxes=a.options.scales.xAxes||[],a.options.scales.xAxes[c]=this._makeAxisConfig(b,"x",c),null!==d&&(a.options.scales.xAxes[c].ticks.callback=function(a,b){return d[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,"y",c),null!==d&&(a.options.scales.yAxes[c].ticks.callback=function(a){return d[parseInt(a,10)]||""})}.bind(this)),a},i.prototype._makeDatasetsConfig=function(){var a=this._chart.getSeries().map(function(a){var b=a.hasColoredValues()?a.getColors():a.getColor(),c={label:a.getLabel(),data:a.getValues(),type:a.getType(),fill:!1,backgroundColor:b,borderColor:this._chart.getType()==g.prototype.TYPE?null:b,lineTension:this._isSmooth(a)?.3:0};return null!==a.getXAxis()&&(c.xAxisID=j("x",a.getXAxis())),null!==a.getYAxis()&&(c.yAxisID=j("y",a.getYAxis())),c}.bind(this));return a},i.prototype._isSmooth=function(a){var b=!1;return this._chart.getType()===f.prototype.TYPE?(b=a.getSmooth(),null===b&&(b=this._chart.getSmooth())):a.getType()===h.prototype.TYPE_LINE&&(b=a.getSmooth()),b},i.prototype.update=function(){a.extend(!0,this._config,this._makeConfig()),this._chartjs.update()},i}); \ No newline at end of file +define(["jquery","core/chartjs","core/chart_axis","core/chart_bar","core/chart_output_base","core/chart_line","core/chart_pie","core/chart_series"],function(a,b,c,d,e,f,g,h){function i(){e.prototype.constructor.apply(this,arguments),this._canvas=this._node,"CANVAS"!=this._canvas.prop("tagName")&&(this._canvas=a(""),this._node.append(this._canvas)),this._build()}var j=function(a,b){return"axis-"+a+"-"+b};return i.prototype=Object.create(e.prototype),i.prototype._config=null,i.prototype._chartjs=null,i.prototype._canvas=null,i.prototype._build=function(){this._config=this._makeConfig(),this._chartjs=new b(this._canvas[0],this._config)},i.prototype._getChartType=function(){var a=this._chart.getType();return this._chart.getType()===d.prototype.TYPE&&this._chart.getHorizontal()===!0&&(a="horizontalBar"),a},i.prototype._makeAxisConfig=function(a,b,d){var e={id:j(b,d)};return a.getPosition()!==c.prototype.POS_DEFAULT&&(e.position=a.getPosition()),null!==a.getLabel()&&(e.scaleLabel={display:!0,labelString:a.getLabel()}),null!==a.getStepSize()&&(e.ticks=e.ticks||{},e.ticks.stepSize=a.getStepSize()),null!==a.getMax()&&(e.ticks=e.ticks||{},e.ticks.max=a.getMax()),null!==a.getMin()&&(e.ticks=e.ticks||{},e.ticks.min=a.getMin()),e},i.prototype._makeConfig=function(){var a={type:this._getChartType(),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){var d=b.getLabels();a.options.scales=a.options.scales||{},a.options.scales.xAxes=a.options.scales.xAxes||[],a.options.scales.xAxes[c]=this._makeAxisConfig(b,"x",c),null!==d&&(a.options.scales.xAxes[c].ticks.callback=function(a,b){return d[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,"y",c),null!==d&&(a.options.scales.yAxes[c].ticks.callback=function(a){return d[parseInt(a,10)]||""})}.bind(this)),a.options.tooltips={callbacks:{label:this._makeTooltip.bind(this)}},a},i.prototype._makeDatasetsConfig=function(){var a=this._chart.getSeries().map(function(a){var b=a.hasColoredValues()?a.getColors():a.getColor(),c={label:a.getLabel(),data:a.getValues(),type:a.getType(),fill:!1,backgroundColor:b,borderColor:this._chart.getType()==g.prototype.TYPE?null:b,lineTension:this._isSmooth(a)?.3:0};return null!==a.getXAxis()&&(c.xAxisID=j("x",a.getXAxis())),null!==a.getYAxis()&&(c.yAxisID=j("y",a.getYAxis())),c}.bind(this));return a},i.prototype._makeTooltip=function(a,b){var c=this._chart.getSeries()[a.datasetIndex],d=c.getLabel(),e=c.getLabels(),f=b.datasets[a.datasetIndex].data,g=f[a.index],h=d+": "+g;return null!==e&&(h+=" "+e[a.index]),h},i.prototype._isSmooth=function(a){var b=!1;return this._chart.getType()===f.prototype.TYPE?(b=a.getSmooth(),null===b&&(b=this._chart.getSmooth())):a.getType()===h.prototype.TYPE_LINE&&(b=a.getSmooth()),b},i.prototype.update=function(){a.extend(!0,this._config,this._makeConfig()),this._chartjs.update()},i}); \ No newline at end of file diff --git a/lib/amd/build/chart_output_htmltable.min.js b/lib/amd/build/chart_output_htmltable.min.js index a65c20b7af4..4a83991bfbf 100644 --- a/lib/amd/build/chart_output_htmltable.min.js +++ b/lib/amd/build/chart_output_htmltable.min.js @@ -1 +1 @@ -define(["jquery","core/chart_output_base"],function(a,b){function c(){b.prototype.constructor.apply(this,arguments),this._build()}return c.prototype=Object.create(b.prototype),c.prototype._build=function(){this._node.empty(),this._node.append(this._makeTable())},c.prototype._makeTable=function(){var b,c,d=a(""),e=this._chart,f=e.getLabels(),g=f.length>0,h=e.getSeries(),i=h[0].getCount();d.addClass("chart-output-htmltable"),null!==e.getTitle()&&d.append(a(""),g&&b.append(a(""),f.length>0&&b.append(a("
").text(e.getTitle())),b=a("
")),h.forEach(function(c){b.append(a("").text(c.getLabel()).attr("scope","col"))}),d.append(b);for(var j=0;i>j;j++){b=a("
").text(f[j]).attr("scope","row"));for(var k=0;k").text(c));d.append(b)}return d},c.prototype.update=function(){this._build()},c}); \ No newline at end of file +define(["jquery","core/chart_output_base"],function(a,b){function c(){b.prototype.constructor.apply(this,arguments),this._build()}return c.prototype=Object.create(b.prototype),c.prototype._build=function(){this._node.empty(),this._node.append(this._makeTable())},c.prototype._makeTable=function(){var b,c,d,e=a(""),f=this._chart,g=f.getLabels(),h=g.length>0,i=f.getSeries(),j=i[0].getCount();e.addClass("chart-output-htmltable"),null!==f.getTitle()&&e.append(a(""),h&&b.append(a(""),g.length>0&&b.append(a("
").text(f.getTitle())),b=a("
")),i.forEach(function(c){b.append(a("").text(c.getLabel()).attr("scope","col"))}),e.append(b);for(var k=0;j>k;k++){b=a("
").text(g[k]).attr("scope","row"));for(var l=0;l").text(c));e.append(b)}return e},c.prototype.update=function(){this._build()},c}); \ No newline at end of file diff --git a/lib/amd/build/chart_series.min.js b/lib/amd/build/chart_series.min.js index 8cc752b5462..c913cdb76b6 100644 --- a/lib/amd/build/chart_series.min.js +++ b/lib/amd/build/chart_series.min.js @@ -1 +1 @@ -define([],function(){function a(a,b){if("string"!=typeof a)throw new Error("Invalid label for series.");if("object"!=typeof b)throw new Error("Values for a series must be an array.");if(b.length<1)throw new Error("Invalid values received for series.");this._colors=[],this._label=a,this._values=b}return a.prototype.TYPE_DEFAULT=null,a.prototype.TYPE_LINE="line",a.prototype._colors=null,a.prototype._label=null,a.prototype._smooth=!1,a.prototype._type=a.prototype.TYPE_DEFAULT,a.prototype._values=null,a.prototype._xaxis=null,a.prototype._yaxis=null,a.prototype.create=function(b){var c=new a(b.label,b.values);return c.setType(b.type),c.setXAxis(b.axes.x),c.setYAxis(b.axes.y),b.colors&&b.colors.length>1?c.setColors(b.colors):c.setColor(b.colors[0]),c.setSmooth(b.smooth),c},a.prototype.getColor=function(){return this._colors[0]||null},a.prototype.getColors=function(){return this._colors},a.prototype.getCount=function(){return this._values.length},a.prototype.getLabel=function(){return this._label},a.prototype.getSmooth=function(){return this._smooth},a.prototype.getType=function(){return this._type},a.prototype.getValues=function(){return this._values},a.prototype.getXAxis=function(){return this._xaxis},a.prototype.getYAxis=function(){return this._yaxis},a.prototype.hasColoredValues=function(){return this._colors.length==this.getCount()},a.prototype.setColor=function(a){this._colors=[a]},a.prototype.setColors=function(a){if(a&&a.length!=this.getCount())throw new Error("When setting multiple colors there must be one per value.");this._colors=a||[]},a.prototype.setSmooth=function(a){a="undefined"==typeof a?null:a,this._smooth=a},a.prototype.setType=function(a){if(a!=this.TYPE_DEFAULT&&a!=this.TYPE_LINE)throw new Error("Invalid serie type.");this._type=a||null},a.prototype.setXAxis=function(a){this._xaxis=a||null},a.prototype.setYAxis=function(a){this._yaxis=a||null},a}); \ No newline at end of file +define([],function(){function a(a,b){if("string"!=typeof a)throw new Error("Invalid label for series.");if("object"!=typeof b)throw new Error("Values for a series must be an array.");if(b.length<1)throw new Error("Invalid values received for series.");this._colors=[],this._label=a,this._values=b}return a.prototype.TYPE_DEFAULT=null,a.prototype.TYPE_LINE="line",a.prototype._colors=null,a.prototype._label=null,a.prototype._labels=null,a.prototype._smooth=!1,a.prototype._type=a.prototype.TYPE_DEFAULT,a.prototype._values=null,a.prototype._xaxis=null,a.prototype._yaxis=null,a.prototype.create=function(b){var c=new a(b.label,b.values);return c.setType(b.type),c.setXAxis(b.axes.x),c.setYAxis(b.axes.y),c.setLabels(b.labels),b.colors&&b.colors.length>1?c.setColors(b.colors):c.setColor(b.colors[0]),c.setSmooth(b.smooth),c},a.prototype.getColor=function(){return this._colors[0]||null},a.prototype.getColors=function(){return this._colors},a.prototype.getCount=function(){return this._values.length},a.prototype.getLabel=function(){return this._label},a.prototype.getLabels=function(){return this._labels},a.prototype.getSmooth=function(){return this._smooth},a.prototype.getType=function(){return this._type},a.prototype.getValues=function(){return this._values},a.prototype.getXAxis=function(){return this._xaxis},a.prototype.getYAxis=function(){return this._yaxis},a.prototype.hasColoredValues=function(){return this._colors.length==this.getCount()},a.prototype.setColor=function(a){this._colors=[a]},a.prototype.setColors=function(a){if(a&&a.length!=this.getCount())throw new Error("When setting multiple colors there must be one per value.");this._colors=a||[]},a.prototype.setLabels=function(a){this._validateLabels(a),a="undefined"==typeof a?null:a,this._labels=a},a.prototype.setSmooth=function(a){a="undefined"==typeof a?null:a,this._smooth=a},a.prototype.setType=function(a){if(a!=this.TYPE_DEFAULT&&a!=this.TYPE_LINE)throw new Error("Invalid serie type.");this._type=a||null},a.prototype.setXAxis=function(a){this._xaxis=a||null},a.prototype.setYAxis=function(a){this._yaxis=a||null},a.prototype._validateLabels=function(a){if(a&&a.length>0&&a.length!=this.getCount())throw new Error("Series labels must match series values.")},a}); \ No newline at end of file diff --git a/lib/amd/src/chart_output_chartjs.js b/lib/amd/src/chart_output_chartjs.js index 3ac1ff146a2..b0075f25969 100644 --- a/lib/amd/src/chart_output_chartjs.js +++ b/lib/amd/src/chart_output_chartjs.js @@ -211,6 +211,12 @@ define([ } }.bind(this)); + config.options.tooltips = { + callbacks: { + label: this._makeTooltip.bind(this) + } + }; + return config; }; @@ -246,6 +252,34 @@ define([ return sets; }; + /** + * Get the chart data, add labels and rebuild the tooltip. + * + * @param {Object[]} tooltipItem The tooltip item data. + * @param {Object[]} data The chart data. + * @returns {String} + * @protected + */ + Output.prototype._makeTooltip = function(tooltipItem, data) { + + // Get series and chart data to rebuild the tooltip and add labels. + var series = this._chart.getSeries()[tooltipItem.datasetIndex]; + var serieLabel = series.getLabel(); + var serieLabels = series.getLabels(); + var chartData = data.datasets[tooltipItem.datasetIndex].data; + var tooltipData = chartData[tooltipItem.index]; + + // Build default tooltip. + var tooltip = serieLabel + ': ' + tooltipData; + + // Add serie labels to the tooltip if any. + if (serieLabels !== null) { + tooltip += ' ' + serieLabels[tooltipItem.index]; + } + + return tooltip; + }; + /** * Verify if the chart line is smooth or not. * diff --git a/lib/amd/src/chart_output_htmltable.js b/lib/amd/src/chart_output_htmltable.js index 83030135f56..ab55bbd6468 100644 --- a/lib/amd/src/chart_output_htmltable.js +++ b/lib/amd/src/chart_output_htmltable.js @@ -63,6 +63,7 @@ define([ labels = c.getLabels(), hasLabel = labels.length > 0, series = c.getSeries(), + seriesLabels, rowCount = series[0].getCount(); // Identify the table. @@ -99,6 +100,10 @@ define([ } for (var serieId = 0; serieId < series.length; serieId++) { value = series[serieId].getValues()[rowId]; + seriesLabels = series[serieId].getLabels(); + if (seriesLabels !== null) { + value += ' ' + series[serieId].getLabels()[rowId]; + } node.append($('').text(value)); } tbl.append(node); diff --git a/lib/amd/src/chart_series.js b/lib/amd/src/chart_series.js index e5756c6397d..e8f48ba1cee 100644 --- a/lib/amd/src/chart_series.js +++ b/lib/amd/src/chart_series.js @@ -79,6 +79,14 @@ define([], function() { */ Series.prototype._label = null; + /** + * The labels for the values of the series. + * + * @type {String[]} + * @protected + */ + Series.prototype._labels = null; + /** * Whether the line of the serie should be smooth or not. * @@ -132,6 +140,7 @@ define([], function() { s.setType(obj.type); s.setXAxis(obj.axes.x); s.setYAxis(obj.axes.y); + s.setLabels(obj.labels); // Colors are exported as an array with 1, or n values. if (obj.colors && obj.colors.length > 1) { @@ -180,6 +189,15 @@ define([], function() { return this._label; }; + /** + * Get labels for the values of the series. + * + * @return {String[]} + */ + Series.prototype.getLabels = function() { + return this._labels; + }; + /** * Get whether the line of the serie should be smooth or not. * @@ -255,6 +273,17 @@ define([], function() { this._colors = colors || []; }; + /** + * Set the labels for the values of the series. + * + * @param labels + */ + Series.prototype.setLabels = function(labels) { + this._validateLabels(labels); + labels = typeof labels === 'undefined' ? null : labels; + this._labels = labels; + }; + /** * Set Whether the line of the serie should be smooth or not. * @@ -298,6 +327,18 @@ define([], function() { this._yaxis = index || null; }; + /** + * Validate series labels. + * + * @protected + * @param {String[]} labels The labels of the serie. + */ + Series.prototype._validateLabels = function(labels) { + if (labels && labels.length > 0 && labels.length != this.getCount()) { + throw new Error('Series labels must match series values.'); + } + }; + return Series; }); diff --git a/lib/classes/chart_series.php b/lib/classes/chart_series.php index 24e36238f8c..4a705fa8c5d 100644 --- a/lib/classes/chart_series.php +++ b/lib/classes/chart_series.php @@ -46,6 +46,8 @@ class chart_series implements JsonSerializable { protected $colors = []; /** @var string Label for this series. */ protected $label; + /** @var string[] Labels for the values of the series. */ + protected $labels = null; /** @var bool Whether the line of the serie should be smooth or not. */ protected $smooth = null; /** @var string Type of the series. */ @@ -104,6 +106,15 @@ class chart_series implements JsonSerializable { return $this->label; } + /** + * Set labels for the values of the series. + * + * @return array + */ + public function get_labels() { + return $this->labels; + } + /** * Get whether the line of the serie should be smooth or not. * @@ -166,6 +177,7 @@ class chart_series implements JsonSerializable { public function jsonSerialize() { $data = [ 'label' => $this->label, + 'labels' => $this->labels, 'type' => $this->type, 'values' => $this->values, 'colors' => $this->colors, @@ -196,6 +208,15 @@ class chart_series implements JsonSerializable { $this->colors = $colors; } + /** + * Set labels for the values of the series. + * + * @param array $labels The labels for the series values. + */ + public function set_labels($labels) { + $this->labels = $labels; + } + /** * Set whether the line of the serie should be smooth or not. *