X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/8b9a4190f70909de9568f45389e7aa3ecbc66b8a..42f8298f6f42d5d63bb3caf65682b7d9d9f8b702:/wxPython/wx/lib/wxPlotCanvas.py diff --git a/wxPython/wx/lib/wxPlotCanvas.py b/wxPython/wx/lib/wxPlotCanvas.py index 44c80bee2d..8b96c6e0e2 100644 --- a/wxPython/wx/lib/wxPlotCanvas.py +++ b/wxPython/wx/lib/wxPlotCanvas.py @@ -1,8 +1,489 @@ +""" +This is a port of Konrad Hinsen's tkPlotCanvas.py plotting module. +After thinking long and hard I came up with the name "wxPlotCanvas.py". -"""Renamer stub: provides a way to drop the wx prefix from wxPython objects.""" +This file contains two parts; first the re-usable library stuff, then, after +a "if __name__=='__main__'" test, a simple frame and a few default plots +for testing. -from wx import _rename -from wxPython.lib import wxPlotCanvas -_rename(globals(), wxPlotCanvas.__dict__, modulename='lib.wxPlotCanvas') -del wxPlotCanvas -del _rename +Harm van der Heijden, feb 1999 + +Original comment follows below: +# This module defines a plot widget for Tk user interfaces. +# It supports only elementary line plots at the moment. +# See the example at the end for documentation... +# +# Written by Konrad Hinsen +# With contributions from RajGopal Srinivasan +# Last revision: 1998-7-28 +# +""" +# 12/13/2003 - Jeff Grimmett (grimmtooth@softhome.net) +# +# o Updated for V2.5 compatability +# o wx.SpinCtl has some issues that cause the control to +# lock up. Noted in other places using it too, it's not this module +# that's at fault. +# o Added deprecation warning. +# + +import warnings +import wx + +warningmsg = r"""\ + +THIS MODULE IS NOW DEPRECATED + +This module has been replaced by wxPyPlot, which in wxPython +can be found in wx.lib.plot.py. + +""" + +warnings.warn(warningmsg, DeprecationWarning, stacklevel=2) + +# Not everybody will have Numeric, so let's be cool about it... +try: + import Numeric +except: + # bummer! + msg = """This module requires the Numeric module, which could not be +imported. It probably is not installed (it's not part of the standard +Python distribution). See the Python site (http://www.python.org) for +information on downloading source or binaries.""" + + print msg + if wx.Platform == '__WXMSW__' and wx.GetApp() is not None: + d = wx.MessageDialog(None, msg, "Numeric not found") + if d.ShowModal() == wx.ID_CANCEL: + d = wx.MessageDialog(None, "I kid you not! Pressing Cancel won't help you!", "Not a joke", wx.OK) + d.ShowModal() + raise + +# +# Plotting classes... +# +class PolyPoints: + + def __init__(self, points, attr): + self.points = Numeric.array(points) + self.scaled = self.points + self.attributes = {} + for name, value in self._attributes.items(): + try: + value = attr[name] + except KeyError: pass + self.attributes[name] = value + + def boundingBox(self): + return Numeric.minimum.reduce(self.points), \ + Numeric.maximum.reduce(self.points) + + def scaleAndShift(self, scale=1, shift=0): + self.scaled = scale*self.points+shift + + +class PolyLine(PolyPoints): + + def __init__(self, points, **attr): + PolyPoints.__init__(self, points, attr) + + _attributes = {'color': 'black', + 'width': 1} + + def draw(self, dc): + color = self.attributes['color'] + width = self.attributes['width'] + arguments = [] + dc.SetPen(wx.Pen(wx.NamedColour(color), width)) + dc.DrawLines(map(tuple,self.scaled)) + + +class PolyMarker(PolyPoints): + + def __init__(self, points, **attr): + + PolyPoints.__init__(self, points, attr) + + _attributes = {'color': 'black', + 'width': 1, + 'fillcolor': None, + 'size': 2, + 'fillstyle': wx.SOLID, + 'outline': 'black', + 'marker': 'circle'} + + def draw(self, dc): + color = self.attributes['color'] + width = self.attributes['width'] + size = self.attributes['size'] + fillcolor = self.attributes['fillcolor'] + fillstyle = self.attributes['fillstyle'] + marker = self.attributes['marker'] + + dc.SetPen(wx.Pen(wx.NamedColour(color),width)) + if fillcolor: + dc.SetBrush(wx.Brush(wx.NamedColour(fillcolor),fillstyle)) + else: + dc.SetBrush(wx.Brush(wx.NamedColour('black'), wx.TRANSPARENT)) + + self._drawmarkers(dc, self.scaled, marker, size) + + def _drawmarkers(self, dc, coords, marker,size=1): + f = eval('self._' +marker) + for xc, yc in coords: + f(dc, xc, yc, size) + + def _circle(self, dc, xc, yc, size=1): + dc.DrawEllipse(xc-2.5*size,yc-2.5*size, 5.*size,5.*size) + + def _dot(self, dc, xc, yc, size=1): + dc.DrawPoint(xc,yc) + + def _square(self, dc, xc, yc, size=1): + dc.DrawRectangle(xc-2.5*size,yc-2.5*size,5.*size,5.*size) + + def _triangle(self, dc, xc, yc, size=1): + dc.DrawPolygon([(-0.5*size*5,0.2886751*size*5), + (0.5*size*5,0.2886751*size*5), + (0.0,-0.577350*size*5)],xc,yc) + + def _triangle_down(self, dc, xc, yc, size=1): + dc.DrawPolygon([(-0.5*size*5,-0.2886751*size*5), + (0.5*size*5,-0.2886751*size*5), + (0.0,0.577350*size*5)],xc,yc) + + def _cross(self, dc, xc, yc, size=1): + dc.DrawLine(xc-2.5*size, yc-2.5*size, xc+2.5*size,yc+2.5*size) + dc.DrawLine(xc-2.5*size,yc+2.5*size, xc+2.5*size,yc-2.5*size) + + def _plus(self, dc, xc, yc, size=1): + dc.DrawLine(xc-2.5*size,yc, xc+2.5*size,yc) + dc.DrawLine(xc,yc-2.5*size,xc, yc+2.5*size) + +class PlotGraphics: + + def __init__(self, objects): + self.objects = objects + + def boundingBox(self): + p1, p2 = self.objects[0].boundingBox() + for o in self.objects[1:]: + p1o, p2o = o.boundingBox() + p1 = Numeric.minimum(p1, p1o) + p2 = Numeric.maximum(p2, p2o) + return p1, p2 + + def scaleAndShift(self, scale=1, shift=0): + for o in self.objects: + o.scaleAndShift(scale, shift) + + def draw(self, canvas): + for o in self.objects: + o.draw(canvas) + + def __len__(self): + return len(self.objects) + + def __getitem__(self, item): + return self.objects[item] + + +class PlotCanvas(wx.Window): + + def __init__(self, parent, id=-1, + pos = wx.DefaultPosition, size = wx.DefaultSize, + style = 0, name = 'plotCanvas'): + wx.Window.__init__(self, parent, id, pos, size, style, name) + self.border = (1,1) + self.SetClientSize((400,400)) + self.SetBackgroundColour("white") + + self.Bind(wx.EVT_SIZE,self.reconfigure) + self.Bind(wx.EVT_PAINT, self.OnPaint) + self._setsize() + self.last_draw = None +# self.font = self._testFont(font) + + def OnPaint(self, event): + pdc = wx.PaintDC(self) + if self.last_draw is not None: + apply(self.draw, self.last_draw + (pdc,)) + + def reconfigure(self, event): + (new_width,new_height) = self.GetClientSize() + if new_width == self.width and new_height == self.height: + return + self._setsize() + # self.redraw() + + def _testFont(self, font): + if font is not None: + bg = self.canvas.cget('background') + try: + item = CanvasText(self.canvas, 0, 0, anchor=NW, + text='0', fill=bg, font=font) + self.canvas.delete(item) + except TclError: + font = None + return font + + def _setsize(self): + (self.width,self.height) = self.GetClientSize(); + self.plotbox_size = 0.97*Numeric.array([self.width, -self.height]) + xo = 0.5*(self.width-self.plotbox_size[0]) + yo = self.height-0.5*(self.height+self.plotbox_size[1]) + self.plotbox_origin = Numeric.array([xo, yo]) + + def draw(self, graphics, xaxis = None, yaxis = None, dc = None): + if dc == None: dc = wx.ClientDC(self) + dc.BeginDrawing() + dc.Clear() + self.last_draw = (graphics, xaxis, yaxis) + p1, p2 = graphics.boundingBox() + xaxis = self._axisInterval(xaxis, p1[0], p2[0]) + yaxis = self._axisInterval(yaxis, p1[1], p2[1]) + text_width = [0., 0.] + text_height = [0., 0.] + if xaxis is not None: + p1[0] = xaxis[0] + p2[0] = xaxis[1] + xticks = self._ticks(xaxis[0], xaxis[1]) + bb = dc.GetTextExtent(xticks[0][1]) + text_height[1] = bb[1] + text_width[0] = 0.5*bb[0] + bb = dc.GetTextExtent(xticks[-1][1]) + text_width[1] = 0.5*bb[0] + else: + xticks = None + if yaxis is not None: + p1[1] = yaxis[0] + p2[1] = yaxis[1] + yticks = self._ticks(yaxis[0], yaxis[1]) + for y in yticks: + bb = dc.GetTextExtent(y[1]) + text_width[0] = max(text_width[0],bb[0]) + h = 0.5*bb[1] + text_height[0] = h + text_height[1] = max(text_height[1], h) + else: + yticks = None + text1 = Numeric.array([text_width[0], -text_height[1]]) + text2 = Numeric.array([text_width[1], -text_height[0]]) + scale = (self.plotbox_size-text1-text2) / (p2-p1) + shift = -p1*scale + self.plotbox_origin + text1 + self._drawAxes(dc, xaxis, yaxis, p1, p2, + scale, shift, xticks, yticks) + graphics.scaleAndShift(scale, shift) + graphics.draw(dc) + dc.EndDrawing() + + def _axisInterval(self, spec, lower, upper): + if spec is None: + return None + if spec == 'minimal': + if lower == upper: + return lower-0.5, upper+0.5 + else: + return lower, upper + if spec == 'automatic': + range = upper-lower + if range == 0.: + return lower-0.5, upper+0.5 + log = Numeric.log10(range) + power = Numeric.floor(log) + fraction = log-power + if fraction <= 0.05: + power = power-1 + grid = 10.**power + lower = lower - lower % grid + mod = upper % grid + if mod != 0: + upper = upper - mod + grid + return lower, upper + if type(spec) == type(()): + lower, upper = spec + if lower <= upper: + return lower, upper + else: + return upper, lower + raise ValueError, str(spec) + ': illegal axis specification' + + def _drawAxes(self, dc, xaxis, yaxis, + bb1, bb2, scale, shift, xticks, yticks): + dc.SetPen(wx.Pen(wx.NamedColour('BLACK'),1)) + if xaxis is not None: + lower, upper = xaxis + text = 1 + for y, d in [(bb1[1], -3), (bb2[1], 3)]: + p1 = scale*Numeric.array([lower, y])+shift + p2 = scale*Numeric.array([upper, y])+shift + dc.DrawLine(p1[0],p1[1], p2[0],p2[1]) + for x, label in xticks: + p = scale*Numeric.array([x, y])+shift + dc.DrawLine(p[0],p[1], p[0],p[1]+d) + if text: + dc.DrawText(label, p[0],p[1]) + text = 0 + + if yaxis is not None: + lower, upper = yaxis + text = 1 + h = dc.GetCharHeight() + for x, d in [(bb1[0], -3), (bb2[0], 3)]: + p1 = scale*Numeric.array([x, lower])+shift + p2 = scale*Numeric.array([x, upper])+shift + dc.DrawLine(p1[0],p1[1], p2[0],p2[1]) + for y, label in yticks: + p = scale*Numeric.array([x, y])+shift + dc.DrawLine(p[0],p[1], p[0]-d,p[1]) + if text: + dc.DrawText(label, + p[0]-dc.GetTextExtent(label)[0], p[1]-0.5*h) + text = 0 + + def _ticks(self, lower, upper): + ideal = (upper-lower)/7. + log = Numeric.log10(ideal) + power = Numeric.floor(log) + fraction = log-power + factor = 1. + error = fraction + for f, lf in self._multiples: + e = Numeric.fabs(fraction-lf) + if e < error: + error = e + factor = f + grid = factor * 10.**power + if power > 3 or power < -3: + format = '%+7.0e' + elif power >= 0: + digits = max(1, int(power)) + format = '%' + `digits`+'.0f' + else: + digits = -int(power) + format = '%'+`digits+2`+'.'+`digits`+'f' + ticks = [] + t = -grid*Numeric.floor(-lower/grid) + while t <= upper: + ticks.append( (t, format % (t,)) ) + t = t + grid + return ticks + + _multiples = [(2., Numeric.log10(2.)), (5., Numeric.log10(5.))] + + def redraw(self,dc=None): + if self.last_draw is not None: + apply(self.draw, self.last_draw + (dc,)) + + def clear(self): + self.canvas.delete('all') + +#--------------------------------------------------------------------------- +# if running standalone... +# +# ...a sample implementation using the above +# + + +if __name__ == '__main__': + def _InitObjects(): + # 100 points sin function, plotted as green circles + data1 = 2.*Numeric.pi*Numeric.arange(200)/200. + data1.shape = (100, 2) + data1[:,1] = Numeric.sin(data1[:,0]) + markers1 = PolyMarker(data1, color='green', marker='circle',size=1) + + # 50 points cos function, plotted as red line + data1 = 2.*Numeric.pi*Numeric.arange(100)/100. + data1.shape = (50,2) + data1[:,1] = Numeric.cos(data1[:,0]) + lines = PolyLine(data1, color='red') + + # A few more points... + pi = Numeric.pi + markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.), + (3.*pi/4., -1)], color='blue', + fillcolor='green', marker='cross') + + return PlotGraphics([markers1, lines, markers2]) + + + class AppFrame(wx.Frame): + def __init__(self, parent, id, title): + wx.Frame.__init__(self, parent, id, title, + wx.DefaultPosition, (400, 400)) + + # Now Create the menu bar and items + self.mainmenu = wx.MenuBar() + + menu = wx.Menu() + menu.Append(200, '&Print...', 'Print the current plot') + self.Bind(wx.EVT_MENU, self.OnFilePrint, id=200) + menu.Append(209, 'E&xit', 'Enough of this already!') + self.Bind(wx.EVT_MENU, self.OnFileExit, id=209) + self.mainmenu.Append(menu, '&File') + + menu = wx.Menu() + menu.Append(210, '&Draw', 'Draw plots') + self.Bind(wx.EVT_MENU,self.OnPlotDraw, id=210) + menu.Append(211, '&Redraw', 'Redraw plots') + self.Bind(wx.EVT_MENU,self.OnPlotRedraw, id=211) + menu.Append(212, '&Clear', 'Clear canvas') + self.Bind(wx.EVT_MENU,self.OnPlotClear, id=212) + self.mainmenu.Append(menu, '&Plot') + + menu = wx.Menu() + menu.Append(220, '&About', 'About this thing...') + self.Bind(wx.EVT_MENU, self.OnHelpAbout, id=220) + self.mainmenu.Append(menu, '&Help') + + self.SetMenuBar(self.mainmenu) + + # A status bar to tell people what's happening + self.CreateStatusBar(1) + + self.client = PlotCanvas(self) + + def OnFilePrint(self, event): + d = wx.MessageDialog(self, +"""As of this writing, printing support in wxPython is shaky at best. +Are you sure you want to do this?""", "Danger!", wx.YES_NO) + if d.ShowModal() == wx.ID_YES: + psdc = wx.PostScriptDC("out.ps", True, self) + self.client.redraw(psdc) + + def OnFileExit(self, event): + self.Close() + + def OnPlotDraw(self, event): + self.client.draw(_InitObjects(),'automatic','automatic'); + + def OnPlotRedraw(self,event): + self.client.redraw() + + def OnPlotClear(self,event): + self.client.last_draw = None + dc = wx.ClientDC(self.client) + dc.Clear() + + def OnHelpAbout(self, event): + about = wx.MessageDialog(self, __doc__, "About...", wx.OK) + about.ShowModal() + + + + class MyApp(wx.App): + def OnInit(self): + frame = AppFrame(None, -1, "wxPlotCanvas") + frame.Show(True) + self.SetTopWindow(frame) + return True + + + app = MyApp(0) + app.MainLoop() + + + + +#----------------------------------------------------------------------------