77 import matplotlib
88 matplotlib.use("'module://pygame_matplotlib.backend_pygame'")
99"""
10+ from __future__ import annotations
11+ import matplotlib
12+ from matplotlib .pyplot import figure
1013import numpy as np
1114from matplotlib .transforms import Affine2D
1215import pygame
16+ from pygame import surface
1317import pygame .image
1418from pygame import gfxdraw
1519from matplotlib ._pylab_helpers import Gcf
1620from matplotlib .backend_bases import (
17- FigureCanvasBase , FigureManagerBase , GraphicsContextBase , RendererBase )
21+ FigureCanvasBase ,
22+ FigureManagerBase ,
23+ GraphicsContextBase ,
24+ RendererBase ,
25+ )
1826from matplotlib .figure import Figure
1927from matplotlib .path import Path
28+ from matplotlib .transforms import Bbox
2029
2130
2231class FigureSurface (pygame .Surface , Figure ):
@@ -26,20 +35,22 @@ class FigureSurface(pygame.Surface, Figure):
2635 that help handling the figures in pygame.
2736 """
2837
38+ canvas : FigureCanvasPygame
39+
2940 def __init__ (self , * args , ** kwargs ):
3041 """Create a FigureSurface object.
3142
3243 Signature is the same as matplotlib Figure object.
3344 """
3445 Figure .__init__ (self , * args , ** kwargs )
3546 pygame .Surface .__init__ (self , self .bbox .size )
36- self .fill (' white' )
47+ self .fill (" white" )
3748
3849 def set_bounding_rect (self , rect : pygame .Rect ):
3950 """Set a bounding rectangle around the figure."""
4051 # First convert inches to pixels for matplotlib
4152 DPI = self .get_dpi ()
42- self .set_size_inches (rect .width / float (DPI ), rect .height / float (DPI ))
53+ self .set_size_inches (rect .width / float (DPI ), rect .height / float (DPI ))
4354 # Initialize a new surface for the plot
4455 pygame .Surface .__init__ (self , (rect .width , rect .height ))
4556 # Redraw the figure
@@ -52,27 +63,33 @@ class RendererPygame(RendererBase):
5263 The draw methods convert maptplotlib into pygame.draw .
5364 """
5465
66+ surface : pygame .Surface
67+
5568 def __init__ (self , dpi ):
5669 super ().__init__ ()
5770 self .dpi = dpi
5871
72+ def rect_from_bbox (self , bbox : Bbox ) -> pygame .Rect :
73+ """Convert a matplotlib bbox to the equivalent pygame Rect."""
74+ print (bbox .get_points ())
75+
5976 def draw_path (self , gc , path , transform , rgbFace = None ):
6077
6178 if rgbFace is not None :
62- color = tuple ([
63- int (val * 255 ) for i , val in enumerate (rgbFace ) if i < 3
64- ] )
79+ color = tuple (
80+ [ int (val * 255 ) for i , val in enumerate (rgbFace ) if i < 3 ]
81+ )
6582 else :
66- color = tuple ([
67- int (val * 255 ) for i , val in enumerate (gc .get_rgb ()) if i < 3
68- ] )
83+ color = tuple (
84+ [ int (val * 255 ) for i , val in enumerate (gc .get_rgb ()) if i < 3 ]
85+ )
6986
7087 linewidth = int (gc .get_linewidth ())
7188
7289 transfrom_to_pygame_axis = Affine2D ()
73- transfrom_to_pygame_axis .set_matrix ([
74- [1 , 0 , 0 ], [0 , - 1 , self .surface .get_height ()], [0 , 0 , 1 ]
75- ] )
90+ transfrom_to_pygame_axis .set_matrix (
91+ [[ 1 , 0 , 0 ], [0 , - 1 , self .surface .get_height ()], [0 , 0 , 1 ] ]
92+ )
7693
7794 transform += transfrom_to_pygame_axis
7895
@@ -95,21 +112,17 @@ def draw_path(self, gc, path, transform, rgbFace=None):
95112 poly_points .append (point )
96113 elif code == Path .CURVE3 or code == Path .CURVE4 :
97114 end_point = point [2 :]
98- points_curve = np .concatenate (
99- (previous_point , point )
100- ). reshape (( - 1 , 2 ))
115+ points_curve = np .concatenate (( previous_point , point )). reshape (
116+ (- 1 , 2 )
117+ )
101118 gfxdraw .bezier (
102119 self .surface , points_curve , len (points_curve ), color
103120 )
104121 previous_point = end_point
105122 poly_points .append (end_point )
106123 elif code == Path .CLOSEPOLY :
107124 if len (poly_points ) > 2 :
108- gfxdraw .filled_polygon (
109- self .surface ,
110- poly_points ,
111- color
112- )
125+ gfxdraw .filled_polygon (self .surface , poly_points , color )
113126 elif code == Path .MOVETO :
114127 poly_points .append (point )
115128 previous_point = point
@@ -119,37 +132,38 @@ def draw_path(self, gc, path, transform, rgbFace=None):
119132 # draw_markers is optional, and we get more correct relative
120133 # timings by leaving it out. backend implementers concerned with
121134 # performance will probably want to implement it
122- # def draw_markers(self, gc, marker_path, marker_trans, path, trans,
123- # rgbFace=None):
124- # pass
135+ # def draw_markers(self, gc, marker_path, marker_trans, path, trans,
136+ # rgbFace=None):
137+ # pass
125138
126139 # draw_path_collection is optional, and we get more correct
127140 # relative timings by leaving it out. backend implementers concerned with
128141 # performance will probably want to implement it
129- # def draw_path_collection(self, gc, master_transform, paths,
130- # all_transforms, offsets, offsetTrans,
131- # facecolors, edgecolors, linewidths, linestyles,
132- # antialiaseds):
133- # pass
142+ # def draw_path_collection(self, gc, master_transform, paths,
143+ # all_transforms, offsets, offsetTrans,
144+ # facecolors, edgecolors, linewidths, linestyles,
145+ # antialiaseds):
146+ # pass
134147
135148 # draw_quad_mesh is optional, and we get more correct
136149 # relative timings by leaving it out. backend implementers concerned with
137150 # performance will probably want to implement it
138- # def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight,
139- # coordinates, offsets, offsetTrans, facecolors,
140- # antialiased, edgecolors):
141- # pass
151+ # def draw_quad_mesh(self, gc, master_transform, meshWidth, meshHeight,
152+ # coordinates, offsets, offsetTrans, facecolors,
153+ # antialiased, edgecolors):
154+ # pass
142155
143156 def draw_image (self , gc , x , y , im ):
144157 img_surf = pygame .image .frombuffer (
145158 # Need to flip the image as pygame starts top left
146159 np .ascontiguousarray (np .flip (im , axis = 0 )),
147- (im .shape [1 ], im .shape [0 ]), 'RGBA'
160+ (im .shape [1 ], im .shape [0 ]),
161+ "RGBA" ,
148162 )
149163 self .surface .blit (
150164 img_surf ,
151165 # Image starts top left
152- (x , self .surface .get_height () - y - im .shape [0 ])
166+ (x , self .surface .get_height () - y - im .shape [0 ]),
153167 )
154168
155169 def draw_text (self , gc , x , y , s , prop , angle , ismath = False , mtext = None ):
@@ -161,7 +175,7 @@ def draw_text(self, gc, x, y, s, prop, angle, ismath=False, mtext=None):
161175 myfont = pygame .font .Font (prop .get_file (), int (font_size ))
162176 # apply it to text on a label
163177 font_surface = myfont .render (
164- s , gc .get_antialiased (), [val * 255 for val in gc .get_rgb ()]
178+ s , gc .get_antialiased (), [val * 255 for val in gc .get_rgb ()]
165179 )
166180 if mtext is not None :
167181 # Reads the position of the mtext
@@ -196,6 +210,22 @@ def points_to_pixels(self, points):
196210 # else
197211 # return points/72.0 * self.dpi.get()
198212
213+ def clear (self ):
214+ self .surface .fill ("white" )
215+
216+ def copy_from_bbox (self , bbox ):
217+ copy = self .surface .copy ()
218+ copy .bbox = bbox
219+ return copy
220+
221+ def restore_region (self , region : pygame .Surface , bbox : Bbox , xy ):
222+ rect = (
223+ pygame .Rect (0 , 0 , * self .surface .get_size ())
224+ if bbox is None
225+ else self .rect_from_bbox (bbox )
226+ )
227+ region .blit (self .surface , rect )
228+
199229
200230class GraphicsContextPygame (GraphicsContextBase ):
201231 """
@@ -243,7 +273,7 @@ def show(*, block=None):
243273 should do nothing.
244274 """
245275 for manager in Gcf .get_all_fig_managers ():
246- manager .show ()
276+ manager .show (block )
247277
248278
249279def new_figure_manager (num , * args , FigureClass = FigureSurface , ** kwargs ):
@@ -282,25 +312,33 @@ class methods button_press_event, button_release_event,
282312 figure : `matplotlib.figure.Figure`
283313 A high-level Figure instance
284314 """
315+
285316 # File types allowed for saving
286317 filetypes = {
287- ' jpeg' : ' Joint Photographic Experts Group' ,
288- ' jpg' : ' Joint Photographic Experts Group' ,
289- ' png' : ' Portable Network Graphics' ,
290- ' bmp' : ' Bitmap Image File' ,
291- ' tga' : ' Truevision Graphics Adapter' ,
318+ " jpeg" : " Joint Photographic Experts Group" ,
319+ " jpg" : " Joint Photographic Experts Group" ,
320+ " png" : " Portable Network Graphics" ,
321+ " bmp" : " Bitmap Image File" ,
322+ " tga" : " Truevision Graphics Adapter" ,
292323 }
324+ figure : FigureSurface
325+ renderer : RendererPygame
293326
294327 def __init__ (self , figure = None ):
295328 super ().__init__ (figure )
296329
297330 # You should provide a print_xxx function for every file format
298331 # you can write.
299332 for file_extension in self .filetypes .keys ():
300- setattr (
301- self , 'print_{}' .format (file_extension ),
302- self ._print_any
303- )
333+ setattr (self , "print_{}" .format (file_extension ), self ._print_any )
334+
335+ def copy_from_bbox (self , bbox ):
336+ renderer = self .get_renderer ()
337+ return renderer .copy_from_bbox (bbox )
338+
339+ def restore_region (self , region , bbox = None , xy = None ):
340+ renderer = self .get_renderer ()
341+ return renderer .restore_region (region , bbox , xy )
304342
305343 def draw (self ):
306344 """
@@ -311,17 +349,44 @@ def draw(self):
311349 deferred work (like computing limits auto-limits and tick
312350 values) that users may want access to before saving to disk.
313351 """
314- renderer = RendererPygame (self .figure .dpi )
315- renderer .surface = self .figure
316- self .figure .draw (renderer )
352+ self .renderer = self .get_renderer (cleared = True )
353+ self .renderer .surface = self .figure
354+ self .figure .draw (self .renderer )
355+
356+ def blit (self , bbox = None ):
357+ self .renderer = self .get_renderer (cleared = False )
358+ self .renderer .surface = self .figure
359+ self .figure .draw (self .renderer )
360+
361+ def get_renderer (self , cleared = False ) -> RendererPygame :
362+ fig = self .figure
363+ reuse_renderer = (
364+ hasattr (self , "renderer" )
365+ and getattr (self , "_last_fig" , None ) == fig
366+ )
367+ if not reuse_renderer :
368+ self .renderer = RendererPygame (self .figure .dpi )
369+ self ._last_fig = fig
370+ elif cleared :
371+ self .renderer .clear ()
372+ return self .renderer
317373
318374 def _print_any (self , filename , * args , ** kwargs ):
319375 """Call the pygame image saving method for the correct extenstion."""
320376 self .draw ()
321377 pygame .image .save (self .figure , filename )
322378
323379 def get_default_filetype (self ):
324- return 'jpg'
380+ return "jpg"
381+
382+ def flush_events (self ):
383+ self .main_display .blit (self .figure , (0 , 0 ))
384+ pygame .display .update ()
385+ events = pygame .event .get ()
386+ for event in events :
387+ if event .type == pygame .QUIT :
388+ pygame .quit ()
389+ show_fig = False
325390
326391
327392class FigureManagerPygame (FigureManagerBase ):
@@ -331,20 +396,23 @@ class FigureManagerPygame(FigureManagerBase):
331396 For non-interactive backends, the base class is sufficient.
332397 """
333398
334- def show (self ):
399+ def show (self , block ):
335400 # do something to display the GUI
336401 pygame .init ()
337402 main_display = pygame .display .set_mode (
338403 self .canvas .figure .get_size (), # Size matches figure size
339404 )
340405
406+ self .canvas .main_display = main_display
407+
341408 FPS = 60
342409 FramePerSec = pygame .time .Clock ()
343410
344411 self .canvas .figure .canvas .draw ()
345412 main_display .blit (self .canvas .figure , (0 , 0 ))
413+ pygame .display .update ()
346414
347- show_fig = True
415+ show_fig = True if block is None else False
348416 while show_fig :
349417 events = pygame .event .get ()
350418 pygame .display .update ()
0 commit comments