]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Support monotone cubic interpolation for vertical line charts (#9084)
authorAkihiko Kusanagi <nagi@nagi-p.com>
Fri, 14 May 2021 21:07:26 +0000 (05:07 +0800)
committerGitHub <noreply@github.com>
Fri, 14 May 2021 21:07:26 +0000 (17:07 -0400)
* Support monotone cubic interpolation for vertical line charts

* Use more more intuitive veriable names

src/controllers/controller.line.js
src/elements/element.line.js
src/helpers/helpers.curve.js
src/plugins/plugin.filler.js
test/fixtures/element.line/cubicInterpolationMode/monotone-horizontal.js [moved from test/fixtures/element.line/cubicInterpolationMode/monotone.js with 100% similarity]
test/fixtures/element.line/cubicInterpolationMode/monotone-horizontal.png [moved from test/fixtures/element.line/cubicInterpolationMode/monotone.png with 100% similarity]
test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.js [new file with mode: 0644]
test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.png [new file with mode: 0644]
types/helpers/helpers.curve.d.ts
types/index.esm.d.ts

index 0346da4264146720d8037d99efbd044c7e3025d2..3fca6a738cc234902500baad8ad0c63057db17e9 100644 (file)
@@ -102,7 +102,8 @@ export default class LineController extends DatasetController {
   }
 
   draw() {
-    this._cachedMeta.dataset.updateControlPoints(this.chart.chartArea);
+    const meta = this._cachedMeta;
+    meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);
     super.draw();
   }
 }
index 6cb13703eda2f294baa5f82333e7f88d465a6480..d2aff52c8bc98fb05b9d8c9ae3627ded2e96eb53 100644 (file)
@@ -257,12 +257,12 @@ export default class LineElement extends Element {
     }
   }
 
-  updateControlPoints(chartArea) {
+  updateControlPoints(chartArea, indexAxis) {
     const me = this;
     const options = me.options;
     if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !me._pointsUpdated) {
       const loop = options.spanGaps ? me._loop : me._fullLoop;
-      _updateBezierControlPoints(me._points, options, chartArea, loop);
+      _updateBezierControlPoints(me._points, options, chartArea, loop, indexAxis);
       me._pointsUpdated = true;
     }
   }
index 238a33c13c952f0800a57bc2277106c42bb73206..9c42c1dc6b172f6432dabd575664747517672959 100644 (file)
@@ -3,6 +3,7 @@ import {_isPointInArea} from './helpers.canvas';
 
 const EPSILON = Number.EPSILON || 1e-14;
 const getPoint = (points, i) => i < points.length && !points[i].skip && points[i];
+const getValueAxis = (indexAxis) => indexAxis === 'x' ? 'y' : 'x';
 
 export function splineCurve(firstPoint, middlePoint, afterPoint, t) {
   // Props to Rob Spencer at scaled innovation for his post on splining between points
@@ -71,9 +72,10 @@ function monotoneAdjust(points, deltaK, mK) {
   }
 }
 
-function monotoneCompute(points, mK) {
+function monotoneCompute(points, mK, indexAxis = 'x') {
+  const valueAxis = getValueAxis(indexAxis);
   const pointsLen = points.length;
-  let deltaX, pointBefore, pointCurrent;
+  let delta, pointBefore, pointCurrent;
   let pointAfter = getPoint(points, 0);
 
   for (let i = 0; i < pointsLen; ++i) {
@@ -84,16 +86,17 @@ function monotoneCompute(points, mK) {
       continue;
     }
 
-    const {x, y} = pointCurrent;
+    const iPixel = pointCurrent[indexAxis];
+    const vPixel = pointCurrent[valueAxis];
     if (pointBefore) {
-      deltaX = (x - pointBefore.x) / 3;
-      pointCurrent.cp1x = x - deltaX;
-      pointCurrent.cp1y = y - deltaX * mK[i];
+      delta = (iPixel - pointBefore[indexAxis]) / 3;
+      pointCurrent[`cp1${indexAxis}`] = iPixel - delta;
+      pointCurrent[`cp1${valueAxis}`] = vPixel - delta * mK[i];
     }
     if (pointAfter) {
-      deltaX = (pointAfter.x - x) / 3;
-      pointCurrent.cp2x = x + deltaX;
-      pointCurrent.cp2y = y + deltaX * mK[i];
+      delta = (pointAfter[indexAxis] - iPixel) / 3;
+      pointCurrent[`cp2${indexAxis}`] = iPixel + delta;
+      pointCurrent[`cp2${valueAxis}`] = vPixel + delta * mK[i];
     }
   }
 }
@@ -113,8 +116,10 @@ function monotoneCompute(points, mK) {
  * cp2x?: number,
  * cp2y?: number,
  * }[]} points
+ * @param {string} indexAxis
  */
-export function splineCurveMonotone(points) {
+export function splineCurveMonotone(points, indexAxis = 'x') {
+  const valueAxis = getValueAxis(indexAxis);
   const pointsLen = points.length;
   const deltaK = Array(pointsLen).fill(0);
   const mK = Array(pointsLen);
@@ -132,10 +137,10 @@ export function splineCurveMonotone(points) {
     }
 
     if (pointAfter) {
-      const slopeDeltaX = (pointAfter.x - pointCurrent.x);
+      const slopeDelta = pointAfter[indexAxis] - pointCurrent[indexAxis];
 
       // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
-      deltaK[i] = slopeDeltaX !== 0 ? (pointAfter.y - pointCurrent.y) / slopeDeltaX : 0;
+      deltaK[i] = slopeDelta !== 0 ? (pointAfter[valueAxis] - pointCurrent[valueAxis]) / slopeDelta : 0;
     }
     mK[i] = !pointBefore ? deltaK[i]
       : !pointAfter ? deltaK[i - 1]
@@ -145,7 +150,7 @@ export function splineCurveMonotone(points) {
 
   monotoneAdjust(points, deltaK, mK);
 
-  monotoneCompute(points, mK);
+  monotoneCompute(points, mK, indexAxis);
 }
 
 function capControlPoint(pt, min, max) {
@@ -177,7 +182,7 @@ function capBezierPoints(points, area) {
 /**
  * @private
  */
-export function _updateBezierControlPoints(points, options, area, loop) {
+export function _updateBezierControlPoints(points, options, area, loop, indexAxis) {
   let i, ilen, point, controlPoints;
 
   // Only consider points that are drawn in case the spanGaps option is used
@@ -186,7 +191,7 @@ export function _updateBezierControlPoints(points, options, area, loop) {
   }
 
   if (options.cubicInterpolationMode === 'monotone') {
-    splineCurveMonotone(points);
+    splineCurveMonotone(points, indexAxis);
   } else {
     let prev = loop ? points[points.length - 1] : points[0];
     for (i = 0, ilen = points.length; i < ilen; ++i) {
index 36467b33f7d6f2a43e895f39a94fc4cb0ee5a19a..b8d0fed4bbd084b8bf9426a97f108c9b3bf0b579 100644 (file)
@@ -575,7 +575,7 @@ export default {
         continue;
       }
 
-      source.line.updateControlPoints(area);
+      source.line.updateControlPoints(area, source.axis);
       if (draw) {
         drawfill(chart.ctx, source, area);
       }
diff --git a/test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.js b/test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.js
new file mode 100644 (file)
index 0000000..2debc62
--- /dev/null
@@ -0,0 +1,28 @@
+module.exports = {
+  config: {
+    type: 'line',
+    data: {
+      datasets: [
+        {
+          data: [{x: 10, y: 1}, {x: 0, y: 5}, {x: -10, y: 15}, {x: -5, y: 19}],
+          borderColor: 'red',
+          fill: false,
+          cubicInterpolationMode: 'monotone'
+        }
+      ]
+    },
+    options: {
+      indexAxis: 'y',
+      scales: {
+        x: {display: false, min: -15, max: 15},
+        y: {type: 'linear', display: false, min: 0, max: 20}
+      }
+    }
+  },
+  options: {
+    canvas: {
+      height: 256,
+      width: 512
+    }
+  }
+};
diff --git a/test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.png b/test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.png
new file mode 100644 (file)
index 0000000..51a80db
Binary files /dev/null and b/test/fixtures/element.line/cubicInterpolationMode/monotone-vertical.png differ
index 8182c5ab5ff3e61e3958e6726962b5c8bf038ea3..d845e1579193f8526893a176ffcd7aa31898a7dc 100644 (file)
@@ -31,4 +31,4 @@ export interface MonotoneSplinePoint extends SplinePoint {
  * between the dataset discrete points due to the interpolation.
  * @see https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
  */
-export function splineCurveMonotone(points: readonly MonotoneSplinePoint[]): void;
+export function splineCurveMonotone(points: readonly MonotoneSplinePoint[], indexAxis?: 'x' | 'y'): void;
index e55068b5ea19b96d952ab2cdbb3dbf112f2ad4b5..6f5d071cf9eb89b542986e516a0884230bd888b5 100644 (file)
@@ -1711,7 +1711,7 @@ export interface LineHoverOptions extends CommonHoverOptions {
 export interface LineElement<T extends LineProps = LineProps, O extends LineOptions = LineOptions>
   extends Element<T, O>,
     VisualElement {
-  updateControlPoints(chartArea: ChartArea): void;
+  updateControlPoints(chartArea: ChartArea, indexAxis?: 'x' | 'y'): void;
   points: Point[];
   readonly segments: Segment[];
   first(): Point | false;