@@ -31,6 +31,7 @@ pub struct Pie<'a, Coord, Label: Display> {
31
31
label_style : TextStyle < ' a > ,
32
32
label_offset : f64 ,
33
33
percentage_style : Option < TextStyle < ' a > > ,
34
+ donut_hole : f64 , // radius of the hole in case of a donut chart
34
35
}
35
36
36
37
impl < ' a , Label : Display > Pie < ' a , ( i32 , i32 ) , Label > {
@@ -62,6 +63,7 @@ impl<'a, Label: Display> Pie<'a, (i32, i32), Label> {
62
63
label_style,
63
64
label_offset : radius_5pct,
64
65
percentage_style : None ,
66
+ donut_hole : 0.0 ,
65
67
}
66
68
}
67
69
@@ -91,6 +93,15 @@ impl<'a, Label: Display> Pie<'a, (i32, i32), Label> {
91
93
pub fn percentages < T : Into < TextStyle < ' a > > > ( & mut self , label_style : T ) {
92
94
self . percentage_style = Some ( label_style. into ( ) ) ;
93
95
}
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
+ }
94
105
}
95
106
96
107
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)
118
129
. get ( index)
119
130
. ok_or_else ( || DrawingErrorKind :: FontError ( Box :: new ( PieError :: LengthMismatch ) ) ) ?;
120
131
// 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
+ } ;
122
137
let ratio = slice / self . total ;
123
138
let theta_final = ratio * 2.0 * PI + offset_theta; // end radian for the wedge
124
139
125
140
// calculate middle for labels before mutating offset
126
141
let middle_theta = ratio * PI + offset_theta;
127
142
143
+ let slice_start = offset_theta;
144
+
128
145
// calculate every fraction of radian for the wedge, offsetting for every iteration, clockwise
129
146
//
130
147
// 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)
138
155
// final point of the wedge may not fall exactly on a radian, so add it extra
139
156
let final_coord = theta_to_ordinal_coord ( * self . radius , theta_final, self . center ) ;
140
157
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
+
141
171
// next wedge calculation will start from previous wedges's last radian
142
172
offset_theta = theta_final;
143
173
@@ -163,8 +193,9 @@ impl<'a, DB: DrawingBackend, Label: Display> Drawable<DB> for Pie<'a, (i32, i32)
163
193
let label_size = backend. estimate_text_size ( & perc_label, percentage_style) ?;
164
194
let text_x_mid = ( label_size. 0 as f64 / 2.0 ) . round ( ) as i32 ;
165
195
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 ;
166
197
let perc_coord = theta_to_ordinal_coord (
167
- self . radius / 2.0 ,
198
+ perc_radius ,
168
199
middle_theta,
169
200
& ( self . center . 0 - text_x_mid, self . center . 1 - text_y_mid) ,
170
201
) ;
0 commit comments