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

94 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 xlsxwriter 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) -> None: 

29 """ 

30 Constructor. 

31 

32 """ 

33 super().__init__() 

34 

35 self.rotation = 0 

36 

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

38 self.label_position_default = "best_fit" 

39 self.label_positions = { 

40 "center": "ctr", 

41 "inside_end": "inEnd", 

42 "outside_end": "outEnd", 

43 "best_fit": "bestFit", 

44 } 

45 

46 def set_rotation(self, rotation: int) -> None: 

47 """ 

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

49 

50 Args: 

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

52 

53 Returns: 

54 Nothing. 

55 

56 """ 

57 if rotation is None: 

58 return 

59 

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

61 if rotation < 0 or rotation > 360: 

62 warn( 

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

64 ) 

65 return 

66 

67 self.rotation = int(rotation) 

68 

69 ########################################################################### 

70 # 

71 # Private API. 

72 # 

73 ########################################################################### 

74 

75 def _write_chart_type(self, args) -> None: 

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

77 # Write the c:pieChart element. 

78 self._write_pie_chart() 

79 

80 ########################################################################### 

81 # 

82 # XML methods. 

83 # 

84 ########################################################################### 

85 

86 def _write_pie_chart(self) -> None: 

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

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

89 self._xml_start_tag("c:pieChart") 

90 

91 # Write the c:varyColors element. 

92 self._write_vary_colors() 

93 

94 # Write the series elements. 

95 for data in self.series: 

96 self._write_ser(data) 

97 

98 # Write the c:firstSliceAng element. 

99 self._write_first_slice_ang() 

100 

101 self._xml_end_tag("c:pieChart") 

102 

103 def _write_plot_area(self) -> None: 

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

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

106 # 

107 # Write the <c:plotArea> element. 

108 

109 self._xml_start_tag("c:plotArea") 

110 

111 # Write the c:layout element. 

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

113 

114 # Write the subclass chart type element. 

115 self._write_chart_type(None) 

116 # Configure a combined chart if present. 

117 second_chart = self.combined 

118 

119 if second_chart: 

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

121 if second_chart.is_secondary: 

122 second_chart.id = 1000 + self.id 

123 else: 

124 second_chart.id = self.id 

125 

126 # Share the same filehandle for writing. 

127 second_chart.fh = self.fh 

128 

129 # Share series index with primary chart. 

130 second_chart.series_index = self.series_index 

131 

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

133 # pylint: disable-next=protected-access 

134 second_chart._write_chart_type(None) 

135 

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

137 self._write_sp_pr(self.plotarea) 

138 

139 self._xml_end_tag("c:plotArea") 

140 

141 def _write_legend(self) -> None: 

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

143 # Write the <c:legend> element. 

144 legend = self.legend 

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

146 font = legend.get("font") 

147 delete_series = [] 

148 overlay = 0 

149 

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

151 delete_series = legend["delete_series"] 

152 

153 if position.startswith("overlay_"): 

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

155 overlay = 1 

156 

157 allowed = { 

158 "right": "r", 

159 "left": "l", 

160 "top": "t", 

161 "bottom": "b", 

162 "top_right": "tr", 

163 } 

164 

165 if position == "none": 

166 return 

167 

168 if position not in allowed: 

169 return 

170 

171 position = allowed[position] 

172 

173 self._xml_start_tag("c:legend") 

174 

175 # Write the c:legendPos element. 

176 self._write_legend_pos(position) 

177 

178 # Remove series labels from the legend. 

179 for index in delete_series: 

180 # Write the c:legendEntry element. 

181 self._write_legend_entry(index) 

182 

183 # Write the c:layout element. 

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

185 

186 # Write the c:overlay element. 

187 if overlay: 

188 self._write_overlay() 

189 

190 # Write the c:spPr element. 

191 self._write_sp_pr(legend) 

192 

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

194 self._write_tx_pr_legend(None, font) 

195 

196 self._xml_end_tag("c:legend") 

197 

198 def _write_tx_pr_legend(self, horiz, font) -> None: 

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

200 

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

202 rotation = font["rotation"] 

203 else: 

204 rotation = None 

205 

206 self._xml_start_tag("c:txPr") 

207 

208 # Write the a:bodyPr element. 

209 self._write_a_body_pr(rotation, horiz) 

210 

211 # Write the a:lstStyle element. 

212 self._write_a_lst_style() 

213 

214 # Write the a:p element. 

215 self._write_a_p_legend(font) 

216 

217 self._xml_end_tag("c:txPr") 

218 

219 def _write_a_p_legend(self, font) -> None: 

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

221 

222 self._xml_start_tag("a:p") 

223 

224 # Write the a:pPr element. 

225 self._write_a_p_pr_legend(font) 

226 

227 # Write the a:endParaRPr element. 

228 self._write_a_end_para_rpr() 

229 

230 self._xml_end_tag("a:p") 

231 

232 def _write_a_p_pr_legend(self, font) -> None: 

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

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

235 

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

237 

238 # Write the a:defRPr element. 

239 self._write_a_def_rpr(font) 

240 

241 self._xml_end_tag("a:pPr") 

242 

243 def _write_vary_colors(self) -> None: 

244 # Write the <c:varyColors> element. 

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

246 

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

248 

249 def _write_first_slice_ang(self) -> None: 

250 # Write the <c:firstSliceAng> element. 

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

252 

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

254 

255 def _write_show_leader_lines(self) -> None: 

256 # Write the <c:showLeaderLines> element. 

257 # 

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

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

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

261 

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