[a9d5684] | 1 | """ |
---|
| 2 | This module implements a faster canvas for plotting. |
---|
| 3 | it ovewrites some matplolib methods to allow printing on sys.platform=='win32' |
---|
| 4 | """ |
---|
| 5 | import wx |
---|
| 6 | import sys |
---|
[2df0b74] | 7 | import logging |
---|
[a9d5684] | 8 | from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg |
---|
| 9 | from matplotlib.backend_bases import MouseEvent, RendererBase |
---|
| 10 | from matplotlib.backends.backend_wx import GraphicsContextWx, PrintoutWx |
---|
| 11 | from matplotlib.backends.backend_wx import RendererWx |
---|
| 12 | |
---|
[463e7ffc] | 13 | logger = logging.getLogger(__name__) |
---|
[c155a16] | 14 | |
---|
[a9d5684] | 15 | |
---|
| 16 | def 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 | |
---|
| 32 | def select(self): |
---|
| 33 | """ |
---|
| 34 | """ |
---|
| 35 | pass |
---|
| 36 | |
---|
| 37 | |
---|
| 38 | def unselect(self): |
---|
| 39 | """ |
---|
| 40 | """ |
---|
| 41 | pass |
---|
| 42 | |
---|
| 43 | |
---|
| 44 | def OnPrintPage(self, page): |
---|
| 45 | """ |
---|
| 46 | override printPage of matplotlib |
---|
| 47 | """ |
---|
| 48 | self.canvas.draw() |
---|
| 49 | dc = self.GetDC() |
---|
| 50 | try: |
---|
[2df0b74] | 51 | (ppw, pph) = self.GetPPIPrinter() # printer's pixels per in |
---|
[a9d5684] | 52 | except: |
---|
| 53 | ppw = 1 |
---|
| 54 | pph = 1 |
---|
[2df0b74] | 55 | (pgw, _) = self.GetPageSizePixels() # page size in pixels |
---|
| 56 | (dcw, _) = dc.GetSize() |
---|
| 57 | (grw, _) = self.canvas.GetSizeTuple() |
---|
[a9d5684] | 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(): |
---|
[2df0b74] | 81 | page_scale = float(dcw) / pgw |
---|
[a9d5684] | 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)) |
---|
[5251ec6] | 99 | except Exception as exc: |
---|
| 100 | logger.error(exc) |
---|
[a9d5684] | 101 | |
---|
| 102 | # restore original figure resolution |
---|
| 103 | self.canvas.figure.set_facecolor(bgcolor) |
---|
[2df0b74] | 104 | # # used to be self.canvas.figure.dpi.set( fig_dpi) |
---|
[a9d5684] | 105 | self.canvas.figure.dpi = fig_dpi |
---|
| 106 | self.canvas.draw() |
---|
| 107 | return True |
---|
| 108 | |
---|
| 109 | GraphicsContextWx.select = select |
---|
| 110 | GraphicsContextWx.unselect = unselect |
---|
| 111 | PrintoutWx.OnPrintPage = OnPrintPage |
---|
| 112 | RendererBase.draw_image = draw_image |
---|
| 113 | |
---|
| 114 | |
---|
| 115 | class 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 |
---|
[2df0b74] | 124 | |
---|
[a9d5684] | 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) |
---|
[2df0b74] | 139 | |
---|
[a9d5684] | 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 |
---|
[2df0b74] | 149 | |
---|
[a9d5684] | 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) |
---|
[7432acb] | 155 | if self.panel.legend is not None and self.panel.legend_pos_loc: |
---|
[a9d5684] | 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 |
---|
[2df0b74] | 168 | |
---|
[a9d5684] | 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) |
---|
[2df0b74] | 187 | |
---|
[a9d5684] | 188 | def set_resizing(self, resizing=False): |
---|
| 189 | """ |
---|
| 190 | Setting the resizing |
---|
| 191 | """ |
---|
| 192 | self.resizing = resizing |
---|
| 193 | self.panel.set_resizing(False) |
---|
[2df0b74] | 194 | |
---|
[a9d5684] | 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() |
---|
[2df0b74] | 206 | # import time |
---|
| 207 | # st = time.time() |
---|
[a9d5684] | 208 | try: |
---|
| 209 | fig.draw(self) |
---|
[5251ec6] | 210 | except ValueError as exc: |
---|
| 211 | logger.error(exc) |
---|
[a9d5684] | 212 | else: |
---|
| 213 | self._isRendered = False |
---|
| 214 | if self.ndraw <= 1: |
---|
| 215 | self.ndraw += 1 |
---|
[2df0b74] | 216 | |
---|
[a9d5684] | 217 | def _onMouseWheel(self, evt): |
---|
| 218 | """Translate mouse wheel events into matplotlib events""" |
---|
| 219 | # Determine mouse location |
---|
[2df0b74] | 220 | _, h = self.figure.canvas.get_width_height() |
---|
[a9d5684] | 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() |
---|
[2df0b74] | 228 | # print "delta,rotation,rate",delta,rotation,rate |
---|
[a9d5684] | 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 |
---|
[2df0b74] | 249 | |
---|
[a9d5684] | 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. |
---|
[2df0b74] | 254 | |
---|
[a9d5684] | 255 | :TODO: Investigate what the root cause of the problem is. |
---|
[2df0b74] | 256 | |
---|
[a9d5684] | 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 |
---|
[b9f6d83] | 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() |
---|
[2df0b74] | 271 | super(FigureCanvas, self)._onLeave(evt) |
---|