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
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/charts/polar.md
Expand Up @@ -66,6 +66,7 @@ The following options can be included in a polar area chart dataset to configure
| [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`
| [`hoverBorderJoinStyle`](#interactions) | `'round'`\|`'bevel'`\|`'miter'` | Yes | Yes | `undefined`
| [`hoverBorderWidth`](#interactions) | `number` | Yes | Yes | `undefined`
| [`circular`](#general) | `boolean` | - | - | `true`
LeeLenaleee marked this conversation as resolved.
Show resolved Hide resolved

All these values, if `undefined`, fallback to the scopes described in [option resolution](../general/options)

Expand All @@ -74,6 +75,7 @@ All these values, if `undefined`, fallback to the scopes described in [option re
| Name | Description
| ---- | ----
| `clip` | How to clip relative to chartArea. Positive value allows overflow, negative value clips that many pixels inside chartArea. `0` = clip at chartArea. Clipping can also be configured per side: `clip: {left: 5, top: false, right: -2, bottom: 0}`
| `circular` | By default Arc is curve. If `circular: false` Arc will be flat.
LeeLenaleee marked this conversation as resolved.
Show resolved Hide resolved

### Styling

Expand Down
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