]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Add selfJoin option for doughnut graphs (#12054)
authorPierre <73789471+R2Jeu-prive@users.noreply.github.com>
Tue, 15 Apr 2025 13:32:13 +0000 (15:32 +0200)
committerGitHub <noreply@github.com>
Tue, 15 Apr 2025 13:32:13 +0000 (15:32 +0200)
Co-authored-by: Pierre Gueguen <gueguenpierre.pro@gmail.com>
src/elements/element.arc.ts
src/types/index.d.ts
test/fixtures/controller.doughnut/selfJoin/doughnut.js [new file with mode: 0644]
test/fixtures/controller.doughnut/selfJoin/doughnut.png [new file with mode: 0644]
test/fixtures/controller.doughnut/selfJoin/pie.js [new file with mode: 0644]
test/fixtures/controller.doughnut/selfJoin/pie.png [new file with mode: 0644]

index e2bd26f523b713730ded97a3f8f11dd5d7d0f662..42f41f045b0a7e43d32c967f7705ccf173d0e7e7 100644 (file)
@@ -1,9 +1,42 @@
 import Element from '../core/core.element.js';
 import {_angleBetween, getAngleFromPoint, TAU, HALF_PI, valueOrDefault} from '../helpers/index.js';
-import {PI, _isBetween, _limitValue} from '../helpers/helpers.math.js';
+import {PI, _angleDiff, _normalizeAngle, _isBetween, _limitValue} from '../helpers/helpers.math.js';
 import {_readValueToProps} from '../helpers/helpers.options.js';
 import type {ArcOptions, Point} from '../types/index.js';
 
+function clipSelf(ctx: CanvasRenderingContext2D, element: ArcElement, endAngle: number) {
+  const {startAngle, x, y, outerRadius, innerRadius, options} = element;
+  const {borderWidth, borderJoinStyle} = options;
+  const outerAngleClip = Math.min(borderWidth / outerRadius, _normalizeAngle(startAngle - endAngle));
+  ctx.beginPath();
+  ctx.arc(x, y, outerRadius - borderWidth / 2, startAngle + outerAngleClip / 2, endAngle - outerAngleClip / 2);
+
+  if (innerRadius > 0) {
+    const innerAngleClip = Math.min(borderWidth / innerRadius, _normalizeAngle(startAngle - endAngle));
+    ctx.arc(x, y, innerRadius + borderWidth / 2, endAngle - innerAngleClip / 2, startAngle + innerAngleClip / 2, true);
+  } else {
+    const clipWidth = Math.min(borderWidth / 2, outerRadius * _normalizeAngle(startAngle - endAngle));
+
+    if (borderJoinStyle === 'round') {
+      ctx.arc(x, y, clipWidth, endAngle - PI / 2, startAngle + PI / 2, true);
+    } else if (borderJoinStyle === 'bevel') {
+      const r = 2 * clipWidth * clipWidth;
+      const endX = -r * Math.cos(endAngle + PI / 2) + x;
+      const endY = -r * Math.sin(endAngle + PI / 2) + y;
+      const startX = r * Math.cos(startAngle + PI / 2) + x;
+      const startY = r * Math.sin(startAngle + PI / 2) + y;
+      ctx.lineTo(endX, endY);
+      ctx.lineTo(startX, startY);
+    }
+  }
+  ctx.closePath();
+
+  ctx.moveTo(0, 0);
+  ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
+
+  ctx.clip('evenodd');
+}
+
 
 function clipArc(ctx: CanvasRenderingContext2D, element: ArcElement, endAngle: number) {
   const {startAngle, pixelMargin, x, y, outerRadius, innerRadius} = element;
@@ -213,7 +246,7 @@ function drawBorder(
   circular: boolean,
 ) {
   const {fullCircles, startAngle, circumference, options} = element;
-  const {borderWidth, borderJoinStyle, borderDash, borderDashOffset} = options;
+  const {borderWidth, borderJoinStyle, borderDash, borderDashOffset, borderRadius} = options;
   const inner = options.borderAlign === 'inner';
 
   if (!borderWidth) {
@@ -246,6 +279,10 @@ function drawBorder(
     clipArc(ctx, element, endAngle);
   }
 
+  if (options.selfJoin && endAngle - startAngle >= PI && borderRadius === 0 && borderJoinStyle !== 'miter') {
+    clipSelf(ctx, element, endAngle);
+  }
+
   if (!fullCircles) {
     pathArc(ctx, element, offset, spacing, endAngle, circular);
     ctx.stroke();
@@ -276,6 +313,7 @@ export default class ArcElement extends Element<ArcProps, ArcOptions> {
     spacing: 0,
     angle: undefined,
     circular: true,
+    selfJoin: false,
   };
 
   static defaultRoutes = {
index 807fe820879d25b4e4cca9736ae8c785176114e0..175336ec793613efcb1399fbe246914be9387044 100644 (file)
@@ -1847,6 +1847,12 @@ export interface ArcBorderRadius {
 }
 
 export interface ArcOptions extends CommonElementOptions {
+  /**
+   * If true, Arc can take up 100% of a circular graph without any visual split or cut. This option doesn't support borderRadius and borderJoinStyle miter
+   * @default true
+   */
+  selfJoin: boolean;
+
   /**
    * Arc stroke alignment.
    */
diff --git a/test/fixtures/controller.doughnut/selfJoin/doughnut.js b/test/fixtures/controller.doughnut/selfJoin/doughnut.js
new file mode 100644 (file)
index 0000000..f29939c
--- /dev/null
@@ -0,0 +1,25 @@
+module.exports = {
+  config: {
+    type: 'doughnut',
+    data: {
+      labels: ['Red'],
+      datasets: [
+        {
+          // option in dataset
+          data: [100],
+          borderWidth: 15,
+          backgroundColor: '#FF0000',
+          borderColor: '#000000',
+          borderAlign: 'center',
+          selfJoin: true
+        }
+      ]
+    }
+  },
+  options: {
+    canvas: {
+      height: 256,
+      width: 512
+    }
+  }
+};
diff --git a/test/fixtures/controller.doughnut/selfJoin/doughnut.png b/test/fixtures/controller.doughnut/selfJoin/doughnut.png
new file mode 100644 (file)
index 0000000..af2d4b4
Binary files /dev/null and b/test/fixtures/controller.doughnut/selfJoin/doughnut.png differ
diff --git a/test/fixtures/controller.doughnut/selfJoin/pie.js b/test/fixtures/controller.doughnut/selfJoin/pie.js
new file mode 100644 (file)
index 0000000..d0187db
--- /dev/null
@@ -0,0 +1,26 @@
+module.exports = {
+  config: {
+    type: 'pie',
+    data: {
+      labels: ['Red'],
+      datasets: [
+        {
+          // option in dataset
+          data: [100],
+          borderWidth: 15,
+          backgroundColor: '#FF0000',
+          borderColor: '#000000',
+          borderAlign: 'center',
+          borderJoinStyle: 'round',
+          selfJoin: true
+        }
+      ]
+    }
+  },
+  options: {
+    canvas: {
+      height: 256,
+      width: 512
+    }
+  }
+};
diff --git a/test/fixtures/controller.doughnut/selfJoin/pie.png b/test/fixtures/controller.doughnut/selfJoin/pie.png
new file mode 100644 (file)
index 0000000..17a2e3b
Binary files /dev/null and b/test/fixtures/controller.doughnut/selfJoin/pie.png differ