Makieなんもわからん.jl

Makie.jl
がわからないので学ぶ。
できるだけPluto.jl
上で実行する。

とりあえず参考にするのは公式サイト

公式サイトのトップページを見ると「つべこべ言わずにTutorialをやってExampleをやれと言っているように見えるので、Basic Tutorialから読む。

バックエンドごとに、CairoMakie.jl
とGLMakie.jl
とWGLMakie
に分かれている。
CairoMakie
は非インタラクティブなプロットしかできない。一方で、GLMakie
とWGLMakie
でならインタラクティブなプロットを作れるらしい。GLMakie
は別のウィンドウを立ち上げるっぽい?対してWGLMakie
はhtmlをレンダリングできる環境に直接グラフを描画してくれるっぽい?Plutoでグリグリ動かせるグラフがほしければWGLMakie
が良さそう。

using CairoMakie
でパッケージを使う。
折れ線グラフはlines
で散布図はscatter
begin
x = range(0, 10, length = 100)
y = sin.(x)
end
# 折れ線グラフ
lines(x, y)
# 散布図
scatter(x, y)
この辺はpyplot
とあまり変わらなさそう。

scatter
やlines
のような関数をMakie
のドキュメントではplotting functionというっぽい。
Makie
のplotting functionは全部!
がつくバージョンがあるらしい。!
がない方は新しくグラフを描画するのに対して、!
がある方はすでに存在するグラフを書き換える。Plots.jl
のplot
とplot!
の関係に似てる気がする。
y2 = cos.(x)
begin
# 1本目
lines(x, y)
# 2本目
lines!(x, y2)
# グラフの表示
current_figure()
end

自動でlegendを表示してくれるような優しさはなさそう

lines
の返り値の型を確認してみたところ、FigureAxisPlot
だったが、lines!
の返り値の型はLines{Tuple{Vector{Point{2, Float32}}}}
だった。グラフとして描画されるのはFigure
, FigureAxisPlot
, Scene
などといった型だけらしいので、lines!
のあとにグラフを描画するにはcurrent_figure()
が必要。ちなみにcurrent_figure
の返り値の型はFigure
だった。

どのplotting functionも、キーワード引数で指定できる属性がある。
plotting function によって使えるキーワード引数は異なる。
lines(x, y, color = :red)
# `markersize`は`lines`にはない
scatter(x, y, color = :red, markersize = 5)
scatter!
やlines!
などのようにPlotオブジェクトを返す関数の場合、plot.attr = val
のようにして後から属性の値を変えることができる。
let
lines(x, y, color = :red)
l = lines!(x, y2, color = :blue)
l.color = :green
current_figure()
end

ところで、どこを見ればプロットオブジェクトの属性を調べることができるのだろう?API referenceには書かれていないようなので困る。

color
やmarkersize
などの一部の属性は配列で渡せる。
scatter(x, y,
markersize = range(5, 15, length = 100),
color = repeat([:crimson, :dodgeblue, :slateblue1, :sienna1, :orchid1], 20),
)
colormap
と組み合わせたときは、color
の値は0から1までの実数をとれる
lines(x, y, color = range(0, 1, length = 100), colormap = :thermal)
ちなみに配列で属性を指定するときは、データ点の数と属性配列の要素数が一致しないとAssertionError
が出る。

:red
や:blue
のような名前つきの色は、Colors.jl
で定義されている。したがって、使える色が知りたいときは、Colors.jl
のドキュメントのNamed Colorsのページを参照するとよい。
colormapに指定できる値とそのときどんな色になるかの一覧はMakieの公式サイトのColormapsの項で説明されている。

疑問点
- colormapがカテゴリカルな場合でも、colorは0から1でないといけないのか?
- colormapのほとんどが
ColorScheme.jl
で定義されているように見えるけど、逆にMakieのドキュメントに載っていないcolormapでもColorSchemes.jl
で定義されていれば使えるのか?

そもそもcolor
の引数は数値の配列か色に変換できるデータでないといけない。
colormap
がtab10
などのようなカテゴリカルなものでもそれは同じ
color
の配列は0から1でなくてもよくて、最大値と最小値がcolormap
の端点に自動的にマッピングされる。マッピングの対応関係を変更するには、colorrange
を指定する。

Makieのドキュメントに載っていないcolormapも使えた。そういうわけで、colormapはColorSchemes.jl
のドキュメントのCatalogue of ColorSchemesのページを参照したほうが良さそう

label
属性を指定すれば、axislegend
関数を使ってlegendを表示できる
begin
lines(x, y, label = "sin")
lines!(x, y2, label = "cos")
axislegend()
current_figure()
end
axislegend()
の返り値はLegend
型なので、current_figure()
が必要

subplotを作るにはFigure
オブジェクトを作る必要がある。
Figure
はいわゆるキャンバスみたいなもので、多分matplotlibでのpyplot.figure
にあたるんだろうなぁと思う。
fig = Figure()
とすると、plot_func(fig[i,j], x, y)
でfig
のi
行j
列にsubplotを描画できる。
let
fig = Figure()
lines(fig[1, 1], x, y)
lines(fig[1,2], x, y)
lines(fig[2,1:2], x, y)
current_figure()
end
fig[1, 1:2]
みたいにインデックスにrange
が使えるのがとても便利。
ちなみにFigure()
はFigure
型、fig[i,j]
はGridPosition
型、lines(fig[i,j],x,y)
はAxisPlot
型になる。

それぞれのsubplotが描画される領域のことをAxis
と呼んでいるっぽい。これもちょっとmatplotlib
と似ててわかりやすい。
Axis(fig[i,j])
とするとFigure
のi
行j
列にAxis
を追加できるみたい。こうして作ったAxis
にプロットを追加するにはplot_func!(ax, x, y)
とする。!
がつくことに注意。
let
fig = Figure()
ax = Axis(fig[1,1])
lines!(ax, x, y)
fig
end
Axis()
の返り値はAxis
だし、lines!(ax, x, y)
の返り値はlines!(x, y)
の返り値と変わらないっぽい。

lines(x, y)
だけでなく、lines(x, func)
でもプロットが描画できる。
x
にはIntervalSets.jl
の区間を使うこともできる。
begin
lines(x, y)
lines!(x, cos)
lines!(0..10, sqrt)
current_figure()
end

Axis
やLegend
はLayoutable
の具体型らしい。Layoutable
なオブジェクトは基本的にFigure
上の任意の位置に配置することができる。
supertype(Legend) == supertype(Axis) == Makie.MakieLayout.Layoutable # => true
したがって、Legend
はAxis
と同様に作成して配置することができる。
let
fig = Figure()
ax1 = Axis(fig[1,1])
l1 = lines!(ax1, 0..10, sin)
ax2 = Axis(fig[2,1])
l2 = lines!(ax2, 0..10, cos)
Legend(fig[1:2,2], [l1,l2], ["sin", "cos"])
fig
end

Colorbar
もLayoutable
の一つなので、Axis
と同じようにFigure
上に配置できる。
let
fig = Figure()
ax = Axis(fig[1,1])
hm = heatmap!(ax, randn(20,20))
Colorbar(fig[1,2], hm)
fig
end

FigureAxisPlot
やAxisPlot
にはiterate
メソッドが実装されているので、destructできる。
FigureAxisPlot
はFigure
, Axis
, Plot
オブジェクトの3つにdestructできる。
let
# heatmapの返り値はFigureAxisPlot
fig, _, hm = heatmap(randn(20, 20), colormap = :thermal)
Colorbar(fig[1,2], hm)
fig
end
AxisPlot
はAxis
とPlot
にdestructできる。
let
fig = Figure()
# linesの返り値はAxisPlot
_, l1 = lines(fig[1,1], 0..10, sin, color = :red)
ax, l2 = lines(fig[2,1], 0..10, cos, color = :blue)
l3 = lines!(ax, 0..10, sqrt, color = :green)
Legend(fig[1:2, 2], [l1, l2, l3], ["sin", "cos", "sqrt"])
fig
end

plotと同じように、Figure
やAxis
にもキーワード引数で指定できる属性がいくつもある。しかし、plotとは異なり、これらの属性をhelp
を通して知ることができない。Axis
の属性については、これを書いている現在はソースコードを読めばある程度把握できる。

しかし、Figure
のキーワード引数については、内部のデータ構造に次々に渡されていくため、どこか1箇所にまとまっているということはなく、結果的に何を指定できるかは全くわからない。バージョンアップを待とう。

Figure
やLayoutable
の属性はこんな感じで指定できる。
let
fig = Figure(backgroundcolor = :ivory1)
ax = Axis(fig[1,1], aspect = 1, xlabel = "x axis", ylabel = "y axis")
hm = heatmap!(ax, randn(20, 20))
Colorbar(fig[1,2], hm, label = "colorbar")
fig
end
plotfunc
がAxisPlot
を返す場合は、キーワード引数にaxis
が含まれる。FigureAxisPlot
のときはfigure
も含まれる。
let
fig, _, hm = heatmap(randn(20, 20);
figure = (backgroundcolor = :ivory1,),
axis = (aspect = 1, xlabel = "x axis", ylabel = "y axis"))
Colorbar(fig[1,2], hm, label = "colorbar")
fig
end
figure
やaxis
の引数はNamedTuple
であることに注意。特に、属性が1つだけのときは、(backgroundcolor = :ivory1,)
のように、後ろにカンマを入れるか、(; backgroundcolor = :ivory1)
のように、頭にセミコロンを入れるかしないといけない。

plotfuncはここを見ればだいたいわかる

多分だけどバージョン1.0まではちょっとずつ増えると思う

わからん原因、Makie.jl
にあるのではなく、GeometryBasics.jl
とObservables.jl
にある気がしてきた。

とりあえずObservables.jl
に関する勉強メモ

GeometryBasics.jl
に関する勉強メモ

新しいノートブックでLayout Tutorialを読む

Layout Tutorialの主な目的は、以下のグラフの作成を通じて、Makieで複雑なレイアウトのグラフを作成する方法を習得することにありそう。

まずは、全部のグラフを配置するおおもとのFigureを作成する
using CairoMakie
# font
noto_sans = assetpath("fonts", "NotoSans-Regular.ttf")
noto_sans_bold = assetpath("fonts", "NotoSans-Bold.ttf")
f = Figure(;
backgroundcolor = RGBf(.98, .98, .98),
resolution = (1000, 700),
font = noto_sans,
)

assetpath
はMakie/assets
のデータにアクセスするための関数。assets
の内容はgithubを見ればわかる。

RGBf
については大したドキュメントがないけれど、多分ColorTypes.jl
のRGB{Float32}
のエイリアスだと思う。Point2f
がGeometryBasics.Point{2, Float32}
なので、その類推で、Makie
でfがつけばFloat32
なのだろうと考えられる。
基本的な使い方はRGBf(r,g,b)
なのだが、r
,g
,b
はそれぞれ0から1までの実数でなければならない(なおかつ、多分Float32にキャストできないといけない)
引数はマイナスでもエラーにはならないが、0として扱われる。同様に、1を超える値は1として扱われる

Figureを4つのパネルに分割する。GridLayout
というのを使うらしい。
この場合は、Figuref
の1列目に、ga
(上段)とgb
(下段)を割り当て、2列目にはgcd
を割り当てている。gc
とgd
はgcd
の中にネストしている。
Tableauでダッシュボード作ったことがあるので、こういうネストしたレイアウトが作れるのは同じ発想が生かせてうれしい。
begin
ga = f[1,1] = GridLayout()
gb = f[2,1] = GridLayout()
gcd = f[1:2, 2] = GridLayout()
gc = gcd[1,1] = GridLayout()
gd = gcd[2,1] = GridLayout()
end

GridLayout
についてはこのページが詳しそう。でも、多分ここはLayout Tutorialを終えてから読むべきところな気がする。

Panel Aからつくる。Axis
を配置してまずはグラフを配置する。
axtop = Axis(ga[1,1])
axmain = Axis(ga[2,1], xlabel = "before", ylabel = "after")
axright = Axis(ga[2,2])
labels = ["treatment", "placebo", "control"]
data = randn(3, 100, 2) .+ [1, 3, 5]
for (label, col) in zip(labels, eachslice(data, dims = 1))
scatter!(axmain, col, label = label)
density!(axtop, col[:, 1])
density!(axright, col[:, 2], direction = :y)
end
f

data
は3次元の多次元配列で、(3×100×2)の形状になっている。
eachslice(data, dims = 1)
によって、行ごとのイテレータができる。このイテレータは、(100×2)の行列をイテレーションごとに返すと考えていい。

scatter(mat)
の形でプロットするとき、基本的には、mat
の各 列 が点であると認識される。例えば、(3×2)の行列だったら、2次元の点が3個ではなく、3次元の点が2個として認識される。
しかし、(n×2)や(n×3)で、nが4以上であれば、自動的に各 行 が点として認識されるように切り替わる。したがって、本来はscatter!(axmain, col')
と書く必要があるところを、col
が100行あるので、scatter(axmain, col)
のように書くことができる。

density
, density!
はカーネル密度推定をしたプロットをつくる。
えっ、外部パッケージなしでカーネル密度推定を!?
できらぁ!!
実際、Plots.jl
ではStatsPlots
が必要だし、matplotlib
ではseaborn
が必要になるカーネル密度推定を、Makie
なら単一パッケージでできるのでチョットだけうれしい。