このドキュメントでは、HTMLドキュメントに rgl
のシーンを埋め込み、埋め込まれたJavascriptを使って HTML ドキュメント内の WebGL 表示を制御する方法を説明します。 rgl
についてのより一般的な情報は、rgl Overview を参照してください。
HTML ドキュメントは、knitr
や rmarkdown
を使った R の Markdown ソースから生成されることを想定しています。 このフォーマットでは、テキストと Markdown マークアップと R コードのチャンクが混在しています。 他の方法についての議論は限られています。
ドキュメントの中に rgl
のシーンを埋め込むには2つの方法があります。 推奨されるのは、rglwidget
を呼び出して、印刷することでドキュメントに埋め込むことができる「ウィジェット」を生成する方法です。
rgl
バージョン0.102.0以前に使われていた古い方法(例えば、writeWebGL
)はサポートされなくなりました。
私は3つ目の方法について実験を行いました。 これは、knitr
で標準的な2Dグラフィックスをインクルードする方法に似たものを目指しています。 つまり、あなたが何かを描いたという事実を検知して、それを自動的にインクルードします。 現在のところ、この方法は推奨されていませんが、将来的には変更される可能性があります。
現在、ほとんどのブラウザがWebGLをサポートしていますが、ブラウザによってはデフォルトで無効になっているものもあります。 様々なブラウザのヘルプについては、https://get.webgl.org をご覧ください。
まずは、虹彩データの簡単なプロットから始めます。 コードチャンクを挿入し、オプションの引数 elementId
で rglwidget
関数を呼び出します。 これにより、後の Javascript コードで画像を参照することができます。 また、プロットからオブジェクトIDを保存し、後で操作できるようにしています。
library(rgl)
plotids <- with(iris, plot3d(Sepal.Length, Sepal.Width, Petal.Length,
type="s", col=as.numeric(Species)))
rglwidget(elementId = "plot3drgl")
次に、データの表示を切り替えるためのボタンを挿入します。
sceneId
は rglwidget()
で使用した elementId
と同じで、ids
はトグルさせたいオブジェクトのオブジェクト ID、label
はボタンに表示されるラベルです。 変数 plotids
の中の名前を調べるには、names()
または unclass()
を適用します。:
## [1] "data" "axes" "xlab" "ylab" "zlab"
## data axes xlab ylab zlab
## 13 14 15 16 17
magrittr
やベースパイプの使用rglwidget()
内の elementId
を toggleWidget()
(または後述の playwidget()
) 内の sceneId
と一致させるのはエラーになりやすいです。 両方が一緒に表示されるような通常のケースでは、magrittr
スタイルのパイプを非常に柔軟に使用することができます。 コントロールウィジェットの第一引数は、rglwidget()
の結果(または他のコントロールウィジェット)を受け入れ、rglwidget()
の controllers
引数は、コントロールウィジェットを受け入れます。 R 4.1.0 では、新しいベースパイプ演算子 |>
も同じように使用できるはずです。
例えば、以下のようになります。
R 4.1.0以上をお持ちの方は、こちらも同様です。:
ボタンとシーンの順番を入れ替えることができます。magrittr
のドット(またはbase pipesの=>
構文)を使って、toggleWidget
を controllers
の引数で rglwidget
に渡します。:
または、R 4.1.0 以降を使って
toggleWidget
を使ってプロットの内容を変更する方法を見てきました。 もっと精巧な表示をすることもできます。 例えば、先ほどのプロットをやり直すことができますが、3つの種を別々の「球体」オブジェクトとして表示し、それらを切り替えるためのボタンを用意します。:
clear3d() # Remove the earlier display
setosa <- with(subset(iris, Species == "setosa"),
spheres3d(Sepal.Length, Sepal.Width, Petal.Length,
col=as.numeric(Species),
radius = 0.211))
versicolor <- with(subset(iris, Species == "versicolor"),
spheres3d(Sepal.Length, Sepal.Width, Petal.Length,
col=as.numeric(Species),
radius = 0.211))
virginica <- with(subset(iris, Species == "virginica"),
spheres3d(Sepal.Length, Sepal.Width, Petal.Length,
col=as.numeric(Species),
radius = 0.211))
aspect3d(1,1,1)
axesid <- decorate3d()
rglwidget() %>%
toggleWidget(ids = setosa) %>%
toggleWidget(ids = versicolor) %>%
toggleWidget(ids = virginica) %>%
toggleWidget(ids = axesid) %>%
asRow(last = 4)
label
の引数を省略したので、ボタンには ids
として渡された変数の名前がラベルとして表示されます。 asRow
関数については between で説明します。
toggleWidget()
は、playwidget
と subsetControl
という2つの関数の便利なラッパーです。 playwidget()
はWebページにボタンを追加し(スライダーの追加やアニメーションなども可能)、subsetControl()
は表示するオブジェクトのサブセットを選択します。
subsetControl
より一般的な例では、スライダーを使って、虹彩ディスプレイのデータのいくつかのサブセットを選択することができます。 例えば
rglwidget() %>%
playwidget(start = 0, stop = 3, interval = 1,
subsetControl(1, subsets = list(
Setosa = setosa,
Versicolor = versicolor,
Virginica = virginica,
All = c(setosa, versicolor, virginica)
)))
この他にもいくつかの「制御」機能があります。
par3dinterpControl
par3dinterpControl
は、par3dinterp
の結果を近似したものです。
例えば、次のコード(play3d
の例に似ています)は、複雑な方法でシーンを回転させます。
M <- r3dDefaults$userMatrix
fn <- par3dinterp(time = (0:2)*0.75, userMatrix = list(M,
rotate3d(M, pi/2, 1, 0, 0),
rotate3d(M, pi/2, 0, 1, 0)) )
rglwidget() %>%
playwidget(par3dinterpControl(fn, 0, 3, steps=15),
step = 0.01, loop = TRUE, rate = 0.5)
注意点としては,生成された Javascript のスライダーは300刻みで,動きが滑らかに見えるようになっています. しかし,300個の userMatrix
の値を保存するには多くのスペースを必要とするため,Javascript のコードで補間を使用しています. しかし,Javascript のコードでは線形補間しかできず,par3dinterp
で行うような複雑なスプラインベースの SO(3) 補間はできません. このため、par3dinterpControl
から15ステップの出力を行い、線形補間の歪みが見えないようにする必要があります。
propertyControl
propertyControl
は、シーンのプロパティの値を設定する、より一般的な関数です。 現在、ほとんどのプロパティに対応していますが、使用するには内部実装の知識が必要です。
clipplaneControl
clipplaneControl
では、スライダを動かしてクリッピングプレーンの位置を制御することができます。
vertexControl
(頂点コントロール)propertyControl
よりも汎用性が低いのが vertexControl
です。 この関数は、シーン内の個々の頂点の属性を設定します。 例えば、setosa グループの最も近い点のx座標を設定したり、その色を黒から白に変更したりします。
setosavals <- subset(iris, Species == "setosa")
which <- which.min(setosavals$Sepal.Width)
init <- setosavals$Sepal.Length[which]
rglwidget() %>%
playwidget(
vertexControl(values = matrix(c(init, 0, 0, 0,
8, 1, 1, 1),
nrow = 2, byrow = TRUE),
attributes = c("x", "red", "green", "blue"),
vertices = which, objid = setosa),
step = 0.01)
ageControl
関連する関数として、ageControl
がありますが、これは属性の仕様が大きく異なります。 この関数は、スライダがシーンの「年齢」をコントロールし、頂点の属性がその年齢に応じて変化する場合に使用します。
説明のために、曲線に沿って動く点を示します。 1つのリストに2つの ageControl
コールを入れます。 最初のものは軌跡の色をコントロールし、2番目のものは点の位置をコントロールします。:
time <- 0:500
xyz <- cbind(cos(time/20), sin(time/10), time)
lineid <- plot3d(xyz, type="l", col = "black")["data"]
sphereid <- spheres3d(xyz[1, , drop=FALSE], radius = 8, col = "red")
rglwidget() %>%
playwidget(list(
ageControl(births = time, ages = c(0, 0, 50),
colors = c("gray", "red", "gray"), objids = lineid),
ageControl(births = 0, ages = time,
vertices = xyz, objids = sphereid)),
start = 0, stop = max(time) + 20, rate = 50,
components = c("Reverse", "Play", "Slower", "Faster",
"Reset", "Slider", "Label"),
loop = TRUE)
rglMouse
rglMouse
関数は、このセクションの他の関数のような意味でのコントロールではありませんが、HTMLコントロールをディスプレイに追加して、ユーザーがマウスモードを選択できるようにするために使用します。
例えば、以下のディスプレイは、最初は特定のポイントを選択できるようになっていますが、マウスモードを変更することで、ユーザがディスプレイを回転させてシーンを別の角度から見ることができるようになります。
ids <- with(iris, plot3d(Sepal.Length, Sepal.Width, Petal.Length,
type="s", col=as.numeric(Species)))
par3d(mouseMode = "selecting")
rglwidget(shared = rglShared(ids["data"])) %>%
rglMouse()
ここで使われている rglShared()
コールについては、below で説明しています。
多くの rgl
ディスプレイは、1つまたは複数の rgl
シーンとコントロールという複数の要素を含みます。 内部的には rgl
は manipulateWidget
パッケージの combineWidgets
関数を使用します。
rgl
パッケージはディスプレイをアレンジするための3つの便利な関数を提供します。 最初の関数は magrittr
パイプ、%>%
です。 パイプを使ってディスプレイを1つのオブジェクトとして構築すると、パイプライン内のオブジェクトは1列に配置されます。
2つ目の便利な関数は、asRow
です。 これは、オブジェクトのリストまたは combineWidgets
オブジェクト(おそらくパイプの結果)を入力として受け取り、それらの(いくつかの)オブジェクトを横一列に並べ替えます。 toggleWidget example のように、last
引数を使用して、asRow
のアクションを指定されたコンポーネントの数に制限することができます。 (last = 0
の場合は、すべてのオブジェクトがスタックされます。これは、いくつかのオブジェクトが rgl
パッケージのものではないので、パイピングが機能しない場合に便利です)。
最後に、getWidgetId
を使うと、HTMLウィジェットからHTML要素のIDを抽出することができます。 これは、以下の crosstalk
の例のように、すべてが同じパイプの要素ではないウィジェットを組み合わせるときに便利です。
これらの便利な関数では不十分な場合は、manipulateWidget::combineWidgets や、manipulateWidget
の他の関数を呼ぶことで、より柔軟な表示のアレンジが可能になります。
crosstalk
との統合crosstalk
パッケージを使うと、ウィジェット同士の通信が可能になります。 現在、観測データの選択とフィルタリングをサポートしています。
rgl
はこれらのメッセージを送信、受信、表示することができます。 rgl
のディスプレイは複数のサブシーンを持つことができ、それぞれが異なるデータセットを表示します。 シーン内の各オブジェクトは、潜在的に crosstalk
的な意味での共有データセットです。
このリンクは rglShared
関数に依存します。 rglShared(id)
(id
は現在のシーンにあるオブジェクトの rgl
の id 値です) を呼び出すと、rgl
オブジェクトの頂点の座標を含む共有データオブジェクトが作成されます。 このオブジェクトは rglwidget
の shared
引数に渡されます。 また、共有データを受け付ける他のウィジェットにも渡すことができ、2つのディスプレイをリンクさせることができます。
共有データオブジェクトが他の方法で作成された場合は、以下の例のように、その key
と group
プロパティをコピーすることで、特定の rgl
id
値にリンクすることができます。
library(crosstalk)
sd <- SharedData$new(mtcars)
ids <- plot3d(sd$origData(), col = mtcars$cyl, type = "s")
# Copy the key and group from existing shared data
rglsd <- rglShared(ids["data"], key = sd$key(), group = sd$groupName())
rglwidget(shared = rglsd) %>%
asRow("Mouse mode: ", rglMouse(getWidgetId(.)),
"Subset: ", filter_checkbox("cylinderselector",
"Cylinders", sd, ~ cyl, inline = TRUE),
last = 4, colsize = c(1,2,1,2), height = 60)
rgl
シーン内の複数のオブジェクトを共有データとみなす必要がある場合は、複数の rglShared()
コールの結果をリストで渡すことができます。 キーの値は、データセット間で共有されていると仮定されます。 これが望ましくない場合は、プレフィックスを使用するなどして、オブジェクト間で異なるようにしてください。
同じ rgl
ID を複数の rglShared()
オブジェクトで使用した場合、すべてのオブジェクトからのメッセージに応答します。 これは、1つのメッセージが前のメッセージをキャンセルしてしまうため、望ましくない動作につながる可能性があります。
このドキュメントの最初のプロットを繰り返します。:
plotids <- with(iris, plot3d(Sepal.Length, Sepal.Width, Petal.Length,
type="s", col=as.numeric(Species)))
subid <- currentSubscene3d()
rglwidget(elementId="plot3drgl2")
ウェブページ上のボタンを使って、プロットの回転など、表示の変更を行いたい場合があります。 まず、ボタンを追加します。 “onclick”イベントには次のような関数を設定します。:
<button type="button" onclick="rotate(10)">Forward</button>
<button type="button" onclick="rotate(-10)">Backward</button>
このボタンを生成します。:
上のコードチャンクで subid
に現在アクティブなサブシーンの番号を格納し、下のスクリプトで
として使用しています。 `r subid`
knitr
はドキュメントを処理する際にその値を代入します。
rotate()
関数は、Javascript の関数 document.getElementById
を使って、シーンを含むWebページの <div>
コンポーネントを取得します。 その中には rglinstance
という名前のコンポーネントが含まれており、修正可能なシーンの情報が含まれています。:
<script type="text/javascript">
var rotate = function(angle) {
var rgl = document.getElementById("plot3drgl2").rglinstance;
rgl.getObj(`r subid`).par3d.userMatrix.rotate(angle, 0,1,0);
rgl.drawScene();
};
</script>
もし、chunk のヘッダに webGL=TRUE
を使っていたら、knitr
の WebGL サポートは、<chunkname>rgl
という形式の名前を持つグローバルオブジェクトを作成します。 例えば、コードチャンクの名前が plot3d2
であれば、オブジェクトの名前は plot3d2rgl
となり、このコードは動作します。:
<script type="text/javascript">
var rotate = function(angle) {
plot3d2rgl.getObj(`r subid`).par3d.userMatrix.rotate(angle, 0,1,0);
plot3d2rgl.drawScene();
};
</script>
このドキュメントでは以下の機能について説明しています。: