source: sasview/src/sas/sasgui/plottools/canvas.py @ 1b061a31

Last change on this file since 1b061a31 was d7bb526, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 9 years ago

Refactored plottools into sasgui

  • Property mode set to 100644
File size: 8.5 KB
Line 
1"""
2This module implements a faster canvas for plotting.
3it ovewrites some matplolib methods to allow printing on sys.platform=='win32'
4"""
5import wx
6import sys
7import logging
8from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
9from matplotlib.backend_bases import MouseEvent, RendererBase
10from matplotlib.backends.backend_wx import GraphicsContextWx, PrintoutWx
11from matplotlib.backends.backend_wx import RendererWx
12
13
14def draw_image(self, x, y, im, bbox, clippath=None, clippath_trans=None):
15    """
16    Draw the image instance into the current axes;
17
18    :param x: is the distance in pixels from the left hand side of the canvas.
19    :param y: the distance from the origin.  That is, if origin is
20        upper, y is the distance from top.  If origin is lower, y
21        is the distance from bottom
22    :param im: the class`matplotlib._image.Image` instance
23    :param bbox: a class `matplotlib.transforms.Bbox` instance for clipping, or
24        None
25
26    """
27    pass
28
29
30def select(self):
31    """
32    """
33    pass
34
35
36def unselect(self):
37    """
38    """
39    pass
40
41
42def OnPrintPage(self, page):
43    """
44    override printPage of matplotlib
45    """
46    self.canvas.draw()
47    dc = self.GetDC()
48    try:
49        (ppw, pph) = self.GetPPIPrinter()  # printer's pixels per in
50    except:
51        ppw = 1
52        pph = 1
53    (pgw, _) = self.GetPageSizePixels()  # page size in pixels
54    (dcw, _) = dc.GetSize()
55    (grw, _) = self.canvas.GetSizeTuple()
56
57    # save current figure dpi resolution and bg color,
58    # so that we can temporarily set them to the dpi of
59    # the printer, and the bg color to white
60    bgcolor = self.canvas.figure.get_facecolor()
61    fig_dpi = self.canvas.figure.dpi
62
63    # draw the bitmap, scaled appropriately
64    vscale = float(ppw) / fig_dpi
65
66    # set figure resolution,bg color for printer
67    self.canvas.figure.dpi = ppw
68    self.canvas.figure.set_facecolor('#FFFFFF')
69
70    renderer = RendererWx(self.canvas.bitmap, self.canvas.figure.dpi)
71    self.canvas.figure.draw(renderer)
72    self.canvas.bitmap.SetWidth(int(self.canvas.bitmap.GetWidth() * vscale))
73    self.canvas.bitmap.SetHeight(int(self.canvas.bitmap.GetHeight() * vscale))
74    self.canvas.draw()
75
76    # page may need additional scaling on preview
77    page_scale = 1.0
78    if self.IsPreview():
79        page_scale = float(dcw) / pgw
80
81    # get margin in pixels = (margin in in) * (pixels/in)
82    top_margin = int(self.margin * pph * page_scale)
83    left_margin = int(self.margin * ppw * page_scale)
84
85    # set scale so that width of output is self.width inches
86    # (assuming grw is size of graph in inches....)
87    user_scale = (self.width * fig_dpi * page_scale) / float(grw)
88    dc.SetDeviceOrigin(left_margin, top_margin)
89    dc.SetUserScale(user_scale, user_scale)
90
91    # this cute little number avoid API inconsistencies in wx
92    try:
93        dc.DrawBitmap(self.canvas.bitmap, 0, 0)
94    except:
95        try:
96            dc.DrawBitmap(self.canvas.bitmap, (0, 0))
97        except:
98            logging.error(sys.exc_value)
99
100    # restore original figure  resolution
101    self.canvas.figure.set_facecolor(bgcolor)
102    # # used to be self.canvas.figure.dpi.set( fig_dpi)
103    self.canvas.figure.dpi = fig_dpi
104    self.canvas.draw()
105    return True
106
107GraphicsContextWx.select = select
108GraphicsContextWx.unselect = unselect
109PrintoutWx.OnPrintPage = OnPrintPage
110RendererBase.draw_image = draw_image
111
112
113class FigureCanvas(FigureCanvasWxAgg):
114    """
115    Add features to the wx agg canvas for better support of AUI and
116    faster plotting.
117    """
118
119    def __init__(self, *args, **kw):
120        super(FigureCanvas, self).__init__(*args, **kw)
121        self._isRendered = False
122
123        # Create an timer for handling draw_idle requests
124        # If there are events pending when the timer is
125        # complete, reset the timer and continue.  The
126        # alternative approach, binding to wx.EVT_IDLE,
127        # doesn't behave as nicely.
128        self.idletimer = wx.CallLater(1, self._onDrawIdle)
129        # panel information
130        self.panel = None
131        self.resizing = False
132        self.xaxis = None
133        self.yaxis = None
134        self.ndraw = 0
135        # Support for mouse wheel
136        self.Bind(wx.EVT_MOUSEWHEEL, self._onMouseWheel)
137
138    def set_panel(self, panel):
139        """
140        Set axes
141        """
142        # set panel
143        self.panel = panel
144        # set axes
145        self.xaxis = panel.subplot.xaxis
146        self.yaxis = panel.subplot.yaxis
147
148    def draw_idle(self, *args, **kwargs):
149        """
150        Render after a delay if no other render requests have been made.
151        """
152        self.panel.subplot.grid(self.panel.grid_on)
153        if self.panel.legend != None and self.panel.legend_pos_loc:
154            self.panel.legend._loc = self.panel.legend_pos_loc
155        self.idletimer.Restart(5, *args, **kwargs)  # Delay by 5 ms
156
157    def _onDrawIdle(self, *args, **kwargs):
158        """
159        """
160        if False and wx.GetApp().Pending():
161            self.idletimer.Restart(5, *args, **kwargs)
162        else:
163            # Draw plot, changes resizing too
164            self.draw(*args, **kwargs)
165            self.resizing = False
166
167    def _get_axes_switch(self):
168        """
169        """
170        # Check resize whether or not True
171        if self.panel.dimension == 3:
172            return
173
174        # This is for fast response when plot is being resized
175        if not self.resizing:
176            self.xaxis.set_visible(True)
177            self.yaxis.set_visible(True)
178            self.panel.schedule_full_draw('del')
179        else:
180            self.xaxis.set_visible(False)
181            self.yaxis.set_visible(False)
182            self.panel.schedule_full_draw('append')
183        # set the resizing back to default= False
184        self.set_resizing(False)
185
186    def set_resizing(self, resizing=False):
187        """
188        Setting the resizing
189        """
190        self.resizing = resizing
191        self.panel.set_resizing(False)
192
193    def draw(self, drawDC=None):
194        """
195        Render the figure using agg.
196        """
197        # Only draw if window is shown, otherwise graph will bleed through
198        # on the notebook style AUI widgets.
199        #    raise
200        fig = FigureCanvasWxAgg
201        if self.IsShownOnScreen() and self.ndraw != 1:
202            self._isRendered = True
203            self._get_axes_switch()
204            # import time
205            # st = time.time()
206            try:
207                fig.draw(self)
208            except ValueError:
209                logging.error(sys.exc_value)
210        else:
211            self._isRendered = False
212        if self.ndraw <= 1:
213            self.ndraw += 1
214
215    def _onMouseWheel(self, evt):
216        """Translate mouse wheel events into matplotlib events"""
217        # Determine mouse location
218        _, h = self.figure.canvas.get_width_height()
219        x = evt.GetX()
220        y = h - evt.GetY()
221
222        # Convert delta/rotation/rate into a floating point step size
223        delta = evt.GetWheelDelta()
224        rotation = evt.GetWheelRotation()
225        rate = evt.GetLinesPerAction()
226        # print "delta,rotation,rate",delta,rotation,rate
227        step = rate * float(rotation) / delta
228
229        # Convert to mpl event
230        evt.Skip()
231        self.scroll_event(x, y, step, guiEvent=evt)
232
233    def scroll_event(self, x, y, step=1, guiEvent=None):
234        """
235        Backend derived classes should call this function on any
236        scroll wheel event.  x,y are the canvas coords: 0,0 is lower,
237        left.  button and key are as defined in MouseEvent
238        """
239        button = 'up' if step >= 0 else 'down'
240        self._button = button
241        s = 'scroll_event'
242        event = MouseEvent(s, self, x, y, button, self._key, guiEvent=guiEvent)
243        setattr(event, 'step', step)
244        self.callbacks.process(s, event)
245        if step != 0:
246            self.panel.is_zoomed = True
247
248    def _onRightButtonDown(self, evt):
249        """
250        Overload the right button down call back to avoid a problem
251        with the context menu over matplotlib plots on linux.
252
253        :TODO: Investigate what the root cause of the problem is.
254
255        """
256        if sys.platform == 'linux2' or self.panel.dimension == 3:
257            evt.Skip()
258        else:
259            FigureCanvasWxAgg._onRightButtonDown(self, evt)
260            # This solves the focusing on rightclick.
261            # Todo: better design
262            self.panel.parent.set_plot_unfocus()
263            self.panel.on_set_focus(None)
264        return
265
266    # CRUFT: wx 3.0.0.0 on OS X doesn't release the mouse on leaving window
267    def _onLeave(self, evt):
268        if self.HasCapture(): self.ReleaseMouse()
269        super(FigureCanvas, self)._onLeave(evt)
Note: See TracBrowser for help on using the repository browser.