Help us understand the problem. What is going on with this article?

【R】[shiny] shinyにJavascriptを組み込む 5. Extending the gauge ~ tutorial1の終わりまで

More than 1 year has passed since last update.

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が、ある値から他の値へと上手く動的に表現していることが確認できます。(元サイトは動きます)この表現を実行するためには、ゲージによって表現したい現在の状態(更新前)と新しい状態(更新後)の両方の情報を保持する必要があります。

JSFiddle

スクリーンショット 2017-10-11 午前9.40.43.png

コードは以下のとおりです。(外部のcssファイルなどに依存するため、このhtml単体ではゲージは表現できません)

example_02_gauge.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のexamplesreferenceを参照してください。

上の例では、ゲージの値を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ウィジットを作成するために、htmlwidgetsdevtoolsパッケージが必要です。
インストールしていない場合は、次のコマンドを実行してください。

console
install.packages("htmlwidgets")
install.packages("devtools")

次にパッケージをRの中に読み込んでください。

console
library("htmlwidgets")
library("devtools")

htmlウィジットを作成するために、Rパッケージの作成が必要になります。これはdevtoolsの領域です。
もしR package buildingの経験がない場合、こちらを参照してください。

幸いにも、devtoolsとhtmlwidgetによって新たなpackagesの作成、widgetの基本的な枠組みの設定が簡略化されます。次のコードでは、ウィジットを構築するための新しいR packageを作成します。

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!」を出力できます。

console
library(C3)
C3Gauge("Hellow, world")

Ok, what hust happened?

これまでの作業では、working directoryにいくつかのフォルダとファイルが生成されていました。

少し振り返りましょう。

最初にcreate関数を実行しました。これにより、Rパッケージ作成のためのC3フォルダと空のRフォルダが作成されます。

console
devtools::create("C3")

C3.png

次に実行したscaffoldWidget関数では、さらに3つのファイルを作成します。

console
scaffoldWidget("C3Gauge", edit = FALSE)

C3_2.png

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フォルダの場所を指定します。

今回の場合、以下のように編集します

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テンプレートコードを詳しく見てみます。

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のデータのやり取りは気にかける必要はありません。

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関数を実行すると、その例が表現できていることが確認できます。

console
devtools::install()                                      
library(C3)
C3Gauge("")

ゲージが作成できました。

C3_3.png

step2:widgetのためのRコードを書く

ゲージをRから表現することができました。しかし、ゲージに表示させる値(現在は80)はまだ設定できていません。R側からゲージの値を設定するために、C3Gauge.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関数はmessagewidthheightの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間のデータの変換をします。以下で試してみましょう。

console
x <- list(data=80)
jsonlite::toJSON(x)

出力は

console
{"data":[80]}

となり、たしかにJavaScriptのデータ形式に変換されています。ここで追加された括弧[]は、データが1次元配列になったことを意味しています。つまり、上記の形でデータを取得するためには、C3Gauge.R内のC3Gaugeで以下のように記述します。

C3gauge.R
x = list(
 data = message
)

さらに、C3Gauge.jsの中の、gaugeDataを生成する行を削除します。静的なgaugeDataを渡す代わりに、Rで生成したリストを以下のように渡します。

C3Gauge.js
json: x

まとめると、C3Gauge.Rのコード全体は以下のようになります。

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のコードは以下のとおりです。

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のゲージを表現できます。

console
devtools::install()                                      
library(C3)
C3Gauge(50)

C3_4.png

今作成したゲージを、shinyの中で表現してみましょう。次のコードでは、1つのゲージを含むアプリケーションを作成します。ゲージの値はaciton buttonをクリックすると更新されます。

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

上記の手順を省いて、サンプルのアプリケーションを実行したい場合は、以下のコマンドを実行してください。

console
devtools::install_github("FrissAnalytics/shinyJsTutorials/widgets/C3")

次回予告

次回のチュートリアルでは、今回作成したゲージについて、renderValueメソッドに変更を加えることで、Stand alone exampleで実現したようなスムーズな値の更新ができるゲージウィジットを作成します。また、closureeventsobjectsなどの、関連するJavaScriptの概念についても詳しく説明します。ゲージの拡張の次に、円グラフや折れ線グラフ、積み重ねエリアチャートを作成します。

参考

How to build a JavaScript based widget

前回
【R】[shiny] shinyにJavascriptを組み込む 1.導入
【R】[shiny] shinyにJavascriptを組み込む 2.Stand alone example ~ 4.First example

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away