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 noticed matplotlib 1.2.0 was out so tried it, but no change
  • I simplified the example to get rid of the wxNotebook, the problem is still there with only a wxPanel
  • I found http://wxpython-users.1045709.n5.nabble.com/advice-on-filling-squares-tp4475117p4479387.html which might give a hint at what is going on
  • 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

    上一篇: 蟒蛇matplotlib blit轴或图的两侧?

    下一篇: matplotlib和wxpython的透明度问题