Home > Blockchain >  How to make a customized Text object interactive in matplotlib
How to make a customized Text object interactive in matplotlib

Time:04-08

I have a customized Text object, which has the same logic as the following simplified object:

import matplotlib.pyplot as plt
from matplotlib.text import Text

class MyText(Text):
    def __init__(self, x, y, txt, height, **kwargs):
        super().__init__(x, y, txt, **kwargs)
        self.height = height
        
    def mydraw(self, ax):
        txt = ax.add_artist(self)
        myset_fontsize(txt, self.height)
        return self
    
    def set_height(self, height):
        self.height = height
        #myset_fontsize(self, height)


def myset_fontsize(txtobj, height):
    trans = txtobj.get_transform()
    pixels, _ = trans.transform((txtobj.height, 0)) - trans.transform((0,0))
    dpi = txtobj.axes.get_figure().get_dpi()
    points = pixels / dpi * 72
    txtobj.set_fontsize(points)
           
if __name__ == '__main__':
    fig, ax = plt.subplots()
    ax.grid(True)
    txt = MyText(0.2, 0.2, 'hello', 0.1)
    txt.mydraw(ax)

MyText is different from the built-in Text in that the fontsize is dependent on the height, which, for example, specifies the height of the text in the data coordinates. Except this, MyText is almost the same as Text. The example code gives the following figure:

enter image description here

This works fine for a static image. However, I want MyTest to be interactive, which includes the following goals:

  • In an interactive plot mode, txt.set_height(0.5) shoule change the fontsize dynamically. I know I can add a snippet as the comment shows, but if MyText object is not added to the axes, txt.set_height(0.5) will throw an AttributeError. In short, txt.set_height() should behave similarly to txt.set_fontsize().

  • When the figure is resized by dragging the plot window, MyText should change the fontsize accordingly, that is, the height of text in the data coordinates should keep the same. But currently the fontsize is unchanged when resizing the figure. I have found this enter image description here

    • When I change the aspect ratio of the figure, MyText should change the fontsize accordingly, same as the second point.

    Thanks for any ideas!

    CodePudding user response:

    After reading the user guide, I found that every artist has a stale attribite, which is some signal to re-render the figure. The complete solution is as follows:

    import matplotlib.pyplot as plt
    from matplotlib.text import Text
    
    class MyText(Text):
        def __init__(self, x, y, txt, height, **kwargs):
            super().__init__(x, y, txt, **kwargs)
            self.height = height
    
        def __call__(self, event):
            # When calling myset_fontsize, `self.stale` will be `True` due to `self.set_fontsize()` in the function body.
            myset_fontsize(self, self.height)
            
        def mydraw(self, ax):
            txt = ax.add_artist(self)
    
            # Connect "draw_event" so that once a draw event happens, a new fontsize is calculated and mark the `Text` object is stale.
            ax.get_figure().canvas.mpl_connect('draw_event', self)
    
            return txt
        
        def set_height(self, height):
            self.height = height
            # When a new height is set, then the
            #`Text` object is stale, which will 
            # forward the signal of re-rendering 
            # the figure to its parent.
            self.stale = True
    
    
    def myset_fontsize(txtobj, height):
        trans = txtobj.get_transform()
        pixels, _ = trans.transform((txtobj.height, 0)) - trans.transform((0,0))
        dpi = txtobj.axes.get_figure().get_dpi()
        points = pixels / dpi * 72
        txtobj.set_fontsize(points)
    

    This solution almost solves my problem although it's not perfect. It's a little inefficient.

    Any improvements are appreciated.

    CodePudding user response:

    If you only need to change the font from the window size. Installed an event handler triggered by resizing the window. Fonts size-bound to one side of the window size(in this case to the width).

    import matplotlib.pyplot as plt
    from matplotlib.text import Text
    
    
    class MyText(Text):
        def __init__(self, x, y, txt, height, **kwargs):
            super().__init__(x, y, txt, **kwargs)
            self.height = height
            self.event_text = fig.canvas.mpl_connect('resize_event', self.mysize)
    
        def mysize(event, ax):
            fig = plt.gcf()
            size_ = fig.get_size_inches()
            txt.set_fontsize(size_[0]*5)
    
        def mydraw(self, ax):
            txt = ax.add_artist(self)
            myset_fontsize(txt, self.height)
            return self
    
        def set_height(self, height):
            self.height = height
            # myset_fontsize(self, height)
    
    
    def myset_fontsize(txtobj, height):
        trans = txtobj.get_transform()
        pixels, _ = trans.transform((txtobj.height, 0)) - trans.transform((0, 0))
        dpi = txtobj.axes.get_figure().get_dpi()
        points = pixels / dpi * 72
        txtobj.set_fontsize(points)
    
    
    if __name__ == '__main__':
        fig, ax = plt.subplots()
        ax.grid(True)
        txt = MyText(0.2, 0.2, 'hello', 0.1)
        txt.mydraw(ax)
        plt.show()
    
  • Related