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
« 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
8# related third-party imports
9import matplotlib.pyplot as plt
10from PIL import Image
11from tzlocal import get_localzone
13# local application/library specific imports
14# from xyfigure.xybase import XYBase
15# from xyfigure.code.xybase import XYBase
16from xyfigure.xybase import XYBase
19class XYViewBase(XYBase):
20 """The base class used by all XYViews."""
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]
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")
34 self._xticks = kwargs.get("xticks", None)
35 self._yticks = kwargs.get("yticks", None)
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)
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
49 rc("text", usetex=True)
50 rc("font", family="serif")
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)
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", {})
64 print("Finished XYViewBase constructor.")
66 @property
67 def models(self):
68 return self._models
70 @models.setter
71 def models(self, value):
72 self._models = value
74 @property
75 def model_keys(self):
76 return self._model_keys
78 def figure(self):
79 pass
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}")
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}")
97class XYView(XYViewBase):
98 """Creates a view that sees models."""
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]
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")
113 # self._xticks = kwargs.get("xticks", None)
114 # self._yticks = kwargs.get("yticks", None)
116 self._xaxislog = kwargs.get("xaxis_log", False)
117 self._yaxislog = kwargs.get("yaxis_log", False)
119 # default = {'scale': 1, 'label': 'ylabel_rhs', 'verification': 0}
120 self._yaxis_rhs = kwargs.get("yaxis_rhs", None)
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)
128 self._background_image = kwargs.get("background_image", None)
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
137 # rc("text", usetex=True)
138 # rc("font", family="serif")
140 # @property
141 # def models(self):
142 # return self._models
144 # @models.setter
145 # def models(self, value):
146 # self._models = value
148 # @property
149 # def model_keys(self):
150 # return self._model_keys
152 def figure(self):
153 """Create a figure (view) of the registered models to the screen."""
154 if self._figure is None:
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}")
163 # ax.ticklabel_format(axis='y', style='scientific')
164 # ax.ticklabel_format(axis='both', style='scientific', scilimits=(0,0))
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.")
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)
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)
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")
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)
204 if self._xticks:
205 ax.set_xticks(self._xticks)
207 if self._yticks:
208 ax.set_yticks(self._yticks)
210 if self._xlim:
211 ax.set_xlim(self._xlim)
213 if self._ylim:
214 ax.set_ylim(self._ylim)
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)
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)
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()
253 if self._details:
254 # Get the local timezone
255 local_timezone = get_localzone()
257 # Get the current time in the local timezone
258 current_time = datetime.now(local_timezone)
260 # Format the date and time stamp
261 timestamp = current_time.strftime("%Y-%m-%d %H:%M:%S %Z%z")
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 )
270 ax.set_title(details_str, fontsize=8, ha="center", color="dimgray")
272 if self._display:
273 plt.show()
275 if self._serialize:
276 self.serialize(self._folder, self._file)
278 plt.close("all")
279 self._figure = None
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}")
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}")
297class XYViewAbaqus(XYViewBase):
298 """Creates an ABAQUS view that sees ABAQUS models."""
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]
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")
313 # self._xticks = kwargs.get("xticks", None)
314 # self._yticks = kwargs.get("yticks", None)
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)
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
329 # rc("text", usetex=True)
330 # rc("font", family="serif")
332 @property
333 def models(self):
334 return self._models
336 @models.setter
337 def models(self, value):
338 self._models = value
340 @property
341 def model_keys(self):
342 return self._model_keys
344 def figure(self):
345 """Create a figure (view) of the registered models to the screen."""
346 if self._figure is None:
348 self._figure, ax = plt.subplots(nrows=1, dpi=self._dpi)
349 if self._verbose:
350 print(f" Figure dpi set to {self._dpi}")
352 self._figure.set_size_inches(self._size)
353 if self._verbose:
354 print(" Figure size set to " + str(self._size) + " inches.")
356 for model in self._models:
357 xs, ys, _ = zip(*model._nodes)
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 )
380 if self._xticks:
381 ax.set_xticks(self._xticks)
383 if self._yticks:
384 ax.set_yticks(self._yticks)
386 if self._xlim:
387 ax.set_xlim(self._xlim)
389 if self._ylim:
390 ax.set_ylim(self._ylim)
392 if self._xlabel:
393 ax.set_xlabel(self._xlabel)
395 if self._ylabel:
396 ax.set_ylabel(self._ylabel)
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)
403 if self._display:
404 plt.show()
406 if self._serialize:
407 self.serialize(self._folder, self._file)
409 plt.close("all")
410 self._figure = None
413"""
414Copyright 2023 Sandia National Laboratories
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"""