简体   繁体   中英

How to make a customized Text object interactive in matplotlib

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:

在此处输入图像描述

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 answer , but mpl_connect needs some way to get the Figure object, and I want MyText interactive after calling txt.mydraw(ax) .

在此处输入图像描述

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

Thanks for any ideas!

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()

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM