Value Spring: Tinder for Cancer
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

355 lines
11KB

  1. library(shinysense)
  2. library(htmlwidgets)
  3. library(shiny)
  4. library(dplyr)
  5. library(shinyjs)
  6. # ---- APP PARAMETERS ----
  7. APP_TITLE <- "App Title"
  8. HELP_TEXT <- "Some guidance text can go here"
  9. SWIPES_MEAN <- c(
  10. "left" = -1L,
  11. "right" = 1L
  12. )
  13. top_male_names <- readRDS(here::here("t4c/data/top_male_names.rds"))
  14. random_name <- function() sample(top_male_names, 1)
  15. possessive_name <- function(x) if (substr(x, nchar(x), nchar(x)) == "s") paste0(x, "'") else paste0(x, "'s")
  16. questions <- readxl::read_xlsx(here::here("docs/questions.xlsx")) %>%
  17. arrange(order)
  18. plot_swipe_results <- function(questions, results, SWIPES_MEAN) {
  19. n_category <- questions %>% group_by(Category) %>% count()
  20. qx <- questions %>%
  21. left_join(results, by = c(id = "question_id")) %>%
  22. mutate(score = -SWIPES_MEAN[swipe]) %>%
  23. group_by(Category) %>%
  24. summarize(score = mean(score)) %>%
  25. left_join(n_category, by = "Category")
  26. ggplot(qx) +
  27. aes(x = Category, y = score, color = score) +
  28. geom_point(size = 5) +
  29. scale_y_continuous(limits = c(-1, 1), breaks = -1:1, labels = c("Acceptable", "Ambivalent", "Undesirable")) +
  30. scale_color_gradient2(low = "#82c878", high = "#eb1455", mid = "#ffcd5a", limits = c(-1, 1)) +
  31. coord_flip() +
  32. guides(color = FALSE) +
  33. labs(x=NULL, y=NULL) +
  34. hrbrthemes::theme_ipsum_ps(base_size = 24, axis_title_just = "cc") +
  35. facet_wrap(~ Category, ncol = 1, scales = "free_y") +
  36. theme(
  37. panel.grid.minor.x = element_blank(),
  38. panel.grid.major.x = element_blank(),
  39. axis.text.y = element_blank()
  40. )
  41. }
  42. phone_container <- function(...) {
  43. div(
  44. class = "phone-container",
  45. div(
  46. class = "marvel-device iphone8plus black",
  47. div(class = "top-bar"),
  48. div(class = "sleep"),
  49. div(class = "volume"),
  50. div(class = "camera"),
  51. div(class = "sensor"),
  52. div(class = "speaker"),
  53. div(class = "screen", ...),
  54. div(class = "home"),
  55. div(class = "bottom-bar")
  56. )
  57. )
  58. }
  59. swipe_help_row_div <- div(
  60. class = "bottom-help text-moffitt-grey",
  61. div(class = "bottom-help-left", img(src = "/images/swipe-left.svg", width = "50px"), p("Bad")),
  62. div(class = "bottom-help-right", img(src = "/images/swipe-right.svg", width = "50px"), p("Okay")),
  63. div(class = "bottom-help-middle", span("Swipe Card", class = "bottom-help-middle-text"))
  64. )
  65. ui <- fixedPage(
  66. includeCSS("devices.min.css"),
  67. includeCSS("t4c.css"),
  68. includeCSS("moffitt-colors.css"),
  69. includeCSS("animate.css"),
  70. shinyjs::useShinyjs(),
  71. 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"),
  72. tags$link(rel = "stylesheet", type = "text/css", href = "https://fonts.googleapis.com/css?family=Amatic+SC:400,700|Coming+Soon"),
  73. # h1("A Good Name and Logo"),
  74. # p("This is a simple demo app..."),
  75. # hr(),
  76. p(),
  77. phone_container(
  78. div(
  79. class = "overlay bg-moffitt-grey splash animated", id = "splash-overlay",
  80. div(
  81. class = "splash-body",
  82. h1("Welcome!"),
  83. p("Talking about cancer treatment options is hard, but we're here to help."),
  84. actionButton("splash_next_screen", "Get Started", class = "btn-bottom-right btn-primary")
  85. )
  86. ),
  87. div(
  88. id = "app-intro",
  89. class = "app-main bg-white animated slideOutRight",
  90. h1(APP_TITLE, class = "app-title text-white bg-moffitt-blue"),
  91. p(class = "help-text text-moffitt-grey",
  92. "Guidance about how to approach the questions goes here."),
  93. div(
  94. id = "test-card-swipe-container",
  95. style = "width: 100%; max-height: 275px; position: relative; height: 100%;",
  96. shinyswiprUI(
  97. "test_card_swipe_ui",
  98. div(class = "swiperCard-header",
  99. h4(class = "card-title text-white bg-moffitt-light-blue",
  100. "Try It Now"
  101. )
  102. ),
  103. div(class = "swiperCard-body",
  104. h4("An Example Question"),
  105. p("Something to think about. Swipe", strong("left"), "if you feel X.",
  106. "Swipe", strong("right"), "if you feel Y.")
  107. )
  108. ),
  109. swipe_help_row_div
  110. ),
  111. shinyjs::hidden(div(
  112. id = "test_swipe_success",
  113. style = "font-size: 1.5em; text-align: center; width: 100%; padding: 50px",
  114. p(icon("thumbs-o-up", class = "fa-3x text-moffitt-green"),
  115. br(),
  116. actionLink("app_intro_next", "Great, let's get started!")
  117. )
  118. ))
  119. ),
  120. div(
  121. id = "app-main",
  122. class = "app-main animated",
  123. h1(APP_TITLE, class = "app-title text-white bg-moffitt-blue"),
  124. p(HELP_TEXT, class = "help-text text-moffitt-grey"),
  125. shinyswiprUI(
  126. "card_swipe_ui",
  127. div(class = "swiperCard-header",
  128. h4(class = "card-title text-white bg-moffitt-light-blue",
  129. textOutput("question_category")
  130. )
  131. ),
  132. div(class = "swiperCard-body",
  133. h4(textOutput("question_topic")),
  134. p(textOutput("question_text")),
  135. uiOutput("question_story_link")
  136. )
  137. ),
  138. swipe_help_row_div
  139. ),
  140. div(
  141. id = "app-story",
  142. class = "app-story animated fadeOutUp",
  143. div(class = "close-button", actionLink("app_main_hide_story", NULL, icon("times", class = "fa-lg"))),
  144. htmlOutput("question_story")
  145. ),
  146. div(
  147. id = "app-results",
  148. class = "app-main app-results bg-white animated slideOutRight",
  149. h1(APP_TITLE, class = "app-title text-white bg-moffitt-blue"),
  150. div(class = "app-results-body",
  151. h2("Results", class = "help-text text-moffitt-grey"),
  152. plotOutput("results_plot", height = "250px")
  153. )
  154. )
  155. ),
  156. hr(),
  157. actionButton("toggle_splash", "Toggle Splash"),
  158. actionButton("back", "Back"),
  159. actionButton("jump_results", "Jump to Last Results"),
  160. h4("Answer History"),
  161. dataTableOutput("results_table")
  162. )
  163. server <- function(input, output, session) {
  164. app_data <- reactiveValues(
  165. init = 1L,
  166. iter = 1L,
  167. results = data_frame(question_id = integer(), swipe = character()),
  168. complete = FALSE,
  169. splash = TRUE,
  170. screen_prev = NULL,
  171. screen_active = "splash-overlay",
  172. screen_next = NULL
  173. )
  174. app_screens <- c(
  175. "splash-overlay" = 1,
  176. "app-intro" = 2,
  177. "app-main" = 3,
  178. "app-results" = 10
  179. )
  180. get_screen_next <- function(x) names(app_screens)[which(names(app_screens) == x) + 1L]
  181. get_screen_prev <- function(x) names(app_screens)[which(names(app_screens) == x) - 1L]
  182. question <- reactive({
  183. if (app_data$iter > length(question_ids)) return(NULL)
  184. questions %>%
  185. filter(id == question_ids[[app_data$iter]])
  186. })
  187. card_swipe <- callModule(shinyswipr, "card_swipe_ui")
  188. card_swipe_test <- callModule(shinyswipr, "test_card_swipe_ui")
  189. question_ids <- arrange(questions, order) %>% pull(id)
  190. output$question_text <- renderText({
  191. if (!app_data$complete) {
  192. question()$`Question Text`
  193. } else {
  194. "No more questions at this time. Hang on while we crunch the numbers..."
  195. }
  196. })
  197. output$question_category <- renderText({
  198. if (!app_data$complete) question()$Category else "Thank You!"
  199. })
  200. output$question_topic <- renderText({
  201. if (!app_data$complete) question()$Topic else "All Done!"
  202. })
  203. output$results_table <- renderDataTable({
  204. app_data$results
  205. })
  206. output$question_story <- renderUI({
  207. if (!app_data$complete) {
  208. q_story <- question()$Story
  209. if (is.na(q_story)) return(p("No story."))
  210. tagList(
  211. h1(app_data$male_name, "Experience"),
  212. q_story %>%
  213. strsplit("\\s*(\r\n){1,2}") %>%
  214. .[[1]] %>%
  215. purrr::map(~ tags$p(.))
  216. )
  217. }
  218. })
  219. output$question_story_link <- renderUI({
  220. if (app_data$complete) return(NULL)
  221. q_story <- question()$Story
  222. if (is.na(q_story)) return(NULL)
  223. app_data$male_name <- possessive_name(random_name())
  224. actionLink("app_main_show_story",
  225. paste("Read", app_data$male_name, "story..."))
  226. })
  227. output$results_plot <- renderPlot({
  228. req(app_data$complete)
  229. plot_swipe_results(questions, app_data$results, SWIPES_MEAN)
  230. })
  231. observe({
  232. # cat("iter:", app_data$iter, "\n")
  233. # cat("ques:", question()$`Question Text`, "\n")
  234. # print(app_data$results)
  235. # str(app_data)
  236. })
  237. # on main app card swipe
  238. observeEvent(card_swipe(), {
  239. if (app_data$complete) {
  240. # cat("\nNothing to do.")
  241. saveRDS(app_data$results, "results.rds")
  242. app_data$screen_next <- "app-results"
  243. return()
  244. }
  245. # save swipe event
  246. app_data$results <- bind_rows(
  247. data_frame(
  248. question_id = question()$id,
  249. swipe = card_swipe()
  250. ),
  251. app_data$results
  252. )
  253. # update app_data
  254. Sys.sleep(0.5) # wait for animation to complete
  255. app_data$iter <- app_data$iter + 1L
  256. next_q <- question()
  257. if (is.null(next_q)) app_data$complete <- TRUE
  258. })
  259. observeEvent(input$splash_next_screen, {
  260. app_data$screen_next <- get_screen_next("splash-overlay")
  261. })
  262. observeEvent(input$toggle_splash, {
  263. if (!app_data$screen_active == "splash-overlay") {
  264. app_data$screen_next <- "splash-overlay"
  265. } else {
  266. app_data$screen_next <- app_data$screen_prev
  267. }
  268. })
  269. observeEvent(input$back, {
  270. if (!is.null(app_data$screen_prev)) app_data$screen_next <- app_data$screen_prev
  271. })
  272. observeEvent(input$jump_results, {
  273. last_results_file <- here::here("t4c", "results.rds")
  274. if (!file.exists(last_results_file)) {
  275. cat("No results saved yet.")
  276. return()
  277. }
  278. app_data$results <- readRDS(last_results_file)
  279. app_data$complete <- TRUE
  280. app_data$screen_next <- "app-results"
  281. })
  282. observeEvent(card_swipe_test(), {
  283. shinyjs::hide("test-card-swipe-container", anim = TRUE, animType = "fade", time = 1)
  284. Sys.sleep(1)
  285. shinyjs::show("test_swipe_success", anim = TRUE, animType = "fade")
  286. })
  287. observeEvent(input$app_intro_next, {
  288. app_data$screen_next <- get_screen_next("app-intro")
  289. })
  290. observeEvent(input$app_main_show_story, {
  291. shinyjs::removeClass("app-story", "fadeOutUp")
  292. shinyjs::addClass("app-story", "fadeInDown")
  293. })
  294. observeEvent(input$app_main_hide_story, {
  295. shinyjs::addClass("app-story", "fadeOutUp")
  296. shinyjs::removeClass("app-story", "fadeInDown")
  297. })
  298. observeEvent(app_data$complete, {
  299. if (!app_data$complete) return()
  300. app_data$screen_next <- "app-results"
  301. })
  302. observeEvent(app_data$screen_next, {
  303. if (is.null(app_data$screen_next)) return()
  304. # cat("\n", app_data$screen_active, "to", app_data$screen_next)
  305. if (app_screens[app_data$screen_active] < app_screens[app_data$screen_next]) {
  306. # Move forward: active out left, next in right
  307. shinyjs::removeClass(app_data$screen_active, "slideInLeft slideInRight")
  308. shinyjs::removeClass(app_data$screen_next, "slideOutLeft slideOutRight")
  309. shinyjs::addClass(app_data$screen_active, "slideOutLeft")
  310. shinyjs::addClass(app_data$screen_next, "slideInRight")
  311. } else {
  312. # Move backward: active out right, next in left
  313. shinyjs::removeClass(app_data$screen_active, "slideInLeft slideInRight")
  314. shinyjs::removeClass(app_data$screen_next, "slideOutLeft slideOutRight")
  315. shinyjs::addClass(app_data$screen_active, "slideOutRight")
  316. shinyjs::addClass(app_data$screen_next, "slideInLeft")
  317. }
  318. # Update screen state
  319. app_data$screen_prev <- app_data$screen_active
  320. app_data$screen_active <- app_data$screen_next
  321. app_data$screen_next <- NULL
  322. }, priority = -100)
  323. }
  324. shinyApp(ui = ui, server = server)