Robert Cunningham wrote:
I'd like to plot the set using a simple "value" vs "channel #" bar graph.

I've looked at the existing demos, and either I'm too much of a newbie to comprehend them, or they don't quite provide the template I need. Is there some demo code available to help me create this simple plot?

Are you looking at just the stuff that comes with PythonCard or wxPython? If so, be sure to check out the stuff in SVN:

http://svn.wxwidgets.org/viewvc/wx/wxPython/3rdParty/FloatCanvas/Demos/

MovingPlot.py and ScaleDemo.py may be helpful.

(Minor rant: Detailed FloatCanvas docs could keep me off the list... Examples & demos tell me "what", but not "why".)

Well, yes, but someone's got to write them! Contributions accepted.

Or is FloatCanvas not the best tool for the job?

It's do the job, but as you've noticed does not have any built-in support for axes, etc.


(My backup plan is to use GnuPlot to generate an image then shove it into a canvas, but I'd lose interactivity.)

I wouldn't do that. However, you might want to look at matplotlib, (and wxmpl for helping put it into a wx app) performance will be worse that FloatCanvas, but there is a lot of nifty plotting stuff supported.

There are also a couple other plotting widgets for wxPython -- one in the wxPython distribution that is very limited, and couple others that have been talked about on the mailing list.

Anyway, I did a bit, and worked up a demo that I think is a start of what you want. It's enclosed, and now in FloatCanvas SVN (which is now part of wxPython SVN) It's tested against SVN head, so it may not work with an older FloatCanvas (though it may).

Note that I'm creating a rectangle object for each bar on the plot. That requires that you loop through them all to re-set the values. For better performance, it would be better to create a custom BarPlot DrawObject that kept one data set, and it would draw the individual rectangles.

There is an issue -- If I put in 1024 data points, then each bar ends up less than a pixel wide, which gets rounded down to zero, and nothing gets drawn. It gets drawn fine when you zoom in, though. I thought we could just add a "minimum width" parameter to FloatCanvas.Rectangle, but it's not that simple, the truncation happens when WorldToPixel creates an integer.

So, in this version, I wrote a new ScaleWorldToPixel method that rounds up, and injected into FloatCanvas before using it -- Python is so cool!

I don't want to change it globally, as others might not want that behavior.

Another issue is zooming -- if you zoom in, you might want to zoom in to the X axis, but re-scale the y axis so you can still see the entire height of the bars. You could do that by writing a GUIMode that captures the zoom and re-defines the projection function. Or you could do the scaling in your BarPlot object, if you wrote one -- that might be the better way to go.

In short, FloatCanvas has the infrastructure you need, but it's going to take some work to get everything you probably want -- you're probably better off with Matplotlib.


-Chris


--
Christopher Barker, Ph.D.
Oceanographer

Emergency Response Division
NOAA/NOS/OR&R            (206) 526-6959   voice
7600 Sand Point Way NE   (206) 526-6329   fax
Seattle, WA  98115       (206) 526-6317   main reception

[EMAIL PROTECTED]
#!/usr/bin/env python

import wx
## import the installed version
#from wx.lib.floatcanvas import NavCanvas, FloatCanvas

## import a local version
import sys
sys.path.append("../")
from floatcanvas import NavCanvas, FloatCanvas

import numpy as N
from numpy import random as random

NumChannels = 200
MaxValue = 2**24


def YScaleFun(center):
    """
    Function that returns a scaling vector to scale y data to same range as x data
    
    This is used by FloatCanvas as a "projection function", so that you can have
    a different scale for X and Y. With the default projection, X and Y are the same scale.

    """
    
    # center gets ignored in this case
    return N.array((1, float(NumChannels)/MaxValue), N.float)

def ScaleWorldToPixel(self, Lengths):
    """
        This is a new version of a function that will get passed to the
        drawing functions of the objects, to Change a length from world to
        pixel coordinates.
        
        This version uses the "ceil" function, so that fractional pixel get
        rounded up, rather than down.

        Lengths should be a NX2 array of (x,y) coordinates, or
        a 2-tuple, or sequence of 2-tuples.
    """
    return  N.ceil(( (N.asarray(Lengths, N.float)*self.TransformVector) )).astype('i')


class DrawFrame(wx.Frame):

    """
    A frame used for the FloatCanvas Demo

    """

    def __init__(self, *args, **kwargs):
        wx.Frame.__init__(self, *args, **kwargs)

        self.CreateStatusBar()

        # Add the Canvas
        FloatCanvas.FloatCanvas.ScaleWorldToPixel = ScaleWorldToPixel
        NC = NavCanvas.NavCanvas(self,-1,
                                     size = (500,500),
                                     BackgroundColor = "DARK SLATE BLUE",
                                     ProjectionFun = YScaleFun,
                                     )
        
        self.Canvas = Canvas = NC.Canvas
        #self.Canvas.ScaleWorldToPixel = ScaleWorldToPixel

        FloatCanvas.EVT_MOTION(self.Canvas, self.OnMove ) 

        self.Values = random.randint(0, MaxValue, (NumChannels,))
       
        self.Bars = []
        self.BarWidth = 0.75
        # add an X axis
        Canvas.AddLine(((0,0), (NumChannels, 0 )),)
        for x in N.linspace(1, NumChannels, 11):
            Canvas.AddText("%i"%x, (x-1+self.BarWidth/2,0), Position="tc")

        for i, Value in enumerate(self.Values):
            bar = Canvas.AddRectangle(XY=(i, 0),
                                      WH=(self.BarWidth, Value),
                                      LineColor = None,
                                      LineStyle = "Solid",
                                      LineWidth    = 1,
                                      FillColor    = "Red",
                                      FillStyle    = "Solid",
                                      )
            self.Bars.append(bar)
        
        # Add a couple a button the Toolbar

        tb = NC.ToolBar
        tb.AddSeparator()

        ResetButton = wx.Button(tb, label="Reset")
        tb.AddControl(ResetButton)
        ResetButton.Bind(wx.EVT_BUTTON, self.ResetData)

#        PlayButton = wx.Button(tb, wx.ID_ANY, "Run")
#        tb.AddControl(PlayButton)
#        PlayButton.Bind(wx.EVT_BUTTON, self.RunTest)
        tb.Realize()

        self.Show()
        Canvas.ZoomToBB()
        Canvas.Draw(True)
   

    def OnMove(self, event):
        """
        Updates the status bar with the world coordinates

        """
        channel, value = event.Coords
        if 0 < channel < NumChannels  :
            channel = "%i"%(channel+1)
        else:
            channel = ""
            
        if value >=0:
            value = "%i"%value
        else:
            value = ""
        self.SetStatusText("Channel: %s,  Value: %s"%(channel, value))

    def ResetData(self, event):
        self.Values = random.randint(0, MaxValue, (NumChannels,))
        for i, bar in enumerate(self.Bars):
            bar.SetShape(bar.XY, (self.BarWidth, self.Values[i])) 
        self.Canvas.Draw(Force=True)

app = wx.App(False)
F = DrawFrame(None, title="FloatCanvas Demo App", size=(700,700) )
app.MainLoop()
    
    
    
    









_______________________________________________
FloatCanvas mailing list
[email protected]
http://mail.mithis.com/cgi-bin/mailman/listinfo/floatcanvas

Reply via email to