Interact.jl を用いて Julia で使う対話機能を持つ可視化ソフトを作ろう

# 本日は

`ipywidgets` という Python のライブラリで Jupyter notebook 上で動作させる対話ツールの構築をサポートするパッケージがあります．

例えば,Jupyter Notebookを開いて,次のようなコードを書いてみましょう

```python 
from matplotlib import pyplot as plt
import numpy as np

from ipywidgets import interact
import ipywidgets as widgets

def plot_something(a):
    xs = np.linspace(-np.pi, np.pi, 100)
    ys = np.sin(xs-a)
    plt.plot(xs, ys)
    plt.xlim(-np.pi, np.pi)

a_slider = widgets.FloatSlider(min=0.0, max=10.0, step=0.05)
interact(plot_something, a=a_slider)
```

実行結果は次のようになります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/962c3e67-fae8-a930-b6ad-8d6c1c4b6187.png)

グラフの描画の上にスライダーが見えます．これをマウスで掴んでグリグリ動かすとそれに応じて三角関数が左右に動くことがわかります．グリグリ動かすことは `plot_something` 関数の引数にある `a` を変化させることと等価です．グリグリさせる対象とグリグリするパラメータを `interact` に投げればお手軽に対話的ツールが作成できるわけです．

# Julia ではどうでしょう？

結論から言うとできます．本日は [Interact.jl](https://github.com/JuliaGizmos/Interact.jl) を用いて同じようなことをやってみましょう．数ヶ月前は Jupyter で動かない，インストールできないとか色々不満がありましたが，今はとても快適です．

# Install

`Interact.jl` は Julia の公式パッケージとして登録されているのでパッケージモードで `add` するだけです

```julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.3.0-alpha.unknown (2019-07-24)
 _/ |\__'_|_|_|\__'_|  |  Commit a8a567e (0 days old master)
|__/                   |

(v1.3) pkg> add Interact.jl
```

基本これだけです．Python で jupyter 入れたけれど IJulia 入れていない場合は下記の手順で　Julia のカーネルを登録することができます．

```julia
julia> ENV["JUPYTER"]=Sys.which("jupyter")
(v1.3) pkg> add IJulia
julia> using IJulia
julia> notebook()
# いつも見慣れたjupyter notebook が起動する
```

`New` notebook を作成するボタンに Julia バージョン番号 と言う形で出現すれば成功です．写真では 1.3.0-alpha と　Julia 1.1.1が混ざっていますが，読者はおそらく Julia 1.1.1 が出ていると思います．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/24e6b881-1f5f-5901-c417-9ee6386e200d.png)

# コードを書く

普段 Python で notebook を触っている方は Python の代わりに Julia を使うの違いしかありません Shift+Enter でどんどん実行させることができます．冒頭で述べた Python とだいたい同じようなことができるコードを書いてみましょう．

ここでは, 描画には `Plots.jl` を用います `PyPlot.jl` や `Makie.jl` でも良いですがグリグリ動かす場合はレスポンスが素早い方がいいので `Plots.jl` を選ぶことにします. `Makie.jl` の場合はそれ自身が対話機能を保持しているのでそちらを用いた方がヌルヌル動きます．
　
## 静的なコードを書く

ヌルヌル動かすための対象を書きましょう．例えばこんな感じ

```julia
using Plots
function f(a)
    xs = -pi:0.01:pi
    ys = sin.(xs .- a)
    plot(xs, ys)
end
```

## パーツ作り
`a` をパラメータとして動かしたいのでこれを動かすように実装します．

まずは Widget という我々が作りたいGUIのパーツを作ります．ここでは slider を作りましょう

```julia
using Interact
a_slider=slider(-3:0.1:3,value=0.2, label="a_slider")
```

上記のコードを書くと次のような部品が出るでしょう．

左には `label` で指定した文字が, 右側には `value` で指定した 0.2　が見えることがわかります．

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/9af56900-b258-bf5f-8553-d8bac0a90455.png)

## グリグリさせたら何をさせるか

それではスライダーを動かしたらそれに応じて何かをする動作を結びつけましょう．

```julia
p=map(a->f(a), observe(a_slider))
```

`map` の第一引数にはスライダーを動かした時にする動作を書きます．その右隣には部品を `observe` させるようにします．

これで `a_slider` を動かすとその時の value に応じて `f` が動くようになります．

## これらを可視化しよう

```julia
map(display,[a_slider, p])
```

各々のオブジェクトを部品とみて `display` させます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/4b27ae1b-6105-6a3b-03a7-8d6ec336a398.png)

そうすると上の図のような結果が得られると思います．

稀にうまくいかない場合がありますがその場合は標準の `WebIO` ライブラリをパッケージモードで `build WebIO` させたり `IJulia` や `Interact.jl` なども `build` させた方が良いでしょう.

# もう少し複雑な例

```julia
using Interact
using Plots
using Rotations

p=ones(3)/sqrt(3)
scatter3d(
    [p[1]],[p[2]],[p[3]],
    xlabel="x",ylabel="y",zlabel="z",
    color=:blue
)

function plot_rotated_pts(x_angle,y_angle,z_angle)
    xyz_rad = deg2rad.([x_angle,y_angle,z_angle])
    r=RotXYZ(xyz_rad...)
    q=r*p
    scatter3d!([q[1]],[q[2]],[q[3]],color=:green,legend=false)
    xlims!((-1.0,1.0)); ylims!((-1.0,1.0)); zlims!((-1.0,1.0))
end

x_axis=slider(-180:180, label="x_axis")
y_axis=slider(-180:180, label="y_axis")
z_axis=slider(-180:180, label="z_axis")

angle_plot=map(
    (x_deg,y_deg,z_deg)->plot_rotated_pts(x_deg,y_deg,z_deg),
    map(observe, [x_axis,y_axis,z_axis])...,
)
    #observe(x_axis),observe(y_axis),observe(z_axis)

map(display,[x_axis, y_axis, z_axis, angle_plot]);
```

パラメータが複数ある場合も同様にできます．パラメータを動かした時に何をさせるかを決める `map` に複数の引数を入力とする関数を設定すれば良いです．`observe` も各々のスライダーに作用させればOKです

```julia
observe(x_axis),observe(y_axis),observe(z_axis)
```

繰り返しがイヤな場合は map でそして `...` splat させます.

```julia
map(observe, [x_axis,y_axis,z_axis])...
```

`...` も Julia の機能ですヘルプモードですので，Juliaさんに訪ねてみましょう．

```julia
help?> ...
search:

  ...

  The "splat" operator, ..., represents a sequence of arguments. ... can be
  used in function definitions, to indicate that the function accepts an
  arbitrary number of arguments. ... can also be used to apply a function to a
  sequence of arguments.

  Examples
  ≡≡≡≡≡≡≡≡≡≡

  julia> add(xs...) = reduce(+, xs)
  add (generic function with 1 method)
  
  julia> add(1, 2, 3, 4, 5)
  15
  
  julia> add([1, 2, 3]...)
  6
  
  julia> add(7, 1:100..., 1000:1100...)
  111107

```

実行結果は下図の通りです．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/fe599d38-9384-7df5-b9d9-bcc4cfb07cfe.png)

グリグリ動かしてみましょう．
今日はこんな感じで終わります．

# さらに興味のある読者へ

[JuliaCon 2019 | Julia + JavaScript = ❤️](https://www.youtube.com/watch?v=A3_vpuFqb-0)