本記事は、Shiny Advent Calendar 2017の8日目の記事です。
Shiny100本ノックの第11弾です。
今回は、手持ちのデータファイルをドラッグ&ドロップでShinyアプリに読み込ませる機能を実装してみたいと思います。
github.com
ソースコードはいつも通り、Githubに置いておきます。
ユーザーごとに作成したデータを可視化してみたいときや、データを更新したときなど、柔軟に読み込めると便利かと思います。
そして次回は、ドラッグ&ドロップで読み込んだデータをプロットするアプリケーションを作ってみます。
またドラッグ&ドロップで読み込まなくても、フォルダーを開いて読み込むこともできます。
そちらはまた別の機会に紹介しようかと思います。
完成形イメージ
完成形は以下のようなイメージです。
ちょっと分かりづらいかもしれませんが、ファイルを選んで左の赤枠にドラッグ&ドロップすると、右側に読み込まれたデータが表示されます。
いつも通り、server.Rとui.Rは必須で必要なのですが、今回ブラウザにドラッグしたデータを読み込むために、Javascriptが必要になります。
実装の全体像をざっくり説明します。
- まず、JavascriptファイルにてドラッグしたデータをShinyにおけるinputとして、server.R側に渡す。
- 次に、server.Rファイルにて、受け取ったデータをテーブル表示に変換して、ui.R側に渡す。
- そして、ui,R側で表示する。
という流れになります。
よくあるshinyの実装は、
- ui.Rにて何をinputとするか決めてserver.Rに渡す。
- server.Rにてどう処理するか決めて、ui.Rに戻す。
- 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