Coverage for cli/src/xyfigure/xyview.py: 59%

173 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-07 19:52 +0000

1# https://www.python.org/dev/peps/pep-0008/#imports 

2# standard library imports 

3import os 

4from datetime import datetime 

5from pathlib import Path 

6import socket 

7 

8# related third-party imports 

9import matplotlib.pyplot as plt 

10from PIL import Image 

11from tzlocal import get_localzone 

12 

13# local application/library specific imports 

14# from xyfigure.xybase import XYBase 

15# from xyfigure.code.xybase import XYBase 

16from xyfigure.xybase import XYBase 

17 

18 

19class XYViewBase(XYBase): 

20 """The base class used by all XYViews.""" 

21 

22 def __init__(self, guid, **kwargs): 

23 super().__init__(guid, **kwargs) 

24 self._models = [] 

25 self._model_keys = kwargs.get("model_keys", None) 

26 self._figure = None 

27 # self._folder = kwargs.get('folder', None) 

28 self._file_base = self._file.split(".")[0] 

29 

30 self._title = kwargs.get("title", "default title") 

31 self._xlabel = kwargs.get("xlabel", "default x axis label") 

32 self._ylabel = kwargs.get("ylabel", "default y axis label") 

33 

34 self._xticks = kwargs.get("xticks", None) 

35 self._yticks = kwargs.get("yticks", None) 

36 

37 self._size = kwargs.get("size", [11.0, 8.5]) 

38 self._dpi = kwargs.get("dpi", 100) 

39 self._xlim = kwargs.get("xlim", None) 

40 self._ylim = kwargs.get("ylim", None) 

41 

42 self._display = kwargs.get("display", True) 

43 self._details = kwargs.get("details", True) 

44 # self._serialize = kwargs.get('serialize', False) # moved up to XYBase 

45 self._latex = kwargs.get("latex", False) 

46 if self._latex: 

47 from matplotlib import rc 

48 

49 rc("text", usetex=True) 

50 rc("font", family="serif") 

51 

52 # default value if axes_kwargs not client-supplied 

53 # https://matplotlib.org/stable/api/axes_api.html 

54 # https://matplotlib.org/stable/api/axes_api.html#appearance 

55 # whether frame (rectangle outline) appears on all axes 

56 self._frame = kwargs.get("frame", True) 

57 

58 # https://matplotlib.org/stable/gallery/subplots_axes_and_figures/axes_props.html#sphx-glr-gallery-subplots-axes-and-figures-axes-props-py 

59 # 

60 # https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.tick_params.html#matplotlib.axes.Axes.tick_params 

61 # default to empty dictionary 

62 self._tick_params = kwargs.get("tick_params", {}) 

63 

64 print("Finished XYViewBase constructor.") 

65 

66 @property 

67 def models(self): 

68 return self._models 

69 

70 @models.setter 

71 def models(self, value): 

72 self._models = value 

73 

74 @property 

75 def model_keys(self): 

76 return self._model_keys 

77 

78 def figure(self): 

79 pass 

80 

81 def serialize(self, folder, filename): # extend base class 

82 # deprecated 

83 # super().serialize(folder, filename) 

84 # self._figure.savefig( 

85 # self._path_file_output, dpi=self._dpi, bbox_inches="tight" 

86 # ) # avoid cutoff of labels 

87 # print(f" Serialized view to: {self._path_file_output}") 

88 

89 # New for XYView class, the so-called input file (input by the user) is the 

90 # output file to which the figure should be serialized. 

91 self._figure.savefig( 

92 self._path_file_input, dpi=self._dpi, bbox_inches="tight" 

93 ) # avoid cutoff of labels 

94 print(f" Serialized view to: {self._path_file_input}") 

95 

96 

97class XYView(XYViewBase): 

98 """Creates a view that sees models.""" 

99 

100 def __init__(self, guid, **kwargs): 

101 super().__init__(guid, **kwargs) 

102 # self._models = [] 

103 # self._model_keys = kwargs.get("model_keys", None) 

104 # self._figure = None 

105 # # self._folder = kwargs.get('folder', None) 

106 # self._file_base = self._file.split(".")[0] 

107 

108 # default value if figure_kwargs not client-supplied 

109 # self._title = kwargs.get("title", "default title") 

110 # self._xlabel = kwargs.get("xlabel", "default x axis label") 

111 # self._ylabel = kwargs.get("ylabel", "default y axis label") 

112 

113 # self._xticks = kwargs.get("xticks", None) 

114 # self._yticks = kwargs.get("yticks", None) 

115 

116 self._xaxislog = kwargs.get("xaxis_log", False) 

117 self._yaxislog = kwargs.get("yaxis_log", False) 

118 

119 # default = {'scale': 1, 'label': 'ylabel_rhs', 'verification': 0} 

120 self._yaxis_rhs = kwargs.get("yaxis_rhs", None) 

121 

122 # inches, U.S. paper, landscape 

123 # self._size = kwargs.get("size", [11.0, 8.5]) 

124 # self._dpi = kwargs.get("dpi", 100) 

125 # self._xlim = kwargs.get("xlim", None) 

126 # self._ylim = kwargs.get("ylim", None) 

127 

128 self._background_image = kwargs.get("background_image", None) 

129 

130 # self._display = kwargs.get("display", True) 

131 # self._details = kwargs.get("details", True) 

132 # # self._serialize = kwargs.get('serialize', False) # moved up to XYBase 

133 # self._latex = kwargs.get("latex", False) 

134 # if self._latex: 

135 # from matplotlib import rc 

136 

137 # rc("text", usetex=True) 

138 # rc("font", family="serif") 

139 

140 # @property 

141 # def models(self): 

142 # return self._models 

143 

144 # @models.setter 

145 # def models(self, value): 

146 # self._models = value 

147 

148 # @property 

149 # def model_keys(self): 

150 # return self._model_keys 

151 

152 def figure(self): 

153 """Create a figure (view) of the registered models to the screen.""" 

154 if self._figure is None: 

155 

156 # dpi versus fig size 

157 # https://stackoverflow.com/questions/47633546/relationship-between-dpi-and-figure-size 

158 # fig, ax = plt.subplots(nrows=1, dpi=self._dpi) 

159 self._figure, ax = plt.subplots(nrows=1, dpi=self._dpi) 

160 if self._verbose: 

161 print(f" Figure dpi set to {self._dpi}") 

162 

163 # ax.ticklabel_format(axis='y', style='scientific') 

164 # ax.ticklabel_format(axis='both', style='scientific', scilimits=(0,0)) 

165 

166 # https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.figure.Figure.html#matplotlib.figure.Figure.set_size_inches 

167 # fig.set_size_inches(self._size) 

168 self._figure.set_size_inches(self._size) 

169 if self._verbose: 

170 print(" Figure size set to " + str(self._size) + " inches.") 

171 

172 if self._background_image: 

173 folder = self._background_image.get("folder", ".") 

174 file = self._background_image.get("file", None) 

175 # rel_path_and_file = os.path.join( 

176 # folder, file 

177 # ) # relative to current run location 

178 path_file = Path(folder).expanduser().joinpath(file) 

179 # im = Image.open(rel_path_and_file) 

180 im = Image.open(path_file) 

181 

182 left = self._background_image.get("left", 0.0) 

183 right = self._background_image.get("right", 1.0) 

184 bottom = self._background_image.get("bottom", 0.0) 

185 top = self._background_image.get("top", 1.0) 

186 al = self._background_image.get("alpha", 1.0) 

187 

188 # https://github.com/matplotlib/matplotlib/issues/3173 

189 # https://matplotlib.org/3.1.1/tutorials/intermediate/imshow_extent.html 

190 bounds = [left, right, bottom, top] 

191 im = ax.imshow(im, zorder=0, extent=bounds, alpha=al, aspect="auto") 

192 

193 for model in self._models: 

194 # needs rearchitecting, a logview descends from a view 

195 if self._xaxislog and not self._yaxislog: # needs rearchitecting 

196 ax.semilogx(model.x, model.y, **model.plot_kwargs) 

197 elif not self._xaxislog and self._yaxislog: 

198 ax.semilogy(model.x, model.y, **model.plot_kwargs) 

199 elif self._xaxislog and self._yaxislog: 

200 ax.loglog(model.x, model.y, **model.plot_kwargs) 

201 else: 

202 ax.plot(model.x, model.y, **model.plot_kwargs) 

203 

204 if self._xticks: 

205 ax.set_xticks(self._xticks) 

206 

207 if self._yticks: 

208 ax.set_yticks(self._yticks) 

209 

210 if self._xlim: 

211 ax.set_xlim(self._xlim) 

212 

213 if self._ylim: 

214 ax.set_ylim(self._ylim) 

215 

216 if self._yaxis_rhs: 

217 rhs_axis_scale = self._yaxis_rhs.get("scale", 1) 

218 rhs_axis_label = self._yaxis_rhs.get("label", None) 

219 # rhs_yticks_str = self._yaxis_rhs.get('yticks', None) 

220 rhs_yticks = self._yaxis_rhs.get("yticks", None) 

221 

222 # ax2 = fig.add_subplot(111, sharex=ax, frameon=False) 

223 ax2 = self._figure.add_subplot(111, sharex=ax, frameon=False) 

224 bottom, top = ax.get_ylim() # get from left-hand-side y-axis 

225 ax2.set_ylim(rhs_axis_scale * bottom, rhs_axis_scale * top) 

226 ax2.yaxis.tick_right() 

227 # ax2.ticklabel_format(axis='both', style='scientific', scilimits=(0,0)) 

228 # ax.ticklabel_format(axis='both', style='scientific', scilimits=(0,0)) 

229 # _ticklabel_format = self._yaxis_rhs.get('ticklabel_format', None) 

230 # _ticklabel_format = self._yaxis_rhs.get('ticklabel_format', None) 

231 # if _ticklabel_format: 

232 # scilimits_str = _ticklabel_format.get('scilimitsl', "(0, 0)") 

233 # ax2.ticklabel_format(**_ticklabel_format) 

234 # ax2.ticklabel_format(axis='both', style='scientific', scilimits=(0,0)) 

235 # plt.ticklabel_format(axis='y', style='scientific', useOffset=False) 

236 # ax2.ticklabel_format(axis='y', style='scientific', useOffset=False) 

237 if rhs_yticks: 

238 ax2.set_yticks(rhs_yticks) 

239 ax2.yaxis.set_label_position("right") 

240 ax2.set_ylabel(rhs_axis_label) 

241 

242 # fig.suptitle(self._title) 

243 self._figure.suptitle(self._title) 

244 ax.set_xlabel(self._xlabel) 

245 ax.set_ylabel(self._ylabel) 

246 # set frame on or off based on the Bool "frame" in .json input 

247 ax.set_frame_on(b=self._frame) 

248 if len(self._tick_params) > 0: 

249 ax.tick_params(**self._tick_params) 

250 ax.grid() 

251 ax.legend() 

252 

253 if self._details: 

254 # Get the local timezone 

255 local_timezone = get_localzone() 

256 

257 # Get the current time in the local timezone 

258 current_time = datetime.now(local_timezone) 

259 

260 # Format the date and time stamp 

261 timestamp = current_time.strftime("%Y-%m-%d %H:%M:%S %Z%z") 

262 

263 user = str(os.getlogin()) 

264 # host = str(os.getenv("HOSTNAME")) 

265 host = socket.gethostname() 

266 details_str = ( 

267 self._file + " created " + timestamp + " by " + user + " on " + host 

268 ) 

269 

270 ax.set_title(details_str, fontsize=8, ha="center", color="dimgray") 

271 

272 if self._display: 

273 plt.show() 

274 

275 if self._serialize: 

276 self.serialize(self._folder, self._file) 

277 

278 plt.close("all") 

279 self._figure = None 

280 

281 # def serialize(self, folder, filename): # extend base class 

282 # # deprecated 

283 # # super().serialize(folder, filename) 

284 # # self._figure.savefig( 

285 # # self._path_file_output, dpi=self._dpi, bbox_inches="tight" 

286 # # ) # avoid cutoff of labels 

287 # # print(f" Serialized view to: {self._path_file_output}") 

288 

289 # # New for XYView class, the so-called input file (input by the user) is the 

290 # # output file to which the figure should be serialized. 

291 # self._figure.savefig( 

292 # self._path_file_input, dpi=self._dpi, bbox_inches="tight" 

293 # ) # avoid cutoff of labels 

294 # print(f" Serialized view to: {self._path_file_input}") 

295 

296 

297class XYViewAbaqus(XYViewBase): 

298 """Creates an ABAQUS view that sees ABAQUS models.""" 

299 

300 def __init__(self, guid, **kwargs): 

301 super().__init__(guid, **kwargs) 

302 # self._models = [] 

303 # self._model_keys = kwargs.get("model_keys", None) 

304 # self._figure = None 

305 # # self._folder = kwargs.get('folder', None) 

306 # self._file_base = self._file.split(".")[0] 

307 

308 # default value if figure_kwargs not client-supplied 

309 # self._title = kwargs.get("title", "default title") 

310 # self._xlabel = kwargs.get("xlabel", "default x axis label") 

311 # self._ylabel = kwargs.get("ylabel", "default y axis label") 

312 

313 # self._xticks = kwargs.get("xticks", None) 

314 # self._yticks = kwargs.get("yticks", None) 

315 

316 # inches, U.S. paper, landscape 

317 # self._size = kwargs.get("size", [11.0, 8.5]) 

318 # self._dpi = kwargs.get("dpi", 100) 

319 # self._xlim = kwargs.get("xlim", None) 

320 # self._ylim = kwargs.get("ylim", None) 

321 

322 # self._display = kwargs.get("display", True) 

323 # self._details = kwargs.get("details", True) 

324 # # self._serialize = kwargs.get('serialize', False) # moved up to XYBase 

325 # self._latex = kwargs.get("latex", False) 

326 # if self._latex: 

327 # from matplotlib import rc 

328 

329 # rc("text", usetex=True) 

330 # rc("font", family="serif") 

331 

332 @property 

333 def models(self): 

334 return self._models 

335 

336 @models.setter 

337 def models(self, value): 

338 self._models = value 

339 

340 @property 

341 def model_keys(self): 

342 return self._model_keys 

343 

344 def figure(self): 

345 """Create a figure (view) of the registered models to the screen.""" 

346 if self._figure is None: 

347 

348 self._figure, ax = plt.subplots(nrows=1, dpi=self._dpi) 

349 if self._verbose: 

350 print(f" Figure dpi set to {self._dpi}") 

351 

352 self._figure.set_size_inches(self._size) 

353 if self._verbose: 

354 print(" Figure size set to " + str(self._size) + " inches.") 

355 

356 for model in self._models: 

357 xs, ys, _ = zip(*model._nodes) 

358 

359 for face in model._elements: 

360 xf = tuple(xs[k - 1] for k in face) # 1-base index to 0-base index 

361 yf = tuple(ys[k - 1] for k in face) 

362 # plt.fill( 

363 # xf, 

364 # yf, 

365 # linestyle="dotted", 

366 # edgecolor="magenta", 

367 # alpha=0.5, 

368 # facecolor="gray", 

369 # ) 

370 plt.fill( 

371 xf, 

372 yf, 

373 alpha=model._alpha, 

374 edgecolor=model._edgecolor, 

375 facecolor=model._facecolor, 

376 linestyle=model._linestyle, 

377 linewidth=model._linewidth, 

378 ) 

379 

380 if self._xticks: 

381 ax.set_xticks(self._xticks) 

382 

383 if self._yticks: 

384 ax.set_yticks(self._yticks) 

385 

386 if self._xlim: 

387 ax.set_xlim(self._xlim) 

388 

389 if self._ylim: 

390 ax.set_ylim(self._ylim) 

391 

392 if self._xlabel: 

393 ax.set_xlabel(self._xlabel) 

394 

395 if self._ylabel: 

396 ax.set_ylabel(self._ylabel) 

397 

398 # set frame on or off based on the Bool "frame" in .json input 

399 ax.set_frame_on(b=self._frame) 

400 if len(self._tick_params) > 0: 

401 ax.tick_params(**self._tick_params) 

402 

403 if self._display: 

404 plt.show() 

405 

406 if self._serialize: 

407 self.serialize(self._folder, self._file) 

408 

409 plt.close("all") 

410 self._figure = None 

411 

412 

413""" 

414Copyright 2023 Sandia National Laboratories 

415 

416Notice: This computer software was prepared by National Technology and Engineering Solutions of 

417Sandia, LLC, hereinafter the Contractor, under Contract DE-NA0003525 with the Department of Energy 

418(DOE). All rights in the computer software are reserved by DOE on behalf of the United States 

419Government and the Contractor as provided in the Contract. You are authorized to use this computer 

420software for Governmental purposes but it is not to be released or distributed to the public. 

421NEITHER THE U.S. GOVERNMENT NOR THE CONTRACTOR MAKES ANY WARRANTY, EXPRESS OR IMPLIED, OR ASSUMES 

422ANY LIABILITY FOR THE USE OF THIS SOFTWARE. This notice including this sentence must appear on any 

423copies of this computer software. Export of this data may require a license from the United States 

424Government. 

425"""