--- title: 【R】[shiny] shinyにJavascriptを組み込む 5. Extending the gauge ~ tutorial1の終わりまで tags: R RStudio Shiny JavaScript author: sasaki_K_sasaki slide: false --- # JavaScriptベースのウィジットの構築方法 [前回:【R】[shiny] shinyにJavascriptを組み込む 2.Stand alone example ~ 4.First example](https://qiita.com/sasaki_K_sasaki/items/fd2919156481ba243d6d) の続きからです。 最初はJSのチュートリアルが入ってくるので、R・shinyで動くところだけで十分という方は **ウィジットの作成 Creating the widget!**のパートからご覧いただければと思います。 しばらくリンク先にページが見当たらなかったのですが(404 Not Found)、[こちら](https://github.com/FrissAnalytics/shinyJsTutorials)からアクセスできるよと教わりました! ## ゲージの拡張 今回作成するウィジットの強みは、チャートを作成した後にデータを更新することができる点です。データの更新はshiny上で可能になりますが、まずはJava Scriptを用いて行ってみましょう。 以前作成した[First Example](https://qiita.com/sasaki_K_sasaki/items/fd2919156481ba243d6d)を、JavaScriptを用いたデータの更新によって拡張します。前の例では設定しなかったスタイルプロパティをゲージに設定し、一定時間ごとにゲージの値を繰り返し更新するような変更を加えてみましょう。 下の画像の例では、C3.jsが、ある値から他の値へと上手く動的に表現していることが確認できます。(元サイトは動きます)この表現を実行するためには、ゲージによって表現したい現在の状態(更新前)と新しい状態(更新後)の両方の情報を保持する必要があります。 [JSFiddle](https://jsfiddle.net/FrissAnalytics/01tv8ojm/6/?utm_source=website&utm_medium=embed&utm_campaign=01tv8ojm) ![スクリーンショット 2017-10-11 午前9.40.43.png](https://qiita-image-store.s3.amazonaws.com/0/143058/5e2d8cb4-8083-f0a4-72e8-3f9e29b66a8b.png) コードは以下のとおりです。(外部のcssファイルなどに依存するため、このhtml単体ではゲージは表現できません) ```html:example_02_gauge.html
``` このコードは、[このリポジトリ](https://github.com/FrissAnalytics/shinyJsTutorials/tree/master/examples)にあるexamplesフォルダの、example_02_gauge.htmlに対応しています。 見てわかるように、`C3.generate`の呼び出しは少々複雑です。より詳細はc3.jsの[examples](http://c3js.org/examples.html)と[reference](http://c3js.org/reference.html)を参照してください。 上の例では、ゲージの値を[setInterval]((https://www.w3schools.com/jsref/met_win_setinterval.asp))という別の関数に更新するコードを書きました。 SetIntervalは、ミリ秒ごとに特定の関数を繰り返し実行するJavaScript関数です。 ```js: setInterval( functionDefinition , ms ); ``` 今回の例では、以下のように利用しています。 ```js: setInterval(function () { // create a random value between 0 and 100, rounded to 2 digits var newValue = Math.floor(100 * Math.random()); // create a data array holding the random value var newData = { 'data': newValue }; // tell the chart to load the new data chart.load({ json: newData }); }, 2000); ``` ここでは、名前を与えずに関数を定義する匿名関数[anonymous function](https://www.w3schools.com/js/js_function_definition.asp)として実行しています。 匿名関数の中で乱数を発生させ、新しいデータオブジェクトを作成するために使っています。 ```js: // create a random value between 0 and 100, rounded to 2 digits var newValue = Math.floor(100 * Math.random()); // create a data array holding the random value var newData = { 'data': newValue }; ``` 最後に、c3.jsに新しく生成したデータをゲージに読み込ませるよう指示します。 ```js: chart.load({ json: newData }); ``` c3は自動的にチャートの状態を更新します。更新までの時間は変更することができ、デフォルトは350ミリ秒ごとの更新です。 JavaScriptの場合、前のコードの中で出てきた`load`関数は[method](https://www.w3schools.com/js/js_object_methods.asp)と呼ばれ、chart objectで呼び出されます。 methodはJavaScriptオブジェクトの一部である特別な関数えと捉えることができます。通常methodはobjectに対して何らかのアクションを起こします。例えば、値の設定、取得などがアクションの例です。c3は特定の図に応じた多くのmethodsを持ちます。詳細は[こちら](http://c3js.org/reference.html#api-focus)をご覧ください。 この段階で、完全に機能するゲージを作成するための準備は整いました。 ## ウィジットの作成 Creating the widget! Rの世界に戻りましょう! htmlウィジットを作成するために、[htmlwidgets](https://cran.r-project.org/web/packages/htmlwidgets/index.html)・[devtools](https://cran.r-project.org/web/packages/devtools/index.html)パッケージが必要です。 インストールしていない場合は、次のコマンドを実行してください。 ```r:console install.packages("htmlwidgets") install.packages("devtools") ``` 次にパッケージをRの中に読み込んでください。 ```r:console library("htmlwidgets") library("devtools") ``` htmlウィジットを作成するために、Rパッケージの作成が必要になります。これはdevtoolsの領域です。 もしR package buildingの経験がない場合、[こちら](http://r-pkgs.had.co.nz/)を参照してください。 幸いにも、devtoolsとhtmlwidgetによって新たなpackagesの作成、widgetの基本的な枠組みの設定が簡略化されます。次のコードでは、ウィジットを構築するための新しいR packageを作成します。 ```r:console # create package using devtools devtools::create("C3") # navigate to package dir setwd("C3") # create widget scaffolding scaffoldWidget("C3Gauge", edit = FALSE) # install package so we can test it install() ``` 最初のうちは、これらの手順は少し難しく感じるかもしれません。しかし、幾つかのウィジットを作成していくうちに習慣になります。```scaffoldWidget```関数はテンプレートを作成する関数です。最初の引数で、ウィジットの名前(今回は"C3Gauge")を指定します。また、作成したファイルを開く必要がないため、 ```edit = FALSE```を指定しています。 上のコードを実行すると、動的なテキストを含むdiv要素を作成するダミーのウィジットが作成されます。動作を確認するため、今作成したパッケージをインストールしましょう。次のコードで、作成したパッケージを読み込み、RstudioのViewerの箇所に単純な「Hello world!」を出力できます。 ```r:console library(C3) C3Gauge("Hellow, world") ``` ## Ok, what hust happened? これまでの作業では、working directoryにいくつかのフォルダとファイルが生成されていました。 少し振り返りましょう。 最初に```create```関数を実行しました。これにより、Rパッケージ作成のためのC3フォルダと空のRフォルダが作成されます。 ```r:console devtools::create("C3") ``` ![C3.png](https://qiita-image-store.s3.amazonaws.com/0/143058/7d3d66af-38c1-0721-84ed-8247257f0913.png) 次に実行した```scaffoldWidget```関数では、さらに3つのファイルを作成します。 ```r:console scaffoldWidget("C3Gauge", edit = FALSE) ``` ![C3_2.png](https://qiita-image-store.s3.amazonaws.com/0/143058/a6c2159b-4aeb-6db5-1969-1307208fe9af.png) instフォルダが生成されたことがわかります。 instフォルダは、全ての外部パッケージの依存関係が保存される汎用Rパッケージです。このフォルダの中にはhtmlwidgetサブフォルダが作成され、内部に以下のファイルを含んでいます。 - C3Gauge.js - C3Gauge.yaml - C3Gauge.R 後にこのフォルダを編集していくことになります。 ## 外部依存関係の調整 ゲージを作成するためには、幾つかの外部依存関係にあるファイルを用意する必要があります。(上の例では、c3.min.jsやd3.v3.min.jsなど) 外部依存関係に関するコードを、元のウィジットのコードと分けて管理するため、htmlwidgetフォルダにlibフォルダを手動で作成し、[このZIPファイル](https://github.com/FrissAnalytics/shinyJsTutorials/blob/master/tutorials/tutorial_01_lib.zip?raw=true)の内容をlibフォルダの中で解凍します。zipファイルの中には、c3.min.js、d3,v3,min,cssが含まれており、今回のウィジットを作成するすべての人が同じバージョンを使用していることが確認されます。 次に、```C3Gauge.yaml```ファイルを編集します。 このファイルでは、使用する外部関係にあるファイルとinstフォルダの場所を指定します。 今回の場合、以下のように編集します ```yaml:C3Gauge.yaml dependencies: - name: d3 version: 3.5.16 src: htmlwidgets script: ./lib/d3.v3.min.js - name: c3 version: 0.4.10 src: htmlwidgets script: ./lib/c3.min.js style: ./lib/c3.min.css ``` yamlファイルはインデントのルールが厳しいため、不要な改行やスペースには注意してください。 これで最初のウィジットを作成する準備は整いました。以下で細かいステップを見ていきます。 ### step1.widgetのためのJavaScriptコードを書く ウィジットを作成するためには、Rが自動的に作成したJavaScriptテンプレートファイルの中に、JavaScriptファイルを記述する必要があります。具体的には、自動生成されたダミーコードの一部を置き換え、```HTMLwidget.widget```のチャート関数の中に詳細を記入していきます。 Rにより自動生成されたC3Gauge.jsテンプレートコードを詳しく見てみます。 ```js:C3Gauge.js HTMLWidgets.widget({ name: 'C3Gauge', type: 'output', factory: function(el, width, height) { // TODO: define shared variables for this instance return { renderValue: function(x) { // TODO: code to render the widget, e.g. el.innerText = x.message; }, resize: function(width, height) { // TODO: code to re-render the widget with a new size } }; } }); ``` ```HTMLwidgets.widget```では、コンポーネントに名前をつけ、function ```factory```を定義する必要があります。現在、"type"の値は「output」という単一の値しか保持することができません。このチュートリアルでは、単純なrenderValue関数を実装します。 c3.jsは自動的にサイズ変更を処理するので、コンテナ要素のサイズが変更された場合の処理を記載する必要はありません。(つまり```resize```関数を明示的に記載する必要はありません) 最初のステップとして、以前のJavaScriptの例で出てきたコードを```renderValue```関数にコピーして見ましょう。現時点では、RとJavaScriptのデータのやり取りは気にかける必要はありません。 ```js:C3Gauge.js HTMLWidgets.widget({ return { renderValue: function(x) { var gaugeData = {'data': 80.0}; // create a chart and set options // note that via the c3.js API we bind the chart to the element with id equal to chart1 var chart = c3.generate({ bindto: el, data: { json: gaugeData, type: 'gauge', }, gauge: { label:{ //returning here the value and not the ratio format: function(value, ratio){ return value;} }, min: 0, max: 100, width: 15, units: 'value' //this is only the text for the label } }); }, ``` ここでパッケージを再インストールし、ライブラリを読み込んでからC3Gauge関数を実行すると、その例が表現できていることが確認できます。 ```r:console devtools::install() library(C3) C3Gauge("") ``` ゲージが作成できました。 ![C3_3.png](https://qiita-image-store.s3.amazonaws.com/0/143058/99c66f96-fc66-9fb6-9a29-29fa3dedd3cc.png) ### step2:widgetのためのRコードを書く ゲージをRから表現することができました。しかし、ゲージに表示させる値(現在は80)はまだ設定できていません。R側からゲージの値を設定するために、```C3Gauge.R```を編集しましょう。 ```r:C3Gauge.R C3Gauge <- function(message, width = NULL, height = NULL) { # forward options using x x = list( message = message ) # create widget htmlwidgets::createWidget( name = 'C3Gauge', x, width = width, height = height, package = 'C3' ) } ``` デフォルトでは、C3Gauge関数は**message**、**width**、**height**の3つの引数を持っています。 自動生成されたテンプレートのコードでは、**message**引数はC3Gauge.jsの```renderValue```関数に渡される任意のデータです。 C3Gaugeのメインは2つのパートで構成されています。 1つは、ウィジットに渡されるすべてのデータを含む```x```というリストです。これは単一の値やdata.frameなど、あらゆるものを保持する複合リストです。 2つめは```createWidget```関数で、```name```、データ```x```、ウィジットの幅```width```、高さ```height```・ウィジットがあるパッケージ名```package```で呼び出されます。 [Stand alone example](https://qiita.com/sasaki_K_sasaki/items/fd2919156481ba243d6d)でデータの更新をどのように行ったかを振り返ると、次のようなJavaScript形式であったことがわかります。 ```r: {'data':80.0} ``` デフォルトでは、htmlwidgetは[jsonlite](https://cran.r-project.org/web/packages/jsonlite/index.html)パッケージの```toJSON```関数を使ってR-JavaScript間のデータの変換をします。以下で試してみましょう。 ```r:console x <- list(data=80) jsonlite::toJSON(x) ``` 出力は ```r:console {"data":[80]} ``` となり、たしかにJavaScriptのデータ形式に変換されています。ここで追加された括弧[]は、データが1次元配列になったことを意味しています。つまり、上記の形でデータを取得するためには、C3Gauge.R内の```C3Gauge```で以下のように記述します。 ```r:C3gauge.R x = list( data = message ) ``` さらに、```C3Gauge.js```の中の、gaugeDataを生成する行を削除します。静的なgaugeDataを渡す代わりに、Rで生成したリストを以下のように渡します。 ```js:C3Gauge.js json: x ``` まとめると、```C3Gauge.R```のコード全体は以下のようになります。 ```r:C3Gauge.R #' #' #' #' #' @import htmlwidgets #' #' @export C3Gauge <- function(message, width = NULL, height = NULL) { # forward options using x x = list( data = message ) # create widget htmlwidgets::createWidget( name = 'C3Gauge', x, width = width, height = height, package = 'C3' ) } #' Shiny bindings for C3Gauge #' #' Output and render functions for using C3Gauge within Shiny #' applications and interactive Rmd documents. #' #' @param outputId output variable to read from #' @param width,height Must be a valid CSS unit (like \code{'100\%'}, #' \code{'400px'}, \code{'auto'}) or a number, which will be coerced to a #' string and have \code{'px'} appended. #' @param expr An expression that generates a C3Gauge #' @param env The environment in which to evaluate \code{expr}. #' @param quoted Is \code{expr} a quoted expression (with \code{quote()})? This #' is useful if you want to save an expression in a variable. #' #' @name C3Gauge-shiny #' #' @export C3GaugeOutput <- function(outputId, width = '100%', height = '400px'){ htmlwidgets::shinyWidgetOutput(outputId, 'C3Gauge', width, height, package = 'C3') } #' @rdname C3Gauge-shiny #' @export renderC3Gauge <- function(expr, env = parent.frame(), quoted = FALSE) { if (!quoted) { expr <- substitute(expr) } # force quoted htmlwidgets::shinyRenderWidget(expr, C3GaugeOutput, env, quoted = TRUE) } ``` そして```C3Gauge.js```のコードは以下のとおりです。 ```js:C3Gauge.js HTMLWidgets.widget({ name: 'C3Gauge', type: 'output', factory: function(el, width, height) { // TODO: define shared variables for this instance return { renderValue: function(x) { // create a chart and set options // note that via the c3.js API we bind the chart to the element with id equal to chart1 var chart = c3.generate({ bindto: el, data: { json: x, type: 'gauge', }, gauge: { label:{ //returning here the value and not the ratio format: function(value, ratio){ return value;} }, min: 0, max: 100, width: 15, units: 'value' //this is only the text for the label } }); }, resize: function(width, height) { // TODO: code to re-render the widget with a new size } }; } }); ``` 次のコードで、値が50のゲージを表現できます。 ```r:console devtools::install() library(C3) C3Gauge(50) ``` ![C3_4.png](https://qiita-image-store.s3.amazonaws.com/0/143058/f3d84e61-b16f-f42f-9ca2-61600f02814a.png) 今作成したゲージを、shinyの中で表現してみましょう。次のコードでは、1つのゲージを含むアプリケーションを作成します。ゲージの値はaciton buttonをクリックすると更新されます。 ```r:shiny_Gauge library(C3) library(shiny) runApp(list( ui = bootstrapPage( actionButton("update","update gauge"), # example use of the automatically generated output function C3GaugeOutput("gauge1") ), server = function(input, output) { # reactive that generates a random value for the gauge value = reactive({ input$update round(runif(1,0,100),2) }) # example use of the automatically generated render function output$gauge1 <- renderC3Gauge({ # C3Gauge widget C3Gauge(value()) }) } )) ``` ![C3_5.png](https://qiita-image-store.s3.amazonaws.com/0/143058/ddf09fbe-4c45-ee18-3d72-54ed1f3fb694.png) 上記の手順を省いて、サンプルのアプリケーションを実行したい場合は、以下のコマンドを実行してください。 ```r:console devtools::install_github("FrissAnalytics/shinyJsTutorials/widgets/C3") ``` ## 次回予告 [次回のチュートリアル](https://shiny.rstudio.com/articles/js-send-message.html)では、今回作成したゲージについて、```renderValue```メソッドに変更を加えることで、[Stand alone example](https://qiita.com/sasaki_K_sasaki/items/fd2919156481ba243d6d)で実現したような**スムーズな値の更新**ができるゲージウィジットを作成します。また、[closure](http://www.w3schools.com/js/js_function_closures.asp)、[events](http://www.w3schools.com/js/js_events.asp)、[objects](http://www.w3schools.com/js/js_objects.asp)などの、関連するJavaScriptの概念についても詳しく説明します。ゲージの拡張の次に、円グラフや折れ線グラフ、積み重ねエリアチャートを作成します。 ## 参考 [How to build a JavaScript based widget] (https://shiny.rstudio.com/articles/js-build-widget.html) 前回 [【R】[shiny] shinyにJavascriptを組み込む 1.導入](https://qiita.com/sasaki_K_sasaki/items/ce8865a2801561cdf86d) [【R】[shiny] shinyにJavascriptを組み込む 2.Stand alone example ~ 4.First example](https://qiita.com/sasaki_K_sasaki/items/fd2919156481ba243d6d)