| [`pointRotation`](#point-styling) | `number` | Yes | Yes | `0`
| [`pointStyle`](#point-styling) | <code>string|Image</code> | Yes | Yes | `'circle'`
| [`showLine`](#line-styling) | `boolean` | - | - | `undefined`
-| [`spanGaps`](#line-styling) | `boolean` | - | - | `undefined`
+| [`spanGaps`](#line-styling) | <code>boolean|number</code> | - | - | `undefined`
| [`steppedLine`](#stepped-line) | <code>boolean|string</code> | - | - | `false`
| [`xAxisID`](#general) | `string` | - | - | first x axis
| [`yAxisID`](#general) | `string` | - | - | first y axis
| `fill` | How to fill the area under the line. See [area charts](area.md).
| `lineTension` | 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.
+| `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.
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.
--- /dev/null
+<!doctype html>
+<html>
+
+<head>
+ <title>Time Scale Point Data</title>
+ <script src="https://cdn.jsdelivr.net/npm/moment@2.24.0/moment.min.js"></script>
+ <script src="../../../dist/Chart.min.js"></script>
+ <script src="../../utils.js"></script>
+ <style>
+ canvas {
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ }
+ </style>
+</head>
+
+<body>
+ <div style="width:75%;">
+ <canvas id="canvas"></canvas>
+ </div>
+ <br>
+ <br>
+ <button id="randomizeData">Randomize Data</button>
+ <button id="addData">Add Data</button>
+ <button id="removeData">Remove Data</button>
+ <script>
+ function newDate(days) {
+ return moment().add(days, 'd').toDate();
+ }
+
+ function newDateString(days) {
+ return moment().add(days, 'd').format();
+ }
+
+ var color = Chart.helpers.color;
+ var config = {
+ type: 'line',
+ data: {
+ datasets: [{
+ label: 'Dataset with string point data',
+ backgroundColor: color(window.chartColors.red).alpha(0.5).rgbString(),
+ borderColor: window.chartColors.red,
+ fill: false,
+ data: [{
+ x: newDateString(0),
+ y: randomScalingFactor()
+ }, {
+ x: newDateString(2),
+ y: randomScalingFactor()
+ }, {
+ x: newDateString(4),
+ y: randomScalingFactor()
+ }, {
+ x: newDateString(6),
+ y: randomScalingFactor()
+ }],
+ }, {
+ label: 'Dataset with date object point data',
+ backgroundColor: color(window.chartColors.blue).alpha(0.5).rgbString(),
+ borderColor: window.chartColors.blue,
+ fill: false,
+ data: [{
+ x: newDate(0),
+ y: randomScalingFactor()
+ }, {
+ x: newDate(2),
+ y: randomScalingFactor()
+ }, {
+ x: newDate(5),
+ y: randomScalingFactor()
+ }, {
+ x: newDate(6),
+ y: randomScalingFactor()
+ }]
+ }]
+ },
+ options: {
+ spanGaps: 1000 * 60 * 60 * 24 * 2, // 2 days
+ responsive: true,
+ title: {
+ display: true,
+ text: 'Chart.js Time - spanGaps: 172800000 (2 days in ms)'
+ },
+ scales: {
+ x: {
+ type: 'time',
+ display: true,
+ scaleLabel: {
+ display: true,
+ labelString: 'Date'
+ },
+ ticks: {
+ major: {
+ fontStyle: 'bold',
+ fontColor: '#FF0000'
+ }
+ }
+ },
+ y: {
+ display: true,
+ scaleLabel: {
+ display: true,
+ labelString: 'value'
+ }
+ }
+ }
+ }
+ };
+
+ window.onload = function() {
+ var ctx = document.getElementById('canvas').getContext('2d');
+ window.myLine = new Chart(ctx, config);
+ };
+
+ document.getElementById('randomizeData').addEventListener('click', function() {
+ config.data.datasets.forEach(function(dataset) {
+ dataset.data.forEach(function(dataObj) {
+ dataObj.y = randomScalingFactor();
+ });
+ });
+
+ window.myLine.update();
+ });
+ document.getElementById('addData').addEventListener('click', function() {
+ if (config.data.datasets.length > 0) {
+ config.data.datasets[0].data.push({
+ x: newDateString(config.data.datasets[0].data.length + 2),
+ y: randomScalingFactor()
+ });
+ config.data.datasets[1].data.push({
+ x: newDate(config.data.datasets[1].data.length + 2),
+ y: randomScalingFactor()
+ });
+
+ window.myLine.update();
+ }
+ });
+
+ document.getElementById('removeData').addEventListener('click', function() {
+ config.data.datasets.forEach(function(dataset) {
+ dataset.data.pop();
+ });
+
+ window.myLine.update();
+ });
+ </script>
+</body>
+
+</html>
const firstOpts = me._resolveDataElementOptions(start, mode);
const sharedOptions = me._getSharedOptions(mode, points[start], firstOpts);
const includeOptions = me._includeOptions(mode, sharedOptions);
+ const spanGaps = valueOrDefault(me._config.spanGaps, me.chart.options.spanGaps);
+ const maxGapLength = helpers.math.isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
+ let prevParsed;
for (let i = 0; i < points.length; ++i) {
const index = start + i;
const properties = {
x,
y,
- skip: isNaN(x) || isNaN(y)
+ skip: isNaN(x) || isNaN(y),
+ stop: i > 0 && (parsed.x - prevParsed.x) > maxGapLength
};
if (includeOptions) {
}
me._updateElement(point, index, properties, mode);
+
+ prevParsed = parsed;
}
me._updateSharedOptions(sharedOptions, mode);
for (end = start + 1; end <= max; ++end) {
const cur = points[end % count];
- if (cur.skip) {
+ if (cur.skip || cur.stop) {
if (!prev.skip) {
loop = false;
result.push({start: start % count, end: (end - 1) % count, loop});
- start = last = null;
+ start = last = cur.stop ? end : null;
}
} else {
last = end;
const loop = !!line._loop;
const {start, end} = findStartAndEnd(points, count, loop, spanGaps);
- if (spanGaps) {
+ if (spanGaps === true) {
return [{start, end, loop}];
}