Skip to content

Commit 7c1f8ec

Browse files
authored
Merge pull request #485 from GyulyVGC/donut-chart
Feat: added method to Pie Chart for creating a Donut Chart
2 parents 50e462b + b46d26b commit 7c1f8ec

File tree

1 file changed

+33
-2
lines changed

1 file changed

+33
-2
lines changed

plotters/src/element/pie.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub struct Pie<'a, Coord, Label: Display> {
3131
label_style: TextStyle<'a>,
3232
label_offset: f64,
3333
percentage_style: Option<TextStyle<'a>>,
34+
donut_hole: f64, // radius of the hole in case of a donut chart
3435
}
3536

3637
impl<'a, Label: Display> Pie<'a, (i32, i32), Label> {
@@ -62,6 +63,7 @@ impl<'a, Label: Display> Pie<'a, (i32, i32), Label> {
6263
label_style,
6364
label_offset: radius_5pct,
6465
percentage_style: None,
66+
donut_hole: 0.0,
6567
}
6668
}
6769

@@ -91,6 +93,15 @@ impl<'a, Label: Display> Pie<'a, (i32, i32), Label> {
9193
pub fn percentages<T: Into<TextStyle<'a>>>(&mut self, label_style: T) {
9294
self.percentage_style = Some(label_style.into());
9395
}
96+
97+
/// Enables creating a donut chart with a hole of the specified radius.
98+
///
99+
/// The passed value must be greater than zero and lower than the chart overall radius, otherwise it'll be ignored.
100+
pub fn donut_hole(&mut self, hole_radius: f64) {
101+
if hole_radius > 0.0 && hole_radius < *self.radius {
102+
self.donut_hole = hole_radius;
103+
}
104+
}
94105
}
95106

96107
impl<'a, DB: DrawingBackend, Label: Display> Drawable<DB> for Pie<'a, (i32, i32), Label> {
@@ -118,13 +129,19 @@ impl<'a, DB: DrawingBackend, Label: Display> Drawable<DB> for Pie<'a, (i32, i32)
118129
.get(index)
119130
.ok_or_else(|| DrawingErrorKind::FontError(Box::new(PieError::LengthMismatch)))?;
120131
// start building wedge line against the previous edge
121-
let mut points = vec![*self.center];
132+
let mut points = if self.donut_hole == 0.0 {
133+
vec![*self.center]
134+
} else {
135+
vec![]
136+
};
122137
let ratio = slice / self.total;
123138
let theta_final = ratio * 2.0 * PI + offset_theta; // end radian for the wedge
124139

125140
// calculate middle for labels before mutating offset
126141
let middle_theta = ratio * PI + offset_theta;
127142

143+
let slice_start = offset_theta;
144+
128145
// calculate every fraction of radian for the wedge, offsetting for every iteration, clockwise
129146
//
130147
// a custom Range such as `for theta in offset_theta..=theta_final` would be more elegant
@@ -138,6 +155,19 @@ impl<'a, DB: DrawingBackend, Label: Display> Drawable<DB> for Pie<'a, (i32, i32)
138155
// final point of the wedge may not fall exactly on a radian, so add it extra
139156
let final_coord = theta_to_ordinal_coord(*self.radius, theta_final, self.center);
140157
points.push(final_coord);
158+
159+
if self.donut_hole > 0.0 {
160+
while offset_theta >= slice_start {
161+
let coord = theta_to_ordinal_coord(self.donut_hole, offset_theta, self.center);
162+
points.push(coord);
163+
offset_theta -= radian_increment;
164+
}
165+
// final point of the wedge may not fall exactly on a radian, so add it extra
166+
let final_coord_inner =
167+
theta_to_ordinal_coord(self.donut_hole, slice_start, self.center);
168+
points.push(final_coord_inner);
169+
}
170+
141171
// next wedge calculation will start from previous wedges's last radian
142172
offset_theta = theta_final;
143173

@@ -163,8 +193,9 @@ impl<'a, DB: DrawingBackend, Label: Display> Drawable<DB> for Pie<'a, (i32, i32)
163193
let label_size = backend.estimate_text_size(&perc_label, percentage_style)?;
164194
let text_x_mid = (label_size.0 as f64 / 2.0).round() as i32;
165195
let text_y_mid = (label_size.1 as f64 / 2.0).round() as i32;
196+
let perc_radius = (self.radius + self.donut_hole) / 2.0;
166197
let perc_coord = theta_to_ordinal_coord(
167-
self.radius / 2.0,
198+
perc_radius,
168199
middle_theta,
169200
&(self.center.0 - text_x_mid, self.center.1 - text_y_mid),
170201
);

0 commit comments

Comments
 (0)