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 ########################################################################
15 from matplotlib
.figure
import Figure
16 from matplotlib
.backends
.backend_cairo
import RendererCairo
, FigureCanvasCairo
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
33 class PlotWidget(custom_result
.ResultWidget
):
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)
76 cr
.set_source_surface(self
.cached_contents
, 0, 0)
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
):
92 custom_result
.show_menu(self
, event
, save_callback
=self
.__save
)
97 def do_button_release_event(self
, event
):
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
):
108 requisition
.width
= self
.figure
.bbox
.width()
109 requisition
.height
= self
.figure
.bbox
.height()
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
)
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
:
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
148 version
= [int(x
) for x
in matplotlib
.__version
__.split('.')]
149 need_save
= version
[:2] < [0, 98]
151 orig_dpi
= self
.figure
.dpi
.get()
152 orig_facecolor
= self
.figure
.get_facecolor()
153 orig_edgecolor
= self
.figure
.get_edgecolor()
156 self
.canvas
.print_figure(filename
)
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)
183 # is not. We just duplicate the algorithm here
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 [, ....])
205 # plot(....., x, y, format [, ...])
213 if isinstance(arg
, numpy
.ndarray
):
215 elif isinstance(arg
, list):
216 # Not supporting nested python lists here
219 raise TypeError("Expected numpy array or list for argument %d" % (xi
+ 1))
223 # y isn't optional, pretend it is to preserve code symmetry
227 if isinstance(arg
, numpy
.ndarray
):
229 elif isinstance(arg
, list):
230 # Not supporting nested python lists here
233 raise TypeError("Expected numpy array or list for argument %d" % (yi
+ 1))
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
):
251 def create_widget(self
):
252 widget
= PlotWidget(self
)
253 self
._replay
(widget
.axes
)
256 def print_result(self
, print_context
, render
=True):
257 figure
= Figure(facecolor
='white', figsize
=(6,4.5))
259 # Don't draw the frame, please.
260 figure
.set_frameon(False)
262 canvas
= _PlotResultCanvas(figure
)
264 axes
= figure
.add_subplot(111)
267 width
, height
= figure
.bbox
.width
, figure
.bbox
.height
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
280 # RendererCairo.new_gc() does a restore to get the context back
281 # to its original state after changes
285 figure
.draw(renderer
)
287 if not hasattr(renderer
, 'gc'):
289 # Reverse the save() we did before drawing
294 def filter_method(baseclass
, name
):
295 if not default_filter(baseclass
, name
):
297 if name
.startswith('get_'):
299 if name
== 'create_widget':
303 Axes
._set
_target
_class
(matplotlib
.axes
.Axes
, filter_method
)
306 def plot(*args
, **kwargs
):
308 axes
.plot(*args
, **kwargs
)
311 def imshow(*args
, **kwargs
):
313 axes
.imshow(*args
, **kwargs
)