Skip to content

Commit

Permalink
Add circular prop to arc element (#10405)
Browse files Browse the repository at this point in the history
* feat: add circular prop to arc element draw actions
* test: add test for arc element with circular:false prop
* feat: add circular prop to Arc element options
* docs: add decriptiption for new Polar area chart prop
* docs: fix circular prop description
* docs: add info about arc element circular prop to elements docs
* docs: move circular prop from general options to styling
  • Loading branch information
thabarbados committed Jul 30, 2022
1 parent 1fef75d commit f0be17c
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 38 deletions.
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`](#styling) | `boolean` | Yes | Yes | `true`

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

Expand All @@ -85,6 +86,7 @@ The style of each arc can be controlled with the following properties:
| `borderColor` | arc border color.
| `borderJoinStyle` | arc border join style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin).
| `borderWidth` | arc border width (in pixels).
| `circular` | By default the Arc is curved. If `circular: false` the Arc will be flat.

All these values, if `undefined`, fallback to the associated [`elements.arc.*`](../configuration/elements.md#arc-configuration) options.

Expand Down
1 change: 1 addition & 0 deletions docs/configuration/elements.md
Expand Up @@ -101,3 +101,4 @@ Namespace: `options.elements.arc`, global arc options: `Chart.defaults.elements.
| `borderColor` | [`Color`](/general/colors.md) | `'#fff'` | Arc stroke color.
| `borderJoinStyle` | `'round'`\|`'bevel'`\|`'miter'` | `'bevel'`\|`'round'` | Line join style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin). The default is `'round'` when `borderAlign` is `'inner'`
| `borderWidth`| `number` | `2` | Arc stroke width.
| `circular` | `boolean` | `true` | By default the Arc is curved. If `circular: false` the Arc will be flat
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 @@ -1746,6 +1746,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

0 comments on commit f0be17c

Please sign in to comment.