├── .Rinstignore ├── data ├── datalist └── cocktails.rda ├── .github ├── .gitignore └── workflows │ ├── check-standard.yaml │ └── test-coverage.yaml ├── man-roxygen └── etc.R ├── .gitmodules ├── man ├── figures │ ├── Screenshot-barplot.png │ ├── Screenshot-hobsons.png │ ├── Screenshot-mainpage.png │ ├── Screenshot-ternary.png │ └── Screenshot-ingredients.png ├── cocktailApp-package.Rd ├── NEWS.Rd ├── cocktails.Rd └── cocktailApp.Rd ├── app.R ├── m4 ├── basedefs.m4 └── DESCRIPTION.m4 ├── .gitattributes ├── inst └── CITATION ├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── Makefile ├── NAMESPACE ├── rebuildTags.sh ├── ChangeLog ├── tests ├── testthat.R └── testthat │ └── test-basic.r ├── data-raw └── import_cocktails.r ├── docker └── Dockerfile ├── README.md ├── README.Rmd ├── LICENSE └── R └── cocktailApp.r /.Rinstignore: -------------------------------------------------------------------------------- 1 | Makefile 2 | -------------------------------------------------------------------------------- /data/datalist: -------------------------------------------------------------------------------- 1 | cocktails 2 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /man-roxygen/etc.R: -------------------------------------------------------------------------------- 1 | #' @author Steven E. Pav \email{shabbychef@@gmail.com} 2 | -------------------------------------------------------------------------------- /data/cocktails.rda: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shabbychef/cocktailApp/HEAD/data/cocktails.rda -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rpkg_make"] 2 | path = rpkg_make 3 | url = https://github.com/shabbychef/rpkg_make.git 4 | -------------------------------------------------------------------------------- /man/figures/Screenshot-barplot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shabbychef/cocktailApp/HEAD/man/figures/Screenshot-barplot.png -------------------------------------------------------------------------------- /man/figures/Screenshot-hobsons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shabbychef/cocktailApp/HEAD/man/figures/Screenshot-hobsons.png -------------------------------------------------------------------------------- /man/figures/Screenshot-mainpage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shabbychef/cocktailApp/HEAD/man/figures/Screenshot-mainpage.png -------------------------------------------------------------------------------- /man/figures/Screenshot-ternary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shabbychef/cocktailApp/HEAD/man/figures/Screenshot-ternary.png -------------------------------------------------------------------------------- /man/figures/Screenshot-ingredients.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shabbychef/cocktailApp/HEAD/man/figures/Screenshot-ingredients.png -------------------------------------------------------------------------------- /app.R: -------------------------------------------------------------------------------- 1 | # /usr/bin/r 2 | # 3 | # Created: 2018.06.15 4 | # Copyright: Steven E. Pav, 2018 5 | # Author: Steven E. Pav 6 | # Comments: Steven E. Pav 7 | 8 | devtools::load_all() 9 | cocktailApp() 10 | 11 | #for vim modeline: (do not edit) 12 | # vim:fdm=marker:fmr=FOLDUP,UNFOLD:cms=#%s:syn=r:ft=r 13 | -------------------------------------------------------------------------------- /m4/basedefs.m4: -------------------------------------------------------------------------------- 1 | define(`m4_CHOMP',`substr($1,0,eval(len($1)-1))')dnl 2 | define(`m4_CHOMP_SYS',`m4_CHOMP(`esyscmd($1)')')dnl 3 | dnl holy fuck. http://mbreen.com/m4.html#quotemacro 4 | define(`LQ',`changequote(<,>)`dnl' 5 | changequote`'') 6 | define(`RQ',`changequote(<,>)dnl` 7 | 'changequote`'') 8 | define(`m4_R_FILES',`m4_CHOMP_SYS(`ls R/*.r | sort | perl -pe "s{^R/}{ `'RQ()};s{$}{`'RQ()};";')')dnl 9 | dnl vim modelines; 10 | dnl vim:ts=2:sw=2:syn=m4:ft=m4:si:cin:nu 11 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # see https://help.github.com/articles/dealing-with-line-endings 2 | # * Mon Jan 14 2013 03:28:56 PM Steven E. Pav 3 | 4 | # Set default behaviour, in case users don't have core.autocrlf set. 5 | * text=auto 6 | 7 | # Explicitly declare text files we want to always be normalized and converted 8 | # to native line endings on checkout. 9 | DESCRIPTION text 10 | NAMESPACE text 11 | *.md text 12 | 13 | # Denote all files that are truly binary and should not be modified. 14 | *.png binary 15 | *.jpg binary 16 | *.pdf binary 17 | 18 | -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | 2 | pkg_year <- sub('.*(2[[:digit:]]{3})-.*', '\\1', meta$Date, perl = TRUE) 3 | pkg_vers <- paste('R package version', meta$Version) 4 | 5 | bibentry( 6 | bibtype = "manual", 7 | header = paste0("To cite the '",meta$Package,"' package in publications use:"), 8 | key = paste0(meta$Package,"-Manual"), 9 | title = paste0("{", meta$Package, "}: ", meta$Title), 10 | author = as.person(meta$Author), 11 | year = pkg_year, 12 | note = pkg_vers, 13 | url = meta$URL, 14 | textVersion = paste0("Steven E. Pav (", pkg_year, "). ", meta$Package, ": ", meta$Title, ". ", pkg_vers, ".") 15 | ) 16 | 17 | 18 | #for vim modeline: (do not edit) 19 | # vim:fdm=marker:fmr=FOLDUP,UNFOLD:cms=#%s:syn=r:ft=r 20 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^nodist$ 2 | ^.codecov_token$ 3 | ^.git$ 4 | ^.gitattributes 5 | ^.gitignore 6 | ^.R_tags 7 | ^.tags 8 | ^.travis.yml 9 | ^\..*installed 10 | cache 11 | README.Rmd 12 | m4 13 | Makefile 14 | man-roxygen 15 | ^\.docker_img$ 16 | ^rebuildTags.sh 17 | ^cocktailApp.*.tar.gz$ 18 | ^cocktailApp.Rcheck$ 19 | ^cocktailApp$ 20 | cocktailApp/libs 21 | cocktailApp.pdf 22 | ^github_extra 23 | unit_test.log 24 | LICENSE 25 | ^docker$ 26 | \.o$ 27 | \.so$ 28 | \.aux$ 29 | \.log$ 30 | \.out$ 31 | ^\.local$ 32 | ^\.staging$ 33 | \.crancheck$ 34 | ^.drat_* 35 | ^.check_tmp 36 | rpkg_make 37 | libs 38 | nohup.out 39 | vignettes 40 | Rd2.pdf 41 | .Rd2pdf* 42 | submodule_directions.sh 43 | ^data-raw$ 44 | ^app.R$ 45 | foo.r 46 | man/my_server.Rd 47 | ^\.github$ 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # for testing, ignore these 2 | 3 | # dev stuff 4 | .tags 5 | .R_tags 6 | .*.swp 7 | .*.swo 8 | 9 | # local build check and whatnot 10 | *.tar.gz 11 | .local/ 12 | .staging/ 13 | *.Rcheck 14 | 15 | # unit testing 16 | unit_test.log 17 | 18 | # build static/dynamic 19 | convoluted_build.sh 20 | 21 | # automagically generated? 22 | # ah, but these are needed for install_github: 23 | #NAMESPACE 24 | #DESCRIPTION 25 | #man/ 26 | 27 | .cocktailApp*installed 28 | # generated as part of the 'local' vignette build 29 | cocktailApp.tex 30 | cocktailApp.log 31 | cocktailApp/ 32 | ./cocktailApp.pdf 33 | *.aux 34 | *.bbl 35 | *.blg 36 | cache/ 37 | .docker_img 38 | *.crancheck 39 | .Rd2pdf*/ 40 | Rd2.pdf 41 | submodule_directions.sh 42 | foo.r 43 | *.csv.bak 44 | man/my_server.Rd 45 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: cocktailApp 2 | Maintainer: Steven E. Pav 3 | Authors@R: c(person(c("Steven", "E."), "Pav", 4 | role=c("aut","cre"), 5 | email="shabbychef@gmail.com", 6 | comment = c(ORCID = "0000-0002-4197-6195"))) 7 | Version: 0.2.4 8 | Date: 2025-11-04 9 | License: LGPL-3 10 | Title: 'shiny' App to Discover Cocktails 11 | BugReports: https://github.com/shabbychef/cocktailApp/issues 12 | Description: A 'shiny' app to discover cocktails. The 13 | app allows one to search for cocktails by ingredient, 14 | filter on rating, and number of ingredients. The 15 | package also contains data with the ingredients of 16 | nearly 26 thousand cocktails scraped from the web. 17 | Depends: 18 | R (>= 3.0.2), 19 | shiny 20 | Imports: 21 | shinythemes, 22 | dplyr, 23 | tidyr, 24 | tibble, 25 | ggplot2, 26 | magrittr, 27 | forcats, 28 | DT 29 | Suggests: 30 | testthat 31 | URL: https://github.com/shabbychef/cocktailApp 32 | Collate: 33 | 'cocktailApp.r' 34 | RoxygenNote: 7.3.2 35 | -------------------------------------------------------------------------------- /man/cocktailApp-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cocktailApp.r 3 | \docType{package} 4 | \name{cocktailApp-package} 5 | \alias{cocktailApp-package} 6 | \title{Shiny app to discover cocktails.} 7 | \description{ 8 | Shiny app to discover cocktails. 9 | } 10 | \note{ 11 | This package is maintained as a hobby. 12 | } 13 | \section{Legal Mumbo Jumbo}{ 14 | 15 | 16 | cocktailApp is distributed in the hope that it will be useful, 17 | but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | GNU Lesser General Public License for more details. 20 | } 21 | 22 | \seealso{ 23 | Useful links: 24 | \itemize{ 25 | \item \url{https://github.com/shabbychef/cocktailApp} 26 | \item Report bugs at \url{https://github.com/shabbychef/cocktailApp/issues} 27 | } 28 | 29 | } 30 | \author{ 31 | Steven E. Pav \email{shabbychef@gmail.com} 32 | 33 | \strong{Maintainer}: Steven E. Pav \email{shabbychef@gmail.com} (\href{https://orcid.org/0000-0002-4197-6195}{ORCID}) 34 | 35 | } 36 | \keyword{package} 37 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ###################### 2 | # 3 | # Created: 2018-06-15 4 | # Copyright: Steven E. Pav, 2018 5 | # Author: Steven E. Pav 6 | ###################### 7 | 8 | ############### FLAGS ############### 9 | 10 | VMAJOR = 0 11 | VMINOR = 2 12 | VPATCH = 4 13 | #VDEV = .0001 14 | VDEV = 15 | PKG_NAME := cocktailApp 16 | 17 | RPKG_USES_RCPP := 0 18 | 19 | include ./rpkg_make/Makefile 20 | 21 | PKG_DEPS += data/cocktails.rda 22 | 23 | data-raw/cocktails.csv : ../drinksy/drinks.csv 24 | cp $< $@ 25 | 26 | # overload 27 | data/%.rda : data-raw/%.csv 28 | r -l usethis,devtools,readr -e '$* <- readr::read_csv("$<",guess_max=100000);usethis::use_data($*,overwrite=TRUE,internal=FALSE)' 29 | 30 | .PHONY : cocktail_data 31 | 32 | cocktail_data : data/cocktails.rda ## copy over data from ../drinksy 33 | 34 | submodule_help : ## help with git submodules 35 | echo "git submodule update --init --recursive" 36 | 37 | #for vim modeline: (do not edit) 38 | # vim:ts=2:sw=2:tw=129:fdm=marker:fmr=FOLDUP,UNFOLD:cms=#%s:tags=.tags;:syn=make:ft=make:ai:si:cin:nu:fo=croqt:cino=p0t0c5(0: 39 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(cocktailApp) 4 | import(shiny) 5 | importFrom(dplyr,arrange) 6 | importFrom(dplyr,coalesce) 7 | importFrom(dplyr,distinct) 8 | importFrom(dplyr,everything) 9 | importFrom(dplyr,filter) 10 | importFrom(dplyr,first) 11 | importFrom(dplyr,left_join) 12 | importFrom(dplyr,mutate) 13 | importFrom(dplyr,n) 14 | importFrom(dplyr,one_of) 15 | importFrom(dplyr,rename) 16 | importFrom(dplyr,right_join) 17 | importFrom(dplyr,sample_n) 18 | importFrom(dplyr,select) 19 | importFrom(dplyr,summarize) 20 | importFrom(dplyr,ungroup) 21 | importFrom(forcats,fct_rev) 22 | importFrom(ggplot2,aes) 23 | importFrom(ggplot2,coord_flip) 24 | importFrom(ggplot2,geom_col) 25 | importFrom(ggplot2,geom_point) 26 | importFrom(ggplot2,geom_text) 27 | importFrom(ggplot2,ggplot) 28 | importFrom(ggplot2,guide_legend) 29 | importFrom(ggplot2,labs) 30 | importFrom(grDevices,rgb) 31 | importFrom(graphics,legend) 32 | importFrom(magrittr,"%>%") 33 | importFrom(shinythemes,shinytheme) 34 | importFrom(stats,setNames) 35 | importFrom(tibble,tribble) 36 | importFrom(tidyr,spread) 37 | importFrom(utils,data) 38 | -------------------------------------------------------------------------------- /rebuildTags.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | # 3 | # get ctags in R for, recursively, foo.R 4 | # 5 | # this builds exuberant ctags for R 6 | # you need to have a .ctags file in your 7 | # home directory. 8 | # 9 | # Copyright: Cerebellum Capital, 2008-2009 10 | # Author: Steven E. Pav 11 | # Comments: Steven E. Pav 12 | 13 | #CTAGS=exuberant-ctags 14 | if [ "$LINUX_DISTRO" == "CRAPINTOSH" ]; then 15 | CTAGS=/opt/local/bin/ctags 16 | else 17 | CTAGS=ctags 18 | fi 19 | echo $CTAGS 20 | CTAGFLAGS='--verbose=no --recurse' 21 | NICE_LEVEL=18 22 | NICE_FLAGS="-n $NICE_LEVEL" 23 | TMP_TAG=.tmp_tags 24 | 25 | ##set up the R tags 26 | #for minimal disruption, write it to $TMP_TAG and then move it... 27 | nice $NICE_FLAGS $CTAGS -f $TMP_TAG $CTAGFLAGS --language-force=R --exclude='.r\~' --fields=+i \ 28 | `find . -name '*.[rR]' | grep -ve '\.git\|\.staging\|\.Rcheck\|\.local'` 2>/dev/null 29 | if [ -s $TMP_TAG ]; 30 | then 31 | mv $TMP_TAG .R_tags; 32 | else 33 | echo "empty R tags?" 1>&2 34 | fi 35 | 36 | ln -sf .R_tags .tags 37 | 38 | # vim:ts=4:sw=2:tw=180:fdm=marker:fmr=FOLDUP,UNFOLD:cms=#%s:syn=sh:ft=sh:ai:si:cin:nu:fo=croql:cino=p0t0c5(0: 39 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2 | 2025-11-04 Steven E. Pav 3 | * 0.2.4 on CRAN 4 | * removing dependency on ggtern. 5 | * adding missing reference to `dplyr::n` which broke two of the plots. 6 | 7 | 2023-07-11 Steven E. Pav 8 | * 0.2.3 on CRAN 9 | * package had been pulled because of upstream dependency. 10 | * convert citEntry to bibentry 11 | 12 | 2022-07-06 Steven E. Pav 13 | * fix bug where you could not search for Gin or Vodka. 14 | 15 | 2021-04-01 Steven E. Pav 16 | * 0.2.2 on CRAN 17 | * merge many short ingredients. 18 | * fix some units in webtender data. 19 | * search by ingredient regex. 20 | 21 | 2019-07-01 Steven E. Pav 22 | * 0.2.1 on CRAN 23 | * CRAN fix for hanging tests. 24 | * revert to ggtern. 25 | 26 | 2018-08-20 Steven E. Pav 27 | * removing ggtern in favor of Ternary package. 28 | 29 | 2018-07-15 Steven E. Pav 30 | * adding Hobson's Choice button. 31 | 32 | 2018-07-07 Steven E. Pav 33 | * adding 10K cocktails from drinksmixer. 34 | 35 | 2018-07-05 Steven E. Pav 36 | * 0.1.0 on CRAN 37 | 38 | 2018-06-15 Steven E. Pav 39 | * start work 40 | 41 | -------------------------------------------------------------------------------- /m4/DESCRIPTION.m4: -------------------------------------------------------------------------------- 1 | dnl divert here just means the output from basedefs does not appear. 2 | divert(-1) 3 | include(basedefs.m4) 4 | divert(0)dnl 5 | Package: PKG_NAME() 6 | Maintainer: Steven E. Pav 7 | Authors@R: c(person(c("Steven", "E."), "Pav", 8 | role=c("aut","cre"), 9 | email="shabbychef@gmail.com", 10 | comment = c(ORCID = "0000-0002-4197-6195"))) 11 | Version: VERSION() 12 | Date: DATE() 13 | License: LGPL-3 14 | Title: 'shiny' App to Discover Cocktails 15 | BugReports: https://github.com/shabbychef/PKG_NAME()/issues 16 | Description: A 'shiny' app to discover cocktails. The 17 | app allows one to search for cocktails by ingredient, 18 | filter on rating, and number of ingredients. The 19 | package also contains data with the ingredients of 20 | nearly 26 thousand cocktails scraped from the web. 21 | Depends: 22 | R (>= 3.0.2), 23 | shiny 24 | Imports: 25 | shinythemes, 26 | dplyr, 27 | tidyr, 28 | tibble, 29 | ggplot2, 30 | magrittr, 31 | dnl Ternary, 32 | dnl ggtern, 33 | forcats, 34 | dnl readr, 35 | dnl urltools, 36 | dnl stringr 37 | DT 38 | Suggests: 39 | testthat 40 | URL: https://github.com/shabbychef/PKG_NAME() 41 | dnl VignetteBuilder: knitr 42 | Collate: 43 | m4_R_FILES() 44 | dnl vim:ts=2:sw=2:tw=79:syn=m4:ft=m4:et 45 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2018 Steven E. Pav. All Rights Reserved. 2 | # Author: Steven E. Pav 3 | 4 | # This file is part of cocktailApp. 5 | # 6 | # cocktailApp is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # cocktailApp is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with cocktailApp. If not, see . 18 | 19 | # env var: 20 | # nb: 21 | # see also: 22 | # todo: 23 | # changelog: 24 | # 25 | # Created: 2018-07-01 26 | # Copyright: Steven E. Pav, 2018-2018 27 | # Author: Steven E. Pav 28 | # Comments: Steven E. Pav 29 | 30 | # because Hadley says it should be like this. 31 | # see https://github.com/hadley/devtools/wiki/Testing 32 | 33 | library(testthat) 34 | library(cocktailApp) 35 | 36 | # once you have a directory of tests, uncomment this. 37 | test_check("cocktailApp") 38 | 39 | #for vim modeline: (do not edit) 40 | # vim:fdm=marker:fmr=FOLDUP,UNFOLD:cms=#%s:syn=r:ft=r 41 | -------------------------------------------------------------------------------- /.github/workflows/check-standard.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macos-latest, r: 'release'} 22 | - {os: windows-latest, r: 'release'} 23 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 24 | - {os: ubuntu-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'oldrel-1'} 26 | 27 | env: 28 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 29 | R_KEEP_PKG_SOURCE: yes 30 | 31 | steps: 32 | - uses: actions/checkout@v3 33 | 34 | - uses: r-lib/actions/setup-pandoc@v2 35 | 36 | - uses: r-lib/actions/setup-r@v2 37 | with: 38 | r-version: ${{ matrix.config.r }} 39 | http-user-agent: ${{ matrix.config.http-user-agent }} 40 | use-public-rspm: true 41 | 42 | - uses: r-lib/actions/setup-r-dependencies@v2 43 | with: 44 | extra-packages: any::rcmdcheck 45 | needs: check 46 | 47 | - uses: r-lib/actions/check-r-package@v2 48 | with: 49 | upload-snapshots: true 50 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: [main, master] 6 | pull_request: 7 | branches: [main, master] 8 | 9 | name: test-coverage 10 | 11 | jobs: 12 | test-coverage: 13 | runs-on: ubuntu-latest 14 | env: 15 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - uses: r-lib/actions/setup-r@v2 21 | with: 22 | use-public-rspm: true 23 | 24 | - uses: r-lib/actions/setup-r-dependencies@v2 25 | with: 26 | extra-packages: any::covr 27 | needs: coverage 28 | 29 | - name: Test coverage 30 | run: | 31 | covr::codecov( 32 | quiet = FALSE, 33 | clean = FALSE, 34 | install_path = file.path(Sys.getenv("RUNNER_TEMP"), "package") 35 | ) 36 | shell: Rscript {0} 37 | 38 | - name: Show testthat output 39 | if: always() 40 | run: | 41 | ## -------------------------------------------------------------------- 42 | find ${{ runner.temp }}/package -name 'testthat.Rout*' -exec cat '{}' \; || true 43 | shell: bash 44 | 45 | - name: Upload test results 46 | if: failure() 47 | uses: actions/upload-artifact@v3 48 | with: 49 | name: coverage-test-failures 50 | path: ${{ runner.temp }}/package 51 | -------------------------------------------------------------------------------- /man/NEWS.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cocktailApp.r 3 | \name{cocktailApp-NEWS} 4 | \alias{cocktailApp-NEWS} 5 | \title{News for package 'cocktailApp':} 6 | \description{ 7 | News for package \sQuote{cocktailApp} 8 | 9 | \newcommand{\CRANpkg}{\href{https://cran.r-project.org/package=#1}{\pkg{#1}}} 10 | \newcommand{\cocktailApp}{\CRANpkg{cocktailApp}} 11 | } 12 | \section{\cocktailApp{} Version 0.2.4 (2025-11-04) }{ 13 | 14 | \itemize{ 15 | \item removing dependency on ggtern. 16 | \item adding missing reference to `dplyr::n` which broke two of the plots. 17 | } 18 | } 19 | 20 | \section{\cocktailApp{} Version 0.2.3 (2023-07-18) }{ 21 | 22 | \itemize{ 23 | \item fix bug where you could not search for Gin or Vodka. 24 | \item package had been archived because of dependency on ggtern. 25 | \item convert citEntry to bibentry 26 | } 27 | } 28 | 29 | \section{\cocktailApp{} Version 0.2.2 (2021-04-01) }{ 30 | 31 | \itemize{ 32 | \item merge many short ingredients. 33 | \item fix some units in webtender data. 34 | \item search by ingredient regex. 35 | } 36 | } 37 | 38 | \section{\cocktailApp{} Version 0.2.1 (2019-07-01) }{ 39 | 40 | \itemize{ 41 | \item CRAN fix as tests were hanging. 42 | \item replace \code{Ternary} package with \code{ggtern}. 43 | } 44 | } 45 | 46 | \section{\cocktailApp{} Version 0.2.0 (2018-08-19) }{ 47 | 48 | \itemize{ 49 | \item adding another source. 50 | \item adding \dQuote{Hobson's Choice} button. 51 | \item removing dependency on \code{ggtern} package, replacing with \code{Ternary}. 52 | } 53 | } 54 | 55 | \section{\cocktailApp{} Initial Version 0.1.0 (2018-07-05) }{ 56 | 57 | \itemize{ 58 | \item first CRAN release. 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /data-raw/import_cocktails.r: -------------------------------------------------------------------------------- 1 | #! /usr/bin/r 2 | # 3 | # Copyright 2019-2019 Steven E. Pav. All Rights Reserved. 4 | # Author: Steven E. Pav 5 | # 6 | # This file is part of cocktailApp. 7 | # 8 | # cocktailApp is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Lesser General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # cocktailApp is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU Lesser General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU Lesser General Public License 19 | # along with cocktailApp. If not, see . 20 | # 21 | # Created: 2019.06.30 22 | # Copyright: Steven E. Pav, 2019 23 | # Author: Steven E. Pav 24 | # Comments: Steven E. Pav 25 | 26 | suppressMessages({ 27 | library(readr) 28 | library(dplyr) 29 | library(usethis) 30 | }) 31 | 32 | cocktails <- readr::read_csv('cocktails.csv', 33 | col_types=cols(amt = col_double(), 34 | unit = col_character(), 35 | ingredient = col_character(), 36 | cocktail = col_character(), 37 | rating = col_double(), 38 | upstream_id = col_double(), 39 | url = col_character(), 40 | votes = col_character(), 41 | added = col_character(), 42 | src = col_character(), 43 | short_ingredient = col_character(), 44 | proportion = col_double() 45 | )) 46 | 47 | 48 | cat('cocktails is ',dim(cocktails),'on',nrow(distinct(cocktails,url)),'distinct cocktails\n') 49 | 50 | usethis::use_data(cocktails,overwrite=TRUE,internal=FALSE) 51 | 52 | #for vim modeline: (do not edit) 53 | # vim:fdm=marker:fmr=FOLDUP,UNFOLD:cms=#%s:syn=r:ft=r 54 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # 2 | # dockerfile to CRAN-check with r-dev 3 | # 4 | # docker build --rm -t shabbychef/cocktailApp-crancheck . 5 | # 6 | # docker run -it --rm --volume $(pwd):/srv:rw cocktailApp-crancheck 7 | # 8 | # Created: 2018-06-15 9 | # Copyright: Steven E. Pav, 2018 10 | # Author: Steven E. Pav 11 | 12 | ##################################################### 13 | # preamble# FOLDUP 14 | FROM shabbychef/crancheck 15 | MAINTAINER Steven E. Pav, shabbychef@gmail.com 16 | # UNFOLD 17 | 18 | ENV DOCKERFILE_REFRESHED_AT 2018-06-15 19 | # see http://crosbymichael.com/dockerfile-best-practices.html 20 | #RUN echo "deb http://archive.ubuntu.com/ubuntu precise main universe" > /etc/apt/sources.list 21 | 22 | RUN (apt-get clean -y ; \ 23 | apt-get update -y -qq; \ 24 | apt-get update --fix-missing ); 25 | 26 | #RUN (apt-get dist-upgrade -y ; \ 27 | #apt-get update -qq ; \ 28 | RUN (DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true apt-get install -q -y --no-install-recommends \ 29 | libgs9 texlive-base texlive-binaries \ 30 | libcupsimage2 libcups2 curl wget \ 31 | qpdf pandoc ghostscript \ 32 | texlive-latex-extra texlive-latex-base texlive-fonts-recommended texlive-fonts-extra \ 33 | liblapack-dev libblas-dev ; \ 34 | sync ; \ 35 | mkdir -p /usr/local/lib/R/site-library ; \ 36 | chmod -R 777 /usr/local/lib/R/site-library ; \ 37 | sync ; \ 38 | /usr/local/bin/install2.r Rcpp testthat roxygen2 devtools knitr formatR codetools) 39 | 40 | RUN (/usr/local/bin/install2.r dplyr tidyr ggplot2 shiny shinythemes DT magrittr forcats ggtern; ) 41 | 42 | RUN groupadd -g 1001 spav && useradd -g spav -u 1001 spav; 43 | USER spav 44 | 45 | ##################################################### 46 | # entry and cmd# FOLDUP 47 | # these are the default, but remind you that you might want to use /usr/bin/R instead? 48 | # always use array syntax: 49 | ENTRYPOINT ["/usr/bin/R","CMD","check","--as-cran","--output=/tmp"] 50 | 51 | # ENTRYPOINT and CMD are better together: 52 | CMD ["/srv/*.tar.gz"] 53 | # UNFOLD 54 | 55 | #for vim modeline: (do not edit) 56 | # vim:nu:fdm=marker:fmr=FOLDUP,UNFOLD:cms=#%s:syn=Dockerfile:ft=Dockerfile:fo=croql 57 | -------------------------------------------------------------------------------- /man/cocktails.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cocktailApp.r 3 | \docType{data} 4 | \name{cocktails} 5 | \alias{cocktails} 6 | \title{Cocktails Data} 7 | \format{ 8 | A \code{data.frame} object with around 117,000 rows and 12 columns. The 9 | data were scraped from four websites: Difford's guide, Webtender, and 10 | Kindred Cocktails, all scraped in late 2017; and Drinks Mixer, scraped in 11 | mid 2018. 12 | 13 | The columns are defined as follows: 14 | \describe{ 15 | \item{\code{amt}}{The numeric amount of the ingredient.} 16 | \item{\code{unit}}{The unit corresponding to the amount. 17 | The most common entry is \code{fl oz}, which is the unit for \sQuote{main} 18 | ingredients. 19 | The second most common entry is \code{garnish}. These two units 20 | account for over 95 percent of the rows of the data.} 21 | \item{\code{ingredient}}{The name of the ingredient. These may have odd 22 | qualifiers, or brand specifications. Some of these qualifications are 23 | stripped out in the \code{short_ingredient} field.} 24 | \item{\code{cocktail}}{The name of the cocktail.} 25 | \item{\code{rating}}{The rating assigned to the cocktail in the upstream database. For some 26 | sources, the ratings have been rescaled. Ratings are on a scale of 0 to 5.} 27 | \item{\code{upstream_id}}{An ID code from the upstream source.} 28 | \item{\code{url}}{The upstream URL.} 29 | \item{\code{votes}}{The number of votes in the rating, from the upstream 30 | database. Not always available.} 31 | \item{\code{added}}{The date the cocktail was added to the upstream database. Not always available.} 32 | \item{\code{src}}{The source of the cocktail, as listed in the upstream database. Usually not available.} 33 | \item{\code{short_ingredient}}{A shortened form of the ingredient, stripping away some of the qualifiers. 34 | This is subject to change in future releases of this package, when a better term extraction solution is found.} 35 | \item{\code{proportion}}{For ingredients where the \code{unit} is \code{fl oz}, 36 | this is the proportion of the given cocktail that consists of the given ingredient. For a given 37 | cocktail, the proportions should sum to one.} 38 | } 39 | } 40 | \source{ 41 | Difford's Guide, \url{https://www.diffordsguide.com/}, 42 | Webtender, \url{https://www.webtender.com}, 43 | Kindred Cocktails, \url{https://kindredcocktails.com}, 44 | Drinks Mixer, \url{http://www.drinksmixer.com}. 45 | } 46 | \usage{ 47 | data(cocktails) 48 | } 49 | \description{ 50 | Ingredients of over 26 thousand cocktails, scraped from the web. 51 | } 52 | \note{ 53 | The data were scraped from several websites, which falls in a legal gray area. 54 | While, in general, raw factual data can not be copyright, there is a difference between the law and a lawsuit. 55 | The package author in no way claims any copyright on this data. 56 | } 57 | \examples{ 58 | data(cocktails) 59 | str(cocktails) 60 | 61 | require(dplyr) 62 | cocktails \%>\% 63 | filter(short_ingredient \%in\% c('Averna','Bourbon')) \%>\% 64 | group_by(cocktail,url) \%>\% 65 | mutate(isok=n() > 1) \%>\% 66 | ungroup() \%>\% 67 | filter(isok) \%>\% 68 | arrange(desc(rating),cocktail) \%>\% 69 | select(cocktail,ingredient,amt,unit,rating) \%>\% 70 | head(n=8) 71 | 72 | } 73 | \keyword{datasets} 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # cocktailApp 4 | 5 | [![Build Status](https://github.com/shabbychef/cocktailApp/workflows/R-CMD-check/badge.svg)](https://github.com/shabbychef/cocktailApp/actions) 6 | [![CRAN](https://www.r-pkg.org/badges/version/cocktailApp)](https://cran.r-project.org/package=cocktailApp) 7 | [![Downloads](http://cranlogs.r-pkg.org/badges/cocktailApp?color=green)](https://www.r-pkg.org:443/pkg/cocktailApp) 8 | [![Total](http://cranlogs.r-pkg.org/badges/grand-total/cocktailApp?color=green)](https://www.r-pkg.org:443/pkg/cocktailApp) 9 | 10 | 11 | A Shiny app to discover cocktails. 12 | 13 | -- Steven E. Pav, shabbychef@gmail.com 14 | 15 | ## Installation 16 | 17 | This package can be installed from 18 | [CRAN](https://cran.r-project.org/package=cocktailApp "CRAN page"), 19 | via [drat](https://github.com/eddelbuettel/drat "drat"), or 20 | from [github](https://github.com/shabbychef/cocktailApp "cocktailApp") 21 | via devtools: 22 | 23 | 24 | ```r 25 | # via CRAN: 26 | install.packages("cocktailApp") 27 | # via drat: 28 | if (require(drat)) { 29 | drat:::add("shabbychef") 30 | install.packages("cocktailApp") 31 | } 32 | # get snapshot from github (may be buggy) 33 | if (require(devtools)) { 34 | # latest greatest 35 | install_github("shabbychef/cocktailApp", ref = "master") 36 | } 37 | ``` 38 | 39 | # Basic Usage 40 | 41 | The app can be run in a few ways: 42 | 43 | 1. You can download the github repo and run the `app.R` in the main directory, 44 | either via `shiny::runApp()` or by moving this directory to a location that 45 | Shiny Server serves. 46 | 1. You can install the package and then use the `cocktailApp()` function. 47 | 48 | ## Screenshots 49 | 50 | ![](man/figures/Screenshot-mainpage.png) 51 | ![](man/figures/Screenshot-ingredients.png) 52 | ![](man/figures/Screenshot-hobsons.png) 53 | ![](man/figures/Screenshot-ternary.png) 54 | ![](man/figures/Screenshot-barplot.png) 55 | 56 | ## Data 57 | 58 | The underlying data to power the shiny app is also available from this package. 59 | It is called, simply, `cocktails`. This data frame has rows for each 60 | ingredient, with amounts, and units, and is joined to information about the 61 | cocktail, which is identified by name, an upstream ID, URL, rating, number of 62 | votes, and more. 63 | 64 | 65 | 66 | ```r 67 | library(cocktailApp) 68 | library(dplyr) 69 | library(knitr) 70 | utils::data("cocktails", package = "cocktailApp") 71 | cocktails %>% arrange(desc(rating)) %>% head(n = 10) %>% 72 | select(cocktail, ingredient, amt, unit, rating) %>% 73 | knitr::kable() 74 | ``` 75 | 76 | 77 | 78 | |cocktail |ingredient | amt|unit | rating| 79 | |:--------------------|:--------------------------------|-----:|:-------|------:| 80 | |Jersey Sour |Berneroy Fine Calvados | 2.00|fl oz | 5| 81 | |Jersey Sour |Freshly squeezed lemon juice | 1.00|fl oz | 5| 82 | |Jersey Sour |Sugar syrup (2 sugar to 1 water) | 0.50|fl oz | 5| 83 | |Jersey Sour |Pasteurised egg white | 0.50|fl oz | 5| 84 | |Jersey Sour |Lemon zest twist | 1.00|garnish | 5| 85 | |Julep (Generic Name) |Mint leaves | 12.00|fresh | 5| 86 | |Julep (Generic Name) |Brandy, whisk(e)y, gin, rum etc. | 2.50|fl oz | 5| 87 | |Julep (Generic Name) |Sugar syrup (2 sugar to 1 water) | 0.75|fl oz | 5| 88 | |Julep (Generic Name) |Angostura Aromatic Bitters | 3.00|dash | 5| 89 | |Julep (Generic Name) |Mint sprig | 1.00|garnish | 5| 90 | 91 | 92 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | ```{r setup,include=FALSE} 2 | #--- 3 | #Title: cocktailApp 4 | #Date: Tue Jun 19 2018 07:58:50 AM 5 | #--- 6 | 7 | # set the knitr options ... for everyone! 8 | # if you unset this, then vignette build bonks. oh, joy. 9 | #opts_knit$set(progress=TRUE) 10 | opts_knit$set(eval.after='fig.cap') 11 | # for a package vignette, you do want to echo. 12 | # opts_chunk$set(echo=FALSE,warning=FALSE,message=FALSE) 13 | opts_chunk$set(warning=FALSE,message=FALSE) 14 | #opts_chunk$set(results="asis") 15 | opts_chunk$set(cache=TRUE,cache.path="cache/") 16 | 17 | #opts_chunk$set(fig.path="github_extra/figure/",dev=c("pdf","cairo_ps")) 18 | #opts_chunk$set(fig.path="github_extra/figure/",dev=c("png","pdf")) 19 | #opts_chunk$set(fig.path="github_extra/figure/",dev=c("png")) 20 | opts_chunk$set(fig.path="man/figures/",dev=c("png")) 21 | opts_chunk$set(fig.width=5,fig.height=4,dpi=64) 22 | 23 | # doing this means that png files are made of figures; 24 | # the savings is small, and it looks like shit: 25 | #opts_chunk$set(fig.path="figure/",dev=c("png","pdf","cairo_ps")) 26 | #opts_chunk$set(fig.width=4,fig.height=4) 27 | # for figures? this is sweave-specific? 28 | #opts_knit$set(eps=TRUE) 29 | 30 | # this would be for figures: 31 | #opts_chunk$set(out.width='.8\\textwidth') 32 | # for text wrapping: 33 | options(width=64,digits=2) 34 | opts_chunk$set(size="small") 35 | opts_chunk$set(tidy=TRUE,tidy.opts=list(width.cutoff=50,keep.blank.line=TRUE)) 36 | 37 | #cocktailApp.meta <- packageDescription('cocktailApp') 38 | library(cocktailApp) 39 | #[![codecov.io](http://codecov.io/github/shabbychef/cocktailApp/coverage.svg?branch=master)](http://codecov.io/github/shabbychef/cocktailApp?branch=master) 40 | #[![Build Status](https://travis-ci.org/shabbychef/cocktailApp.png)](https://travis-ci.org/shabbychef/cocktailApp) 41 | #[![codecov.io](http://codecov.io/github/shabbychef/cocktailApp/coverage.svg?branch=master)](http://codecov.io/github/shabbychef/cocktailApp?branch=master) 42 | ``` 43 | 44 | # cocktailApp 45 | 46 | [![Build Status](https://github.com/shabbychef/cocktailApp/workflows/R-CMD-check/badge.svg)](https://github.com/shabbychef/cocktailApp/actions) 47 | [![CRAN](https://www.r-pkg.org/badges/version/cocktailApp)](https://cran.r-project.org/package=cocktailApp) 48 | [![Downloads](http://cranlogs.r-pkg.org/badges/cocktailApp?color=green)](https://www.r-pkg.org:443/pkg/cocktailApp) 49 | [![Total](http://cranlogs.r-pkg.org/badges/grand-total/cocktailApp?color=green)](https://www.r-pkg.org:443/pkg/cocktailApp) 50 | 51 | 52 | A Shiny app to discover cocktails. 53 | 54 | -- Steven E. Pav, shabbychef@gmail.com 55 | 56 | ## Installation 57 | 58 | This package can be installed from 59 | [CRAN](https://cran.r-project.org/package=cocktailApp "CRAN page"), 60 | via [drat](https://github.com/eddelbuettel/drat "drat"), or 61 | from [github](https://github.com/shabbychef/cocktailApp "cocktailApp") 62 | via devtools: 63 | 64 | ```{r install,eval=FALSE,echo=TRUE} 65 | # via CRAN: 66 | install.packages("cocktailApp") 67 | # via drat: 68 | if (require(drat)) { 69 | drat:::add("shabbychef") 70 | install.packages("cocktailApp") 71 | } 72 | # get snapshot from github (may be buggy) 73 | if (require(devtools)) { 74 | # latest greatest 75 | install_github('shabbychef/cocktailApp',ref='master') 76 | } 77 | ``` 78 | 79 | # Basic Usage 80 | 81 | The app can be run in a few ways: 82 | 83 | 1. You can download the github repo and run the `app.R` in the main directory, 84 | either via `shiny::runApp()` or by moving this directory to a location that 85 | Shiny Server serves. 86 | 1. You can install the package and then use the `cocktailApp()` function. 87 | 88 | ## Screenshots 89 | 90 | ![](man/figures/Screenshot-mainpage.png) 91 | ![](man/figures/Screenshot-ingredients.png) 92 | ![](man/figures/Screenshot-hobsons.png) 93 | ![](man/figures/Screenshot-ternary.png) 94 | ![](man/figures/Screenshot-barplot.png) 95 | 96 | ## Data 97 | 98 | The underlying data to power the shiny app is also available from this package. 99 | It is called, simply, `cocktails`. This data frame has rows for each 100 | ingredient, with amounts, and units, and is joined to information about the 101 | cocktail, which is identified by name, an upstream ID, URL, rating, number of 102 | votes, and more. 103 | 104 | 105 | ```{r data_basic,cache=FALSE,eval=TRUE,echo=TRUE} 106 | library(cocktailApp) 107 | library(dplyr) 108 | library(knitr) 109 | utils::data('cocktails',package='cocktailApp') 110 | cocktails %>% 111 | arrange(desc(rating)) %>% 112 | head(n=10) %>% 113 | select(cocktail,ingredient,amt,unit,rating) %>% 114 | knitr::kable() 115 | ``` 116 | 117 | 118 | -------------------------------------------------------------------------------- /man/cocktailApp.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/cocktailApp.r 3 | \name{cocktailApp} 4 | \alias{cocktailApp} 5 | \title{cocktailApp .} 6 | \usage{ 7 | cocktailApp(page_title = "Drink Schnauzer", enableBookmarking = "url") 8 | } 9 | \arguments{ 10 | \item{page_title}{an optional page title for the app. A \code{NULL} value 11 | causes no page title to be used.} 12 | 13 | \item{enableBookmarking}{Can be one of \code{"url"}, \code{"server"}, or 14 | \code{"disable"}. The default value, \code{NULL}, will respect the setting from 15 | any previous calls to \code{\link[shiny:enableBookmarking]{enableBookmarking()}}. See \code{\link[shiny:enableBookmarking]{enableBookmarking()}} 16 | for more information on bookmarking your app.} 17 | } 18 | \value{ 19 | Runs the \code{shiny} app. 20 | } 21 | \description{ 22 | A \code{shiny} app to explore cocktails. The app allows you to enter ingredients 23 | that a cocktail must have, or ingredients that it must not have. One can 24 | filter by number of ingredients, minimum rating, minimum \sQuote{t stat} 25 | (computed as the rating minus the T stat zero all multiplied by the square 26 | root of the number of ratings). One can also search for cocktail by regex. 27 | 28 | In the main tab, titled \dQuote{drinks}, one can find a table with the 29 | summaries of matching cocktails. Selecting rows of this table will 30 | cause the cocktail table below to be populated with more details on each 31 | selected cocktail. Selecting rows will also populate the bar chart 32 | in the \dQuote{plots} tab. 33 | 34 | If two or more ingredients are selected, drinks with non-zero quantities 35 | of both of these will be shown in a ternary plot in the \dQuote{tern} 36 | tab. 37 | 38 | In the \dQuote{other} tab is a table with common co-ingredients of the 39 | selected ingredients. A co-ingredient is an ingredient that commonly 40 | occurs with the selected ingredient, as measured by the number of 41 | cocktails, and by \sQuote{rho}, which is like a correlation based 42 | on the proportion. 43 | 44 | A checkbox labelled, \dQuote{Hobson's Choice} allows you to populate 45 | the cocktail table with five random cocktails that meet the numerical 46 | filters on number of ingredients, rating, and so on, but which do not 47 | meet the ingredient selections. Unselecting and re-selecting the 48 | checkbox selects a new set of random cocktails. Note that the random 49 | selection is not responsive to changes in the numerical filters. 50 | } 51 | \section{Screenshots}{ 52 | 53 | 54 | The main page looks as follows. In this case the user has selected 55 | two ingredients, \sQuote{Benedictine} and \sQuote{Bourbon}. The 56 | user has modified some of the numeric filters resulting in only 57 | six cocktails in the cocktail table on the right in the main 58 | tab. 59 | 60 | \if{html}{ 61 | \figure{Screenshot-mainpage.png}{options: width="100\%" alt="Screenshot: landing page of app"} 62 | } 63 | \if{latex}{ 64 | \figure{Screenshot-mainpage.png}{options: width=14cm} 65 | } 66 | 67 | In the next screenshot, the user has selected two of 68 | the rows of the cocktail table, 69 | which causes the ingredients table 70 | on the lower right to be populated with the recipes of the 71 | selected cocktails. Instead one could click on the linked 72 | cocktail names to be taken to the upstream source of the recipe, 73 | which is recommended since those pages typically have better 74 | instructions. 75 | 76 | \if{html}{ 77 | \figure{Screenshot-ingredients.png}{options: width="100\%" alt="Screenshot: landing page of app, with selected cocktails"} 78 | } 79 | \if{latex}{ 80 | \figure{Screenshot-ingredients.png}{options: width=14cm} 81 | } 82 | 83 | In the following screenshot, the user has selected two ingredients, 84 | \sQuote{Benedictine} and \sQuote{bourbon}, then clicked on the 85 | the main table, then selected the \sQuote{plots} tab. This 86 | shows a bar plot of the proportions of all ingredients 87 | in all the selected cocktails. 88 | 89 | \if{html}{ 90 | \figure{Screenshot-barplot.png}{options: width="100\%" alt="Screenshot: bar plot of ingredients"} 91 | } 92 | \if{latex}{ 93 | \figure{Screenshot-barplot.png}{options: width=14cm} 94 | } 95 | 96 | In this screenshot, the user has checked the \dQuote{Hobson's Choice} 97 | box, which adds 5 random cocktails to the cocktail table. 98 | 99 | \if{html}{ 100 | \figure{Screenshot-hobsons.png}{options: width="100\%" alt="Screenshot: main page with Hobsons choice"} 101 | } 102 | \if{latex}{ 103 | \figure{Screenshot-hobsons.png}{options: width=14cm} 104 | } 105 | } 106 | 107 | \examples{ 108 | \dontrun{ 109 | cocktailApp() 110 | } 111 | } 112 | \author{ 113 | Steven E. Pav \email{shabbychef@gmail.com} 114 | } 115 | \keyword{shiny} 116 | -------------------------------------------------------------------------------- /tests/testthat/test-basic.r: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Steven E. Pav. All Rights Reserved. 2 | # Author: Steven E. Pav 3 | 4 | # This file is part of cocktailApp. 5 | # 6 | # cocktailApp is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # cocktailApp is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with cocktailApp. If not, see . 18 | 19 | # env var: 20 | # nb: 21 | # see also: 22 | # todo: 23 | # changelog: 24 | # 25 | # Created: 2018.07.01 26 | # Copyright: Steven E. Pav, 2018-2018 27 | # Author: Steven E. Pav 28 | # Comments: Steven E. Pav 29 | 30 | # helpers#FOLDUP 31 | set.char.seed <- function(str) { 32 | set.seed(as.integer(charToRaw(str))) 33 | } 34 | #UNFOLD 35 | 36 | library(dplyr) 37 | context("data as expected")# FOLDUP 38 | 39 | utils::data("cocktails", package="cocktailApp") 40 | test_that("data_size",{#FOLDUP 41 | indat <- cocktails 42 | expect_gt(nrow(cocktails),10e4) 43 | expect_gt(ncol(cocktails),11) 44 | expect_true(all(c('amt','unit','ingredient','cocktail','url','short_ingredient') %in% colnames(cocktails))) 45 | })#UNFOLD 46 | 47 | # UNFOLD 48 | 49 | context("code runs at all")#FOLDUP 50 | utils::data("cocktails", package="cocktailApp") 51 | 52 | test_that("shiny bits",{#FOLDUP 53 | indat <- cocktails 54 | 55 | expect_error(recipe_df <- .add_id(indat),NA) 56 | expect_error(cocktail_df <- .distill_info(recipe_df),NA) 57 | 58 | both <- list(recipe=recipe_df %>% dplyr::select(-cocktail,-rating,-votes,-url),cocktail=cocktail_df) 59 | 60 | expect_error(both2 <- .filter_ingredients(both,name_regex='sazerac',must_have_ing=c('Bourbon','Averna'), 61 | must_not_have_ing=c(), 62 | logical_sense='OR'),NA) 63 | 64 | #skip_on_cran() 65 | #skip_on_travis() 66 | two_ing <- c('Bourbon','Averna') 67 | expect_error(both3 <- .filter_num_ingredients(both2,must_have_ing=two_ing,min_rating=2,max_ingr=10,max_other_ingr=5),NA) 68 | expect_error(both4 <- .filter_tstat(both3,min_t=2,t_zero=1),NA) 69 | expect_error(both5 <- .filter_src(both4,from_sources=c('diffordsguide')),NA) 70 | expect_error(both6 <- .add_description(both5),NA) 71 | 72 | expect_error(tbl <- .drinks_table(both6),NA) 73 | expect_error(merged <- .merge_both(both6),NA) 74 | })#UNFOLD 75 | test_that("filter ingredients",{#FOLDUP 76 | #indat <- head(cocktails,100) 77 | indat <- cocktails 78 | 79 | expect_error(recipe_df <- .add_id(indat),NA) 80 | expect_error(cocktail_df <- .distill_info(recipe_df),NA) 81 | 82 | both <- list(recipe=recipe_df %>% dplyr::select(-cocktail,-rating,-votes,-url),cocktail=cocktail_df) 83 | 84 | expect_error(test0 <- .filter_ingredients(both,name_regex='', 85 | must_have_ing=c(), 86 | must_not_have_ing=c(), 87 | logical_sense='OR'), 88 | NA) 89 | expect_error(test1 <- .filter_ingredients(both,name_regex='sazerac', 90 | must_have_ing=c('Bourbon','Averna'), 91 | must_not_have_ing=c(), 92 | logical_sense='OR'), 93 | NA) 94 | expect_error(test2 <- .filter_ingredients(both,name_regex='sazerac', 95 | must_have_ing=c('Bourbon'), 96 | must_not_have_ing=c('Averna'), 97 | logical_sense='OR'), 98 | NA) 99 | expect_error(test3 <- .filter_ingredients(both,name_regex='sazerac', 100 | must_have_ing=c('Bourbon'), 101 | must_not_have_ing=c('Averna'), 102 | logical_sense='AND'), 103 | NA) 104 | expect_error(test4 <- .filter_ingredients(both,name_regex='', 105 | must_have_ing=c(), 106 | must_not_have_ing=c(), 107 | ing_regex='hartreus', 108 | logical_sense='OR'), 109 | NA) 110 | expect_error(test5 <- .filter_ingredients(both,name_regex='', 111 | must_have_ing=c(), 112 | must_not_have_ing=c('Benedictine'), 113 | ing_regex='hartreus', 114 | logical_sense='OR'), 115 | NA) 116 | expect_error(test6 <- .filter_ingredients(both,name_regex='sazerac', 117 | must_have_ing=c('Bourbon','Averna'), 118 | must_not_have_ing=c(), 119 | logical_sense='AND'), 120 | NA) 121 | expect_error(test7 <- .filter_ingredients(both,name_regex='', 122 | must_have_ing=c('Bourbon'), 123 | must_not_have_ing=c(), 124 | logical_sense='AND', 125 | extra_ids=c(1,2,3,4)), 126 | NA) 127 | })#UNFOLD 128 | test_that('plot stuff',{# FOLDUP 129 | #indat <- head(cocktails,200) 130 | indat <- cocktails 131 | expect_error(both <- .gen_both(indat),NA) 132 | expect_error(both_alt <- .gen_both(),NA) 133 | 134 | expect_error(both2 <- .filter_ingredients(both,name_regex='sazerac',must_have_ing=c('Bourbon','Averna'), 135 | must_not_have_ing=c(), 136 | logical_sense='OR'),NA) 137 | 138 | two_ing <- c('Bourbon','Averna') 139 | expect_error(both3 <- .filter_num_ingredients(both2,must_have_ing=two_ing,min_rating=2,max_ingr=10,max_other_ingr=5),NA) 140 | HAS_GGTERN <- FALSE 141 | if (HAS_GGTERN) { 142 | expect_error(ptern <- .prepare_ternary(both3,two_ing=two_ing),NA) 143 | expect_error(.make_ggtern_plot(ptern,two_ing),NA) 144 | } 145 | #skip_on_cran() 146 | #skip_on_travis() 147 | 148 | expect_error(both4 <- .filter_tstat(both3,min_t=2,t_zero=1),NA) 149 | expect_error(both5 <- .filter_src(both4,from_sources=c('diffordsguide')),NA) 150 | expect_error(both6 <- .add_description(both5),NA) 151 | 152 | expect_error(merged <- .merge_both(both6),NA) 153 | 154 | expect_error(ph <- .make_bar_plot(merged),NA) 155 | })# UNFOLD 156 | test_that('correlation and coingredient',{# FOLDUP 157 | #indat <- head(cocktails,100) 158 | indat <- cocktails 159 | 160 | #skip_on_cran() 161 | #skip_on_travis() 162 | expect_error(recipe_df <- .add_id(indat),NA) 163 | expect_error(rhov1 <- .coingredients(recipe_df),NA) 164 | expect_error(rhov2 <- .ingredient_rho(recipe_df),NA) 165 | })# UNFOLD 166 | test_that("call the app?",{#FOLDUP 167 | expect_error(blah <- cocktailApp(),NA) 168 | })#UNFOLD 169 | # 2FIX: check the effects of NA 170 | #UNFOLD 171 | 172 | 173 | #for vim modeline: (do not edit) 174 | # vim:ts=2:sw=2:tw=79:fdm=marker:fmr=FOLDUP,UNFOLD:cms=#%s:syn=r:ft=r:ai:si:cin:nu:fo=croql:cino=p0t0c5(0: 175 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /R/cocktailApp.r: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2018 Steven E. Pav. All Rights Reserved. 2 | # Author: Steven E. Pav 3 | # 4 | # This file is part of cocktailApp. 5 | # 6 | # cocktailApp is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # cocktailApp is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU Lesser General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU Lesser General Public License 17 | # along with cocktailApp. If not, see . 18 | 19 | # Created: 2018-06-15 20 | # Copyright: Steven E. Pav, 2018 21 | # Author: Steven E. Pav 22 | # Comments: Steven E. Pav 23 | 24 | # no longer @importFrom Ternary TernaryPlot TernaryPoints TernaryText 25 | 26 | #' Shiny app to discover cocktails. 27 | #' 28 | #' @section Legal Mumbo Jumbo: 29 | #' 30 | #' cocktailApp is distributed in the hope that it will be useful, 31 | #' but WITHOUT ANY WARRANTY; without even the implied warranty of 32 | #' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 33 | #' GNU Lesser General Public License for more details. 34 | #' 35 | #' @template etc 36 | #' 37 | #' @import shiny 38 | #' @importFrom dplyr mutate arrange select filter rename left_join right_join coalesce distinct summarize everything ungroup first sample_n one_of n 39 | #' @importFrom ggplot2 ggplot labs coord_flip aes geom_col geom_point geom_text guide_legend 40 | #' @importFrom shinythemes shinytheme 41 | #' @importFrom magrittr %>% 42 | #' @importFrom forcats fct_rev 43 | #' @importFrom utils data 44 | #' @importFrom tibble tribble 45 | #' @importFrom tidyr spread 46 | #' @importFrom stats setNames 47 | #' 48 | #' @importFrom graphics legend 49 | #' @importFrom grDevices rgb 50 | #' 51 | #' 52 | #' @name cocktailApp-package 53 | #' @rdname cocktailApp-package 54 | #' @docType package 55 | #' @title Shiny app to discover cocktails. 56 | #' @keywords package 57 | #' @note 58 | #' 59 | #' This package is maintained as a hobby. 60 | #' 61 | "_PACKAGE" 62 | 63 | #' @title News for package 'cocktailApp': 64 | #' 65 | #' @description 66 | #' 67 | #' News for package \sQuote{cocktailApp} 68 | #' 69 | #' \newcommand{\CRANpkg}{\href{https://cran.r-project.org/package=#1}{\pkg{#1}}} 70 | #' \newcommand{\cocktailApp}{\CRANpkg{cocktailApp}} 71 | #' 72 | #' @section \cocktailApp{} Version 0.2.4 (2025-11-04) : 73 | #' \itemize{ 74 | #' \item removing dependency on ggtern. 75 | #' \item adding missing reference to `dplyr::n` which broke two of the plots. 76 | #' } 77 | #' 78 | #' @section \cocktailApp{} Version 0.2.3 (2023-07-18) : 79 | #' \itemize{ 80 | #' \item fix bug where you could not search for Gin or Vodka. 81 | #' \item package had been archived because of dependency on ggtern. 82 | #' \item convert citEntry to bibentry 83 | #' } 84 | #' 85 | #' @section \cocktailApp{} Version 0.2.2 (2021-04-01) : 86 | #' \itemize{ 87 | #' \item merge many short ingredients. 88 | #' \item fix some units in webtender data. 89 | #' \item search by ingredient regex. 90 | #' } 91 | #' 92 | #' @section \cocktailApp{} Version 0.2.1 (2019-07-01) : 93 | #' \itemize{ 94 | #' \item CRAN fix as tests were hanging. 95 | #' \item replace \code{Ternary} package with \code{ggtern}. 96 | #' } 97 | #' 98 | #' @section \cocktailApp{} Version 0.2.0 (2018-08-19) : 99 | #' \itemize{ 100 | #' \item adding another source. 101 | #' \item adding \dQuote{Hobson's Choice} button. 102 | #' \item removing dependency on \code{ggtern} package, replacing with \code{Ternary}. 103 | #' } 104 | #' 105 | #' @section \cocktailApp{} Initial Version 0.1.0 (2018-07-05) : 106 | #' \itemize{ 107 | #' \item first CRAN release. 108 | #' } 109 | #' 110 | #' @name cocktailApp-NEWS 111 | #' @rdname NEWS 112 | NULL 113 | 114 | #' @title Cocktails Data 115 | #' @description Ingredients of over 26 thousand cocktails, scraped from the web. 116 | #' @format A \code{data.frame} object with around 117,000 rows and 12 columns. The 117 | #' data were scraped from four websites: Difford's guide, Webtender, and 118 | #' Kindred Cocktails, all scraped in late 2017; and Drinks Mixer, scraped in 119 | #' mid 2018. 120 | #' 121 | #' The columns are defined as follows: 122 | #' \describe{ 123 | #' \item{\code{amt}}{The numeric amount of the ingredient.} 124 | #' \item{\code{unit}}{The unit corresponding to the amount. 125 | #' The most common entry is \code{fl oz}, which is the unit for \sQuote{main} 126 | #' ingredients. 127 | #' The second most common entry is \code{garnish}. These two units 128 | #' account for over 95 percent of the rows of the data.} 129 | #' \item{\code{ingredient}}{The name of the ingredient. These may have odd 130 | #' qualifiers, or brand specifications. Some of these qualifications are 131 | #' stripped out in the \code{short_ingredient} field.} 132 | #' \item{\code{cocktail}}{The name of the cocktail.} 133 | #' \item{\code{rating}}{The rating assigned to the cocktail in the upstream database. For some 134 | #' sources, the ratings have been rescaled. Ratings are on a scale of 0 to 5.} 135 | #' \item{\code{upstream_id}}{An ID code from the upstream source.} 136 | #' \item{\code{url}}{The upstream URL.} 137 | #' \item{\code{votes}}{The number of votes in the rating, from the upstream 138 | #' database. Not always available.} 139 | #' \item{\code{added}}{The date the cocktail was added to the upstream database. Not always available.} 140 | #' \item{\code{src}}{The source of the cocktail, as listed in the upstream database. Usually not available.} 141 | #' \item{\code{short_ingredient}}{A shortened form of the ingredient, stripping away some of the qualifiers. 142 | #' This is subject to change in future releases of this package, when a better term extraction solution is found.} 143 | #' \item{\code{proportion}}{For ingredients where the \code{unit} is \code{fl oz}, 144 | #' this is the proportion of the given cocktail that consists of the given ingredient. For a given 145 | #' cocktail, the proportions should sum to one.} 146 | #' } 147 | #' @source Difford's Guide, \url{https://www.diffordsguide.com/}, 148 | #' Webtender, \url{https://www.webtender.com}, 149 | #' Kindred Cocktails, \url{https://kindredcocktails.com}, 150 | #' Drinks Mixer, \url{http://www.drinksmixer.com}. 151 | #' @note 152 | #' The data were scraped from several websites, which falls in a legal gray area. 153 | #' While, in general, raw factual data can not be copyright, there is a difference between the law and a lawsuit. 154 | #' The package author in no way claims any copyright on this data. 155 | #' @usage data(cocktails) 156 | #' @examples 157 | #' data(cocktails) 158 | #' str(cocktails) 159 | #' 160 | #' require(dplyr) 161 | #' cocktails %>% 162 | #' filter(short_ingredient %in% c('Averna','Bourbon')) %>% 163 | #' group_by(cocktail,url) %>% 164 | #' mutate(isok=n() > 1) %>% 165 | #' ungroup() %>% 166 | #' filter(isok) %>% 167 | #' arrange(desc(rating),cocktail) %>% 168 | #' select(cocktail,ingredient,amt,unit,rating) %>% 169 | #' head(n=8) 170 | #' 171 | "cocktails" 172 | 173 | globalVariables(c('cocktails','votes','rating','cocktail','proportion','url','short_ingredient','unit', 174 | 'cocktail_id','coamount','amt', 175 | 'deno','deno2','rhoval', 176 | 'sum_cova','n','rat','tot_ingr','tot_has_ingr','tot_am','ncocktails', 177 | 'tstat','page_src','tst', 178 | 'has_or_must','has_and_must','has_not_must', 179 | 'matches_name','ingr_class','description', 180 | 'isfl', 181 | 'match_re_ing', 182 | 'has_ing','bad_ing', 183 | 'ck_ok', 184 | 'pct_amt', 185 | 'tot_amt','has_both','Other', 186 | 'ingredient','coingredient','cova','wts')) 187 | 188 | 189 | # Define UI for ... 190 | my_ui <- function(page_title='Drink Schnauzer') { # nocov start 191 | utils::data("cocktails", package="cocktailApp") 192 | indat <- cocktails 193 | 194 | # let's order ingredients by number of times they 195 | # appear, then alphabetical. seems about right. 196 | 197 | normo <- indat %>% 198 | dplyr::group_by(short_ingredient) %>% 199 | dplyr::summarize(tot_am=sum(proportion,na.rm=TRUE)) %>% 200 | dplyr::ungroup() %>% 201 | dplyr::mutate(ingr_class=cut(tot_am,breaks=c(-1,0,10,100,Inf), 202 | labels=c('garnish','uncommon-spirit','common-spirit','base-spirit'))) %>% 203 | dplyr::arrange(tot_am,short_ingredient) %>% 204 | dplyr::mutate(ingr_class=forcats::fct_rev(ingr_class)) %>% 205 | dplyr::arrange(ingr_class,short_ingredient) 206 | 207 | #ingr <- unique(indat$ingredient) 208 | #ingr <- normo$ingredient 209 | ingr <- (split(normo$short_ingredient,normo$ingr_class)) 210 | #drnk <- unique(indat$cocktail) 211 | 212 | sources <- indat %>% 213 | dplyr::select(url) %>% 214 | dplyr::mutate(url=gsub('^http://(www.)?(.+).com/.+$','\\2',url)) %>% 215 | dplyr::distinct(url) 216 | all_source <- unique(sources$url) 217 | 218 | if (!is.null(page_title)) { 219 | tp_bits <- titlePanel(page_title) 220 | } else { 221 | tp_bits <- tagList() 222 | } 223 | 224 | # Define UI for ... 225 | shinyUI( 226 | fluidPage(theme=shinytheme("spacelab"),#FOLDUP 227 | # for this, see: http://stackoverflow.com/a/22886762/164611 228 | # Application title 229 | tags$head( 230 | # load accounting js 231 | #tags$script(src='js/accounting.js'), 232 | tags$script(src='test.js'), 233 | # points for style: 234 | tags$style(".table .alignRight {color: black; text-align:right;}"), 235 | tags$link(rel="stylesheet", type="text/css", href="style.css") 236 | ), 237 | tp_bits, 238 | # tags$img(id = "logoimg", src = "logo.png", width = "200px"), 239 | sidebarLayout(#FOLDUP 240 | position="left", 241 | sidebarPanel(#FOLDUP 242 | width=2, 243 | selectInput("must_have_ing","Must Have:",choices=ingr,selected=c(),multiple=TRUE), 244 | selectInput("logical_sense","Join by:",choices=c('OR','AND'),selected='OR',multiple=FALSE), 245 | selectInput("must_not_have_ing","Must Not Have:",choices=ingr,selected=c(),multiple=TRUE), 246 | selectInput("from_sources","Sources:",choices=all_source,selected=all_source[grepl('diffords|kindred',all_source)],multiple=TRUE), 247 | textInput("name_regex","Name Regex:",value='',placeholder='^sazerac'), 248 | textInput("ing_regex","Ingredient Regex:",value='',placeholder='^[Cc]hartreus'), 249 | helpText('Select for random cocktails:'), 250 | checkboxInput("hobsons","Hobson's Choice!",value=FALSE), 251 | hr(), 252 | sliderInput("max_ingr","Maximum Ingredients:",sep='',min=1,max=20,value=6), 253 | sliderInput("max_other_ingr","Maximum Unlisted Ingredients:",sep='',min=1,max=20,value=6), 254 | sliderInput("min_rating","Minimum Rating",min=1,max=5,value=3.5,step=0.5), 255 | sliderInput("min_tstat","Minimum T-Stat",min=1,max=100,value=2,step=0.25), 256 | sliderInput("t_zero","T-Stat Zero",min=1,max=5,value=2.5,step=0.25), 257 | hr(), 258 | helpText('data scraped from the web'), 259 | bookmarkButton('bookmark',title='bookmark page'), 260 | hr() 261 | ),#UNFOLD 262 | mainPanel(#FOLDUP 263 | width=9, 264 | tabsetPanel( 265 | tabPanel('drinks',#FOLDUP 266 | helpText('Select rows from this table to see the recipe below', 267 | 'and also in the plot tab.'), 268 | DT::dataTableOutput('drinks_table'), 269 | hr(), 270 | helpText('Ingredients Table:'), 271 | tableOutput('ingredients_table') 272 | ),#UNFOLD 273 | tabPanel('plots',#FOLDUP 274 | helpText('A bar plot of ingredients in the selected cocktails.', 275 | 'If nothing appears here, select rows of the table in the "drinks" tab to populate.'), 276 | plotOutput('selected_ingredients_bar_plot') 277 | ),#UNFOLD 278 | tabPanel('other',#FOLDUP 279 | helpText('This is not well tested, but here one should find a table of common co-ingredients.', 280 | 'If you have selected ingredients in the "Must Have" input, other ingredients which', 281 | 'commonly co-occur should appear in this table.'), 282 | DT::dataTableOutput('suggestions_table') 283 | ) 284 | ) # tabSetPanel#UNFOLD 285 | ) # mainPanel#UNFOLD 286 | ) # sidebarLayout#UNFOLD 287 | ) # fluidPage#UNFOLD 288 | ) # shinyUI 289 | 290 | } # nocov end 291 | 292 | 293 | .applylink <- function(title,url) { 294 | as.character(a(title,href=url,target="_blank")) 295 | } 296 | applylink <- function(title,url) { 297 | as.character(mapply(.applylink,title,url)) 298 | } 299 | 300 | # add a unique ID to recipe data based on url and cocktail name 301 | .add_id <- function(recipe_df) { 302 | # fake a distinct id 303 | subs <- recipe_df %>% 304 | dplyr::distinct(cocktail,url) %>% 305 | tibble::rowid_to_column(var='cocktail_id') 306 | recipe_df %>% 307 | dplyr::left_join(subs,by=c('cocktail','url')) 308 | } 309 | 310 | # creates information about cocktails from the recipe data frame 311 | .distill_info <- function(recipe_df) { 312 | cocktail_df <- recipe_df %>% 313 | dplyr::mutate(isfl=(unit=='fl oz')) %>% 314 | dplyr::group_by(cocktail_id) %>% 315 | dplyr::summarize(cocktail=dplyr::first(cocktail), 316 | rating=dplyr::first(rating), 317 | votes=dplyr::first(votes), 318 | url=dplyr::first(url), 319 | tot_ingr=sum(isfl)) %>% 320 | dplyr::ungroup() %>% 321 | mutate(votes=as.numeric(votes)) %>% 322 | dplyr::mutate(page_src=gsub('^http://(www.)?(.+).com/.+$','\\2',url)) 323 | } 324 | 325 | .gen_both <- function(raw_dat=NULL) { 326 | if (missing(raw_dat) || is.null(raw_dat)) { 327 | utils::data("cocktails", package="cocktailApp") 328 | raw_dat <- cocktails 329 | } 330 | # basically normalize the data: recipes and cocktails, 331 | # and keep them in a list 332 | # fake a distinct id 333 | recipe_df <- raw_dat %>% 334 | .add_id() 335 | cocktail_df <- recipe_df %>% 336 | .distill_info() 337 | list(recipe=recipe_df %>% dplyr::select(-cocktail,-rating,-votes,-url),cocktail=cocktail_df) 338 | } 339 | 340 | .filter_ingredients <- function(both,name_regex,must_have_ing,must_not_have_ing, 341 | ing_regex='',logical_sense=c('AND','OR'),extra_ids=NULL) { 342 | logical_sense <- match.arg(logical_sense) 343 | 344 | if (nzchar(name_regex)) { 345 | match_name <- both$cocktail %>% 346 | dplyr::distinct(cocktail,cocktail_id) %>% 347 | dplyr::filter(grepl(pattern=name_regex,x=cocktail,ignore.case=TRUE,perl=TRUE,fixed=FALSE)) %>% 348 | dplyr::distinct(cocktail_id) %>% 349 | dplyr::mutate(matches_name=nzchar(name_regex)) 350 | } else { 351 | # empty 352 | match_name <- tibble::tribble(~cocktail_id,~matches_name) 353 | } 354 | if (!is.null(extra_ids)) { 355 | more_match <- both$cocktail %>% 356 | dplyr::filter(cocktail_id %in% extra_ids) %>% 357 | dplyr::select(cocktail_id) %>% 358 | dplyr::mutate(matches_name=TRUE) 359 | match_name <- match_name %>% rbind(more_match) 360 | } 361 | 362 | # for the moment, these operations are terribly slow in dplyr 0.8.2 363 | # cf https://github.com/tidyverse/dplyr/issues/4458 364 | # when we upgrade to 0.8.3, it is worth timing the alternatives 365 | # on this. 366 | ok_by_ing <- both$recipe %>% 367 | dplyr::mutate(bad_ing=(short_ingredient %in% must_not_have_ing)) 368 | 369 | # search regex by ingredient 370 | if (nzchar(ing_regex)) { 371 | ok_by_ing <- ok_by_ing %>% 372 | dplyr::mutate(match_re_ing=grepl(pattern=ing_regex,x=short_ingredient,ignore.case=TRUE,perl=TRUE,fixed=FALSE)) 373 | } else { 374 | ok_by_ing <- ok_by_ing %>% 375 | dplyr::mutate(match_re_ing=FALSE) 376 | } 377 | if ((length(must_have_ing) > 0) || nzchar(ing_regex)) { 378 | if ((logical_sense=='AND') && (length(must_have_ing) > 1)) { # n.b. AND with one term is the same as OR. 379 | ok_by_ing <- ok_by_ing %>% 380 | dplyr::group_by(cocktail_id) %>% 381 | dplyr::summarize(ck_ok = (all(must_have_ing %in% short_ingredient) | any(match_re_ing)) & !any(bad_ing)) %>% 382 | dplyr::ungroup() 383 | } else { 384 | ok_by_ing <- ok_by_ing %>% 385 | dplyr::mutate(has_ing=(short_ingredient %in% must_have_ing)) %>% 386 | dplyr::group_by(cocktail_id) %>% 387 | dplyr::summarize(ck_ok=any(has_ing | match_re_ing) & !any(bad_ing)) %>% 388 | dplyr::ungroup() 389 | } 390 | } else { 391 | # empty 392 | ok_by_ing <- tibble::tribble(~cocktail_id,~ck_ok) 393 | } 394 | 395 | new_recipe <- match_name %>% 396 | dplyr::left_join(both$recipe,by='cocktail_id') %>% 397 | dplyr::select(-matches_name) %>% 398 | rbind(ok_by_ing %>% 399 | dplyr::filter(ck_ok) %>% 400 | dplyr::select(-ck_ok) %>% 401 | dplyr::left_join(both$recipe,by='cocktail_id')) 402 | 403 | new_cocktail <- both$cocktail %>% 404 | dplyr::right_join(new_recipe %>% dplyr::distinct(cocktail_id),by='cocktail_id') 405 | 406 | list(recipe=new_recipe,cocktail=new_cocktail) 407 | } 408 | 409 | .filter_num_ingredients <- function(both,must_have_ing,min_rating,max_ingr,max_other_ingr) { 410 | tot_has <- both$recipe %>% 411 | dplyr::group_by(cocktail_id) %>% 412 | dplyr::summarize(tot_has_ingr=sum(short_ingredient %in% must_have_ing)) %>% 413 | dplyr::ungroup() 414 | 415 | new_cocktail <- both$cocktail %>% 416 | dplyr::filter(rating >= min_rating, 417 | tot_ingr <= max_ingr) %>% 418 | dplyr::left_join(tot_has,by='cocktail_id') %>% 419 | dplyr::filter(tot_ingr <= max_other_ingr + tot_has_ingr) %>% 420 | dplyr::select(-tot_has_ingr) 421 | 422 | new_recipe <- both$recipe %>% 423 | dplyr::right_join(new_cocktail %>% select(cocktail_id),by='cocktail_id') 424 | 425 | list(recipe=new_recipe,cocktail=new_cocktail) 426 | } 427 | 428 | .filter_tstat <- function(both,min_t=2,t_zero=2.5,miss_votes=20,t_digits=2) { 429 | new_cocktail <- both$cocktail %>% 430 | dplyr::mutate(tstat=signif((rating - t_zero) * sqrt(coalesce(votes,miss_votes)),t_digits)) %>% 431 | dplyr::filter(tstat >= min_t) 432 | new_recipe <- both$recipe %>% 433 | dplyr::right_join(new_cocktail %>% select(cocktail_id),by='cocktail_id') 434 | 435 | list(recipe=new_recipe,cocktail=new_cocktail) 436 | } 437 | .filter_src <- function(both,from_sources) { 438 | new_cocktail <- both$cocktail %>% 439 | dplyr::filter(page_src %in% from_sources) 440 | new_recipe <- both$recipe %>% 441 | dplyr::right_join(new_cocktail %>% select(cocktail_id),by='cocktail_id') 442 | list(recipe=new_recipe,cocktail=new_cocktail) 443 | } 444 | 445 | 446 | .add_description <- function(both) { 447 | descdat <- both$recipe %>% 448 | dplyr::filter(unit=='fl oz') %>% 449 | dplyr::arrange(dplyr::desc(amt)) %>% 450 | dplyr::group_by(cocktail_id) %>% 451 | dplyr::summarize(description=paste0(paste0(short_ingredient,collapse=', '),'.')) %>% 452 | dplyr::ungroup() 453 | new_cocktail <- both$cocktail %>% 454 | dplyr::left_join(descdat,by=c('cocktail_id')) 455 | list(recipe=both$recipe,cocktail=new_cocktail) 456 | } 457 | 458 | # merge and arrange 459 | .merge_both <- function(both) { 460 | both$recipe %>% 461 | dplyr::left_join(both$cocktail,by='cocktail_id') %>% 462 | dplyr::select(cocktail,rating,amt,unit,ingredient,everything()) %>% 463 | dplyr::arrange(dplyr::desc(rating),cocktail,dplyr::desc(as.numeric(unit=='fl oz')),dplyr::desc(amt)) 464 | } 465 | 466 | .drinks_table <- function(both) { 467 | otdat <- both$cocktail %>% 468 | dplyr::mutate(cocktail=applylink(cocktail,url)) %>% 469 | dplyr::select(rating,tstat,cocktail,description) 470 | } 471 | 472 | # from the recipe_df, compute co-ingredients table. 473 | .coingredients <- function(recipe_df) { 474 | sub_df <- recipe_df %>% 475 | dplyr::select(short_ingredient,cocktail_id,rating,proportion) 476 | coing <- sub_df %>% 477 | dplyr::filter(!is.na(proportion)) %>% 478 | dplyr::mutate(rating=coalesce(rating,1)) %>% 479 | dplyr::inner_join(sub_df %>% 480 | dplyr::rename(coingredient=short_ingredient,coamount=proportion),by=c('cocktail_id','rating'),relationship='many-to-many') %>% 481 | dplyr::mutate(cova=proportion * coamount) %>% 482 | dplyr::mutate(wts=rating) %>% 483 | dplyr::group_by(short_ingredient,coingredient) %>% 484 | dplyr::summarize(sum_cova=sum(cova*wts,na.rm=TRUE), 485 | sum_wts=sum(wts,na.rm=TRUE), 486 | ncocktails=n()) %>% 487 | dplyr::ungroup() %>% 488 | dplyr::arrange(dplyr::desc(ncocktails)) 489 | } 490 | 491 | .ingredient_rho <- function(recipe_df) { 492 | coing <- .coingredients(recipe_df) 493 | diagv <- coing %>% 494 | dplyr::filter(short_ingredient==coingredient) %>% 495 | dplyr::mutate(deno=sqrt(sum_cova)) 496 | 497 | rhov <- coing %>% 498 | dplyr::left_join(diagv %>% select(short_ingredient,deno),by='short_ingredient',relationship='many-to-many') %>% 499 | dplyr::left_join(diagv %>% select(coingredient,deno) %>% rename(deno2=deno),by='coingredient',relationship='many-to-many') %>% 500 | dplyr::mutate(rhoval=sum_cova / (deno * deno2)) %>% 501 | dplyr::filter(!is.na(rhoval)) %>% 502 | dplyr::select(short_ingredient,coingredient,ncocktails,rhoval) %>% 503 | dplyr::filter(ncocktails > 2) %>% 504 | dplyr::arrange(dplyr::desc(rhoval)) 505 | } 506 | 507 | .make_bar_plot <- function(both_df) { 508 | #facet_grid(.~rating) + 509 | ph <- both_df %>% 510 | dplyr::filter(unit=='fl oz') %>% 511 | dplyr::arrange(dplyr::desc(rating)) %>% 512 | mutate(pct_amt=100*proportion) %>% 513 | ggplot(aes(ingredient,pct_amt,fill=cocktail)) + 514 | geom_col(position='dodge') + 515 | coord_flip() + 516 | labs(y='amount (%)', 517 | x='ingredient', 518 | fill='cocktail', 519 | title='selected drinks') 520 | } 521 | 522 | # testing 523 | #preing <- c('bourbon','benedictine') 524 | #tern_df <- data.frame(bourbon=runif(50,max=0.5),benedictine=runif(50,max=0.3)) %>% 525 | #mutate(Other=1-bourbon-benedictine) %>% 526 | #mutate(rating=sample(2:5,n(),replace=TRUE)) %>% 527 | #mutate(cocktail=sample(paste0('xy',letters),n(),replace=TRUE)) %>% 528 | #mutate(page_src=sample(1:3,n(),replace=TRUE)) 529 | # 530 | 531 | #.make_tern_plot <- function(tern_df,preing) { 532 | #preing <- c(preing,'Other') 533 | #TernaryPlot(alab=paste(preing[1],'\u2192'), 534 | #blab=paste(preing[2],'\u2192'), 535 | #clab=paste('\u2190',preing[3]), 536 | #atip=preing[1],btip=preing[2],ctip=preing[3], 537 | #point='up', lab.cex=0.8, grid.minor.lines = 0, 538 | #grid.lty='solid', col=rgb(0.9, 0.9, 0.9), grid.col='white', 539 | #axis.col=rgb(0.6, 0.6, 0.6), ticks.col=rgb(0.6, 0.6, 0.6), 540 | #padding=0.08) 541 | ##bg=tern_df$rating / 3, 542 | #coords <- tern_df %>% select(one_of(preing[1]),one_of(preing[2]),one_of(preing[3])) 543 | #blue0 <- min(min(tern_df$rating),1) 544 | #blueness <- (tern_df$rating - blue0) / (5 - blue0) 545 | #redness <- 1 - blueness 546 | #fac_src <- factor(tern_df$page_src) 547 | #pch0 <- 22 548 | #TernaryPoints(coords, 549 | #col=rgb(red=redness,green=0,blue=blueness,alpha=0.25), 550 | #bg=rgb(red=redness,green=0,blue=blueness,alpha=0.25), 551 | #cex=tern_df$rating / 3, 552 | #pch=pch0+as.numeric(fac_src)) 553 | #TernaryText(coords, 554 | #tern_df$cocktail, 555 | #col=rgb(red=redness,green=0,blue=blueness,alpha=0.65), 556 | #bg=rgb(red=redness,green=0,blue=blueness,alpha=0.65), 557 | #cex=1) 558 | #legend('right', 559 | #pt.cex=1.8, 560 | #pt.bg=rgb(0, 0, 255, 128, NULL, 255), 561 | #pch=pch0+(1:length(levels(fac_src))), 562 | #legend=levels(fac_src), 563 | #cex=0.8, bty='n') 564 | #} 565 | 566 | # WAT? 567 | # @param input the shiny server (reactive) input list. 568 | # @param output the shiny server (reactive) output list. 569 | # @param session a shiny server session object? 570 | # Define server logic # FOLDUP 571 | my_server <- function(input, output, session) { # nocov start 572 | get_both <- reactive({ 573 | utils::data("cocktails", package="cocktailApp") 574 | both <- .gen_both(cocktails) 575 | }) 576 | # like a covariance of ingredients 577 | get_ing_rho <- reactive({ 578 | both <- get_both() 579 | # add rating 580 | recipe_df <- both$recipe %>% 581 | left_join(both$cocktail,by='cocktail_id') 582 | rhov <- .ingredient_rho(recipe_df) 583 | }) 584 | suggested_ingr <- reactive({ 585 | retv <- get_ing_rho() %>% 586 | rename(ingredient=short_ingredient) %>% 587 | dplyr::filter(ingredient %in% input$must_have_ing,ingredient != coingredient,ncocktails > 5) %>% 588 | dplyr::arrange(dplyr::desc(rhoval)) 589 | }) 590 | 591 | filter_ingr <- reactive({ 592 | .filter_ingredients(both=get_both(),name_regex=input$name_regex,extra_ids=hobsons_choice$ids, 593 | must_have_ing=input$must_have_ing, 594 | must_not_have_ing=input$must_not_have_ing, 595 | ing_regex=input$ing_regex, 596 | logical_sense=input$logical_sense) 597 | }) 598 | filter_num_ingr <- reactive({ 599 | .filter_num_ingredients(both=filter_ingr(),must_have_ing=input$must_have_ing, 600 | min_rating=input$min_rating,max_ingr=input$max_ingr, 601 | max_other_ingr=input$max_other_ingr) 602 | 603 | }) 604 | filter_tstat <- reactive({ 605 | .filter_tstat(both=filter_num_ingr(),min_t=input$min_tstat,t_zero=input$t_zero) 606 | }) 607 | filter_src <- reactive({ 608 | .filter_src(both=filter_tstat(),from_sources=input$from_sources) 609 | }) 610 | final_both <- reactive({ 611 | .add_description(both=filter_src()) 612 | }) 613 | final_merged <- reactive({ 614 | .merge_both(both=final_both()) 615 | }) 616 | selectable <- reactive({ 617 | }) 618 | 619 | # if the user selects any drinks from the table, 620 | # take their ingredients 621 | selected_drinks <- reactive({ 622 | both <- final_both() 623 | drinks <- both$cocktail 624 | 625 | selrows <- input$drinks_table_rows_selected 626 | otdat <- both$recipe %>% 627 | dplyr::inner_join(drinks[selrows,] %>% 628 | dplyr::select(cocktail_id,cocktail,rating),by='cocktail_id') 629 | otdat 630 | }) 631 | 632 | hobsons_choice <- reactiveValues(ids=NULL) 633 | 634 | observeEvent(input$hobsons,{ 635 | if (input$hobsons) { 636 | both <- .filter_num_ingredients(both=get_both(),must_have_ing=c(), 637 | min_rating=input$min_rating,max_ingr=input$max_ingr, 638 | max_other_ingr=input$max_other_ingr) 639 | both <- .filter_tstat(both=both,min_t=input$min_tstat,t_zero=input$t_zero) 640 | both <- .filter_src(both=both,from_sources=input$from_sources) 641 | eligible <- both$cocktail %>% 642 | distinct(cocktail_id) %>% 643 | sample_n(size=5,replace=FALSE) 644 | hobsons_choice$ids <- eligible$cocktail_id 645 | } else { 646 | hobsons_choice$ids <- NULL 647 | } 648 | }) 649 | # table of comparables #FOLDUP 650 | output$drinks_table <- DT::renderDataTable({ 651 | otdat <- .drinks_table(both=final_both()) 652 | # for this javascript shiznit, recall that javascript starts 653 | # counting at zero! 654 | # 655 | # cf 656 | # col rendering: http://rstudio.github.io/DT/options.html 657 | # https://github.com/jcheng5/shiny-jsdemo/blob/master/ui.r 658 | DT::datatable(otdat,caption='Matching cocktails. Click on a row to populate the ingredients table below.', 659 | escape=FALSE,rownames=FALSE, 660 | options=list(order=list(list(1,'desc'),list(0,'desc'),list(2,'asc')), 661 | paging=TRUE, 662 | pageLength=15)) 663 | }, 664 | server=TRUE)#UNFOLD 665 | # table of suggestions #FOLDUP 666 | output$suggestions_table <- DT::renderDataTable({ 667 | selco <- suggested_ingr() 668 | 669 | # for this javascript shiznit, recall that javascript starts 670 | # counting at zero! 671 | # 672 | # cf 673 | # col rendering: http://rstudio.github.io/DT/options.html 674 | # https://github.com/jcheng5/shiny-jsdemo/blob/master/ui.r 675 | DT::datatable(selco, caption='Coingredients.', 676 | escape=FALSE, rownames=FALSE, 677 | options=list(paging=TRUE, 678 | pageLength=20)) %>% 679 | DT::formatRound(columns=c('rhoval'),digits=2) 680 | }, 681 | server=TRUE)#UNFOLD 682 | 683 | output$selected_ingredients_bar_plot <- renderPlot({ 684 | ph <- selected_drinks() %>% .make_bar_plot() 685 | ph 686 | }) 687 | output$ingredients_table <- renderTable({ 688 | # may have to select down some more. 689 | retv <- selected_drinks() %>% dplyr::select(cocktail,amt,unit,ingredient) 690 | },striped=TRUE,width='100%') 691 | 692 | #'drinks_table_rows_all', 693 | setBookmarkExclude(c('bookmark', 694 | 'drinks_table_cell_clicked', 695 | 'drinks_table_row_last_clicked')) 696 | observeEvent(input$bookmark,{ session$doBookmark() }) 697 | } # nocov end 698 | 699 | # UNFOLD 700 | 701 | #' @title cocktailApp . 702 | #' 703 | #' @description 704 | #' 705 | #' A \code{shiny} app to explore cocktails. The app allows you to enter ingredients 706 | #' that a cocktail must have, or ingredients that it must not have. One can 707 | #' filter by number of ingredients, minimum rating, minimum \sQuote{t stat} 708 | #' (computed as the rating minus the T stat zero all multiplied by the square 709 | #' root of the number of ratings). One can also search for cocktail by regex. 710 | #' 711 | #' In the main tab, titled \dQuote{drinks}, one can find a table with the 712 | #' summaries of matching cocktails. Selecting rows of this table will 713 | #' cause the cocktail table below to be populated with more details on each 714 | #' selected cocktail. Selecting rows will also populate the bar chart 715 | #' in the \dQuote{plots} tab. 716 | #' 717 | #' If two or more ingredients are selected, drinks with non-zero quantities 718 | #' of both of these will be shown in a ternary plot in the \dQuote{tern} 719 | #' tab. 720 | #' 721 | #' In the \dQuote{other} tab is a table with common co-ingredients of the 722 | #' selected ingredients. A co-ingredient is an ingredient that commonly 723 | #' occurs with the selected ingredient, as measured by the number of 724 | #' cocktails, and by \sQuote{rho}, which is like a correlation based 725 | #' on the proportion. 726 | #' 727 | #' A checkbox labelled, \dQuote{Hobson's Choice} allows you to populate 728 | #' the cocktail table with five random cocktails that meet the numerical 729 | #' filters on number of ingredients, rating, and so on, but which do not 730 | #' meet the ingredient selections. Unselecting and re-selecting the 731 | #' checkbox selects a new set of random cocktails. Note that the random 732 | #' selection is not responsive to changes in the numerical filters. 733 | #' 734 | #' @section Screenshots: 735 | #' 736 | #' The main page looks as follows. In this case the user has selected 737 | #' two ingredients, \sQuote{Benedictine} and \sQuote{Bourbon}. The 738 | #' user has modified some of the numeric filters resulting in only 739 | #' six cocktails in the cocktail table on the right in the main 740 | #' tab. 741 | #' 742 | #' \if{html}{ 743 | #' \figure{Screenshot-mainpage.png}{options: width="100\%" alt="Screenshot: landing page of app"} 744 | #' } 745 | #' \if{latex}{ 746 | #' \figure{Screenshot-mainpage.png}{options: width=14cm} 747 | #' } 748 | #' 749 | #' In the next screenshot, the user has selected two of 750 | #' the rows of the cocktail table, 751 | #' which causes the ingredients table 752 | #' on the lower right to be populated with the recipes of the 753 | #' selected cocktails. Instead one could click on the linked 754 | #' cocktail names to be taken to the upstream source of the recipe, 755 | #' which is recommended since those pages typically have better 756 | #' instructions. 757 | #' 758 | #' \if{html}{ 759 | #' \figure{Screenshot-ingredients.png}{options: width="100\%" alt="Screenshot: landing page of app, with selected cocktails"} 760 | #' } 761 | #' \if{latex}{ 762 | #' \figure{Screenshot-ingredients.png}{options: width=14cm} 763 | #' } 764 | #' 765 | #' In the following screenshot, the user has selected two ingredients, 766 | #' \sQuote{Benedictine} and \sQuote{bourbon}, then clicked on the 767 | #' the main table, then selected the \sQuote{plots} tab. This 768 | #' shows a bar plot of the proportions of all ingredients 769 | #' in all the selected cocktails. 770 | #' 771 | #' \if{html}{ 772 | #' \figure{Screenshot-barplot.png}{options: width="100\%" alt="Screenshot: bar plot of ingredients"} 773 | #' } 774 | #' \if{latex}{ 775 | #' \figure{Screenshot-barplot.png}{options: width=14cm} 776 | #' } 777 | #' 778 | #' In this screenshot, the user has checked the \dQuote{Hobson's Choice} 779 | #' box, which adds 5 random cocktails to the cocktail table. 780 | #' 781 | #' \if{html}{ 782 | #' \figure{Screenshot-hobsons.png}{options: width="100\%" alt="Screenshot: main page with Hobsons choice"} 783 | #' } 784 | #' \if{latex}{ 785 | #' \figure{Screenshot-hobsons.png}{options: width=14cm} 786 | #' } 787 | #' 788 | #' @return Runs the \code{shiny} app. 789 | #' 790 | #' @param page_title an optional page title for the app. A \code{NULL} value 791 | #' causes no page title to be used. 792 | #' @inheritParams shiny::shinyApp 793 | #' @keywords shiny 794 | #' @template etc 795 | #' @name cocktailApp 796 | #' @rdname cocktailApp 797 | #' @examples 798 | #' \dontrun{ 799 | #' cocktailApp() 800 | #' } 801 | #' @export 802 | cocktailApp <- function(page_title='Drink Schnauzer',enableBookmarking='url') { 803 | shinyApp(ui=function(request){ my_ui(page_title=page_title) }, # to enable bookmarking, do this rigamarole. 804 | server=my_server, 805 | enableBookmarking=enableBookmarking) 806 | } 807 | # importFrom DT dataTableOutput renderDataTable datatable 808 | 809 | #for vim modeline: (do not edit) 810 | # vim:fdm=marker:fmr=FOLDUP,UNFOLD:cms=#%s:syn=r:ft=r 811 | --------------------------------------------------------------------------------