sugitora commited on
Commit
fa4e707
·
verified ·
1 Parent(s): 2984034

Update app.R

Browse files
Files changed (1) hide show
  1. app.R +174 -45
app.R CHANGED
@@ -1,58 +1,187 @@
 
 
 
 
 
1
  library(shiny)
2
  library(bslib)
3
- library(dplyr)
4
- library(ggplot2)
 
 
 
5
 
6
- df <- readr::read_csv("penguins.csv")
7
- # Find subset of columns that are suitable for scatter plot
8
- df_num <- df |> select(where(is.numeric), -Year)
 
 
 
 
 
9
 
10
- ui <- page_sidebar(
11
- theme = bs_theme(bootswatch = "minty"),
12
- title = "Penguins explorer",
 
 
 
 
13
  sidebar = sidebar(
14
- varSelectInput("xvar", "X variable", df_num, selected = "Bill Length (mm)"),
15
- varSelectInput("yvar", "Y variable", df_num, selected = "Bill Depth (mm)"),
16
- checkboxGroupInput("species", "Filter by species",
17
- choices = unique(df$Species), selected = unique(df$Species)
18
- ),
19
- hr(), # Add a horizontal rule
20
- checkboxInput("by_species", "Show species", TRUE),
21
- checkboxInput("show_margins", "Show marginal plots", TRUE),
22
- checkboxInput("smooth", "Add smoother"),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  ),
24
- plotOutput("scatter")
 
 
 
 
 
 
 
 
 
 
 
25
  )
26
 
 
27
  server <- function(input, output, session) {
28
- subsetted <- reactive({
29
- req(input$species)
30
- df |> filter(Species %in% input$species)
31
- })
32
-
33
- output$scatter <- renderPlot(
34
- {
35
- p <- ggplot(subsetted(), aes(!!input$xvar, !!input$yvar)) +
36
- theme_light() +
37
- list(
38
- theme(legend.position = "bottom"),
39
- if (input$by_species) aes(color = Species),
40
- geom_point(),
41
- if (input$smooth) geom_smooth()
42
- )
43
-
44
- if (input$show_margins) {
45
- margin_type <- if (input$by_species) "density" else "histogram"
46
- p <- p |> ggExtra::ggMarginal(
47
- type = margin_type, margins = "both",
48
- size = 8, groupColour = input$by_species, groupFill = input$by_species
49
- )
50
  }
51
-
52
- p
53
- },
54
- res = 100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  )
56
  }
57
 
58
- shinyApp(ui, server)
 
1
+ # install.packages("shinyWidgets")
2
+ # install.packages("shinycssloaders")
3
+ # install.packages("bslib")
4
+ # install.packages("plotly")
5
+
6
  library(shiny)
7
  library(bslib)
8
+ library(tidyverse)
9
+ library(DT)
10
+ library(plotly)
11
+ library(shinyWidgets)
12
+ library(shinycssloaders)
13
 
14
+ # --- カスタムCSS(LINE風の配色とカードデザイン) ---
15
+ custom_css <- "
16
+ .main-title { color: #06C755; font-weight: bold; margin-bottom: 20px; }
17
+ .card { border-radius: 15px; border: none; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
18
+ .btn-primary { background-color: #06C755; border: none; }
19
+ .btn-primary:hover { background-color: #05a346; }
20
+ .nav-tabs .nav-link.active { border-bottom: 3px solid #06C755 !important; color: #06C755 !important; }
21
+ "
22
 
23
+ # --- UI ---
24
+ ui <- page_navbar(
25
+ title = span("Quali Fit トーク履歴解析", class = "main-title"),
26
+ theme = bs_theme(version = 5, bootswatch = "flatly", primary = "#06C755"),
27
+ header = tags$head(tags$style(custom_css)),
28
+
29
+ # サイドバー(設定・アップロード)
30
  sidebar = sidebar(
31
+ title = "操作パネル",
32
+ fileInput("file1", "履歴ファイルをアップロード", accept = c(".csv", ".txt")),
33
+ hr(),
34
+ uiOutput("filter_ui"),
35
+ hr(),
36
+ downloadButton("downloadData", "CSVでエクスポート", class = "btn-primary w-100")
37
+ ),
38
+
39
+ # メインパネル(タブ構成)
40
+ nav_panel("📊 ダッシュボード",
41
+ layout_column_wrap(
42
+ width = 1/3,
43
+ value_box(
44
+ title = "総メッセージ数",
45
+ value = textOutput("total_msg"),
46
+ showcase = icon("comment-dots")
47
+ ),
48
+ value_box(
49
+ title = "アクティブ人数",
50
+ value = textOutput("total_members"),
51
+ showcase = icon("users")
52
+ ),
53
+ value_box(
54
+ title = "最多発言者",
55
+ value = textOutput("top_sender"),
56
+ showcase = icon("award")
57
+ )
58
+ ),
59
+ layout_column_wrap(
60
+ width = 1/2,
61
+ card(card_header("発言者別の割合"), withSpinner(plotlyOutput("piePlot"))),
62
+ card(card_header("時系列のアクティビティ"), withSpinner(plotlyOutput("timePlot")))
63
+ )
64
+ ),
65
+
66
+ nav_panel("💬 トーク閲覧",
67
+ card(
68
+ full_screen = TRUE,
69
+ card_header("メッセージ履歴"),
70
+ DTOutput("table")
71
+ )
72
  ),
73
+
74
+ nav_panel("ℹ️ 使い方",
75
+ card(
76
+ markdown("
77
+ ### Quali Fit 履歴解析ツール
78
+ 1. 左側のサイドバーから、LINEから書き出した`.txt`または`.csv`ファイルをアップロードしてください。
79
+ 2. **ダッシュボード**タブで、コミュニケーションの統計を確認できます。
80
+ 3. **トーク閲覧**タブで、特定のキーワードや人物で検索が可能です。
81
+ 4. フィルタリング後のデータは、CSVとして保存できます。
82
+ ")
83
+ )
84
+ )
85
  )
86
 
87
+ # --- Server ---
88
  server <- function(input, output, session) {
89
+
90
+ # 解析ロジック
91
+ parsed_df <- reactive({
92
+ req(input$file1)
93
+ lines <- readLines(input$file1$datapath, warn = FALSE, encoding = "UTF-8")
94
+
95
+ chat_list <- list()
96
+ current_date <- NA
97
+ current_thread <- "不明"
98
+
99
+ for (line in lines) {
100
+ if (grepl("\\[LINE\\] (.*?)とのトーク履歴", line)) {
101
+ current_thread <- sub("\\[LINE\\] (.*?)とのトーク履歴.*", "\\1", line)
102
+ next
 
 
 
 
 
 
 
 
103
  }
104
+ if (grepl("^\\d{4}/\\d{2}/\\d{2}", line)) {
105
+ current_date <- sub("^(\\d{4}/\\d{2}/\\d{2}).*", "\\1", line)
106
+ next
107
+ }
108
+ if (grepl("^\"?\\d{2}:\\d{2}\t", line)) {
109
+ clean_line <- gsub("^\"", "", line)
110
+ parts <- strsplit(clean_line, "\t")[[1]]
111
+ if (length(parts) >= 3) {
112
+ chat_list[[length(chat_list) + 1]] <- data.frame(
113
+ Thread = current_thread,
114
+ Date = as.Date(current_date, format="%Y/%m/%d"),
115
+ Time = parts[1],
116
+ Sender = parts[2],
117
+ Message = gsub("\"$", "", parts[3]),
118
+ stringsAsFactors = FALSE
119
+ )
120
+ }
121
+ } else if (length(chat_list) > 0 && !is.na(current_date)) {
122
+ last_idx <- length(chat_list)
123
+ chat_list[[last_idx]]$Message <- paste(chat_list[[last_idx]]$Message, line)
124
+ }
125
+ }
126
+ bind_rows(chat_list)
127
+ })
128
+
129
+ # 動的フィルター
130
+ output$filter_ui <- renderUI({
131
+ df <- parsed_df()
132
+ tagList(
133
+ pickerInput("thread_sel", "スレッド選択:", choices = unique(df$Thread), multiple = TRUE, selected = unique(df$Thread), options = list(`actions-box` = TRUE)),
134
+ pickerInput("sender_sel", "送信者選択:", choices = unique(df$Sender), multiple = TRUE, selected = unique(df$Sender), options = list(`actions-box` = TRUE)),
135
+ # 修正点: btnDel を btnReset に変更
136
+ searchInput("keyword", "本文検索:", placeholder = "キーワード入力...", btnSearch = icon("search"), btnReset = icon("times"))
137
+ )
138
+ })
139
+
140
+ # フィルタリング後のデータ
141
+ filtered_df <- reactive({
142
+ df <- parsed_df()
143
+ if (!is.null(input$thread_sel)) df <- df %>% filter(Thread %in% input$thread_sel)
144
+ if (!is.null(input$sender_sel)) df <- df %>% filter(Sender %in% input$sender_sel)
145
+ if (!is.null(input$keyword) && input$keyword != "") {
146
+ df <- df %>% filter(grepl(input$keyword, Message, ignore.case = TRUE))
147
+ }
148
+ df
149
+ })
150
+
151
+ # --- メトリクス ---
152
+ output$total_msg <- renderText({ nrow(filtered_df()) })
153
+ output$total_members <- renderText({ n_distinct(filtered_df()$Sender) })
154
+ output$top_sender <- renderText({
155
+ res <- filtered_df() %>% count(Sender) %>% slice_max(n, n = 1) %>% pull(Sender)
156
+ if(length(res) > 0) res[1] else "なし"
157
+ })
158
+
159
+ # --- グラフ作成 ---
160
+ output$piePlot <- renderPlotly({
161
+ plot_ly(filtered_df() %>% count(Sender), labels = ~Sender, values = ~n, type = 'pie', hole = 0.4) %>%
162
+ layout(showlegend = TRUE)
163
+ })
164
+
165
+ output$timePlot <- renderPlotly({
166
+ p <- filtered_df() %>% count(Date) %>%
167
+ ggplot(aes(x = Date, y = n)) +
168
+ # 修正点: size を linewidth に変更
169
+ geom_line(color = "#06C755", linewidth = 1) +
170
+ geom_point(color = "#06C755") +
171
+ theme_minimal() + labs(x = "", y = "投稿数")
172
+ ggplotly(p)
173
+ })
174
+
175
+ # --- テーブル ---
176
+ output$table <- renderDT({
177
+ datatable(filtered_df(), options = list(pageLength = 15, autoWidth = TRUE), rownames = FALSE)
178
+ })
179
+
180
+ # --- エクスポート ---
181
+ output$downloadData <- downloadHandler(
182
+ filename = function() { paste0("Struct_Chat_", Sys.Date(), ".csv") },
183
+ content = function(file) { write.csv(filtered_df(), file, row.names = FALSE, fileEncoding = "CP932") }
184
  )
185
  }
186
 
187
+ shinyApp(ui, server)