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

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since fe15198 was 7432acb, checked in by andyfaff, 8 years ago

MAINT: search+replace '!= None' by 'is not None'

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