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

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

119 statements  

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

2# 

3# ChartScatter - A class for writing the Excel XLSX Scatter charts. 

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 

11from warnings import warn 

12 

13from . import chart 

14 

15 

16class ChartScatter(chart.Chart): 

17 """ 

18 A class for writing the Excel XLSX Scatter charts. 

19 

20 

21 """ 

22 

23 ########################################################################### 

24 # 

25 # Public API. 

26 # 

27 ########################################################################### 

28 

29 def __init__(self, options: Optional[Dict[str, Any]] = None) -> None: 

30 """ 

31 Constructor. 

32 

33 """ 

34 super().__init__() 

35 

36 if options is None: 

37 options = {} 

38 

39 self.subtype = options.get("subtype") 

40 

41 if not self.subtype: 

42 self.subtype = "marker_only" 

43 

44 self.cross_between = "midCat" 

45 self.horiz_val_axis = 0 

46 self.val_axis_position = "b" 

47 self.smooth_allowed = True 

48 self.requires_category = True 

49 

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

51 self.label_position_default = "right" 

52 self.label_positions = { 

53 "center": "ctr", 

54 "right": "r", 

55 "left": "l", 

56 "above": "t", 

57 "below": "b", 

58 # For backward compatibility. 

59 "top": "t", 

60 "bottom": "b", 

61 } 

62 

63 def combine(self, chart: Optional[chart.Chart] = None) -> None: 

64 # pylint: disable=redefined-outer-name 

65 """ 

66 Create a combination chart with a secondary chart. 

67 

68 Note: Override parent method to add a warning. 

69 

70 Args: 

71 chart: The secondary chart to combine with the primary chart. 

72 

73 Returns: 

74 Nothing. 

75 

76 """ 

77 if chart is None: 

78 return 

79 

80 warn( 

81 "Combined chart not currently supported with scatter chart " 

82 "as the primary chart" 

83 ) 

84 

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

86 # 

87 # Private API. 

88 # 

89 ########################################################################### 

90 

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

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

93 # Write the c:scatterChart element. 

94 self._write_scatter_chart(args) 

95 

96 ########################################################################### 

97 # 

98 # XML methods. 

99 # 

100 ########################################################################### 

101 

102 def _write_scatter_chart(self, args) -> None: 

103 # Write the <c:scatterChart> element. 

104 

105 if args["primary_axes"]: 

106 series = self._get_primary_axes_series() 

107 else: 

108 series = self._get_secondary_axes_series() 

109 

110 if not series: 

111 return 

112 

113 style = "lineMarker" 

114 subtype = self.subtype 

115 

116 # Set the user defined chart subtype. 

117 if subtype == "marker_only": 

118 style = "lineMarker" 

119 

120 if subtype == "straight_with_markers": 

121 style = "lineMarker" 

122 

123 if subtype == "straight": 

124 style = "lineMarker" 

125 self.default_marker = {"type": "none"} 

126 

127 if subtype == "smooth_with_markers": 

128 style = "smoothMarker" 

129 

130 if subtype == "smooth": 

131 style = "smoothMarker" 

132 self.default_marker = {"type": "none"} 

133 

134 # Add default formatting to the series data. 

135 self._modify_series_formatting() 

136 

137 self._xml_start_tag("c:scatterChart") 

138 

139 # Write the c:scatterStyle element. 

140 self._write_scatter_style(style) 

141 

142 # Write the series elements. 

143 for data in series: 

144 self._write_ser(data) 

145 

146 # Write the c:axId elements 

147 self._write_axis_ids(args) 

148 

149 self._xml_end_tag("c:scatterChart") 

150 

151 def _write_ser(self, series) -> None: 

152 # Over-ridden to write c:xVal/c:yVal instead of c:cat/c:val elements. 

153 # Write the <c:ser> element. 

154 

155 index = self.series_index 

156 self.series_index += 1 

157 

158 self._xml_start_tag("c:ser") 

159 

160 # Write the c:idx element. 

161 self._write_idx(index) 

162 

163 # Write the c:order element. 

164 self._write_order(index) 

165 

166 # Write the series name. 

167 self._write_series_name(series) 

168 

169 # Write the c:spPr element. 

170 self._write_sp_pr(series) 

171 

172 # Write the c:marker element. 

173 self._write_marker(series.get("marker")) 

174 

175 # Write the c:dPt element. 

176 self._write_d_pt(series.get("points")) 

177 

178 # Write the c:dLbls element. 

179 self._write_d_lbls(series.get("labels")) 

180 

181 # Write the c:trendline element. 

182 self._write_trendline(series.get("trendline")) 

183 

184 # Write the c:errBars element. 

185 self._write_error_bars(series.get("error_bars")) 

186 

187 # Write the c:xVal element. 

188 self._write_x_val(series) 

189 

190 # Write the c:yVal element. 

191 self._write_y_val(series) 

192 

193 # Write the c:smooth element. 

194 if "smooth" in self.subtype and series["smooth"] is None: 

195 # Default is on for smooth scatter charts. 

196 self._write_c_smooth(True) 

197 else: 

198 self._write_c_smooth(series["smooth"]) 

199 

200 self._xml_end_tag("c:ser") 

201 

202 def _write_plot_area(self) -> None: 

203 # Over-ridden to have 2 valAx elements for scatter charts instead 

204 # of catAx/valAx. 

205 # 

206 # Write the <c:plotArea> element. 

207 self._xml_start_tag("c:plotArea") 

208 

209 # Write the c:layout element. 

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

211 

212 # Write the subclass chart elements for primary and secondary axes. 

213 self._write_chart_type({"primary_axes": 1}) 

214 self._write_chart_type({"primary_axes": 0}) 

215 

216 # Write c:catAx and c:valAx elements for series using primary axes. 

217 self._write_cat_val_axis( 

218 { 

219 "x_axis": self.x_axis, 

220 "y_axis": self.y_axis, 

221 "axis_ids": self.axis_ids, 

222 "position": "b", 

223 } 

224 ) 

225 

226 tmp = self.horiz_val_axis 

227 self.horiz_val_axis = 1 

228 

229 self._write_val_axis( 

230 { 

231 "x_axis": self.x_axis, 

232 "y_axis": self.y_axis, 

233 "axis_ids": self.axis_ids, 

234 "position": "l", 

235 } 

236 ) 

237 

238 self.horiz_val_axis = tmp 

239 

240 # Write c:valAx and c:catAx elements for series using secondary axes 

241 self._write_cat_val_axis( 

242 { 

243 "x_axis": self.x2_axis, 

244 "y_axis": self.y2_axis, 

245 "axis_ids": self.axis2_ids, 

246 "position": "b", 

247 } 

248 ) 

249 self.horiz_val_axis = 1 

250 self._write_val_axis( 

251 { 

252 "x_axis": self.x2_axis, 

253 "y_axis": self.y2_axis, 

254 "axis_ids": self.axis2_ids, 

255 "position": "l", 

256 } 

257 ) 

258 

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

260 self._write_sp_pr(self.plotarea) 

261 

262 self._xml_end_tag("c:plotArea") 

263 

264 def _write_x_val(self, series) -> None: 

265 # Write the <c:xVal> element. 

266 formula = series.get("categories") 

267 data_id = series.get("cat_data_id") 

268 data = self.formula_data[data_id] 

269 

270 self._xml_start_tag("c:xVal") 

271 

272 # Check the type of cached data. 

273 data_type = self._get_data_type(data) 

274 

275 if data_type == "str": 

276 # Write the c:numRef element. 

277 self._write_str_ref(formula, data, data_type) 

278 else: 

279 # Write the c:numRef element. 

280 self._write_num_ref(formula, data, data_type) 

281 

282 self._xml_end_tag("c:xVal") 

283 

284 def _write_y_val(self, series) -> None: 

285 # Write the <c:yVal> element. 

286 formula = series.get("values") 

287 data_id = series.get("val_data_id") 

288 data = self.formula_data[data_id] 

289 

290 self._xml_start_tag("c:yVal") 

291 

292 # Unlike Cat axes data should only be numeric. 

293 # Write the c:numRef element. 

294 self._write_num_ref(formula, data, "num") 

295 

296 self._xml_end_tag("c:yVal") 

297 

298 def _write_scatter_style(self, val) -> None: 

299 # Write the <c:scatterStyle> element. 

300 attributes = [("val", val)] 

301 

302 self._xml_empty_tag("c:scatterStyle", attributes) 

303 

304 def _modify_series_formatting(self) -> None: 

305 # Add default formatting to the series data unless it has already been 

306 # specified by the user. 

307 subtype = self.subtype 

308 

309 # The default scatter style "markers only" requires a line type. 

310 if subtype == "marker_only": 

311 # Go through each series and define default values. 

312 for series in self.series: 

313 # Set a line type unless there is already a user defined type. 

314 if not series["line"]["defined"]: 

315 series["line"] = { 

316 "width": 2.25, 

317 "none": 1, 

318 "defined": 1, 

319 } 

320 

321 def _write_d_pt_point(self, index, point) -> None: 

322 # Write an individual <c:dPt> element. Override the parent method to 

323 # add markers. 

324 

325 self._xml_start_tag("c:dPt") 

326 

327 # Write the c:idx element. 

328 self._write_idx(index) 

329 

330 self._xml_start_tag("c:marker") 

331 

332 # Write the c:spPr element. 

333 self._write_sp_pr(point) 

334 

335 self._xml_end_tag("c:marker") 

336 

337 self._xml_end_tag("c:dPt")