简体   繁体   English

有没有办法使用 matplotlib 指定宽度和高度的 plot 文本?

[英]Is there a way to plot text with specified width and height using matplotlib?

I want to create a sequence logo using matplotlib for my work.我想为我的工作使用 matplotlib 创建一个序列徽标。

Sequence logo is like this as shown in https://en.wikipedia.org/wiki/Sequence_logo .序列标识如https ://en.wikipedia.org/wiki/Sequence_logo所示。 Each character has a specific height.每个字符都有特定的高度。 And I'd like to make this using matplotlib.我想用 matplotlib 来做这个。

How do I change the aspect ratio of a font?如何更改字体的纵横比? I've tried to use Affine2D from matplotlib.transform as following, but it didn't work.我尝试使用 matplotlib.transform 中的 Affine2D,如下所示,但它没有用。

ax.text(1,1,"A").set_transform(matplotlib.transforms.Affine2D().scale(0.5,1))

Is there an easy workaround?有简单的解决方法吗?

I've tried different ways of stretching text's width in Matplotlib but nothing worked out.我尝试了不同的方法来拉伸 Matplotlib 中的文本宽度,但没有任何结果。 Probably they didn't implement stretching correctly yet, I even saw in docs this notice like This feature is not implemented yet!可能他们还没有正确实现拉伸,我什至在文档中看到了这个通知,比如This feature is not implemented yet! for one of font-stretching function.对于字体拉伸 function 之一。

So I decided to write my own helper functions to achieve this task.所以我决定编写自己的辅助函数来完成这个任务。 It uses drawing functions of PIL module, you have to install one time next modules python -m pip install pillow numpy matplotlib .它使用 PIL 模块的绘图功能,您必须安装一次下一个模块python -m pip install pillow numpy matplotlib

Notice that my helper function text_draw_mpl(...) accepts x, y offset and width, height all expressed in your plot units, eg if you have x/y ranging from 0 to 1 on your plot then you have to use in function values like 0.1, 0.2, 0.3, 0.4. Notice that my helper function text_draw_mpl(...) accepts x, y offset and width, height all expressed in your plot units, eg if you have x/y ranging from 0 to 1 on your plot then you have to use in function values比如 0.1、0.2、0.3、0.4。

My other helper function text_draw_np(...) is low level function, probably you'll never use it straight away, it uses width, height expressed in pixels and produces on output RGB array of shape (height, width, 3) (3 for RGB colors). My other helper function text_draw_np(...) is low level function, probably you'll never use it straight away, it uses width, height expressed in pixels and produces on output RGB array of shape (height, width, 3) (3对于 RGB 颜色)。

In my functions you may pass background color ( bg argument) and foreground color ( color argument) both as string color name (like 'magenta' or 'blue' ) and also as RGB tuple like (0, 255, 0) for green color.在我的函数中,您可以将背景色( bg参数)和前景色( color参数)作为字符串颜色名称(如'magenta''blue' )和 RGB 元组(如(0, 255, 0)用于绿色. By default if not provided foreground color is black and background color is white.默认情况下,如果未提供前景色为黑色,背景色为白色。

Notice that my function supports argument remove_gaps , if it is True that empty space will be removed from all sides of drawn text picture, if it is False then empty space remains.请注意,我的 function 支持参数remove_gaps ,如果为True将从绘制的文本图片的所有侧面删除空白空间,如果为False则保留空白空间。 Empty space is introduced by the way Glyphs are drawn inside Font file, eg small letter m has more space at the top, capital letter T less space at the top.空白空间是通过在字体文件中绘制字形的方式引入的,例如,小写字母m在顶部有更多空间,大写字母T在顶部空间更少。 Font has this space so that whole text has same height and also that two lines of text over each other have some gap between and don't merge.字体有这个空间,因此整个文本具有相同的高度,并且两行文本彼此之间有一些间隙并且不会合并。

Also notice that I provided path to default Windows Arial font c:/windows/fonts/arial.ttf , if you have Linux, or you want other font, just download any free Unicode TrueType (.ttf) font from internet (eg from here ) and put that font nearby to your script and modify path in my code below. Also notice that I provided path to default Windows Arial font c:/windows/fonts/arial.ttf , if you have Linux, or you want other font, just download any free Unicode TrueType (.ttf) font from internet (eg from here ) 并将该字体放在您的脚本附近,并在下面的代码中修改路径。 Also PIL module supports other formats, as stated in its docs Supported: TrueType and OpenType fonts (as well as other font formats supported by the FreeType library) . PIL 模块还支持其他格式,如其文档Supported: TrueType and OpenType fonts (as well as other font formats supported by the FreeType library)中所述。

Try it online!在线尝试!

def text_draw_np(text, width, height, *, font = 'c:/windows/fonts/arial.ttf', bg = (255, 255, 255), color = (0, 0, 0), remove_gaps = False, cache = {}):
    import math, numpy as np, PIL.Image, PIL.ImageDraw, PIL.ImageFont, PIL.ImageColor
    def get_font(fname, size):
        key = ('font', fname, size)
        if key not in cache:
            cache[key] = PIL.ImageFont.truetype(fname, size = size, encoding = 'unic')
        return cache[key]
    width, height = math.ceil(width), math.ceil(height)
    pil_font = get_font(font, 24)
    text_width, text_height = pil_font.getsize(text)
    pil_font = get_font(font, math.ceil(1.2 * 24 * max(width / text_width, height / text_height)))
    text_width, text_height = pil_font.getsize(text)
    canvas = PIL.Image.new('RGB', (text_width, text_height), bg)
    draw = PIL.ImageDraw.Draw(canvas)
    draw.text((0, 0), text, font = pil_font, fill = color)
    if remove_gaps:
        a = np.asarray(canvas)
        bg_rgb = PIL.ImageColor.getrgb(bg)
        b = np.zeros_like(a)
        b[:, :, 0] = bg_rgb[0]; b[:, :, 1] = bg_rgb[1]; b[:, :, 2] = bg_rgb[2]
        t0 = np.any((a != b).reshape(a.shape[0], -1), axis = -1)
        top, bot = np.flatnonzero(t0)[0], np.flatnonzero(t0)[-1]
        t0 = np.any((a != b).transpose(1, 0, 2).reshape(a.shape[1], -1), axis = -1)
        lef, rig = np.flatnonzero(t0)[0], np.flatnonzero(t0)[-1]
        a = a[top : bot, lef : rig]
        canvas = PIL.Image.fromarray(a)
    canvas = canvas.resize((width, height), PIL.Image.LANCZOS)
    return np.asarray(canvas)
    
def text_draw_mpl(fig, ax, text, offset_x, offset_y, width, height, **nargs):
    axbb = ax.get_window_extent().transformed(fig.dpi_scale_trans.inverted())
    pxw, pxh = axbb.width * fig.dpi * width / (ax.get_xlim()[1] - ax.get_xlim()[0]), axbb.height * fig.dpi * height / (ax.get_ylim()[1] - ax.get_ylim()[0])
    ax.imshow(text_draw_np(text, pxw * 1.2, pxh * 1.2, **nargs), extent = (offset_x, offset_x + width, offset_y, offset_y + height), aspect = 'auto', interpolation = 'lanczos')

import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.set_ylim(0, 1000)
ax.set_xlim(0, 1000)
text_draw_mpl(fig, ax, 'Hello!', 100, 500, 150, 500, color = 'green', bg = 'magenta', remove_gaps = True)
text_draw_mpl(fig, ax, 'World!', 100, 200, 800, 100, color = 'blue', bg = 'yellow', remove_gaps = True)
text_draw_mpl(fig, ax, ' Gaps ', 400, 500, 500, 200, color = 'red', bg = 'gray', remove_gaps = False)
plt.show()

Output: Output:

在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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