LoginSignup
4
6

More than 5 years have passed since last update.

Python matplotlibでフローチャートのようなものを強引に描く(2)

Last updated at Posted at 2018-01-31

はじめに

仕事でフローチャートのようなものを作る必要が生じた.
普段は英語版でいいのだが,今回は,英語版・日本語版双方を作る必要が生じたので,「matplotlibでフローチャートのようなものを描く」プログラムの見直しを行った.それほど頻繁に使うものでもないので,あとで使うときに,何をやっているか,すぐに思い出せるよう配慮した(つもり).

前の投稿はこれ.
Python, matplotlibでフローチャートのようなものを強引に描く

今回のプログラムの特徴は(といっても大したことはないが...)

  • 基本はグラフ用紙の上にボックス・テキスト・矢印を,目で見て修正しながら仕上げる(前と同じ)
  • 日本語も含め,フォントを選択できるようにした(変数:fp).
  • 作図領域の横幅は固定であるが,縦は縦横比(変数:Raspect)で調整するようにした.
  • チャート作成時は,グリッド・座標軸を表示し,完成したと思ったらグリッド・座標軸を消して画像をファイル出力する(前と同じ:変数:iflag).iflag==0 なら,plt.axis('off') で軸を非表示にして,グリッドも描かない.

作業環境は以下の通り.

  • MacBook Pro (Retina, 13-inch, Mid 2014)
  • macOS High Sierra
  • Python 3.6.4

ここで紹介する事例は以下の3点である.

  • グラフ用紙(チャートを書くためのグラフ用紙:内容は含まず)
  • 日本語を用いた簡単なチャート
  • 少し複雑な英語版チャート

グラフ用紙

このプログラムは,座標軸とグリッドを表示するだけであり,中身は含まれていない.
すなわち,座標軸とグリッド以外表示しない.
しかし,これをベースに中身を書き加えて行けるよう,フォント指定などの部分は含んでいる.

注意事項は以下の通り.

  • フォント指定のため,from matplotlib.font_manager import FontProperties をインポートしておく.
  • フォントはフルパスで指定する.フォントのフルパスは,ImageMagick がインストールされていれば convert -list font で確認するのが便利.
  • 座標軸・グリッドを表示したければ,iflag=1,非表示にしたければ iflag=0 とする.
  • 描画する座標の範囲は,xmin, xmax, ymin, ymax で指定する
  • y軸は,ax1.set_ylim([ymax,ymin]) により,下方向に数値が増加するよう指定.
  • 描画範囲の縦横比(アスペクトレシオ)は,Raspect で指定.ここでは Raspect=1 としている.
# Flowchart
from math import *                                   # 数学関数使用
import matplotlib.pyplot as plt                      # 基本 pyplot
from matplotlib.patches import Polygon,Rectangle     # Polygon, Rectangle使用
from matplotlib.ticker import *                      # MultipleLocator使用
from matplotlib.font_manager import FontProperties   # フォント指定のため

# フォントをフルパスで指定
#fon='/Library/Fonts/Arial.ttf'
#fon='/System/Library/Fonts/Helvetica.ttc'
#fon='/Library/Fonts/Tahoma.ttf'
fon='/Library/Fonts/Times New Roman.ttf'
#fon='/Users/kk/Library/Fonts/ipaexg.ttf'
fp=FontProperties(fname=fon) # 使用フォント指定


# iflag=0: without axis and grid, iflag=1: with axis and grid
iflag=1 # グリッド・座標軸表示有無の指定

# definition if the range of axis
xmin=-8
xmax=8
ymin=0
ymax=5
Raspect=1 # 縦横比指定
iw=abs(xmax-xmin)/2
ih=abs(ymin-ymax)/abs(xmax-xmin)*iw*Raspect

fig = plt.figure(figsize=(iw,ih),facecolor='w')
ax1 = fig.add_subplot(1,1,1, adjustable='box', aspect=Raspect)
ax1.set_xlim([xmin,xmax])
ax1.set_ylim([ymax,ymin])
if iflag==0: # グリッド・座標軸非表示
    plt.axis('off')
else:
    ax1.tick_params(labelsize=10)
    ax1.xaxis.set_major_locator(MultipleLocator(1))
    ax1.yaxis.set_major_locator(MultipleLocator(1))
    plt.grid(which='major',lw=0.3, color='#777777',linestyle='-')

fsz=20 # font size
plt.rcParams["font.size"] = fsz
plt.rcParams['font.family'] ='sans-serif'
#################################################
#     (something to draw)                       #
#################################################

#================================================
if iflag==0:
    fnameF='fig_gra1.png'
    plt.savefig(fnameF, dpi=200, bbox_inches="tight", pad_inches=0.1)
plt.show()

作例

日本語を用いた簡単なチャート

日本語を用いた簡単なチャートの事例を示す.

やっていることは以下の通り.

  • フォントは ipaexg.ttf (fon='/Users/kk/Library/Fonts/ipaexg.ttf'
  • グリッド・座標軸を表示(iflag=1
  • 描画範囲は,x 方向は -8〜+8,y 方向は 0〜25 まで.
  • 縦横比は Raspect=0.8 に指定.
  • フォントサイズは fsz=16 に指定.
  • リスト ss[] に描画したい文字列を格納
  • Rectangle で描画したボックスの中に文字列を描画していく.
  • 矢印は,少しカッコつけて自前で Polygon で定義
# Flowchart
from math import *
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon,Rectangle
from matplotlib.ticker import *
from matplotlib.font_manager import FontProperties

#fon='/Library/Fonts/Arial.ttf'
#fon='/System/Library/Fonts/Helvetica.ttc'
#fon='/Library/Fonts/Tahoma.ttf'
#fon='/Library/Fonts/Times New Roman.ttf'
fon='/Users/kk/Library/Fonts/ipaexg.ttf'
fp=FontProperties(fname=fon)

# iflag=0: without axis and grid, iflag=1: with axis and grid
iflag=1

# definition if the range of axis
xmin=-8
xmax=8
ymin=0
ymax=25
Raspect=0.8
iw=abs(xmax-xmin)/2
ih=abs(ymin-ymax)/abs(xmax-xmin)*iw*Raspect

fig = plt.figure(figsize=(iw,ih),facecolor='w')
ax1 = fig.add_subplot(1,1,1, adjustable='box', aspect=Raspect)
ax1.set_xlim([xmin,xmax])
ax1.set_ylim([ymax,ymin])
if iflag==0: 
    plt.axis('off')
else:
    ax1.tick_params(labelsize=10)
    ax1.xaxis.set_major_locator(MultipleLocator(1))
    ax1.yaxis.set_major_locator(MultipleLocator(1))
    plt.grid(which='major',lw=0.3, color='#777777',linestyle='-')

fsz=16 # font size
plt.rcParams["font.size"] = fsz
plt.rcParams['font.family'] ='sans-serif'
#################################################
#     (something to draw)                       #
#################################################
# store texts drawn
ss=[]
ss=ss+[u'グラフ用紙の作成']
ss=ss+[u'出力したいテキストの入力']
ss=ss+[u'ボックス頂部の座標設定']
ss=ss+[u'矢印頂部の座標設定\n(ボックス底部の座標)']
ss=ss+[u'矢印形状の定義']
ss=ss+[u'ボックスおよびテキスト描画']
ss=ss+[u'矢印描画']
ss=ss+[u'画像出力']

# define top of box
dyt=2 # height of text box
dya=0.75 # height of allow
yt=[]
for i in range(0,len(ss)):
    if i==0:
        _y=1
    else:
        _y=_y+dyt+dya
    yt=yt+[float(_y)]

# define top of allow
ya=[]
for i in range(0,len(ss)-1):
    if i==0:
        _y= 1+dyt
    else:
        _y=_y+dya+dyt
    ya=ya+[float(_y)]
# define arrow shape
# total length of arrow: dya
dxa=dya*2 # totalwidth of arrow 
x1=dxa/4; y1=0      #       7 + 1
x2=x1   ; y2=dya/2  #       +   +
x3=dxa/2; y3=y2     #       +   +
x4=0    ; y4=dya    #   5 + 6    2 + 3 
x5=-x3  ; y5=y3     #     +       +
x6=-x2  ; y6=y2     #       +   +
x7=-x1  ; y7=y1     #         4

# draw text and box
xi=-5.5 # left edge of box
dx=11   # width of box
xx=0    # center of text
for i in range(0,len(ss)):
    yi=yt[i]
    ax1.add_patch(Rectangle((xi,yi),dx,dyt,fc='#ffffff',ec='#000000'))
    yy=yi+dyt/2
    ax1.text(xx,yy,ss[i],fontsize=fsz,ha='center',va='center',fontproperties=fp)

# draw arrows
for i in range(0,len(ya)):
    yi=ya[i]
    yi1=y1+yi
    yi2=y2+yi
    yi3=y3+yi
    yi4=y4+yi
    yi5=y5+yi
    yi6=y6+yi
    yi7=y7+yi
    xy=([x1,yi1],[x1,yi1],[x2,yi2],[x3,yi3],[x4,yi4],[x5,yi5],[x6,yi6],[x7,yi7])
    ax1.add_patch(Polygon(xy,closed=True,fc='#eeeeee',ec='#000000'))


#================================================
if iflag==0:
    fnameF='fig_gra2.png'
    plt.savefig(fnameF, dpi=200, bbox_inches="tight", pad_inches=0.1)
plt.show()

作例

少し複雑な英語版チャート

少し複雑な英語版チャートの事例を示す.

やっていることは以下の通り.

  • フォントは tahoma.ttf (fon='/Library/Fonts/Tahoma.ttf'
  • グリッド・座標軸を表示(iflag=1
  • 描画範囲は,x 方向は -8〜+8,y 方向は 0〜40 まで.
  • 縦横比は Raspect=0.7 に指定.
  • フォントサイズは fsz=12 に指定.
  • リスト a[] に描画したい文字列を格納
  • リスト nln=[] は1アイテム(1つのかたまり)の中の行数を格納する
  • リスト nsp=[] は1アイテムを示すボックスの形を格納する.0 はボックス(長方形),1 はひし形.
  • Polygon でボックスおよび判断を示すひし形を描画し,その中に文字列を描画していく.
  • 濃い灰色のボックスにはメインアイテムの文字列を,薄い灰色のボックスにはサブアイテムの文字列を描画している.
  • ボックスとボックスを結ぶ線は plot で先に描画してしまう.
  • 矢印は,頭の部分だけ arrow によりあとから描画する.
# Flowchart
from math import *
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon,Rectangle
from matplotlib.ticker import *
from matplotlib.font_manager import FontProperties

#fon='/Library/Fonts/Arial.ttf'
#fon='/System/Library/Fonts/Helvetica.ttc'
fon='/Library/Fonts/Tahoma.ttf'
#fon='/Library/Fonts/Times New Roman.ttf'
fp=FontProperties(fname=fon)

# iflag=0: without axis and grid, iflag=1: with axis and grid
iflag=1

# definition if the range of axis
xmin=-8
xmax=8
ymin=0
ymax=40
Raspect=0.7
iw=abs(xmax-xmin)/2
ih=abs(ymin-ymax)/abs(xmax-xmin)*iw*Raspect

fig = plt.figure(figsize=(iw,ih),facecolor='w')
ax1 = fig.add_subplot(1,1,1, adjustable='box', aspect=Raspect)
ax1.set_xlim([xmin,xmax])
ax1.set_ylim([ymax,ymin])
if iflag==0: 
    plt.axis('off')
else:
    ax1.tick_params(labelsize=10)
    ax1.xaxis.set_major_locator(MultipleLocator(1))
    ax1.yaxis.set_major_locator(MultipleLocator(1))
    plt.grid(which='major',lw=0.3, color='#777777',linestyle='-')

fsz=12 # font size
plt.rcParams["font.size"] = fsz
plt.rcParams['font.family'] ='sans-serif'
#################################################
#     (something to draw)                       #
#################################################
# Store texts drawn
a=[] # text
nln=[] # number of lines in 1 item
nsp=[] # shape (0: rectangle, 1: diamond)
nln=nln+[int(1)]
nsp=nsp+[int(0)]
a=a+['Set target output $P=P_1,P_2,..,P_N$'] 
nln=nln+[int(4)]
nsp=nsp+[int(0)]
a=a+['Set design conditions'] 
a=a+['    $\eta$ : combined efficiency']
a=a+['    $v$ : waterway velocity']
a=a+['    $n$ : waterway roughness coefficient']
nln=nln+[int(1)]
nsp=nsp+[int(0)]
a=a+['Set initial FSL $FSL=FSL_0$']
nln=nln+[int(1)]
nsp=nsp+[int(0)]
a=a+['$FSL=FSL+\Delta H$']
nln=nln+[int(4)]
nsp=nsp+[int(0)]
a=a+['Set parameters']
a=a+['    $MOL$ considering $R_{pt}\leq 1.2$']
a=a+['    $NWL=FSL-(FSL-MOL)\:/\:3$']
a=a+['    $V_e$ : net storage,    $H$ : dam height']
nln=nln+[int(1)]
nsp=nsp+[int(0)]
a=a+['$P_i=P_1,P_2,..,P_N$']
nln=nln+[int(1)]
nsp=nsp+[int(0)]
a=a+['Set initial discharge $Q=Q_0$']
nln=nln+[int(1)]
nsp=nsp+[int(0)]
a=a+['$Q=Q+\Delta Q$']
nln=nln+[int(4)]
nsp=nsp+[int(0)]
a=a+['Output calculation'] 
a=a+['    (1) Re-calculate head loss ($h_L$)']
a=a+['    (2) Re-calculate output']
a=a+['$P_Q=9.8 \\times (NWL-h_L) \\times Q \\times \eta$']
nln=nln+[int(1)]
nsp=nsp+[int(1)]
a=a+['$P_i \leq P_Q$']
nln=nln+[int(2)]
nsp=nsp+[int(0)]
a=a+['Calculate generating duration $T$']
a=a+['$T=V_e \:/\: Q \:/\: 3600$ (hr)']
nln=nln+[int(2)]
nsp=nsp+[int(0)]
a=a+['Output results']
a=a+['$P_i \quad H \quad T \quad Q \quad h_L \quad \dots$'];

dh=1       # height of 1 line
dx=10      # width of box
x0=2       # location of center
xs=x0-dx/2 # starting coordinate of x of box
xe=x0+dx/2 # ending coordinate of x of box
i=-1       # initialization of line counter

# Draw text and box
for nl, ns in zip(nln,nsp):
    if ns==0: # rectangle
        # main item
        if i==-1: ye=0
        ys=ye+dh; ye=ys+dh
        xy=([xs,ys],[xe,ys],[xe,ye],[xs,ye])
        poly = Polygon(xy, facecolor='#dddddd', edgecolor='#000000',lw=1)
        ax1.add_patch(poly)
        i=i+1; ax1.text(x0,ys+dh*1/2,a[i],fontsize=fsz,ha='center',va='center',fontproperties=fp)
        if 2<=nl: # sub-items
            ys=ye; ye=ys+(nl-1)*dh
            xy=[(xs,ys),(xe,ys),(xe,ye),(xs,ye)]
            poly = Polygon(xy, facecolor='#eeeeee', edgecolor='#000000',lw=1)
            ax1.add_patch(poly)
            for j in range(0,nl-1):
                i=i+1
                if a[i][0]=='$':
                    ax1.text(x0,ys+dh*(2*j+1)/2,a[i],fontsize=fsz,ha='center',va='center',fontproperties=fp)
                else:
                    ax1.text(xs,ys+dh*(2*j+1)/2,a[i],fontsize=fsz,ha='left',va='center',fontproperties=fp)
    if ns==1: # diamond (main item)
        ys=ye+dh; ye=ys+2*dh
        xy=([x0,ys],[x0+dx/4,0.5*(ys+ye)],[x0,ye],[x0-dx/4,0.5*(ys+ye)])
        poly = Polygon(xy, facecolor='#dddddd', edgecolor='#000000',lw=1)
        ax1.add_patch(poly)
        i=i+1; ax1.text(x0,0.5*(ys+ye),a[i],fontsize=fsz,ha='center',va='center',fontproperties=fp)

# draw lines on center line
eln=[3,8,10,12,17,19,21,23,28,31,34,37] # y-coordinate of line end
for el in eln: 
    ax1.plot([x0,x0],[el-dh, el],color='#000000',lw=1)
# draw arrows on center line
hw=0.3; hl=0.4
for el in eln[0:len(eln)-1]:
    ax1.arrow(x0, el-hl,0, 0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)

# draw lines
x1=x0-dx/4  ; y1=29
x2=x0-dx/2-1; y2=29
x3=x0-dx/2-1; y3=21.5
x4=x0-dx/2  ; y4=21.5
ax1.plot([x1,x2,x3,x4],[y1,y2,y3,y4],color='#000000',lw=1)
x1=x0-dx/2  ; y1=34.5
x2=x0-dx/2-2; y2=34.5
x3=x0-dx/2-2; y3=17.5
x4=x0-dx/2  ; y4=17.5
ax1.plot([x1,x2,x3,x4],[y1,y2,y3,y4],color='#000000',lw=1)
x1=x0       ; y1=37
x2=x0-dx/2-3; y2=37
x3=x0-dx/2-3; y3=10.5
x4=x0-dx/2  ; y4=10.5
ax1.plot([x1,x2,x3,x4],[y1,y2,y3,y4],color='#000000',lw=1)
# draw arrows
hw=0.3; hl=0.4
ax1.arrow(x0-dx/2-hl,21.5,0.01,0,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
ax1.arrow(x0-dx/2-hl,17.5,0.01,0,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
ax1.arrow(x0-dx/2-hl,10.5,0.01,0,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
# draw text
ax1.text(x0-dx/4-0.1,29.5,'No',fontsize=fsz,ha='right',va='center',fontproperties=fp)
ax1.text(x0+0.5,30.5,'Yes',fontsize=fsz,ha='left',va='center',fontproperties=fp)


#================================================
if iflag==0:
    fnameF='fig_gra3.png'
    plt.savefig(fnameF, dpi=200, bbox_inches="tight", pad_inches=0.1)
plt.show()

作例

追記:日本語を用いた簡単なチャート(annotateで矢印)

  • 矢印をannotateで書いてみました.
  • ax1.xxx は使わず,全て plt.xxx で書いてみました.
# Flowchart
from math import *
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon,Rectangle
from matplotlib.ticker import *
from matplotlib.font_manager import FontProperties

#fon='/Library/Fonts/Arial.ttf'
#fon='/System/Library/Fonts/Helvetica.ttc'
#fon='/Library/Fonts/Tahoma.ttf'
#fon='/Library/Fonts/Times New Roman.ttf'
fon='/Users/kk/Library/Fonts/ipaexg.ttf'
fp=FontProperties(fname=fon)

# iflag=0: without axis and grid, iflag=1: with axis and grid
iflag=1

# definition if the range of axis
xmin=-8
xmax=8
ymin=0
ymax=25
Raspect=0.8
iw=abs(xmax-xmin)/2
ih=abs(ymin-ymax)/abs(xmax-xmin)*iw*Raspect

fig = plt.figure(figsize=(iw,ih),facecolor='w')
plt.gca().set_aspect(Raspect,adjustable='box')
plt.xlim([xmin,xmax])
plt.ylim([ymax,ymin])
if iflag==0: 
    plt.axis('off')
else:
    plt.gca().tick_params(labelsize=10)
    plt.gca().xaxis.set_major_locator(MultipleLocator(1))
    plt.gca().yaxis.set_major_locator(MultipleLocator(1))
    plt.grid(which='major',lw=0.3, color='#777777',linestyle='-')

fsz=16 # font size
plt.rcParams["font.size"] = fsz
plt.rcParams['font.family'] ='sans-serif'
#################################################
#     (something to draw)                       #
#################################################
# store texts drawn
ss=[]
ss=ss+[u'グラフ用紙の作成']
ss=ss+[u'出力したいテキストの入力']
ss=ss+[u'ボックス頂部の座標設定']
ss=ss+[u'矢印頂部の座標設定\n(ボックス底部の座標)']
ss=ss+[u'矢印形状の定義']
ss=ss+[u'ボックスおよびテキスト描画']
ss=ss+[u'矢印描画']
ss=ss+[u'画像出力']

# define top of box
dyt=2 # height of text box
dya=0.75 # height of allow
yt=[]
for i in range(0,len(ss)):
    if i==0:
        _y=1
    else:
        _y=_y+dyt+dya
    yt=yt+[float(_y)]

# define top of allow
ya=[]
for i in range(0,len(ss)-1):
    if i==0:
        _y= 1+dyt
    else:
        _y=_y+dya+dyt
    ya=ya+[float(_y)]

# draw text and box
xi=-5.5 # left edge of box
dx=11   # width of box
xx=0    # center of text
for i in range(0,len(ss)):
    yi=yt[i]
    plt.fill([xi,xi,xi+dx,xi+dx],[yi,yi+dyt,yi+dyt,yi],fc='#ffffff',ec='#000000')
    yy=yi+dyt/2
    plt.text(xx,yy,ss[i],fontsize=fsz,ha='center',va='center',fontproperties=fp)

# draw arrow
sv=0
for y2 in ya:
    y1=y2+dya
    x1=0; x2=x1
    plt.annotate('',
        xy=(x1,y1), xycoords='data',
        xytext=(x2,y2), textcoords='data', fontsize=0,
        arrowprops=dict(shrink=sv,width=20,headwidth=40,headlength=10,
                        connectionstyle='arc3',facecolor='#eeeeee',edgecolor='#000000'))

#================================================
if iflag==0:
    fnameF='fig_gra2.png'
    plt.savefig(fnameF, dpi=200, bbox_inches="tight", pad_inches=0.1)
plt.show()

以 上

4
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6