jrcagle 77 Practically a Master Poster

So I have this nifty wxPython widget -- a Cartesian Coordinate plane with scrolling ability. Three issues:

(1) On scrolling outwards, I set it to resize itself, which seemed like a reasonable plan. But the resizing is very choppy. Is there any way to capture the last wx.SIZE event instead of each individual wx.SIZE event?

(2) Can one dynamically determine the scrollbar thumb width? Currently, I have it set as a constant 15 pixels.

(3) Why is the background black instead of the standard light gray? I tried Clear()ing the bitmap with a background brush, but it didn't work.

Other than that, if there are no bugs, I'll throw it into Code Snippets or another suitable area.

Jeff

import wx
import math

SCROLLBAR_WIDTH=15

class CoordPanel(wx.ScrolledWindow):

    """A Cartesian coordinate panel.  Points can be plotted in Cartesian
coordinates; scrolling is enabled."""

    def __init__(self, parent, id = -1, pos = wx.DefaultPosition,
                 size = wx.DefaultSize, style = wx.HSCROLL | wx.VSCROLL,
                 name = "scrolledWindow", xRange=(-10,10), yRange=(-10,10),
                 full_window = wx.DefaultSize, x_major=5, x_minor=1,
                 y_major=5, y_minor=1):
        """
xRange and yRange define the [xmin, xmax]x[ymin, ymax] size of the
window.  full_window defines the total (virtual) number of pixels in the
window.  x_major and x_minor specify the major and minor tick marks in
the x-direction; likewise for y_major and y_minor."""
        
        wx.ScrolledWindow.__init__(self, parent, id, pos, size, style, name)
        self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
        self.UX, self.UY = full_window
        width, height = size
        if self.UX < width:
            self.UX = width
        if self.UY < height:
            self.UY = height
        self.SetScrollbars(pixelsPerUnitX = 1,
                         pixelsPerUnitY = 1,
                         noUnitsX = self.UX,
                         noUnitsY = self.UY)
        
        self.xRange = xRange
        self.yRange = yRange
        self.SetScales()

        self.ticks = (x_major, x_minor, y_major, y_minor)
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.NewAxes(None)
        self.NewGraph()

    def SetScales(self):
        """Set the ratio of pixels to units in the x and y directions."""
        
        first, last = self.xRange
        if first >= last:
            raise ValueError, "Illegal xRange: %s" % (str(xRange))
        self.dx = last - first
        
        first, last = self.yRange
        if first >= last:
            raise ValueError, "Illegal yRange: %s" % (str(yRange))
        self.dy = last - first

        self._ppx = self.UX/self.dx
        self._ppy = self.UY/self.dy

        
    def to_canvas(self, point):
        """Return canvas coords for cartesian pt (x0,y0)"""
        
        x0, y0 = point
        x = (x0 - self.xRange[0])*self._ppx
        y = -(y0 - self.yRange[0])*self._ppy + self.UY
        return (x,y)

    def to_cartesian(self, point):
        """Return cartesian coords for canvas pt (x0,y0)"""
        
        x0,y0 = point
        x = x0/self._ppx + self.xRange[0]
        y = (self.UY - y0)/self._ppy + self.yRange[0]
        return (x,y)

    def DrawLine(self, x0,y0,x1,y1, pen):
        """Draw a line specified in Cartesian coordinates."""
        
        x0,y0 = self.to_canvas((x0,y0))
        x1,y1 = self.to_canvas((x1,y1))
        dc = wx.MemoryDC()
        dc.SelectObject(self._Graph)
        dc.SetPen(pen)
        dc.DrawLine(x0,y0,x1,y1)

    def DrawPoint(self, x0,y0, pen):
        """Draw a point specified in Cartesian coordinates."""
        
        x0,y0 = self.to_canvas((x0,y0))
        dc = wx.MemoryDC()
        dc.SelectObject(self._Graph)
        dc.SetPen(pen)
        dc.DrawPoint(x0,y0)

    def NewAxes(self, ticks=None):
        """Draw x- and y-axes with major and minor marks as specified by ticks,
a tuple of (x_major, x_minor, y_major, y_minor)."""

        self.TurnAxesOn()
        if ticks == None:
            ticks = self.ticks
        x_major, x_minor, y_major, y_minor = ticks
        
        self._Window = wx.EmptyBitmap(self.UX, self.UY)
        dc = wx.MemoryDC()
        dc.SetBackground(wx.LIGHT_GREY_BRUSH)
        dc.Clear()
        dc.SelectObject(self._Window)
        pen = wx.Pen("green", 1, wx.SOLID)
        dc.SetPen(pen)
        x0,y0=self.to_canvas((self.xRange[0],0))
        x1,y1=self.to_canvas((self.xRange[1],0))
        dc.DrawLine(x0,y0,x1,y1)

        x0,y0=self.to_canvas((0,self.yRange[0]))
        x1,y1=self.to_canvas((0,self.yRange[1]))
        dc.DrawLine(x0,y0,x1,y1)
        
        x = math.ceil(self.xRange[0]/x_minor) * x_minor 
        endx = self.xRange[1]
        while x < endx:
            x0, y0 = self.to_canvas((x,0))
            if x % x_major == 0:
                dc.DrawLine(x0,y0+8,x0,y0-8)
            else:
                dc.DrawLine(x0,y0+3,x0,y0-3)
            x += x_minor

        y = math.ceil(self.yRange[0]/y_minor) * y_minor
        endy = self.yRange[1]
        while y < endy:
            x0, y0 = self.to_canvas((0,y))
            if y % y_major == 0:
                dc.DrawLine(x0+8,y0,x0-8,y0)
            else:
                dc.DrawLine(x0+3,y0,x0-3,y0)
            y += y_minor

    def NewGraph(self):
        """Clear the graph area."""
        self._Graph = wx.EmptyBitmap(self.UX, self.UY)

    def TurnAxesOn(self):
        """Enable the axes on the display."""
        self._showaxes = True
        self.Refresh()

    def TurnAxesOff(self):
        """Disable the axes from the display."""
        self._showaxes = False
        self.Refresh()

    def ToggleAxes(self):
        """Toggle display of the axes."""
        self._showaxes = not self._showaxes
        self.Refresh()
        
    def OnPaint(self, event):
        """Show axes (if enabled) and graph."""
        dc = wx.BufferedPaintDC(self)
        self.DoPrepareDC(dc)
        if self._showaxes:
            dc.DrawBitmap(self._Window, 0, 0, useMask=False)
            self._Graph.SetMask(wx.Mask(self._Graph))
            dc.DrawBitmap(self._Graph, 0, 0, useMask=True)
        else:
            dc.DrawBitmap(self._Graph, 0, 0)

    def OnSize(self, event):
        """Resize graph area, stretching if necessary."""
        w, h = self.GetParent().GetClientSize()
        self.SetClientSize((w-SCROLLBAR_WIDTH,h-SCROLLBAR_WIDTH))
        if w <= self.UX and h <= self.UY:
            pass
        else:
            self.SetClientSize((w,h))
            self.UX = w
            self.UY = h
            self.SetScales()
            self.NewAxes()
            self.NewGraph()
            self.Refresh()

class GraphPanel(CoordPanel):

    """
A demo subclass of CoordPanel that graphs a single function.
Left-click draws the function in dot mode; right click in line mode.
The middle mouse button toggles the axes on and off."""

    def __init__(self, parent, id = -1, pos = wx.DefaultPosition,
                 size = wx.DefaultSize, style = wx.HSCROLL | wx.VSCROLL,
                 name = "scrolledWindow", xRange=(-10,10), yRange=(-10,10),
                 full_window = wx.DefaultSize, x_major=5, x_minor=1,
                 y_major=5, y_minor=1, func=lambda x:x*x):
        CoordPanel.__init__(self, parent, id, pos, size, style,
                            name, xRange, yRange, full_window,
                            x_major, x_minor, y_major, y_minor)
        self.Bind(wx.EVT_LEFT_UP, self.MouseClick)
        self.Bind(wx.EVT_RIGHT_UP, self.MouseClick)
        self.Bind(wx.EVT_MIDDLE_UP, lambda e: self.ToggleAxes())
        self.last_style = None
        self.func = func

    def DrawDotFunc(self, func):
        """Draw function in dot style."""
        self.last_style = "dot"
        x = self.xRange[0]
        dx = 0.1
        pen = wx.Pen("green", 1, wx.SOLID)
        while x < self.xRange[1]:
            try:
                y = func(x)
            except:
                pass
            else:
                self.DrawPoint(x,y, pen)
            x += dx

    def DrawLineFunc(self, func):
        """Draw function in connected-line style."""
        self.last_style = "line"
        x = self.xRange[0]
        dx = 0.1
        pen = wx.Pen("green", 1, wx.SOLID)

        old_x = x
        try:
            old_y = func(x)
        except:
            old_y = 0
        while x < self.xRange[1]:
            try:
                y = func(x)
            except:
                pass
            else:
                self.DrawLine(x, y, old_x, old_y, pen)
                old_y = y
            old_x = x
            
            x += dx
        
    def MouseClick(self, event):
        if not event:
            print "there!"
            return
        self.last_event = event
        self.NewGraph()
        if event.LeftUp():
            self.DrawDotFunc(self.func)
        elif event.RightUp():
            self.DrawLineFunc(self.func)
        self.Refresh()
        
    def OnSize(self, event):
        w, h = self.GetParent().GetClientSize()
        self.SetClientSize((w-SCROLLBAR_WIDTH,h-SCROLLBAR_WIDTH))
        if w <= self.UX and h <= self.UY:
            pass
        else:
            self.SetClientSize((w,h))
            self.UX = w
            self.UY = h
            self.SetScrollbars(pixelsPerUnitX = 1,
                         pixelsPerUnitY = 1,
                         noUnitsX = self.UX,
                         noUnitsY = self.UY)
            self.SetScales()
            if self._showaxes:
                self.NewAxes()
            self.NewGraph()
            
            if self.last_style == "dot":
                self.DrawDotFunc(self.func)
            elif self.last_style == "line":
                self.DrawLineFunc(self.func)
            self.Refresh()

    
if __name__ == "__main__":
    app = wx.PySimpleApp(0)

    app.frame = wx.Frame(parent=None)
    app.frame.sizer = wx.BoxSizer(orient=wx.VERTICAL)
    app.frame.SetSizer(app.frame.sizer)
    app.coords = GraphPanel(parent = app.frame, size=(390,400), full_window=(500,500),
                            func=lambda x:x*x)
    app.frame.sizer.Add(app.coords)
    app.frame.Bind(wx.EVT_SIZE, app.coords.OnSize)
    
    app.frame.SetTitle("Coordinate Canvas 1.0")
    app.frame.Show()
    app.MainLoop()
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.