Subclassing PyPlot, need some help

Andriy basilisk96 at gmail.com
Fri Aug 3 11:49:25 PDT 2007


> The easiest option would be to create a wx.MemoryDC for each subplot,
> then draw each resulting bitmap to the main DC where you want them.
>
> However, while this would work great on screen, it wouldn't work so well
> for printing, as you'd be printing bitmaps, rather than vector graphics.
> It may end up looking OK, if you get the resolutions right, but it feels
> kludgy.
>
> Better options:
>
> Maybe the best way (and the most work) would be to move to
> wx.GraphicsContext, rather than DC -- then you could take advantage of
> transforms to put your plots where and at what size you want them.
>
> Easier: You can probably use a combination of:
>
> wx.DC.SetDeviceOrigin
> wx.DC.SetUserScale
>
> setting those parameters differently, you may be able draw each subplot
> where you want, all with the same DC. UserScale has issues, as DCs are
> all integer based, but you may even be able to do without that. Really,
> setting the Origin should be all you need to draw your subplots where
> you want, as long as the Drawing code doesn't use the size of the DC to
> scale and position things.
>
> Hope that helps,
>
> -Chris

Thanks guys for all the suggestions, they made this little problem
more interesting. I've trashed the idea of keeping a list of
PlotCanvas instances for the subplots, and instead I just maintain a
list of the subplot parameters that are used to draw them (graphics
objects, axes, etc.)

After digging deeper into PlotCanvas drawing code, I discovered two
attributes that govern the size and position of the plot on the
canvas: plotbox_size and plotbox_origin. I exploited these by:
 1) overriding the OnSize handler and recalculating them,
 2) overriding the _Draw method, where I traverse the list of graphics
parameters, and draw each plot at its proper position and size on the
canvas. Nothing special, really - just using a wx.BufferedDC onto a
ClientDC and a bitmap. All that has worked out very well... almost :)

It looks excellent on screen and saves the canvas to an image file as
expected. The last hurdle now is printing: it seems to be scaling
incorrectly (looks at least twice as large as it should be, and the
print preview image varies in size depending on the zoom percentage
selected).  Maybe I need to override a few more methods to make this
work completely? We'll see.

Here's my code so far:

<<CODE START>>
import wx
import string as _string
import numpy as _N
from wx.lib.plot import PlotCanvas, PlotGraphics, PlotPrintout,
PolyLine, PolyMarker

class FlexPlot(PlotCanvas):
    def __init__(self, parent, rows=1, cols=1, title=''):
        self.parent = parent
        # Subplots setup
        if rows < 1: rows = 1
        if cols < 1: cols = 1
        self.rows = rows
        self.cols = cols
        self.Title = title
        self.numplots = self.rows * self.cols
        self.plotArgs = [None] * self.numplots
        #now initialize the superclass
        PlotCanvas.__init__(self, parent)

    def OnSize(self, event):
        canvasSize = self.canvas.GetClientSize()
        self._Buffer = wx.EmptyBitmap(canvasSize.width, canvasSize.height)
        self._setSize()
        canvasPlotBoxSize, canvasOrigin = self.plotbox_size, self.plotbox_origin
        self.SubPlotSize = (canvasPlotBoxSize[0]/self.cols,
canvasPlotBoxSize[1]/self.rows)
        self.plotbox_size = _N.array(self.SubPlotSize)
        size = self.plotbox_size
        #reference origin for all subplots from top left corner of canvas
        self.canvasOrigin = canvasOrigin - size * _N.array((0, self.rows-1))
        self._Draw()
        if event is not None:
            event.Skip()

    def OnPaint(self, event):
        PlotCanvas.OnPaint(self, event)
        event.Skip()

    def _Draw(self, graphics=None, xAxis = None, yAxis = None, dc = None):
        if dc == None:
            #set new dc and clear it
            dc = wx.BufferedDC(wx.ClientDC(self.canvas), self._Buffer)
            dc.Clear()
        size = self.plotbox_size
        canvasOrigin = self.canvasOrigin
        for i, subPlotArgs in enumerate(self.plotArgs):
            if subPlotArgs is None:
                self.Clear()
            else:
                #tblPos is a custom function; returns row, col
position based on index i
                r, c = tblPos(self.rows, self.cols, i)
                offsets = _N.array((c, r)) #switch: column is X, row is Y
                self.plotbox_origin = canvasOrigin + offsets * size
                graphics, xSpec, ySpec, gridSpec, legendSpec = subPlotArgs
                PlotCanvas._Draw(self, graphics, xSpec, ySpec, dc=dc)
        #disable scrollbars
        self.sb_vert.Show(False)
        self.sb_hor.Show(False)

    def Plot(self, graphics, index=0, xlim=None, ylim=None,
grid=False, legend=False):
        self.plotArgs[index] = [graphics, xlim, ylim, grid, legend]
        self.resetDefaults(self)
        self.Draw(graphics, xAxis=xlim, yAxis=ylim)
        self.SetEnableGrid(grid)
        self.SetEnableLegend(legend)

    def SaveFile(self, fileName=''):
        """Saves the current plot and subplots to an image file."""
        #Delegate to superclass.
        res = PlotCanvas.SaveFile(self, fileName)
        return res

    def Printout(self, paper=None):
        """Print current plot."""
        #Delegate to superclass.
        PlotCanvas.Printout(self, paper=None)

    def resetDefaults(self, client):
        """Just to reset the fonts back to customized defaults"""
        client.SetGridColour('light gray')
        client.SetFont(wx.Font(10,wx.SWISS,wx.NORMAL,wx.NORMAL))
        client.SetFontSizeAxis(10)
        client.SetFontSizeLegend(7)
        client.SetXSpec('auto')
        client.SetYSpec('auto')

    def _printDraw(self, printDC):
        """Used for printing."""
        #not sure yet how to fix this one...
        self._Draw(dc=printDC)
<<CODE END>>

Phew!
-amv




More information about the wxpython-users mailing list