本日は
先日 Interact.jl を用いて Julia で使う対話機能を持つ可視化ソフトを作ろう
なる記事を書きました.これの続編として時計を作ってみましょう.
動いている様子
この記事で出来上がるアプリの様子は大まかに下のようになります.
- 左上のボタンで何秒まで時計の針を回すかを設定します(ここでは5秒).
Start
ボタンで時計を動かします.5秒たつと状態がリセットされ針が元の位置に戻ります.
書いていこう
コードを書いていきます.Jupyter notebook で書いていくと捗ります.
まずは下記のようにパッケージをロードしましょう.
using Plots
using Interact
時計の部品を作る
ボタン以外の部分,すなわち,時計の針やメモリは全て Plots.jl
でまかなっています.Plots.jl
グラフの描画ツールなので使用用途として適切かというと反論できないです.もっと専用のGUIツールがあれば教えてくださいまし.
Plots.jl
で時計を作ります.
function tick_clock(sec)
rad = sec * 2pi / 60
r = mod(rad, 2pi)
r = pi / 2 - r
# draw needle
q = quiver([0], [0],
quiver = ([cos(r)], [sin(r)]),
color = :black,
linewidth = 4,
linealpha = 0.5)
# draw tick
for k in 0:59
offset = 15
t = k - offset
if mod(k, 5) == 0
b, e = 0.9, 1.1
plot!([b * cos(-t * 2pi / 60), e * cos(-t * 2pi / 60)],
[b * sin(-t * 2pi / 60), e * sin(-t * 2pi / 60)],
color = :black)
annotate!([(1.2 * cos(-t * 2pi / 60), 1.2 * sin(-t * 2pi / 60), "$k")])
else
b, e = 0.95, 1.05
plot!([b * cos(-t * 2pi / 60), e * cos(-t * 2pi / 60)],
[b * sin(-t * 2pi / 60), e * sin(-t * 2pi / 60)],
color = :black)
end
end
# adjust style
plot!(xlim = (-1.1, 1.1), ylim = (-1.1, 1.5),
aspect_ratio = :equal,
leg = false,
showaxis = false,
grid = false,
title = "$(sec) [s]")
end
-
tick_clock
関数に整数型のsec
を入れるとsec
秒目を刻む時計を作ることができます.時計の針はquiver
で実現しています(# draw needle
). - 時計の秒を指すメモリの実現では円の角度に応じて長くしたり短くしています(
# draw tick
).円グラフの pi char とか使えばこの辺はもっとスマートに作れるんじゃないかなとは思いますが強引にプロットする方策をとりました. - 文字を挿入する場合は
annotate
を用いることで実現します. -
# adjust style
のブロックでは描画範囲を各々の時刻で統一させるために設定しています.これをしておかないと時計が綺麗に描画できないです.
ボタンを作ろう
ボタンをポチッと押したら動作するようなアプリを作りたいのでボタンを作ります.また,タイマーとして設定する初期値を変更するボタンも欲しいです.Interact.jl
から export されている button
で実現できます.
btn₊ = button("+")
btn₋ = button("-")
btn₊10 = button("+10")
btn₋10 = button("-10")
const initval = 5
timer = Interact.@map begin
&btn₊, &btn₋
s = initval + 10*&btn₊10 + &btn₊ - &btn₋ - 10*&btn₋10
# do not allow set `s` to negative value.
s = max(s, 0)
Widgets.wdglabel("$(s)")
end
btnstart = button("Start")
加えて下記のコードを実行するとボタンが描画されます.
buttons = hbox(btn₋10, btn₋,timer, btn₊,btn₊10, btnstart)
buttons |> display
-
timer
はタイマーの初期値を描画するラベルを提供します.ボタンを押した時にその機能に応じて値を変化させることができます.変化のロジックばInteract.@map begin ... end
で制御することができます.&
を前に置いておくことででそのUIの部品が持っている値を取得させることができるようです.button
の場合はそのボタンを押した回数をIntで持っています.その値を利用して+10
のボタンを押すと値が 10 増えるようにしています. -
注意:このロジックは改良の余地があります.
btn₋10
を数回押すと値が負になりますが ここではtimer
の持つ値は0以上にしたいわけです.元に戻そうとするとbtn₊
orbtn₊10
に対応するボタンを少なくともbtn₋10
の押した回数押さないといけません.改良は読者の演習問題として,ここでは気にせず先に進みます.
Start ボタンを押したら起動するアクションを定義する
ボタンを押したら何かをするには Interact.jl から export された on
関数を用いてボタンと何かをする関数を紐づけることにします.
See Observables
# btnstart と timer はすでに上で定義した
function dosomething(t)
maxtime = parse(Int, t.val.children[1])
# run_clock 関数はあとで定義する
run_clock(maxtime)
end
on(_ -> dosomething(timer), btnstart)
-
_ -> dosomething(timer)
がbtnstart
に対応するボタン(ここではStart
と書かれているボタン)を押した時に実行される動作になります._
はbtnstartが持っている値ですがここでは使わないので_
と適当に書いています. -
maxtime = parse(Int, t.val.children[1])
はちょっとカッコ悪いですが,wdglabel に格納されている値がString
で持っているのでこれを Int に変換させます.下記で使う Observable を使うとwdglabel
の値をひもづけられそうな気はしますが・・・.よくわかってないです.
run_clock
関数の実装
dosomething
関数の実装の詳細を記述する run_clock
を定義していないので書いていきます.
timestatus = Observable(0)
function run_clock(maxsec)
init_state = 0
interval = 1
count = init_state
while true
sleep(interval)
timestatus[] = count
count += interval
if count > maxsec
break
end
end
# reset
sleep(interval)
timestatus[] = init_state
end
- ループを回るたびに
sleep
させて時計が1秒ごとにカチカチ動く様子を表現しています. - あらかじめ指定していた
maxsec
を超えたら while ループを止めます. -
timestatus
にループの状態を記録させてその状態に合わせて時計の描画を制御させます.ここでの制御は具体的には時計の部品を作るセクションで定義したtick_clock
関数を呼び出すことをさしています.
timestatus
の値によって時計を変化させたい
tick_clock
関数に入力する値は timestatus
が持っている値を用いたいです.それを実現するには
clock=map(sec->tick_clock(sec), timestatus)
を実行して値をひもづけるようにします.このようにすると timestatus
の値を変更する, つまり,
timestatus[] = nanikanoatai
のようなコードを実行するたびに tick_clock
がセットした値を入力引数として実行されます.
UI のレイアウトを決めて描画
buttons = hbox(btn₋10, btn₋, timer, btn₊, btn₊10, btnstart)
clock = map(sec->tick_clock(sec), timestatus)
ui = vbox(buttons,clock)
display(ui)
# ui |> display と書いても良い
ボタンを横に並べて時計をその下に置くレイアウトにします.
hbox
で横に並べて vbox
で縦に配置するようにします.
最後は display
させることで仕上がります.お疲れ様です.
まとめ
- 時計アプリを作りました.コードはGistにあります.
- see my gist