]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Bugfix: span gaps over null values beyond scale limits (#11984)
authorMariss Tubelis <mariss@mariss.no>
Fri, 3 Jan 2025 00:17:24 +0000 (01:17 +0100)
committerGitHub <noreply@github.com>
Fri, 3 Jan 2025 00:17:24 +0000 (19:17 -0500)
* Bugfix: spanGaps not working near min and max limits

* Fix error when meta.dataset.options = null

* Add tests for correct setting of line controller properties _drawStart and _drawCount

* Fix spacing in controller line tests

* Add tension to test

* Add a better test case

* Avoid the use of FindLastIndex

* Avoid taking 0 for null value and improve naming

src/helpers/helpers.extras.ts
test/specs/controller.line.tests.js

index dc34ecf07f904fccab749bf8bd0ef10d6471f5e1..beabb6d96f3ba12766d48041df802f729951401f 100644 (file)
@@ -91,25 +91,41 @@ export function _getStartAndCountOfVisiblePoints(meta: ChartMeta<'line' | 'scatt
   let count = pointCount;
 
   if (meta._sorted) {
-    const {iScale, _parsed} = meta;
+    const {iScale, vScale, _parsed} = meta;
+    const spanGaps = meta.dataset ? meta.dataset.options ? meta.dataset.options.spanGaps : null : null;
     const axis = iScale.axis;
     const {min, max, minDefined, maxDefined} = iScale.getUserBounds();
 
     if (minDefined) {
-      start = _limitValue(Math.min(
+      start = Math.min(
         // @ts-expect-error Need to type _parsed
         _lookupByKey(_parsed, axis, min).lo,
         // @ts-expect-error Need to fix types on _lookupByKey
-        animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo),
-      0, pointCount - 1);
+        animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo);
+      if (spanGaps) {
+        const distanceToDefinedLo = (_parsed
+          .slice(0, start + 1)
+          .reverse()
+          .findIndex(
+            point => point[vScale.axis] || point[vScale.axis] === 0));
+        start -= Math.max(0, distanceToDefinedLo);
+      }
+      start = _limitValue(start, 0, pointCount - 1);
     }
     if (maxDefined) {
-      count = _limitValue(Math.max(
+      let end = Math.max(
         // @ts-expect-error Need to type _parsed
         _lookupByKey(_parsed, iScale.axis, max, true).hi + 1,
         // @ts-expect-error Need to fix types on _lookupByKey
-        animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max), true).hi + 1),
-      start, pointCount) - start;
+        animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max), true).hi + 1);
+      if (spanGaps) {
+        const distanceToDefinedHi = (_parsed
+          .slice(end - 1)
+          .findIndex(
+            point => point[vScale.axis] || point[vScale.axis] === 0));
+        end += Math.max(0, distanceToDefinedHi);
+      }
+      count = _limitValue(end, start, pointCount) - start;
     } else {
       count = pointCount - start;
     }
index 368627d68a0cf4c7406203573c123829052227e4..3924a79887dd9a4fda803af82758c97a8dc87e4a 100644 (file)
@@ -1071,6 +1071,93 @@ describe('Chart.controllers.line', function() {
     expect(visiblePoints.length).toBe(6);
   }, 500);
 
+  it('should correctly calc _drawStart and _drawCount when first points beyond scale limits are null and spanGaps=true', async() => {
+    var chart = window.acquireChart({
+      type: 'line',
+      data: {
+        labels: [0, 10, 20, 30, 40, 50],
+        datasets: [{
+          data: [3, null, 2, 3, null, 1.5],
+          spanGaps: true,
+          tension: 0.4
+        }]
+      },
+      options: {
+        scales: {
+          x: {
+            type: 'linear',
+            min: 11,
+            max: 40,
+          }
+        }
+      }
+    });
+
+    chart.update();
+    var controller = chart.getDatasetMeta(0).controller;
+
+    expect(controller._drawStart).toBe(0);
+    expect(controller._drawCount).toBe(6);
+  }, 500);
+
+  it('should correctly calc _drawStart and _drawCount when all points beyond scale limits are null and spanGaps=true', async() => {
+    var chart = window.acquireChart({
+      type: 'line',
+      data: {
+        labels: [0, 10, 20, 30, 40, 50],
+        datasets: [{
+          data: [null, null, 2, 3, null, null],
+          spanGaps: true,
+          tension: 0.4
+        }]
+      },
+      options: {
+        scales: {
+          x: {
+            type: 'linear',
+            min: 11,
+            max: 40,
+          }
+        }
+      }
+    });
+
+    chart.update();
+    var controller = chart.getDatasetMeta(0).controller;
+
+    expect(controller._drawStart).toBe(1);
+    expect(controller._drawCount).toBe(4);
+  }, 500);
+
+  it('should correctly calc _drawStart and _drawCount when spanGaps=false', async() => {
+    var chart = window.acquireChart({
+      type: 'line',
+      data: {
+        labels: [0, 10, 20, 30, 40, 50],
+        datasets: [{
+          data: [3, null, 2, 3, null, 1.5],
+          spanGaps: false,
+          tension: 0.4
+        }]
+      },
+      options: {
+        scales: {
+          x: {
+            type: 'linear',
+            min: 11,
+            max: 40,
+          }
+        }
+      }
+    });
+
+    chart.update();
+    var controller = chart.getDatasetMeta(0).controller;
+
+    expect(controller._drawStart).toBe(1);
+    expect(controller._drawCount).toBe(4);
+  }, 500);
+
   it('should not override tooltip title and label callbacks', async() => {
     const chart = window.acquireChart({
       type: 'line',