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 |
---|
7 | import logging |
---|
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 | |
---|
13 | logger = logging.getLogger(__name__) |
---|
14 | |
---|
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: |
---|
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 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 | |
---|
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 |
---|
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 as exc: |
---|
211 | logger.error(exc) |
---|
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) |
---|