Python matplotlibで棒グラフ（ハッチングと２軸グラフの凡例）

# はじめに
この記事を紹介しようと思ったきっかけは以下の通りです．

+ データ比較用の棒グラフを白黒で作成したかったので，ハッチングにより対応することにしました．色とりどりのグラフはよく見かけますが，今の時代に白黒グラフは以外に少ない？
+ ２軸グラフを作成する時，凡例部をうまくプログラムできないかと考えていたのですが，気に入った方法が見つかったので，実践してみることにしました．


棒グラフのプログラムは，[こちらのサイト](http://python-remrin.hatenadiary.jp/entry/2017/04/28/150338) を参考に，自前のものを書き換えています．

なお，当方の環境は以下のとおりです．

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


# ハッチングパターン
下の図は，棒グラフ本体は描画せずに，凡例だけを描画し，余白削除をしたものです．

<img src="https://qiita-image-store.s3.amazonaws.com/0/129300/de0ec6fb-bcf2-8f6a-bbd4-11914808020d.jpeg" width=30%>

上の図を描くコードは以下の通り．

+ ```plt.axis('off')``` で軸を消しています．
+ 棒のデータはいれず，プロット位置と棒の幅は，```plt.plot([0][0],width=0, ...)``` とし，ラベルとハッチングパターンのみを指定します．この時，それぞれを変数，g1, ... g9 に格納します．
+ ```plt.legend``` で handlers に g1 から g9 を入れて凡例を描画します．
+ ハッチングパターンは，x とか xx とか，描きたいパターンの記号の数を増やすと細かくなります．
+ この方法は，[matplotlib Legend guide](https://matplotlib.org/users/legend_guide.html?highlight=matplotlib%20axes%20axes%20update) を参考にしました．

## プログラム全文

```py:
import matplotlib.pyplot as plt


def main():
    fsz=20   # fontsize
    fig=plt.figure(facecolor='w')
    plt.rcParams['font.size']=fsz
    plt.rcParams['font.family']='monospace'
    plt.axis('off')

    g1=plt.bar([0],[0],width=0,align='center',label='hatch="x"',fill=None,hatch='x')
    g2=plt.bar([0],[0],width=0,align='center',label='hatch="xx"',fill=None,hatch='xx')
    g3=plt.bar([0],[0],width=0,align='center',label='hatch="xxx"',fill=None,hatch='xxx')
    g4=plt.bar([0],[0],width=0,align='center',label='hatch="//"',fill=None,hatch='//')
    g5=plt.bar([0],[0],width=0,align='center',label='hatch="||"',fill=None,hatch='||')
    g6=plt.bar([0],[0],width=0,align='center',label='hatch=".."',fill=None,hatch='..')
    g7=plt.bar([0],[0],width=0,align='center',label='hatch="++"',fill=None,hatch='++')
    g8=plt.bar([0],[0],width=0,align='center',label='hatch="--"',fill=None,hatch='--')
    g9=plt.bar([0],[0],width=0,align='center',label='hatch="*"',fill=None,hatch='*')
    # 凡例をまとめて描画
    plt.legend(handles=[g1,g2,g3,g4,g5,g6,g7,g8,g9],loc='best',shadow=True)
    
    fnameF='fig_legend.jpg'
    plt.savefig(fnameF, dpi=200, bbox_inches="tight", pad_inches=0.1)
    plt.show()

    
#==============
# Execution
#==============
if __name__ == '__main__': main()
```

# ２軸の棒グラフ
２軸棒グラフの描画事例を以下に示します．これは筆者が [Python matplotlibで棒グラフ（２軸グラフと積み上げグラフ）](https://qiita.com/damyarou/items/1269820e403f697bc9e6) に投稿したプログラムを書き換えたものです．

![fig_bar2.jpg](https://qiita-image-store.s3.amazonaws.com/0/129300/d83f7efa-4404-cdbf-30bd-08e24c5c33c1.jpeg)

上記グラフのコードを以下に示します．

左軸グラフのデータ描画部を ```g1=plt.bar(x,hp,width=w, ...)``` とし，右軸グラフのデータ描画部を，```g2=plt.bar(x+w,lp,width=w, ...)``` として，凡例を，```plt.legend(handles=[g1,g2], ...)``` で描画しています．

なお，元データをプロット時に並び替えたいので，文字列も含めて numpy 配列にしています．並び替えは ```numpy.argsort``` で昇順のインデックスを取得し，あとは一気に書き換えます．

## プログラム全文

```py:
import matplotlib.pyplot as plt
import numpy as np


def main():
    # プロットデータ
    site=np.array(['L1','K ','M ','Q ','K1 ','J ','J1','G ','H ','I '])
    hdam=np.array([ 189, 108,  94, 186, 145, 138, 169, 108, 95,  90])
    lway=np.array([2304,4312,4300,2195,3999,3808,2987,3193,5034,5838])

    # データの並び替え
    ii=np.argsort(hdam)
    sp=site[ii]
    hp=hdam[ii]
    lp=lway[ii]
    
    x=np.arange(len(sp)) # ｘ座標
    w=0.3 # 棒の幅

    fsz=16
    fig=plt.figure(figsize=(12,6),facecolor='w')
    plt.rcParams["font.size"] = fsz
    # 左軸グラフの描画
    plt.ylim(0,300)
    plt.ylabel('Dam height (m)')
    plt.xlabel('Site name')
    plt.xticks(x+w/2,sp,rotation=0)
    plt.grid(color='#999999',linestyle='--')
    g1=plt.bar(x,hp,width=w,align='center',label='Dam height (m)',fill=None,hatch='xx')
    # 右軸グラフの描画
    plt.twinx()
    plt.ylim(0,6000)
    plt.ylabel('Waterway length (m)')
    g2=plt.bar(x+w,lp,width=w,align='center',label='Waterway length (m)',fill=None,hatch='/')
    # 凡例をまとめて描画
    plt.legend(handles=[g1,g2],loc='best',shadow=True)

    plt.tight_layout()
    fnameF='fig_bar2.jpg'
    plt.savefig(fnameF, dpi=100, bbox_inches="tight", pad_inches=0.1)
    plt.show()

    
#---------------
# Execute
#---------------
if __name__ == '__main__': main()
```

# １軸の棒グラフ
上記と似た事例として，１軸のデータ比較用棒グラフを示しておきます．

![fig_bar1.jpg](https://qiita-image-store.s3.amazonaws.com/0/129300/260a9af7-81f4-a14d-27f0-c127a91b272a.jpeg)

下に示すコードの中ではコメントアウトしていますが，これは１軸グラフなので，もちろん

```py:
plt.bar(x,  y1,width=w,label='with crane load',  align='center',lw=1,fill=None,hatch='xx')
plt.bar(x+w,y2,width=w,label='without crne load',align='center',lw=1,fill=None,hatch='/')
plt.legend(shadow=True,loc='best')
```

でも問題ありません．

## プログラム全文

```py:
import matplotlib.pyplot as plt
import numpy as np


def main():
    # プロットデータ
    y1=[8127.220, 3109.375, 3683.840, 6504.671, 11687.447, 13068.109, 3993.506, 5995.114, 7050.279, 4058.274]
    y2=[1989.080, 2133.271, 4057.118, 5238.903, 10335.058, 11830.916, 3870.442, 5212.632, 5389.747, 2943.048]
    ss= ['B-C','B-1','B-2','B-3','B-A','B-P','C-E','C-I','B-W','C-W']
    
    x = np.arange(len(y1)) # ｘ座標 
    w = 0.3  #棒の幅

    fsz=16
    fig=plt.figure(figsize=(12,6),facecolor='w')
    plt.rcParams["font.size"]=fsz
    plt.rcParams['font.family']='sans-serif'
    plt.ylim([0,15000])
    plt.ylabel('Max. bending moment (kN-m)')
    plt.xticks(x+w/2,ss,rotation=60)
#    plt.bar(x,  y1,width=w,label='with crane load',  align='center',lw=1,fill=None,hatch='xx')
#    plt.bar(x+w,y2,width=w,label='without crne load',align='center',lw=1,fill=None,hatch='/')
#    plt.legend(shadow=True,loc='best')
    g1=plt.bar(x,  y1,width=w,label='with crane load',  align='center',lw=1,fill=None,hatch='xx')
    g2=plt.bar(x+w,y2,width=w,label='without crne load',align='center',lw=1,fill=None,hatch='/')
    plt.legend(handles=[g1,g2],loc='best',shadow=True)
    fnameF='fig_bar1.jpg'
    plt.savefig(fnameF, dpi=100, bbox_inches="tight", pad_inches=0.1)
    plt.show()

    
#---------------
# Execute
#---------------
if __name__ == '__main__': main()
```

# おまけで余白削除Pythonプログラム
プログラムが格納されているディレクトリ内の jpg ファイルを検索し，余白削除を行うプログラムを紹介しておきます．参考記事を以下に示します・

+ [自分の記事](https://qiita.com/damyarou/items/50e33f56e4922b67e587)
+ [trim](https://gist.github.com/mattjmorrison/932345)
+ [ファイル名取得](https://qiita.com/CORDEA/items/ee44799e5d029ce3aaac)


```py
from PIL import Image, ImageChops
import glob, os

def trim(im, border):
  bg = Image.new(im.mode, im.size, border)
  diff = ImageChops.difference(im, bg)
  bbox = diff.getbbox()
  if bbox:
    return im.crop(bbox)


def main():
    lfig=[os.path.basename(r) for r in glob.glob('*.jpg')]
    for fig in lfig:
        img_org=Image.open(fig,'r')
        img_new=trim(img_org,'#ffffff')
        img_new.save(fig, 'JPEG', quality=100, optimize=True)
        img_new.show()


#==============
# Execution
#==============
if __name__ == '__main__': main()
```


以　上