1###############################################################################
2#
3# Chartsheet - A class for writing the Excel XLSX Worksheet file.
4#
5# SPDX-License-Identifier: BSD-2-Clause
6#
7# Copyright (c) 2013-2025, John McNamara, jmcnamara@cpan.org
8#
9
10from typing import Any, Dict, Optional
11
12from xlsxwriter import worksheet
13from xlsxwriter.chart import Chart
14from xlsxwriter.drawing import Drawing
15
16
17class Chartsheet(worksheet.Worksheet):
18 """
19 A class for writing the Excel XLSX Chartsheet file.
20
21
22 """
23
24 ###########################################################################
25 #
26 # Public API.
27 #
28 ###########################################################################
29
30 def __init__(self) -> None:
31 """
32 Constructor.
33
34 """
35
36 super().__init__()
37
38 self.is_chartsheet = True
39 self.drawing = None
40 self.chart = None
41 self.charts = []
42 self.zoom_scale_normal = False
43 self.orientation = 0
44 self.protection = False
45
46 def set_chart(self, chart: Chart) -> Chart:
47 """
48 Set the chart object for the chartsheet.
49 Args:
50 chart: Chart object.
51 Returns:
52 chart: A reference to the chart object.
53 """
54 chart.embedded = False
55 chart.protection = self.protection
56 self.chart = chart
57 self.charts.append([0, 0, chart, 0, 0, 1, 1])
58 return chart
59
60 def protect(
61 self, password: str = "", options: Optional[Dict[str, Any]] = None
62 ) -> None:
63 """
64 Set the password and protection options of the worksheet.
65
66 Args:
67 password: An optional password string.
68 options: A dictionary of worksheet objects to protect.
69
70 Returns:
71 Nothing.
72
73 """
74 # This method is overridden from parent worksheet class.
75
76 # Chartsheets only allow a reduced set of protect options.
77 copy = {}
78
79 if not options:
80 options = {}
81
82 if options.get("objects") is None:
83 copy["objects"] = False
84 else:
85 # Objects are default on for chartsheets, so reverse state.
86 copy["objects"] = not options["objects"]
87
88 if options.get("content") is None:
89 copy["content"] = True
90 else:
91 copy["content"] = options["content"]
92
93 copy["sheet"] = False
94 copy["scenarios"] = True
95
96 # If objects and content are both off then the chartsheet isn't
97 # protected, unless it has a password.
98 if password == "" and copy["objects"] and not copy["content"]:
99 return
100
101 if self.chart:
102 self.chart.protection = True
103 else:
104 self.protection = True
105
106 # Call the parent method.
107 super().protect(password, copy)
108
109 ###########################################################################
110 #
111 # Private API.
112 #
113 ###########################################################################
114 def _assemble_xml_file(self) -> None:
115 # Assemble and write the XML file.
116
117 # Write the XML declaration.
118 self._xml_declaration()
119
120 # Write the root worksheet element.
121 self._write_chartsheet()
122
123 # Write the worksheet properties.
124 self._write_sheet_pr()
125
126 # Write the sheet view properties.
127 self._write_sheet_views()
128
129 # Write the sheetProtection element.
130 self._write_sheet_protection()
131
132 # Write the printOptions element.
133 self._write_print_options()
134
135 # Write the worksheet page_margins.
136 self._write_page_margins()
137
138 # Write the worksheet page setup.
139 self._write_page_setup()
140
141 # Write the headerFooter element.
142 self._write_header_footer()
143
144 # Write the drawing element.
145 self._write_drawings()
146
147 # Write the legacyDrawingHF element.
148 self._write_legacy_drawing_hf()
149
150 # Close the worksheet tag.
151 self._xml_end_tag("chartsheet")
152
153 # Close the file.
154 self._xml_close()
155
156 def _prepare_chart(self, index, chart_id, drawing_id) -> None:
157 # Set up chart/drawings.
158
159 self.chart.id = chart_id - 1
160
161 self.drawing = Drawing()
162 self.drawing.orientation = self.orientation
163
164 self.external_drawing_links.append(
165 ["/drawing", "../drawings/drawing" + str(drawing_id) + ".xml"]
166 )
167
168 self.drawing_links.append(
169 ["/chart", "../charts/chart" + str(chart_id) + ".xml"]
170 )
171
172 ###########################################################################
173 #
174 # XML methods.
175 #
176 ###########################################################################
177
178 def _write_chartsheet(self) -> None:
179 # Write the <worksheet> element. This is the root element.
180
181 schema = "http://schemas.openxmlformats.org/"
182 xmlns = schema + "spreadsheetml/2006/main"
183 xmlns_r = schema + "officeDocument/2006/relationships"
184
185 attributes = [("xmlns", xmlns), ("xmlns:r", xmlns_r)]
186
187 self._xml_start_tag("chartsheet", attributes)
188
189 def _write_sheet_pr(self) -> None:
190 # Write the <sheetPr> element for Sheet level properties.
191 attributes = []
192
193 if self.filter_on:
194 attributes.append(("filterMode", 1))
195
196 if self.fit_page or self.tab_color:
197 self._xml_start_tag("sheetPr", attributes)
198 self._write_tab_color()
199 self._write_page_set_up_pr()
200 self._xml_end_tag("sheetPr")
201 else:
202 self._xml_empty_tag("sheetPr", attributes)