Python
matplotlib

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

1. はじめに

まずは作例を.

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

ちなみに,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点の扱いを含め,グラフの説明をしたいと思います.

2. プログラム解説

(1) データ読み込み

データは別途行った計算結果を np.loadtxt で読み込んでいます.
ファイル名,データファイルから読み込む列を usecols で指定,読み飛ばす行数を skiprows で指定.
読み込んだデータを hr と mre という numpy 配列に格納します.

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

(2) 条件を満たすインデックスを取得

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

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を使っています.

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を白に指定しているので,白抜き丸記号でプロットされるようになります.

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 で描画します.面倒ですがこの2ステップで一つの矢印を描きます.
  • 矢の幅 hw と長さ hl を指定します.
  • 下向き矢印の場合は,始点 y 座標を矢の長さ hl だけ上に取ります.
  • y 方向増分座標は図上で目立たないように小さくとります.ここでは -0.01 としました.
  • あとは facecolor と edgecolor による色の指定と,指定した矢の幅,長さとなるように head_width および head_length を指定します.
  • 上向き矢印の場合は,始点を矢の長さ分下により,小さな y 方向増分(ここでは +0.01 )を指定するようにします.
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で余白を小さくしています.

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

(7) その他

あとは色々な装飾をするのにごちゃごちゃやっています.

3. プログラム全文

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()

以 上