Value Spring: Tinder for Cancer
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

283 行
8.7KB

  1. library(shinysense)
  2. library(htmlwidgets)
  3. library(shiny)
  4. library(dplyr)
  5. library(shinyjs)
  6. library(ggplot2)
  7. # ---- APP PARAMETERS ----
  8. APP_TITLE <- "App Title"
  9. HELP_TEXT <- "Some guidance text can go here"
  10. SWIPES_MEAN <- c(
  11. "left" = -1L,
  12. "right" = 1L
  13. )
  14. top_male_names <- readRDS(here::here("data/top_male_names.rds"))
  15. random_name <- function() sample(top_male_names, 1)
  16. possessive_name <- function(x) if (substr(x, nchar(x), nchar(x)) == "s") paste0(x, "'") else paste0(x, "'s")
  17. questions <- readxl::read_xlsx(here::here("docs/questions.xlsx")) %>%
  18. arrange(order)
  19. plot_swipe_results <- function(questions, results, SWIPES_MEAN) {
  20. n_category <- questions %>% group_by(Category) %>% count()
  21. qx <- questions %>%
  22. left_join(results, by = c(id = "question_id")) %>%
  23. mutate(score = -SWIPES_MEAN[swipe]) %>%
  24. group_by(Category) %>%
  25. summarize(score = mean(score)) %>%
  26. left_join(n_category, by = "Category")
  27. ggplot(qx) +
  28. aes(x = Category, y = score, color = score) +
  29. geom_point(size = 5) +
  30. scale_y_continuous(limits = c(-1, 1), breaks = -1:1, labels = c("Acceptable", "Ambivalent", "Undesirable")) +
  31. scale_color_gradient2(low = "#82c878", high = "#eb1455", mid = "#ffcd5a", limits = c(-1, 1)) +
  32. coord_flip() +
  33. guides(color = FALSE) +
  34. labs(x=NULL, y=NULL) +
  35. hrbrthemes::theme_ipsum_ps(base_size = 26, axis_title_just = "cc") +
  36. facet_wrap(~ Category, ncol = 1, scales = "free_y") +
  37. theme(
  38. panel.grid.minor.x = element_blank(),
  39. panel.grid.major.x = element_blank(),
  40. axis.text.y = element_blank()
  41. )
  42. }
  43. phone_container <- function(...) {
  44. div(
  45. class = "phone-container",
  46. div(
  47. class = "marvel-device iphone8plus black",
  48. div(class = "top-bar"),
  49. div(class = "sleep"),
  50. div(class = "volume"),
  51. div(class = "camera"),
  52. div(class = "sensor"),
  53. div(class = "speaker"),
  54. div(class = "screen", ...),
  55. div(class = "home"),
  56. div(class = "bottom-bar")
  57. )
  58. )
  59. }
  60. swipe_help_row_div <- div(
  61. class = "bottom-help text-moffitt-grey",
  62. div(class = "bottom-help-left", img(src = "/images/swipe-left.svg", width = "50px"), p("Bad")),
  63. div(class = "bottom-help-right", img(src = "/images/swipe-right.svg", width = "50px"), p("Okay")),
  64. div(class = "bottom-help-middle", span("Swipe Card", class = "bottom-help-middle-text"))
  65. )
  66. source("screens.R", local = TRUE)
  67. ui <- fixedPage(
  68. includeCSS("www/css/devices.min.css"),
  69. includeCSS("www/css/t4c.css"),
  70. includeCSS("www/css/moffitt-colors.css"),
  71. includeCSS("www/css/animate.css"),
  72. shinyjs::useShinyjs(),
  73. tags$link(rel = "stylesheet", type = "text/css", href = "https://fonts.googleapis.com/css?family=IBM+Plex+Sans:100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,700i"),
  74. tags$link(rel = "stylesheet", type = "text/css", href = "https://fonts.googleapis.com/css?family=Amatic+SC:400,700|Coming+Soon"),
  75. p(),
  76. phone_container(
  77. screens[["splash-overlay"]],
  78. screens[["app-intro"]],
  79. screens[["app-main"]],
  80. screens[["app-story"]],
  81. screens[["app-results"]]
  82. ),
  83. hr(),
  84. actionButton("toggle_splash", "Toggle Splash"),
  85. actionButton("back", "Back"),
  86. actionButton("jump_results", "Jump to Last Results"),
  87. h4("Answer History"),
  88. dataTableOutput("results_table")
  89. )
  90. server <- function(input, output, session) {
  91. app_data <- reactiveValues(
  92. init = 1L,
  93. iter = 1L,
  94. results = data_frame(question_id = integer(), swipe = character()),
  95. complete = FALSE,
  96. splash = TRUE,
  97. screen_prev = NULL,
  98. screen_active = "splash-overlay",
  99. screen_next = NULL
  100. )
  101. app_screens <- c(
  102. "splash-overlay" = 1,
  103. "app-intro" = 2,
  104. "app-main" = 3,
  105. "app-results" = 10
  106. )
  107. get_screen_next <- function(x) names(app_screens)[which(names(app_screens) == x) + 1L]
  108. get_screen_prev <- function(x) names(app_screens)[which(names(app_screens) == x) - 1L]
  109. question <- reactive({
  110. if (app_data$iter > length(question_ids)) return(NULL)
  111. questions %>%
  112. filter(id == question_ids[[app_data$iter]])
  113. })
  114. card_swipe <- callModule(shinyswipr, "card_swipe_ui")
  115. card_swipe_test <- callModule(shinyswipr, "test_card_swipe_ui")
  116. question_ids <- arrange(questions, order) %>% pull(id)
  117. output$question_text <- renderText({
  118. if (!app_data$complete) {
  119. question()$`Question Text`
  120. } else {
  121. "No more questions at this time. Hang on while we crunch the numbers..."
  122. }
  123. })
  124. output$question_category <- renderText({
  125. if (!app_data$complete) question()$Category else "Thank You!"
  126. })
  127. output$question_topic <- renderText({
  128. if (!app_data$complete) question()$Topic else "All Done!"
  129. })
  130. output$results_table <- renderDataTable({
  131. app_data$results
  132. })
  133. output$question_story <- renderUI({
  134. if (!app_data$complete) {
  135. q_story <- question()$Story
  136. if (is.na(q_story)) return(p("No story."))
  137. tagList(
  138. h1(app_data$male_name, "Experience"),
  139. q_story %>%
  140. strsplit("\\s*(\r\n){1,2}") %>%
  141. .[[1]] %>%
  142. purrr::map(~ tags$p(.))
  143. )
  144. }
  145. })
  146. output$question_story_link <- renderUI({
  147. if (app_data$complete) return(NULL)
  148. q_story <- question()$Story
  149. if (is.na(q_story)) return(NULL)
  150. app_data$male_name <- possessive_name(random_name())
  151. actionLink("app_main_show_story",
  152. paste("Read", app_data$male_name, "story..."))
  153. })
  154. output$results_plot <- renderPlot({
  155. req(app_data$complete)
  156. plot_swipe_results(questions, app_data$results, SWIPES_MEAN)
  157. })
  158. observe({
  159. # cat("iter:", app_data$iter, "\n")
  160. # cat("ques:", question()$`Question Text`, "\n")
  161. # print(app_data$results)
  162. # str(app_data)
  163. })
  164. # on main app card swipe
  165. observeEvent(card_swipe(), {
  166. if (app_data$complete) {
  167. # cat("\nNothing to do.")
  168. saveRDS(app_data$results, "results.rds")
  169. app_data$screen_next <- "app-results"
  170. return()
  171. }
  172. # save swipe event
  173. app_data$results <- bind_rows(
  174. data_frame(
  175. question_id = question()$id,
  176. swipe = card_swipe()
  177. ),
  178. app_data$results
  179. )
  180. # update app_data
  181. Sys.sleep(0.5) # wait for animation to complete
  182. app_data$iter <- app_data$iter + 1L
  183. next_q <- question()
  184. if (is.null(next_q)) app_data$complete <- TRUE
  185. })
  186. observeEvent(input$splash_next_screen, {
  187. app_data$screen_next <- get_screen_next("splash-overlay")
  188. })
  189. observeEvent(input$toggle_splash, {
  190. if (!app_data$screen_active == "splash-overlay") {
  191. app_data$screen_next <- "splash-overlay"
  192. } else {
  193. app_data$screen_next <- app_data$screen_prev
  194. }
  195. })
  196. observeEvent(input$back, {
  197. if (!is.null(app_data$screen_prev)) app_data$screen_next <- app_data$screen_prev
  198. })
  199. observeEvent(input$jump_results, {
  200. last_results_file <- "results.rds"
  201. if (!file.exists(last_results_file)) {
  202. cat("No results saved yet.")
  203. return()
  204. }
  205. app_data$results <- readRDS(last_results_file)
  206. app_data$complete <- TRUE
  207. app_data$screen_next <- "app-results"
  208. })
  209. observeEvent(card_swipe_test(), {
  210. shinyjs::hide("test-card-swipe-container", anim = TRUE, animType = "fade", time = 1)
  211. Sys.sleep(1)
  212. shinyjs::show("test_swipe_success", anim = TRUE, animType = "fade")
  213. })
  214. observeEvent(input$app_intro_next, {
  215. app_data$screen_next <- get_screen_next("app-intro")
  216. })
  217. observeEvent(input$app_main_show_story, {
  218. shinyjs::removeClass("app-story", "fadeOutUp")
  219. shinyjs::addClass("app-story", "fadeInDown")
  220. })
  221. observeEvent(input$app_main_hide_story, {
  222. shinyjs::addClass("app-story", "fadeOutUp")
  223. shinyjs::removeClass("app-story", "fadeInDown")
  224. })
  225. observeEvent(app_data$complete, {
  226. if (!app_data$complete) return()
  227. app_data$screen_next <- "app-results"
  228. })
  229. observeEvent(app_data$screen_next, {
  230. if (is.null(app_data$screen_next)) return()
  231. # cat("\n", app_data$screen_active, "to", app_data$screen_next)
  232. if (app_screens[app_data$screen_active] < app_screens[app_data$screen_next]) {
  233. # Move forward: active out left, next in right
  234. shinyjs::removeClass(app_data$screen_active, "slideInLeft slideInRight")
  235. shinyjs::removeClass(app_data$screen_next, "slideOutLeft slideOutRight")
  236. shinyjs::addClass(app_data$screen_active, "slideOutLeft")
  237. shinyjs::addClass(app_data$screen_next, "slideInRight")
  238. } else {
  239. # Move backward: active out right, next in left
  240. shinyjs::removeClass(app_data$screen_active, "slideInLeft slideInRight")
  241. shinyjs::removeClass(app_data$screen_next, "slideOutLeft slideOutRight")
  242. shinyjs::addClass(app_data$screen_active, "slideOutRight")
  243. shinyjs::addClass(app_data$screen_next, "slideInLeft")
  244. }
  245. # Update screen state
  246. app_data$screen_prev <- app_data$screen_active
  247. app_data$screen_active <- app_data$screen_next
  248. app_data$screen_next <- NULL
  249. }, priority = -100)
  250. }
  251. shinyApp(ui = ui, server = server)