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