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

ticket-1249
Last change on this file since 34f23c8 was 34f23c8, checked in by Paul Kienzle <pkienzle@…>, 3 months ago

py3/wx4 compatibility changes for gui. Refs #1249

  • Property mode set to 100644
File size: 8.6 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.GetSize()
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 Exception as exc:
100            logger.error(exc)
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        if self.panel is not None:
155            # TODO: grid/panel manip doesn't belong here
156            self.panel.subplot.grid(self.panel.grid_on)
157            if self.panel.legend is not None and self.panel.legend_pos_loc:
158                self.panel.legend._loc = self.panel.legend_pos_loc
159        self.idletimer.Restart(5, *args, **kwargs)  # Delay by 5 ms
160
161    def _onDrawIdle(self, *args, **kwargs):
162        """
163        """
164        if False and wx.GetApp().Pending():
165            self.idletimer.Restart(5, *args, **kwargs)
166        else:
167            # Draw plot, changes resizing too
168            self.draw(*args, **kwargs)
169            self.resizing = False
170
171    def _get_axes_switch(self):
172        """
173        """
174        # Check resize whether or not True
175        if self.panel.dimension == 3:
176            return
177
178        # This is for fast response when plot is being resized
179        if not self.resizing:
180            self.xaxis.set_visible(True)
181            self.yaxis.set_visible(True)
182            self.panel.schedule_full_draw('del')
183        else:
184            self.xaxis.set_visible(False)
185            self.yaxis.set_visible(False)
186            self.panel.schedule_full_draw('append')
187        # set the resizing back to default= False
188        self.set_resizing(False)
189
190    def set_resizing(self, resizing=False):
191        """
192        Setting the resizing
193        """
194        self.resizing = resizing
195        self.panel.set_resizing(False)
196
197    def draw(self, drawDC=None):
198        """
199        Render the figure using agg.
200        """
201        # Only draw if window is shown, otherwise graph will bleed through
202        # on the notebook style AUI widgets.
203        #    raise
204        fig = FigureCanvasWxAgg
205        if self.IsShownOnScreen() and self.ndraw != 1:
206            self._isRendered = True
207            self._get_axes_switch()
208            # import time
209            # st = time.time()
210            try:
211                fig.draw(self)
212            except ValueError as exc:
213                logger.error(exc)
214        else:
215            self._isRendered = False
216        if self.ndraw <= 1:
217            self.ndraw += 1
218
219    def _onMouseWheel(self, evt):
220        """Translate mouse wheel events into matplotlib events"""
221        # Determine mouse location
222        _, h = self.figure.canvas.get_width_height()
223        x = evt.GetX()
224        y = h - evt.GetY()
225
226        # Convert delta/rotation/rate into a floating point step size
227        delta = evt.GetWheelDelta()
228        rotation = evt.GetWheelRotation()
229        rate = evt.GetLinesPerAction()
230        # print "delta,rotation,rate",delta,rotation,rate
231        step = rate * float(rotation) / delta
232
233        # Convert to mpl event
234        evt.Skip()
235        self.scroll_event(x, y, step, guiEvent=evt)
236
237    def scroll_event(self, x, y, step=1, guiEvent=None):
238        """
239        Backend derived classes should call this function on any
240        scroll wheel event.  x,y are the canvas coords: 0,0 is lower,
241        left.  button and key are as defined in MouseEvent
242        """
243        button = 'up' if step >= 0 else 'down'
244        self._button = button
245        s = 'scroll_event'
246        event = MouseEvent(s, self, x, y, button, self._key, guiEvent=guiEvent)
247        setattr(event, 'step', step)
248        self.callbacks.process(s, event)
249        if step != 0:
250            self.panel.is_zoomed = True
251
252    def _onRightButtonDown(self, evt):
253        """
254        Overload the right button down call back to avoid a problem
255        with the context menu over matplotlib plots on linux.
256
257        :TODO: Investigate what the root cause of the problem is.
258
259        """
260        if sys.platform == 'linux2' or self.panel.dimension == 3:
261            evt.Skip()
262        else:
263            FigureCanvasWxAgg._onRightButtonDown(self, evt)
264            # This solves the focusing on rightclick.
265            # Todo: better design
266            self.panel.parent.set_plot_unfocus()
267            self.panel.on_set_focus(None)
268        return
269
270    # CRUFT: wx 3.0.0.0 on OS X doesn't release the mouse on leaving window
271    def _onLeave(self, evt):
272        if self.HasCapture(): self.ReleaseMouse()
273        super(FigureCanvas, self)._onLeave(evt)
Note: See TracBrowser for help on using the repository browser.