~ubuntu-branches/ubuntu/karmic/python-scipy/karmic

« back to all changes in this revision

Viewing changes to Lib/plt/wxplt.py

  • Committer: Bazaar Package Importer
  • Author(s): Daniel T. Chen (new)
  • Date: 2005-03-16 02:15:29 UTC
  • Revision ID: james.westby@ubuntu.com-20050316021529-xrjlowsejs0cijig
Tags: upstream-0.3.2
ImportĀ upstreamĀ versionĀ 0.3.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from wxPython import wx
 
2
import string
 
3
from Numeric import *
 
4
from scipy_base.fastumath import *
 
5
import time
 
6
 
 
7
from plot_utility import *
 
8
from plot_objects import *
 
9
from lena import lena
 
10
 
 
11
def loop():
 
12
    global bub
 
13
    bub.SetSize((400,400))
 
14
    for i in range(20):
 
15
        #if bub.GetSizeTuple()[0] ==400: bub.SetSize((200,200))
 
16
        #else: bub.SetSize((400,400))
 
17
        #bub.client.layout_all()
 
18
        bub.client.draw_graph_area()
 
19
 
 
20
# Issues:
 
21
#  -- Fix textobject.size() for non-90 degree rotations
 
22
#  -- Auto attributes haven't been fully thought through
 
23
#  -- Axis titles probably should be axis properties
 
24
#     This might save some current layout problems.
 
25
#  -- Little attention has been paid to round off errors.
 
26
#     Occassionly you'll see the consequences in a miss
 
27
#     placed grid line or slightly off markers, but overall
 
28
#     it's not so bad.
 
29
#  -- Could use a more sophisticated property setting scheme,
 
30
#     perhaps like graphite????  Anyway you can do bad things
 
31
#     such as assign a string instead of a text_object to
 
32
#     titles, etc.  This is bad.  Optional typing in future
 
33
#     Python would remove the need for fancy property type
 
34
#     checking system (I think).  Hope it comes to pass...
 
35
#  -- This should be split up into several modules.  Probably
 
36
#     a package.
 
37
#  -- Printing on windows does not print out line styles.  
 
38
#     Everything is printed as solid lines. argh!
 
39
#     Update: This seems like an issue with calling dc.SetUserScale
 
40
#     If this isn't called, the problem is fixed - but the graph is 
 
41
#     tiny!!!  Should I work out the appropriate scaling in draw()
 
42
#     or should the SetUserScale() method maintain line types?
 
43
#  -- Print Preview scaling is not correct.  Not sure why not,
 
44
#     but it looks lit the dc size is different the printer dc size.
 
45
#     How to fix?
 
46
#  -- Rotated text does not print with correct font type. Is this
 
47
#     a wxPython(windows) problem or mine?
 
48
#  -- Plot windows do not become top window when using gui_thread.
 
49
 
 
50
# To Add:
 
51
#  -- Legend
 
52
#  -- MouseDown support for changing fonts
 
53
from plot_utility import *
 
54
 
 
55
 
 
56
 
 
57
#make this a box_object?
 
58
 
 
59
 
 
60
aspect_ratios = ['normal', 'equal']
 
61
 
 
62
class plot_canvas(wx.wxWindow,property_object):
 
63
    _attributes = {
 
64
       'background_color': ['light grey',colors,"Window background color" \
 
65
                                           " Currently broken"],
 
66
       'aspect_ratio': ['normal',aspect_ratios,"Set the axis aspect ratio"],
 
67
       'hold': ['off',['on','off'],"Used externally for adding lines to plot"],
 
68
     }
 
69
 
 
70
    __type_hack__ = "plot_canvas"
 
71
    
 
72
    #background color is not working...
 
73
    def __init__(self, parent, id = -1, pos=wx.wxPyDefaultPosition,
 
74
                 size=wx.wxPyDefaultSize, **attr):
 
75
        wx.wxWindow.__init__(self, parent, id, pos,size)
 
76
        wx.EVT_PAINT(self,self.on_paint)
 
77
        property_object.__init__(self, attr)
 
78
        background = wx.wxNamedColour(self.background_color)
 
79
        self.SetBackgroundColour(background) 
 
80
        ##self.title = text_object('')
 
81
        ##self.x_title = text_object('')
 
82
        ##self.y_title = text_object('')
 
83
        self.title = text_window(self,'')
 
84
        self.x_title = text_window(self,'')
 
85
        self.y_title = text_window(self,'')
 
86
        self.all_titles = [self.title,self.x_title,self.y_title] #handy to have
 
87
        ##self.x_axis = axis_object(graph_location='above',rotate=0)        
 
88
        ##self.y_axis = axis_object(graph_location='right',rotate=90)
 
89
        self.x_axis = axis_window(self,graph_location='above',rotate=0)        
 
90
        self.y_axis = axis_window(self,graph_location='right',rotate=90)
 
91
 
 
92
        self.image_list = graphic_list()
 
93
        self.line_list = auto_line_list()  # make this the data object.
 
94
        self.legend = legend_object() 
 
95
        self.text_list = None  # list of text objects to place on screen
 
96
        self.overlays = None   # list of objects to draw on top of graph 
 
97
                               # (boxes, circles, etc.)
 
98
        ##self.y2_axis = axis_object(graph_location='left',rotate=90) 
 
99
        self.client_size = (0,0)
 
100
        # zoom selection helpers
 
101
        self._mouse_selection = 0
 
102
        self._mouse_selection_start = wx.wxPoint(0,0)
 
103
        self._mouse_selection_stop = wx.wxPoint(0,0)
 
104
        # mouse events
 
105
        wx.EVT_RIGHT_DOWN(self,self.on_right_down)
 
106
        wx.EVT_LEFT_DOWN(self, self.on_mouse_event)
 
107
        wx.EVT_LEFT_UP(self, self.on_mouse_event)
 
108
        wx.EVT_MOTION(self, self.on_mouse_event)
 
109
        wx.EVT_MOTION(self, self.on_mouse_event)
 
110
        wx.EVT_SIZE(self, self.update)
 
111
 
 
112
    # event handler
 
113
 
 
114
    def on_mouse_event(self, event):
 
115
        if event.LeftDown():
 
116
            # start zoom selection
 
117
            self._mouse_selection_start = event.GetPosition()
 
118
            self._mouse_selection_stop = event.GetPosition()
 
119
            self._mouse_selection = 1
 
120
        elif event.LeftUp() and self._mouse_selection:
 
121
            # zoom in
 
122
            self.zoom(event.GetPosition())
 
123
            self._mouse_selection = 0
 
124
        elif event.LeftIsDown() and self._mouse_selection:
 
125
            # update zoom selection
 
126
            self.rubberband(event.GetPosition())
 
127
        else:
 
128
            pass
 
129
 
 
130
    def auto_zoom(self):
 
131
        # zoom to full scale
 
132
        # cannot call autoscale(), since validate_active() apparently
 
133
        # leads to a race condition:(
 
134
        self.x_axis.bounds = ['auto','auto']
 
135
        self.y_axis.bounds = ['auto','auto']
 
136
        self.x_axis.tick_interval = 'auto'
 
137
        self.y_axis.tick_interval = 'auto'
 
138
        self.update()
 
139
    
 
140
    def on_auto_zoom(self,event):
 
141
        self.auto_zoom()    
 
142
        
 
143
    def on_paint(self, event):
 
144
        self.draw(wx.wxPaintDC(self))
 
145
 
 
146
    def on_right_down(self,event):
 
147
        menu_found = 0
 
148
        pos = event.GetX(),event.GetY()
 
149
        dc = wx.wxClientDC(self)
 
150
        for title in self.all_titles:
 
151
            title.set_dc(dc) # this dc stuff is a pain...
 
152
            if title.contains(pos):
 
153
                title.format_popup(pos)
 
154
                break
 
155
                menu_found = 1
 
156
            title.clear_dc()
 
157
        if not menu_found:
 
158
            if self.x_axis.contains(pos,dc):
 
159
                self.x_axis.format_popup(pos)
 
160
            elif self.y_axis.contains(pos,dc):
 
161
                self.y_axis.format_popup(pos)
 
162
            else:
 
163
                self.format_popup(pos)
 
164
 
 
165
    def format_popup(self,pos):
 
166
        menu = wx.wxMenu()
 
167
        menu.Append(500, 'Auto Zoom', 'Auto Zoom')
 
168
        wx.EVT_MENU(self, 500, self.on_auto_zoom)
 
169
        menu.UpdateUI()
 
170
        self.PopupMenuXY(menu,pos[0],pos[1])        
 
171
    # workers
 
172
    
 
173
    def rubberband(self, new):
 
174
        """Delete previous selection band and paint new one."""
 
175
        if self._mouse_selection:
 
176
            dc = wx.wxClientDC(self)
 
177
            dc.SetLogicalFunction(wx.wxXOR)
 
178
            dc.SetPen(wx.wxGREY_PEN)
 
179
            dc.SetBrush(wx.wxTRANSPARENT_BRUSH)
 
180
            diff = self._mouse_selection_stop - self._mouse_selection_start
 
181
            dc.DrawRectangle(self._mouse_selection_start.x,
 
182
                             self._mouse_selection_start.y, diff.x, diff.y)
 
183
            self._mouse_selection_stop = new
 
184
            diff = self._mouse_selection_stop - self._mouse_selection_start
 
185
            dc.DrawRectangle(self._mouse_selection_start.x,
 
186
                             self._mouse_selection_start.y, diff.x, diff.y)
 
187
 
 
188
    def save(self,path,image_type):
 
189
        w,h = self.GetSizeTuple()
 
190
        bitmap = wx.wxEmptyBitmap(w,h)
 
191
        dc = wx.wxMemoryDC()
 
192
        dc.SelectObject(bitmap)
 
193
        #self.update()
 
194
        # The background isn't drawn right without this cluge.   
 
195
        #fill_color = get_color(self.background_color)
 
196
        fill_color = get_color('white')        
 
197
        dc.SetPen(wx.wxPen(fill_color))
 
198
        dc.SetBrush(wx.wxBrush(fill_color)) #how to handle transparency???
 
199
        dc.DrawRectangle(0,0,w,h)
 
200
        dc.SetPen(wx.wxNullPen)
 
201
        dc.SetBrush(wx.wxNullBrush)
 
202
        # end cluge
 
203
        self.draw(dc)
 
204
        image = wx.wxImageFromBitmap(bitmap)
 
205
        wx.wxInitAllImageHandlers()
 
206
        image.SaveFile(path,image_type_map[image_type])
 
207
 
 
208
    def layout_all(self,dc=None):
 
209
        #settingbackgroundcolors
 
210
        #background = wx.wxNamedColour(self.background_color)
 
211
        #if self.GetBackgroundColour() != background:
 
212
        #   self.SetBackgroundColour(background) 
 
213
           #self.Clear()
 
214
        #   print 'refreshing'  
 
215
        if not dc: dc = wx.wxClientDC(self)            
 
216
        self.client_size = array(self.GetClientSizeTuple())
 
217
        # set the device context for all titles so they can
 
218
        # calculate their size
 
219
        for text_obj in self.all_titles:
 
220
            text_obj.set_dc(dc)
 
221
            
 
222
        graph_area = box_object((0,0),self.client_size)
 
223
        graph_area.inflate(.95) # shrink box slightly
 
224
        
 
225
        # shrink graph area to make room for titles
 
226
        graph_area = self.layout_border_text(graph_area)        
 
227
        # layout axis and graph data
 
228
        graph_area = self.layout_graph(graph_area,dc)
 
229
        # center titles around graph area.
 
230
        self.finalize_border_text(graph_area,dc)   
 
231
        self.graph_box = graph_area
 
232
        # clear the dc for all titles
 
233
        # ? neccessary ?
 
234
        for text_obj in self.all_titles:
 
235
            text_obj.clear_dc()
 
236
        self.layout_data()
 
237
        
 
238
        #self.legend.layout(self.line_list,graph_area,dc)
 
239
        
 
240
    def layout_border_text(self,graph_area):
 
241
        # Shrink graph area to make room for titles.
 
242
        # Also, specify where the text is to live
 
243
        # in realation to the graph.  This only
 
244
        # specifies one axis.  The other can only
 
245
        # be specified after the final graph area
 
246
        # is calculated.
 
247
        margin = 4
 
248
        graph_area.trim_top(self.title.height()+margin)
 
249
        graph_area.trim_bottom(self.x_title.height()+margin)            
 
250
        self.y_title.rotate = 90 # make sure it is rotated
 
251
        graph_area.trim_left(self.y_title.width()+margin)
 
252
        #this is just to make so extra room for axis labels
 
253
        #on the x axis...
 
254
        graph_area.trim_right(12)
 
255
        return graph_area
 
256
 
 
257
    def layout_data(self):    
 
258
        # get scale and offset
 
259
        axis_range = array((self.x_axis.range(),self.y_axis.range()),Float)
 
260
        # negative y to account for positve down in window coordinates
 
261
        scale = self.graph_box.size() / axis_range * array((1.,-1.))
 
262
        offset = self.graph_to_window(array((0.,0.)))
 
263
        self.image_list.scale_and_shift(scale,offset)
 
264
        self.line_list.scale_and_shift(scale,offset)        
 
265
        #self.legend 
 
266
        #self.text_list
 
267
        #self.overlays
 
268
        
 
269
    def layout_graph(self,graph_area,dc):                        
 
270
        self.axes = []
 
271
        #data_x_bounds,data_y_bounds = [0,6.28], [-1.1,1000]
 
272
        #jeez this is unwieldy code...
 
273
        smalls = []; bigs =[]
 
274
        if len(self.line_list):
 
275
            p1,p2 =  self.line_list.bounding_box()
 
276
            smalls.append(p1);bigs.append(p2)
 
277
        if len(self.image_list):
 
278
            p1,p2 =  self.image_list.bounding_box()
 
279
            smalls.append(p1);bigs.append(p2)        
 
280
        if len(smalls):    
 
281
            min_point = minimum.reduce(smalls)
 
282
            max_point = maximum.reduce(bigs)
 
283
        else:
 
284
            min_point = array((-1.,-1.),)
 
285
            max_point = array((1.,1.))               
 
286
        data_x_bounds = array((min_point[0],max_point[0]))
 
287
        data_y_bounds = array((min_point[1],max_point[1]))
 
288
        self.x_axis.calculate_ticks(data_x_bounds)
 
289
        height = self.x_axis.max_label_height(dc)
 
290
        graph_area.trim_bottom(height)
 
291
        self.y_axis.calculate_ticks(data_y_bounds)
 
292
        width = self.y_axis.max_label_width(dc)
 
293
        graph_area.trim_left(width)
 
294
        if self.aspect_ratio == 'equal':
 
295
            x_scale = float(graph_area.width()) / self.x_axis.range()
 
296
            y_scale = float(graph_area.height()) / self.y_axis.range()
 
297
            #print 'scales:', x_scale,y_scale,self.x_axis.range(),self.y_axis.range()
 
298
            if x_scale > y_scale:
 
299
                new_width = y_scale * self.x_axis.range()
 
300
                remove = .5 * (graph_area.width() - new_width)
 
301
                graph_area.trim_left(remove)
 
302
                graph_area.trim_right(remove)
 
303
            else:    
 
304
                new_height = x_scale * self.y_axis.range()
 
305
                remove = .5 * (graph_area.height() - new_height)
 
306
                graph_area.trim_top(remove)
 
307
                graph_area.trim_bottom(remove)
 
308
        #self.y2_axis = axis_object(graph_location='left',rotate=90)
 
309
        #self.y2_axis.label_location = 'plus'
 
310
        #self.y2_axis.calculate_ticks(y2bounds)
 
311
        #width = self.y2_axis.max_label_width(dc)
 
312
        #graph_area.trim_right(width)
 
313
        self.x_axis.layout(graph_area,dc)
 
314
        self.x_axis.move((graph_area.left(),graph_area.bottom()))
 
315
        self.axes.append(self.x_axis)
 
316
        self.y_axis.layout(graph_area,dc)
 
317
        self.y_axis.move((graph_area.left(),graph_area.bottom()))
 
318
        self.axes.append(self.y_axis)
 
319
        #self.y2_axis.grid_color = 'wheat'
 
320
        #self.y2_axis.layout(graph_area,dc)
 
321
        #self.y2_axis.move((graph_area.right(),graph_area.bottom()))
 
322
        #self.axes.append(self.y2_axis)
 
323
        self.border = border_object()
 
324
        self.border.layout(graph_area,self.x_axis,self.y_axis)
 
325
        return graph_area
 
326
        
 
327
    def finalize_border_text(self,graph_area,dc):
 
328
        # Center the titles around the graph.
 
329
        # -- Really need to make axis object box_objects.
 
330
        #    Use this to help determine more appropriate 
 
331
        #    title location.  Current works fine
 
332
        #    if axis labels are beside graph.  Title
 
333
        #    will be to far away if they are in center of graph        
 
334
        margin = 4
 
335
        if self.title:   
 
336
            self.title.center_on_x_of(graph_area)
 
337
            self.title.above(graph_area,margin)
 
338
        if self.x_title: 
 
339
            offset = self.x_axis.max_label_height(dc)
 
340
            self.x_title.center_on_x_of(graph_area)
 
341
            self.x_title.below(graph_area,margin + offset)
 
342
        if self.y_title: 
 
343
            offset = self.y_axis.max_label_width(dc)
 
344
            self.y_title.center_on_y_of(graph_area)
 
345
            self.y_title.left_of(graph_area,margin+offset)
 
346
        #if self.y2_title:self.y2_title.center_on_y_of(graph_area)
 
347
    
 
348
    def graph_to_window(self,pts):
 
349
        axis_range =  array((self.x_axis.range(),self.y_axis.range()))
 
350
        # negative y to account for positve down in window coordinates
 
351
        scale = self.graph_box.size() / axis_range * array((1.,-1.))
 
352
        graph_min = array((self.x_axis.ticks[0],self.y_axis.ticks[0]))
 
353
        zero_offset = (array((0.,0))- graph_min)  * scale 
 
354
        graph_offset = array((self.graph_box.left(),self.graph_box.bottom()))
 
355
        return pts * scale + zero_offset + graph_offset
 
356
                   
 
357
    def reset_size(self, dc = None):
 
358
        new_size = self.GetClientSizeTuple()
 
359
        if new_size != self.client_size:
 
360
            self.layout_all(dc)
 
361
            self.client_size = new_size
 
362
 
 
363
    def draw_graph_area(self,dc=None):
 
364
        if not dc: dc = wx.wxClientDC(self)                                     
 
365
        self.layout_data() # just to check how real time plot would go...
 
366
 
 
367
        gb = self.graph_box
 
368
        #clear the plot area
 
369
        # SHOULD SET PEN HERE TO FILL BACKGROUND WITH CORRECT COLOR
 
370
        fill_color = get_color('white')
 
371
        dc.SetPen(wx.wxPen(fill_color))
 
372
        dc.SetBrush(wx.wxBrush(fill_color))
 
373
        # NEEDED FOR REAL-TIME PLOTTING
 
374
        dc.DrawRectangle(gb.left(),gb.top(),
 
375
                         gb.width()+1,gb.height()+1)
 
376
        #needed to make sure images stay within bounds
 
377
        ##dc.SetClippingRegion(gb.left()-1,gb.top()-1,
 
378
        ##                     gb.width()+2,gb.height()+2)  # mod by GAP 26092003
 
379
        dc.SetClippingRegion(int(gb.left()-1),int(gb.top()-1),
 
380
                             int(gb.width()+2),int(gb.height()+2))
 
381
        # draw images
 
382
        self.image_list.draw(dc)
 
383
        dc.DestroyClippingRegion()        
 
384
        # draw axes lines and tick marks               
 
385
        t1 = time.clock()    
 
386
        for axis in self.axes:
 
387
            axis.draw_lines(dc)
 
388
        #for axis in self.axes:
 
389
        #    axis.draw_grid_lines(dc)
 
390
        #for axis in self.axes:
 
391
        #    axis.draw_ticks(dc)    
 
392
        t2 = time.clock()
 
393
        #print 'lines:', t2 - t1
 
394
        #draw border
 
395
        t1 = time.clock(); self.border.draw(dc); t2 = time.clock()
 
396
        #print 'border:', t2 - t1                    
 
397
        # slightly larger clipping area so that marks
 
398
        # aren't clipped on edges
 
399
        # should really clip markers and lines separately
 
400
        # draw lines
 
401
        self.line_list.clip_box(self.graph_box)
 
402
        self.line_list.draw(dc)
 
403
        # draw text        
 
404
        # draw legend
 
405
        # self.legend.draw(dc)
 
406
        # draw overlay objects
 
407
        
 
408
    def draw(self,dc=None):
 
409
        #if not len(self.line_list) or len(self.image_list):
 
410
        #    return
 
411
        # resize if necessary
 
412
        #print 'draw'
 
413
        #print 'dc:',dc
 
414
        t1 = time.clock();self.reset_size(dc);t2 = time.clock()
 
415
        #print 'resize:',t2 - t1        
 
416
        if not dc: dc = wx.wxClientDC(self)
 
417
        # draw titles and axes labels
 
418
        t1 = time.clock()    
 
419
        for text in self.all_titles:
 
420
            text.draw(dc)        
 
421
        for axis in self.axes:
 
422
            axis.draw_labels(dc)
 
423
        t2 = time.clock()
 
424
        #print 'text:',t2 - t1
 
425
        self.draw_graph_area(dc)
 
426
            
 
427
    def update(self,event=None):
 
428
        self.client_size = (0,0) # forces the layout
 
429
        self.Refresh()        
 
430
 
 
431
    def zoom(self, stop):
 
432
        """Delete selection band and zoom selection to full scale."""
 
433
        # delete rubberband
 
434
        dc = wx.wxClientDC(self)
 
435
        dc.SetLogicalFunction(wx.wxXOR)
 
436
        dc.SetPen(wx.wxGREY_PEN)
 
437
        dc.SetBrush(wx.wxTRANSPARENT_BRUSH)
 
438
        diff = self._mouse_selection_stop - self._mouse_selection_start
 
439
        dc.DrawRectangle(self._mouse_selection_start.x,
 
440
                         self._mouse_selection_start.y, diff.x, diff.y)
 
441
        self._mouse_selection_stop = stop
 
442
        # get zoom-area coordinates
 
443
        p1 = self._mouse_selection_start
 
444
        p2 = self._mouse_selection_stop
 
445
        gb = self.graph_box
 
446
        if abs(p1.x-p2.x) < 3 and abs(p1.y-p2.y) < 3:
 
447
            # zoom aborted (we assume)
 
448
            return
 
449
        left = float(min(p1.x, p2.x) - gb.left()) / gb.width()
 
450
        right = float(max(p1.x, p2.x) - gb.left()) / gb.width()
 
451
        top = float(min(p1.y, p2.y) - gb.top()) / gb.height()
 
452
        bottom = float(max(p1.y, p2.y) - gb.top()) / gb.height()
 
453
        # convert to real bounds
 
454
        width = self.x_axis.ticks[-1] - self.x_axis.ticks[0]
 
455
        height = self.y_axis.ticks[-1] - self.y_axis.ticks[0]
 
456
        left = left * width + self.x_axis.ticks[0]
 
457
        right = right * width + self.x_axis.ticks[0]
 
458
        top = self.y_axis.ticks[-1] - top * height
 
459
        bottom = self.y_axis.ticks[-1] - bottom * height
 
460
        x_int = auto_interval([left, right])
 
461
        y_int = auto_interval([bottom,top])
 
462
        self.x_axis.bounds = auto_bounds([left, right],x_int)
 
463
        self.y_axis.bounds = auto_bounds([bottom, top],y_int)
 
464
        self.update()
 
465
 
 
466
 
 
467
 
 
468
 
 
469
#------------------ tick utilities -----------------------
 
470
# flexible log function
 
471
#------------------ end tick utilities -----------------------
 
472
 
 
473
class graph_printout(wx.wxPrintout):
 
474
    """Print wrapper."""
 
475
    # Do not change method names in this class,
 
476
    # we have to override wxPrintout methods here!
 
477
    def __init__(self, graph):
 
478
        wx.wxPrintout.__init__(self)
 
479
        self.graph = graph
 
480
 
 
481
    def HasPage(self, page):
 
482
        if page == 1:
 
483
            return wx.true
 
484
        else:
 
485
            return wx.false
 
486
 
 
487
    def GetPageInfo(self):
 
488
        return (1, 1, 1, 1)
 
489
 
 
490
    def OnPrintPage(self, page):
 
491
        dc = self.GetDC()
 
492
        # .5 inch margins are automatic
 
493
        # on my HP.  Probably not standard.
 
494
        # Need smarter margin control.
 
495
        w_inch,h_inch = self.GetPPIPrinter()
 
496
        x_margin = .0* w_inch
 
497
        y_margin = .0* h_inch
 
498
        #-------------------------------------------
 
499
        # One possible method of setting scaling factors...
 
500
        #print w_inch,h_inch
 
501
        #print dc.GetSizeTuple()        
 
502
        graph_box = box_object((0,0),self.graph.GetSizeTuple())
 
503
        # Get the size of the DC in pixels
 
504
        page_size = dc.GetSizeTuple()
 
505
        #print 'dc size:',page_size
 
506
        #page_size = self.GetPageSizePixels()
 
507
        #print 'page size:',page_size
 
508
        print_box = box_object((0,0),page_size)        
 
509
        print_box.trim_left(x_margin)
 
510
        print_box.trim_right(x_margin)
 
511
        print_box.trim_top(y_margin)
 
512
        print_box.trim_bottom(y_margin)
 
513
        # Calculate a suitable scaling factor
 
514
        scales = array(print_box.size(), Float)/graph_box.size()
 
515
        # Use x or y scaling factor, whichever fits on the DC
 
516
        scale = min(scales)
 
517
        # resize the graph and center on the page
 
518
        graph_box.inflate(scale)
 
519
        graph_box.center_on(print_box)
 
520
        # set the device scale and origin
 
521
        dc.SetUserScale(scale, scale)
 
522
        dc.SetDeviceOrigin(graph_box.left(),graph_box.top())
 
523
        #-------------------------------------------
 
524
        #print 'print dc size:', dc.GetSizeTuple()
 
525
        self.graph.draw(dc)
 
526
        return wx.true
 
527
 
 
528
 
 
529
class plot_window(plot_canvas):
 
530
    """Plot canvas window.
 
531
 
 
532
    This is a complete q+d hack, but it does (somewhat) work.
 
533
    """
 
534
    def __init__(self, parent, id = -1, pos=wx.wxPyDefaultPosition,
 
535
                 size=wx.wxPyDefaultSize,**attr):
 
536
        plot_canvas.__init__(self, parent, id, pos, size)
 
537
        self.parent = parent
 
538
        self.proxy_object_alive = wx.true
 
539
        scipy.plt.figure(self)
 
540
        self.layout_all()
 
541
        scipy.plt.xaxis('fit')
 
542
        # initial print setup
 
543
        self.print_data = wx.wxPrintData()
 
544
        self.print_data.SetPaperId(wx.wxPAPER_LETTER)
 
545
        self.print_data.SetOrientation(wx.wxLANDSCAPE)
 
546
 
 
547
    def add(self, x, y, **keywds):
 
548
        """Add data to plot."""
 
549
        groups = scipy.plt.plot_groups([x, y, 'b-'])
 
550
        lines = []
 
551
        for group in groups:
 
552
            lines.extend(scipy.plt.lines_from_group(group))
 
553
        # check for hold here
 
554
        for name in plot_objects.poly_marker._attributes.keys():
 
555
            value = keywds.get(name)
 
556
            if value is not None:
 
557
                for k in range(len(lines)):
 
558
                    exec('lines[k].markers.%s = value' % name)
 
559
        for i in lines:
 
560
            self.line_list.append(i)
 
561
        self.update();
 
562
 
 
563
    def clear(self):
 
564
        """Clear canvas, remove all plots."""
 
565
        self.line_list = auto_line_list()
 
566
        scipy.plt.autoscale()
 
567
 
 
568
    def set_title(self, title):
 
569
        """Set plot title."""
 
570
        self.title = text_window(self, title)
 
571
        self.all_titles[0] = self.title
 
572
        self.update()
 
573
        
 
574
    def set_xaxis_title(self, xtitle):
 
575
        """Set x-axis title"""
 
576
        self.x_title = text_window(self, xtitle)
 
577
        self.all_titles[1] = self.x_title
 
578
        self.update()
 
579
 
 
580
    def set_yaxis_title(self, ytitle):
 
581
        """Set y-axis title"""
 
582
        self.y_title = text_window(self, ytitle)
 
583
        self.all_titles[2] = self.y_title
 
584
        self.update()
 
585
 
 
586
    def printout(self, paper=None):
 
587
        """Print current plot."""
 
588
        if paper != None:
 
589
            self.print_data.SetPaperId(paper)
 
590
        pdd = wx.wxPrintDialogData()
 
591
        pdd.SetPrintData(self.print_data)
 
592
        printer = wx.wxPrinter(pdd)
 
593
        out = graph_printout(self)
 
594
        print_ok = printer.Print(self.parent, out)
 
595
        if print_ok:
 
596
            self.print_data = printer.GetPrintDialogData().GetPrintData()
 
597
        out.Destroy()
 
598
 
 
599
    def print_preview(self):
 
600
        """Print-preview current plot."""
 
601
        printout = graph_printout(self)
 
602
        printout2 = graph_printout(self)
 
603
        self.preview = wx.wxPrintPreview(printout, printout2, self.print_data)
 
604
        if not self.preview.Ok():
 
605
            wx.wxMessageDialog(self, "Print Preview failed.\n" \
 
606
                               "Check that default printer is configured\n", \
 
607
                               "Print error", wx.wxOK|wx.wxCENTRE).ShowModal()
 
608
        else:
 
609
            frame = wx.wxPreviewFrame(self.preview, self.parent, "Preview")
 
610
            frame.Initialize()
 
611
            frame.SetPosition(self.GetPosition())
 
612
            frame.SetSize(self.GetSize())
 
613
            frame.Show(wx.true)
 
614
 
 
615
 
 
616
class plot_frame(wx.wxFrame):
 
617
    """wxFrame for interactive use of plot_canvas."""
 
618
    TITLE_FONT = 210
 
619
    AXIS_FONT = 211
 
620
    LABEL_FONT = 212
 
621
    
 
622
    TITLE_TEXT,X_TEXT,Y_TEXT = 220,221,222
 
623
 
 
624
    default_size = (500,400) # the default on Linux is always tiny???
 
625
    
 
626
    def __init__(self, parent=wx.NULL, id = -1, title = '', 
 
627
                 pos=wx.wxPyDefaultPosition,
 
628
                 size=default_size,visible=1):
 
629
        wx.wxFrame.__init__(self, parent, id, title,pos,size)
 
630
        # Now Create the menu bar and items
 
631
        self.mainmenu = wx.wxMenuBar()
 
632
        menu = wx.wxMenu()
 
633
        menu.Append(200, '&Save As...', 'Save plot to image file')
 
634
        wx.EVT_MENU(self, 200, self.file_save_as)
 
635
        menu.Append(203, '&Print...', 'Print the current plot')
 
636
        wx.EVT_MENU(self, 203, self.file_print)
 
637
        menu.Append(204, 'Print Pre&view', 'Preview the current plot')
 
638
        wx.EVT_MENU(self, 204, self.file_preview)
 
639
        menu.Append(205, 'Close', 'Close plot')
 
640
        wx.EVT_MENU(self, 205, self.file_close)
 
641
        self.mainmenu.Append(menu, '&File')
 
642
        menu = wx.wxMenu()
 
643
        menu.Append(self.TITLE_TEXT, '&Graph Title', 'Title for plot')
 
644
        wx.EVT_MENU(self,self.TITLE_TEXT,self.title)
 
645
        menu.Append(self.X_TEXT, '&X Title', 'Title for X axis')
 
646
        wx.EVT_MENU(self,self.X_TEXT,self.title)
 
647
        menu.Append(self.Y_TEXT, '&Y Title', 'Title for Y axis')
 
648
        wx.EVT_MENU(self,self.Y_TEXT,self.title)
 
649
        self.mainmenu.Append(menu, '&Titles')
 
650
        #menu = wx.wxMenu()        
 
651
        #menu.Append(300, '&Profile', 'Check the hot spots in the program')
 
652
        #wx.EVT_MENU(self,300,self.OnProfile)
 
653
        #self.mainmenu.Append(menu, '&Utility')
 
654
        self.SetMenuBar(self.mainmenu)
 
655
        # A status bar to tell people what's happening
 
656
        self.CreateStatusBar(1)
 
657
        self.print_data = wx.wxPrintData()
 
658
        self.print_data.SetPaperId(wx.wxPAPER_LETTER)
 
659
        self.client = plot_canvas(self)
 
660
        if visible: self.Show(1)
 
661
        self.Raise()
 
662
        self.SetFocus()        
 
663
                    
 
664
    def plot_draw(self, event):
 
665
        #self.client.graphics = _InitObjects()
 
666
        self.client.title.text = 'Bubba'
 
667
        self.client.x_title.text = 'x title'
 
668
        self.client.y_title.text = 'y title'
 
669
        #self.client.y2_title.text = 'y2 title'
 
670
        for i in _InitObjects():
 
671
            self.client.line_list.append(i)
 
672
        #self.client.image_list.append(lena_obj())    
 
673
        self.client.draw();
 
674
 
 
675
 
 
676
    def profile(self, event):
 
677
        import profile
 
678
        #self.client.graphics = _InitObjects()
 
679
        self.client.title.text = 'Bubba'
 
680
        self.client.x_title.text = 'x title'
 
681
        self.client.y_title.text = 'y title'
 
682
        #self.client.y2_title.text = 'y2 title'
 
683
        #for i in _InitObjects():
 
684
        #    self.client.line_list.append(i)
 
685
        #self.client.image_list.append(lena_obj())    
 
686
        global bub
 
687
        bub = self
 
688
        profile.run('from plt import loop;loop()','profile')        
 
689
        
 
690
    def file_print(self, event):
 
691
        self.print_data.SetPaperId(wx.wxPAPER_LETTER)
 
692
        pdd = wx.wxPrintDialogData()
 
693
        pdd.SetPrintData(self.print_data)
 
694
        printer = wx.wxPrinter(pdd)
 
695
        printout = graph_printout(self.client)
 
696
        print_ok = printer.Print(self, printout)
 
697
        #Is Abort() not wrapped?
 
698
        #if not printer.Abort() and not print_ok:     
 
699
        #    wx.wxMessageBox("There was a problem printing.\n" \
 
700
        #                    "Perhaps your current printer is not set correctly?",
 
701
        #                    "Printing", wx.wxOK)
 
702
        #else:
 
703
        #    self.print_data = printer.GetPrintDialogData().GetPrintData()
 
704
        if print_ok:
 
705
            self.print_data = printer.GetPrintDialogData().GetPrintData()
 
706
        printout.Destroy()
 
707
 
 
708
    def file_preview(self, event):
 
709
        printout = graph_printout(self.client)
 
710
        printout2 = graph_printout(self.client)
 
711
        self.preview = wx.wxPrintPreview(printout, printout2, self.print_data)
 
712
        if not self.preview.Ok():
 
713
            #self.log.WriteText("Print Preview failed." \
 
714
            #                   "Check that default printer is configured\n")
 
715
            print "Print Preview failed." \
 
716
                  "Check that default printer is configured\n"
 
717
            return
 
718
        frame = wx.wxPreviewFrame(self.preview, self, "Preview")
 
719
        frame.Initialize()
 
720
        frame.SetPosition(self.GetPosition())
 
721
        frame.SetSize(self.GetSize())
 
722
        frame.Show(wx.true)
 
723
 
 
724
    def file_save_as(self, event):
 
725
        import os
 
726
        wildcard = "PNG files (*.png)|*.png|" \
 
727
                   "BMP files (*.bmp)|*.bmp|" \
 
728
                   "JPEG files (*.jpg)|*.jpg|" \
 
729
                   "PCX files (*.pcx)|*.pcx|" \
 
730
                   "TIFF files (*.tif)|*.tif|" \
 
731
                   "All Files |*|"
 
732
        dlg = wx.wxFileDialog(self, "Save As", ".", "", wildcard, wx.wxSAVE)
 
733
        if dlg.ShowModal() == wx.wxID_OK:
 
734
            f = dlg.GetPath()
 
735
            dummy, ftype = os.path.splitext(f)
 
736
            # strip .
 
737
            ftype = ftype[1:]
 
738
            if ftype in image_type_map.keys():
 
739
                self.client.save(dlg.GetPath(),ftype)
 
740
            else:
 
741
                msg = "Extension is currently used to determine file type." \
 
742
                      "'%s' is not a valid extension."  \
 
743
                      "You may use one of the following extensions. %s" \
 
744
                          % (ftype,image_type_map.keys())   
 
745
                d = wx.wxMessageDialog(self,msg,style=wx.wxOK)
 
746
                d.ShowModal()
 
747
                d.Destroy()
 
748
        dlg.Destroy()
 
749
 
 
750
    def file_close(self, event):
 
751
        self.Close()
 
752
        
 
753
    def format_font(self,event):
 
754
        font_attr,color_attr = 'font','color'
 
755
        if event.GetId() == self.TITLE_FONT:
 
756
            texts = [self.client.title]
 
757
        elif event.GetId() == self.AXIS_FONT:            
 
758
            texts = [self.client.x_title,self.client.y_title]
 
759
           #texts = [self.client.y_title]
 
760
        elif event.GetId() == self.LABEL_FONT:
 
761
            texts = [self.client.x_axis,self.client.y_axis]
 
762
            font_attr,color_attr = 'label_font','label_color'
 
763
        data = wx.wxFontData()
 
764
        current_color = get_color(getattr(texts[0],color_attr))
 
765
        current_font = getattr(texts[0],font_attr)
 
766
        data.SetColour(current_color)
 
767
        data.SetInitialFont(current_font)
 
768
        dlg = wx.wxFontDialog(self, data)
 
769
        if dlg.ShowModal() == wx.wxID_OK:
 
770
            data = dlg.GetFontData()
 
771
            font = data.GetChosenFont()
 
772
            color = data.GetColour()
 
773
            rgb = color.Red(),color.Green(),color.Blue()
 
774
            for text in texts:
 
775
                setattr(text,color_attr,rgb)
 
776
                setattr(text,font_attr,font)
 
777
                self.client.update()
 
778
        dlg.Destroy()
 
779
 
 
780
    def title(self,event):
 
781
        if event.GetId() == self.TITLE_TEXT:
 
782
            title = self.client.title
 
783
            prompt = 'Enter graph title'
 
784
        elif event.GetId() == self.X_TEXT:            
 
785
            title = self.client.x_title
 
786
            prompt = 'Enter x axis title'
 
787
        elif event.GetId() == self.Y_TEXT:
 
788
            title = self.client.y_title
 
789
            prompt = 'Enter y axis title'        
 
790
        dlg = wx.wxTextEntryDialog(self, prompt,'', title.text)
 
791
        if dlg.ShowModal() == wx.wxID_OK:
 
792
            title.text = dlg.GetValue()
 
793
        dlg.Destroy()
 
794
        self.client.update()
 
795
 
 
796
    def update(self):
 
797
        self.client.update()
 
798
            
 
799
    def __getattr__(self,key):
 
800
        try:        
 
801
            return self.__dict__[key]
 
802
        except KeyError:  
 
803
            return getattr(self.__dict__['client'],key)
 
804
    """        
 
805
    def __setattr__(self,key,val):
 
806
        #print key,val
 
807
        #if plot_canvas._attributes.has_key(key):
 
808
        #    self.__dict__['client'].__dict__[key] = val
 
809
        #    return None
 
810
        self.__dict__[key] = val
 
811
    """    
 
812
 
 
813
 
 
814
# global functions
 
815
 
 
816
def lena_obj():
 
817
    return image_object(lena(),colormap='grey')
 
818
 
 
819
def _InitObjects():
 
820
    # 100 points sin function, plotted as green circles
 
821
    data1 = 2.*pi*arange(200)/200.
 
822
    data1.shape = (100, 2)
 
823
    data1[:,1] = sin(data1[:,0])
 
824
    #markers1 = poly_marker(data1, color='green', marker='circle',size=1)
 
825
    markers1 = line_object(data1)
 
826
    
 
827
    # 50 points cos function, plotted as red line
 
828
    data1 = 2.*pi*arange(100)/100.
 
829
    data1.shape = (50,2)
 
830
    data1[:,1] = cos(data1[:,0])
 
831
    #lines = poly_line(data1, color='red')
 
832
    lines = line_object(data1)
 
833
    # A few more points...
 
834
    #markers2 = poly_marker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
 
835
    #                      (3.*pi/4., -1)], color='blue',
 
836
    #                      fillcolor='green', marker='cross')
 
837
    markers2 = line_object([(0., 0.), (pi/4., 1.), (pi/2, 0.),(3.*pi/4., -1)])
 
838
    # An Image
 
839
    return [markers1]#, lines, markers2]
 
840
 
 
841
 
 
842
def test_axis():
 
843
    a = axis_object(rotate = 0)
 
844
    graph_area = box_object((10,10),(100,100))
 
845
    bounds = (-1.,1.)
 
846
    a.calculate_ticks(bounds)
 
847
    dummy_dc = 0
 
848
    a.layout(graph_area,dummy_dc)
 
849
    print a.tick_points
 
850
    
 
851
    bounds = (0.,1.)
 
852
    a.calculate_ticks(bounds)
 
853
    a.layout(graph_area,dummy_dc)
 
854
    print a.tick_points
 
855
    print a.tick_start
 
856
    print a.tick_stop
 
857
 
 
858
 
 
859
 
 
860
if __name__ == '__main__':
 
861
        
 
862
    class MyApp(wx.wxApp):
 
863
        def OnInit(self):
 
864
            frame = plot_frame(wx.NULL, -1, "Graph",size=(400,400))
 
865
            frame.Show(wx.TRUE)
 
866
            self.SetTopWindow(frame)
 
867
            #frame.OnPlotDraw(None)
 
868
            return wx.TRUE
 
869
 
 
870
 
 
871
    app = MyApp(0)
 
872
    app.MainLoop()