2013-10-14 6 views
8

私がやっているプロジェクトでは、構造化ファイル(xml)からユーザー入力を受けなければなりません。このファイルには、私がmatplotlibキャンバスにプロットしなければならないエリアの道路データが含まれています。問題は、道路に沿って、道路名をレンダリングしなければならず、道路の大部分が湾曲していることです。テキストをある角度でレンダーする方法は分かっています。しかし、文字列の途中で文字の角度を変更することが可能かどうか疑問に思っていましたか?このようなmatplotlibの曲線テキストレンダリング

何か:Draw rotated text on curved path

しかし、matplotlibのを使用。

+0

これを解決しましたか?私はそれが必要です。ありがとう。 –

+0

@ tommy.carstensen - 私はこれを解決することができませんでした:( – deepak

+0

@tommy。carstensen - 誰かが答えを見つけたように見えます:) (私はもはやそれは必要ありません) – deepak

答えて

8

問題が私の感想です: 描画した後の調整を理解するテキストを強固にするために、私はmatplotlib.textから、子クラス、CurvedTextを導き出します。 CurvedTextオブジェクトは、x - およびy - 値の配列の文字列と曲線を取ります。表示されるテキスト自体は、それぞれが適切な位置にプロットに追加された文字に分割されます。文字列が空であればmatplotlib.textは何も描画しないので、すべてのスペースを不可視のaで置き換えます。 figureを調整すると、オーバーロードされたdraw()update_positions()関数を呼び出します。これは、文字の位置と向きが正しく維持されるように注意します。呼び出し順序(各文字のdraw()関数も同様に呼び出される)を保証するために、CurvedTextオブジェクトは、各文字のzorderがそれ自身のzorderよりも高いことに注意します。私の例であるhereに続いて、テキストは任意の位置合わせを持つことができます。テキストを現在の解像度のカーブに収めることができない場合、残りは隠されますが、サイズ変更時に表示されます。以下は、アプリケーションの例を示すコードです。

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],' ', axes, **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() 

結果は以下のようになります。テキストは鋭く曲げ曲線の凹面側を以下のとき

curved text in matplotlib

は、いくつかの問題は、まだあります。これは、文字が重なりを考慮せずにカーブに沿って「つなぎ合わせ」されているためです。時間があれば、それを改善しようとします。コメントは大歓迎です。

テスト済みpython 3.5と2.7

+0

ねえ、私はもう答えが必要ない間、本当にあなたの答えをありがとう! 4年前に私が探していたものでした! 誰かがそれを見つけることを願っています:) – deepak

+0

@ThomasKühn:派生クラスをうまく使いました。私はPython 2.7と完全な互換性を持つようにいくつかの編集を提案しました。それらは編集キューに表示される必要があります。 – Daan

+0

@ダーン編集をありがとう。 –

4

私はあなたの問題は非常に興味深い見つけたので、私はmatplotlibのテキストツール使用して、かなり近づいて何かを作っ:実装がはるかに完璧からですが、それは良い出発です

from __future__ import division 
import itertools 
import matplotlib.pyplot as plt 
import numpy as np 
%matplotlib inline 

# define figure and axes properties 
fig, ax = plt.subplots(figsize=(8,6)) 
ax.set_xlim(left=0, right=10) 
ax.set_ylim(bottom=-1.5, top=1.5) 
(xmin, xmax), (ymin, ymax) = ax.get_xlim(), ax.get_ylim() 

# calculate a shape factor, more explanation on usage further 
# it is a representation of the distortion of the actual image compared to a 
# cartesian space: 
fshape = abs(fig.get_figwidth()*(xmax - xmin)/(ymax - ymin)/fig.get_figheight()) 

# the text you want to plot along your line 
thetext = 'the text is flowing  ' 

# generate a cycler, so that the string is cycled through 
lettercycler = itertools.cycle(tuple(thetext)) 

# generate dummy river coordinates 
xvals = np.linspace(1, 10, 300) 
yvals = np.sin(xvals)**3 

# every XX datapoints, a character is printed 
markerevery = 10 

# calculate the rotation angle for the labels (in degrees) 
# the angle is calculated as the slope between two datapoints. 
# it is then multiplied by a shape factor to get from the angles in a 
# cartesian space to the angles in this figure 
# first calculate the slope between two consecutive points, multiply with the 
# shape factor, get the angle in radians with the arctangens functions, and 
# convert to degrees 
angles = np.rad2deg(np.arctan((yvals[1:]-yvals[:-1])/(xvals[1:]-xvals[:-1])*fshape)) 

# plot the 'river' 
ax.plot(xvals, yvals, 'b', linewidth=3) 

# loop over the data points, but only plot a character every XX steps 
for counter in np.arange(0, len(xvals)-1, step=markerevery): 
    # plot the character in between two datapoints 
    xcoord = (xvals[counter] + xvals[counter+1])/2. 
    ycoord = (yvals[counter] + yvals[counter+1])/2. 

    # plot using the text method, set the rotation so it follows the line, 
    # aling in the center for a nicer look, optionally, a box can be drawn 
    # around the letter 
    ax.text(xcoord, ycoord, lettercycler.next(), 
      fontsize=25, rotation=angles[counter], 
      horizontalalignment='center', verticalalignment='center', 
      bbox=dict(facecolor='white', edgecolor='white', alpha=0.5)) 

example output

を私の意見ではポイント。

さらに、マーカーの回転を伴う散布図を有することで、この場合には理想的なmatplotlibの開発があるようです。しかし、私のプログラミング能力は、この問題に取り組むために必要なハードコアほどではないので、私はここでは手伝いできません。ここで

matplotlib on github: pull request

matplotlib on github: issue