Julia で時計を作ろう

# 本日は

先日 [Interact.jl を用いて Julia で使う対話機能を持つ可視化ソフトを作ろう](https://qiita.com/SatoshiTerasaki/items/ac48b2a2cb33cd476446)
なる記事を書きました．これの続編として時計を作ってみましょう．

# 動いている様子

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

![stop_watch_by_julia.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/ade8acc9-a59c-59c6-032b-326a7f4b67e9.gif)

- 左上のボタンで何秒まで時計の針を回すかを設定します（ここでは５秒）．`Start` ボタンで時計を動かします．５秒たつと状態がリセットされ針が元の位置に戻ります．

# 書いていこう

コードを書いていきます．Jupyter notebook で書いていくと捗ります．
まずは下記のようにパッケージをロードしましょう．

```julia:パッケージのロード
using Plots
using Interact
```


## 時計の部品を作る

ボタン以外の部分，すなわち，時計の針やメモリは全て `Plots.jl` でまかなっています．`Plots.jl` グラフの描画ツールなので使用用途として適切かというと反論できないです．もっと専用のGUIツールがあれば教えてくださいまし．

`Plots.jl` で時計を作ります．

```julia:時計の部品
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` で実現できます．

```julia:ボタンたち
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")
```

加えて下記のコードを実行するとボタンが描画されます．

```julia:一時的に可視化
buttons = hbox(btn₋10, btn₋,timer, btn₊,btn₊10, btnstart)
buttons |> display
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/76f19490-0a13-4202-c89c-e873f8d25ec0.png)

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

- 注意：このロジックは改良の余地があります．`btn₋10` を数回押すと値が負になりますが ここでは `timer` の持つ値は０以上にしたいわけです．元に戻そうとすると `btn₊` or `btn₊10` に対応するボタンを少なくとも `btn₋10` の押した回数押さないといけません．改良は読者の演習問題として，ここでは気にせず先に進みます．

## Start ボタンを押したら起動するアクションを定義する

ボタンを押したら何かをするには　Interact.jl から export された `on` 関数を用いてボタンと何かをする関数を紐づけることにします．

See [Observables](https://juliagizmos.github.io/Interact.jl/latest/observables/#Functions-1)

```julia:ボタンを押したら何かをする
# 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` を定義していないので書いていきます．

```julia:実装
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` させて時計が１秒ごとにカチカチ動く様子を表現しています．
- あらかじめ指定していた `maxsec` を超えたら while ループを止めます．
- `timestatus` にループの状態を記録させてその状態に合わせて時計の描画を制御させます．ここでの制御は具体的には時計の部品を作るセクションで定義した `tick_clock` 関数を呼び出すことをさしています．

## `timestatus` の値によって時計を変化させたい

`tick_clock`関数に入力する値は `timestatus` が持っている値を用いたいです．それを実現するには 

```julia
clock=map(sec->tick_clock(sec), timestatus)
``` 

を実行して値をひもづけるようにします．このようにすると `timestatus` の値を変更する, つまり, 

```julia
timestatus[] = nanikanoatai
```

のようなコードを実行するたびに `tick_clock` がセットした値を入力引数として実行されます．

## UI のレイアウトを決めて描画

```julia
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` させることで仕上がります．お疲れ様です．

[ここに続きを書きました](https://qiita.com/SatoshiTerasaki/items/8e92c21124f926ef4b86)

# まとめ

- 時計アプリを作りました.コードはGistにあります．
- [see my gist](https://gist.github.com/terasakisatoshi/add48038dd258dbc373dc61eeea7cc65)