htmlwidgetsで独自パッケージをつくってHello Worldしてみる

RStudioが先日、htmlwidgetsというパッケージを発表しました。RからJavascriptの可視化ライブラリを使うためのフレームワーク的なものみたいです(あんまりよく分かってない)。

htmlwidgets for R

もうこんな感じのチャラい可視化ができるJS勢をうらやむ必要はありません!

私たちは無限の可能性を手にしています!

f:id:yutannihilation:20141222224548p:plain

…なんですけど、千里の道も一歩から。今回は退屈でしょぼーい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の一択です。

f:id:yutannihilation:20141222230220p:plain

ここでは仮にhellohellohowlowというパッケージをつくってみます。Nirvanaわりと好きです。

f:id:yutannihilation:20141222230240p:plain

scaffoldingをつくる

scaffoldingって「足場」とか「土台」みたいな意味らしいです。ネーミングはよく分かりませんがドキュメントに言われるがままにコマンドを打ってみます。

htmlwidgets::scaffoldWidget("hoge")

すると、以下の3つのファイルができます。

  • hoge.RJavascriptに値を渡す関数
  • hoge.yaml:使うJavascriptのライブラリやCSSなどを書く
  • hoge.js:Rから値を受け取って可視化する関数

f:id:yutannihilation:20141222231139p:plain

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...

この後にhogeOutputrenderHogeという関数もあるんですが、これはShinyを使ってインタラクティブにやるときに使うやつなので、ひとまず今回は無視します。

ここでxcreateWidgetに渡していますが、これが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オブジェクトです。この例ではelinnerHTMLx.messageの値を設定しています。つまり、Rから渡した値がテキストとして表示されることになります。

initialize()では、なにやらreturnしていますが、ここでreturnした値はinstanceオブジェクトに格納され、renderValue()などから参照可能になります。

たとえば、initialize()

return { "attr1": value1 }

のようにしておくと、renderValue()などでは

instance.attr1

で値にアクセスできます。

こんにちは世界

さっそくCtrl+Shift+Bでパッケージをビルドします。

library(hellohellohowlow)
hoge("こんにちは世界")

とコマンドを実行すると、右下のペインに文字がそのまま表示されるはずです。

f:id:yutannihilation:20141223002357p:plain

はい、そっけない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文字列が表示されるはずです。

f:id:yutannihilation:20141223004743p:plain

ChromeならF12、FirefoxならCtrl+Shift+Kを押すとコンソールが開きます。以下のようにオブジェクトが表示されているはずです。

f:id:yutannihilation:20141223004754p:plain

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}] 

htmlwidgetsRJSONIO使ってるらしいので、このイケてない挙動はそのせいなのかなと邪推しています。

> 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}"

これどうしたもんかな。。みんな困ってないんだろうか。


追記:やり方ドキュメントに書いてあったので記事書きました。