Transparency issue with matplotlib and wxpython
I'm developping a GUI based on matplotlib and wxpython, for Windows. I have an issue that becomes visible when the "Windows XP" theme (green Start button, blue taskbar) is used. I'm quite sure the same problem will show up on Vista as well. I know an imperfect workaround, but I can't fix this issue properly.
My matplotlib plots are embedded in wxPanels, which are embedded in a wxNotebook (tabs, if you prefer). With some themes, the background colour of the tabs is actually a gradient, and wx tries to match it with a colour close to it. This is the imperfect workaround: the colour matches at the top of the window, but not at the bottom.
That is why I would like to actually use a matplotlib plot with a transparent background. When I set the facecolor of the figure to be transparent, it actually takes the "colour" of the desktop or whatever window is located behind, not the wxPanel/wxNotebook. If I resize the window, the transparent part accumulates drawn data. This last issue is not theme-specific anymore, I observe it on the "Windows Classic" one as well.
In the example code below, I would rather expect the transparent part of the figure to show the blue colour set on the panel embedding the plot.
import wx
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
import wxversion
import sys
class MyFrame ( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 300,650 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )
bSizer3 = wx.BoxSizer( wx.VERTICAL )
self.m_notebook2 = wx.Notebook( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0 )
self.m_panel1 = wx.Panel( self.m_notebook2, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
bSizer4 = wx.BoxSizer( wx.VERTICAL )
self.m_panel2 = wx.Panel( self.m_panel1, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
self.m_panel2.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_ACTIVECAPTION ) )
bSizer4.Add( self.m_panel2, 1, wx.EXPAND |wx.ALL, 5 )
self.m_panel1.SetSizer( bSizer4 )
self.m_panel1.Layout()
bSizer4.Fit( self.m_panel1 )
self.m_notebook2.AddPage( self.m_panel1, u"a page", False )
bSizer3.Add( self.m_notebook2, 1, wx.EXPAND |wx.ALL, 0 )
self.SetSizer( bSizer3 )
self.Layout()
self.Centre( wx.BOTH )
def __del__( self ):
pass
class MyPlot:
def __init__(self, panel, notebook):
self.panel = panel
self.panel.figure = Figure(None, dpi=None)
self.panel.canvas = FigureCanvasWxAgg(self.panel, -1, self.panel.figure)
self.panel.axes = self.panel.figure.add_subplot(111)
self.panel.Bind(wx.EVT_SIZE, self.onSize)
# I want this code to work whatever the theme. It works fine on WinXP "Windows Classic" theme, but not on WinXP "Windows XP" theme (with green Start menu button and blue taskbar)
self.setColor(None)
# The problem here is that SYS_COLOUR_BTNFACE does not match the notebook background for the Windows XP theme
# Solution 1: get background gradient from notebook
# Source: http://wxpython-users.1045709.n5.nabble.com/wxTextCtrl-doesn-t-show-background-as-expected-on-a-notebook-panel-td2359680.html
# Problem: match is perfect at top, not at bottom (not much difference, though)
#
# # # Uncomment below
# rgbtuple = notebook.GetThemeBackgroundColour()
# clr = [c / 255. for c in rgbtuple]
# self.panel.figure.set_facecolor(clr)
# self.panel.figure.set_edgecolor(clr)
# Solution 2: set transparent figure facecolor to capture color from panel
# Problem 1: it takes the color from behind the frame (ie desktop, any other window behind...), not the color of the panel
# Problem 2 (linked to problem 1): it gets worse when resizing as it doesn't repaint but accumulates
#
# http://matplotlib.1069221.n5.nabble.com/redrawing-plot-with-transparent-background-in-wx-tt26694.html seems related but did not receive any answer
# # # Recomment above, uncomment below
#self.panel.figure.set_facecolor('None')
self.setSize()
def setSize(self):
pixels = tuple(self.panel.GetClientSize())
self.panel.SetSize(pixels)
self.panel.canvas.SetSize(pixels)
self.panel.figure.set_size_inches(float(pixels[0]) / self.panel.figure.get_dpi(),
float(pixels[1]) / self.panel.figure.get_dpi())
def onSize(self, event):
self.setSize()
def setColor(self, rgbtuple=None):
if rgbtuple is None:
rgbtuple = wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE).Get()
clr = [c / 255. for c in rgbtuple]
self.panel.figure.set_facecolor(clr)
self.panel.figure.set_edgecolor(clr)
self.panel.canvas.SetBackgroundColour(wx.Colour(*rgbtuple))
print "python " + str(sys.version_info)
print "wx " + str(wxversion.getInstalled())
print "matplotlib " + matplotlib.__version__
app = wx.App(0)
window = MyFrame(None)
plot = MyPlot(window.m_panel2, window.m_notebook2)
window.Show()
app.MainLoop()
The blue colour is normally visible if you grab a corner of the window and shake it to resize it several times. I am immediately resizing on purpose rather than waiting for the window to be idle.
Please uncomment the indicated lines in the code to see what I am probably not clearly explaining.
My configuration: Python 2.7.3, wx 2.9.4-msw, matplotlib 1.1.1
Edit
I didn't make much progress:
I also developped another example where I overlay two figures, to see where the transparency "stops". Interestingly enough, the figure below stops the transparency of the figure on top, but not the wxPanel.
import wx
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.patches import Rectangle
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
import wxversion
import sys
class MyFrame ( wx.Frame ):
def __init__( self, parent ):
wx.Frame.__init__ ( self, parent, id = wx.ID_ANY, title = wx.EmptyString, pos = wx.DefaultPosition, size = wx.Size( 300,650 ), style = wx.DEFAULT_FRAME_STYLE|wx.TAB_TRAVERSAL )
self.SetSizeHintsSz( wx.DefaultSize, wx.DefaultSize )
bSizer4 = wx.BoxSizer( wx.VERTICAL )
self.m_panel2 = wx.Panel( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TAB_TRAVERSAL )
self.m_panel2.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_ACTIVECAPTION ) )
bSizer4.Add( self.m_panel2, 1, wx.EXPAND |wx.ALL, 5 )
self.m_panel2.Layout()
self.SetSizer( bSizer4 )
self.Layout()
self.Centre( wx.BOTH )
def __del__( self ):
pass
class MyPlot:
def __init__(self, panel):
self.panel = panel
# This figure is behind
self.panel.figure = Figure(figsize=(2,2), dpi=None)
self.panel.canvas = FigureCanvasWxAgg(self.panel, -1, self.panel.figure)
self.panel.axes = self.panel.figure.add_axes([0.3,0.3,0.5,0.5])
patch = Rectangle((0,0), 0.3, 0.2, facecolor='red')
self.panel.axes.add_patch(patch)
# This figure is on top
self.panel.figure2 = Figure(figsize=(3,3), dpi=None)
self.panel.canvas2 = FigureCanvasWxAgg(self.panel, -1, self.panel.figure2)
self.panel.axes2 = self.panel.figure2.add_axes([0.3,0.3,0.5,0.5])
patch2 = Rectangle((0.5,0.5), 0.4, 0.1, facecolor='blue')
self.panel.axes2.add_patch(patch2)
# Make the top figure transparent
# self.panel.figure2.set_facecolor('None') # equivalent to self.panel.figure2.patch.set_alpha(0)
# By default, leave figure on bottom opaque
# Uncomment to see effect of transparency
#self.panel.figure.patch.set_alpha(0.5)
self.panel.figure2.patch.set_alpha(0.5)
# Draw everything
self.panel.canvas.draw()
print "python " + str(sys.version_info)
print "wx " + str(wxversion.getInstalled())
print "matplotlib " + matplotlib.__version__
app = wx.App(0)
window = MyFrame(None)
plot = MyPlot(window.m_panel2)
window.Show()
app.MainLoop()
Thanks for your help :)
链接地址: http://www.djcxy.com/p/37900.html