mirror of
https://github.com/chartjs/Chart.js.git
synced 2026-02-20 01:31:20 +01:00
Add selfJoin option for doughnut graphs (#12054)
Co-authored-by: Pierre Gueguen <gueguenpierre.pro@gmail.com>
This commit is contained in:
@@ -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 = {
|
||||
|
||||
6
src/types/index.d.ts
vendored
6
src/types/index.d.ts
vendored
@@ -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.
|
||||
*/
|
||||
|
||||
25
test/fixtures/controller.doughnut/selfJoin/doughnut.js
vendored
Normal file
25
test/fixtures/controller.doughnut/selfJoin/doughnut.js
vendored
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
test/fixtures/controller.doughnut/selfJoin/doughnut.png
vendored
Normal file
BIN
test/fixtures/controller.doughnut/selfJoin/doughnut.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
26
test/fixtures/controller.doughnut/selfJoin/pie.js
vendored
Normal file
26
test/fixtures/controller.doughnut/selfJoin/pie.js
vendored
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
test/fixtures/controller.doughnut/selfJoin/pie.png
vendored
Normal file
BIN
test/fixtures/controller.doughnut/selfJoin/pie.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
Reference in New Issue
Block a user