Np-Urのデータ分析教室

オーブンソースデータなどWeb上から入手できるデータを用いて、RとPython両方使って分析した結果を書いていきます

【R Shiny】ドラッグ&ドロップでファイルをアップロードする方法

本記事は、Shiny Advent Calendar 2017の8日目の記事です。



Shiny100本ノックの第11弾です。

今回は、手持ちのデータファイルをドラッグ&ドロップでShinyアプリに読み込ませる機能を実装してみたいと思います。
github.com
ソースコードはいつも通り、Githubに置いておきます。

ユーザーごとに作成したデータを可視化してみたいときや、データを更新したときなど、柔軟に読み込めると便利かと思います。

そして次回は、ドラッグ&ドロップで読み込んだデータをプロットするアプリケーションを作ってみます。

またドラッグ&ドロップで読み込まなくても、フォルダーを開いて読み込むこともできます。
そちらはまた別の機会に紹介しようかと思います。

完成形イメージ

完成形は以下のようなイメージです。
f:id:Np-Ur:20171203125541p:plain

ちょっと分かりづらいかもしれませんが、ファイルを選んで左の赤枠にドラッグ&ドロップすると、右側に読み込まれたデータが表示されます。

いつも通り、server.Rとui.Rは必須で必要なのですが、今回ブラウザにドラッグしたデータを読み込むために、Javascriptが必要になります。

実装の全体像をざっくり説明します。

  1. まず、JavascriptファイルにてドラッグしたデータをShinyにおけるinputとして、server.R側に渡す。
  2. 次に、server.Rファイルにて、受け取ったデータをテーブル表示に変換して、ui.R側に渡す。
  3. そして、ui,R側で表示する。

という流れになります。

よくあるshinyの実装は、

  1. ui.Rにて何をinputとするか決めてserver.Rに渡す。
  2. server.Rにてどう処理するか決めて、ui.Rに戻す。
  3. ui.Rにて表示する。

という流れですね。

入力(1番目の部分)が、ui.Rから読み込むかJavascriptで読み込むかの違いがあるだけで、あとの処理は普段と一緒です。

コード解説

ということでコードを紹介します。
今回のファイルは、
ー ui.R
ー server.R
ー www/
 - styles.css
 - drag.js

という構成になっています。

ui.Rのソースコード

ui.Rのソースコードを紹介します。

library(shiny)

shinyUI(
  fluidPage(
    tags$head(tags$link(rel = "stylesheet", href = "styles.css", type = "text/css"),
              tags$script(src = "drag.js")),
    sidebarLayout(
      sidebarPanel(
        h3("赤枠にデータをドロップ"),
        div(id="drop-area", ondragover = "f1(event)", ondrop = "f2(event)")
      ),
      mainPanel(
        tableOutput('table')
      )
    )
  )
)

まず、tag$head~という部分でCSSファイルとJavascriptファイルを読み込んでいます。
この辺りは以下でもまとめているのでご覧ください。
www.randpy.tokyo

また、

div(id="drop-area", ondragover = "f1(event)", ondrop = "f2(event)")

こちらはドラッグ&ドロップを行う箇所です。

ドラッグ&ドロップが行われたときの挙動を「f1」という関数と「f2」という関数を呼んで制御しています。
f1とf2が何をやっているかについては、次節で説明します。

また以下の部分で、読み込んだデータを元に出力をしています。

mainPanel(
  tableOutput('table')
)

www/drag.jsのソースコード

次に、www/drag.jsのソースコードです。

var datasets = {};
var f1 = function(e) { 
  e.preventDefault(); //ドラッグを許可するための定型文
};
var f2 = function(e) {
    e.preventDefault();//ドラッグを許可するための定型文
    
    var file = e.dataTransfer.files[0];
    
    var reader = new FileReader();//ファイルを読み込むためのオブジェクト
    reader.name = file.name;//onload処理で名前を取り出すために追加...bad know-howらしい 
    
    reader.onload = function(e) {
        datasets[e.target.name] = e.target.result;
        Shiny.onInputChange("mydata", datasets);
    };
    reader.readAsText(file);//この読み込みが終わってから上のonload処理が始まる
};

コメントでも書いているのですが、一応説明をしていきます。

先ほどui.Rで書いた ondragover = "f1(event)"の部分ですが、

var f1 = function(e) { 
  e.preventDefault(); 
};

こちらで定義しています。これを書かないとブラウザがドラッグを拒否してしまうので、定型文として書くようにしましょう。

またui.Rでondrop = "f2(event)"と書いたところですが、以下のように処理をしています。

var f2 = function(e) {
   e.preventDefault();   
   var file = e.dataTransfer.files[0];}

f1と同じように、「e.preventDefault()」でドラッグ&ドロップを許可しています。

ドラッグしたファイルですが、「e.dataTransfer.files」で取り出すことができます。
なお、e.dataTransfer.files[0]としていることで分かるかもですが、複数ファイルのアップロードも可能です。

次に以下の箇所にてFile APIからFileReaderというオブジェクトを使って、ファイル内のデータをShinyに渡すという処理を書いています。

var reader = new FileReader();

ここからの書き方ですが、慣れてないと分かりづらいかもしれません。

reader.onload = function(e) { //reader.readAsText(file)が終わってからこちらの処理が走る
  datasets[e.target.name] = e.target.result;
  Shiny.onInputChange("mydata", datasets);
};
reader.readAsText(file);

まず、ファイル中のテキスト情報を読み込むためには、reader.readAsText()と書きます。他にも色々メソッドがありますが、そこらへんは公式ドキュメントを見てみてください。

そして、テキストが読み終わった段階で、`reader.onload = function(e){` という処理が走ります。

ここが一番重要なところかと思いますが、ブラウザ上で受け取ったデータをShinyのinputとして渡すためには

Shiny.onInputChange(~~)

という風に書けばOkです。

server.Rのソースコード

そして、server.Rのソースコードです。

server = function(input, output, session) {
  observeEvent(input$mydata, {

  name = names(input$mydata)
  csv_file = reactive(read.csv(text=input$mydata[[name]]))
  output$table = renderTable(csv_file()[1,])
  
  })
}

先ほど、Jsファイルから渡ってきた「mydata」という変数をcsvファイルとして受け取り、テーブル形式の「table」という変数で返しています。

www/styles.cssのソースコード

最後に、www/styles.cssのソースコードを紹介します。

#drop-area {
    border-style:solid;
    border-color:red;
    height:100px;
    overflow:auto;
}

特に説明はいらないかもしれません。
ここでは、ドラッグ&ドロップを行う場所が分かりやすくなるように、赤枠線で仕切りを作っているだけです。

まとめ

と、Javascriptファイルが入ってきたので、少し複雑だったかもしれません。
分かりづらいところがあったら申し訳ございません。

ドラッグ&ドロップについては、以下のサイトがかなり簡潔に要点をまとめてくれていて、とても分かりやすいと思います。
今から3分で,HTML5のドラッグ&ドロップAPIと File APIを習得しよう

またわざわざShinyでファイルを読み込むだけでは、少し芸が無いので…。
次回は、ドラッグ&ドロップで読み込んだデータをプロットしてみるというところまで実装してみます。

お楽しみに!



追記

プロットの方も公開しました。
是非ご覧ください!
www.randpy.tokyo