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
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
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#
10from warnings import warn
12from xlsxwriter import chart
15class ChartPie(chart.Chart):
16 """
17 A class for writing the Excel XLSX Pie charts.
20 """
22 ###########################################################################
23 #
24 # Public API.
25 #
26 ###########################################################################
28 def __init__(self) -> None:
29 """
30 Constructor.
32 """
33 super().__init__()
35 self.rotation = 0
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 }
46 def set_rotation(self, rotation: int) -> None:
47 """
48 Set the Pie/Doughnut chart rotation: the angle of the first slice.
50 Args:
51 rotation: First segment angle: 0 <= rotation <= 360.
53 Returns:
54 Nothing.
56 """
57 if rotation is None:
58 return
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
67 self.rotation = int(rotation)
69 ###########################################################################
70 #
71 # Private API.
72 #
73 ###########################################################################
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()
80 ###########################################################################
81 #
82 # XML methods.
83 #
84 ###########################################################################
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")
91 # Write the c:varyColors element.
92 self._write_vary_colors()
94 # Write the series elements.
95 for data in self.series:
96 self._write_ser(data)
98 # Write the c:firstSliceAng element.
99 self._write_first_slice_ang()
101 self._xml_end_tag("c:pieChart")
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.
109 self._xml_start_tag("c:plotArea")
111 # Write the c:layout element.
112 self._write_layout(self.plotarea.get("layout"), "plot")
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
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
126 # Share the same filehandle for writing.
127 second_chart.fh = self.fh
129 # Share series index with primary chart.
130 second_chart.series_index = self.series_index
132 # Write the subclass chart type elements for combined chart.
133 # pylint: disable-next=protected-access
134 second_chart._write_chart_type(None)
136 # Write the c:spPr element for the plotarea formatting.
137 self._write_sp_pr(self.plotarea)
139 self._xml_end_tag("c:plotArea")
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
150 if legend.get("delete_series") and isinstance(legend["delete_series"], list):
151 delete_series = legend["delete_series"]
153 if position.startswith("overlay_"):
154 position = position.replace("overlay_", "")
155 overlay = 1
157 allowed = {
158 "right": "r",
159 "left": "l",
160 "top": "t",
161 "bottom": "b",
162 "top_right": "tr",
163 }
165 if position == "none":
166 return
168 if position not in allowed:
169 return
171 position = allowed[position]
173 self._xml_start_tag("c:legend")
175 # Write the c:legendPos element.
176 self._write_legend_pos(position)
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)
183 # Write the c:layout element.
184 self._write_layout(legend.get("layout"), "legend")
186 # Write the c:overlay element.
187 if overlay:
188 self._write_overlay()
190 # Write the c:spPr element.
191 self._write_sp_pr(legend)
193 # Write the c:txPr element. Over-ridden.
194 self._write_tx_pr_legend(None, font)
196 self._xml_end_tag("c:legend")
198 def _write_tx_pr_legend(self, horiz, font) -> None:
199 # Write the <c:txPr> element for legends.
201 if font and font.get("rotation"):
202 rotation = font["rotation"]
203 else:
204 rotation = None
206 self._xml_start_tag("c:txPr")
208 # Write the a:bodyPr element.
209 self._write_a_body_pr(rotation, horiz)
211 # Write the a:lstStyle element.
212 self._write_a_lst_style()
214 # Write the a:p element.
215 self._write_a_p_legend(font)
217 self._xml_end_tag("c:txPr")
219 def _write_a_p_legend(self, font) -> None:
220 # Write the <a:p> element for legends.
222 self._xml_start_tag("a:p")
224 # Write the a:pPr element.
225 self._write_a_p_pr_legend(font)
227 # Write the a:endParaRPr element.
228 self._write_a_end_para_rpr()
230 self._xml_end_tag("a:p")
232 def _write_a_p_pr_legend(self, font) -> None:
233 # Write the <a:pPr> element for legends.
234 attributes = [("rtl", 0)]
236 self._xml_start_tag("a:pPr", attributes)
238 # Write the a:defRPr element.
239 self._write_a_def_rpr(font)
241 self._xml_end_tag("a:pPr")
243 def _write_vary_colors(self) -> None:
244 # Write the <c:varyColors> element.
245 attributes = [("val", 1)]
247 self._xml_empty_tag("c:varyColors", attributes)
249 def _write_first_slice_ang(self) -> None:
250 # Write the <c:firstSliceAng> element.
251 attributes = [("val", self.rotation)]
253 self._xml_empty_tag("c:firstSliceAng", attributes)
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)]
262 self._xml_empty_tag("c:showLeaderLines", attributes)