MDL-55146 core: Support multiple axes in charts
authorFrederic Massart <fred@moodle.com>
Thu, 7 Jul 2016 06:14:26 +0000 (14:14 +0800)
committerDan Poltawski <dan@moodle.com>
Mon, 25 Jul 2016 09:43:12 +0000 (10:43 +0100)
Part of MDL-54987 epic.

lib/amd/build/chart_base.min.js
lib/amd/build/chart_output_chartjs.min.js
lib/amd/build/chart_pie.min.js
lib/amd/build/chart_series.min.js
lib/amd/src/chart_base.js
lib/amd/src/chart_output_chartjs.js
lib/amd/src/chart_pie.js
lib/amd/src/chart_series.js
lib/classes/chart_base.php
lib/classes/chart_series.php

index f99005f..5cb9007 100644 (file)
Binary files a/lib/amd/build/chart_base.min.js and b/lib/amd/build/chart_base.min.js differ
index a9dfb53..c0b56d0 100644 (file)
Binary files a/lib/amd/build/chart_output_chartjs.min.js and b/lib/amd/build/chart_output_chartjs.min.js differ
index ff55da7..aab6464 100644 (file)
Binary files a/lib/amd/build/chart_pie.min.js and b/lib/amd/build/chart_pie.min.js differ
index b74b9d9..91e0a00 100644 (file)
Binary files a/lib/amd/build/chart_series.min.js and b/lib/amd/build/chart_series.min.js differ
index c99efcf..3020d0d 100644 (file)
@@ -107,7 +107,7 @@ define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) {
      * @param {module:core/chart_series} series The series to add.
      */
     Base.prototype.addSeries = function(series) {
-        this._validateSerie(series);
+        this._validateSeries(series);
         this._series.push(series);
 
         // Give a default color from the set.
@@ -302,6 +302,7 @@ define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) {
      */
     Base.prototype.setXAxis = function(axis, index) {
         index = typeof index === 'undefined' ? 0 : index;
+        this._validateAxis('x', axis, index);
         this._xaxes[index] = axis;
     };
 
@@ -315,16 +316,35 @@ define(['core/chart_series', 'core/chart_axis'], function(Series, Axis) {
      */
     Base.prototype.setYAxis = function(axis, index) {
         index = typeof index === 'undefined' ? 0 : index;
+        this._validateAxis('y', axis, index);
         this._yaxes[index] = axis;
     };
 
+    /**
+     * Validate an axis.
+     *
+     * @protected
+     * @param {String} xy X or Y axis.
+     * @param {module:core/chart_axis} axis The axis to validate.
+     * @param {Number} [index=0] The index of the axis.
+     */
+    Base.prototype._validateAxis = function(xy, axis, index) {
+        index = typeof index === 'undefined' ? 0 : index;
+        if (index > 0) {
+            var axes = xy == 'x' ? this._xaxes : this._yaxes;
+            if (typeof axes[index - 1] === 'undefined') {
+                throw new Error('Missing ' + xy + ' axis at index lower than ' + index);
+            }
+        }
+    };
+
     /**
      * Validate a series.
      *
      * @protected
      * @param {module:core/chart_series} series The series to validate.
      */
-    Base.prototype._validateSerie = function(series) {
+    Base.prototype._validateSeries = function(series) {
         if (this._series.length && this._series[0].getCount() != series.getCount()) {
             throw new Error('Series do not have an equal number of values.');
 
index faa9b3e..c23aad0 100644 (file)
@@ -29,6 +29,17 @@ define([
     'core/chart_pie',
 ], function($, Chartjs, Axis, Base, Pie) {
 
+    /**
+     * Makes an axis ID.
+     *
+     * @param {String} xy Accepts 'x' and 'y'.
+     * @param {Number} index The axis index.
+     * @return {String}
+     */
+    var makeAxisId = function(xy, index) {
+        return 'axis-' + xy + '-' + index;
+    };
+
     /**
      * Chart output for Chart.js.
      *
@@ -89,10 +100,14 @@ define([
      *
      * @protected
      * @param {module:core/chart_axis} axis The axis.
+     * @param {String} xy Accepts 'x' or 'y'.
+     * @param {Number} index The axis index.
      * @return {Object} The axis config.
      */
-    Output.prototype._makeAxisConfig = function(axis) {
-        var scaleData = {};
+    Output.prototype._makeAxisConfig = function(axis, xy, index) {
+        var scaleData = {
+            id: makeAxisId(xy, index)
+        };
 
         if (axis.getPosition() !== Axis.prototype.POS_DEFAULT) {
             scaleData.position = axis.getPosition();
@@ -148,7 +163,7 @@ define([
         this._chart.getXAxes().forEach(function(axis, i) {
             config.options.scales = config.options.scales || {};
             config.options.scales.xAxes = config.options.scales.xAxes || [];
-            config.options.scales.xAxes[i] = this._makeAxisConfig(axis);
+            config.options.scales.xAxes[i] = this._makeAxisConfig(axis, 'x', i);
         }.bind(this));
 
         this._chart.getYAxes().forEach(function(axis, i) {
@@ -156,7 +171,7 @@ define([
 
             config.options.scales = config.options.scales || {};
             config.options.scales.yAxes = config.options.scales.yAxes || [];
-            config.options.scales.yAxes[i] = this._makeAxisConfig(axis);
+            config.options.scales.yAxes[i] = this._makeAxisConfig(axis, 'y', i);
 
             if (axisLabels !== null) {
                 config.options.scales.yAxes[i].ticks.callback = function(value) {
@@ -177,15 +192,24 @@ define([
     Output.prototype._makeDatasetsConfig = function() {
         var sets = this._chart.getSeries().map(function(series) {
             var colors = series.hasColoredValues() ? series.getColors() : series.getColor();
-            return {
+            var dataset = {
                 label: series.getLabel(),
                 data: series.getValues(),
                 type: series.getType(),
                 fill: false,
                 backgroundColor: colors,
                 // Pie charts look better without borders.
-                borderColor: this._chart.getType() == Pie.prototype.TYPE ? null : colors
+                borderColor: this._chart.getType() == Pie.prototype.TYPE ? null : colors,
             };
+
+            if (series.getXAxis() !== null) {
+                dataset.xAxisID = makeAxisId('x', series.getXAxis());
+            }
+            if (series.getYAxis() !== null) {
+                dataset.yAxisID = makeAxisId('y', series.getYAxis());
+            }
+
+            return dataset;
         }.bind(this));
         return sets;
     };
index 7e73905..632e109 100644 (file)
@@ -62,11 +62,11 @@ define(['core/chart_base'], function(Base) {
      *
      * @override
      */
-    Pie.prototype._validateSerie = function() {
+    Pie.prototype._validateSeries = function() {
         if (this._series.length >= 1) {
             throw new Error('Pie charts only support one serie.');
         }
-        return Base.prototype._validateSerie.apply(this, arguments);
+        return Base.prototype._validateSeries.apply(this, arguments);
     };
 
     return Pie;
index e6b3e24..c32d523 100644 (file)
@@ -95,6 +95,22 @@ define([], function() {
      */
     Series.prototype._values = null;
 
+    /**
+     * The index of the X axis.
+     *
+     * @type {Number[]}
+     * @protected
+     */
+    Series.prototype._xaxis = null;
+
+    /**
+     * The index of the Y axis.
+     *
+     * @type {Number[]}
+     * @protected
+     */
+    Series.prototype._yaxis = null;
+
     /**
      * Create a new instance of a series from serialised data.
      *
@@ -106,6 +122,8 @@ define([], function() {
     Series.prototype.create = function(obj) {
         var s = new Series(obj.label, obj.values);
         s.setType(obj.type);
+        s.setXAxis(obj.axes.x);
+        s.setYAxis(obj.axes.y);
 
         // Colors are exported as an array with 1, or n values.
         if (obj.colors && obj.colors.length > 1) {
@@ -171,6 +189,24 @@ define([], function() {
         return this._values;
     };
 
+    /**
+     * Get the index of the X axis.
+     *
+     * @return {Number}
+     */
+    Series.prototype.getXAxis = function() {
+        return this._xaxis;
+    };
+
+    /**
+     * Get the index of the Y axis.
+     *
+     * @return {Number}
+     */
+    Series.prototype.getYAxis = function() {
+        return this._yaxis;
+    };
+
     /**
      * Whether there is a color per value.
      *
@@ -213,6 +249,25 @@ define([], function() {
         this._type = type || null;
     };
 
+    /**
+     * Set the index of the X axis.
+     *
+     * @param {Number} index The index.
+     */
+    Series.prototype.setXAxis = function(index) {
+        this._xaxis = index || null;
+    };
+
+
+    /**
+     * Set the index of the Y axis.
+     *
+     * @param {Number} index The index.
+     */
+    Series.prototype.setYAxis = function(index) {
+        this._yaxis = index || null;
+    };
+
     return Series;
 
 });
index acac8a4..86f106c 100644 (file)
@@ -237,6 +237,7 @@ class chart_base implements JsonSerializable, renderable {
      * @param int $index The index of the axis.
      */
     public function set_xaxis(chart_axis $axis, $index = 0) {
+        $this->validateAxis('x', $axis, $index);
         return $this->xaxes[$index] = $axis;
     }
 
@@ -249,7 +250,29 @@ class chart_base implements JsonSerializable, renderable {
      * @param int $index The index of the axis.
      */
     public function set_yaxis(chart_axis $axis, $index = 0) {
+        $this->validateAxis('y', $axis, $index);
         return $this->yaxes[$index] = $axis;
     }
 
+    /**
+     * Validate an axis.
+     *
+     * We validate this from PHP because not doing it here could result in errors being
+     * hard to trace down. For instance, if we were to add axis at keys without another
+     * axis preceding, we would effectively contain the axes in an associative array
+     * rather than a simple array, and that would have consequences on serialisation.
+     *
+     * @param string} $xy Accepts x or y.
+     * @param chart_axis $axis The axis to validate.
+     * @param index $index The index of the axis.
+     */
+    protected function validateAxis($xy, chart_axis $axis, $index = 0) {
+        if ($index > 0) {
+            $axes = $xy == 'x' ? $this->xaxes : $this->yaxes;
+            if (!isset($axes[$index - 1])) {
+                throw new coding_exception('Missing ' . $xy . ' axis at index lower than ' . $index);
+            }
+        }
+    }
+
 }
index ae73b72..8b01e41 100644 (file)
@@ -50,6 +50,10 @@ class chart_series implements JsonSerializable {
     protected $type = self::TYPE_DEFAULT;
     /** @var float[] Values of the series. */
     protected $values = [];
+    /** @var int Index of the X axis. */
+    protected $xaxis = null;
+    /** @var int Index of the Y axis. */
+    protected $yaxis = null;
 
     /**
      * Constructor.
@@ -116,6 +120,24 @@ class chart_series implements JsonSerializable {
         return $this->values;
     }
 
+    /**
+     * Get the index of the X axis.
+     *
+     * @return int
+     */
+    public function get_xaxis() {
+        return $this->xaxis;
+    }
+
+    /**
+     * Get the index of the Y axis.
+     *
+     * @return int
+     */
+    public function get_yaxis() {
+        return $this->yaxis;
+    }
+
     /**
      * Whether there is a color per value.
      *
@@ -136,6 +158,10 @@ class chart_series implements JsonSerializable {
             'type' => $this->type,
             'values' => $this->values,
             'colors' => $this->colors,
+            'axes' => [
+                'x' => $this->xaxis,
+                'y' => $this->yaxis,
+            ]
         ];
         return $data;
     }
@@ -170,4 +196,22 @@ class chart_series implements JsonSerializable {
         $this->type = $type;
     }
 
+    /**
+     * Set the index of the X axis.
+     *
+     * @param int $index The index.
+     */
+    public function set_xaxis($index) {
+        $this->xaxis = $index;
+    }
+
+    /**
+     * Set the index of the Y axis.
+     *
+     * @param int $index The index.
+     */
+    public function set_yaxis($index) {
+        $this->yaxis = $index;
+    }
+
 }