Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/xlsxwriter/chart_pie.py: 16%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

95 statements  

1############################################################################### 

2# 

3# ChartPie - A class for writing the Excel XLSX Pie charts. 

4# 

5# SPDX-License-Identifier: BSD-2-Clause 

6# 

7# Copyright (c) 2013-2025, John McNamara, jmcnamara@cpan.org 

8# 

9 

10from warnings import warn 

11 

12from . import chart 

13 

14 

15class ChartPie(chart.Chart): 

16 """ 

17 A class for writing the Excel XLSX Pie charts. 

18 

19 

20 """ 

21 

22 ########################################################################### 

23 # 

24 # Public API. 

25 # 

26 ########################################################################### 

27 

28 def __init__(self): 

29 """ 

30 Constructor. 

31 

32 """ 

33 super().__init__() 

34 

35 self.vary_data_color = 1 

36 self.rotation = 0 

37 

38 # Set the available data label positions for this chart type. 

39 self.label_position_default = "best_fit" 

40 self.label_positions = { 

41 "center": "ctr", 

42 "inside_end": "inEnd", 

43 "outside_end": "outEnd", 

44 "best_fit": "bestFit", 

45 } 

46 

47 def set_rotation(self, rotation): 

48 """ 

49 Set the Pie/Doughnut chart rotation: the angle of the first slice. 

50 

51 Args: 

52 rotation: First segment angle: 0 <= rotation <= 360. 

53 

54 Returns: 

55 Nothing. 

56 

57 """ 

58 if rotation is None: 

59 return 

60 

61 # Ensure the rotation is in Excel's range. 

62 if rotation < 0 or rotation > 360: 

63 warn( 

64 f"Chart rotation '{rotation}' outside Excel range: 0 <= rotation <= 360" 

65 ) 

66 return 

67 

68 self.rotation = int(rotation) 

69 

70 ########################################################################### 

71 # 

72 # Private API. 

73 # 

74 ########################################################################### 

75 

76 def _write_chart_type(self, args): 

77 # Override the virtual superclass method with a chart specific method. 

78 # Write the c:pieChart element. 

79 self._write_pie_chart() 

80 

81 ########################################################################### 

82 # 

83 # XML methods. 

84 # 

85 ########################################################################### 

86 

87 def _write_pie_chart(self): 

88 # Write the <c:pieChart> element. Over-ridden method to remove 

89 # axis_id code since Pie charts don't require val and cat axes. 

90 self._xml_start_tag("c:pieChart") 

91 

92 # Write the c:varyColors element. 

93 self._write_vary_colors() 

94 

95 # Write the series elements. 

96 for data in self.series: 

97 self._write_ser(data) 

98 

99 # Write the c:firstSliceAng element. 

100 self._write_first_slice_ang() 

101 

102 self._xml_end_tag("c:pieChart") 

103 

104 def _write_plot_area(self): 

105 # Over-ridden method to remove the cat_axis() and val_axis() code 

106 # since Pie charts don't require those axes. 

107 # 

108 # Write the <c:plotArea> element. 

109 

110 self._xml_start_tag("c:plotArea") 

111 

112 # Write the c:layout element. 

113 self._write_layout(self.plotarea.get("layout"), "plot") 

114 

115 # Write the subclass chart type element. 

116 self._write_chart_type(None) 

117 # Configure a combined chart if present. 

118 second_chart = self.combined 

119 

120 if second_chart: 

121 # Secondary axis has unique id otherwise use same as primary. 

122 if second_chart.is_secondary: 

123 second_chart.id = 1000 + self.id 

124 else: 

125 second_chart.id = self.id 

126 

127 # Share the same filehandle for writing. 

128 second_chart.fh = self.fh 

129 

130 # Share series index with primary chart. 

131 second_chart.series_index = self.series_index 

132 

133 # Write the subclass chart type elements for combined chart. 

134 # pylint: disable-next=protected-access 

135 second_chart._write_chart_type(None) 

136 

137 # Write the c:spPr element for the plotarea formatting. 

138 self._write_sp_pr(self.plotarea) 

139 

140 self._xml_end_tag("c:plotArea") 

141 

142 def _write_legend(self): 

143 # Over-ridden method to add <c:txPr> to legend. 

144 # Write the <c:legend> element. 

145 legend = self.legend 

146 position = legend.get("position", "right") 

147 font = legend.get("font") 

148 delete_series = [] 

149 overlay = 0 

150 

151 if legend.get("delete_series") and isinstance(legend["delete_series"], list): 

152 delete_series = legend["delete_series"] 

153 

154 if position.startswith("overlay_"): 

155 position = position.replace("overlay_", "") 

156 overlay = 1 

157 

158 allowed = { 

159 "right": "r", 

160 "left": "l", 

161 "top": "t", 

162 "bottom": "b", 

163 "top_right": "tr", 

164 } 

165 

166 if position == "none": 

167 return 

168 

169 if position not in allowed: 

170 return 

171 

172 position = allowed[position] 

173 

174 self._xml_start_tag("c:legend") 

175 

176 # Write the c:legendPos element. 

177 self._write_legend_pos(position) 

178 

179 # Remove series labels from the legend. 

180 for index in delete_series: 

181 # Write the c:legendEntry element. 

182 self._write_legend_entry(index) 

183 

184 # Write the c:layout element. 

185 self._write_layout(legend.get("layout"), "legend") 

186 

187 # Write the c:overlay element. 

188 if overlay: 

189 self._write_overlay() 

190 

191 # Write the c:spPr element. 

192 self._write_sp_pr(legend) 

193 

194 # Write the c:txPr element. Over-ridden. 

195 self._write_tx_pr_legend(None, font) 

196 

197 self._xml_end_tag("c:legend") 

198 

199 def _write_tx_pr_legend(self, horiz, font): 

200 # Write the <c:txPr> element for legends. 

201 

202 if font and font.get("rotation"): 

203 rotation = font["rotation"] 

204 else: 

205 rotation = None 

206 

207 self._xml_start_tag("c:txPr") 

208 

209 # Write the a:bodyPr element. 

210 self._write_a_body_pr(rotation, horiz) 

211 

212 # Write the a:lstStyle element. 

213 self._write_a_lst_style() 

214 

215 # Write the a:p element. 

216 self._write_a_p_legend(font) 

217 

218 self._xml_end_tag("c:txPr") 

219 

220 def _write_a_p_legend(self, font): 

221 # Write the <a:p> element for legends. 

222 

223 self._xml_start_tag("a:p") 

224 

225 # Write the a:pPr element. 

226 self._write_a_p_pr_legend(font) 

227 

228 # Write the a:endParaRPr element. 

229 self._write_a_end_para_rpr() 

230 

231 self._xml_end_tag("a:p") 

232 

233 def _write_a_p_pr_legend(self, font): 

234 # Write the <a:pPr> element for legends. 

235 attributes = [("rtl", 0)] 

236 

237 self._xml_start_tag("a:pPr", attributes) 

238 

239 # Write the a:defRPr element. 

240 self._write_a_def_rpr(font) 

241 

242 self._xml_end_tag("a:pPr") 

243 

244 def _write_vary_colors(self): 

245 # Write the <c:varyColors> element. 

246 attributes = [("val", 1)] 

247 

248 self._xml_empty_tag("c:varyColors", attributes) 

249 

250 def _write_first_slice_ang(self): 

251 # Write the <c:firstSliceAng> element. 

252 attributes = [("val", self.rotation)] 

253 

254 self._xml_empty_tag("c:firstSliceAng", attributes) 

255 

256 def _write_show_leader_lines(self): 

257 # Write the <c:showLeaderLines> element. 

258 # 

259 # This is for Pie/Doughnut charts. Other chart types only supported 

260 # leader lines after Excel 2015 via an extension element. 

261 attributes = [("val", 1)] 

262 

263 self._xml_empty_tag("c:showLeaderLines", attributes)