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

170 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-18 01:09 +0000

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

2# standard library imports 

3import os 

4from datetime import datetime 

5 

6# related third-party imports 

7import matplotlib.pyplot as plt 

8from PIL import Image 

9from pathlib import Path 

10 

11# local application/library specific imports 

12# from xyfigure.xybase import XYBase 

13# from xyfigure.code.xybase import XYBase 

14from xyfigure.xybase import XYBase 

15 

16 

17class XYViewBase(XYBase): 

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

19 

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

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

22 self._models = [] 

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

24 self._figure = None 

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

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

27 

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

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

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

31 

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

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

34 

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

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

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

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

39 

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

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

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

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

44 if self._latex: 

45 from matplotlib import rc 

46 

47 rc("text", usetex=True) 

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

49 

50 # default value if axes_kwargs not client-supplied 

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

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

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

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

55 

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

57 # 

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

59 # default to empty dictionary 

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

61 

62 print("Finished XYViewBase constructor.") 

63 

64 @property 

65 def models(self): 

66 return self._models 

67 

68 @models.setter 

69 def models(self, value): 

70 self._models = value 

71 

72 @property 

73 def model_keys(self): 

74 return self._model_keys 

75 

76 def figure(self): 

77 pass 

78 

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

80 # deprecated 

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

82 # self._figure.savefig( 

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

84 # ) # avoid cutoff of labels 

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

86 

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

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

89 self._figure.savefig( 

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

91 ) # avoid cutoff of labels 

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

93 

94 

95class XYView(XYViewBase): 

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

97 

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

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

100 # self._models = [] 

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

102 # self._figure = None 

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

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

105 

106 # default value if figure_kwargs not client-supplied 

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

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

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

110 

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

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

113 

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

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

116 

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

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

119 

120 # inches, U.S. paper, landscape 

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

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

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

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

125 

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

127 

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

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

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

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

132 # if self._latex: 

133 # from matplotlib import rc 

134 

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

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

137 

138 # @property 

139 # def models(self): 

140 # return self._models 

141 

142 # @models.setter 

143 # def models(self, value): 

144 # self._models = value 

145 

146 # @property 

147 # def model_keys(self): 

148 # return self._model_keys 

149 

150 def figure(self): 

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

152 if self._figure is None: 

153 

154 # dpi versus fig size 

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

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

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

158 if self._verbose: 

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

160 

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

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

163 

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

165 # fig.set_size_inches(self._size) 

166 self._figure.set_size_inches(self._size) 

167 if self._verbose: 

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

169 

170 if self._background_image: 

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

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

173 # rel_path_and_file = os.path.join( 

174 # folder, file 

175 # ) # relative to current run location 

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

177 # im = Image.open(rel_path_and_file) 

178 im = Image.open(path_file) 

179 

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

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

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

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

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

185 

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

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

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

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

190 

191 for model in self._models: 

192 # needs rearchitecting, a logview descends from a view 

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

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

195 elif not self._xaxislog and self._yaxislog: 

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

197 elif self._xaxislog and self._yaxislog: 

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

199 else: 

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

201 

202 if self._xticks: 

203 ax.set_xticks(self._xticks) 

204 

205 if self._yticks: 

206 ax.set_yticks(self._yticks) 

207 

208 if self._xlim: 

209 ax.set_xlim(self._xlim) 

210 

211 if self._ylim: 

212 ax.set_ylim(self._ylim) 

213 

214 if self._yaxis_rhs: 

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

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

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

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

219 

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

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

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

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

224 ax2.yaxis.tick_right() 

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

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

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

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

229 # if _ticklabel_format: 

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

231 # ax2.ticklabel_format(**_ticklabel_format) 

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

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

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

235 if rhs_yticks: 

236 ax2.set_yticks(rhs_yticks) 

237 ax2.yaxis.set_label_position("right") 

238 ax2.set_ylabel(rhs_axis_label) 

239 

240 # fig.suptitle(self._title) 

241 self._figure.suptitle(self._title) 

242 ax.set_xlabel(self._xlabel) 

243 ax.set_ylabel(self._ylabel) 

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

245 ax.set_frame_on(b=self._frame) 

246 if len(self._tick_params) > 0: 

247 ax.tick_params(**self._tick_params) 

248 ax.grid() 

249 ax.legend() 

250 

251 if self._details: 

252 now = datetime.now() 

253 now_str = now.strftime("%Y-%m-%d %H:%M:%S") 

254 user = str(os.getlogin()) 

255 host = str(os.getenv("HOSTNAME")) 

256 details_str = ( 

257 self._file + " created " + now_str + " by " + user + " on " + host 

258 ) 

259 ax.set_title(details_str, fontsize=10, ha="center", color="dimgray") 

260 

261 if self._display: 

262 plt.show() 

263 

264 if self._serialize: 

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

266 

267 plt.close("all") 

268 self._figure = None 

269 

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

271 # # deprecated 

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

273 # # self._figure.savefig( 

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

275 # # ) # avoid cutoff of labels 

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

277 

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

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

280 # self._figure.savefig( 

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

282 # ) # avoid cutoff of labels 

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

284 

285 

286class XYViewAbaqus(XYViewBase): 

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

288 

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

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

291 # self._models = [] 

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

293 # self._figure = None 

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

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

296 

297 # default value if figure_kwargs not client-supplied 

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

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

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

301 

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

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

304 

305 # inches, U.S. paper, landscape 

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

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

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

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

310 

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

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

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

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

315 # if self._latex: 

316 # from matplotlib import rc 

317 

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

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

320 

321 @property 

322 def models(self): 

323 return self._models 

324 

325 @models.setter 

326 def models(self, value): 

327 self._models = value 

328 

329 @property 

330 def model_keys(self): 

331 return self._model_keys 

332 

333 def figure(self): 

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

335 if self._figure is None: 

336 

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

338 if self._verbose: 

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

340 

341 self._figure.set_size_inches(self._size) 

342 if self._verbose: 

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

344 

345 for model in self._models: 

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

347 

348 for face in model._elements: 

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

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

351 # plt.fill( 

352 # xf, 

353 # yf, 

354 # linestyle="dotted", 

355 # edgecolor="magenta", 

356 # alpha=0.5, 

357 # facecolor="gray", 

358 # ) 

359 plt.fill( 

360 xf, 

361 yf, 

362 alpha=model._alpha, 

363 edgecolor=model._edgecolor, 

364 facecolor=model._facecolor, 

365 linestyle=model._linestyle, 

366 linewidth=model._linewidth, 

367 ) 

368 

369 if self._xticks: 

370 ax.set_xticks(self._xticks) 

371 

372 if self._yticks: 

373 ax.set_yticks(self._yticks) 

374 

375 if self._xlim: 

376 ax.set_xlim(self._xlim) 

377 

378 if self._ylim: 

379 ax.set_ylim(self._ylim) 

380 

381 if self._xlabel: 

382 ax.set_xlabel(self._xlabel) 

383 

384 if self._ylabel: 

385 ax.set_ylabel(self._ylabel) 

386 

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

388 ax.set_frame_on(b=self._frame) 

389 if len(self._tick_params) > 0: 

390 ax.tick_params(**self._tick_params) 

391 

392 if self._display: 

393 plt.show() 

394 

395 if self._serialize: 

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

397 

398 plt.close("all") 

399 self._figure = None 

400 

401 

402""" 

403Copyright 2023 Sandia National Laboratories 

404 

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

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

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

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

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

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

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

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

413Government. 

414"""