12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【超訳】htmlwidgets パッケージの使い方 #rstatsj

Last updated at Posted at 2015-08-13

この文章は、htmlwidgets パッケージの Vignette
Introduction to HTML Widgets (2015-06-21)
https://cran.r-project.org/web/packages/htmlwidgets/vignettes/develop_intro.html
を適当に翻訳したものです。

概要

htmlwidgets パッケージは、JavaScript ライブラリに対する R バインディングを作成するためのフレームワークを提供します。
HTML ウィジェットは次のことができます:

  • 従来の R プロットと全く同じように、データ解析のために R コンソールから使用できます。
  • R Markdown ドキュメントに埋め込むことができます。
  • Shiny ウェブアプリケーションに組み込むことができます。
  • 単独のウェブページとして保存でき、電子メールや Dropbox などでアドホックに共有できます。

簡単な約束事に従ってコーディングすることで、非常に少ないコードで HTML ウィジェットを作成することができます。
すべてのウィジェットは次のコンポーネントを含みます:

  • 依存関係(Dependencies): ウィジェットで使用される JavaScript と CSS ファイル(例えば、あなたがラッパーを作りたいライブラリ)です。
  • R バインディング: 入力データをウィジェットに提供したり、描画に必要な様々なオプションをウィジェットに渡すために、ユーザが呼び出す関数群です。
  • JavaScript バインディング: R バインディングに集められたデータとオプションをすべて結合し、基礎となる JavaScript ライブラリへ渡すための JavaScript コードです。

HTML ウィジェットは常に R パッケージ内でまかなわれるため、依存関係のあるすべてのソースコードを含んでいるべきです。
これはウィジェットに依存するすべてのコードを確保するためであり、これによりウィジェットはどんな場合でも再現可能になります(つまり、実行するのに、インターネット接続や可用性のあるインターネットサービスといったものを要求しません)。

例(sigma.js)

sigma.js というグラフ可視化ライブラリをラップする、シンプルなウィジェットの作成を一通り行ってみましょう。
これを終えると、GEXF(Graph Exchange XML Format)形式のデータファイルのインタラクティブな可視化が表示できるようになります。
例えば:

library(sigma)
data <- system.file("example/ediaspora.gexf.xml", package = "sigma")
sigma(data)

sigma.png

上の図はインタラクティブではないので、単に可視化のイメージだと思って下さい。
下のデモセクションの手順に従うことにより、この図のインタラクティブバージョンで遊ぶことができます。

このバインディングを作成するためのコードは非常に短いです。
下記で全てのコンポーネントの作り方をステップバイステップで見ていきます。
さらに、独自のウィジェットの作成方法について説明します(ベースとなるファイルコンポーネントの自動生成についても説明します)。

ファイル構成

ウィジェットの名前を sigma とし、同じ名前の R パッケージの中に作ることにします。
JavaScript バインディングのソースコードファイルの名前を sigma.js とします。
このウィジェットは GEXF データファイルを読み込むので、sigma.min.js ライブラリと GEXF プラグインを含める必要があります。
パッケージに追加するファイルはこうなります:

R/
| sigma.R

inst/
|-- htmlwidgets/
|   |-- sigma.js
|   |-- sigma.yaml
|   |-- lib/
|   |   |-- sigma-1.0.3/
|   |   |   |-- sigma.min.js
|   |   |   |-- plugins/
|   |   |   |   |-- sigma.parsers.gexf.min.js

JavaScript、YAML、および他の依存ファイルは、全て inst/htmlwidgets ディレクトリに含めるという決まりがあります(これにより、 パッケージの中の htmlwidgets という名前のサブディレクトリにインストールされます)。

依存関係(Dependencies)

依存関係とは、ウィジェットで使用する JavaScript や CSS などのファイルのことを指します。
依存関係のファイルは inst/htmlwidgets/lib の下に置きます。
依存関係は YAML 設定ファイルによって指定します。
YAML 設定ファイルの名前は、ウィジェットの名前を使います。
ここでは、 sigma.yaml であり、次のようになります:

dependencies:
  - name: sigma
    version: 1.0.3
    src: htmlwidgets/lib/sigma-1.0.3
    script: 
      - sigma.min.js
      - plugins/sigma.parsers.gexf.min.js

依存関係の src には、ライブラリを含むディレクトリを指定し、script には、特定の JavaScript ファイルを指定します。
もしライブラリが複数の JavaScript ファイルを含む場合は、上で示すように、- で始まる一行につき 1 ファイルを指定します。
他にも、stylesheet エントリや metahead も指定できます。
複数の dependencies を一つの YAML ファイルに記述できます。
詳細については、htmltools パッケージの htmlDependency 関数のドキュメントを参照してください。

R バインディング

ウィジェットを呼び出す R の関数をユーザに提供する必要があります。
典型的には、この関数は入力データおよびウィジェットの表示を制御する様々なオプションを受け取ります。
この R 関数 sigma は次のようになります:

#' @import htmlwidgets
#' @export
sigma <- function(gexf, drawEdges = TRUE, drawNodes = TRUE,
                  width = NULL, height = NULL) {
  
  # gexf ファイルの読み込み
  data <- paste(readLines(gexf), collapse="\n")
  
  # 設定を保持するリストを作成
  settings <- list(
    drawEdges = drawEdges,
    drawNodes = drawNodes
  )
  
  # 'x' を使ってデータと設定を受け渡す
  x <- list(
    data = data,
    settings = settings
  )
  
  # ウィジェットの作成
  htmlwidgets::createWidget("sigma", x, width = width, height = height)
}

この関数が受けとる引数は、2 種類に分けられます。
一つは描画したい GEXF データファイル(data)で、もう一つはどのように描画するかを決める追加オプション(setting)です。
これらの入力は名前付きリスト x に集約され、htmlwidgets::createWidget 関数に渡されます。
この変数 x はこの後、sigma の JavaScript バインディングにおいて利用可能になります(これは後で説明します)。
引数に指定された幅(width)と高さ(height)も、ウィジェットに渡されます(ウィジェットのサイズはデフォルトで自動的に決まるため、通常は幅や高さを明示的に指定する必要はありません)。

この sigma ウィジェットを、Shiny アプリケーションの中でも動くようにしたい場合があります。
そのためには、次のように Shiny 出力関数とレンダリング関数を追加します。

#' @export
sigmaOutput <- function(outputId, width = "100%", height = "400px") {
  shinyWidgetOutput(outputId, "sigma", width, height, package = "sigma")
}
#' @export
renderSigma <- function(expr, env = parent.frame(), quoted = FALSE) {
  if(!quoted) { expr <- substitute(expr) } # 強制的にクオートする
  shinyRenderWidget(expr, sigmaOutput, env, quoted = TRUE)
}

JavaScript バインディング

このパズルの 3 番目のピースは、ウィジェットをアクティブにするために必要な JacaScript コードです。
この JavaScript バインディングが書かれたファイルは、inst/htmlwidgets/sigma.js に配置することが慣習となっています。
このバインディングの完全なソースコードは次のようになります:

HTMLWidgets.widget({

  name: "sigma",
  
  type: "output",
  
  initialize: function(el, width, height) {
   
    // sigma オブジェクトを作成し、要素に紐づける
    var sig = new sigma(el.id);
    
    // インスタンスの一部として返す
    return {
      sig: sig
    };
  },
  
  renderValue: function(el, x, instance) {
      
    // gexf データのパース
    var parser = new DOMParser();
    var data = parser.parseFromString(x.data, "application/xml");
    
    // 設定の適用
    for (var name in x.settings)
      instance.sig.settings(name, x.settings[name]);
    
    // sigma インスタンスの更新
    sigma.parsers.gexf(
      data,          // パースされた gexf データ
      instance.sig,  // initialize で作成した sigma インスタンス
      function() {
        // 新しい設定とデータを反映するには refresh を呼ぶ必要がある
        instance.sig.refresh();
      }
    );
  },
  
  resize: function(el, width, height, instance) {
    
    // sigma の renderers の resize に渡す
    for (var name in instance.sig.renderers)
      instance.sig.renderers[name].resize(width, height);  
  }
});

このウィジェットに名前(name)とタイプ(type)を与え、次の 3 つの関数を実装しています。

  1. initialize 関数はウィジェットオブジェクトを作成し、DOM 要素に紐づけて、そのウィジェットオブジェクトを含むインスタンスデータを返します。今回の場合は、sigma オブジェクトを作成し、ページ上でそのウィジェットを保持する DOM 要素の id を渡しています。あとで sigma オブジェクトにアクセスする必要があるので(データや設定を更新するため)、ウィジェットのインスタンスデータのメンバ sig として返しています。

  2. renderValue 関数は、動的なデータと設定を実際にウィジェットに注入します。ウィジェットに必要なすべてのものが renderValue に引数として渡されます。x 引数は、ウィジェットのデータと設定を持っています。instance 引数は、操作対象の sigma オブジェクトへの参照を持っています。renderValue 関数は、GEXF データをパースして更新し、sigma オブジェクトに対する設定を適用し、refresh 関数を呼んでスクリーン上に新しい値を反映させています。

  3. resize 関数は、ウィジェットを含む DOM 要素のサイズが変更されるたびに呼び出されます。今回の場合は、sigma オブジェクトの各 renderer にサイズの情報を転送しています。

すべての JavaScript ライブラリは、初期化、DOM 要素への紐づけ、動的なデータ更新、サイズ変更といった操作方法を提供します。
ウィジェット作成における JavaScript バインディングの主な仕事は、上記の 3 つの関数を JavaScript ライブラリの操作と正確に対応付けることです。

デモ

以上でウィジェットの作成は完了です!
これらのコードを自分で書かずにこのウィジェットを試してみたい場合は、次のようにして GitHub からインストールできます:

devtools::install_github('jjallaire/sigma')

ediaspora.gexf.xml ファイルをダウンロードして、inst/htmlwidgets/examples ディレクトリに置いて下さい。

次のコードによって、このサンプルデータに対して sigma ウィジェットを試すことができます。

library(sigma)
sigma(system.file("examples/ediaspora.gexf.xml", package = "sigma"))

このコードを R コンソール上で実行すると、RStudio Viewer(RStudio を使っていない場合は外部ブラウザ)でウィジェットを見ることができます。

また、Shiny アプリの中でウィジェットを使うこともできます:

library(shiny)
library(sigma)

gexf <- system.file("examples/ediaspora.gexf.xml", package = "sigma")

ui = shinyUI(fluidPage(
  checkboxInput("drawEdges", "Draw Edges", value = TRUE),
  checkboxInput("drawNodes", "Draw Nodes", value = TRUE),
  sigmaOutput('sigma')
))

server = function(input, output) {
  output$sigma <- renderSigma(
    sigma(gexf, 
          drawEdges = input$drawEdges, 
          drawNodes = input$drawNodes)
  )
}

shinyApp(ui = ui, server = server)

独自のウィジェットを作る

必要なもの

独自のウィジェットを実装するためには、htmlwidget パッケージに依存した新規の R パッケージを作成する必要があります。
htmlwidget パッケージを CRAN からインストールするには、次のようにします:

install.packages("htmlwidgets")

必ずしも必要というわけではありませんが、次の手順では devtools パッケージを使います。
devtools パッケージを CRAN からインストールするには、次のようにします:

install.packages("devtools")

ひな形の作成

新しいウィジェットを作成するために、scaffoldWidget 関数を使ってウィジェットのひな形を作成します。
scaffoldWidget 関数は次のことを行います:

  • ウィジェットに必要な .R .js .yaml ファイルを作成します。
  • Bower が使用できる場合、自動的に JavaScript ライブラリ(および依存関係)をダウンロードして .yaml ファイルに必要な情報を追加します。

これにより正しいファイル構成で始められるため、この方法は非常にお勧めです。
次のコードは、'mywidget' という名前のウィジェットを同じパッケージ名で作りたいと仮定した場合の例です:

devtools::create("mywidget")               # devtools を使ってパッケージを作成する
setwd("mywidget")                          # パッケージディレクトリをワーキングディレクトリに設定
htmlwidgets::scaffoldWidget("mywidget")    # ウィジェットのひな形を生成する
devtools::install()                        # 確認のためにパッケージをインストールする

これにより、text 引数を取り、ウィジェットの HTML 要素にそのテキストを表示するだけの単純なウィジェットが作成されます。
このウィジェットは、次のようにして試すことができます:

library(mywidget)
mywidget("hello, world")

これは、考えうる最も単純なウィジェットであり、まだ JavaScript ライブラリを含んでいません(scaffoldWidget 関数は bowerPkg 引数を指定することで JavaScript ライブラリと依存関係を含めたひな形を生成することもできます)。
開発を始める前に、上で説明した sigma の例について、各コンポーネントの理解を深めるためにもう一度復習しておくと良いでしょう。
また、それだけでなく、次のセクションで紹介する記事や例についても調査しておくと良いでしょう。

さらに学ぶために

記事

より高度な話題に触れた記事があります:

多くの JavaScript ライブラリは、ウィジェットとそれを含む HTML 要素のサイズを同期させるためにインタラクションを追加する必要があるので、Sizing の記事は特に重要です。

他のパッケージのコードを調べるのは、ウィジェットの作成について学ぶための非常に良い方法です:

  1. networkD3 パッケージは、D3 を直接使用したウィジェットの作成、大きなウィジェットに対するカスタムサイジングポリシーの使用、一つのパッケージで複数のウィジェットを提供、といったことについての良い例です。
  2. dygraphs パッケージは、ウィジェットインスタンスデータの使用、動的なサイズ変更、magrittr を使って巨大な JavaScript API を部品化してパイプに渡しやすいように分解する、といったことについての良い例です。
  3. sparkline パッケージは、カスタム HTML(sparkline は <div> 要素ではなく <span> 要素に紐づける必要があるため)を生成する関数についての良い例です。

質問および問題発生時には

ウィジェットの開発について質問がある場合、または開発をしているときに問題が発生した場合は、プロジェクトの GitHub リポジトリに対して、ためらわずにイシューを投げてください

※訳注

1. 本家サイト

htmlwidgets パッケージの作者による本家サイトです。

2. 日本語資料

現在ネット上にある htmlwidgets パッケージについての日本語の資料は、ほぼ @yutannihilation さんによるものです。

3. ギャラリー

htmlwidgets によって作成された R パッケージを集めたサイトです(現在 53 個)。

本家サイトにも Showcase があります(現在 8 個)。

12
9
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?