shinydashboard(AdminLTE)でスクロールバーが2つできちゃう問題を直す
ウルトラ小ネタです。
スクロールバーが2つできちゃう
shinydashboard を使って開発中、ブラウザの右端のスクロールバーが2つできてしまってレイアウトが崩れるのに気が付きました。
どうも原因は、SidebarとBodyの両方がブラウザの縦幅を超えるほど長い場合に起こるようです。 僕が開発中のアプリの場合、サイドバーのメニューを開いたときにブラウザの縦幅を超えるため、メニューを開閉するたびにレイアウトがズレてしまい、なんとかする必要がありました。
CSS を上書きする
解決方法は、以下に書かれていました。
shinydashboardは AdminLTE という管理画面用Bootstrapテーマを使用しており、そちらのCSSのバグのようです。 つまり、cssを上書きすればOKなので、shinyアプリに使っているcssファイルに、
.wrapper { overflow-y: hidden; }
と書けばOKです。
以上です。
確率分布をさわれるShinyアプリ「確率分布Viewer」に新機能を追加しました!
私が運営中のShinyアプリ「確率分布いろいろ」ですが、 このたびアプリ名を「確率分布Viewer」に変更しました。
それに伴い、 新機能 として、
日本語・英語切替
ブックマーク機能
を追加しました!
ぜひ遊んでみてください。
こちらからアクセス可能です!↓
確率分布Viewer - http://statdist.ksmzn.com
それでは新機能をご紹介します。
新機能1: 日本語・英語切り替え機能
まず、「日本語・英語切り替え機能」 です。
より多くの方に使っていただけるように、英語対応しました。 もちろん、これまでの日本語表示も同様にお使いいただけます。
アプリを起動すると、最初は英語で表示します。 言語を切り替えるには、画面右上の「English」ボタンを押して、表示したい言語を選択します。
試しに「Japanese」を選択にしてみると、日本語表示に切り替わりました。 もちろん、分布の形状は維持したままです。
現在対応しているのは日本語と英語の2つのみです。 他の言語に対応する予定は今の所ございませんが、 プルリクエスト いただけたら喜んで反映させていただきます。
この機能を作るにいたっては、shiny.i18n パッケージを使いました。 詳しく知りたいかたは、こちらの記事↓をご覧ください。
また、英語の文章や単語は、以前翻訳版アプリを作成していただいた @kaz_yosさんにご許可いただき、 こちらにも反映いたしました。 元の英語版はこちらから見ることができます。 @kaz_yosさん、ありがとうございました。
新機能2: ブックマーク機能
shinyの標準機能であるブックマーク機能を搭載しました。
ブックマーク機能とは、Shinyアプリの状態を保存・復元できる機能です。 この機能については後日ブログにまとめる予定なので、ここでは「確率分布Viewer」アプリでの使い方を記します。
例えば、ポアソン分布の画面を開き、適当にパラメータを変更します。 次に、右上の 「Bookmark」 ボタンを押すと、 分布の状態がURLのクエリパラメータとして保存されます!
このURLをコピーし、新しいタブやウィンドウのURL欄に入力して開いてみると、 保存した状態の分布のカタチがそのまま復元できます!
もちろん、他の人に共有することも可能です。 試しにこのURL ↓ にアクセスしてみてください。
もちろん、日本語・英語を切り替えてもしっかり反映されます。 これで、分布の形状を同僚に伝えたいときに活用できますね! ブックマークボタンを魔改造してTweetボタンも付けましたので、是非Twitterにシェアしてみてください。
これから
このアプリを作ったのは3年以上も前なのですが、Shiny とその周辺技術はかなり進歩しており、 私が「こういうことできるかな?」と思ったことがほぼ実現できて驚きました。 (たくさん魔改造も入ってますが)
今回のBookmark機能もそうですし、前回記事でご紹介した「ShinyModule」機能も、 アプリのリファクタリングにかなり役立ちました。
機能が増えたせいか、アプリの起動がちょっと重くなったのが課題です。 プロファイリングなど頑張ります。 アドバイス等ありましたら是非お願いします。
これからもっと便利にするために、
対応する確率分布の数を増やす(3次元とか良いですね)
確率分布同士の関係性を見る機能を作る
ことを考えています。 趣味プロジェクトなのでゆっくりやりたいと思います。
アプリのソースコードは全てこちら↓から閲覧可能です。(レポジトリ名変更しました) 稚拙ですがご参考になれば幸いです。
皆様よろしくお願いします。
「ShinyModule」で中規模Shinyアプリをキレイにする
こんにちは。今日はShiny力を高める記事です。
Shinyは便利ですが、だんだん規模が大きくなったときに、どうコードを書けば良いのか悩みます。 そこで今回は、Shinyコードが多くなってきたときに便利な機能 「ShinyModule」 について説明します。
ShinyModuleとは
概要
ShinyModuleとは、大きくなったShinyアプリを見通しよく書くための機能で、バージョン0.13.0から追加されました 。 その概念を大雑把に述べると、ちっちゃなShinyアプリのようなものです。 大きなShinyアプリから切り出して、関心ごとのShinyアプリ(のようなもの)に分けたのがShiny Moduleというイメージです。
小さなShinyModuleを組み合わせて大きなShinyアプリをつくることで、見通しよいコードとなります。
何が嬉しいのか
ShinyModuleは関数のように再利用することができます。そのため、似たようなパーツを複数持つShinyアプリを作る際に、似たようなコードをコピペせずに済みます。 複数タブで似たようなページを持つアプリで特に便利です。
ここまで聞くと「関数で良いだろ」と思うかもしれませんが、よりShinyに特化したものがShinyModuleというイメージですかね。
一番うれしいのは、名前空間をいい感じに管理してくれることです。
名前空間がやばい
みなさんは、Shinyアプリを作っていて、input・outputのIDが被りそうになった経験はありませんか?
例えば、複数のグラフを載せるアプリの場合、outputのIDを output$result1_plot
, output$result2_plot
, output$result3_plot
... として、管理が大変になったり。
さらに、各プロットの平均も載せようとして、 output$result1_mean
, output$result2_mean
, output$result3_mean
... としてしまうと、ますます混乱していきます。
そんなときこそ、ShinyModuleを使うときです。
まず、server.RからModuleとして切り離し、そのModuleの中で output$plot
, output$mean
という名前でserver側の処理を書きます。
次に、同様にui部分でもModuleとして切り離し、各グラフを識別する ID (この場合、 result1
, result2
, result3
... )を記載するだけでOKです。
これだけで、result1
を渡したときは、アプリ全体の中では output$result1-plot
, output$result1-mean
として認識されるようになります。
書き方
基本
説明だけではイメージしにくいと思うので、実際に書いてみましょう。
ShinyModuleは、次のような型式で書きます。
# UI irisPlotUI <- function(){ # UI } # Server irisPlot <- function(){ # Serverロジック }
UI部分のModuleとServer部分のModuleが必要で、UI側は末尾が Input
, Output
, UI
のいずれかでなければなりません。
Server側はそれらの末尾文字列を消したものと同名である必要があります。
実例 〜 改修前 〜
では、これを踏まえ、irisデータのヒストグラムを作るShinyアプリを書いてみます。こんな感じです。
上側にPetal, 下側にSepalの、それぞれ Length か Width のヒストグラムを表示するアプリです。
ShinyModuleを使わずに素直に書くと、次のようなコードになりました。
# No Shiny Module library(shiny) ui <- fluidPage( selectInput('petal_col', '列:', c('Petal.Length', 'Petal.Width')), plotOutput('petal_plot'), selectInput('sepal_col', '列:', c('Sepal.Length', 'Sepal.Width')), plotOutput('sepal_plot') ) server <- function(input, output, session) { output$petal_plot <- renderPlot({ hist(iris[, input$petal_col]) }) output$sepal_plot <- renderPlot({ hist(iris[, input$sepal_col]) }) } shinyApp(ui, server)
このくらいならわかりやすいですが、ui, serverそれぞれで、コピペしてしまっている箇所がありますね。 さらにプロットを増やしたくなると、煩雑なコードになるのが目に見えています。
これを、ShinyModuleを使って綺麗にしましょう。
Module UI
ShinyModule のUI側は、次のように書きます。
irisPlotUI <- function(id, cols) { ns <- NS(id) tagList( selectInput(ns('col'), '列:', cols), plotOutput(ns('plot')) ) }
id, colsを引数にとり、何らかのアウトプット(この場合はselectInputとプロット)を返します。
ShinyModuleの書き方のルールとして、第一引数は必ず id
です。これが名前を識別するためのidとなります。
第二引数以降は、各Moduleで必要なものを追加できます。
この場合、selectInputに必要な選択肢として、cols
を使うので追加しました。
通常のShinyと違うのは、NS
の部分です。
NS
関数は、識別するための文字列を渡すと、Namespace(名前空間)を管理してくれる関数を返します。
> ns <- shiny::NS('result1') > ns('plot') [1] "result1-plot"
名前空間を管理といっても、識別文字列を先頭に追加するだけです。 単純ですが、これを使って名前が被らないようにしています。
Module Server
次に、これに対応するServer側の書き方を見てみましょう。
irisPlot <- function(input, output, session, main){ output$plot <- renderPlot({ hist(iris[, input$col], main = main) }) }
input, output, sessionを引数にとるところまでは、通常のShinyのserverと同じです。
さらに、追加引数として、そのmodule特有の引数をとることもできます。
この場合、ヒストグラムのタイトルとして main
という引数を追加しています。
お気づきのとおり、単純にoutputのIDを plot
と書くことができます。
ShinyModuleを使わない例のように、わざわざ output$petal-plot
, output$sepal-plot
などと書き分ける必要がありません。
便利ですね。
Module を組み込む
では、これらのModuleを、ui, serverに組み込みましょう。
まず、uiは次のように書きます。
ui <- fluidPage( irisPlotUI('petal', c('Petal.Length', 'Petal.Width')), irisPlotUI('sepal', c('Sepal.Length', 'Sepal.Width')) )
単純に irisPlotUI
を呼び出し、id と 選択肢を引数として渡します。
このidが、それぞれのModuleの間で識別するために使われます。
次に、server側です。
server <- function(input, output, session) { callModule(irisPlot, "petal", "Petal Plot") callModule(irisPlot, "sepal", "Sepal Plot") }
server側の書き方は、少し特殊です。
callModule
関数の第一引数に Module名、第二引数にid, 第三引数以降にはそのModuleで必要なものを渡します。
実例 〜 改修後 〜
以上で改修終了です。最終的にこのようなコードになりました。
# Shiny Module library(shiny) irisPlotUI <- function(id, cols){ ns <- NS(id) tagList( selectInput(ns('col'), '列:', cols), plotOutput(ns('plot')) ) } irisPlot <- function(input, output, session, main){ output$plot <- renderPlot({ hist(iris[, input$col], main = main) }) } ui <- fluidPage( irisPlotUI('petal', c('Petal.Length', 'Petal.Width')), irisPlotUI('sepal', c('Sepal.Length', 'Sepal.Width')) ) server <- function(input, output, session) { callModule(irisPlot, "petal", "Petal Plot") callModule(irisPlot, "sepal", "Sepal Plot") } shinyApp(ui, server)
行数さえ増えたものの、見通しがよくなりました。
コアの機能部分が独立したModuleになっているので、ui, server内で無駄なコピペが減っています。 もっとプロットを増やしたくなっても、簡単に修正可能です。 さらに、名前管理も自前でやる部分が少なくなり、心理的負担も減りました。
終わりに
ShinyModuleによる見通しのよい書き方をご説明しました。 いきなりShinyModuleを使う必要はありませんが、アプリが大きくなり、処理を関数に分けたくなったときに検討する価値はあります。
僕が運営している 「ShinyDistributionsApp」 というアプリでは、ShinyModule部分を module.R という別のRファイルに書き、global.Rの中から読み込むようにしています。 こうすると、ui.R, server.Rが簡潔になるのでオススメです。 特にShinyDashboardを使う場合はui.R, server.Rが長くなりがちなので、是非試してみてください。 コードは こちら です。
参考文献
- http://shiny.rstudio.com/articles/modules.html
- https://www.rstudio.com/resources/videos/modularizing-shiny-app-code/
- https://www.rstudio.com/resources/videos/shiny-modules/
- https://beta.rstudioconnect.com/content/2816/Shiny-Modules.html
- http://zevross.com/blog/2016/04/19/r-powered-web-applications-with-shiny-a-tutorial-and-cheat-sheet-with-40-example-apps/#create-re-useable-ui-elements