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