はじめに
エクセルで作った表を TeX や html などに貼り付けいたい場合、どうしているだろうか?
簡単なのは、エクセルを起動してスクリーンショットを撮って貼り付けてしまう、エクセルの表を pdf 出力し、これを Preview などで加工する方法だと思う。
しかしながら、表の中身が変わったり、たくさんの表の処理を行う場合など、何度も同じ作業をやらなければならないので、面倒である。
そこで、Python プログラムにより、エクセルのデータを読み取って表形式で画像出力する方法を考えた。
どうしてもエクセルの画像じゃないと嫌だという人には役に立たないが、私のニーズは数値を表形式で示せればいいので、このような方法は有益である。
表形式出力の方法として、matplotlib.table を使う方法と、自力で patches.Rectangle の中に text データを書き込む方法を試してみた。
当方の環境は以下の通り。
M1 Pro MacBook Pro 14"
Python 3.17.7
matplotlib 3.10.5
openpyxl 3.1.5
作成
まずは作例を示そう。
(1)エクセルの表(スクリーンショット)
(2)matplotlib.table
(3)Rectangle + text
解説
inp_excel
- エクセルの表からデータを抽出し2次元配列に格納する。
- データの大きさが変わることを想定している。
- 行方向はA列を検索し、空セルを3つ数えたらその直前までを読み取り範囲とする。
- 列方向は2行めを検索し、空セルを1つ数えたらその直前までを読み取り範囲とする。
- データは空セルも含めて指定した範囲内にあるものはすべて読み込む。
- 後で数値計算用データとしても使えるよう、空セルは空文字列、文字列は文字列、数値は数値(ここでは小数点3位まで)として2次元配列に格納する。
- 数値であるかの判定は、float を当てて
try... except...
で処理する方法が信頼できるようである。
make_sdata
表として出力するため、数値を文字列に変換し、全要素を文字列とした配列を作成。
mp_tbl
-
matplotlib.table
で表を表示。 - 全要素を文字列に変換した2次元配列を
table
に投げ込む。 - 表の表示においては
edges='open'
として、ここでは罫線の表示は行わない。 -
bbox
で表の大きさを指定。 -
tbl
というtable
オブジェクトを作成後、tbl
に対して装飾を施していく。 -
tbl.auto_set_font_size(False)
,tbl.set_fontsize(fsz)
でフォントサイズを指定 -
tbl[i,j].set_text_props(ha='left')
で指定箇所表示を左寄せに指定 -
tbl[i,j].visible_edges='closed'
で指定せるの罫線による取り囲みを指定 -
tbl[i,j].set_edgecolor('#000000')
で罫線色を黒に指定 -
tbl[i,j].set_linewidth(0.5)
で罫線はばを0.5に指定 -
tbl[i,j].set_facecolor('#dddddd')
で指定箇所を灰色で塗りつぶし
my_tbl
patches.Rectangle + text
で表を表示。
説明は省略。
fig
グラフの全体像の定義
-
data=inp_excel(fnameR)
エクセルデータ取得() -
sdata=make_sdata(data)
エクセルデータは、文字列は文字列、数値は数値として読み込まれているので、数値を必要桁に揃えた文字列に変換している。 -
tw,th=1,1
で表の幅と高さ定義。 - ```cおl```及び
colw
という配列で列幅を定義。 - ここではグラフの描画範囲を
x=(0,1)
、y=(0,1)
で定義し、表もこの領域全体に表示するようにしている。 - 一般的なグラフ作成の手続きを踏んで、
plt.axis('off')
で軸を非表示にする。 - あとは見ての通りかな。
main
2つのケースの制御
プログラム
import openpyxl as excel
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
def inp_excel(fnameR):
book=excel.load_workbook(fnameR,data_only=True)
worksheet=book['data']
srmax,scmax=50,10 # search range
k=0
sr=[]
for i in range(0,srmax):
nrow=i+1
ss=worksheet.cell(row=nrow,column=1).value
if ss==None: k=k+1
if k<3: sr.append(ss)
if k==3: break
n=len(sr)
k=0
sc=[]
for j in range(0,scmax):
ncol=j+1
ss=worksheet.cell(row=2,column=ncol).value
if ss==None: k=k+1
if k<1: sc.append(ss)
if k==1: break
m=len(sc)
rstr=f'A1:{chr(64+m)}{n}'
rect=worksheet[f'{rstr}']
data=[]
for row in rect:
row_data=[]
for cell in row:
s=cell.value
if s==None: s1=''
else:
try:
s=float(s)
s1=np.round(s,decimals=3)
except ValueError:
s1=s
row_data.append(s1)
data.append(row_data)
data=np.array(data,dtype=object)
return data
def make_sdata(data):
# change to str data
n,m=data.shape
sdata=np.empty((n,m),dtype=object)
for i in range(n):
for j in range(m):
s=data[i,j]
if s=='': s1=''
else:
try:
s=float(s)
s1=f'{s:.3f}'
except ValueError:
s1=s
sdata[i,j]=s1
return sdata
def mp_tbl(sdata,trow,dparam,fsz):
# draw table using matplotlib.table
xmin,xmax=dparam[0],dparam[1]
ymin,ymax=dparam[2],dparam[3]
tw,th=dparam[4],dparam[5]
colw=dparam[6]
tbl=plt.table(
cellText=sdata,
colWidths=colw,
cellLoc='center',
edges='open',
bbox=[xmin,ymin,tw,th]
)
tbl.auto_set_font_size(False)
tbl.set_fontsize(fsz)
n,m=sdata.shape
for i in range(n):
for j in range(m):
if i==trow[0] or i==trow[1] or i==trow[2]:
tbl[i,j].set_text_props(ha='left')
if i!=trow[0] and i!=trow[1] and i!=trow[2] and sdata[i,j]!='':
tbl[i,j].visible_edges='closed'
tbl[i,j].set_edgecolor('#000000')
tbl[i,j].set_linewidth(0.5)
if i==trow[0]+1 or i==trow[1]+1 or i==trow[2]+2:
tbl[i,j].set_facecolor('#dddddd')
def my_tbl(sdata,trow,dparam,fsz):
# draw table using rectangle
xmin,xmax=dparam[0],dparam[1]
ymin,ymax=dparam[2],dparam[3]
tw,th=dparam[4],dparam[5]
colw=dparam[6]
n,m=sdata.shape
dh=th/n
for i in range(n):
for j in range(m):
if sdata[i,j]=='': continue
col='#ffffff'
if i==trow[0]+1 or i==trow[1]+1 or i==trow[2]+2:
col='#dddddd'
x0=xmin+np.sum(colw[0:j])
y0=ymax-dh*(i+1)
if j==0: x0=xmin
r=patches.Rectangle(
xy=(x0,y0),
width=colw[j],
height=dh,
edgecolor='#000000',
facecolor=col,
linewidth=0.5,
clip_on=False
)
if i==trow[0] or i==trow[1] or i==trow[2]:
hha,vva='left','center'
xs=x0+0.1*colw[j]
else:
plt.gca().add_patch(r)
hha,vva='center','center'
xs=x0+0.5*colw[j]
ys=y0+0.5*dh
plt.text(xs,ys,sdata[i,j],ha=hha,va=vva,fontsize=fsz)
def fig(nnn,fnameR,fnameF):
data=inp_excel(fnameR)
sdata=make_sdata(data)
n,m=sdata.shape
trow=[]
for i in range(n):
if sdata[i,0]=='Data for basic design': trow.append(i)
if sdata[i,0]=='Data for surging analysis': trow.append(i)
if sdata[i,0]=='Shaft data': trow.append(i)
i,j=trow[2]+1,2; sdata[i,j]=f'{data[i,j]:.0f}'
print(sdata)
tstr='Surging analysis data'
tw,th=1,1
col=np.array([2.0, 0.8, 0.8]+[1]*(m-3))
colw=col/np.sum(col)*tw
fsz=7.5
xmin,xmax=0,1
ymin,ymax=0,1
iw,ih=10, 12 # default (inch)
plt.figure(figsize=(iw,ih),facecolor='w')
plt.xlim([xmin,xmax])
plt.ylim([ymin,ymax])
plt.axis('off')
dparam=[xmin,xmax,ymin,ymax,tw,th,colw]
if nnn==1: mp_tbl(sdata,trow,dparam,fsz)
if nnn==2: my_tbl(sdata,trow,dparam,fsz)
if nnn==1: astr=' (matplotlib.table)'
if nnn==2: astr=' (Rectangle + text)'
tstr=tstr+astr
plt.title(tstr,x=np.sum(colw[0:m])/2,fontsize=fsz+2,weight='bold')
plt.savefig(fnameF, dpi=200, bbox_inches="tight", pad_inches=0.1)
#plt.show()
def main():
#fnameR='surge_data_TR.xlsx'
#fnameR='surge_data_HR_1.xlsx'
fnameR='surge_data_HR_2.xlsx'
for iii in [1,2]:
if iii==1: fnameF='fig_test1.jpg'
if iii==2: fnameF='fig_test2.jpg'
fig(iii,fnameR,fnameF)
#---------------
# Execute
#---------------
if __name__ == '__main__': main()
感想
matplotlib.table
もいじっていたら、だいぶ癖がわかってきたというか、自由に使えるようになってきた気がする。
どうしても上手くいかないときは、Rectangle + text
という手もあるので、色々経験していこうと思う。
以 上