Makie.jl を用いた対話的プロットのサンプル

# 本日は

Julia のグラフ描画ライブラリ Makie.jl の対話的プロットのサンプルを作りましたので公開します。
例えば、グラフのパラメータをスライダーで動かしてその様子を逐次描画させることができます。

Makie.jlのリポジトリ https://github.com/JuliaPlots/Makie.jl を見る３Dの描画やアニメーションの生成などが可能ですが汎用的なサンプルが少ないので、このサンプルをどう自分のタスクに活かすかがピンとこないものが多いです。

ここではパラメータを二つもつ関数を描画し、それらのパラメータを動かすことでグラフが変化することを確認できるサンプルを作りました。 ここではみんな大好き正規分布を例に出しました。


# 静的なサンプル

ひとまず $\mu=0$, $\sigma=1$ の場合の正規分布を描画できるようになりましょう。

下記のコードをREPLに貼り付けてみましょう。

```julia
using Makie
using Distributions: Normal, pdf

function main()
    μ = 0
    σ = 1
    d = Normal(μ, σ)
    xs = -3.:0.01:3.
    ys = pdf.(d, xs)
    lines(xs, ys)
end

main()
```

結果は下記の通りになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/82639ce5-96fe-fddf-4a97-3be415bc4175.png)

お山さんが描けました。`lines` には x座標の点とそれに対応するy座標の配列を渡してもできますが `Point2f0(x, y)` を要素とする配列を渡すこともできます。`main()` を下記のように変更しても良いです。

```julia
function main()
    μ = 0
    σ = 1
    d = Normal(μ, σ)
    xs = -3.:0.01:3.
    lines([Point2f0(x, pdf(d, x)) for x in xs])
end
```

Python での list の内包表記は `map` と [do block syntax](https://docs.julialang.org/en/v1/manual/functions/index.html#Do-Block-Syntax-for-Function-Arguments-1) を用いても書くことができます。

```julia
pos = map(LinRange(-3., 3., 200) do x
    Point2f0(x, pdf(d, x))
end
lines(pos)
```

Makie.jl のサンプルコードを見ると `map(配列） do 何かをする処理 end` のシンタックスがよく出てくるのですが、 これを知っておくと Makie.jl のコードが読みやすくなります。 また [LinRange](https://docs.julialang.org/en/v1/base/collections/#Base.LinRange) も Base にて定義されている Julia の型です.

# 動的に動作するサンプル

$\mu$ および $\sigma$ を動かした時に得られる関数の形を対話的に得るためにはスライダーを導入します。

## とりあえず μ だけ動かす

$\mu$ だけ動かしてみます。

```julia
using Makie
using AbstractPlotting: textslider
using Distributions: Normal, pdf
function main()
    s_μ, obs_μ = textslider(range(-1., stop=1., length=101), "μ", start=0)
    σ = 1
    pos = lift(obs_μ) do μ
            d = Normal(μ, σ)
            [Point2f0(x, pdf(d, x)) for x in -5.:0.02:5.]
        end
    p = lines(pos)
    RecordEvents(
        hbox(p, vbox(s_μ), parent=Scene()),
        "output"
    )
end
```

textslider を用意して $\mu$ に関するスライダーを動かした時にどういう配列を生成するかという処理を書きます.
`pos = lift(obs_μ) do μ 処理 end` のブロックは次のように書くのと同じです

```julia
    pos = lift(μ -> begin
                         d = Normal(μ, σ)
                         [Point2f0(x, pdf(d, x)) for x in -5.:0.02:5.]
                    end,
                obs_μ
          )
```

このスタイルは Makie.jl のドキュメントにある [Animation using time](http://juliaplots.org/MakieGallery.jl/stable/interaction.html#Animation-using-time-1) のそれと同じです。チュートリアルがこちらの形式で、サンプルが do ブロックで書かれているのでわかりにくいです・・・。 この場合は do ブロックで書いた方がbegin end ブロックで関数の処理を書く方法より好まれると思います。

結果は下記のようになります

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/975c19d7-3bd7-e4ad-5dd0-b8c5def73ac4.png)

## σ も動かす

$\sigma$ の方も動かしていきましょう。

```julia
using Makie
using AbstractPlotting: textslider
using Distributions: Normal, pdf


function main()
    s_μ, obs_μ = textslider(range(-1., stop=1., length=101), "μ", start=0)
    s_σ, obs_σ = textslider(range(0.1, stop=5.1, length=101), "σ", start=1)
    
    params = lift(tuple, obs_μ, obs_σ)
    pos = lift(params) do (μ, σ)
        d = Normal(μ, σ)
        map(LinRange(-5, 5, 100)) do x
            Point2f0(x, pdf(d, x))
        end
    end

    p = lines(pos)

    RecordEvents(
        hbox(
            p, 
            vbox(s_μ, s_σ), 
            parent=Scene()
        ),
        "output"
    )

end

main()
```

`lift do` ブロックの部分は

```julia
    pos = lift((μ, σ) -> begin
                            d = Normal(μ, σ)
                            [Point2f0(x, pdf(d, x)) 
                                for x in -5.:0.02:5.]
                         end,
               obs_μ, obs_σ)
```

と同じです。パラメータが複数の場合は処理の引数と `obs_ｘ` をただならべればよいですが do block syntax にする時に `lift((obs_μ, obs_σ)) do (μ, σ) 処理 end` だとダメで 

```julia
pos = lift(lift(tuple, obs_μ, obs_σ)) do (μ, σ)
            d = Normal(μ, σ)
            [Point2f0(x, pdf(d, x)) for x in -5.:0.02:5.]
        end
```

のように　`lift(tuple, ...)` を挿入しないといけないようです. 
c.f. [OrbitDiagram](https://simondanisch.github.io/ReferenceImages/gallery//orbit_diagram/index.html)

複数のオブジェクトがパラメータとして扱う場合は一捻り必要みたいですが、ここではこういうものだという理解で留めておきます。

# 動作の様子
![gaussian.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/135978/0adce0e6-800a-6581-e4a5-1b90e025f00f.gif)



# 3D プロットの例

xy-平面に　$z=(a\tanh(x) + b\tanh(y) + c)^2$ という $a, b, c$ をパラメータとする関数を描画してみます。


```julia

using Makie
using AbstractPlotting: textslider

function target_function(x, y, a, b, c)
        (a*tanh(x) + b*tanh(y) + c)^2
end

function plotsome(z::Array{Float32,2}, a, b, c)
    lspace = range(-1., stop=1., step=0.05)
    z = Float32[target_function(x, y, a, b, c) for x in lspace, y in lspace]
    z
end

function main()
    lspace = range(-1., stop=1., step=0.05)
    N = length(collect(lspace))
    s_a, obs_a = textslider(lspace, "a", start=0.1)
    s_b, obs_b = textslider(lspace, "b", start=0.1)
    s_c, obs_c = textslider(lspace, "c", start=0.1)
    args_n = (obs_a, obs_b, obs_c)
    update_rule = plotsome(zeros(Float32, (N, N)), to_value.(args_n)...)
    positions = lift(plotsome, Node(update_rule), args_n...)

    sur = surface(lspace, lspace, positions)
    axis = sur[Axis]
    axis[:names][:axisnames] = ("x", "y", "(a*tanh(x)+b*tanh(y)+c)^2")

    RecordEvents(
        hbox(
            sur, 
            vbox(s_a, s_b, s_c), 
            parent=Scene()
        ),
        "output"
    )

end

main()
```

この場合、$a, b, c$ のスライダーが出ます。