]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Fix inRange for full circle arc (#9871)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Wed, 17 Nov 2021 22:10:19 +0000 (00:10 +0200)
committerGitHub <noreply@github.com>
Wed, 17 Nov 2021 22:10:19 +0000 (00:10 +0200)
* Update misleading sample comment

* Fix inRange for full circle arc

src/elements/element.arc.js
src/elements/element.bar.js
src/helpers/helpers.math.js
src/helpers/helpers.segment.js
src/plugins/plugin.filler.js
src/plugins/plugin.legend.js
test/specs/element.arc.tests.js

index 5c2182fb88e63e2d2346f457c948b3a6bdbf217d..66b5167711587728cdddef3df6a3c61cec48c905 100644 (file)
@@ -1,6 +1,6 @@
 import Element from '../core/core.element';
-import {_angleBetween, getAngleFromPoint, TAU, HALF_PI} from '../helpers/index';
-import {PI, _limitValue} from '../helpers/helpers.math';
+import {_angleBetween, getAngleFromPoint, TAU, HALF_PI, valueOrDefault} from '../helpers/index';
+import {PI, _isBetween, _limitValue} from '../helpers/helpers.math';
 import {_readValueToProps} from '../helpers/helpers.options';
 
 function clipArc(ctx, element, endAngle) {
@@ -282,8 +282,9 @@ export default class ArcElement extends Element {
       'circumference'
     ], useFinalPosition);
     const rAdjust = this.options.spacing / 2;
-    const betweenAngles = circumference >= TAU || _angleBetween(angle, startAngle, endAngle);
-    const withinRadius = (distance >= innerRadius + rAdjust && distance <= outerRadius + rAdjust);
+    const _circumference = valueOrDefault(circumference, endAngle - startAngle);
+    const betweenAngles = _circumference >= TAU || _angleBetween(angle, startAngle, endAngle);
+    const withinRadius = _isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust);
 
     return (betweenAngles && withinRadius);
   }
index 41a609b75e4dc0f14b7dfb435655e1d360e20982..3805671a2fa61b520e35f03ed5b8673dac2f2ce8 100644 (file)
@@ -1,5 +1,5 @@
 import Element from '../core/core.element';
-import {isObject, _limitValue} from '../helpers';
+import {isObject, _isBetween, _limitValue} from '../helpers';
 import {addRoundedRectPath} from '../helpers/helpers.canvas';
 import {toTRBL, toTRBLCorners} from '../helpers/helpers.options';
 
@@ -105,8 +105,8 @@ function inRange(bar, x, y, useFinalPosition) {
   const bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
 
   return bounds
-               && (skipX || x >= bounds.left && x <= bounds.right)
-               && (skipY || y >= bounds.top && y <= bounds.bottom);
+               && (skipX || _isBetween(x, bounds.left, bounds.right))
+               && (skipY || _isBetween(y, bounds.top, bounds.bottom));
 }
 
 function hasRadius(radius) {
index fea406c0fc67b38de0d6803bcf53f0c23754a77e..1b1e4ff53552abb9180101e9ef4d40a452a1011c 100644 (file)
@@ -173,6 +173,21 @@ export function _limitValue(value, min, max) {
   return Math.max(min, Math.min(max, value));
 }
 
+/**
+ * @param {number} value
+ * @private
+ */
 export function _int16Range(value) {
   return _limitValue(value, -32768, 32767);
 }
+
+/**
+ * @param {number} value
+ * @param {number} start
+ * @param {number} end
+ * @param {number} [epsilon]
+ * @private
+ */
+export function _isBetween(value, start, end, epsilon = 1e-6) {
+  return value >= Math.min(start, end) - epsilon && value <= Math.max(start, end) + epsilon;
+}
index 3bbfebb0dfa4d11f84a4fb89b44a881499c4f454..13e4e28d9c9113c1f764adf54fc04a0615392cf1 100644 (file)
@@ -1,4 +1,4 @@
-import {_angleBetween, _angleDiff, _normalizeAngle} from './helpers.math';
+import {_angleBetween, _angleDiff, _isBetween, _normalizeAngle} from './helpers.math';
 import {createContext} from './helpers.options';
 
 /**
@@ -16,7 +16,7 @@ function propertyFn(property) {
     };
   }
   return {
-    between: (n, s, e) => n >= Math.min(s, e) && n <= Math.max(e, s),
+    between: _isBetween,
     compare: (a, b) => a - b,
     normalize: x => x
   };
index 28a1df2ee89f2f1256cb090a3931dfc97b34decd..8650248cfb4536d2fa920c2905a7e872fbe0035d 100644 (file)
@@ -8,7 +8,7 @@ import LineElement from '../elements/element.line';
 import {_boundSegment, _boundSegments} from '../helpers/helpers.segment';
 import {clipArea, unclipArea} from '../helpers/helpers.canvas';
 import {isArray, isFinite, isObject, valueOrDefault} from '../helpers/helpers.core';
-import {TAU, _normalizeAngle} from '../helpers/helpers.math';
+import {TAU, _isBetween, _normalizeAngle} from '../helpers/helpers.math';
 
 /**
  * @typedef { import('../core/core.controller').default } Chart
@@ -293,7 +293,7 @@ function findPoint(line, sourcePoint, property) {
     const segment = segments[i];
     const firstValue = linePoints[segment.start][property];
     const lastValue = linePoints[segment.end][property];
-    if (pointValue >= firstValue && pointValue <= lastValue) {
+    if (_isBetween(pointValue, firstValue, lastValue)) {
       first = pointValue === firstValue;
       last = pointValue === lastValue;
       break;
index ccd31800a67c217ed864a36241765bdfabee66a2..d0bcb82761c37be984ea63a0edee845f6d828e48 100644 (file)
@@ -5,7 +5,7 @@ import {addRoundedRectPath, drawPoint, renderText} from '../helpers/helpers.canv
 import {
   callback as call, valueOrDefault, toFont,
   toPadding, getRtlAdapter, overrideTextDirection, restoreTextDirection,
-  clipArea, unclipArea
+  clipArea, unclipArea, _isBetween
 } from '../helpers/index';
 import {_toLeftRightCenter, _alignStartEnd, _textX} from '../helpers/helpers.extras';
 import {toTRBLCorners} from '../helpers/helpers.options';
@@ -493,13 +493,15 @@ export class Legend extends Element {
   _getLegendItemAt(x, y) {
     let i, hitBox, lh;
 
-    if (x >= this.left && x <= this.right && y >= this.top && y <= this.bottom) {
+    if (_isBetween(x, this.left, this.right)
+      && _isBetween(y, this.top, this.bottom)) {
       // See if we are touching one of the dataset boxes
       lh = this.legendHitBoxes;
       for (i = 0; i < lh.length; ++i) {
         hitBox = lh[i];
 
-        if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
+        if (_isBetween(x, hitBox.left, hitBox.left + hitBox.width)
+          && _isBetween(y, hitBox.top, hitBox.top + hitBox.height)) {
           // Touching an element
           return this.legendItems[i];
         }
index 23380aa250dce5bb0660f79c5ac2bff6ef970e5d..e2ec0788b4b93fe2fcd550528d0d9f45219ea252 100644 (file)
@@ -23,6 +23,39 @@ describe('Arc element tests', function() {
     expect(arc.inRange(-1.0 * Math.sqrt(7), Math.sqrt(7))).toBe(false);
   });
 
+  it ('should determine if in range when full circle', function() {
+    // Mock out the arc as if the controller put it there
+    var arc = new Chart.elements.ArcElement({
+      startAngle: 0,
+      endAngle: Math.PI * 2,
+      x: 0,
+      y: 0,
+      innerRadius: 5,
+      outerRadius: 10,
+      options: {
+        spacing: 0,
+        offset: 0,
+      }
+    });
+
+    for (const radius of [5, 7.5, 10]) {
+      for (let angle = 0; angle <= 360; angle += 22.5) {
+        const rad = angle / 180 * Math.PI;
+        const x = Math.sin(rad) * radius;
+        const y = Math.cos(rad) * radius;
+        expect(arc.inRange(x, y)).withContext(`radius: ${radius}, angle: ${angle}`).toBeTrue();
+      }
+    }
+    for (const radius of [4, 11]) {
+      for (let angle = 0; angle <= 360; angle += 22.5) {
+        const rad = angle / 180 * Math.PI;
+        const x = Math.sin(rad) * radius;
+        const y = Math.cos(rad) * radius;
+        expect(arc.inRange(x, y)).withContext(`radius: ${radius}, angle: ${angle}`).toBeFalse();
+      }
+    }
+  });
+
   it ('should include spacing for in range check', function() {
     // Mock out the arc as if the controller put it there
     var arc = new Chart.elements.ArcElement({