]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Parse from custom properties in data (#7489)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Thu, 18 Jun 2020 21:36:53 +0000 (00:36 +0300)
committerGitHub <noreply@github.com>
Thu, 18 Jun 2020 21:36:53 +0000 (17:36 -0400)
* Parse from custom properties in data
* Resolve CC issues
* Review update

15 files changed:
docs/docs/axes/cartesian/time.md
docs/docs/general/data-structures.md
docs/docs/getting-started/v3-migration.md
src/controllers/controller.bar.js
src/controllers/controller.bubble.js
src/core/core.datasetController.js
src/core/core.scale.js
src/helpers/helpers.core.js
src/scales/scale.time.js
test/fixtures/controller.bar/bar-thickness-no-overlap.json
test/fixtures/controller.bar/data/parsing.js [new file with mode: 0644]
test/fixtures/controller.bar/data/parsing.png [new file with mode: 0644]
test/fixtures/scale.time/data-ty.js
test/specs/core.datasetController.tests.js
test/specs/scale.time.tests.js

index 557b1fb41aec78cfacc6167d18d71cd8d36d8e12..61b1b2cf28ca23ef41a40ce01149336a5f95b301 100644 (file)
@@ -12,17 +12,7 @@ The time scale **requires** both a date library and corresponding adapter to be
 
 ### Input Data
 
-The x-axis data points may additionally be specified via the `t` or `x` attribute when using the time scale.
-
-```javascript
-data: [{
-    x: new Date(),
-    y: 1
-}, {
-    t: new Date(),
-    y: 10
-}]
-```
+See [data structures](../../general/data-structures.md).
 
 ### Date Formats
 
@@ -153,7 +143,7 @@ The `bounds` property controls the scale boundary strategy (bypassed by `min`/`m
 The `ticks.source` property controls the ticks generation.
 
 * `'auto'`: generates "optimal" ticks based on scale size and time options
-* `'data'`: generates ticks from data (including labels from data `{t|x|y}` objects)
+* `'data'`: generates ticks from data (including labels from data `{x|y}` objects)
 * `'labels'`: generates ticks from user given `labels` ONLY
 
 ### Parser
index 7e6c7230c6c5fcb3a16ae50053aeb84e31938e75..1d53bbe7e9b38e4dbfe0933e087df8066cf3d0a3 100644 (file)
@@ -29,6 +29,54 @@ data: [{x:'Sales', y:20}, {x:'Revenue', y:10}]
 
 This is also the internal format used for parsed data. In this mode, parsing can be disabled by specifying `parsing: false` at chart options or dataset. If parsing is disabled, data must be sorted and in the formats the associated chart type and scales use internally.
 
+## Object[] using custom properties
+
+```javascript
+type: 'bar',
+data: {
+    datasets: [{
+        data: [{id: 'Sales', nested: {value: 1500}}, {id: 'Purchases', nested: {value: 500}}]
+    }]
+},
+options: {
+    parsing: {
+        xAxisKey: 'id',
+        yAxisKey: 'nested.value'
+    }
+}
+```
+
+### `parsing` can also be specified per dataset
+
+```javascript
+const data = [{x: 'Jan', net: 100, cogs: 50, gm: 50}, {x: 'Feb', net: 120, cogs: 55, gm: 75}];
+const cfg = {
+    type: 'bar',
+    data: {
+        labels: ['Jan', 'Feb'],
+        datasets: [{
+            label: 'Net sales',
+            data: data,
+            parsing: {
+                yAxisKey: 'net'
+            }
+        }, {
+            label: 'Cost of goods sold',
+            data: data,
+            parsing: {
+                yAxisKey: 'cogs'
+            }
+        }, {
+            label: 'Gross margin',
+            data: data,
+            parsing: {
+                yAxisKey: 'gm'
+            }
+        }]
+    },
+};
+```
+
 ## Object
 
 ```javascript
index b5190a50ea64969d73c9044d80e3737d2823330e..c93c0fb01b1c98a8f82fea3a27dc20add1d56715 100644 (file)
@@ -32,6 +32,7 @@ A number of changes were made to the configuration options passed to the `Chart`
 #### Generic changes
 
 * Indexable options are now looping. `backgroundColor: ['red', 'green']` will result in alternating `'red'` / `'green'` if there are more than 2 data points.
+* The input properties of object data can now be freely specified, see [data structures](../general/data-structures.md) for details.
 
 #### Specific changes
 
@@ -61,6 +62,7 @@ A number of changes were made to the configuration options passed to the `Chart`
 * Dataset options are now configured as `options[type].datasets` rather than `options.datasets[type]`
 * To override the platform class used in a chart instance, pass `platform: PlatformClass` in the config object. Note that the class should be passed, not an instance of the class.
 * `aspectRatio` defaults to 1 for doughnut, pie, polarArea, and radar charts
+* `TimeScale` does not read `t` from object data by default anymore. The default property is `x` or `y`, depending on the orientation. See [data structures](../general/data-structures.md) for details on how to change the default.
 
 #### Defaults
 
index e39d748a7b0c64df2c41daaa2059209e6fb9283c..5afb5b4ed7b9ea4a385e993d138c9ca889127a98 100644 (file)
@@ -2,7 +2,7 @@ import DatasetController from '../core/core.datasetController';
 import defaults from '../core/core.defaults';
 import {Rectangle} from '../elements/index';
 import {clipArea, unclipArea} from '../helpers/helpers.canvas';
-import {isArray, isNullOrUndef, valueOrDefault} from '../helpers/helpers.core';
+import {isArray, isNullOrUndef, valueOrDefault, resolveObjectKey} from '../helpers/helpers.core';
 import {_limitValue, sign} from '../helpers/helpers.math';
 
 defaults.set('bar', {
@@ -120,9 +120,9 @@ function computeFlexCategoryTraits(index, ruler, options) {
        };
 }
 
-function parseFloatBar(arr, item, vScale, i) {
-       const startValue = vScale.parse(arr[0], i);
-       const endValue = vScale.parse(arr[1], i);
+function parseFloatBar(entry, item, vScale, i) {
+       const startValue = vScale.parse(entry[0], i);
+       const endValue = vScale.parse(entry[1], i);
        const min = Math.min(startValue, endValue);
        const max = Math.max(startValue, endValue);
        let barStart = min;
@@ -147,6 +147,15 @@ function parseFloatBar(arr, item, vScale, i) {
        };
 }
 
+function parseValue(entry, item, vScale, i) {
+       if (isArray(entry)) {
+               parseFloatBar(entry, item, vScale, i);
+       } else {
+               item[vScale.axis] = vScale.parse(entry, i);
+       }
+       return item;
+}
+
 function parseArrayOrPrimitive(meta, data, start, count) {
        const iScale = meta.iScale;
        const vScale = meta.vScale;
@@ -159,14 +168,7 @@ function parseArrayOrPrimitive(meta, data, start, count) {
                entry = data[i];
                item = {};
                item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
-
-               if (isArray(entry)) {
-                       parseFloatBar(entry, item, vScale, i);
-               } else {
-                       item[vScale.axis] = vScale.parse(entry, i);
-               }
-
-               parsed.push(item);
+               parsed.push(parseValue(entry, item, vScale, i));
        }
        return parsed;
 }
@@ -202,20 +204,16 @@ export default class BarController extends DatasetController {
         */
        parseObjectData(meta, data, start, count) {
                const {iScale, vScale} = meta;
-               const vProp = vScale.axis;
+               const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;
+               const iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;
+               const vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;
                const parsed = [];
-               let i, ilen, item, obj, value;
+               let i, ilen, item, obj;
                for (i = start, ilen = start + count; i < ilen; ++i) {
                        obj = data[i];
                        item = {};
-                       item[iScale.axis] = iScale.parseObject(obj, iScale.axis, i);
-                       value = obj[vProp];
-                       if (isArray(value)) {
-                               parseFloatBar(value, item, vScale, i);
-                       } else {
-                               item[vScale.axis] = vScale.parseObject(obj, vProp, i);
-                       }
-                       parsed.push(item);
+                       item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i);
+                       parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i));
                }
                return parsed;
        }
index fdbe5b90f1a89b476fd9e7fb081122c3f20eda8b..0fd32fec388e3126b1b78750b5b72800c21e1e66 100644 (file)
@@ -2,6 +2,7 @@ import DatasetController from '../core/core.datasetController';
 import defaults from '../core/core.defaults';
 import {Point} from '../elements/index';
 import {resolve} from '../helpers/helpers.options';
+import {resolveObjectKey} from '../helpers/helpers.core';
 
 defaults.set('bubble', {
        animation: {
@@ -36,13 +37,14 @@ export default class BubbleController extends DatasetController {
         */
        parseObjectData(meta, data, start, count) {
                const {xScale, yScale} = meta;
+               const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;
                const parsed = [];
                let i, ilen, item;
                for (i = start, ilen = start + count; i < ilen; ++i) {
                        item = data[i];
                        parsed.push({
-                               x: xScale.parseObject(item, 'x', i),
-                               y: yScale.parseObject(item, 'y', i),
+                               x: xScale.parse(resolveObjectKey(item, xAxisKey), i),
+                               y: yScale.parse(resolveObjectKey(item, yAxisKey), i),
                                _custom: item && item.r && +item.r
                        });
                }
index 76df52bc54f97d314ca62afc12541e76aa0ac005..517187154e6af075217a6418e27f8c46fd11f683 100644 (file)
@@ -1,5 +1,5 @@
 import Animations from './core.animations';
-import {isObject, merge, _merger, isArray, valueOrDefault, mergeIf} from '../helpers/helpers.core';
+import {isObject, merge, _merger, isArray, valueOrDefault, mergeIf, resolveObjectKey} from '../helpers/helpers.core';
 import {listenArrayEvents, unlistenArrayEvents} from '../helpers/helpers.collection';
 import {resolve} from '../helpers/helpers.options';
 import {getHoverColor} from '../helpers/helpers.color';
@@ -162,6 +162,7 @@ export default class DatasetController {
                this._cachedMeta = this.getMeta();
                this._type = this._cachedMeta.type;
                this._config = undefined;
+               /** @type {boolean | object} */
                this._parsing = false;
                this._data = undefined;
                this._objectData = undefined;
@@ -456,6 +457,7 @@ export default class DatasetController {
         */
        parseObjectData(meta, data, start, count) {
                const {xScale, yScale} = meta;
+               const {xAxisKey = 'x', yAxisKey = 'y'} = this._parsing;
                const parsed = new Array(count);
                let i, ilen, index, item;
 
@@ -463,8 +465,8 @@ export default class DatasetController {
                        index = i + start;
                        item = data[index];
                        parsed[i] = {
-                               x: xScale.parseObject(item, 'x', index),
-                               y: yScale.parseObject(item, 'y', index)
+                               x: xScale.parse(resolveObjectKey(item, xAxisKey), index),
+                               y: yScale.parse(resolveObjectKey(item, yAxisKey), index)
                        };
                }
                return parsed;
index c7fc1e45de026a1597b146f81d6136a3c0de6212..898413a481f308bf9ccfb995cc759dc67498a78d 100644 (file)
@@ -369,21 +369,6 @@ export default class Scale extends Element {
                return raw;
        }
 
-       /**
-        * Parse an object for axis to internal representation.
-        * @param {object} obj
-        * @param {string} axis
-        * @param {number} index
-        * @since 3.0
-        * @protected
-        */
-       parseObject(obj, axis, index) {
-               if (obj[axis] !== undefined) {
-                       return this.parse(obj[axis], index);
-               }
-               return null;
-       }
-
        /**
         * @return {{min: number, max: number, minDefined: boolean, maxDefined: boolean}}
         * @protected
index a283ffba36d2518fc1b1729d8595d928da80995d..743e3f02d4ee1f4eef469af17d8be56ac4f26bdd 100644 (file)
@@ -260,3 +260,19 @@ export function _deprecated(scope, value, previous, current) {
                        '" is deprecated. Please use "' + current + '" instead');
        }
 }
+
+export function resolveObjectKey(obj, key) {
+       if (key.length < 3) {
+               return obj[key];
+       }
+       const keys = key.split('.');
+       for (let i = 0, n = keys.length; i < n; ++i) {
+               const k = keys[i];
+               if (k in obj) {
+                       obj = obj[k];
+               } else {
+                       return;
+               }
+       }
+       return obj;
+}
index 2dc8a4cc16249029b4b818c53432b6ccb290651e..322a7c706cad8b25d2aeeda694fba730064548bc 100644 (file)
@@ -596,22 +596,6 @@ class TimeScale extends Scale {
                return parse(this, raw);
        }
 
-       /**
-        * @param {object} obj
-        * @param {string} axis
-        * @param {number} index
-        * @return {number|null}
-        */
-       parseObject(obj, axis, index) {
-               if (obj && obj.t) {
-                       return this.parse(obj.t, index);
-               }
-               if (obj[axis] !== undefined) {
-                       return this.parse(obj[axis], index);
-               }
-               return null;
-       }
-
        invalidateCaches() {
                this._cache = {
                        data: [],
index 742f9a3e803ea49e63f361910d8aee260b088831..be67964a3fc5e07e33c77609b0f81c08e4459602 100644 (file)
@@ -6,11 +6,11 @@
             "datasets": [{
                 "backgroundColor": "#FF6384",
                 "data": [
-                    {"y": "1", "t": "2016"},
-                    {"y": "2", "t": "2017"},
-                    {"y": "3", "t": "2017-08"},
-                    {"y": "4", "t": "2024"},
-                    {"y": "5", "t": "2030"}
+                    {"y": "1", "x": "2016"},
+                    {"y": "2", "x": "2017"},
+                    {"y": "3", "x": "2017-08"},
+                    {"y": "4", "x": "2024"},
+                    {"y": "5", "x": "2030"}
                 ]
             }]
         },
diff --git a/test/fixtures/controller.bar/data/parsing.js b/test/fixtures/controller.bar/data/parsing.js
new file mode 100644 (file)
index 0000000..aecab69
--- /dev/null
@@ -0,0 +1,46 @@
+const data = [{x: 'Jan', net: 100, cogs: 50, gm: 50}, {x: 'Feb', net: 120, cogs: 55, gm: 75}];
+
+module.exports = {
+       config: {
+               type: 'bar',
+               data: {
+                       labels: ['Jan', 'Feb'],
+                       datasets: [{
+                               label: 'Net sales',
+                               backgroundColor: 'blue',
+                               data: data,
+                               parsing: {
+                                       yAxisKey: 'net'
+                               }
+                       }, {
+                               label: 'Cost of goods sold',
+                               backgroundColor: 'red',
+                               data: data,
+                               parsing: {
+                                       yAxisKey: 'cogs'
+                               }
+                       }, {
+                               label: 'Gross margin',
+                               backgroundColor: 'green',
+                               data: data,
+                               parsing: {
+                                       yAxisKey: 'gm'
+                               }
+                       }]
+               },
+               options: {
+                       legend: false,
+                       title: false,
+                       scales: {
+                               x: {display: false},
+                               y: {display: false}
+                       }
+               }
+       },
+       options: {
+               canvas: {
+                       height: 256,
+                       width: 512
+               }
+       }
+};
diff --git a/test/fixtures/controller.bar/data/parsing.png b/test/fixtures/controller.bar/data/parsing.png
new file mode 100644 (file)
index 0000000..f0a63fd
Binary files /dev/null and b/test/fixtures/controller.bar/data/parsing.png differ
index be1fa404d26bdd7efe26d18b82bbe1e463dc9184..6fff2a486660f5e407da4e68c91f4637b152797a 100644 (file)
@@ -30,7 +30,10 @@ module.exports = {
                                        t: newDateFromRef(9),
                                        y: 5
                                }],
-                               fill: false
+                               fill: false,
+                               parsing: {
+                                       xAxisKey: 't'
+                               }
                        }],
                },
                options: {
index c78b96a05679b6bf2ad3b1054b6f23cf4eb56270..67aacaa3dbbd607b2b2ada198c9847d73e0bca5f 100644 (file)
@@ -167,6 +167,43 @@ describe('Chart.DatasetController', function() {
                expect(parsedYValues2).toEqual([0, 1, 2, 3, 0]); // label indices
        });
 
+       it('should parse using provided keys', function() {
+               const chart = acquireChart({
+                       type: 'line',
+                       data: {
+                               datasets: [{
+                                       data: [
+                                               {x: 1, data: {key: 'one', value: 20}},
+                                               {data: {key: 'two', value: 30}}
+                                       ]
+                               }]
+                       },
+                       options: {
+                               parsing: {
+                                       xAxisKey: 'data.key',
+                                       yAxisKey: 'data.value'
+                               },
+                               scales: {
+                                       x: {
+                                               type: 'category',
+                                               labels: ['one', 'two']
+                                       },
+                                       y: {
+                                               type: 'linear'
+                                       },
+                               }
+                       }
+               });
+
+               const meta = chart.getDatasetMeta(0);
+               const parsedXValues = meta._parsed.map(p => p.x);
+               const parsedYValues = meta._parsed.map(p => p.y);
+
+               expect(meta.data.length).toBe(2);
+               expect(parsedXValues).toEqual([0, 1]); // label indices
+               expect(parsedYValues).toEqual([20, 30]);
+       });
+
        it('should synchronize metadata when data are inserted or removed and parsing is on', function() {
                const data = [0, 1, 2, 3, 4, 5];
                const chart = acquireChart({
index 5d399eb5bc86484caa4bcdb66c65b2b926c24731..fe172fd392366f96c15d6887f31eb16e3fbbe739 100644 (file)
@@ -466,7 +466,7 @@ describe('Time scale tests', function() {
                        data: {
                                datasets: [{
                                        xAxisID: 'x',
-                                       data: [{t: '2015-01-01T20:00:00', y: 10}, {t: '2015-01-02T21:00:00', y: 3}]
+                                       data: [{x: '2015-01-01T20:00:00', y: 10}, {x: '2015-01-02T21:00:00', y: 3}]
                                }],
                        },
                        options: {
@@ -530,6 +530,7 @@ describe('Time scale tests', function() {
                                }],
                        },
                        options: {
+                               parsing: {xAxisKey: 't'},
                                scales: {
                                        x: {
                                                type: 'time',
@@ -697,9 +698,9 @@ describe('Time scale tests', function() {
                                                datasets: [
                                                        {data: [0, 1, 2, 3, 4, 5]},
                                                        {data: [
-                                                               {t: '2018', y: 6},
-                                                               {t: '2020', y: 7},
-                                                               {t: '2043', y: 8}
+                                                               {x: '2018', y: 6},
+                                                               {x: '2020', y: 7},
+                                                               {x: '2043', y: 8}
                                                        ]}
                                                ]
                                        },