htmlwidgetsで独自パッケージをつくってHello Worldしてみる
RStudioが先日、htmlwidgetsというパッケージを発表しました。RからJavascriptの可視化ライブラリを使うためのフレームワーク的なものみたいです(あんまりよく分かってない)。
もうこんな感じのチャラい可視化ができるJS勢をうらやむ必要はありません!
私たちは無限の可能性を手にしています!
…なんですけど、千里の道も一歩から。今回は退屈でしょぼーいHello Worldの話です。
ドキュメント
このへん。
Creating a widget
http://www.htmlwidgets.org/develop_intro.html#creating-your-own-widgets
プロジェクトをつくる
上のドキュメントでは、前半、
devtools::create("mywidget") # create package using devtools setwd("mywidget") # navigate to package dir
って書いてありますが、RStudioネイティブのアテクシたちはそんなかったるいことやってられませんことよ。
File > New Projectの一択です。
ここでは仮にhellohellohowlow
というパッケージをつくってみます。Nirvanaわりと好きです。
scaffoldingをつくる
scaffoldingって「足場」とか「土台」みたいな意味らしいです。ネーミングはよく分かりませんがドキュメントに言われるがままにコマンドを打ってみます。
htmlwidgets::scaffoldWidget("hoge")
すると、以下の3つのファイルができます。
hoge.R
:Javascriptに値を渡す関数hoge.yaml
:使うJavascriptのライブラリやCSSなどを書くhoge.js
:Rから値を受け取って可視化する関数
hoge.yaml
は今回は使わないので、残りの2つを見てみます。
hoge.R
#' <Add Title> #' #' <Add Description> #' #' @import htmlwidgets #' #' @export hoge <- function(message, width = NULL, height = NULL) { # forward options using x x = list( message = message ) # create widget htmlwidgets::createWidget( name = 'hoge', x, width = width, height = height, package = 'hellohellohowlow' ) } ...snip...
この後にhogeOutput
、renderHoge
という関数もあるんですが、これはShinyを使ってインタラクティブにやるときに使うやつなので、ひとまず今回は無視します。
ここでx
をcreateWidget
に渡していますが、これがJavascript側に渡されます。
package
引数は、省略するとname
と同じ値が入ります。今回はパッケージ名と関数名を別にしているので省略不可です。
hoge.js
HTMLWidgets.widget({ name: 'hoge', type: 'output', initialize: function(el, width, height) { return { // TODO: add instance fields as required } }, renderValue: function(el, x, instance) { el.innerText = x.message; }, resize: function(el, width, height, instance) { } });
hoge.js
は、initialize()
→renderValue()
(もしリサイズされたら、→resize()
)という流れで処理が進みます。
R側から渡すデータx
を受け取るのはrenderValue()
です。el
という引数がありますが、これは結果を出力するためのDOMオブジェクトです。この例ではel
のinnerHTML
にx.message
の値を設定しています。つまり、Rから渡した値がテキストとして表示されることになります。
initialize()
では、なにやらreturn
していますが、ここでreturn
した値はinstance
オブジェクトに格納され、renderValue()
などから参照可能になります。
たとえば、initialize()
で
return { "attr1": value1 }
のようにしておくと、renderValue()
などでは
instance.attr1
で値にアクセスできます。
こんにちは世界
さっそくCtrl+Shift+B
でパッケージをビルドします。
library(hellohellohowlow) hoge("こんにちは世界")
とコマンドを実行すると、右下のペインに文字がそのまま表示されるはずです。
はい、そっけないHello Worldおわり!
デバッグ
ということでHello Worldは終わったわけですが、今後華々しい可視化の世界に足を踏み入れる前に、RのデータはJavascriptからはどう見えているのか確認しておきたいと思います。先ほどの例をちょっと変えます。
ファイルを変更してみる
hoge.R
例ではlistに囲んでいましたが、指定した値をそのまま渡すようにします。
hoge <- function(x, width = NULL, height = NULL) { htmlwidgets::createWidget( name = 'hoge', x, width = width, height = height, package = 'hellohellohowlow' ) }
hoge.js
与えられた値をJSONにして表示するようにしてみます。
あと、console.log()
でオブジェクトをコンソールにも表示してみます。
renderValue: function(el, x, instance) { console.log(x); el.innerText = JSON.stringify(x); },
これでもう一度ビルドします。
実行
まず渡すデータを用意します。
> set.seed(100) > days <- seq(as.Date("2014-12-18"),as.Date("2014-12-23"), by = "day") > ( x <- data.frame(days, value = runif(length(days))) ) days value 1 2014-12-18 0.30776611 2 2014-12-19 0.25767250 3 2014-12-20 0.55232243 4 2014-12-21 0.05638315 5 2014-12-22 0.46854928 6 2014-12-23 0.48377074
これをhoge()
に渡します。
hoge(x)
すると、JSON文字列が表示されるはずです。
ChromeならF12、FirefoxならCtrl+Shift+Kを押すとコンソールが開きます。以下のようにオブジェクトが表示されているはずです。
RからJavascriptにどう値を渡してるのか謎すぎる、、と思ったときはこんな感じでデバッグできます。(たぶんもっといいデバッグ方法あるんでしょうけど。知識がないのでとりあえずこんな感じでやってます)
困ったこと
ここで困るのは、今見たようにデータがカラム方向にまとまっていることです。
Rにとってはいい感じのデータ形式ですが、Javascriptにとって、特に天下のD3.jsにとってはそうではありません(といいつつあまり自信ないので違ったらツッコミください。。)。データ点ごとにまとまったデータ形式が必要になります。
つまり、こんな感じです。
[ {"days":"2014-12-18","value":0.3078}, {"days":"2014-12-19","value":0.2577}, {"days":"2014-12-20","value":0.5523}, {"days":"2014-12-21","value":0.0564}, {"days":"2014-12-22","value":0.4685}, {"days":"2014-12-23","value":0.4838} ]
何を隠そう、jsonlite
はこういう形式に直してくれます。あったまいいー!
> jsonlite::toJSON(x) [{"days":"2014-12-18","value":0.3078},{"days":"2014-12-19","value":0.2577},{"days":"2014-12-20","value":0.5523},{"days":"2014-12-21","value":0.0564},{"days":"2014-12-22","value":0.4685},{"days":"2014-12-23","value":0.4838}]
htmlwidgets
はRJSONIO
使ってるらしいので、このイケてない挙動はそのせいなのかなと邪推しています。
> RJSONIO::toJSON(x) [1] "{\n \"days\": [ 16422, 16423, 16424, 16425, 16426, 16427 ],\n\"value\": [ 0.30777, 0.25767, 0.55232, 0.056383, 0.46855, 0.48377 ] \n}"
これどうしたもんかな。。みんな困ってないんだろうか。
追記:やり方ドキュメントに書いてあったので記事書きました。