本文へスキップ
からだにいいもの

Rのトピックスを中心に『まだ、まだ、知らない、役に立つ情報?』を発信します。

Rで解析:タブと入力欄の揃ったShinyアプリを少ないコードで作る「EZRShiny」パッケージ

「EZRShiny」パッケージは、ナビゲーションバーとタブを備えたShinyアプリの画面を、少ない行数で見た目の揃った形に組み立てられるパッケージです。入力欄や出力欄のラベルに説明のツールチップを付けたり、操作に合わせてタブやボタンを出し入れしたりもできます。渡すのはタブの名前と、その中に並べる入力欄・出力欄で、手元のデータフレームを見せるための画面をその並びのとおりに書いていきます。shinyとbslibの上に乗る拡張なので、UIとserverに分けるShinyアプリの基本的な書き方を知っていることが前提です。集計結果を確かめる画面を周りの人と共有したい人が、UIの組み立てに時間をかけずに画面を用意できるのではないかと考えます。

パッケージバージョンは0.1.0。Windows 11 x64 (build 26200)のR version 4.6.1で確認しています。

パッケージのインストール

下記コマンドを実行してください。

# パッケージのインストール
install.packages("EZRShiny")

# パッケージの読み込み
library("EZRShiny")
スポンサーリンク

コマンド例

詳細はコメント、パッケージのヘルプを確認してください。例は、京都府内の直売所に九条ねぎ・賀茂なす・万願寺とうがらしが週ごとに出荷された量を記録した架空のデータです。このデータを見せる画面を、部品ごとに作っていきます。

EZRShinyの入力欄・出力欄のコマンドは、Shinyの画面部品(HTML)を返します。単独で実行するとHTMLが表示されるだけなので、実際の見た目はアプリを起動して確かめます。

# 京都の野菜の週ごとの出荷量(架空のデータ)
set.seed(1234)
farmData <- data.frame(
  日付 = rep(seq(as.Date("2026-04-01"), by = "week", length.out = 12), times = 3),
  品目 = rep(c("九条ねぎ", "賀茂なす", "万願寺とうがらし"), each = 12),
  産地 = sample(c("伏見区", "亀岡市", "京丹後市"), 36, replace = TRUE),
  出荷量 = round(c(rnorm(12, 120, 15), rnorm(12, 80, 20), rnorm(12, 95, 10)))
)
head(farmData)
        日付     品目     産地 出荷量
1 2026-04-01 九条ねぎ   亀岡市    120
2 2026-04-08 九条ねぎ   亀岡市    106
3 2026-04-15 九条ねぎ   伏見区    137
4 2026-04-22 九条ねぎ 京丹後市    113
5 2026-04-29 九条ねぎ   伏見区    109
6 2026-05-06 九条ねぎ   伏見区    112

ラベルにツールチップを付ける:addTooltipコマンド

オプション意味初期値
labelラベル。文字列かshinyのタグなし
tooltipTextマウスを重ねたときに表示する文。NAかNULLでツールチップを付けず、ラベルをそのまま返すなし
# 「出荷量」のラベルに単位の説明を付ける
addTooltip("出荷量", "単位はkgです。直売所の伝票から集計しています")
[[1]]
<div class="container-fluid">
  <bslib-tooltip placement="auto" bsOptions="[]" data-require-bs-version="5" data-require-bs-caller="tooltip()">
    <template>単位はkgです。直売所の伝票から集計しています</template>
    出荷量
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="bi bi-info-circle " style="height:1em;width:1em;fill:currentColor;vertical-align:-0.125em;" aria-hidden="true" role="img" ><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"></path>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"></path></svg>
  </bslib-tooltip>
</div>

# NAを渡すとツールチップを付けず、ラベルをそのまま返す
addTooltip("出荷量", NA)
[1] "出荷量"

説明アイコン付きの文を置く:navSpanTextコマンド

オプション意味初期値
text表示する文なし
tooltipTextアイコンにマウスを重ねたときに表示する文NA
# 集計の範囲を、文の後ろの説明アイコンで補う
navSpanText("2026年度 春の出荷",
  tooltipText = "4月から6月に伏見区・亀岡市・京丹後市の直売所へ出荷された分です"
)
<span>
  2026年度 春の出荷
  <bslib-tooltip placement="bottom" bsOptions="[]" data-require-bs-version="5" data-require-bs-caller="tooltip()">
    <template>4月から6月に伏見区・亀岡市・京丹後市の直売所へ出荷された分です</template>
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="bi bi-info-circle " style="height:1em;width:1em;fill:currentColor;vertical-align:-0.125em;" aria-hidden="true" role="img" ><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"></path>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"></path></svg>
  </bslib-tooltip>
</span>

選択メニューを作る:navSelectコマンド

オプション意味初期値
inputId入力欄のID。serverではinput$<inputId>で読むなし
label入力欄の上か横に表示するラベルなし
choices選択肢。NULLのままにして、後からserverでshiny::updateSelectizeInput()で入れることもできるNULL
selected最初に選ばれている選択肢。初期値は1つ目の選択肢choices[1]
multipleTRUEで複数を選べるようにするFALSE
createTRUEで選択肢に無い値を入力できるようにするFALSE
tooltipTextラベルに付ける説明のツールチップNA
# 品目を複数選べるメニュー。最初は九条ねぎと賀茂なすを選んでおく
navSelect("item", "品目",
  choices = unique(farmData$品目),
  selected = c("九条ねぎ", "賀茂なす"),
  multiple = TRUE,
  tooltipText = "複数の品目を重ねて比べられます"
)
<div class="form-group shiny-input-container" style="width:100%;">
  <label class="control-label" id="item-label" for="item">
    <bslib-tooltip placement="auto" bsOptions="[]" data-require-bs-version="5" data-require-bs-caller="tooltip()">
      <template>複数の品目を重ねて比べられます</template>
      品目
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="bi bi-info-circle " style="height:1em;width:1em;fill:currentColor;vertical-align:-0.125em;" aria-hidden="true" role="img" ><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"></path>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"></path></svg>
    </bslib-tooltip>
  </label>
  <div>
    <select class="shiny-input-select form-control" id="item" multiple="multiple"><option value="九条ねぎ" selected>九条ねぎ</option>
<option value="賀茂なす" selected>賀茂なす</option>
<option value="万願寺とうがらし">万願寺とうがらし</option></select>
    <script type="application/json" data-for="item">{"create":false,"plugins":["selectize-plugin-a11y"]}</script>
  </div>
</div>

# 産地のメニュー。一覧に無い産地も入力できるようにする
navSelect("area", "産地",
  choices = c("伏見区", "亀岡市", "京丹後市"),
  create = TRUE
)
<div class="form-group shiny-input-container" style="width:100%;">
  <label class="control-label" id="area-label" for="area">産地</label>
  <div>
    <select class="shiny-input-select form-control" id="area"><option value="伏見区" selected>伏見区</option>
<option value="亀岡市">亀岡市</option>
<option value="京丹後市">京丹後市</option></select>
    <script type="application/json" data-for="area">{"create":true,"plugins":["selectize-plugin-a11y"]}</script>
  </div>
</div>

数値の入力欄を作る:navNumericコマンド

オプション意味初期値
inputId入力欄のID。serverではinput$<inputId>で読むなし
label入力欄の上か横に表示するラベルなし
value最初に入っている数値1
min入力できる最小値。NAで制限なしNA
max入力できる最大値。NAで制限なしNA
tooltipTextラベルに付ける説明のツールチップNA
# 出荷量の下限。0から200kgの間で入力させる
navNumeric("minAmount", "出荷量の下限(kg)",
  value = 50, min = 0, max = 200,
  tooltipText = "この値より少ない週は集計から外します"
)
<div class="form-group shiny-input-container" style="width:100%;">
  <label class="control-label" id="minAmount-label" for="minAmount">
    <bslib-tooltip placement="auto" bsOptions="[]" data-require-bs-version="5" data-require-bs-caller="tooltip()">
      <template>この値より少ない週は集計から外します</template>
      出荷量の下限(kg)
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="bi bi-info-circle " style="height:1em;width:1em;fill:currentColor;vertical-align:-0.125em;" aria-hidden="true" role="img" ><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"></path>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"></path></svg>
    </bslib-tooltip>
  </label>
  <input id="minAmount" type="number" class="shiny-input-number form-control" value="50" data-update-on="change" min="0" max="200"/>
</div>

日付の入力欄を作る:navDateコマンド

オプション意味初期値
inputId入力欄のID。serverではinput$<inputId>で読むなし
label入力欄の上か横に表示するラベルなし
rangeTRUEで開始日と終了日を選ぶ。FALSEで1日だけ選ぶFALSE
tooltipTextラベルに付ける説明のツールチップNA
# 集計期間を開始日と終了日で選ばせる
navDate("period", "集計期間", range = TRUE)
<div class="form-group shiny-input-container">
  <label class="control-label" id="period-label" for="period">集計期間</label>
  <input id="period" class="sw-air-picker form-control" type="text" autocomplete="off"/>
  <script type="application/json" data-for="period">{"updateOn":"change","todayButtonAsDate":false,"language":"EN","options":{"autoClose":false,"timepicker":false,"range":true,"dateFormat":"yyyy-MM-dd","multipleDates":false,"multipleDatesSeparator":" to ","view":"days","minView":"days","clearButton":true,"todayButton":false,"buttons":"clear","monthsField":"monthsShort","onlyTimepicker":false,"toggleSelected":true}}</script>
</div>

# 1日だけ選ばせる
navDate("shipDay", "出荷日")
<div class="form-group shiny-input-container">
  <label class="control-label" id="shipDay-label" for="shipDay">出荷日</label>
  <input id="shipDay" class="sw-air-picker form-control" type="text" autocomplete="off"/>
  <script type="application/json" data-for="shipDay">{"updateOn":"change","todayButtonAsDate":false,"language":"EN","options":{"autoClose":false,"timepicker":false,"range":false,"dateFormat":"yyyy-MM-dd","multipleDates":false,"multipleDatesSeparator":" to ","view":"days","minView":"days","clearButton":true,"todayButton":false,"buttons":"clear","monthsField":"monthsShort","onlyTimepicker":false,"toggleSelected":true}}</script>
</div>

このほかの入力欄も、inputId、labelの順で同じように書けます。ボタンはnavButton、ファイルの読み込みはnavUpload、ダウンロードはnavDownload、チェックボックスはnavCheckbox、文字の入力はnavText、色の選択はnavColorです。

プロットの出力欄を作る:navOutputPlotコマンド

オプション意味初期値
outputId出力欄のID。serverでoutput$<outputId> <- render…()として中身を入れるなし
label出力欄の上に表示する見出しNULL
tooltipText見出しの後ろに付ける説明のツールチップNA
# 週ごとの出荷量のプロットを置く欄。見出しに説明を付ける
navOutputPlot("amountPlot",
  label = "週ごとの出荷量",
  tooltipText = "選んだ品目を色分けしてプロットします"
)
<div class="ezr-output-label" style="font-weight: 500; margin-bottom: 0.25rem;">
  <bslib-tooltip placement="auto" bsOptions="[]" data-require-bs-version="5" data-require-bs-caller="tooltip()">
    <template>選んだ品目を色分けしてプロットします</template>
    週ごとの出荷量
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="bi bi-info-circle " style="height:1em;width:1em;fill:currentColor;vertical-align:-0.125em;" aria-hidden="true" role="img" ><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"></path>
<path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"></path></svg>
  </bslib-tooltip>
</div>
<div class="shiny-plot-output html-fill-item" id="amountPlot" style="width:80%;height:400px;"></div>

表はnavOutputTable、文字はnavOutputText、plotlyのグラフはnavOutputPlotly、ggiraphのグラフはnavOutputGirafe、画像はnavOutputPicで、オプションはnavOutputPlotと同じです。

1段のタブを作る:singleTabコマンド

オプション意味初期値
titleナビゲーションバーに表示するタブの名前なし
…タブの中に並べる入力欄・出力欄なし
iconタブの名前の前に付けるアイコン。bsicons::bs_icon(“table”)などNULL
heightタブの中身を入れるカードの高さ。options(EZRShiny.cardHeight = “70vh”)で全タブの初期値を変えられるcardHeight()
# 「出荷の推移」タブ。品目のメニューとプロットの欄を上から並べる
trendTab <- singleTab("出荷の推移",
  navSelect("item", "品目", choices = unique(farmData$品目), multiple = TRUE),
  navOutputPlot("amountPlot", label = "週ごとの出荷量"),
  icon = bsicons::bs_icon("graph-up")
)

サブタブを持つタブを作る:biLevelTabコマンド

オプション意味初期値
titleナビゲーションバーに表示するタブの名前なし
…中に並べるサブタブ。subTabで作るなし
iconタブの名前の前に付けるアイコン。bsicons::bs_icon(“table”)などNULL
idサブタブの並びのID。showNavTabsとhideNavTabsで使う。初期値はtitleから空白を除いたものtitle
heightタブの中身を入れるカードの高さ。options(EZRShiny.cardHeight = “70vh”)で全タブの初期値を変えられるcardHeight()
# 「産地別」タブ。集計ボタンのサブタブと、一覧表のサブタブを持つ
areaTab <- biLevelTab("産地別",
  subTab("集計",
    navNumeric("minAmount", "出荷量の下限(kg)", value = 50, min = 0, max = 200),
    navButton("calc", "産地ごとに集計する"),
    value = "calcTab"
  ),
  subTab("一覧表",
    navOutputTable("areaTable"),
    value = "tableTab"
  ),
  id = "areaTabs"
)

ナビゲーションバー付きの画面を組み立てる:UINavコマンド

オプション意味初期値
…ナビゲーションバーに並べるタブなし
logoFileナビゲーションバーの左に表示する画像のファイル名。左から順に並ぶ。画像はアプリのwwwフォルダに置き、”logo.png”のようにファイル名だけを書く(png、svg、jpg、gif)。NULLか””でロゴなしNULL
appNameロゴの後ろに表示するアプリ名。NULLのときはグローバル変数appNameを使い、無ければ名前なしNULL
logoHeightロゴの高さ。CSSの長さで指定し、全てのロゴに同じ値を使う“40vh”
barColorナビゲーションバーの背景色。色の名前か”#005596″のような16進数。NULLでテーマの色。文字の色は読みやすいように明暗が切り替わるNULL
themeアプリ全体の見た目。bslib::bs_theme(primary = “#005596”, preset = “cosmo”)のように指定するbslib::bs_theme()
# 2つのタブをナビゲーションバーに並べ、バーを京紫に近い色にする
ui <- UINav(
  trendTab,
  areaTab,
  appName = "京都やさい出荷ダッシュボード",
  barColor = "#5B3A8C",
  theme = bslib::bs_theme(primary = "#5B3A8C")
)

入力欄を使えなくする:deactivateItemsコマンド

オプション意味初期値
itemIDs切り替える入力欄のID。文字ベクトルで指定するなし

serverの中で使います。使えるように戻すときはactivateItemsで、オプションは同じです。

# 下限が0のときは集計ボタンを押せなくする
buttonServer <- function(input, output, session) {
  shiny::observe({
    if (isTRUE(input$minAmount > 0)) {
      activateItems("calc")
    } else {
      deactivateItems("calc")
    }
  })
}

サブタブを隠す:hideNavTabsコマンド

オプション意味初期値
rootIDタブの並びのIDなし
tabIDs隠す、または表示するタブのID。文字ベクトルで指定するなし

serverの中で使います。隠したタブを表示するときはshowNavTabsで、オプションは同じです。

# 集計するまでは一覧表のサブタブを隠しておき、ボタンが押されたら表示する
tabServer <- function(input, output, session) {
  hideNavTabs("areaTabs", "tableTab")
  shiny::observeEvent(input$calc, {
    showNavTabs("areaTabs", "tableTab")
  })
}

ここまでの部品をつないで、アプリを起動します。

UIのタブ切り替えと表示制御:showNavTabsコマンド

# serverで、プロット・一覧表の中身と、ボタン・タブの切り替えをまとめる
server <- function(input, output, session) {
  hideNavTabs("areaTabs", "tableTab")

  # 下限が0のときは集計ボタンを押せなくする
  shiny::observe({
    if (isTRUE(input$minAmount > 0)) {
      activateItems("calc")
    } else {
      deactivateItems("calc")
    }
  })

  # 選んだ品目の出荷量を週ごとにプロット
  output$amountPlot <- shiny::renderPlot({
    shiny::req(input$item)
    useData <- farmData[farmData$品目 %in% input$item, ]
    plot(出荷量 ~ 日付,
      data = useData, type = "n",
      xlab = "日付", ylab = "出荷量(kg)"
    )
    for (i in seq_along(input$item)) {
      oneItem <- useData[useData$品目 == input$item[i], ]
      lines(oneItem$日付, oneItem$出荷量, col = i, lwd = 2)
    }
    legend("topright", legend = input$item, col = seq_along(input$item), lwd = 2)
  })

  # ボタンが押されたら、下限以上の週だけを産地ごとに合計して表示する
  areaSum <- shiny::eventReactive(input$calc, {
    useData <- farmData[farmData$出荷量 >= input$minAmount, ]
    aggregate(出荷量 ~ 産地 + 品目, data = useData, FUN = sum)
  })
  output$areaTable <- DT::renderDT(areaSum())
  shiny::observeEvent(input$calc, {
    showNavTabs("areaTabs", "tableTab")
  })
}

# RStudioなどで実行したときにアプリを起動する
if (interactive()) {
  shiny::shinyApp(ui, server)
}

同梱のサンプルアプリを起動する:runExampleAppコマンド

オプション意味初期値
…shiny::runApp()に渡すオプション。launch.browser = TRUEなどなし
# サンプルアプリを作っているファイルの一覧
list.files(system.file("examples", "titanicExplorer", package = "EZRShiny"),
  recursive = TRUE
)
[1] "app.R"
[2] "Functions/1-Read_In_Data_Functions.R"
[3] "Functions/2-Summary_Functions.R"
[4] "Functions/3-Plot_Functions.R"
[5] "Necessary_Files/titanic.csv"
[6] "www/logo.svg"

# RStudioなどで実行したときに、ブラウザでサンプルアプリを開く
if (interactive()) {
  runExampleApp(launch.browser = TRUE)
}

サンプルアプリは、app.Rと、データの読み込み・集計・プロットのコマンドを分けたFunctionsフォルダでできています。自分のアプリをファイルに分けて書くときの見本になります。


この記事が誰かの役に立ちますように。

スポンサーリンク
価格および配送状況は変更される場合があります。購入時は商品ページをご確認ください。
当サイトに表示されている商品情報はAmazonから提供されたものであり、更新または削除される場合があります。
karada-goodはAmazonアソシエイトとして、適格販売により収入を得ています。