Python matplotlibでフローチャートのようなものを強引に描く（２）

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

前の投稿はこれ．
[Python, matplotlibでフローチャートのようなものを強引に描く](https://qiita.com/damyarou/items/818b20ea0def9ff43484)

今回のプログラムの特徴は（といっても大したことはないが．．．）

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

作業環境は以下の通り．

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

ここで紹介する事例は以下の３点である．

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


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

注意事項は以下の通り．

+ フォント指定のため，*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* としている．


```python:
# 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()
```

## 作例
<img src="https://qiita-image-store.s3.amazonaws.com/0/129300/065abdfb-7e7f-d15f-1bc3-b9fec7efeee5.png" width=70%>



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

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

+ フォントは 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* で定義

```python:
# 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()
```

## 作例
<img src="https://qiita-image-store.s3.amazonaws.com/0/129300/cb37426c-28fd-96a0-a188-980062382603.png" width=70%>



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

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

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

```python:
# 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()
```

## 作例
<img src="https://qiita-image-store.s3.amazonaws.com/0/129300/62ab94e2-7276-f31a-bc85-cf7bd75661fb.png" width=70%>


# 追記：日本語を用いた簡単なチャート（annotateで矢印）

+ 矢印をannotateで書いてみました．
+ ax1.xxx は使わず，全て plt.xxx で書いてみました．

```python:
# 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()
```

以　上