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