### 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
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
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
#### 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
* 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
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', {
};
}
-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;
};
}
+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;
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;
}
*/
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;
}
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: {
*/
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
});
}
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';
this._cachedMeta = this.getMeta();
this._type = this._cachedMeta.type;
this._config = undefined;
+ /** @type {boolean | object} */
this._parsing = false;
this._data = undefined;
this._objectData = undefined;
*/
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;
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;
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
'" 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;
+}
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: [],
"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"}
]
}]
},
--- /dev/null
+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
+ }
+ }
+};
t: newDateFromRef(9),
y: 5
}],
- fill: false
+ fill: false,
+ parsing: {
+ xAxisKey: 't'
+ }
}],
},
options: {
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({
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: {
}],
},
options: {
+ parsing: {xAxisKey: 't'},
scales: {
x: {
type: 'time',
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}
]}
]
},