octave で三角関数のグラフを描いて、ImageMagik でアニメーションしてみる
Windows の octave 5.1.0 で遊んでみた。
その昔「三角関数って、こうやって説明したら良いのにこういうの無いよね」って書いてた blog を思い出したので作ってみた。 (その blog でも gif を作ってました)
octave で画像を描く
clear # 変数クリア
cd "Z:\\Temp" # 画像の保存先
max=4*pi # グラフの終わり [rad]
div=100 # 分割数
theta_a=linspace(0,pi*2,36);
hax = newplot();
h=gcf();
for i=0:div
theta_b = max .* i / div # 現在の角度
hold off # 一度全部消す
# 左の円と、右の sin 関数を描く (動かない部分)
plot(cos(theta_a) - 1, sin(theta_a), "-b", theta_a, sin(theta_a), "-r", "linewidth", .5)
# この後は全部上書き (以下は動く部分)
hold on
now_x=cos(theta_b);
now_y=sin(theta_b);
# 左の円周を回る点
# 中心から円周へ線を引く
plot([0-1, now_x-1], [0, now_y], "-g", "linewidth", 2)
# 右の sin の点
plot(theta_b, now_y, "or", "MarkerSize", 3)
# 円周を回る点から、右の sin のグラフへ線を引く
plot([now_x-1, theta_b], [now_y, now_y], "g", "linewidth", 2)
# 軸の書式設定
set(gca, 'box', "off", 'Xaxislocation', "origin", 'Yaxislocation', "origin")
set(gca, 'xtick', [0 pi/2 2*pi/2 3*pi/2 4*pi/2 5*pi/2 6*pi/2 7*pi/2 8*pi/2])
xticklabels(hax,{'_0','\pi/2','\pi','3\pi/2','2\pi','5\pi/2','3\pi','7\pi/2','4\pi'})
axis("equal",[-2 pi*4 -1 1])
# 保存
saveas (h, sprintf('temp_%03d.png', i));
endfor
ImageMagik で画像を繋げて動画にする
ImageMagik から ImageMagick-7.0.8-68-portable-Q16-x86.zip 辺りを拾ってくる。中の convert を取り出してコマンドプロンプトでトリミング & アニメーションへ変換。
※事前に gimp 等でトリミング範囲を確認しておく
> convert -crop 989x183+145+343 +repage -delay 3 -layers Optimize Z:\Temp\temp_???.png Z:\Temp\anime_opt.gif
101枚合成して 115kB に収まるって小さいですね。必要の無い部分をトリミングしたり、うまいこと圧縮してるんですね。
ループは 0~100 でなくて、1~100 にすれば良かったかな。最後 $4\pi$ までやりたかったので、気付きにくい 0 は要らなかったかも。
div=360 にして、convert で -delay 1 にすれば同程度の速度で 2度づつ描きます。(ブラウザはそんなに早く更新できないので今でも無駄ですが)
sin のグラフが一周期で終わってるのはわざとです。$2\pi$ 以降のグラフが思い描けますよね?ということで。
画像を保存するところ、
# 保存
print "-S989,193" -dpng temp.png
rename ("temp.png", sprintf('temp_%03d.png', i));
みたいにするとグラフ自体が小さくなってしまい悲しい。
本当は eps とかで保存して、適当に拡大縮小をして使うのが良いのかも。そうすると ghostview も入れないといけなくなるけど。
sin のグラフが伸びていくほうが自然かな
sin を書いていくような動画にしてみた。
clear # 変数クリア
cd 'Z:/Temp'
max=4*pi # グラフの終わり [rad]
div=360 # 分割数
theta_a=linspace(0,pi*2,36); # 動かない部分用
# theta_b は動く部分用
hax=newplot();
h=gcf();
for i=1:div
theta_b = max .* i / div # 現在の角度
hold off # 一度全部消す
# 左の円を描く (動かない部分)
plot(cos(theta_a) - 1, sin(theta_a), "-b", "linewidth", .5)
# ここからは全部上書き (以下は動く部分)
hold on
# 右の sin 関数を描く
fplot (@sin, [0, theta_b], "-r", "linewidth", .5)
now_x=cos(theta_b);
now_y=sin(theta_b);
# 左の円周を回る点
# 中心から円周へ線を引く
plot([0-1, now_x-1], [0, now_y], "-g", "linewidth", 2)
# 右の sin の点
plot(theta_b, now_y, "or", "MarkerSize", 3)
# 円周を回る点から、右の sin のグラフへ線を引く
plot([now_x-1, theta_b], [now_y, now_y], "g", "linewidth", 2)
# 軸の書式設定
grid on
set(gca, 'box', "off", 'Xaxislocation', "origin", 'Yaxislocation', "origin")
set(gca, 'xtick', [0 pi/2 2*pi/2 3*pi/2 4*pi/2 5*pi/2 6*pi/2 7*pi/2 8*pi/2])
xticklabels(hax,{'0','\pi/2','\pi','3\pi/2','2\pi','5\pi/2','3\pi','7\pi/2','4\pi'})
axis("equal",[-2.2 pi*4+0.2 -1.2 1.2])
xlabel(' \Theta [rad]')
legend({'unit circle',' sin(\Theta)'})
# 画面を更新する
refresh
print -depsc temp.eps
rename ("temp.eps", sprintf('temp_%03d.eps', i));
endfor
xlabel が X 軸からかけ離れたところにあるって、なかなか謎仕様ですね。離れてるのでアニメーション化の時に切り落とされましたとさ。
> ImageMagick-7.0.8-Q16-HDRI\magick.exe convert -density 200x200 -crop 1307x279+175+439 +repage -delay 3 temp_???.eps anime.gif
> ImageMagick-7.0.8-Q16-HDRI\magick.exe convert -layers Optimize anime.gif anime_opt.gif
> ImageMagick-7.0.8-Q16-HDRI\magick.exe convert -layers OptimizeFrame anime.gif anime_optFrame.gif
>dir *.gif
...
2019/10/15 11:16 5,860,507 anime.gif
2019/10/15 11:18 1,885,793 anime_opt.gif
2019/10/15 11:19 4,900,334 anime_optFrame.gif
anime.gif: すべての画像をそのまま繋げたもの。
anime_optFrame.gif: anime.gif から変更ある部分 (長方形の領域) のみを切り取って繋げたもの
anime_opt.gif: anime_optFrame.gif の長方形の領域の中でも変化のないドットを透明にしたもの。
IrfanView で拡大したりするとゆらゆらするけど、ブラウザで見ると大丈夫ですね。
スクロールさせてみた
clear # 変数クリア
cd 'I:/Users/All Users/Temp'
max=2*pi # グラフの終わり [rad]
div=90 # 分割数
theta_a=linspace(0,pi*2,36); # 動かない部分用
# theta_b は動く部分用
hax=newplot();
h=gcf();
for i=1:div
theta_b = max .* i / div # 現在の角度
theta_start = theta_b - pi # グラフ開始の角度
hold off # 一度全部消す
# 左の円を描く (動かない部分)
plot(cos(theta_a) + theta_start - 1, sin(theta_a), "-b", "linewidth", .5)
hold on
# Y軸っぽいの
plot([theta_start theta_start], [-1.2 1.2],'k')
# 以下は動く部分
# 右の sin 関数を描く
fplot (@sin, [theta_start, theta_b], "-r", "linewidth", .5)
now_x=cos(theta_b);
now_y=sin(theta_b);
# 左の円周を回る点
# 中心から円周へ線を引く
plot([theta_start-1, theta_start+now_x-1], [0, now_y], "-g", "linewidth", 2)
# 右の sin の点
plot(theta_b, now_y, "or", "MarkerSize", 3)
# 円周を回る点から、右の sin のグラフへ線を引く
plot([theta_start+now_x-1, theta_b], [now_y, now_y], "g", "linewidth", 2)
# 軸の書式設定
grid on
set(gca, 'box', "off", 'Xaxislocation', "origin", 'Yaxislocation', "right")
if ( theta_start < -pi/2 )
set(gca, 'xtick', [-pi/2 0 pi/2 2*pi/2 3*pi/2 4*pi/2])
xticklabels(hax,{'3\pi/2','0','\pi/2','\pi', '3\pi/2'})
elseif( theta_start < 0 )
set(gca, 'xtick', [0 pi/2 2*pi/2 3*pi/2 4*pi/2 5*pi/2])
xticklabels(hax,{'0','\pi/2','\pi','3\pi/2','2\pi','\pi/2'})
elseif( theta_start < pi/2 )
set(gca, 'xtick', [pi/2 2*pi/2 3*pi/2 4*pi/2 5*pi/2 3*pi])
xticklabels(hax,{'\pi/2','\pi','3\pi/2','2\pi','\pi/2','\pi'})
else
set(gca, 'xtick', [2*pi/2 3*pi/2 4*pi/2 5*pi/2 3*pi])
xticklabels(hax,{'\pi','3\pi/2','2\pi','\pi/2','\pi'})
endif
axis("equal",[theta_start-2.2 theta_start+max+0.2 -1.2 1.2])
xlabel(' \Theta [rad]')
legend({'unit circle',' sin(\Theta)'})
# 画面を更新する
refresh
print -depsc temp.eps
rename ("temp.eps", sprintf('temp_%03d.eps', i));
endfor
axis で、表示する範囲を移動させ、左の円も同じように移動させてみた
ImageMagick-7.0.8-Q16-HDRI\magick.exe" convert -density 200x200 -crop 1300x344+216+407 +repage -layers Optimize -delay 3 temp_???.eps anime.gif
$2\pi \rightarrow 0$ みたいに $ 5\pi/2 \rightarrow \pi/2$, $3\pi \rightarrow \pi \dots$ とかやろうかな?とおもったけど、それをするとループできないのでやめときました。