6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Julia で時計を作ろう

Last updated at Posted at 2019-07-27

本日は

先日 Interact.jl を用いて Julia で使う対話機能を持つ可視化ソフトを作ろう
なる記事を書きました.これの続編として時計を作ってみましょう.

動いている様子

この記事で出来上がるアプリの様子は大まかに下のようになります.

stop_watch_by_julia.gif

  • 左上のボタンで何秒まで時計の針を回すかを設定します(ここでは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

image.png

  • timer はタイマーの初期値を描画するラベルを提供します.ボタンを押した時にその機能に応じて値を変化させることができます.変化のロジックば Interact.@map begin ... end で制御することができます.& を前に置いておくことででそのUIの部品が持っている値を取得させることができるようです.button の場合はそのボタンを押した回数をIntで持っています.その値を利用して +10 のボタンを押すと値が 10 増えるようにしています.

  • 注意:このロジックは改良の余地があります.btn₋10 を数回押すと値が負になりますが ここでは timer の持つ値は0以上にしたいわけです.元に戻そうとすると btn₊ or btn₊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
6
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?