Skip to content

Commit c3e093c

Browse files
committed
added datasetSpacing feature
1 parent ab79c3b commit c3e093c

7 files changed

Lines changed: 203 additions & 4 deletions

File tree

docs/charts/doughnut.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ The style of each arc can be controlled with the following properties:
149149
| `borderJoinStyle` | arc border join style. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin).
150150
| `borderWidth` | arc border width (in pixels).
151151
| `offset` | arc offset (in pixels).
152-
| `spacing` | Fixed arc offset (in pixels). Similar to `offset` but applies to all arcs.
152+
| `spacing` | Fixed arc (slice) offset (in pixels). Similar to `offset` but applies to all slices.
153+
| `datasetSpacing` | Fixed spacing between datasets (in pixels). This property only applies to multi-dataset doughnut/pie charts. It adjusts the spacing between concentric rings of data, allowing better visual separation between datasets.
153154
| `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.
154155

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

src/controllers/controller.doughnut.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,16 @@ export default class DoughnutController extends DatasetController {
234234

235235
meta.total = this.calculateTotal();
236236

237-
this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index);
238-
this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0);
237+
// For index 0, 0 spacing
238+
// For index 1, 1 spacing
239+
// For index 2, 2 spacings (account for the spacing for previous datasets)
240+
// These are triangular numbers, so N*(N+1)/2 are the count of spacings we need to add.
241+
const datasetSpacing = this.index > 0 && this.options.datasetSpacing
242+
? (this.options.datasetSpacing || 0) * (this.index * (this.index + 1) / 2)
243+
: 0;
244+
245+
this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index) - datasetSpacing;
246+
this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight - datasetSpacing, 0);
239247

240248
this.updateElements(arcs, 0, arcs.length, mode);
241249
}

src/types/index.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,11 +336,17 @@ export interface DoughnutControllerChartOptions {
336336
rotation: number;
337337

338338
/**
339-
* Spacing between the arcs
339+
* Spacing between the arcs (slices)
340340
* @default 0
341341
*/
342342
spacing: number;
343343

344+
/**
345+
* Spacing between the dataSets
346+
* @default 0
347+
*/
348+
datasetSpacing: number;
349+
344350
/**
345351
* Geometry used to apply arc spacing.
346352
* - `proportional`: legacy behavior (default for polarArea).
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
{
2+
"config": {
3+
"type": "doughnut",
4+
"data": {
5+
"datasets": [
6+
{
7+
"label": "Dataset 1",
8+
"data": [
9+
10,
10+
20,
11+
40
12+
],
13+
"backgroundColor": [
14+
"rgba(255, 99, 132, 0.8)",
15+
"rgba(54, 162, 235, 0.8)",
16+
"rgba(255, 206, 86, 0.8)"
17+
],
18+
"borderWidth": 0,
19+
"weight": 1
20+
},
21+
{
22+
"label": "Dataset 2",
23+
"data": [
24+
15,
25+
25,
26+
35
27+
],
28+
"backgroundColor": [
29+
"rgba(75, 192, 192, 0.8)",
30+
"rgba(153, 102, 255, 0.8)",
31+
"rgba(255, 159, 64, 0.8)"
32+
],
33+
"borderWidth": 0,
34+
"weight": 1
35+
},
36+
{
37+
"label": "Dataset 3",
38+
"data": [
39+
20,
40+
30,
41+
30
42+
],
43+
"backgroundColor": [
44+
"rgba(255, 99, 132, 0.5)",
45+
"rgba(54, 162, 235, 0.5)",
46+
"rgba(255, 206, 86, 0.5)"
47+
],
48+
"borderWidth": 0,
49+
"weight": 1
50+
}
51+
],
52+
"labels": [
53+
"Category A",
54+
"Category B",
55+
"Category C"
56+
]
57+
},
58+
"options": {
59+
"datasetSpacing": 8,
60+
"responsive": false,
61+
"maintainAspectRatio": true,
62+
"plugins": {
63+
"legend": {
64+
"display": true,
65+
"position": "top"
66+
}
67+
}
68+
}
69+
}
70+
}
55.2 KB
Loading

test/specs/controller.doughnut.tests.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,4 +460,117 @@ describe('Chart.controllers.doughnut', function() {
460460
after: []
461461
}]);
462462
});
463+
464+
it ('should apply datasetSpacing to multiple datasets', function() {
465+
var chart = window.acquireChart({
466+
type: 'doughnut',
467+
data: {
468+
datasets: [{
469+
data: [10, 20],
470+
label: 'Dataset 1',
471+
backgroundColor: ['red', 'blue']
472+
}, {
473+
data: [15, 25],
474+
label: 'Dataset 2',
475+
backgroundColor: ['green', 'yellow']
476+
}, {
477+
data: [20, 30],
478+
label: 'Dataset 3',
479+
backgroundColor: ['orange', 'purple']
480+
}],
481+
labels: ['label0', 'label1']
482+
},
483+
options: {
484+
plugins: {
485+
legend: false,
486+
title: false
487+
},
488+
datasetSpacing: 10
489+
}
490+
});
491+
492+
chart.update();
493+
494+
var controller0 = chart.getDatasetMeta(0).controller;
495+
var controller1 = chart.getDatasetMeta(1).controller;
496+
var controller2 = chart.getDatasetMeta(2).controller;
497+
498+
// Verify that outer/inner radius decrease by datasetSpacing for each dataset
499+
expect(controller0.outerRadius).toBeGreaterThan(0);
500+
expect(controller1.outerRadius).toBeGreaterThan(0);
501+
expect(controller2.outerRadius).toBeGreaterThan(0);
502+
503+
expect(controller0.outerRadius).toBeGreaterThan(controller1.outerRadius);
504+
expect(controller1.outerRadius).toBeGreaterThan(controller2.outerRadius);
505+
506+
// The outer radius should decrease as we move to inner datasets
507+
// Each dataset should have its spacing applied
508+
var spacing0to1 = controller0.outerRadius - controller1.outerRadius;
509+
var spacing1to2 = controller1.outerRadius - controller2.outerRadius;
510+
expect(spacing0to1).toBeGreaterThan(0);
511+
expect(spacing1to2).toBeGreaterThan(0);
512+
});
513+
514+
it ('should handle zero datasetSpacing', function() {
515+
var chart = window.acquireChart({
516+
type: 'doughnut',
517+
data: {
518+
datasets: [{
519+
data: [10, 20],
520+
label: 'Dataset 1'
521+
}, {
522+
data: [15, 25],
523+
label: 'Dataset 2'
524+
}],
525+
labels: ['label0', 'label1']
526+
},
527+
options: {
528+
plugins: {
529+
legend: false,
530+
title: false
531+
},
532+
datasetSpacing: 0
533+
}
534+
});
535+
536+
chart.update();
537+
538+
var controller0 = chart.getDatasetMeta(0).controller;
539+
var controller1 = chart.getDatasetMeta(1).controller;
540+
541+
// With zero spacing, the radius difference should be only due to radiusLength
542+
var radiusLength = (controller0.outerRadius - controller0.innerRadius);
543+
expect(controller0.outerRadius - controller1.outerRadius).toBeCloseTo(radiusLength, 0);
544+
});
545+
546+
it ('should handle undefined datasetSpacing (default to 0)', function() {
547+
var chart = window.acquireChart({
548+
type: 'doughnut',
549+
data: {
550+
datasets: [{
551+
data: [10, 20],
552+
label: 'Dataset 1'
553+
}, {
554+
data: [15, 25],
555+
label: 'Dataset 2'
556+
}],
557+
labels: ['label0', 'label1']
558+
},
559+
options: {
560+
plugins: {
561+
legend: false,
562+
title: false
563+
}
564+
}
565+
});
566+
567+
chart.update();
568+
569+
var controller0 = chart.getDatasetMeta(0).controller;
570+
var controller1 = chart.getDatasetMeta(1).controller;
571+
572+
// With undefined spacing (defaults to 0), the radius difference should be only due to radiusLength
573+
var radiusLength = (controller0.outerRadius - controller0.innerRadius);
574+
expect(controller0.outerRadius - controller1.outerRadius).toBeCloseTo(radiusLength, 0);
575+
});
463576
});

test/types/options.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ const doughnutOptions: DoughnutControllerChartOptions = {
3939
radius: 100,
4040
rotation: 0,
4141
spacing: 0,
42+
datasetSpacing: 0,
4243
animation: false,
4344
spacingMode: 'angular',
4445
};

0 commit comments

Comments
 (0)