Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add circular prop to arc element #10405

Merged
merged 7 commits into from Jul 30, 2022
Merged
89 changes: 51 additions & 38 deletions src/elements/element.arc.js
Expand Up @@ -93,7 +93,7 @@ function rThetaToXY(r, theta, x, y) {
* @param {CanvasRenderingContext2D} ctx
* @param {ArcElement} element
*/
function pathArc(ctx, element, offset, spacing, end) {
function pathArc(ctx, element, offset, spacing, end, circular) {
const {x, y, startAngle: start, pixelMargin, innerRadius: innerR} = element;

const outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0);
Expand Down Expand Up @@ -131,52 +131,64 @@ function pathArc(ctx, element, offset, spacing, end) {

ctx.beginPath();

// The first arc segment from point 1 to point 2
ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerEndAdjustedAngle);
if (circular) {
// The first arc segment from point 1 to point 2
ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerEndAdjustedAngle);

// The corner segment from point 2 to point 3
if (outerEnd > 0) {
const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI);
}
// The corner segment from point 2 to point 3
if (outerEnd > 0) {
const pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI);
}

// The line from point 3 to point 4
const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
ctx.lineTo(p4.x, p4.y);
// The line from point 3 to point 4
const p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
ctx.lineTo(p4.x, p4.y);

// The corner segment from point 4 to point 5
if (innerEnd > 0) {
const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
ctx.arc(pCenter.x, pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI);
}
// The corner segment from point 4 to point 5
if (innerEnd > 0) {
const pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
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 6
ctx.arc(x, y, innerRadius, endAngle - (innerEnd / innerRadius), startAngle + (innerStart / innerRadius), true);

// The corner segment from point 6 to point 7
if (innerStart > 0) {
const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI);
}
// The corner segment from point 6 to point 7
if (innerStart > 0) {
const pCenter = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
ctx.arc(pCenter.x, pCenter.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI);
}

// The line from point 7 to point 8
const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
ctx.lineTo(p8.x, p8.y);
// The line from point 7 to point 8
const p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
ctx.lineTo(p8.x, p8.y);

// The corner segment from point 8 to point 1
if (outerStart > 0) {
const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle);
// The corner segment from point 8 to point 1
if (outerStart > 0) {
const pCenter = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
ctx.arc(pCenter.x, pCenter.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle);
}
} else {
ctx.moveTo(x, y);

const outerStartX = Math.cos(outerStartAdjustedAngle) * outerRadius + x;
const outerStartY = Math.sin(outerStartAdjustedAngle) * outerRadius + y;
ctx.lineTo(outerStartX, outerStartY);

const outerEndX = Math.cos(outerEndAdjustedAngle) * outerRadius + x;
const outerEndY = Math.sin(outerEndAdjustedAngle) * outerRadius + y;
ctx.lineTo(outerEndX, outerEndY);
}

ctx.closePath();
}

function drawArc(ctx, element, offset, spacing) {
function drawArc(ctx, element, offset, spacing, circular) {
const {fullCircles, startAngle, circumference} = element;
let endAngle = element.endAngle;
if (fullCircles) {
pathArc(ctx, element, offset, spacing, startAngle + TAU);
pathArc(ctx, element, offset, spacing, startAngle + TAU, circular);

for (let i = 0; i < fullCircles; ++i) {
ctx.fill();
Expand All @@ -189,8 +201,7 @@ function drawArc(ctx, element, offset, spacing) {
}
}
}

pathArc(ctx, element, offset, spacing, endAngle);
pathArc(ctx, element, offset, spacing, endAngle, circular);
ctx.fill();
return endAngle;
}
Expand Down Expand Up @@ -219,7 +230,7 @@ function drawFullCircleBorders(ctx, element, inner) {
}
}

function drawBorder(ctx, element, offset, spacing, endAngle) {
function drawBorder(ctx, element, offset, spacing, endAngle, circular) {
const {options} = element;
const {borderWidth, borderJoinStyle} = options;
const inner = options.borderAlign === 'inner';
Expand All @@ -244,7 +255,7 @@ function drawBorder(ctx, element, offset, spacing, endAngle) {
clipArc(ctx, element, endAngle);
}

pathArc(ctx, element, offset, spacing, endAngle);
pathArc(ctx, element, offset, spacing, endAngle, circular);
ctx.stroke();
}

Expand Down Expand Up @@ -323,6 +334,7 @@ export default class ArcElement extends Element {
const {options, circumference} = this;
const offset = (options.offset || 0) / 2;
const spacing = (options.spacing || 0) / 2;
const circular = options.circular;
this.pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0;
this.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0;

Expand All @@ -345,8 +357,8 @@ export default class ArcElement extends Element {
ctx.fillStyle = options.backgroundColor;
ctx.strokeStyle = options.borderColor;

const endAngle = drawArc(ctx, this, radiusOffset, spacing);
drawBorder(ctx, this, radiusOffset, spacing, endAngle);
const endAngle = drawArc(ctx, this, radiusOffset, spacing, circular);
drawBorder(ctx, this, radiusOffset, spacing, endAngle, circular);

ctx.restore();
}
Expand All @@ -366,6 +378,7 @@ ArcElement.defaults = {
offset: 0,
spacing: 0,
angle: undefined,
circular: true,
};

/**
Expand Down
32 changes: 32 additions & 0 deletions test/specs/element.arc.tests.js
Expand Up @@ -218,4 +218,36 @@ describe('Arc element tests', function() {

expect(ctx.getCalls().length).toBe(0);
});

it('should draw when circular: false', function() {
var arc = new Chart.elements.ArcElement({
startAngle: 0,
endAngle: Math.PI * 2,
x: 2,
y: 2,
innerRadius: 0,
outerRadius: 2,
options: {
spacing: 0,
offset: 0,
scales: {
r: {
grid: {
circular: false,
},
},
},
elements: {
arc: {
circular: false
},
},
}
});

var ctx = window.createMockContext();
arc.draw(ctx);

expect(ctx.getCalls().length).toBeGreaterThan(0);
});
});
6 changes: 6 additions & 0 deletions types/index.esm.d.ts
Expand Up @@ -1740,6 +1740,12 @@ export interface ArcOptions extends CommonElementOptions {
* Arc offset (in pixels).
*/
offset: number;

/**
* If false, Arc will be flat.
* @default true
*/
circular: boolean;
}

export interface ArcHoverOptions extends CommonHoverOptions {
Expand Down