I got a bit frustrated by trying to match the background color of the axes/figure with the background color of the tkinter app on each platform so I did a little digging:
In general, two things need to be done:
Tell matplotlib to use a transparent background for the figure and axes object by setting the facecolor to "none":
f.set_facecolor("none")
a.set_facecolor("none")
Tell the backend renderer to also make the background transparent, e.g. for saving it as png:
f.savefig('mygraph.png', transparent=True)
The problem is: How do I tell this to FigureCanvasTkAgg
? Looking at the sourcecode of FigureCanvasTk, that does the drawing, one can see that the tk.Canvas
always gets created with a white background. AFAIK a Canvas can't be transparent (please correct me), but when created without a specific background color, it will have the same background color as the Frame surrounding it, given by the ttk.Style
of it. So there are two workarounds I can think of:
After creating the FigureCanvasTkAgg, set the background color of the canvas to the same as the current style:
s = ttk.Style()
bg = s.lookup("TFrame", "background")
bg_16bit = self.winfo_rgb(bg)
bg_string = "#" + "".join([hex(bg_color >> 8)[2:] for bg_color in bg_16bit])
canvas.get_tk_widget().config(bg=bg_string)
Lines 3 & 4 are necessary on e.g. Windows, since s.lookup("TFrame", "background")
can return a system color name here, that then needs to be converted to a standard #aabbcc
hex color
Making your own MyFigureCanvasTk
that's just a copy of the code in matplotlib, that skips setting the background color of the Canvas
:
class MyFigureCanvasTk(FigureCanvasTk):
# Redefine __init__ to get rid of the background="white" in the tk.Canvas
def __init__(self, figure=None, master=None):
super().__init__(figure)
self._idle_draw_id = None
self._event_loop_id = None
w, h = self.get_width_height(physical=True)
self._tkcanvas = tk.Canvas(
master=master,
#background="white"
width=w,
height=h,
borderwidth=0,
highlightthickness=0,
)
self._tkphoto = tk.PhotoImage(master=self._tkcanvas, width=w, height=h)
self._tkcanvas.create_image(w // 2, h // 2, image=self._tkphoto)
self._tkcanvas.bind("<Configure>", self.resize)
if sys.platform == "win32":
self._tkcanvas.bind("<Map>", self._update_device_pixel_ratio)
self._tkcanvas.bind("<Key>", self.key_press)
self._tkcanvas.bind("<Motion>", self.motion_notify_event)
self._tkcanvas.bind("<Enter>", self.enter_notify_event)
self._tkcanvas.bind("<Leave>", self.leave_notify_event)
self._tkcanvas.bind("<KeyRelease>", self.key_release)
for name in ["<Button-1>", "<Button-2>", "<Button-3>"]:
self._tkcanvas.bind(name, self.button_press_event)
for name in ["<Double-Button-1>", "<Double-Button-2>", "<Double-Button-3>"]:
self._tkcanvas.bind(name, self.button_dblclick_event)
for name in ["<ButtonRelease-1>", "<ButtonRelease-2>", "<ButtonRelease-3>"]:
self._tkcanvas.bind(name, self.button_release_event)
# Mouse wheel on Linux generates button 4/5 events
for name in "<Button-4>", "<Button-5>":
self._tkcanvas.bind(name, self.scroll_event)
# Mouse wheel for windows goes to the window with the focus.
# Since the canvas won't usually have the focus, bind the
# event to the window containing the canvas instead.
# See https://wiki.tcl-lang.org/3893 (mousewheel) for details
root = self._tkcanvas.winfo_toplevel()
root.bind("<MouseWheel>", self.scroll_event_windows, "+")
class MyFigureCanvasTkAgg(FigureCanvasAgg, MyFigureCanvasTk):
def draw(self):
super().draw()
self.blit()
def blit(self, bbox=None):
_backend_tk.blit(
self._tkphoto, self.renderer.buffer_rgba(), (0, 1, 2, 3), bbox=bbox
)
This let's the graph blend in with whatever background color might be surrounding it. It should work on all platforms (tested only on Windows and Linux, with python 3.11, tkinter 8.6.12). Maybe this helps someone stumbling over this question.