Source code for bdsim.graphics

import matplotlib
import matplotlib.pyplot as plt
from matplotlib import animation
from bdsim.components import SinkBlock

[docs]class GraphicsBlock(SinkBlock): """ A GraphicsBlock is a subclass of SinkBlock that represents a block that has inputs but no outputs and creates/updates a graphical display. """ blockclass='graphics'
[docs] def __init__(self, movie=None, **blockargs): """ Create a graphical display block. :param movie: Save animation in this file in MP4 format, defaults to None :type movie: str, optional :param blockargs: |BlockOptions| :type blockargs: dict :return: transfer function block base class :rtype: TransferBlock This is the parent class of all graphic display blocks. """ super().__init__(**blockargs) self._graphics = True self.movie = movie
[docs] def start(self): plt.draw() plt.show(block=False) if self.movie is not None and not self.bd.options.animation: print('enabling global animation option to allow movie option on block', self) self.bd.options.animation = True if self.movie is not None: self.writer = animation.FFMpegWriter(fps=10, extra_args=['-vcodec', 'libx264']) self.writer.setup(fig=self.fig, outfile=self.movie) print('movie block', self, ' --> ', self.movie)
[docs] def step(self, state=None): super().step() if state.options.animation: self.fig.canvas.flush_events() if self.movie is not None: self.writer.grab_frame()
[docs] def done(self, state=None, block=False, **kwargs): if self.fig is not None: self.fig.canvas.start_event_loop(0.001) if self.movie is not None: self.writer.finish() # self.cleanup() plt.show(block=block)
[docs] def savefig(self, filename=None, format='pdf', **kwargs): """ Save the figure as an image file :param fname: Name of file to save graphics to :type fname: str :param ``**kwargs``: Options passed to `savefig <https://matplotlib.org/3.2.2/api/_as_gen/matplotlib.pyplot.savefig.html>`_ The file format is taken from the file extension and can be jpeg, png or pdf. """ try: plt.figure(self.fig.number) # make block's figure the current one if filename is None: filename = self.name filename += "." + format print('saved {} -> {}'.format(str(self), filename)) plt.savefig(filename, **kwargs) # save the current figure except: pass
[docs] def create_figure(self, state): def move_figure(f, x, y): """Move figure's upper left corner to pixel (x, y)""" backend = matplotlib.get_backend() x = int(x) + gstate.xoffset y = int(y) if backend == 'TkAgg': f.canvas.manager.window.wm_geometry("+%d+%d" % (x, y)) elif backend == 'WXAgg': f.canvas.manager.window.SetPosition((x, y)) else: # This works for QT and GTK # You can also use window.setGeometry f.canvas.manager.window.move(x, y) gstate = state options = state.options print('#figs', plt.get_fignums()) if gstate.fignum == 0: # no figures yet created, lazy initialization matplotlib.use(options.backend) mpl_backend = matplotlib.get_backend() # split the string ntiles = [int(x) for x in options.tiles.split('x')] print("Graphics:") print(' backend:', mpl_backend) xoffset = 0 if options.shape is None: if mpl_backend == 'Qt5Agg': # next line actually creates a figure if none already exist screen = plt.get_current_fig_manager().canvas.screen() # this is a QScreenClass object, see https://doc.qt.io/qt-5/qscreen.html#availableGeometry-prop # next line creates a figure sz = screen.availableSize() dpiscale = screen.devicePixelRatio() # is 2.0 for Mac laptop screen print(sz.width(), sz.height(), dpiscale) # check for a second screen if options.altscreen: vsize = screen.availableVirtualGeometry().getCoords() if vsize[0] < 0: # extra monitor to the left xoffset = vsize[0] elif vsize[0] >= sz[0]: # extra monitor to the right xoffset = vsize[0] screen_width, screen_height = sz.width(), sz.height() dpi = screen.physicalDotsPerInch() f = plt.gcf() elif mpl_backend == 'TkAgg': print(' #figs', plt.get_fignums()) window = plt.get_current_fig_manager().window screen_width, screen_height = window.winfo_screenwidth(), window.winfo_screenheight() dpiscale = 1 print(' Size: %d x %d' % (screen_width, screen_height)) f = plt.gcf() dpi = f.dpi else: # all other backends f = plt.figure() dpi = f.dpi screen_width, screen_height = f.get_size_inches() * f.dpi # compute fig size in inches (width, height) figsize = [ screen_width / ntiles[1] / dpi, screen_height / ntiles[0] / dpi ] else: # shape is given explictly screen_width, screen_height = [int(x) for x in options.shape.split(':')] f = plt.gcf() f.canvas.manager.set_window_title(f"bdsim: Figure {f.number:d}") # save graphics info away in state gstate.figsize = figsize gstate.dpi = dpi gstate.screensize_pix = (screen_width, screen_height) gstate.ntiles = ntiles gstate.xoffset = xoffset # resize the figure f.set_size_inches(figsize, forward=True) plt.ion() else: # subsequent figures f = plt.figure(figsize=gstate.figsize) print(' #figs', plt.get_fignums()) # move the figure to right place on screen row = gstate.fignum // gstate.ntiles[0] col = gstate.fignum % gstate.ntiles[0] move_figure(f, col * gstate.figsize[0] * gstate.dpi, row * gstate.figsize[1] * gstate.dpi) gstate.fignum += 1 #print('create figure', self.fignum, row, col) print(f) return f