| `fill` | How to fill the area under the line. See [area charts](area.md).
| `tension` | Bezier curve tension of the line. Set to 0 to draw straightlines. This option is ignored if monotone cubic interpolation is used.
| `showLine` | If false, the line is not drawn for this dataset.
-| `spanGaps` | If true, lines will be drawn between points with no or null data. If false, points with `NaN` data will create a break in the line. Can also be a number specifying the maximum gap length to span. The unit of the value depends on the scale used.
+| `spanGaps` | If true, lines will be drawn between points with no or null data. If false, points with `null` data will create a break in the line. Can also be a number specifying the maximum gap length to span. The unit of the value depends on the scale used.
If the value is `undefined`, `showLine` and `spanGaps` fallback to the associated [chart configuration options](#configuration-options). The rest of the values fallback to the associated [`elements.line.*`](../configuration/elements.md#line-configuration) options.
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| `showLine` | `boolean` | `true` | If false, the lines between points are not drawn.
-| `spanGaps` | `boolean`\|`number` | `false` | If true, lines will be drawn between points with no or null data. If false, points with `NaN` data will create a break in the line. Can also be a number specifying the maximum gap length to span. The unit of the value depends on the scale used.
+| `spanGaps` | `boolean`\|`number` | `false` | If true, lines will be drawn between points with no or null data. If false, points with `null` data will create a break in the line. Can also be a number specifying the maximum gap length to span. The unit of the value depends on the scale used.
## Default Options
| `borderWidth` | The line width (in pixels).
| `fill` | How to fill the area under the line. See [area charts](area.md).
| `tension` | Bezier curve tension of the line. Set to 0 to draw straight lines.
-| `spanGaps` | If true, lines will be drawn between points with no or null data. If false, points with `NaN` data will create a break in the line.
+| `spanGaps` | If true, lines will be drawn between points with no or null data. If false, points with `null` data will create a break in the line.
If the value is `undefined`, `spanGaps` fallback to the associated [chart configuration options](#configuration-options). The rest of the values fallback to the associated [`elements.line.*`](../configuration/elements.md#line-configuration) options.
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
-| `spanGaps` | `boolean` | `false` | If false, NaN data causes a break in the line.
+| `spanGaps` | `boolean` | `false` | If false, `null` data causes a break in the line.
## Scale Options
if (label) {
label += ': ';
}
- if (!isNaN(context.parsed.y)) {
+ if (context.parsed.y !== null) {
label += new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(context.parsed.y);
}
return label;
// Returns true if the scale instance is horizontal
isHorizontal: function() {},
- // Get the correct value from the value from this.chart.data.datasets[x].data[]
- // If dataValue is an object, returns .x or .y depending on the return of isHorizontal()
- // If the value is undefined, returns NaN
- // Otherwise returns the value.
- // Note that in all cases, the returned value is not guaranteed to be a number
- getRightValue: function(dataValue) {},
-
// Returns the scale tick objects ({label, major})
getTicks: function() {}
}
## Object[]
```javascript
-data: [{x: 10, y: 20}, {x: 15, y: 10}]
+data: [{x: 10, y: 20}, {x: 15, y: null}, {x: 20, y: 10}]
```
```javascript
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.
-The values provided must be parsable by the associated scales or in the internal format of the associated scales. A common mistake would be to provide integers for the `category` scale, which uses integers as an internal format, where each integer represents an index in the labels array.
+The values provided must be parsable by the associated scales or in the internal format of the associated scales. A common mistake would be to provide integers for the `category` scale, which uses integers as an internal format, where each integer represents an index in the labels array. `null` can be used for skipped values.
## Object[] using custom properties
clipArea(chart.ctx, chart.chartArea);
for (; i < ilen; ++i) {
- if (!isNaN(me.getParsed(i)[vScale.axis])) {
+ if (me.getParsed(i)[vScale.axis] !== null) {
rects[i].draw(me._ctx);
}
}
}
/**
- * @private
- */
+ * @private
+ */
_circumference(i, reset) {
const me = this;
const opts = me.options;
const meta = me._cachedMeta;
const circumference = me._getCircumference();
- return reset && opts.animation.animateRotate ? 0 : this.chart.getDataVisibility(i) ? me.calculateCircumference(meta._parsed[i] * circumference / TAU) : 0;
+ if ((reset && opts.animation.animateRotate) || !this.chart.getDataVisibility(i) || meta._parsed[i] === null) {
+ return 0;
+ }
+ return me.calculateCircumference(meta._parsed[i] * circumference / TAU);
}
updateElements(arcs, start, count, mode) {
for (i = 0; i < metaData.length; i++) {
const value = meta._parsed[i];
- if (!isNaN(value) && this.chart.getDataVisibility(i)) {
+ if (value !== null && this.chart.getDataVisibility(i)) {
total += Math.abs(value);
}
}
import Animations from './core.animations';
import defaults from './core.defaults';
-import {isObject, isArray, valueOrDefault, resolveObjectKey, defined} from '../helpers/helpers.core';
+import {isArray, isFinite, isObject, valueOrDefault, resolveObjectKey, defined} from '../helpers/helpers.core';
import {listenArrayEvents, unlistenArrayEvents} from '../helpers/helpers.collection';
import {sign} from '../helpers/helpers.math';
const keys = stack.keys;
let i, ilen, datasetIndex, otherValue;
+ if (value === null) {
+ return;
+ }
+
for (i = 0, ilen = keys.length; i < ilen; ++i) {
datasetIndex = +keys[i];
if (datasetIndex === dsIndex) {
break;
}
otherValue = stack.values[datasetIndex];
- if (!isNaN(otherValue) && (value === 0 || sign(value) === sign(otherValue))) {
+ if (isFinite(otherValue) && (value === 0 || sign(value) === sign(otherValue))) {
value += otherValue;
}
}
parsed = me.parsePrimitiveData(meta, data, start, count);
}
- const isNotInOrderComparedToPrev = () => isNaN(cur[iAxis]) || (prev && cur[iAxis] < prev[iAxis]);
+ const isNotInOrderComparedToPrev = () => cur[iAxis] === null || (prev && cur[iAxis] < prev[iAxis]);
for (i = 0; i < count; ++i) {
meta._parsed[i + start] = cur = parsed[i];
if (sorted) {
* @protected
*/
updateRangeFromParsed(range, scale, parsed, stack) {
- let value = parsed[scale.axis];
+ const parsedValue = parsed[scale.axis];
+ let value = parsedValue === null ? NaN : parsedValue;
const values = stack && parsed._stacks[scale.axis];
if (stack && values) {
stack.values = values;
// in addition to the stacked value
range.min = Math.min(range.min, value);
range.max = Math.max(range.max, value);
- value = applyStack(stack, value, this._cachedMeta.index, true);
+ value = applyStack(stack, parsedValue, this._cachedMeta.index, true);
}
range.min = Math.min(range.min, value);
range.max = Math.max(range.max, value);
parsed = _parsed[i];
value = parsed[scale.axis];
otherValue = parsed[otherScale.axis];
- return (isNaN(value) || isNaN(otherValue) || otherMin > otherValue || otherMax < otherValue);
+ return (!isFinite(value) || !isFinite(otherValue) || otherMin > otherValue || otherMax < otherValue);
}
for (i = 0; i < ilen; ++i) {
for (i = 0, ilen = parsed.length; i < ilen; ++i) {
value = parsed[i][scale.axis];
- if (!isNaN(value)) {
+ if (isFinite(value)) {
values.push(value);
}
}
// Utils
getPixelForValue(value) {
- return this.getPixelForDecimal((value - this._startValue) / this._valueRange);
+ return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
}
getValueForPixel(pixel) {
parse(raw, index) { // eslint-disable-line no-unused-vars
if (isNullOrUndef(raw)) {
- return NaN;
+ return null;
}
if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {
- return NaN;
+ return null;
}
return +raw;
this._zero = true;
return undefined;
}
- return isFinite(value) && value > 0 ? value : NaN;
+ return isFinite(value) && value > 0 ? value : null;
}
determineDataLimits() {
*/
function parse(scale, input) {
if (isNullOrUndef(input)) {
- return NaN;
+ return null;
}
const adapter = scale._adapter;
}
if (value === null) {
- return NaN;
+ return null;
}
if (round) {
*/
parse(raw, index) { // eslint-disable-line no-unused-vars
if (raw === undefined) {
- return NaN;
+ return null;
}
return parse(this, raw);
}
*/
getDecimalForValue(value) {
const me = this;
- return (value - me.min) / (me.max - me.min);
+ return value === null ? NaN : (value - me.min) / (me.max - me.min);
}
/**
module.exports = {
config: {
type: 'line',
+ parsing: false,
data: {
datasets: [
{
- data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: NaN, y: -10}, {x: 19, y: -5}],
+ data: [{x: 0, y: 10}, {x: 5, y: 0}, {x: null, y: -10}, {x: 19, y: -5}],
borderColor: 'red',
fill: true,
spanGaps: true,