JavaScriptベースのウィジットの構築方法
前回:【R】[shiny] shinyにJavascriptを組み込む 2.Stand alone example ~ 4.First example の続きからです。
最初はJSのチュートリアルが入ってくるので、R・shinyで動くところだけで十分という方は
ウィジットの作成 Creating the widget!のパートからご覧いただければと思います。
しばらくリンク先にページが見当たらなかったのですが(404 Not Found)、こちらからアクセスできるよと教わりました!
ゲージの拡張
今回作成するウィジットの強みは、チャートを作成した後にデータを更新することができる点です。データの更新はshiny上で可能になりますが、まずはJava Scriptを用いて行ってみましょう。
以前作成したFirst Exampleを、JavaScriptを用いたデータの更新によって拡張します。前の例では設定しなかったスタイルプロパティをゲージに設定し、一定時間ごとにゲージの値を繰り返し更新するような変更を加えてみましょう。
下の画像の例では、C3.jsが、ある値から他の値へと上手く動的に表現していることが確認できます。(元サイトは動きます)この表現を実行するためには、ゲージによって表現したい現在の状態(更新前)と新しい状態(更新後)の両方の情報を保持する必要があります。
コードは以下のとおりです。(外部のcssファイルなどに依存するため、このhtml単体ではゲージは表現できません)
<html>
<head>
<!-- required css style file for C3.js -->
<link href="c3.min.css" rel="stylesheet" type="text/css">
</head>
<body>
<!-- container element in which we will create the chart -->
<div id="chart1"></div>
<!-- required javascript libraries-->
<script src="d3.v3.min.js" charset="utf-8"></script>
<script src="c3.min.js"></script>
<!-- javascript block to render and update the chart-->
<script>
var gaugeData = {'data': 80.0}
// create a chart and set options
// note that we bind the chart to the element with id equal to chart1 via the c3.js API
var chart = c3.generate({
bindto: '#chart1',
data: {
json: gaugeData,
type: 'gauge',
},
gauge: {
label:{
//returning the value here 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
}
});
// this function will update every 2000 milliseconds
// and create a new value between 0 and 100
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);
</script>
</body>
</html>
このコードは、このリポジトリにあるexamplesフォルダの、example_02_gauge.htmlに対応しています。
見てわかるように、C3.generate
の呼び出しは少々複雑です。より詳細はc3.jsのexamplesとreferenceを参照してください。
上の例では、ゲージの値をsetIntervalという別の関数に更新するコードを書きました。
SetIntervalは、ミリ秒ごとに特定の関数を繰り返し実行するJavaScript関数です。
setInterval( functionDefinition , ms );
今回の例では、以下のように利用しています。
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として実行しています。
匿名関数の中で乱数を発生させ、新しいデータオブジェクトを作成するために使っています。
// 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に新しく生成したデータをゲージに読み込ませるよう指示します。
chart.load({
json: newData
});
c3は自動的にチャートの状態を更新します。更新までの時間は変更することができ、デフォルトは350ミリ秒ごとの更新です。
JavaScriptの場合、前のコードの中で出てきたload
関数はmethodと呼ばれ、chart objectで呼び出されます。
methodはJavaScriptオブジェクトの一部である特別な関数えと捉えることができます。通常methodはobjectに対して何らかのアクションを起こします。例えば、値の設定、取得などがアクションの例です。c3は特定の図に応じた多くのmethodsを持ちます。詳細はこちらをご覧ください。
この段階で、完全に機能するゲージを作成するための準備は整いました。
ウィジットの作成 Creating the widget!
Rの世界に戻りましょう!
htmlウィジットを作成するために、htmlwidgets・devtoolsパッケージが必要です。
インストールしていない場合は、次のコマンドを実行してください。
install.packages("htmlwidgets")
install.packages("devtools")
次にパッケージをRの中に読み込んでください。
library("htmlwidgets")
library("devtools")
htmlウィジットを作成するために、Rパッケージの作成が必要になります。これはdevtoolsの領域です。
もしR package buildingの経験がない場合、こちらを参照してください。
幸いにも、devtoolsとhtmlwidgetによって新たなpackagesの作成、widgetの基本的な枠組みの設定が簡略化されます。次のコードでは、ウィジットを構築するための新しいR packageを作成します。
# 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!」を出力できます。
library(C3)
C3Gauge("Hellow, world")
Ok, what hust happened?
これまでの作業では、working directoryにいくつかのフォルダとファイルが生成されていました。
少し振り返りましょう。
最初にcreate
関数を実行しました。これにより、Rパッケージ作成のためのC3フォルダと空のRフォルダが作成されます。
devtools::create("C3")
次に実行したscaffoldWidget
関数では、さらに3つのファイルを作成します。
scaffoldWidget("C3Gauge", edit = FALSE)
instフォルダが生成されたことがわかります。
instフォルダは、全ての外部パッケージの依存関係が保存される汎用Rパッケージです。このフォルダの中にはhtmlwidgetサブフォルダが作成され、内部に以下のファイルを含んでいます。
- C3Gauge.js
- C3Gauge.yaml
- C3Gauge.R
後にこのフォルダを編集していくことになります。
外部依存関係の調整
ゲージを作成するためには、幾つかの外部依存関係にあるファイルを用意する必要があります。(上の例では、c3.min.jsやd3.v3.min.jsなど)
外部依存関係に関するコードを、元のウィジットのコードと分けて管理するため、htmlwidgetフォルダにlibフォルダを手動で作成し、このZIPファイルの内容をlibフォルダの中で解凍します。zipファイルの中には、c3.min.js、d3,v3,min,cssが含まれており、今回のウィジットを作成するすべての人が同じバージョンを使用していることが確認されます。
次に、C3Gauge.yaml
ファイルを編集します。
このファイルでは、使用する外部関係にあるファイルとinstフォルダの場所を指定します。
今回の場合、以下のように編集します
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テンプレートコードを詳しく見てみます。
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のデータのやり取りは気にかける必要はありません。
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関数を実行すると、その例が表現できていることが確認できます。
devtools::install()
library(C3)
C3Gauge("")
ゲージが作成できました。
step2:widgetのためのRコードを書く
ゲージをRから表現することができました。しかし、ゲージに表示させる値(現在は80)はまだ設定できていません。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でデータの更新をどのように行ったかを振り返ると、次のようなJavaScript形式であったことがわかります。
{'data':80.0}
デフォルトでは、htmlwidgetはjsonliteパッケージのtoJSON
関数を使ってR-JavaScript間のデータの変換をします。以下で試してみましょう。
x <- list(data=80)
jsonlite::toJSON(x)
出力は
{"data":[80]}
となり、たしかにJavaScriptのデータ形式に変換されています。ここで追加された括弧[]は、データが1次元配列になったことを意味しています。つまり、上記の形でデータを取得するためには、C3Gauge.R内のC3Gauge
で以下のように記述します。
x = list(
data = message
)
さらに、C3Gauge.js
の中の、gaugeDataを生成する行を削除します。静的なgaugeDataを渡す代わりに、Rで生成したリストを以下のように渡します。
json: x
まとめると、C3Gauge.R
のコード全体は以下のようになります。
#' <Add Title>
#'
#' <Add Description>
#'
#' @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
のコードは以下のとおりです。
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のゲージを表現できます。
devtools::install()
library(C3)
C3Gauge(50)
今作成したゲージを、shinyの中で表現してみましょう。次のコードでは、1つのゲージを含むアプリケーションを作成します。ゲージの値はaciton buttonをクリックすると更新されます。
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())
})
}
))
上記の手順を省いて、サンプルのアプリケーションを実行したい場合は、以下のコマンドを実行してください。
devtools::install_github("FrissAnalytics/shinyJsTutorials/widgets/C3")
次回予告
次回のチュートリアルでは、今回作成したゲージについて、renderValue
メソッドに変更を加えることで、Stand alone exampleで実現したようなスムーズな値の更新ができるゲージウィジットを作成します。また、closure、events、objectsなどの、関連するJavaScriptの概念についても詳しく説明します。ゲージの拡張の次に、円グラフや折れ線グラフ、積み重ねエリアチャートを作成します。
参考
How to build a JavaScript based widget
前回
【R】[shiny] shinyにJavascriptを組み込む 1.導入
【R】[shiny] shinyにJavascriptを組み込む 2.Stand alone example ~ 4.First example