mirror of
https://github.com/chartjs/Chart.js.git
synced 2024-10-06 04:09:08 +02:00
fix: Always draw full arcs and borders for doughnut slices (#10806)
* test: Add a failing test for single-slice doughnut with offset * fix: Always draw full arcs and borders for doughnut slices Fixes #10801 * test: Update existing image
This commit is contained in:
parent
69175847ef
commit
89487501b6
@ -67,21 +67,18 @@ function rThetaToXY(r: number, theta: number, x: number, y: number) {
|
||||
|
||||
|
||||
/**
|
||||
* Path the arc, respecting the border radius
|
||||
*
|
||||
* 8 points of interest exist around the arc segment.
|
||||
* These points define the intersection of the arc edges and the corners.
|
||||
* Path the arc, respecting border radius by separating into left and right halves.
|
||||
*
|
||||
* Start End
|
||||
*
|
||||
* 1---------2 Outer
|
||||
* 1--->a--->2 Outer
|
||||
* / \
|
||||
* 8 3
|
||||
* | |
|
||||
* | |
|
||||
* 7 4
|
||||
* \ /
|
||||
* 6---------5 Inner
|
||||
* 6<---b<---5 Inner
|
||||
*/
|
||||
function pathArc(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
@ -129,8 +126,10 @@ function pathArc(
|
||||
ctx.beginPath();
|
||||
|
||||
if (circular) {
|
||||
// The first arc segment from point 1 to point 2
|
||||
ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerEndAdjustedAngle);
|
||||
// The first arc segments from point 1 to point a to point 2
|
||||
const outerMidAdjustedAngle = (outerStartAdjustedAngle + outerEndAdjustedAngle) / 2;
|
||||
ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerMidAdjustedAngle);
|
||||
ctx.arc(x, y, outerRadius, outerMidAdjustedAngle, outerEndAdjustedAngle);
|
||||
|
||||
// The corner segment from point 2 to point 3
|
||||
if (outerEnd > 0) {
|
||||
@ -148,8 +147,10 @@ function pathArc(
|
||||
ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI);
|
||||
}
|
||||
|
||||
// The inner arc from point 5 to point 6
|
||||
ctx.arc(x, y, innerRadius, endAngle - (innerEnd / innerRadius), startAngle + (innerStart / innerRadius), true);
|
||||
// The inner arc from point 5 to point b to point 6
|
||||
const innerMidAdjustedAngle = ((endAngle - (innerEnd / innerRadius)) + (startAngle + (innerStart / innerRadius))) / 2;
|
||||
ctx.arc(x, y, innerRadius, endAngle - (innerEnd / innerRadius), innerMidAdjustedAngle, true);
|
||||
ctx.arc(x, y, innerRadius, innerMidAdjustedAngle, startAngle + (innerStart / innerRadius), true);
|
||||
|
||||
// The corner segment from point 6 to point 7
|
||||
if (innerStart > 0) {
|
||||
@ -191,17 +192,12 @@ function drawArc(
|
||||
const {fullCircles, startAngle, circumference} = element;
|
||||
let endAngle = element.endAngle;
|
||||
if (fullCircles) {
|
||||
pathArc(ctx, element, offset, spacing, startAngle + TAU, circular);
|
||||
|
||||
pathArc(ctx, element, offset, spacing, endAngle, circular);
|
||||
for (let i = 0; i < fullCircles; ++i) {
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
if (!isNaN(circumference)) {
|
||||
endAngle = startAngle + circumference % TAU;
|
||||
if (circumference % TAU === 0) {
|
||||
endAngle += TAU;
|
||||
}
|
||||
endAngle = startAngle + (circumference % TAU || TAU);
|
||||
}
|
||||
}
|
||||
pathArc(ctx, element, offset, spacing, endAngle, circular);
|
||||
@ -209,39 +205,14 @@ function drawArc(
|
||||
return endAngle;
|
||||
}
|
||||
|
||||
function drawFullCircleBorders(ctx: CanvasRenderingContext2D, element: ArcElement, inner: boolean) {
|
||||
const {x, y, startAngle, pixelMargin, fullCircles} = element;
|
||||
const outerRadius = Math.max(element.outerRadius - pixelMargin, 0);
|
||||
const innerRadius = element.innerRadius + pixelMargin;
|
||||
|
||||
let i;
|
||||
|
||||
if (inner) {
|
||||
clipArc(ctx, element, startAngle + TAU);
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, innerRadius, startAngle + TAU, startAngle, true);
|
||||
for (i = 0; i < fullCircles; ++i) {
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, outerRadius, startAngle, startAngle + TAU);
|
||||
for (i = 0; i < fullCircles; ++i) {
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function drawBorder(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
element: ArcElement,
|
||||
offset: number,
|
||||
spacing: number,
|
||||
endAngle: number,
|
||||
circular: boolean,
|
||||
) {
|
||||
const {options} = element;
|
||||
const {fullCircles, startAngle, circumference, options} = element;
|
||||
const {borderWidth, borderJoinStyle} = options;
|
||||
const inner = options.borderAlign === 'inner';
|
||||
|
||||
@ -257,16 +228,25 @@ function drawBorder(
|
||||
ctx.lineJoin = borderJoinStyle || 'bevel';
|
||||
}
|
||||
|
||||
if (element.fullCircles) {
|
||||
drawFullCircleBorders(ctx, element, inner);
|
||||
let endAngle = element.endAngle;
|
||||
if (fullCircles) {
|
||||
pathArc(ctx, element, offset, spacing, endAngle, circular);
|
||||
for (let i = 0; i < fullCircles; ++i) {
|
||||
ctx.stroke();
|
||||
}
|
||||
if (!isNaN(circumference)) {
|
||||
endAngle = startAngle + (circumference % TAU || TAU);
|
||||
}
|
||||
}
|
||||
|
||||
if (inner) {
|
||||
clipArc(ctx, element, endAngle);
|
||||
}
|
||||
|
||||
pathArc(ctx, element, offset, spacing, endAngle, circular);
|
||||
ctx.stroke();
|
||||
if (!fullCircles) {
|
||||
pathArc(ctx, element, offset, spacing, endAngle, circular);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
export interface ArcProps extends Point {
|
||||
@ -385,8 +365,8 @@ export default class ArcElement extends Element<ArcProps, ArcOptions> {
|
||||
ctx.fillStyle = options.backgroundColor;
|
||||
ctx.strokeStyle = options.borderColor;
|
||||
|
||||
const endAngle = drawArc(ctx, this, radiusOffset, spacing, circular);
|
||||
drawBorder(ctx, this, radiusOffset, spacing, endAngle, circular);
|
||||
drawArc(ctx, this, radiusOffset, spacing, circular);
|
||||
drawBorder(ctx, this, radiusOffset, spacing, circular);
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 11 KiB |
16
test/fixtures/controller.doughnut/single-slice-offset.js
vendored
Normal file
16
test/fixtures/controller.doughnut/single-slice-offset.js
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
config: {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: ['A'],
|
||||
datasets: [{
|
||||
data: [385],
|
||||
backgroundColor: 'rgba(0,0,0,0.3)',
|
||||
borderColor: 'rgba(0,0,0,0.5)',
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
offset: 20
|
||||
}
|
||||
}
|
||||
};
|
BIN
test/fixtures/controller.doughnut/single-slice-offset.png
vendored
Normal file
BIN
test/fixtures/controller.doughnut/single-slice-offset.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
Loading…
Reference in New Issue
Block a user