shiny.fluent その1
はじめに
shiny.fluent は、Microsoft Fluent UI を Shiny で使うためのパッケージです。
Fluent UI はFluent Design Systemを実装したものです。これは次の記事にあるように四つの原則にもとづいているとのことです。
Shiny Fluent Tutorial: Build Beautiful Shiny Apps をみるとshiny.fluentの使い始め方がわかります。
内容は、概ね、Tutorial: Create your first shiny.fluent dashboardと同じでした。
以下、なぞってみます。
インストール
githubから直接インストールします。
remotes::install_github("Appsilon/shiny.react")
remotes::install_github("Appsilon/shiny.fluent")
リソース
Hello World アプリ
上記チュートリアルでは、初めにHello Worldアプリをつくり、ShinyアプリとしてFluent UIの機能とコンポーネントを使ってテキストを表示する方法を提示しています。
library(shiny)
library(shiny.fluent)
ui <- fluentPage(
Text("Hello World !", variant = "mega")
)
server <- function(input, output) {}
shinyApp(ui = ui, server = server)
UIは、shiny.fluent のfluentPage() を使っています。
テキストは、shiny.fluent のText()コンポーネントをつかって表示しています。ここでは、variant
引数で文字のサイズを指定しています。このようなText
コンポーネントのオプションは、Microsoft Fluent UI ReactのTextに説明があります。
データを表で表示する
データテーブルを表で表示します。データは、shiny.flunent付属のダミーデータ fluentSalesDealsを使用してます。
library(shiny)
library(shiny.fluent)
library(tibble)
columns <- tibble(
fieldName = c('rep_name', "date", "deal_amount", "city", "is_closed"),
name = c("Sales rep", "Close date", "Amount", "City", "Is closed?")
)
ui <- fluentPage(
Text("Hello !", variant = "mega"),
uiOutput("table")
)
server <- function(input, output) {
output$table <- renderUI({
DetailsList(items = fluentSalesDeals,
columns = columns)
})
}
shinyApp(ui = ui, server = server)
UIに、uiOutput()
をつかって、表のアウトプットとしています。対応したアウトプットをserver
にて renderUI()
を使って作成しています。このように、専用のアウトプットやレンダーを提供せず、UI系のものを使っているようです。
shiny.fluentのDetailsList()コンポーネントを使ってデータを表として表示しています。表に表示するカラムと表示名の指定を、columns
引数でおこなっています。カラム名の指定は、データフレームの形でデータ上のカラム名 fieldName
で "rep_name"
と表示上のカラム名 name
で "Sales rep"
を指定しています。
オプションはshiny.fluentのDetailsListやMicrosoft Fluent UI ReactのDetailsListに詳しいです。が、オプションについてはまだ正直よくわかりませんでした。
簡単な入力を追加する
簡単な入力例として、表データをフィルタするトグルを追加しています。表の is_closed
の値が0
の場合は、まだオープンな取引ということなので、これをフィルタするトグルを入力として追加しています。データは入力に従ってフィルタされるため、reactive()
を使っています。
library(shiny)
library(shiny.fluent)
library(tibble)
library(dplyr)
columns <- tibble(
fieldName = c('rep_name', "date", "deal_amount", "city", "is_closed"),
name = c("Sales rep", "Close date", "Amount", "City", "Is closed?")
)
ui <- fluentPage(
Toggle.shinyInput("includeOpen", label = "Include open deals"),
Text("Hello !", variant = "mega"),
uiOutput("table")
)
server <- function(input, output) {
filteredData <- reactive({
fluentSalesDeals %>%
filter(
is_closed | input$includeOpen
)
})
output$table <- renderUI({
DetailsList(items = filteredData,
columns = columns
})
}
shinyApp(ui = ui, server = server)
shiny.fluentのToggle.shinyInputをつかって、トグルスイッチの入力を作成しています。第一引数がインプットの名前、ラベル引数が使えます。コンポーネント名に.shinyInput
とついている関数が提供されていました。
トグルの値はゼロイチなので、フィルタにこれをつかっています。フィルタ対象カラム is_closed
の値もゼロイチなので、これらの論理和(OR)を用いると、トグルの値がゼロの時にis_closed
の値が1のもののみをフィルタアウトすること、トグルの値が1のときにis_closed
の値がゼロでも1でもフィルタアウト(フィルタされない)することができます。
オプションは、Microsoft Fluent UI ReactのToggleに詳しいです。たとえば、トグルスイッチの脇にOn/Offを明示する場合には、次のようにすることがわかります。
Toggle.shinyInput("includeOpen", label = "Include open deals",
onText = "On", offText = "Off")
Cardをつかって見た目を良くする
ページ上のパーツを陰影や間隔をつけて見た目で区別しやすいようにします。ここでは、カードのメタファーで、見た目の区別しやすさを考えています。Fluent UI Style Elevationにdiv
のクラスをつかった見た目の制御があります。ここでは、ms-depth-8
を使います。
ui <- fluentPage(
div(class = "ms-depth-8",
Toggle.shinyInput("includeOpen", label = "Include open deals"),
),
div(class = "ms-depth-8",
Text("Hello !", variant = "mega"),
uiOutput("table")
)
)
つぎに、padding
などを設定して、さらに見やすくします。
ui <- fluentPage(
Stack(
class = "ms-depth-8",
tokens = list(padding = 20, childrenGap = 10),
Text("Filter", variant = "large),
Toggle.shinyInput("includeOpen", label = "Include open deals"),
),
div(
class = "ms-depth-8",
tokens = list(padding = 20, childrenGap = 10),
Text("Sales deals details", variant = "large"),
uiOutput("table")
)
)
shiny.fluentのStackを使用して、コンポーネントを整列しています。Microsoft Fluent UI ReactのStackにpadding
とchildrenGap
の説明があります。
これらのdiv
では、class
を指定し、padding
を指定し、childrenGap
を指定し、タイトルのテキストがあり、コンポーネントがあります。これらの見た目の指定を使いまわせるように関数化します。
Card <- function(..., title = NULL) {
Stack(
class = "ms-depth-8",
tokens = list(padding = 20, childrenGap = 10),
if (!is.null(title)) Text(title, variant = "large"),
...
)
}
このように関数化すると、次のように使用できます。
filter <- tagList(
Toggle.shinyInput("includeOpen", label = "Include open deals")
)
Card(title = "Filter", filter),
Card(title = "Hello.", uiOutput("table"))
Card
はタイトルが指定でき、内容があるものとして関数化できました。filter
用のCard
では、tagList
を使って、Card
の中のコンポーネントを列挙しています。
Gridレイアウト
レイアウトをグリッドにして、デバイスに対してフレキシブルな配置を実現しています。Microsoft Fluent UI ReactのLayoutにGridレイアウトの説明があり、定義に使用できるクラス名とその説明があります。
クラス名 ms-Grid
が Grid レイアウトの宣言となり、ms-Grid-col
はカラムであることを指定し、ms-sm12
はウィンドウサイズがsmall(320px - 479px)のときに幅が12カラムとなるように指定しています。
div(class="ms-Grid", dir="ltr", style = "padding: 0px",
div(class="ms-Grid-col ms-sm12", style = "passing: 10px",
Card(title = "Filter", filter),
),
div(class="ms-Grid-col ms-sm12", style = "passing: 10px",
Card(title = "Hello.", uiOutput("table"))
)
)
繰り返されるdiv
の定義部分、一個めのdiv
とそれ以降のdiv
をそれぞれGrid
とGridItem
として二つ関数化すると、つぎのようにできるようになります。
Grid <- function(...) {
div(
class = "ms-Grid",
dir = "ltr",
style = "padding: 0px",
...
)
}
GridItem <- function(..., class = "ms-sm12") {
div(
class = paste("ms-Grid-col", class),
style = "padding: 10px",
...
)
}
Grid(
GridItem(Card(title = "Filter", filter)),
GridItem(Card(title = "Hello.", uiOutput("table"))
)
Grid
には、GridItem
を引数としてとることができ、GridItem
には、Card
を引数としてとることができました。
他のフィルターを追加する
fluentSalesDealsデータのSales repのフィルターを追加します。fluentPeopleデータを使います。このデータは、key, imageUrl, imageInitials, text, secandaryText, tertiaryText, optionalText, isValid, presence, canExpand, colorのカラムで構成されています。
NormalPeoplePicker.shinyInputは、PeoplePickerをShinyで使用できるようにしたもので、データに含まれる名前や画像情報、プレゼンス情報からなるコンポーネントを人物選択フォームの選択時に適用できるようにし、リッチな人物選択フォームを実現しています。
filters <- tagList(
div(
Label("Salse Representative"),
NormalPeoplePicker.shinyInput("people", options = fluentPeople)
),
Toggle.shinyInput("includeOpen", label = "Include open deals")
)
NormalPeoplePicker
の選択の値は input$people
として データのフィルタに使用します。
filteredData <- reactive({
fluentSalesDeals %>% filter(
length(input$people) == 0 | rep_id %in% input$people,
is_closed | input$includeOpen
)
})
ここでは filter
で論理和(OR)をつかい、input$people
で選択数がゼロの時はすべてTRUEとなるので全レコードがのこり、input$people
に選択数が1個以上ある場合はOR以降の条件でフィルタされるようにしています。つまり無選択では全レコードを表示するためにフィルタされず、誰かが選択されているその条件でフィルタされるというものです。
棒グラフを追加する
いくつかのフィルター入力でデータを選択できるようになったので、選択したデータを棒グラフで表示するようにしています。
ここでは、ウィンドウの幅に対応してフィルターと棒グラフの配置を変えるようにGridレイアウトのクラスを指定しています。フィルターはms-sm12
とms-xl4
を指定し、棒グラフはms-sm12
とms-xl8
と指定します。こうすると、ウインドウ幅が狭い(small, 320px - 479px)ときはどちらも幅12カラムとなり、広い(extra large, 1024px - 1365px)ときはフィルターが幅4カラム、棒グラフが幅8カラムとなるようになります。Layoutに解説があります。
棒グラフの表示には、plotlyOutput
を使用し、高さを指定しています。
ui <- fluentPage(
Grid(
GridItem(class = "ms-sm12 ms-xl4",
Card(title = "Filters", filters)
),
GridItem(class = "ms-sm12 ms-xl8",
Card(title = " Deals count",
div(
plotlyOutput("plot", height = "300px")
)
)
),
GridItem(
Card(title = "Deals data",
div(style = 'height: 500px; overflow: auto',
uiOutput("table")
)
)
)
)
)
棒グラフの描画には、ggplot2とPlotlyを使用しています。
output$plot <- renderPlotly({
ggplot(filteredData(), aes(x = rep_name)) +
geom_bar(fill = unique(filteredData()$color)) +
xlab("Sales rep") +
ylab("Number of deals") +
theme_light()
})
おわりに
チュートリアルをひととおり見た時のメモとして作成しました。PeoplePickerのようなリッチなUIやGridレイアウトがすぐに使えることがわかったのはよかったです。
一方で、shiny.fluent の使い始めについてはわかったのですが、DetailsListでカラム名をクリックするとソートできるようするにはどうすれば良いのかなどの実際の使用するときの要求事項の実装方法についてはまだわからない状態です。Reactの理解がある程度ないと使いこなせないのかと思いました。
次は、Tutorial: Build a Full Shiny Dashboard With shiny.fluentを見てみます。
Discussion