Remove arg-highlight on calculate
[reinteract.git] / lib / replot.py
blob1d5b96a5ab4b814c1b210ebe97a873143fd7f25b
1 # Copyright 2007, 2008 Owen Taylor
2 # Copyright 2008 Kai Willadsen
3 # Copyright 2009, 2010 Jorn Baayen
5 # This file is part of Reinteract and distributed under the terms
6 # of the BSD license. See the file COPYING in the Reinteract
7 # distribution for full details.
9 ########################################################################
11 import cairo
12 import pango
13 import gtk
14 import matplotlib
15 from matplotlib.figure import Figure
16 from matplotlib.backends.backend_cairo import RendererCairo, FigureCanvasCairo
17 import numpy
19 from reinteract.recorded_object import RecordedObject, default_filter
20 import reinteract.custom_result as custom_result
22 DEFAULT_FIGURE_WIDTH = 6
23 DEFAULT_FIGURE_HEIGHT = 4.5
24 DEFAULT_ASPECT_RATIO = DEFAULT_FIGURE_WIDTH / DEFAULT_FIGURE_HEIGHT
26 class _PlotResultCanvas(FigureCanvasCairo):
27 def draw_event(*args):
28 # Since we never change anything about the figure, the only time we
29 # need to redraw is in response to an expose event, which we handle
30 # ourselves
31 pass
33 class PlotWidget(custom_result.ResultWidget):
34 __gsignals__ = {
35 'button-press-event': 'override',
36 'button-release-event': 'override',
37 'expose-event': 'override',
38 'size-allocate': 'override',
39 'unrealize': 'override'
42 def __init__(self, result):
43 custom_result.ResultWidget.__init__(self)
45 figsize=(DEFAULT_FIGURE_WIDTH, DEFAULT_FIGURE_HEIGHT)
47 self.figure = Figure(facecolor='white', figsize=figsize)
48 self.canvas = _PlotResultCanvas(self.figure)
50 self.axes = self.figure.add_subplot(111)
52 self.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE)
54 self.cached_contents = None
56 self.sidebar_width = -1
58 def do_expose_event(self, event):
59 cr = self.window.cairo_create()
61 if not self.cached_contents:
62 self.cached_contents = cr.get_target().create_similar(cairo.CONTENT_COLOR,
63 self.allocation.width, self.allocation.height)
65 renderer = RendererCairo(self.figure.dpi)
66 renderer.set_width_height(self.allocation.width, self.allocation.height)
67 renderer.set_ctx_from_surface(self.cached_contents)
69 self.figure.draw(renderer)
71 # event.region is not bound: http://bugzilla.gnome.org/show_bug.cgi?id=487158
72 # gdk_context = gtk.gdk.CairoContext(renderer.ctx)
73 # gdk_context.region(event.region)
74 # gdk_context.clip()
76 cr.set_source_surface(self.cached_contents, 0, 0)
77 cr.paint()
79 def do_size_allocate(self, allocation):
80 if allocation.width != self.allocation.width or allocation.height != self.allocation.height:
81 self.cached_contents = None
83 gtk.DrawingArea.do_size_allocate(self, allocation)
85 def do_unrealize(self):
86 gtk.DrawingArea.do_unrealize(self)
88 self.cached_contents = None
90 def do_button_press_event(self, event):
91 if event.button == 3:
92 custom_result.show_menu(self, event, save_callback=self.__save)
93 return True
94 else:
95 return True
97 def do_button_release_event(self, event):
98 return True
100 def do_realize(self):
101 gtk.DrawingArea.do_realize(self)
102 cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR)
103 self.window.set_cursor(cursor)
105 def do_size_request(self, requisition):
106 try:
107 # matplotlib < 0.98
108 requisition.width = self.figure.bbox.width()
109 requisition.height = self.figure.bbox.height()
110 except TypeError:
111 # matplotlib >= 0.98
112 requisition.width = self.figure.bbox.width
113 requisition.height = self.figure.bbox.height
115 def recompute_figure_size(self):
116 width = (self.sidebar_width / self.figure.dpi)
117 height = width / DEFAULT_ASPECT_RATIO
119 self.figure.set_figwidth(width)
120 self.figure.set_figheight(height)
122 self.queue_resize()
124 def sync_dpi(self, dpi):
125 self.figure.set_dpi(dpi)
126 if self.sidebar_width >= 0:
127 self.recompute_figure_size()
129 def set_sidebar_width(self, width):
130 if self.sidebar_width == width:
131 return
133 self.sidebar_width = width
134 if self.sidebar_width >= 0:
135 self.recompute_figure_size()
137 def sync_style(self, style):
138 self.cached_contents = None
140 matplotlib.rcParams['font.size'] = self.parent.style.font_desc.get_size() / pango.SCALE
142 def __save(self, filename):
143 # The save/restore here was added to matplotlib's after 0.90. We duplicate
144 # it for compatibility with older versions. (The code would need modification
145 # for 0.98 and newer, which is the reason for the particular version in the
146 # check)
148 version = [int(x) for x in matplotlib.__version__.split('.')]
149 need_save = version[:2] < [0, 98]
150 if need_save:
151 orig_dpi = self.figure.dpi.get()
152 orig_facecolor = self.figure.get_facecolor()
153 orig_edgecolor = self.figure.get_edgecolor()
155 try:
156 self.canvas.print_figure(filename)
157 finally:
158 if need_save:
159 self.figure.dpi.set(orig_dpi)
160 self.figure.set_facecolor(orig_facecolor)
161 self.figure.set_edgecolor(orig_edgecolor)
162 self.figure.set_canvas(self.canvas)
164 # Return dimensions to caller.
166 # def do_size_allocate(self, allocation):
167 # gtk.DrawingArea.do_size_allocate(self, allocation)
169 # dpi = self.figure.dpi.get()
170 # self.figure.set_size_inches (allocation.width / dpi, allocation.height / dpi)
172 def _validate_args(args):
174 # The matplotlib argument parsing is a little wonky
176 # plot(x, y, 'fmt', y2)
177 # plot(x1, y2, x2, y2, 'fmt', y3)
179 # Are valid, but
181 # plot(x, y, y2)
183 # is not. We just duplicate the algorithm here
185 l = len(args)
186 i = 0
187 while True:
188 xi = None
189 yi = None
190 formati = None
192 remaining = l - i
193 if remaining == 0:
194 break
195 elif remaining == 1:
196 yi = i
197 i += 1
198 # The 'remaining != 3 and' encapsulates the wonkyness referred to above
199 elif remaining == 2 or (remaining != 3 and not isinstance(args[i + 2], basestring)):
200 # plot(...., x, y [, ....])
201 xi = i
202 yi = i + 1
203 i += 2
204 else:
205 # plot(....., x, y, format [, ...])
206 xi = i
207 yi = i + 1
208 formati = i + 2
209 i += 3
211 if xi is not None:
212 arg = args[xi]
213 if isinstance(arg, numpy.ndarray):
214 xshape = arg.shape
215 elif isinstance(arg, list):
216 # Not supporting nested python lists here
217 xshape = (len(arg),)
218 else:
219 raise TypeError("Expected numpy array or list for argument %d" % (xi + 1))
220 else:
221 xshape = None
223 # y isn't optional, pretend it is to preserve code symmetry
225 if yi is not None:
226 arg = args[yi]
227 if isinstance(arg, numpy.ndarray):
228 yshape = arg.shape
229 elif isinstance(arg, list):
230 # Not supporting nested python lists here
231 yshape = (len(arg),)
232 else:
233 raise TypeError("Expected numpy array or list for argument %d" % (yi + 1))
234 else:
235 yshape = None
237 if xshape is not None and yshape is not None and xshape != yshape:
238 raise TypeError("Shapes of arguments %d and %d aren't compatible" % ((xi + 1), (yi + 1)))
240 if formati is not None and not isinstance(args[formati], basestring):
241 raise TypeError("Expected format string for argument %d" % (formati + 1))
243 class Axes(RecordedObject, custom_result.CustomResult):
244 def __init__(self, display='inline'):
245 RecordedObject.__init__(self)
246 self.display = display
248 def _check_plot(self, name, args, kwargs, spec):
249 _validate_args(args)
251 def create_widget(self):
252 widget = PlotWidget(self)
253 self._replay(widget.axes)
254 return widget
256 def print_result(self, print_context, render=True):
257 figure = Figure(facecolor='white', figsize=(6,4.5))
258 figure.set_dpi(72)
259 # Don't draw the frame, please.
260 figure.set_frameon(False)
262 canvas = _PlotResultCanvas(figure)
264 axes = figure.add_subplot(111)
265 self._replay(axes)
267 width, height = figure.bbox.width, figure.bbox.height
269 if render:
270 cr = print_context.get_cairo_context()
272 renderer = RendererCairo(figure.dpi)
273 renderer.set_width_height(width, height)
274 if hasattr(renderer, 'gc'):
275 # matplotlib-0.99 and newer
276 renderer.gc.ctx = cr
277 else:
278 # matplotlib-0.98
280 # RendererCairo.new_gc() does a restore to get the context back
281 # to its original state after changes
282 cr.save()
283 renderer.ctx = cr
285 figure.draw(renderer)
287 if not hasattr(renderer, 'gc'):
288 # matplotlib-0.98
289 # Reverse the save() we did before drawing
290 cr.restore()
292 return height
294 def filter_method(baseclass, name):
295 if not default_filter(baseclass, name):
296 return False
297 if name.startswith('get_'):
298 return False
299 if name == 'create_widget':
300 return False
301 return True
303 Axes._set_target_class(matplotlib.axes.Axes, filter_method)
306 def plot(*args, **kwargs):
307 axes = Axes()
308 axes.plot(*args, **kwargs)
309 return axes
311 def imshow(*args, **kwargs):
312 axes = Axes()
313 axes.imshow(*args, **kwargs)
314 return axes