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
« 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
6# related third-party imports
7import matplotlib.pyplot as plt
8from PIL import Image
9from pathlib import Path
11# local application/library specific imports
12# from xyfigure.xybase import XYBase
13# from xyfigure.code.xybase import XYBase
14from xyfigure.xybase import XYBase
17class XYViewBase(XYBase):
18 """The base class used by all XYViews."""
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]
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")
32 self._xticks = kwargs.get("xticks", None)
33 self._yticks = kwargs.get("yticks", None)
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)
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
47 rc("text", usetex=True)
48 rc("font", family="serif")
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)
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", {})
62 print("Finished XYViewBase constructor.")
64 @property
65 def models(self):
66 return self._models
68 @models.setter
69 def models(self, value):
70 self._models = value
72 @property
73 def model_keys(self):
74 return self._model_keys
76 def figure(self):
77 pass
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}")
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}")
95class XYView(XYViewBase):
96 """Creates a view that sees models."""
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]
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")
111 # self._xticks = kwargs.get("xticks", None)
112 # self._yticks = kwargs.get("yticks", None)
114 self._xaxislog = kwargs.get("xaxis_log", False)
115 self._yaxislog = kwargs.get("yaxis_log", False)
117 # default = {'scale': 1, 'label': 'ylabel_rhs', 'verification': 0}
118 self._yaxis_rhs = kwargs.get("yaxis_rhs", None)
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)
126 self._background_image = kwargs.get("background_image", None)
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
135 # rc("text", usetex=True)
136 # rc("font", family="serif")
138 # @property
139 # def models(self):
140 # return self._models
142 # @models.setter
143 # def models(self, value):
144 # self._models = value
146 # @property
147 # def model_keys(self):
148 # return self._model_keys
150 def figure(self):
151 """Create a figure (view) of the registered models to the screen."""
152 if self._figure is None:
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}")
161 # ax.ticklabel_format(axis='y', style='scientific')
162 # ax.ticklabel_format(axis='both', style='scientific', scilimits=(0,0))
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.")
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)
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)
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")
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)
202 if self._xticks:
203 ax.set_xticks(self._xticks)
205 if self._yticks:
206 ax.set_yticks(self._yticks)
208 if self._xlim:
209 ax.set_xlim(self._xlim)
211 if self._ylim:
212 ax.set_ylim(self._ylim)
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)
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)
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()
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")
261 if self._display:
262 plt.show()
264 if self._serialize:
265 self.serialize(self._folder, self._file)
267 plt.close("all")
268 self._figure = None
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}")
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}")
286class XYViewAbaqus(XYViewBase):
287 """Creates an ABAQUS view that sees ABAQUS models."""
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]
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")
302 # self._xticks = kwargs.get("xticks", None)
303 # self._yticks = kwargs.get("yticks", None)
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)
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
318 # rc("text", usetex=True)
319 # rc("font", family="serif")
321 @property
322 def models(self):
323 return self._models
325 @models.setter
326 def models(self, value):
327 self._models = value
329 @property
330 def model_keys(self):
331 return self._model_keys
333 def figure(self):
334 """Create a figure (view) of the registered models to the screen."""
335 if self._figure is None:
337 self._figure, ax = plt.subplots(nrows=1, dpi=self._dpi)
338 if self._verbose:
339 print(f" Figure dpi set to {self._dpi}")
341 self._figure.set_size_inches(self._size)
342 if self._verbose:
343 print(" Figure size set to " + str(self._size) + " inches.")
345 for model in self._models:
346 xs, ys, _ = zip(*model._nodes)
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 )
369 if self._xticks:
370 ax.set_xticks(self._xticks)
372 if self._yticks:
373 ax.set_yticks(self._yticks)
375 if self._xlim:
376 ax.set_xlim(self._xlim)
378 if self._ylim:
379 ax.set_ylim(self._ylim)
381 if self._xlabel:
382 ax.set_xlabel(self._xlabel)
384 if self._ylabel:
385 ax.set_ylabel(self._ylabel)
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)
392 if self._display:
393 plt.show()
395 if self._serialize:
396 self.serialize(self._folder, self._file)
398 plt.close("all")
399 self._figure = None
402"""
403Copyright 2023 Sandia National Laboratories
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"""