matplotlib グラフ作成Tips (3) 白抜き記号と矢印

# 1. はじめに
まずは作例を．


<img src="https://qiita-image-store.s3.amazonaws.com/0/129300/1b0ae421-aa63-639d-745e-ad4f8b1cc0bc.png" width=70%>


とある設計をするのに，上記のグラフを作りました．私は現在海外勤務中であり，日本語入りのグラフは意味を持たないので，アルファベットのみ使用していることはご勘弁を．

ちなみに，Qiita内におけるグラフは以下により大きさを調整しています．

```
<img src="https://qiita-image-store.s3.amazonaws.com/0/129300/1b0ae421-aa63-639d-745e-ad4f8b1cc0bc.png" width=70%>
```

このグラフの作図上のポイントは，白抜き記号の描画と矢印の扱いです．

白黒ベースで作図したい場合，白抜き記号はぜひ使いたいのですが，これを実現するのに以外に手間取りました．

また matplotlib の矢印は，quiver で描くのもいいのですが，どうも勝手がよくないので，私の場合は arrow を用いています．しかし，arrow は始点座標と増分を指定すると，そこまで線を引いて更に矢印の head_length 分先端がとびだしてしまいます．これを意図した位置で止めるのに自分なりの方法を使っています．

これら２点の扱いを含め，グラフの説明をしたいと思います．

# 2. プログラム解説
## (1) データ読み込み
データは別途行った計算結果を np.loadtxt で読み込んでいます．
ファイル名，データファイルから読み込む列を usecols で指定，読み飛ばす行数を skiprows で指定．
読み込んだデータを hr と mre という numpy 配列に格納します．

```python:
data1 = np.loadtxt(fnameR1, usecols=(0,4),skiprows=1)
hr=data1[:,0]
mre=data1[:,1]
```

## (2) 条件を満たすインデックスを取得
下の事例では，hs という numpy １次元配列から，その値が```y5(=72)```となるインデックスを取り出します．
np.where はリストを返すようなので，その値が１つだけでこれをスカラーに変換する場合，```i5=_i[0][0]```としてやる必要があります．
これに気がつくのに結構時間がかかった．

```python:
y5=72
_i=np.where(hs==y5); i5=_i[0][0]
```

## (3) グラフ全体像の指定
私の場合，グラフ全体像を指定する部分は，描画部分の前半にまとめています．
あとでコピペで使いまわすのに便利だから．
下の事例では，横軸数値は 0.2 ピッチ，縦軸数値は 5 ピッチで描画し，グリッドは横軸 0.1 ピッチ，縦軸 1 ピッチとしています．これを行うため，```import matplotlib.ticker as tick```して，```tick.MultipleLocator```を使っています．

```python:
fsz=16
xmin=0; xmax=1.4; dx=0.2
ymin=60; ymax=85; dy=1
#fig=plt.figure(figsize=(8,10))
#plt.style.use('dark_background')
fig=plt.figure(figsize=(8,10),facecolor='w')
plt.rcParams["font.size"] = fsz
plt.rcParams['font.family'] ='sans-serif'
plt.xlim(xmin,xmax)
plt.ylim(ymin,ymax)
plt.xlabel('Bending moment $M\: / \:bh^2$ (MPa)')
plt.ylabel('Elevation (EL.m)')
plt.gca().xaxis.set_major_locator(tick.MultipleLocator(0.2))
plt.gca().yaxis.set_major_locator(tick.MultipleLocator(5.0))
plt.gca().xaxis.set_minor_locator(tick.MultipleLocator(0.1))
plt.gca().yaxis.set_minor_locator(tick.MultipleLocator(1.0))
plt.grid(which='both',color='#999999',linestyle='solid')
```

## (4) 白抜き記号の描画
白抜き記号を描画するには，colorで色を指定した後，markerfacecolorで内部の色を指定します．下の事例では，黒い丸を黒い実線で結ぶというのが基本ですが，makerfacecolorを白に指定しているので，白抜き丸記号でプロットされるようになります．

```python:
plt.plot(ms1,hs,'o-',ms=8,color='#000000',markerfacecolor='#ffffff',label='90 nos Tens-rebar',lw=1.5)
```

## (5) 矢印
グラフ左側に縦方向に範囲を指定する矢印を描いています．これを描くのに一苦労です．
matplotlib の arrow は head_length 分飛び出す特性があることは前述のとおりです．このため私の場合，以下の方法で対応しています．
操作の基本的な考え方を示します．

+ 矢印の線の部分は plot で，矢の部分は arrow で描画します．面倒ですがこの２ステップで一つの矢印を描きます．
+ 矢の幅 hw と長さ hl を指定します．
+ 下向き矢印の場合は，始点 y 座標を矢の長さ hl だけ上に取ります．
+ y 方向増分座標は図上で目立たないように小さくとります．ここでは -0.01 としました．
+ あとは facecolor と edgecolor による色の指定と，指定した矢の幅，長さとなるように head_width および head_length を指定します．
+ 上向き矢印の場合は，始点を矢の長さ分下により，小さな y 方向増分（ここでは +0.01 ）を指定するようにします．

```python:
hw=0.015; hl=0.4
plt.arrow(_x,60+hl,0,-0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
plt.arrow(_x,y2-hl,0, 0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
```

## (6) 画像の保存
デフォルトでは荒っぽいので dpi を指定しています．またデフォルトでは画像の余白が大きすぎるので，```bbox_inches="tight", pad_inches=0.1```で余白を小さくしています．

```python:
fnameF='fig_rc_cf2.png'
plt.savefig(fnameF, dpi=200, bbox_inches="tight", pad_inches=0.1)
```

## (7) その他
あとは色々な装飾をするのにごちゃごちゃやっています．


# 3. プログラム全文
```python:
import sys
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.ticker as tick

fnameR1='_inp_req.txt'
fnameR2='_inp_uls.txt'
data1 = np.loadtxt(fnameR1, usecols=(0,4),skiprows=1)
hr=data1[:,0]
mre=data1[:,1]
data2 = np.loadtxt(fnameR2, usecols=(0,3),skiprows=0)
n=16
hs=data2[0:n,0]
ms1=data2[0*n:1*n,1]
ms2=data2[1*n:2*n,1]
ms3=data2[2*n:3*n,1]
ms4=data2[3*n:4*n,1]
ms5=data2[4*n:5*n,1]
hr=81-hr
hs=81-hs
y5=72
y4=68
y3=65
y2=62
_i=np.where(hs==y5); i5=_i[0][0]
_i=np.where(hs==y4); i4=_i[0][0]
_i=np.where(hs==y3); i3=_i[0][0]
_i=np.where(hs==y2); i2=_i[0][0]

# Drawing
fsz=16
xmin=0; xmax=1.4; dx=0.2
ymin=60; ymax=85; dy=1
#fig=plt.figure(figsize=(8,10))
#plt.style.use('dark_background')
fig=plt.figure(figsize=(8,10),facecolor='w')
plt.rcParams["font.size"] = fsz
plt.rcParams['font.family'] ='sans-serif'
plt.xlim(xmin,xmax)
plt.ylim(ymin,ymax)
plt.xlabel('Bending moment $M\: / \:bh^2$ (MPa)')
plt.ylabel('Elevation (EL.m)')
plt.gca().xaxis.set_major_locator(tick.MultipleLocator(0.2))
plt.gca().yaxis.set_major_locator(tick.MultipleLocator(5.0))
plt.gca().xaxis.set_minor_locator(tick.MultipleLocator(0.1))
plt.gca().yaxis.set_minor_locator(tick.MultipleLocator(1.0))
plt.grid(which='both',color='#999999',linestyle='solid')

plt.plot(mre,hr,'-',color='#999999',label='Required moment',lw=3)
plt.plot(ms1,hs,'o-',ms=8,color='#000000',markerfacecolor='#ffffff',label='90 nos Tens-rebar',lw=1.5)
plt.plot(ms2,hs,'o-',ms=8,color='#777777',markerfacecolor='#777777',label='70 nos Tens-rebar',lw=1.5)
plt.plot(ms3,hs,'s-',ms=8,color='#000000',markerfacecolor='#ffffff',label='50 nos Tens-rebar',lw=1.5)
plt.plot(ms4,hs,'s-',ms=8,color='#777777',markerfacecolor='#777777',label='30 nos Tens-rebar',lw=1.5)
plt.plot(ms5,hs,'D-',ms=8,color='#000000',markerfacecolor='#ffffff',label='10 nos Tens-rebar',lw=1.5)
plt.plot([xmin,xmax],[81,81],'--',color='#000000',lw=2)
_x=0.07; _dx=0.03
plt.plot([_x-_dx,ms2[i2]],[y2,y2],'-.',color='#000000',lw=1.5)
plt.plot([_x-_dx,ms3[i3]],[y3,y3],'-.',color='#000000',lw=1.5)
plt.plot([_x-_dx,ms4[i4]],[y4,y4],'-.',color='#000000',lw=1.5)
plt.plot([_x-_dx,ms5[i5]],[y5,y5],'-.',color='#000000',lw=1.5)
plt.plot([_x,_x],[60,81],'-',color='#000000',lw=1.5)
hw=0.015; hl=0.4
plt.arrow(_x,60+hl,0,-0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
plt.arrow(_x,y2-hl,0, 0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
plt.arrow(_x,y2+hl,0,-0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
plt.arrow(_x,y3-hl,0, 0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
plt.arrow(_x,y3+hl,0,-0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
plt.arrow(_x,y4-hl,0, 0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
plt.arrow(_x,y4+hl,0,-0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
plt.arrow(_x,y5-hl,0, 0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
plt.arrow(_x,y5+hl,0,-0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
plt.arrow(_x,81-hl,0, 0.01,fc='#000000',ec='#000000',head_width=hw,head_length=hl)
plt.text(xmin+0.02,81+0.2,'Top of retaining wall (EL.81.0)',fontsize=fsz-2,ha='left',va='bottom')
dd=0.01
_y=0.5*(60+y2); plt.text(_x-dd,_y,'T32'   ,rotation=90,va='center',ha='right',fontsize=fsz-2)
_y=0.5*(60+y2); plt.text(_x+dd,_y,'90 nos',rotation=90,va='center',ha='left' ,fontsize=fsz-2)
_y=0.5*(y2+y3); plt.text(_x-dd,_y,'T32'   ,rotation=90,va='center',ha='right',fontsize=fsz-2)
_y=0.5*(y2+y3); plt.text(_x+dd,_y,'70 nos',rotation=90,va='center',ha='left' ,fontsize=fsz-2)
_y=0.5*(y3+y4); plt.text(_x-dd,_y,'T32'   ,rotation=90,va='center',ha='right',fontsize=fsz-2)
_y=0.5*(y3+y4); plt.text(_x+dd,_y,'50 nos',rotation=90,va='center',ha='left' ,fontsize=fsz-2)
_y=0.5*(y4+y5); plt.text(_x-dd,_y,'T32'   ,rotation=90,va='center',ha='right',fontsize=fsz-2)
_y=0.5*(y4+y5); plt.text(_x+dd,_y,'30 nos',rotation=90,va='center',ha='left' ,fontsize=fsz-2)
_y=0.5*(y5+81); plt.text(_x+dd,_y,'T32 10 nos',rotation=90,va='center',ha='left' ,fontsize=fsz-2)
plt.legend(shadow=True,facecolor='#ffffff',fontsize=fsz-2)
plt.title('Wall height=21m, counterfort slope=1:0.4',loc='left',fontsize=fsz)

fnameF='fig_rc_cf2.png'
plt.savefig(fnameF, dpi=200, bbox_inches="tight", pad_inches=0.1)
plt.show()
```
以　上