They are also registered under two aliases in the `Chart` core. Other than their different default value, and different alias, they are exactly the same.
-import { useEffect } from 'react';
-
-export const ExampleChart = () => {
+import { useEffect, useRef } from 'react';
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+<Tabs
+ defaultValue='doughnut'
+ values={[
+ {label: 'Doughnut', value: 'doughnut' },
+ {label: 'Pie', value: 'pie' },
+ ]}
+>
+<TabItem value="doughnut">
+
+```jsx live
+function example() {
+ const canvas = useRef(null);
useEffect(() => {
const cfg = {
type: 'doughnut',
'rgb(255, 99, 132)',
'rgb(54, 162, 235)',
'rgb(255, 205, 86)'
- ]
+ ],
+ hoverOffset: 4
}]
}
};
- new Chart(document.getElementById('chartjs-0').getContext('2d'), cfg);
+ new Chart(canvas.current.getContext('2d'), cfg);
});
- return <div className="chartjs-wrapper"><canvas id="chartjs-0" className="chartjs"></canvas></div>;
+ return <div className="chartjs-wrapper"><canvas ref={canvas} className="chartjs"></canvas></div>;
}
+```
-<ExampleChart/>
+</TabItem>
-## Example Usage
+<TabItem value="pie">
-```javascript
-// For a pie chart
-var myPieChart = new Chart(ctx, {
- type: 'pie',
- data: data,
- options: options
-});
+```jsx live
+function example() {
+ const canvas = useRef(null);
+ useEffect(() => {
+ const cfg = {
+ type: 'pie',
+ data: {
+ labels: [
+ 'Red',
+ 'Blue',
+ 'Yellow'
+ ],
+ datasets: [{
+ label: 'My First Dataset',
+ data: [300, 50, 100],
+ backgroundColor: [
+ 'rgb(255, 99, 132)',
+ 'rgb(54, 162, 235)',
+ 'rgb(255, 205, 86)'
+ ],
+ hoverOffset: 4
+ }]
+ }
+ };
+ new Chart(canvas.current.getContext('2d'), cfg);
+ });
+ return <div className="chartjs-wrapper"><canvas ref={canvas} className="chartjs"></canvas></div>;
+}
```
-```javascript
-// And for a doughnut chart
-var myDoughnutChart = new Chart(ctx, {
- type: 'doughnut',
- data: data,
- options: options
-});
-```
+</TabItem>
+</Tabs>
## Dataset Properties
| [`hoverBackgroundColor`](#interations) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`
| [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | Yes | Yes | `undefined`
| [`hoverBorderWidth`](#interactions) | `number` | Yes | Yes | `undefined`
+| [`hoverOffset`](#interactions) | `number` | Yes | Yes | `0`
+| [`offset`](#styling) | `number` | Yes | Yes | `0`
| [`weight`](#styling) | `number` | - | - | `1`
### General
| `backgroundColor` | arc background color.
| `borderColor` | arc border color.
| `borderWidth` | arc border width (in pixels).
+| `offset` | arc offset (in pixels).
| `weight` | The relative thickness of the dataset. Providing a value for weight will cause the pie or doughnut dataset to be drawn with a thickness relative to the sum of all the dataset weight values.
All these values, if `undefined`, fallback to the associated [`elements.arc.*`](../configuration/elements.md#arc-configuration) options.
| `hoverBackgroundColor` | arc background color when hovered.
| `hoverBorderColor` | arc border color when hovered.
| `hoverBorderWidth` | arc border width when hovered (in pixels).
+| `hoverOffset` | arc offset when hovered (in pixels).
All these values, if `undefined`, fallback to the associated [`elements.arc.*`](../configuration/elements.md#arc-configuration) options.
const cutout = options.cutoutPercentage / 100 || 0;
const chartWeight = me._getRingWeight(me.index);
const {ratioX, ratioY, offsetX, offsetY} = getRatioAndOffset(options.rotation, options.circumference, cutout);
- const borderWidth = me.getMaxBorderWidth();
- const maxWidth = (chartArea.right - chartArea.left - borderWidth) / ratioX;
- const maxHeight = (chartArea.bottom - chartArea.top - borderWidth) / ratioY;
+ const spacing = me.getMaxBorderWidth() + me.getMaxOffset(arcs);
+ const maxWidth = (chartArea.right - chartArea.left - spacing) / ratioX;
+ const maxHeight = (chartArea.bottom - chartArea.top - spacing) / ratioY;
const outerRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
const innerRadius = Math.max(outerRadius * cutout, 0);
const radiusLength = (outerRadius - innerRadius) / me._getVisibleDatasetWeightTotal();
return max;
}
+ getMaxOffset(arcs) {
+ let max = 0;
+
+ for (let i = 0, ilen = arcs.length; i < ilen; ++i) {
+ const options = this.resolveDataElementOptions(i);
+ max = Math.max(max, options.offset || 0, options.hoverOffset || 0);
+ }
+ return max;
+ }
+
/**
* Get radius length offset of the dataset in relation to the visible datasets weights. This allows determining the inner and outer radius correctly
* @private
'borderColor',
'borderWidth',
'borderAlign',
- 'hoverBackgroundColor',
- 'hoverBorderColor',
- 'hoverBorderWidth',
+ 'offset'
],
animation: {
numbers: {
type: 'number',
- properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y']
+ properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth']
},
// Boolean - Whether we animate the rotation of the Doughnut
animateRotate: true,
const TAU = Math.PI * 2;
-function clipArc(ctx, model) {
- const {startAngle, endAngle, pixelMargin, x, y} = model;
- let angleMargin = pixelMargin / model.outerRadius;
+function clipArc(ctx, element) {
+ const {startAngle, endAngle, pixelMargin, x, y, outerRadius, innerRadius} = element;
+ let angleMargin = pixelMargin / outerRadius;
// Draw an inner border by cliping the arc and drawing a double-width border
// Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
ctx.beginPath();
- ctx.arc(x, y, model.outerRadius, startAngle - angleMargin, endAngle + angleMargin);
- if (model.innerRadius > pixelMargin) {
- angleMargin = pixelMargin / model.innerRadius;
- ctx.arc(x, y, model.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true);
+ ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);
+ if (innerRadius > pixelMargin) {
+ angleMargin = pixelMargin / innerRadius;
+ ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);
} else {
ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2);
}
}
-function pathArc(ctx, model) {
+function pathArc(ctx, element) {
+ const {x, y, startAngle, endAngle, pixelMargin} = element;
+ const outerRadius = Math.max(element.outerRadius - pixelMargin, 0);
+ const innerRadius = element.innerRadius + pixelMargin;
+
ctx.beginPath();
- ctx.arc(model.x, model.y, model.outerRadius, model.startAngle, model.endAngle);
- ctx.arc(model.x, model.y, model.innerRadius, model.endAngle, model.startAngle, true);
+ ctx.arc(x, y, outerRadius, startAngle, endAngle);
+ ctx.arc(x, y, innerRadius, endAngle, startAngle, true);
ctx.closePath();
}
-function drawArc(ctx, model, circumference) {
- if (model.fullCircles) {
- model.endAngle = model.startAngle + TAU;
+function drawArc(ctx, element) {
+ if (element.fullCircles) {
+ element.endAngle = element.startAngle + TAU;
- pathArc(ctx, model);
+ pathArc(ctx, element);
- for (let i = 0; i < model.fullCircles; ++i) {
+ for (let i = 0; i < element.fullCircles; ++i) {
ctx.fill();
}
- model.endAngle = model.startAngle + circumference % TAU;
+ element.endAngle = element.startAngle + element.circumference % TAU;
}
- pathArc(ctx, model);
+ pathArc(ctx, element);
ctx.fill();
}
-function drawFullCircleBorders(ctx, element, model, inner) {
- const endAngle = model.endAngle;
+function drawFullCircleBorders(ctx, element, inner) {
+ const {x, y, startAngle, endAngle, pixelMargin} = element;
+ const outerRadius = Math.max(element.outerRadius - pixelMargin, 0);
+ const innerRadius = element.innerRadius + pixelMargin;
+
let i;
if (inner) {
- model.endAngle = model.startAngle + TAU;
- clipArc(ctx, model);
- model.endAngle = endAngle;
- if (model.endAngle === model.startAngle && model.fullCircles) {
- model.endAngle += TAU;
- model.fullCircles--;
+ element.endAngle = element.startAngle + TAU;
+ clipArc(ctx, element);
+ element.endAngle = endAngle;
+ if (element.endAngle === element.startAngle) {
+ element.endAngle += TAU;
+ element.fullCircles--;
}
}
ctx.beginPath();
- ctx.arc(model.x, model.y, model.innerRadius, model.startAngle + TAU, model.startAngle, true);
- for (i = 0; i < model.fullCircles; ++i) {
+ ctx.arc(x, y, innerRadius, startAngle + TAU, startAngle, true);
+ for (i = 0; i < element.fullCircles; ++i) {
ctx.stroke();
}
ctx.beginPath();
- ctx.arc(model.x, model.y, element.outerRadius, model.startAngle, model.startAngle + TAU);
- for (i = 0; i < model.fullCircles; ++i) {
+ ctx.arc(x, y, outerRadius, startAngle, startAngle + TAU);
+ for (i = 0; i < element.fullCircles; ++i) {
ctx.stroke();
}
}
-function drawBorder(ctx, element, model) {
- const options = element.options;
+function drawBorder(ctx, element) {
+ const {x, y, startAngle, endAngle, pixelMargin, options} = element;
+ const outerRadius = element.outerRadius;
+ const innerRadius = element.innerRadius + pixelMargin;
const inner = options.borderAlign === 'inner';
+ if (!options.borderWidth) {
+ return;
+ }
+
if (inner) {
ctx.lineWidth = options.borderWidth * 2;
ctx.lineJoin = 'round';
ctx.lineJoin = 'bevel';
}
- if (model.fullCircles) {
- drawFullCircleBorders(ctx, element, model, inner);
+ if (element.fullCircles) {
+ drawFullCircleBorders(ctx, element, inner);
}
if (inner) {
- clipArc(ctx, model);
+ clipArc(ctx, element);
}
ctx.beginPath();
- ctx.arc(model.x, model.y, element.outerRadius, model.startAngle, model.endAngle);
- ctx.arc(model.x, model.y, model.innerRadius, model.endAngle, model.startAngle, true);
+ ctx.arc(x, y, outerRadius, startAngle, endAngle);
+ ctx.arc(x, y, innerRadius, endAngle, startAngle, true);
ctx.closePath();
ctx.stroke();
}
this.endAngle = undefined;
this.innerRadius = undefined;
this.outerRadius = undefined;
+ this.pixelMargin = 0;
+ this.fullCircles = 0;
if (cfg) {
Object.assign(this, cfg);
draw(ctx) {
const me = this;
const options = me.options;
- const pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0;
- const model = {
- x: me.x,
- y: me.y,
- innerRadius: me.innerRadius,
- outerRadius: Math.max(me.outerRadius - pixelMargin, 0),
- pixelMargin,
- startAngle: me.startAngle,
- endAngle: me.endAngle,
- fullCircles: Math.floor(me.circumference / TAU)
- };
+ const offset = options.offset || 0;
+ me.pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0;
+ me.fullCircles = Math.floor(me.circumference / TAU);
if (me.circumference === 0) {
return;
ctx.save();
+ if (offset && me.circumference < TAU) {
+ const halfAngle = (me.startAngle + me.endAngle) / 2;
+ ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset);
+ }
+
ctx.fillStyle = options.backgroundColor;
ctx.strokeStyle = options.borderColor;
- drawArc(ctx, model, me.circumference);
-
- if (options.borderWidth) {
- drawBorder(ctx, me, model);
- }
+ drawArc(ctx, me);
+ drawBorder(ctx, me);
ctx.restore();
}
Arc.defaults = {
borderAlign: 'center',
borderColor: '#fff',
- borderWidth: 2
+ borderWidth: 2,
+ offset: 0
};
/**