#!/usr/bin/env python # -*- coding: utf-8 -*- import wx import wx.lib.plot as plot import datetime from pymisc import tupleslices, _ # Cursors import cStringIO def getZoomInCursor(): data = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x02\x03\x00\x00\x00\x9d\x19\xd5k\x00\x00\x00\tPLTE\x00\x00\x10\xff\xff\xff\x00\x00\x00\x14\x18\x1f\xbf\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x00bIDAT\x08\xd7c`\x80\x02\xd6P\x07\x10%\xb6*\x00De\xadZ\x02$\x19W\x86\xae\x04RlKC\xb3\x80\x92bSWF\x01%\xa5BW\x86M\x00RQ\xab\xa6aR\x109\xa8J\xa8>\xa0)\xab&\x80\xcd\x04\x1b*\xb6*j%\xd8>\xb7U`\x1b\x19\xc1\x92@\xe9%`J\nB\xb1A\x04\x19\x1d\xa0\xce\x03\x002\xa8"\xe9\xbd\x95.\xf3\x00\x00\x00\x00IEND\xaeB`\x82' stream = cStringIO.StringIO(data) return wx.CursorFromImage(wx.ImageFromStream(stream)) def getZoomOutCursor(): data = '\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x18\x00\x00\x00\x18\x02\x03\x00\x00\x00\x9d\x19\xd5k\x00\x00\x00\tPLTE\x00\x00x\xff\xff\xff\x00\x00\x00\xacA\x1d=\x00\x00\x00\x01tRNS\x00@\xe6\xd8f\x00\x00\x00aIDAT\x08\xd7c`\x80\x02\xd6P\x07\x10%\xb6*\x00De\xadZ\x02$\x19W\x86\xae\x04RlKC\xb3\x80\x92bSC\xa3\x80\x92R\xa1\xa1a\x13\x80T\xd4\xaai\x98\x14D\x0e\xaa\x12\xaa\x0fh\xca\xaa\t`3\xc1\x86\x8a\xad\x8aZ\t\xb6\xcfm\x15\xd8FF\xb0$Pz\t\x98\x92\x82Pl\x10AF\x07\xa8\xf3\x00\xac\xb9!\x99\xa0\xbeu\xe9\x00\x00\x00\x00IEND\xaeB`\x82' stream = cStringIO.StringIO(data) return wx.CursorFromImage(wx.ImageFromStream(stream)) class MyPlotCanvas(plot.PlotCanvas): MaxZoom = 4 # Minimum number of ticks visible MinX = 0 MaxX = 0 """Custom PlotCanvas with ability to set label texts for xticks, zoom buttons.""" def __init__(self, *args, **kwargs): plot.PlotCanvas.__init__(self, *args, **kwargs) self.canvas.Bind(wx.EVT_MOTION, self.UpdateCursor) self.zoomincursor = getZoomInCursor() self.zoomoutcursor = getZoomOutCursor() self.xticklabels = [] # [(xpos, label), ...] def UpdateCursor(self, evt): if evt.ControlDown(): self.SetCursor(self.zoomoutcursor) else: self.SetCursor(self.zoomincursor) def OnMouseLeftDown(self, evt): pass def OnMouseLeftUp(self, evt): if evt.ControlDown(): self.Zoom(self._getXY(evt), (2, 1)) else: self.Zoom(self._getXY(evt), (0.5, 1)) def Zoom(self, Center, Ratio): """ Zoom on the plot Centers on the X,Y coords given in Center Zooms by the Ratio = (Xratio, Yratio) given """ self.last_PointLabel = None #reset maker x,y = Center if self.last_draw != None: (graphics, xAxis, yAxis) = self.last_draw w = (xAxis[1] - xAxis[0]) * Ratio[0] if w < self.MaxZoom: w = self.MaxZoom if w > (self.MaxX - self.MinX): self.Reset() return xAxis = ( x - w/2, x + w/2 ) self._Draw(graphics, xAxis, yAxis) def SetShowScrollbars(self, value): """Set True to show scrollbars""" if value not in [True,False]: raise TypeError, "Value should be True or False" if value == self.GetShowScrollbars(): return self.sb_vert.Show(False) self.sb_hor.Show(value) wx.CallAfter(self.Layout) def GetShowScrollbars(self): """Set True to show scrollbars""" return self.sb_hor.IsShown() def _textwidth(self, text): dc = wx.PaintDC(self.canvas) dc.SetFont(self._getFont(self._fontSizeAxis)) return dc.GetTextExtent(text)[0] def _xticks(self, lower, upper): '''Return manually defined ticks that are in range lower, upper, and limit the number of visible ticks so that they don't overlap''' def filterticks(ticks): '''Return only ticks that are in range''' return [(xpos, label) for xpos, label in ticks if xpos <= upper and xpos >= lower] # We first determine the amount of ticks we take, and then which are in range # This way the grid doesn't move relative to lines when viewport changes all_in_range = filterticks(self.xticklabels) if not all_in_range: return [(0, "")] # No ticks in range labelwidth = self._textwidth(all_in_range[0][1]) * 2.0 graphwidth = self.GetSize()[0] - self._textwidth("Legend") # Rough approximation tickcount = int(graphwidth / labelwidth) tickcount = max(1, tickcount) tickcount = min(len(all_in_range), tickcount) skipticks = len(all_in_range) // tickcount ticks = self.xticklabels[::skipticks] ticks = filterticks(ticks) if not ticks: # Too small box or out of range, but the plot code assumes there is atleast one tick ticks = [(0, "")] return ticks class GraphPanel(wx.Panel): def __init__(self, *args, **kwargs): kwargs['style'] = wx.SUNKEN_BORDER | kwargs.get('style', 0) wx.Panel.__init__(self, *args, **kwargs) self.canvas = MyPlotCanvas(self) self.canvas.SetXSpec('auto') self.canvas.SetYSpec('auto') self.canvas.SetEnableGrid(True) self.canvas.SetEnableZoom(True) self.show_groupaverage = True self.show_sectionaverage = True self.title = "" self.setdata([]) self.Bind(wx.EVT_SIZE, self.OnResize) def OnResize(self, evt): self.canvas.SetSize(self.GetSize()) def setdata(self, data): """data as [(xlabel, per-animal, avg-group, avg-section), ...]""" self.canvas.xticklabels = [(i, data[i][0]) for i in range(len(data))] linewidth = 2 markersize = 1 points = [(i, data[i][1]) for i in range(len(data))] self.lines_animal = [] self.lines_animal.append(plot.PolyLine(points, width = linewidth, colour = 'blue')) self.lines_animal.append(plot.PolyMarker(points, marker = 'square', legend = _("One animal"), size = markersize, colour = 'blue')) points = [(i, data[i][2]) for i in range(len(data))] self.lines_group = [] self.lines_group.append(plot.PolyLine(points, width = linewidth, colour = 'green')) self.lines_group.append(plot.PolyMarker(points, marker = 'circle', legend = _("Group average"), size = markersize, colour = 'green')) points = [(i, data[i][3]) for i in range(len(data))] self.lines_section = [] self.lines_section.append(plot.PolyLine(points, width = linewidth, colour = 'red')) self.lines_section.append(plot.PolyMarker(points, marker = 'triangle', legend = _("Section average"), size = markersize, colour = 'red')) self.canvas.MinX = 0 self.canvas.MaxX = len(data) def redraw(self): self.canvas.SetEnableLegend(self.show_sectionaverage or self.show_groupaverage) elements = [] elements += self.lines_animal if self.show_groupaverage: elements += self.lines_group if self.show_sectionaverage: elements += self.lines_section graph = plot.PlotGraphics(elements, title = self.title, yLabel = _("TODO: unit")) self.canvas.Draw(graph) if __name__ == "__main__": print "Unit testing" class MyApp(wx.App): def OnInit(self): wx.InitAllImageHandlers() frame = wx.Frame(None, -1, "Graph unit testing") panel = GraphPanel(frame) date = datetime.datetime.today() dates = [date + datetime.timedelta(days = i) for i in range(0, 90)] panel.setdata([(d.strftime("%d.%m"), i, 1.05 ** i, i / 4.) for i, d in enumerate(dates)]) panel.redraw() frame.Show(True) self.SetTopWindow(frame) return True app = MyApp(0) app.MainLoop()