Source code for src.gui.graphing.basicframe

"""A basic, standalone, single-graph frame for displaying data.
"""

import logging
from PIL import Image
import Queue
import tempfile
import time
import threading
import wx
from wx.lib import newevent

from src.core.configuration import c
from src.core.graph import AbstractGraphManager
from src.gui.graphing.basicpanel import GraphPanel
from src.tools.general import gridArrangement

__all__ = ['StandardGraphManager', 'GraphThread', 'GraphFrame']
log = logging.getLogger('transport')

ASCHECK = wx.ID_ANY
FIG_WIDTH = 380
FIG_HEIGHT = 280

UPDATE_DELAY = c.getGraphDelay()

[docs]class StandardGraphManager(AbstractGraphManager): """A class for sending data from experiments to GUI panels. Parameters ---------- parentFrame : wx.Window The frame which will be used as the parent for the `GraphFrame` objects spawned by this manager. """ def __init__(self, parentFrame): """Instantiate a graph manager.""" super(StandardGraphManager, self).__init__(parentFrame) self.parentFrame = parentFrame
[docs] def setGraphs(self, graphs): """Set the graphs which will be managed. Parameters ---------- graphs : list of Graph The `Graph` objects which will send their data to the frames managed by this object. """ self.__graphs = graphs self.__dataQueues = [] self.__graphThreads = [] self.__figures = [] for g in self.__graphs: currdq = Queue.Queue() self.__dataQueues.append(currdq) g.setDataQueue(currdq) currgt = GraphThread(self.parentFrame, currdq, g.getColumns()) self.__graphThreads.append(currgt)
[docs] def abort(self, timeout=10): """Abort the update threads.""" log.info('Ending graph manager.') for gt in self.__graphThreads: gt.abort() for gt in self.__graphThreads: gt.join(timeout) self.__figures.append(gt.getFigure()) self.__dataQueues = [] self.__graphThreads = []
[docs] def start(self): """Start the update threads.""" for gt in self.__graphThreads: gt.start()
[docs] def getGraphs(self): """Return the figures from the graphing panels.""" figs = [] for figthread in self.__graphThreads: figs.append(figthread.getFigure()) return figs
[docs] def saveGraphs(self, filename): """Save the graphs to a file. Resize all graphs to the appropriate size. Then arrange them into a grid and save that grid to the file specified by `filename`. Parameters ---------- filename : str A string pointing to which the graph images should be saved. Note that all graphs go to the same file. `filename` should be a complete path name, including an extension. """ if __debug__: log.debug('Preparing to save graphs to file %s...', filename) output = [] for figure in self.__figures: subFile = tempfile.mktemp('.png') output.append((figure, subFile)) if len(output) > 0: log.debug('Attempting to save graphs to file %s...', filename) wx.CallAfter(_saveSub, output, filename) else: log.debug('No graphs present; aborting the save operation.')
[docs]class GraphThread(threading.Thread): """A thread to update a particular graph at regular intervals. Parameters ---------- parentFrame : wx.Window The frame or panel which will contain the Frame managed by this thread. dataQueue : Queue The `Queue` object which will move data from the `Graph` object to the `GraphFrame` associated with this thread (and, therefore, to the `GraphPanel`. columns : 2-tuple of str Strings representing the column names which provide the x- and y-values for the graph. These strings will label the axes in the `GraphPanel`. """ def __init__(self, parentFrame, dataQueue, columns): super(GraphThread, self).__init__() self.setDaemon(True) (self.UpdateEvent, self.EVT_UPDATE_GRAPH) = newevent.NewEvent() graphFrame = GraphFrame(parentFrame, dataQueue, self, columns) graphFrame.Show() self.graphPanel = graphFrame.graphPanel self.graphPanel.Bind(self.EVT_UPDATE_GRAPH, self.graphPanel.onUpdate) self.keepGoing = True self.running = False self.figure = None if __debug__: log.debug('Graph frame shown.') def abort(self): self.keepGoing = False def run(self): log.info('Beginning execution of graph thread.') self.running = True while self.keepGoing: evt = self.UpdateEvent() wx.PostEvent(self.graphPanel, evt) time.sleep(UPDATE_DELAY) wx.PostEvent(self.graphPanel, evt) log.info('Ending execution of graph thread.') self.running = False def getFigure(self): return self.graphPanel.fig def isRunning(self): return self.running
[docs]class GraphFrame(wx.Frame): """A frame for containing a single graph. Parameters ---------- parent : Frame This frame's parent (so that if the parent is closed, this will also be closed). dataQueue : Queue The `Queue.Queue` object which will be passed to the panel for transferring data from the `Graph`. thread : Thread The `threading.Thread` object in which the graph will run. columns : iterable A list or tuple containing the names of the columns relevent to this graph. In order, they are the x-column, the y-column, and the column which triggers new plots. """ __all__ = ['update', 'getFigure'] def __init__(self, parent, dataQueue, thread, columns): super(GraphFrame, self).__init__(parent, wx.ID_ANY) self.SetTitle('%s vs %s' % (columns[1], columns[0])) self.thread = thread self.SetSize((400,350)) self.SetMinSize((400,300)) self.Bind(wx.EVT_CLOSE, self.onClose) self.graphPanel = GraphPanel(self, columns, None, dataQueue) mainbox = wx.BoxSizer(wx.VERTICAL) mainbox.Add(self.graphPanel, proportion=1, flag=wx.EXPAND) self.SetAutoLayout(True) self.SetSizer(mainbox) self.Layout() if __debug__: log.debug('GraphFrame created: ' + self.GetTitle()) def getFigure(self): return self.graphPanel.fig def onClose(self, event): if self.thread.isRunning(): self.thread.abort() self.thread.join() self.Show(False)
def _saveSub(data, outputFilename): filenames = [] for fig, filename in data: filenames.append(filename) fig.savefig(filename) rows, cols = gridArrangement(len(filenames)) outputImage = Image.new("RGBA", ((FIG_WIDTH+20)*cols, (FIG_HEIGHT+20)*rows)) index = 0 for row in range(rows): for col in range(cols): if index >= len(filenames): break curr = Image.open(filenames[index]) curr = curr.resize((FIG_WIDTH, FIG_HEIGHT), Image.ANTIALIAS) x = (10 + FIG_WIDTH)*col + 10 y = (10 + FIG_HEIGHT)*row + 10 outputImage.paste(curr, (x, y)) index += 1 outputImage.save(outputFilename)