matplotlib中的弯曲文本渲染 [英] Curved text rendering in matplotlib

查看:103
本文介绍了matplotlib中的弯曲文本渲染的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我正在做的项目中,我必须从结构化文件(xml)中获取用户输入.该文件包含一个区域的道路数据,我必须将其绘制到matplotlib画布上.问题在于,沿着道路,我还必须渲染道路名称,并且大多数道路都是弯曲的.我知道如何倾斜显示文本.但是我想知道是否可以在字符串中间更改文本角度?

In a project I'm doing, I have to take in a user input from a structured file (xml). The file contains road data of an area, which I have to plot on to the matplotlib canvas. The problem is that along with the road, I also have to render the road name, and most of the roads are curved. I know how to render text in an angle. But I was wondering whether it is possible to change the text angle midway through the string?

类似这样的内容:在弯曲路径上绘制旋转的文本

但是使用matplotlib.

But using matplotlib.

推荐答案

这是我对这个问题的看法: 为了使文本在绘制后能够灵活地进行图形调整,我从matplotlib.text派生了子类. CurvedText对象采用x-和y-值数组形式的字符串和曲线.要显示的文本本身被切成单独的字符,每个字符都被添加到绘图中的适当位置.由于如果字符串为空,matplotlib.text不会绘制任何内容,所以我用不可见的'a'替换所有空格.调整figure时,重载的draw()会调用update_positions()函数,该函数要确保字符位置和方向保持正确.为了确保调用顺序(每个字符的draw()函数也将被调用),CurvedText对象还应注意每个字符的zorder高于其自己的zorder.按照我的示例此处,文本可以具有任何对齐方式.如果文本无法以当前分辨率适合曲线,则其余部分将被隐藏,但在调整大小时将显示.下面是带有示例应用程序的代码.

Here is my take on the problem: In order to make the text robust to figure adjustments after drawing, I derive a child class, CurvedText, from matplotlib.text. The CurvedText object takes a string and a curve in the form of x- and y-value arrays. The text to be displayed itself is cut into separate characters, which each are added to the plot at the appropriate position. As matplotlib.text draws nothing if the string is empty, I replace all spaces by invisible 'a's. Upon figure adjustment, the overloaded draw() calls the update_positions() function, which takes care that the character positions and orientations stay correct. To assure the calling order (each character's draw() function will be called as well) the CurvedText object also takes care that the zorder of each character is higher than its own zorder. Following my example here, the text can have any alignment. If the text cannot be fit to the curve at the current resolution, the rest will be hidden, but will appear upon resizing. Below is the code with an example of application.

from matplotlib import pyplot as plt
from matplotlib import patches
from matplotlib import text as mtext
import numpy as np
import math

class CurvedText(mtext.Text):
    """
    A text object that follows an arbitrary curve.
    """
    def __init__(self, x, y, text, axes, **kwargs):
        super(CurvedText, self).__init__(x[0],y[0],' ', **kwargs)

        axes.add_artist(self)

        ##saving the curve:
        self.__x = x
        self.__y = y
        self.__zorder = self.get_zorder()

        ##creating the text objects
        self.__Characters = []
        for c in text:
            if c == ' ':
                ##make this an invisible 'a':
                t = mtext.Text(0,0,'a')
                t.set_alpha(0.0)
            else:
                t = mtext.Text(0,0,c, **kwargs)

            #resetting unnecessary arguments
            t.set_ha('center')
            t.set_rotation(0)
            t.set_zorder(self.__zorder +1)

            self.__Characters.append((c,t))
            axes.add_artist(t)


    ##overloading some member functions, to assure correct functionality
    ##on update
    def set_zorder(self, zorder):
        super(CurvedText, self).set_zorder(zorder)
        self.__zorder = self.get_zorder()
        for c,t in self.__Characters:
            t.set_zorder(self.__zorder+1)

    def draw(self, renderer, *args, **kwargs):
        """
        Overload of the Text.draw() function. Do not do
        do any drawing, but update the positions and rotation
        angles of self.__Characters.
        """
        self.update_positions(renderer)

    def update_positions(self,renderer):
        """
        Update positions and rotations of the individual text elements.
        """

        #preparations

        ##determining the aspect ratio:
        ##from https://stackoverflow.com/a/42014041/2454357

        ##data limits
        xlim = self.axes.get_xlim()
        ylim = self.axes.get_ylim()
        ## Axis size on figure
        figW, figH = self.axes.get_figure().get_size_inches()
        ## Ratio of display units
        _, _, w, h = self.axes.get_position().bounds
        ##final aspect ratio
        aspect = ((figW * w)/(figH * h))*(ylim[1]-ylim[0])/(xlim[1]-xlim[0])

        #points of the curve in figure coordinates:
        x_fig,y_fig = (
            np.array(l) for l in zip(*self.axes.transData.transform([
            (i,j) for i,j in zip(self.__x,self.__y)
            ]))
        )

        #point distances in figure coordinates
        x_fig_dist = (x_fig[1:]-x_fig[:-1])
        y_fig_dist = (y_fig[1:]-y_fig[:-1])
        r_fig_dist = np.sqrt(x_fig_dist**2+y_fig_dist**2)

        #arc length in figure coordinates
        l_fig = np.insert(np.cumsum(r_fig_dist),0,0)

        #angles in figure coordinates
        rads = np.arctan2((y_fig[1:] - y_fig[:-1]),(x_fig[1:] - x_fig[:-1]))
        degs = np.rad2deg(rads)


        rel_pos = 10
        for c,t in self.__Characters:
            #finding the width of c:
            t.set_rotation(0)
            t.set_va('center')
            bbox1  = t.get_window_extent(renderer=renderer)
            w = bbox1.width
            h = bbox1.height

            #ignore all letters that don't fit:
            if rel_pos+w/2 > l_fig[-1]:
                t.set_alpha(0.0)
                rel_pos += w
                continue

            elif c != ' ':
                t.set_alpha(1.0)

            #finding the two data points between which the horizontal
            #center point of the character will be situated
            #left and right indices:
            il = np.where(rel_pos+w/2 >= l_fig)[0][-1]
            ir = np.where(rel_pos+w/2 <= l_fig)[0][0]

            #if we exactly hit a data point:
            if ir == il:
                ir += 1

            #how much of the letter width was needed to find il:
            used = l_fig[il]-rel_pos
            rel_pos = l_fig[il]

            #relative distance between il and ir where the center
            #of the character will be
            fraction = (w/2-used)/r_fig_dist[il]

            ##setting the character position in data coordinates:
            ##interpolate between the two points:
            x = self.__x[il]+fraction*(self.__x[ir]-self.__x[il])
            y = self.__y[il]+fraction*(self.__y[ir]-self.__y[il])

            #getting the offset when setting correct vertical alignment
            #in data coordinates
            t.set_va(self.get_va())
            bbox2  = t.get_window_extent(renderer=renderer)

            bbox1d = self.axes.transData.inverted().transform(bbox1)
            bbox2d = self.axes.transData.inverted().transform(bbox2)
            dr = np.array(bbox2d[0]-bbox1d[0])

            #the rotation/stretch matrix
            rad = rads[il]
            rot_mat = np.array([
                [math.cos(rad), math.sin(rad)*aspect],
                [-math.sin(rad)/aspect, math.cos(rad)]
            ])

            ##computing the offset vector of the rotated character
            drp = np.dot(dr,rot_mat)

            #setting final position and rotation:
            t.set_position(np.array([x,y])+drp)
            t.set_rotation(degs[il])

            t.set_va('center')
            t.set_ha('center')

            #updating rel_pos to right edge of character
            rel_pos += w-used




if __name__ == '__main__':
    Figure, Axes = plt.subplots(2,2, figsize=(7,7), dpi=100)


    N = 100

    curves = [
        [
            np.linspace(0,1,N),
            np.linspace(0,1,N),
        ],
        [
            np.linspace(0,2*np.pi,N),
            np.sin(np.linspace(0,2*np.pi,N)),
        ],
        [
            -np.cos(np.linspace(0,2*np.pi,N)),
            np.sin(np.linspace(0,2*np.pi,N)),
        ],
        [
            np.cos(np.linspace(0,2*np.pi,N)),
            np.sin(np.linspace(0,2*np.pi,N)),
        ],
    ]

    texts = [
        'straight lines work the same as rotated text',
        'wavy curves work well on the convex side',
        'you even can annotate parametric curves',
        'changing the plotting direction also changes text orientation',
    ]

    for ax, curve, text in zip(Axes.reshape(-1), curves, texts):
        #plotting the curve
        ax.plot(*curve, color='b')

        #adjusting plot limits
        stretch = 0.2
        xlim = ax.get_xlim()
        w = xlim[1] - xlim[0]
        ax.set_xlim([xlim[0]-stretch*w, xlim[1]+stretch*w])
        ylim = ax.get_ylim()
        h = ylim[1] - ylim[0]
        ax.set_ylim([ylim[0]-stretch*h, ylim[1]+stretch*h])

        #adding the text
        text = CurvedText(
            x = curve[0],
            y = curve[1],
            text=text,#'this this is a very, very long text',
            va = 'bottom',
            axes = ax, ##calls ax.add_artist in __init__
        )

    plt.show()

结果如下:

当文本遵循急剧弯曲的曲线的凹面时,仍然存在一些问题.这是因为在不考虑重叠的情况下,字符沿着曲线缝合在一起".如果有时间,我会尝试改善这一点.任何意见都非常欢迎.

There are still some problems, when the text follows the concave side of a sharply bending curve. This is because the characters are 'stitched together' along the curve without accounting for overlap. If I have time, I'll try to improve on that. Any comments are very welcome.

python 3.5和2.7

这篇关于matplotlib中的弯曲文本渲染的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆