Initial release - 0.1 Alpha
Browse filesPolyphenol Estimation Pipeline Shiny Application
A web interface for the Polyphenol Estimation Pipeline developed by
Stephanie M.G. Wilson at UC Davis.
Features:
- ASA24 and NHANES diet recall data processing
- Food disaggregation using FDA FDD v3.1
- Polyphenol intake calculation (total and class levels)
- 42-component Dietary Inflammatory Index (DII)
- Interactive charts and data tables
- CSV export of all results
Pipeline: https://github.com/SWi1/polyphenol_pipeline
Shiny App: Richard Stoker, USDA-ARS
- .gitattributes +5 -0
- CHANGELOG.md +87 -0
- Dockerfile +53 -0
- LICENSE +120 -0
- README.md +251 -5
- app.R +34 -0
- assets/PEPlogo_720x720.jpeg +3 -0
- assets/splash_animation.mp4 +3 -0
- data/FDA_FDD_All_Records_v_3.1.xlsx +3 -0
- data/FDA_FooDB_Mapping_Nov_2025.csv +3 -0
- data/FooDB_DII_polyphenol_list.csv +3 -0
- data/FooDB_Eugenol_Content_Final.csv +3 -0
- data/FooDB_polyphenol_content_with_dbPUPsubstrates_Aug25.csv +3 -0
- data/FooDB_polyphenol_list_3072.csv +3 -0
- demo_data/VVKAJ_Items.csv +3 -0
- docs/shiny_app.md +78 -0
- docs/shiny_running_locally.md +208 -0
- docs/shiny_user_guide.md +185 -0
- global.R +219 -0
- server.R +1184 -0
- ui.R +708 -0
- www/PEPlogo_720x720.jpeg +3 -0
- www/Polyphenol_Estimation_Pipeline_Overview.png +3 -0
- www/custom.css +748 -0
- www/splash_animation.mp4 +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,8 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
*.xlsx filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
*.csv filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
*.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
*.jpeg filter=lfs diff=lfs merge=lfs -text
|
CHANGELOG.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Changelog
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
## [0.1 Alpha] - 2025-11-26
|
| 7 |
+
|
| 8 |
+
Initial release of the Polyphenol Estimation Pipeline Shiny Application.
|
| 9 |
+
|
| 10 |
+
### Added
|
| 11 |
+
|
| 12 |
+
**Core Pipeline Functionality**
|
| 13 |
+
- ASA24 diet recall data processing
|
| 14 |
+
- NHANES diet recall data processing
|
| 15 |
+
- Food disaggregation using FDA Food Disaggregation Database (FDD) v3.1
|
| 16 |
+
- Ingredient mapping to FooDB polyphenol content database
|
| 17 |
+
- Coffee/tea brewing adjustment for accurate polyphenol estimation
|
| 18 |
+
- Polyphenol intake calculation at total and class levels
|
| 19 |
+
- Food contributor analysis identifying top polyphenol sources
|
| 20 |
+
- 42-component Dietary Inflammatory Index (DII) calculation including:
|
| 21 |
+
- Eugenol intake from FooDB
|
| 22 |
+
- Six polyphenol subclasses (isoflavones, flavan-3-ols, flavones, flavonols, flavanones, anthocyanidins)
|
| 23 |
+
- Seven DII food components (garlic, ginger, onion, turmeric, tea, pepper, thyme/oregano)
|
| 24 |
+
- DII scoring based on Shivappa et al. (2014) parameters
|
| 25 |
+
- Energy-standardized results (mg/1000 kcal)
|
| 26 |
+
|
| 27 |
+
**User Interface**
|
| 28 |
+
- Five-tab navigation structure: Get Started, Input, Results, QA/QC, About
|
| 29 |
+
- Animated splash screen with pipeline logo
|
| 30 |
+
- Interactive data preview with validation feedback
|
| 31 |
+
- Chart/Table toggle buttons for all results views
|
| 32 |
+
- Responsive design supporting various screen sizes
|
| 33 |
+
- Bootstrap 5 theming via bslib
|
| 34 |
+
|
| 35 |
+
**Results Dashboard**
|
| 36 |
+
- Summary cards showing key metrics (subjects analyzed, mean intake, classes, unmapped foods)
|
| 37 |
+
- Total Intake tab: subject bar chart and distribution histogram
|
| 38 |
+
- Polyphenol Class tab: mean intake by chemical class
|
| 39 |
+
- Food Contributors tab: treemap visualization of top food sources
|
| 40 |
+
- DII Scores tab: distribution histogram and per-subject bar chart with color coding
|
| 41 |
+
|
| 42 |
+
**Data Export**
|
| 43 |
+
- Individual CSV downloads for each results table
|
| 44 |
+
- Download All button creating ZIP archive with all results
|
| 45 |
+
- DII scores included in export package
|
| 46 |
+
|
| 47 |
+
**Quality Assurance**
|
| 48 |
+
- Input data validation for required columns
|
| 49 |
+
- Multiple recall requirement enforcement
|
| 50 |
+
- Unmapped foods identification and reporting
|
| 51 |
+
- Missing food distribution visualization
|
| 52 |
+
|
| 53 |
+
**Documentation**
|
| 54 |
+
- README with setup and usage instructions
|
| 55 |
+
- In-app About page with methodology description
|
| 56 |
+
- Pipeline overview diagram
|
| 57 |
+
|
| 58 |
+
**Deployment**
|
| 59 |
+
- Dockerfile for Hugging Face Spaces deployment
|
| 60 |
+
- Self-contained application with all required data files
|
| 61 |
+
- Demo data included for testing
|
| 62 |
+
|
| 63 |
+
### Technical Details
|
| 64 |
+
|
| 65 |
+
- Built with R Shiny and bslib (Bootstrap 5)
|
| 66 |
+
- Interactive visualizations via Plotly
|
| 67 |
+
- Data tables via DT package
|
| 68 |
+
- Custom CSS styling with CSS variables for theming
|
| 69 |
+
|
| 70 |
+
### Known Limitations
|
| 71 |
+
|
| 72 |
+
- Large datasets may experience slower processing times
|
| 73 |
+
- NHANES data support is functional but less tested than ASA24
|
| 74 |
+
|
| 75 |
+
---
|
| 76 |
+
|
| 77 |
+
## Planned Features
|
| 78 |
+
|
| 79 |
+
**Next Release**
|
| 80 |
+
- Docker container image for local deployment
|
| 81 |
+
- Compound-level polyphenol analysis
|
| 82 |
+
|
| 83 |
+
**Future Releases**
|
| 84 |
+
- Batch processing support
|
| 85 |
+
- Custom polyphenol database uploads
|
| 86 |
+
- PDF/HTML report generation
|
| 87 |
+
- Additional visualization options
|
Dockerfile
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ==============================================================================
|
| 2 |
+
# Dockerfile for Polyphenol Estimation Pipeline Shiny App
|
| 3 |
+
# Pipeline: Stephanie M.G. Wilson
|
| 4 |
+
# App: Richard Stoker
|
| 5 |
+
# Version: Alpha 0.1
|
| 6 |
+
# ==============================================================================
|
| 7 |
+
|
| 8 |
+
FROM rocker/shiny-verse:4.3.2
|
| 9 |
+
|
| 10 |
+
# Install system dependencies
|
| 11 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 12 |
+
libcurl4-openssl-dev \
|
| 13 |
+
libssl-dev \
|
| 14 |
+
libxml2-dev \
|
| 15 |
+
libfontconfig1-dev \
|
| 16 |
+
libharfbuzz-dev \
|
| 17 |
+
libfribidi-dev \
|
| 18 |
+
libfreetype6-dev \
|
| 19 |
+
libpng-dev \
|
| 20 |
+
libtiff5-dev \
|
| 21 |
+
libjpeg-dev \
|
| 22 |
+
zlib1g-dev \
|
| 23 |
+
&& apt-get clean \
|
| 24 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 25 |
+
|
| 26 |
+
# Install R packages
|
| 27 |
+
RUN R -e "install.packages(c( \
|
| 28 |
+
'shiny', \
|
| 29 |
+
'bslib', \
|
| 30 |
+
'shinyWidgets', \
|
| 31 |
+
'DT', \
|
| 32 |
+
'plotly', \
|
| 33 |
+
'tidyverse', \
|
| 34 |
+
'readxl', \
|
| 35 |
+
'vroom', \
|
| 36 |
+
'rmarkdown', \
|
| 37 |
+
'zip' \
|
| 38 |
+
), repos='https://cran.rstudio.com/', dependencies=TRUE)"
|
| 39 |
+
|
| 40 |
+
# Create app directory
|
| 41 |
+
WORKDIR /app
|
| 42 |
+
|
| 43 |
+
# Copy application files
|
| 44 |
+
COPY app.R global.R ui.R server.R ./
|
| 45 |
+
COPY www/ ./www/
|
| 46 |
+
COPY data/ ./data/
|
| 47 |
+
COPY demo_data/ ./demo_data/
|
| 48 |
+
|
| 49 |
+
# Expose port for Hugging Face Spaces
|
| 50 |
+
EXPOSE 7860
|
| 51 |
+
|
| 52 |
+
# Run the application
|
| 53 |
+
CMD ["R", "-e", "shiny::runApp('/app', host='0.0.0.0', port=7860)"]
|
LICENSE
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# License
|
| 2 |
+
|
| 3 |
+
This project contains multiple components with different licenses:
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Shiny Application Code
|
| 8 |
+
|
| 9 |
+
**License:** CC0 1.0 Universal (Public Domain Dedication)
|
| 10 |
+
|
| 11 |
+
The Shiny application code (app.R, global.R, ui.R, server.R, custom.css, and documentation)
|
| 12 |
+
was developed by Richard Stoker at the United States Department of Agriculture -
|
| 13 |
+
Agricultural Research Service.
|
| 14 |
+
|
| 15 |
+
As a work of the United States Government, this code is in the public domain within
|
| 16 |
+
the United States. Additionally, Richard Stoker waives copyright and related rights
|
| 17 |
+
in the work worldwide through the CC0 1.0 Universal public domain dedication.
|
| 18 |
+
|
| 19 |
+
You can copy, modify, distribute and perform the work, even for commercial purposes,
|
| 20 |
+
all without asking permission.
|
| 21 |
+
|
| 22 |
+
### CC0 1.0 Universal
|
| 23 |
+
|
| 24 |
+
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES.
|
| 25 |
+
DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP.
|
| 26 |
+
CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS
|
| 27 |
+
MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
| 28 |
+
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF
|
| 29 |
+
THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER.
|
| 30 |
+
|
| 31 |
+
#### Statement of Purpose
|
| 32 |
+
|
| 33 |
+
The laws of most jurisdictions throughout the world automatically confer exclusive
|
| 34 |
+
Copyright and Related Rights (defined below) upon the creator and subsequent
|
| 35 |
+
owner(s) (each and all, an "owner") of an original work of authorship and/or a
|
| 36 |
+
database (each, a "Work").
|
| 37 |
+
|
| 38 |
+
Certain owners wish to permanently relinquish those rights to a Work for the
|
| 39 |
+
purpose of contributing to a commons of creative, cultural and scientific works
|
| 40 |
+
("Commons") that the public can reliably and without fear of later claims of
|
| 41 |
+
infringement build upon, modify, incorporate in other works, reuse and
|
| 42 |
+
redistribute as freely as possible in any form whatsoever and for any purposes,
|
| 43 |
+
including without limitation commercial purposes.
|
| 44 |
+
|
| 45 |
+
For the complete CC0 1.0 Universal text, see:
|
| 46 |
+
https://creativecommons.org/publicdomain/zero/1.0/legalcode
|
| 47 |
+
|
| 48 |
+
---
|
| 49 |
+
|
| 50 |
+
## Polyphenol Estimation Pipeline
|
| 51 |
+
|
| 52 |
+
**License:** MIT License
|
| 53 |
+
|
| 54 |
+
The underlying Polyphenol Estimation Pipeline methodology and the reference database
|
| 55 |
+
files in the `data/` folder were developed by Stephanie M.G. Wilson at the
|
| 56 |
+
University of California, Davis and are distributed under the MIT License.
|
| 57 |
+
|
| 58 |
+
**Repository:** https://github.com/SWi1/polyphenol_pipeline
|
| 59 |
+
|
| 60 |
+
### Files covered by MIT License:
|
| 61 |
+
|
| 62 |
+
- `data/FDA_FDD_All_Records_v_3.1.xlsx`
|
| 63 |
+
- `data/FDA_FooDB_Mapping_Nov_2025.csv`
|
| 64 |
+
- `data/FooDB_polyphenol_content_with_dbPUPsubstrates_Aug25.csv`
|
| 65 |
+
- `data/FooDB_polyphenol_list_3072.csv`
|
| 66 |
+
- `data/FooDB_Eugenol_Content_Final.csv`
|
| 67 |
+
- `data/FooDB_DII_polyphenol_list.csv`
|
| 68 |
+
- `demo_data/VVKAJ_Items.csv`
|
| 69 |
+
|
| 70 |
+
### MIT License Text
|
| 71 |
+
|
| 72 |
+
Copyright (c) 2025 Stephanie M.G. Wilson
|
| 73 |
+
|
| 74 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 75 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 76 |
+
in the Software without restriction, including without limitation the rights
|
| 77 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 78 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 79 |
+
furnished to do so, subject to the following conditions:
|
| 80 |
+
|
| 81 |
+
The above copyright notice and this permission notice shall be included in all
|
| 82 |
+
copies or substantial portions of the Software.
|
| 83 |
+
|
| 84 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 85 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 86 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 87 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 88 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 89 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 90 |
+
SOFTWARE.
|
| 91 |
+
|
| 92 |
+
---
|
| 93 |
+
|
| 94 |
+
## Third-Party R Packages
|
| 95 |
+
|
| 96 |
+
This application uses the following R packages:
|
| 97 |
+
|
| 98 |
+
| Package | License | Author/Maintainer |
|
| 99 |
+
|---------|---------|-------------------|
|
| 100 |
+
| shiny | GPL-3 | Winston Chang, RStudio |
|
| 101 |
+
| bslib | MIT | Carson Sievert, RStudio |
|
| 102 |
+
| shinyWidgets | GPL-3 | Victor Perrier, dreamRs |
|
| 103 |
+
| DT | GPL-3 | Yihui Xie, RStudio |
|
| 104 |
+
| plotly | MIT | Carson Sievert |
|
| 105 |
+
| tidyverse | MIT | Hadley Wickham, RStudio |
|
| 106 |
+
| readxl | MIT | Hadley Wickham, RStudio |
|
| 107 |
+
| vroom | MIT | Jim Hester, RStudio |
|
| 108 |
+
| rmarkdown | GPL-3 | Yihui Xie, RStudio |
|
| 109 |
+
| zip | MIT | Gabor Csardi, RStudio |
|
| 110 |
+
|
| 111 |
+
For full license texts of these packages, see their respective CRAN pages or
|
| 112 |
+
package documentation.
|
| 113 |
+
|
| 114 |
+
---
|
| 115 |
+
|
| 116 |
+
## Summary
|
| 117 |
+
|
| 118 |
+
- **Shiny app code:** CC0 (Public Domain) - Richard Stoker, USDA-ARS
|
| 119 |
+
- **Pipeline data files:** MIT License - Stephanie M.G. Wilson, UC Davis
|
| 120 |
+
- **R packages:** Various open source licenses (GPL-3, MIT)
|
README.md
CHANGED
|
@@ -1,11 +1,257 @@
|
|
| 1 |
---
|
| 2 |
-
title: Polyphenol
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
license: cc0-1.0
|
| 9 |
---
|
| 10 |
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Polyphenol Estimation Pipeline
|
| 3 |
+
emoji: 🍇
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: gray
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
license: cc0-1.0
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# Polyphenol Estimation Pipeline - Shiny Application
|
| 12 |
+
|
| 13 |
+
A web-based interface for the Polyphenol Estimation Pipeline.
|
| 14 |
+
|
| 15 |
+
**Version:** 0.1 Alpha
|
| 16 |
+
**Release Date:** November 2025
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## About
|
| 21 |
+
|
| 22 |
+
This Shiny application provides a graphical interface for the **Polyphenol Estimation Pipeline** developed by **Stephanie M.G. Wilson** at the University of California, Davis.
|
| 23 |
+
|
| 24 |
+
**Pipeline Repository:** https://github.com/SWi1/polyphenol_pipeline
|
| 25 |
+
|
| 26 |
+
The pipeline - created by Dr. Wilson - automates the estimation of dietary polyphenol intake from ASA24 or NHANES diet recall data. It disaggregates reported foods into underlying ingredients using the FDA Food Disaggregation Database (FDD), maps those ingredients to polyphenol content from FooDB, and calculates intake at total, class, and compound levels. The pipeline also computes a 42-component Dietary Inflammatory Index (DII).
|
| 27 |
+
|
| 28 |
+
**This Shiny app is a wrapper around Dr. Wilson's pipeline.** All methodology, database mappings, and calculations come from her work. This interface simply makes it easier for researchers to run the pipeline without writing R code.
|
| 29 |
+
|
| 30 |
+
For full details on the methodology, database mappings, and validation, see the [pipeline repository](https://github.com/SWi1/polyphenol_pipeline).
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## Try the App
|
| 35 |
+
|
| 36 |
+
### Option 1: Hugging Face (No Installation Required)
|
| 37 |
+
|
| 38 |
+
The easiest way to use this application is through the hosted version on Hugging Face Spaces:
|
| 39 |
+
|
| 40 |
+
**[Launch App on Hugging Face](https://huggingface.co/spaces/richtext/Polyphenol-Pipeline-Shinyapp)**
|
| 41 |
+
|
| 42 |
+
Just open the link, load the demo data, and run the pipeline.
|
| 43 |
+
|
| 44 |
+
### Option 2: Run Locally in RStudio
|
| 45 |
+
|
| 46 |
+
**Requirements:**
|
| 47 |
+
- R (version 4.1 or higher recommended)
|
| 48 |
+
- RStudio (optional but recommended)
|
| 49 |
+
|
| 50 |
+
**Step 1: Download the Application**
|
| 51 |
+
|
| 52 |
+
Clone or download this repository:
|
| 53 |
+
```bash
|
| 54 |
+
git clone https://github.com/RichardStoker-USDA/Polyphenol-Pipeline-Shinyapp.git
|
| 55 |
+
cd Polyphenol-Pipeline-Shinyapp
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
Or download as a ZIP file and extract it.
|
| 59 |
+
|
| 60 |
+
**Step 2: Install Required Packages**
|
| 61 |
+
|
| 62 |
+
Open R or RStudio and run:
|
| 63 |
+
```r
|
| 64 |
+
install.packages(c(
|
| 65 |
+
"shiny",
|
| 66 |
+
"bslib",
|
| 67 |
+
"shinyWidgets",
|
| 68 |
+
"DT",
|
| 69 |
+
"plotly",
|
| 70 |
+
"tidyverse",
|
| 71 |
+
"readxl",
|
| 72 |
+
"vroom",
|
| 73 |
+
"rmarkdown",
|
| 74 |
+
"zip"
|
| 75 |
+
))
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
This only needs to be done once.
|
| 79 |
+
|
| 80 |
+
**Step 3: Run the Application**
|
| 81 |
+
|
| 82 |
+
In RStudio:
|
| 83 |
+
1. Open the project folder
|
| 84 |
+
2. Open `app.R`
|
| 85 |
+
3. Click the **Run App** button in the top-right of the editor
|
| 86 |
+
|
| 87 |
+
Or from the R console:
|
| 88 |
+
```r
|
| 89 |
+
setwd("/path/to/Polyphenol-Pipeline-Shinyapp")
|
| 90 |
+
shiny::runApp()
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
The app will open in your default web browser.
|
| 94 |
+
|
| 95 |
+
### Option 3: Docker Container
|
| 96 |
+
|
| 97 |
+
*Docker container image coming soon.*
|
| 98 |
+
|
| 99 |
+
A pre-built Docker image will be available for running the application in any environment without needing to install R or dependencies.
|
| 100 |
+
|
| 101 |
+
---
|
| 102 |
+
|
| 103 |
+
## How to Use the App
|
| 104 |
+
|
| 105 |
+
1. **Select Data Source** - Choose ASA24 or NHANES format
|
| 106 |
+
2. **Upload Data** - Upload your diet recall CSV/Excel file, or click "Load Demo Data" to try with sample data
|
| 107 |
+
3. **Run Pipeline** - Click "Run Pipeline" to process your data
|
| 108 |
+
4. **View Results** - Explore interactive charts and tables showing:
|
| 109 |
+
- Total polyphenol intake by subject
|
| 110 |
+
- Intake by polyphenol class
|
| 111 |
+
- Top food contributors
|
| 112 |
+
- DII scores (if enabled)
|
| 113 |
+
5. **Download Results** - Export individual tables or all results as a ZIP file
|
| 114 |
+
|
| 115 |
+
### Data Requirements
|
| 116 |
+
|
| 117 |
+
**ASA24 Format:**
|
| 118 |
+
- Required columns: `UserName`, `RecallNo`, `FoodCode`, `FoodAmt`
|
| 119 |
+
- Recommended: `Food_Description`, `KCAL`, and other nutrient columns for DII calculation
|
| 120 |
+
- Each participant must have at least 2 recalls
|
| 121 |
+
|
| 122 |
+
**NHANES Format:**
|
| 123 |
+
- Required columns: `SEQN`, `RecallNo`, `DRXIFDCD`
|
| 124 |
+
- Recommended: `DRXIGRMS`, `DRXIKCAL`, and other nutrient columns
|
| 125 |
+
- Each participant must have at least 2 recalls
|
| 126 |
+
|
| 127 |
+
---
|
| 128 |
+
|
| 129 |
+
## File Structure
|
| 130 |
+
|
| 131 |
+
```
|
| 132 |
+
shiny_app/
|
| 133 |
+
├── app.R # Application entry point
|
| 134 |
+
├── global.R # Package loading, database loading, helper functions
|
| 135 |
+
├── ui.R # User interface definition
|
| 136 |
+
├── server.R # Server logic and pipeline processing
|
| 137 |
+
├── Dockerfile # Docker configuration for deployment
|
| 138 |
+
├── README.md # This file
|
| 139 |
+
├── LICENSE # CC0 license for the Shiny app
|
| 140 |
+
├── CHANGELOG.md # Version history
|
| 141 |
+
│
|
| 142 |
+
├── data/ # Reference databases (from pipeline repository)
|
| 143 |
+
│ ├── FDA_FDD_All_Records_v_3.1.xlsx
|
| 144 |
+
│ ├── FDA_FooDB_Mapping_Nov_2025.csv
|
| 145 |
+
│ ├── FooDB_polyphenol_content_with_dbPUPsubstrates_Aug25.csv
|
| 146 |
+
│ ├── FooDB_polyphenol_list_3072.csv
|
| 147 |
+
│ ├── FooDB_Eugenol_Content_Final.csv
|
| 148 |
+
│ └── FooDB_DII_polyphenol_list.csv
|
| 149 |
+
│
|
| 150 |
+
├── demo_data/ # Sample data for testing
|
| 151 |
+
│ └── VVKAJ_Items.csv # Sample ASA24 Items file
|
| 152 |
+
│
|
| 153 |
+
├── www/ # Static assets
|
| 154 |
+
│ ├── custom.css # Application styling
|
| 155 |
+
│ ├── splash_animation.mp4
|
| 156 |
+
│ ├── PEPlogo_720x720.jpeg
|
| 157 |
+
│ └── Polyphenol_Estimation_Pipeline_Overview.png
|
| 158 |
+
│
|
| 159 |
+
├── assets/ # Additional assets
|
| 160 |
+
│ ├── splash_animation.mp4
|
| 161 |
+
│ └── PEPlogo_720x720.jpeg
|
| 162 |
+
│
|
| 163 |
+
└── docs/ # Documentation
|
| 164 |
+
├── shiny_app.md
|
| 165 |
+
├── shiny_user_guide.md
|
| 166 |
+
└── shiny_running_locally.md
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
### Files from the Pipeline Repository
|
| 170 |
+
|
| 171 |
+
The following files in the `data/` folder are copied from Stephanie Wilson's [polyphenol_pipeline repository](https://github.com/SWi1/polyphenol_pipeline) and are required for the application to function:
|
| 172 |
+
|
| 173 |
+
| File | Description | Source |
|
| 174 |
+
|------|-------------|--------|
|
| 175 |
+
| `FDA_FDD_All_Records_v_3.1.xlsx` | FDA Food Disaggregation Database v3.1 | `provided_files/` |
|
| 176 |
+
| `FDA_FooDB_Mapping_Nov_2025.csv` | Mapping between FDD ingredients and FooDB | `provided_files/` |
|
| 177 |
+
| `FooDB_polyphenol_content_with_dbPUPsubstrates_Aug25.csv` | Polyphenol content data from FooDB | `provided_files/` |
|
| 178 |
+
| `FooDB_polyphenol_list_3072.csv` | Polyphenol compound taxonomy | `provided_files/` |
|
| 179 |
+
| `FooDB_Eugenol_Content_Final.csv` | Eugenol content for DII calculation | `provided_files/` |
|
| 180 |
+
| `FooDB_DII_polyphenol_list.csv` | Polyphenol subclasses for DII | `provided_files/` |
|
| 181 |
+
|
| 182 |
+
The demo data file `VVKAJ_Items.csv` is also from the pipeline repository's `demo_data/` folder.
|
| 183 |
+
|
| 184 |
+
These files are distributed under the MIT license from the original pipeline repository.
|
| 185 |
+
|
| 186 |
+
---
|
| 187 |
+
|
| 188 |
+
## Credits
|
| 189 |
+
|
| 190 |
+
### Pipeline Development
|
| 191 |
+
|
| 192 |
+
**Stephanie M.G. Wilson**
|
| 193 |
+
University of California, Davis
|
| 194 |
+
smgwilson@ucdavis.edu
|
| 195 |
+
https://github.com/SWi1/polyphenol_pipeline
|
| 196 |
+
|
| 197 |
+
The polyphenol estimation methodology, database mappings, and DII calculations were developed by Dr. Wilson. This Shiny application is a wrapper that provides a graphical interface for her pipeline.
|
| 198 |
+
|
| 199 |
+
### Shiny Application Development
|
| 200 |
+
|
| 201 |
+
**Richard Stoker**
|
| 202 |
+
United States Department of Agriculture - Agricultural Research Service
|
| 203 |
+
Richard.Stoker@usda.gov
|
| 204 |
+
|
| 205 |
+
---
|
| 206 |
+
|
| 207 |
+
## License
|
| 208 |
+
|
| 209 |
+
**Shiny Application:** CC0 1.0 Universal (Public Domain Dedication)
|
| 210 |
+
|
| 211 |
+
This Shiny application code is released into the public domain under CC0. You can copy, modify, distribute, and use the work, even for commercial purposes, without asking permission.
|
| 212 |
+
|
| 213 |
+
**Pipeline and Data Files:** MIT License
|
| 214 |
+
|
| 215 |
+
The underlying Polyphenol Estimation Pipeline and reference database files are licensed under the MIT License by Stephanie M.G. Wilson. See the [pipeline repository](https://github.com/SWi1/polyphenol_pipeline) for details.
|
| 216 |
+
|
| 217 |
+
**Third-Party Packages:**
|
| 218 |
+
|
| 219 |
+
This application uses the following R packages, each with their own licenses:
|
| 220 |
+
- shiny (GPL-3)
|
| 221 |
+
- bslib (MIT)
|
| 222 |
+
- shinyWidgets (GPL-3)
|
| 223 |
+
- DT (GPL-3)
|
| 224 |
+
- plotly (MIT)
|
| 225 |
+
- tidyverse (MIT)
|
| 226 |
+
- readxl (MIT)
|
| 227 |
+
- vroom (MIT)
|
| 228 |
+
- rmarkdown (GPL-3)
|
| 229 |
+
- zip (MIT)
|
| 230 |
+
|
| 231 |
+
See the LICENSE file for full details.
|
| 232 |
+
|
| 233 |
+
---
|
| 234 |
+
|
| 235 |
+
## Citation
|
| 236 |
+
|
| 237 |
+
If you use this application in your research, please cite the pipeline repository:
|
| 238 |
+
|
| 239 |
+
> Wilson, S.M.G. Polyphenol Estimation Pipeline. https://github.com/SWi1/polyphenol_pipeline
|
| 240 |
+
|
| 241 |
+
For DII calculations, please also cite:
|
| 242 |
+
|
| 243 |
+
> Shivappa, N., Steck, S. E., Hurley, T. G., Hussey, J. R., & Hebert, J. R. (2014). Designing and developing a literature-derived, population-based dietary inflammatory index. *Public Health Nutrition*, 17(8), 1689-1696. https://doi.org/10.1017/s1368980013002115
|
| 244 |
+
|
| 245 |
+
---
|
| 246 |
+
|
| 247 |
+
## Version History
|
| 248 |
+
|
| 249 |
+
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
| 250 |
+
|
| 251 |
+
---
|
| 252 |
+
|
| 253 |
+
## Feedback and Issues
|
| 254 |
+
|
| 255 |
+
For issues with the Shiny application interface, contact Richard Stoker at Richard.Stoker@usda.gov.
|
| 256 |
+
|
| 257 |
+
For questions about the pipeline methodology or database mappings, contact Stephanie Wilson at smgwilson@ucdavis.edu or open an issue on the [pipeline repository](https://github.com/SWi1/polyphenol_pipeline/issues).
|
app.R
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ==============================================================================
|
| 2 |
+
# Polyphenol Estimation Pipeline - Shiny Application
|
| 3 |
+
# ==============================================================================
|
| 4 |
+
#
|
| 5 |
+
# PIPELINE DEVELOPED BY:
|
| 6 |
+
# Stephanie M.G. Wilson
|
| 7 |
+
# University of California, Davis
|
| 8 |
+
# Contact: smgwilson@ucdavis.edu
|
| 9 |
+
# Repository: https://github.com/SWi1/polyphenol_pipeline/
|
| 10 |
+
# License: MIT
|
| 11 |
+
#
|
| 12 |
+
# SHINY APP DEVELOPED BY:
|
| 13 |
+
# Richard Stoker
|
| 14 |
+
# United States Department of Agriculture - Agricultural Research Service
|
| 15 |
+
# Contact: Richard.Stoker@usda.gov
|
| 16 |
+
# License: CC0 (Public Domain)
|
| 17 |
+
#
|
| 18 |
+
# DESCRIPTION:
|
| 19 |
+
# Interactive web application for the Polyphenol Estimation Pipeline.
|
| 20 |
+
# Provides a user-friendly interface for estimating dietary polyphenol
|
| 21 |
+
# intake from ASA24 or NHANES diet recall data.
|
| 22 |
+
#
|
| 23 |
+
# VERSION: 0.1 Alpha
|
| 24 |
+
# DATE: November 2025
|
| 25 |
+
#
|
| 26 |
+
# ==============================================================================
|
| 27 |
+
|
| 28 |
+
# Load application components
|
| 29 |
+
source("global.R")
|
| 30 |
+
source("ui.R")
|
| 31 |
+
source("server.R")
|
| 32 |
+
|
| 33 |
+
# Run the application
|
| 34 |
+
shinyApp(ui = ui, server = server)
|
assets/PEPlogo_720x720.jpeg
ADDED
|
Git LFS Details
|
assets/splash_animation.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:250f4294a2e232df9e80a634bd9da079e9e042637197d5e69c6f9abd8e6cb5c8
|
| 3 |
+
size 256212
|
data/FDA_FDD_All_Records_v_3.1.xlsx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bd7a4718d280557474bc374d82647e08836882c2f78cc2d89926f5dab42800cf
|
| 3 |
+
size 2342856
|
data/FDA_FooDB_Mapping_Nov_2025.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3ce5a818c5ebb808be5ab373ed999dc36c6d8e14b5c99a3249e1026cf3aa979d
|
| 3 |
+
size 111455
|
data/FooDB_DII_polyphenol_list.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e2fb393ffa61f9adfb4074c9f6bde837648d273d7284b4b13ace376abb889cdd
|
| 3 |
+
size 52041
|
data/FooDB_Eugenol_Content_Final.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:acf87041651fd7846d541208c9db05825827f430cd1dbf4ccf980fa159742b2a
|
| 3 |
+
size 35356
|
data/FooDB_polyphenol_content_with_dbPUPsubstrates_Aug25.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c30a37776940e764394b8284f9c4df827c98af1d35261577a92e69fc370b3c69
|
| 3 |
+
size 7972025
|
data/FooDB_polyphenol_list_3072.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:aa731b35b5d05fb10faaf4875e121b509ad97dc6789e2ee305a8ee8bfcfbd98a
|
| 3 |
+
size 546936
|
demo_data/VVKAJ_Items.csv
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bacb6b17b01bb3bf419a4f9ce3d6474760b20aef07100c92064e9d9621e1c7a5
|
| 3 |
+
size 525037
|
docs/shiny_app.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
layout: default
|
| 3 |
+
title: Shiny App
|
| 4 |
+
nav_order: 100
|
| 5 |
+
has_children: true
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
# Polyphenol Estimation Pipeline - Shiny Application
|
| 9 |
+
|
| 10 |
+
A web-based interface for estimating dietary polyphenol intake from 24-hour diet recall data.
|
| 11 |
+
|
| 12 |
+
**Version:** 0.1 Alpha
|
| 13 |
+
|
| 14 |
+
## About
|
| 15 |
+
|
| 16 |
+
This Shiny application provides a graphical interface for the Polyphenol Estimation Pipeline developed by **Stephanie M.G. Wilson** at the University of California, Davis. The app allows researchers to run the pipeline without writing R code.
|
| 17 |
+
|
| 18 |
+
**Pipeline Repository:** [github.com/SWi1/polyphenol_pipeline](https://github.com/SWi1/polyphenol_pipeline)
|
| 19 |
+
|
| 20 |
+
## What the App Does
|
| 21 |
+
|
| 22 |
+
The Shiny app implements the same polyphenol estimation methodology as the original R scripts:
|
| 23 |
+
|
| 24 |
+
- Accepts ASA24 or NHANES diet recall data
|
| 25 |
+
- Disaggregates reported foods into ingredients using FDA FDD v3.1
|
| 26 |
+
- Maps ingredients to polyphenol content from FooDB
|
| 27 |
+
- Calculates intake at total and class levels
|
| 28 |
+
- Identifies top food contributors to polyphenol intake
|
| 29 |
+
- Computes a 42-component Dietary Inflammatory Index (DII)
|
| 30 |
+
- Generates QA/QC reports for unmapped foods
|
| 31 |
+
- Exports results as CSV files
|
| 32 |
+
|
| 33 |
+
## App Structure
|
| 34 |
+
|
| 35 |
+
The application has five main tabs:
|
| 36 |
+
|
| 37 |
+
| Tab | Purpose |
|
| 38 |
+
|-----|---------|
|
| 39 |
+
| **Get Started** | Overview of the pipeline and usage instructions |
|
| 40 |
+
| **Input** | Upload data, select format, configure options, run pipeline |
|
| 41 |
+
| **Results** | Interactive charts and tables with Chart/Table toggle |
|
| 42 |
+
| **QA/QC** | Unmapped foods report and data quality metrics |
|
| 43 |
+
| **About** | Pipeline methodology, credits, and citation information |
|
| 44 |
+
|
| 45 |
+
## How to Access
|
| 46 |
+
|
| 47 |
+
### Option 1: Hosted Version (No Installation)
|
| 48 |
+
|
| 49 |
+
A hosted version is available on Hugging Face Spaces:
|
| 50 |
+
|
| 51 |
+
**[Launch App](https://huggingface.co/spaces/richtext/Polyphenol-Pipeline-Shinyapp)**
|
| 52 |
+
|
| 53 |
+
### Option 2: Run Locally
|
| 54 |
+
|
| 55 |
+
Run the app on your own computer using R and RStudio. See [Running Locally](shiny_running_locally.html) for setup instructions.
|
| 56 |
+
|
| 57 |
+
### Option 3: Docker Container
|
| 58 |
+
|
| 59 |
+
*Coming soon.* A Docker container image will be available for running the application in any environment.
|
| 60 |
+
|
| 61 |
+
## Credits
|
| 62 |
+
|
| 63 |
+
**Pipeline Development**
|
| 64 |
+
Stephanie M.G. Wilson
|
| 65 |
+
University of California, Davis
|
| 66 |
+
smgwilson@ucdavis.edu
|
| 67 |
+
|
| 68 |
+
**Shiny Application Development**
|
| 69 |
+
Richard Stoker
|
| 70 |
+
United States Department of Agriculture - Agricultural Research Service
|
| 71 |
+
Richard.Stoker@usda.gov
|
| 72 |
+
|
| 73 |
+
## License
|
| 74 |
+
|
| 75 |
+
- **Shiny Application:** CC0 1.0 (Public Domain)
|
| 76 |
+
- **Pipeline and Data Files:** MIT License (Stephanie M.G. Wilson)
|
| 77 |
+
|
| 78 |
+
See the LICENSE file in the repository for full details.
|
docs/shiny_running_locally.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
layout: default
|
| 3 |
+
title: Running Locally
|
| 4 |
+
parent: Shiny App
|
| 5 |
+
nav_order: 2
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
# Running the Shiny App Locally
|
| 9 |
+
|
| 10 |
+
Instructions for running the Polyphenol Estimation Pipeline Shiny application on your own computer.
|
| 11 |
+
|
| 12 |
+
## Requirements
|
| 13 |
+
|
| 14 |
+
- **R** version 4.1 or later
|
| 15 |
+
- **RStudio** (recommended, but not required)
|
| 16 |
+
|
| 17 |
+
### Installing R
|
| 18 |
+
|
| 19 |
+
Download and install R from CRAN:
|
| 20 |
+
- **Windows/Mac:** [https://cran.r-project.org/](https://cran.r-project.org/)
|
| 21 |
+
- **Linux:** Install via your package manager or CRAN
|
| 22 |
+
|
| 23 |
+
### Installing RStudio (Optional)
|
| 24 |
+
|
| 25 |
+
Download RStudio Desktop (free version):
|
| 26 |
+
- [https://posit.co/download/rstudio-desktop/](https://posit.co/download/rstudio-desktop/)
|
| 27 |
+
|
| 28 |
+
RStudio provides a better development experience but is not required to run the app.
|
| 29 |
+
|
| 30 |
+
## Setup Instructions
|
| 31 |
+
|
| 32 |
+
### Step 1: Get the Application Files
|
| 33 |
+
|
| 34 |
+
**Option A: Clone from GitHub**
|
| 35 |
+
```bash
|
| 36 |
+
git clone https://github.com/RichardStoker-USDA/Polyphenol-Pipeline-Shinyapp.git
|
| 37 |
+
cd Polyphenol-Pipeline-Shinyapp
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
**Option B: Download ZIP**
|
| 41 |
+
|
| 42 |
+
Download and extract the repository ZIP file.
|
| 43 |
+
|
| 44 |
+
### Step 2: Install Required R Packages
|
| 45 |
+
|
| 46 |
+
Open R or RStudio and run this command once:
|
| 47 |
+
|
| 48 |
+
```r
|
| 49 |
+
install.packages(c(
|
| 50 |
+
"shiny",
|
| 51 |
+
"bslib",
|
| 52 |
+
"shinyWidgets",
|
| 53 |
+
"DT",
|
| 54 |
+
"plotly",
|
| 55 |
+
"tidyverse",
|
| 56 |
+
"readxl",
|
| 57 |
+
"vroom",
|
| 58 |
+
"rmarkdown",
|
| 59 |
+
"zip"
|
| 60 |
+
))
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
This may take a few minutes. You only need to do this once.
|
| 64 |
+
|
| 65 |
+
### Step 3: Run the Application
|
| 66 |
+
|
| 67 |
+
**Using RStudio:**
|
| 68 |
+
|
| 69 |
+
1. Open RStudio
|
| 70 |
+
2. File > Open File > navigate to the application folder
|
| 71 |
+
3. Open `app.R`
|
| 72 |
+
4. Click the **Run App** button in the top-right of the editor
|
| 73 |
+
|
| 74 |
+
**Using R Console:**
|
| 75 |
+
|
| 76 |
+
```r
|
| 77 |
+
# Set working directory to the application folder
|
| 78 |
+
setwd("/path/to/Polyphenol-Pipeline-Shinyapp")
|
| 79 |
+
|
| 80 |
+
# Run the application
|
| 81 |
+
shiny::runApp()
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
The app will open in your default web browser.
|
| 85 |
+
|
| 86 |
+
### Step 4: Verify It Works
|
| 87 |
+
|
| 88 |
+
1. Click **Load Demo Data** on the Input tab
|
| 89 |
+
2. Click **Run Pipeline**
|
| 90 |
+
3. Confirm that charts appear on the Results tab
|
| 91 |
+
|
| 92 |
+
## Running Options
|
| 93 |
+
|
| 94 |
+
### Specify a Port
|
| 95 |
+
|
| 96 |
+
If the default port is in use:
|
| 97 |
+
|
| 98 |
+
```r
|
| 99 |
+
shiny::runApp(port = 4000)
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
### Run in External Browser
|
| 103 |
+
|
| 104 |
+
Force the app to open in your browser instead of RStudio Viewer:
|
| 105 |
+
|
| 106 |
+
```r
|
| 107 |
+
shiny::runApp(launch.browser = TRUE)
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
### Run on Local Network
|
| 111 |
+
|
| 112 |
+
Allow other computers on your network to access the app:
|
| 113 |
+
|
| 114 |
+
```r
|
| 115 |
+
shiny::runApp(host = "0.0.0.0", port = 3838)
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
Then access via `http://YOUR_IP_ADDRESS:3838` from other machines.
|
| 119 |
+
|
| 120 |
+
## Troubleshooting
|
| 121 |
+
|
| 122 |
+
### Package Installation Errors
|
| 123 |
+
|
| 124 |
+
If a package fails to install:
|
| 125 |
+
|
| 126 |
+
1. Check your R version: `R.version.string`
|
| 127 |
+
2. Try installing packages one at a time to identify the problem
|
| 128 |
+
3. On Linux, you may need system libraries:
|
| 129 |
+
```bash
|
| 130 |
+
# Ubuntu/Debian
|
| 131 |
+
sudo apt-get install libcurl4-openssl-dev libssl-dev libxml2-dev
|
| 132 |
+
```
|
| 133 |
+
|
| 134 |
+
### "File Not Found" Errors
|
| 135 |
+
|
| 136 |
+
Verify your working directory is correct:
|
| 137 |
+
|
| 138 |
+
```r
|
| 139 |
+
getwd() # Should show the path to the application folder
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
If not, set it:
|
| 143 |
+
|
| 144 |
+
```r
|
| 145 |
+
setwd("/correct/path/to/application")
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
### "Port Already in Use"
|
| 149 |
+
|
| 150 |
+
Another application is using the default port. Specify a different one:
|
| 151 |
+
|
| 152 |
+
```r
|
| 153 |
+
shiny::runApp(port = 3839)
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
### Charts Not Rendering
|
| 157 |
+
|
| 158 |
+
Ensure Plotly is installed correctly:
|
| 159 |
+
|
| 160 |
+
```r
|
| 161 |
+
install.packages("plotly")
|
| 162 |
+
library(plotly)
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
### Slow Performance
|
| 166 |
+
|
| 167 |
+
Large datasets take longer to process. The progress indicator shows which step is running. Processing typically completes within a few minutes for most datasets.
|
| 168 |
+
|
| 169 |
+
## File Structure
|
| 170 |
+
|
| 171 |
+
The application folder is self-contained:
|
| 172 |
+
|
| 173 |
+
```
|
| 174 |
+
shiny_app/
|
| 175 |
+
├── app.R # Application entry point
|
| 176 |
+
├── global.R # Package loading, database loading, helper functions
|
| 177 |
+
├── ui.R # User interface definition
|
| 178 |
+
├── server.R # Server logic and pipeline processing
|
| 179 |
+
├── Dockerfile # Docker configuration (for deployment)
|
| 180 |
+
├── README.md # Documentation
|
| 181 |
+
├── LICENSE # License information
|
| 182 |
+
├── CHANGELOG.md # Version history
|
| 183 |
+
│
|
| 184 |
+
├── data/ # Reference databases (required)
|
| 185 |
+
│ ├── FDA_FDD_All_Records_v_3.1.xlsx
|
| 186 |
+
│ ├── FDA_FooDB_Mapping_Nov_2025.csv
|
| 187 |
+
│ ├── FooDB_polyphenol_content_with_dbPUPsubstrates_Aug25.csv
|
| 188 |
+
│ ├── FooDB_polyphenol_list_3072.csv
|
| 189 |
+
│ ├── FooDB_Eugenol_Content_Final.csv
|
| 190 |
+
│ └── FooDB_DII_polyphenol_list.csv
|
| 191 |
+
│
|
| 192 |
+
├── demo_data/ # Sample data for testing
|
| 193 |
+
│ └── VVKAJ_Items.csv
|
| 194 |
+
│
|
| 195 |
+
├── www/ # Static assets
|
| 196 |
+
│ ├── custom.css
|
| 197 |
+
│ ├── splash_animation.mp4
|
| 198 |
+
│ ├── PEPlogo_720x720.jpeg
|
| 199 |
+
│ └── Polyphenol_Estimation_Pipeline_Overview.png
|
| 200 |
+
│
|
| 201 |
+
└── docs/ # Documentation files
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
All reference databases are included in the `data/` folder. No additional setup is required beyond installing R packages.
|
| 205 |
+
|
| 206 |
+
## Docker Alternative
|
| 207 |
+
|
| 208 |
+
*Coming soon.* A Docker container will be available for running the application without installing R or managing dependencies.
|
docs/shiny_user_guide.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
layout: default
|
| 3 |
+
title: User Guide
|
| 4 |
+
parent: Shiny App
|
| 5 |
+
nav_order: 1
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
# Shiny App User Guide
|
| 9 |
+
|
| 10 |
+
Step-by-step instructions for using the Polyphenol Estimation Pipeline Shiny application.
|
| 11 |
+
|
| 12 |
+
## Quick Start
|
| 13 |
+
|
| 14 |
+
1. Open the app (hosted version or run locally)
|
| 15 |
+
2. Go to the **Input** tab
|
| 16 |
+
3. Click **Load Demo Data** to try with sample data, or upload your own file
|
| 17 |
+
4. Click **Run Pipeline**
|
| 18 |
+
5. View results in the **Results** tab
|
| 19 |
+
6. Use Chart/Table buttons to switch between visualizations and data tables
|
| 20 |
+
7. Download results as CSV files
|
| 21 |
+
|
| 22 |
+
## Data Requirements
|
| 23 |
+
|
| 24 |
+
### ASA24 Data
|
| 25 |
+
|
| 26 |
+
Use the **Items Analysis File** from the ASA24 Researcher Site.
|
| 27 |
+
|
| 28 |
+
**Required columns:**
|
| 29 |
+
- `UserName` - Participant identifier
|
| 30 |
+
- `FoodCode` - WWEIA food code
|
| 31 |
+
- `RecallNo` - Recall number (1, 2, etc.)
|
| 32 |
+
- `FoodAmt` - Amount consumed in grams
|
| 33 |
+
|
| 34 |
+
**Recommended columns for DII calculation:**
|
| 35 |
+
- `KCAL`, `PROT`, `TFAT`, `CARB`, `FIBE` and other nutrient columns
|
| 36 |
+
|
| 37 |
+
**Important:** Each participant must have at least 2 completed recalls.
|
| 38 |
+
|
| 39 |
+
### NHANES Data
|
| 40 |
+
|
| 41 |
+
Use the **Individual Foods** file from NHANES dietary data.
|
| 42 |
+
|
| 43 |
+
**Required columns:**
|
| 44 |
+
- `SEQN` - Participant sequence number
|
| 45 |
+
- `DRXIFDCD` - Food code
|
| 46 |
+
- `RecallNo` - Recall day
|
| 47 |
+
|
| 48 |
+
**Recommended columns:**
|
| 49 |
+
- `DRXIGRMS` - Amount consumed in grams
|
| 50 |
+
- `DRXIKCAL`, `DRXIPROT`, `DRXITFAT` and other nutrient columns
|
| 51 |
+
|
| 52 |
+
## Using the Application
|
| 53 |
+
|
| 54 |
+
### Step 1: Get Started Tab
|
| 55 |
+
|
| 56 |
+
The Get Started tab provides:
|
| 57 |
+
- Overview of what the pipeline does
|
| 58 |
+
- Three-step guide to using the app
|
| 59 |
+
- Pipeline workflow diagram
|
| 60 |
+
|
| 61 |
+
Click **Go to Input** to proceed.
|
| 62 |
+
|
| 63 |
+
### Step 2: Input Tab
|
| 64 |
+
|
| 65 |
+
**Select Data Source**
|
| 66 |
+
|
| 67 |
+
Choose your data format:
|
| 68 |
+
- **ASA24** for ASA24 Items files
|
| 69 |
+
- **NHANES** for NHANES Individual Foods files
|
| 70 |
+
|
| 71 |
+
**Upload Data**
|
| 72 |
+
|
| 73 |
+
Click the upload area or drag and drop your file. Accepted formats:
|
| 74 |
+
- CSV (.csv)
|
| 75 |
+
- Excel (.xlsx, .xls)
|
| 76 |
+
|
| 77 |
+
Click "Data format help" for column requirements.
|
| 78 |
+
|
| 79 |
+
The app validates your data and shows a preview on the right.
|
| 80 |
+
|
| 81 |
+
**Or Load Demo Data**
|
| 82 |
+
|
| 83 |
+
Click **Load Demo Data** to test with included sample ASA24 data.
|
| 84 |
+
|
| 85 |
+
**Configure Options**
|
| 86 |
+
|
| 87 |
+
- **Include DII Calculation** - Check to compute the 42-component Dietary Inflammatory Index
|
| 88 |
+
|
| 89 |
+
### Step 3: Run Pipeline
|
| 90 |
+
|
| 91 |
+
Click **Run Pipeline** to process your data.
|
| 92 |
+
|
| 93 |
+
A progress indicator shows each step:
|
| 94 |
+
1. Preparing dietary data
|
| 95 |
+
2. Calculating nutrient totals
|
| 96 |
+
3. Disaggregating foods (FDA FDD)
|
| 97 |
+
4. Mapping to FooDB
|
| 98 |
+
5. Calculating polyphenol content
|
| 99 |
+
6. Summarizing total intake
|
| 100 |
+
7. Calculating class-level intake
|
| 101 |
+
8. Identifying food contributors
|
| 102 |
+
9. Calculating DII scores (if enabled)
|
| 103 |
+
10. Generating QA/QC report
|
| 104 |
+
|
| 105 |
+
The app navigates to Results when complete.
|
| 106 |
+
|
| 107 |
+
### Step 4: View Results
|
| 108 |
+
|
| 109 |
+
**Summary Cards**
|
| 110 |
+
|
| 111 |
+
Four cards at the top show:
|
| 112 |
+
- Subjects Analyzed
|
| 113 |
+
- Mean Polyphenol Intake (mg/day)
|
| 114 |
+
- Polyphenol Classes
|
| 115 |
+
- Unmapped Foods
|
| 116 |
+
|
| 117 |
+
**Results Tabs**
|
| 118 |
+
|
| 119 |
+
Each tab has **Chart** and **Table** buttons in the header. Click to switch views.
|
| 120 |
+
|
| 121 |
+
**Total Intake**
|
| 122 |
+
- Bar chart: Mean intake by subject (top 30)
|
| 123 |
+
- Histogram: Distribution of intake across subjects
|
| 124 |
+
- Table: Subject-level values
|
| 125 |
+
|
| 126 |
+
**By Polyphenol Class**
|
| 127 |
+
- Bar chart: Mean intake by polyphenol class
|
| 128 |
+
- Table: Class-level breakdown per subject
|
| 129 |
+
|
| 130 |
+
**Food Contributors**
|
| 131 |
+
- Treemap: Top 50 foods by polyphenol contribution
|
| 132 |
+
- Table: Food contribution details
|
| 133 |
+
|
| 134 |
+
**DII Scores** (if enabled)
|
| 135 |
+
- Histogram: Distribution of DII scores
|
| 136 |
+
- Bar chart: Per-subject scores (green = anti-inflammatory, red = pro-inflammatory)
|
| 137 |
+
- Table: DII scores with all/no-alcohol variants
|
| 138 |
+
|
| 139 |
+
### Step 5: QA/QC Tab
|
| 140 |
+
|
| 141 |
+
The QA/QC tab reports foods that could not be mapped to FooDB.
|
| 142 |
+
|
| 143 |
+
These items do not contribute to polyphenol estimates. Review the list to assess data quality.
|
| 144 |
+
|
| 145 |
+
Use Chart/Table buttons to switch between:
|
| 146 |
+
- Chart: Distribution of unmapped food percentage by recall
|
| 147 |
+
- Table: List of unmapped food items
|
| 148 |
+
|
| 149 |
+
### Step 6: Download Results
|
| 150 |
+
|
| 151 |
+
**Individual Downloads**
|
| 152 |
+
|
| 153 |
+
Each results tab has a download button (download icon) to export that specific table.
|
| 154 |
+
|
| 155 |
+
**Download All**
|
| 156 |
+
|
| 157 |
+
Click **Download All Results** at the bottom of the Results tab to get a ZIP file containing:
|
| 158 |
+
|
| 159 |
+
| File | Contents |
|
| 160 |
+
|------|----------|
|
| 161 |
+
| `total_intake_by_subject.csv` | Mean polyphenol intake per subject |
|
| 162 |
+
| `total_intake_by_recall.csv` | Polyphenol intake per recall |
|
| 163 |
+
| `class_intake_by_subject.csv` | Class-level intake per subject |
|
| 164 |
+
| `class_intake_by_recall.csv` | Class-level intake per recall |
|
| 165 |
+
| `food_contributors.csv` | Food contribution rankings |
|
| 166 |
+
| `dii_scores_by_subject.csv` | DII scores per subject (if enabled) |
|
| 167 |
+
| `dii_scores_by_recall.csv` | DII scores per recall (if enabled) |
|
| 168 |
+
| `unmapped_foods.csv` | Foods not mapped to FooDB (if any) |
|
| 169 |
+
|
| 170 |
+
## Tips
|
| 171 |
+
|
| 172 |
+
- **Test first:** Use "Load Demo Data" to explore the app before uploading your data
|
| 173 |
+
- **Check QA/QC:** Review unmapped foods to understand data coverage
|
| 174 |
+
- **Large files:** Allow extra processing time for datasets with many subjects
|
| 175 |
+
- **Reset:** Click "Reset" to clear all data and start over
|
| 176 |
+
- **Full screen:** Click the expand icon on any card for full-screen view
|
| 177 |
+
|
| 178 |
+
## About Page
|
| 179 |
+
|
| 180 |
+
The About tab contains:
|
| 181 |
+
- Pipeline methodology explanation
|
| 182 |
+
- Three-step process description
|
| 183 |
+
- Information about the 42-component DII calculation
|
| 184 |
+
- Credits for pipeline and application development
|
| 185 |
+
- Citation information for DII methodology
|
global.R
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ==============================================================================
|
| 2 |
+
# Polyphenol Estimation Pipeline - Shiny Application
|
| 3 |
+
# Global Configuration and Database Loading
|
| 4 |
+
# ==============================================================================
|
| 5 |
+
#
|
| 6 |
+
# PIPELINE DEVELOPED BY:
|
| 7 |
+
# Stephanie M.G. Wilson
|
| 8 |
+
# University of California, Davis
|
| 9 |
+
# Contact: smgwilson@ucdavis.edu
|
| 10 |
+
# Repository: https://github.com/SWi1/polyphenol_pipeline/
|
| 11 |
+
# License: MIT
|
| 12 |
+
#
|
| 13 |
+
# SHINY APP DEVELOPED BY:
|
| 14 |
+
# Richard Stoker
|
| 15 |
+
# United States Department of Agriculture - Agricultural Research Service
|
| 16 |
+
# Contact: Richard.Stoker@usda.gov
|
| 17 |
+
# License: CC0 (Public Domain)
|
| 18 |
+
#
|
| 19 |
+
# DESCRIPTION:
|
| 20 |
+
# This file loads required packages, reference databases, and defines
|
| 21 |
+
# helper functions used throughout the Shiny application.
|
| 22 |
+
#
|
| 23 |
+
# VERSION: 0.1 Alpha
|
| 24 |
+
# DATE: November 2025
|
| 25 |
+
#
|
| 26 |
+
# ==============================================================================
|
| 27 |
+
|
| 28 |
+
# Load required packages
|
| 29 |
+
suppressPackageStartupMessages({
|
| 30 |
+
library(shiny)
|
| 31 |
+
library(bslib)
|
| 32 |
+
library(shinyWidgets)
|
| 33 |
+
library(DT)
|
| 34 |
+
library(plotly)
|
| 35 |
+
library(tidyverse)
|
| 36 |
+
library(readxl)
|
| 37 |
+
library(vroom)
|
| 38 |
+
library(rmarkdown)
|
| 39 |
+
library(zip)
|
| 40 |
+
})
|
| 41 |
+
|
| 42 |
+
# ------------------------------------------------------------------------------
|
| 43 |
+
# Application Constants
|
| 44 |
+
# ------------------------------------------------------------------------------
|
| 45 |
+
APP_TITLE <- "Polyphenol Estimation Pipeline"
|
| 46 |
+
APP_VERSION <- "0.1 Alpha"
|
| 47 |
+
PRIMARY_COLOR <- "#6f42c1"
|
| 48 |
+
SECONDARY_COLOR <- "#5c2d91"
|
| 49 |
+
|
| 50 |
+
# ------------------------------------------------------------------------------
|
| 51 |
+
# File Paths - Self-contained within shiny_app directory
|
| 52 |
+
# ------------------------------------------------------------------------------
|
| 53 |
+
DATA_DIR <- "data"
|
| 54 |
+
DEMO_DIR <- "demo_data"
|
| 55 |
+
|
| 56 |
+
# Reference database paths
|
| 57 |
+
FDD_FILE <- file.path(DATA_DIR, "FDA_FDD_All_Records_v_3.1.xlsx")
|
| 58 |
+
FOODB_CONTENT_FILE <- file.path(DATA_DIR, "FooDB_polyphenol_content_with_dbPUPsubstrates_Aug25.csv")
|
| 59 |
+
CLASS_TAX_FILE <- file.path(DATA_DIR, "FooDB_polyphenol_list_3072.csv")
|
| 60 |
+
MAPPING_FILE <- file.path(DATA_DIR, "FDA_FooDB_Mapping_Nov_2025.csv")
|
| 61 |
+
EUGENOL_FILE <- file.path(DATA_DIR, "FooDB_Eugenol_Content_Final.csv")
|
| 62 |
+
DII_SUBCLASSES_FILE <- file.path(DATA_DIR, "FooDB_DII_polyphenol_list.csv")
|
| 63 |
+
|
| 64 |
+
# Demo data
|
| 65 |
+
DEMO_DATA_FILE <- file.path(DEMO_DIR, "VVKAJ_Items.csv")
|
| 66 |
+
|
| 67 |
+
# ------------------------------------------------------------------------------
|
| 68 |
+
# Load Reference Databases
|
| 69 |
+
# These files are loaded once at startup for performance
|
| 70 |
+
# ------------------------------------------------------------------------------
|
| 71 |
+
message("Loading reference databases...")
|
| 72 |
+
|
| 73 |
+
# FDA Food Disaggregation Database V3.1
|
| 74 |
+
if (file.exists(FDD_FILE)) {
|
| 75 |
+
FDD_V3 <- read_xlsx(FDD_FILE) %>%
|
| 76 |
+
rename(
|
| 77 |
+
latest_survey = "Latest Survey",
|
| 78 |
+
wweia_food_code = "WWEIA Food Code",
|
| 79 |
+
wweia_food_description = "WWEIA Food Description",
|
| 80 |
+
fdd_ingredient = "Basic Ingredient Description",
|
| 81 |
+
ingredient_percent = "Ingredient Percent"
|
| 82 |
+
) %>%
|
| 83 |
+
select(wweia_food_code, wweia_food_description, fdd_ingredient, ingredient_percent) %>%
|
| 84 |
+
mutate(wweia_food_code = as.integer(wweia_food_code))
|
| 85 |
+
message(" - FDA FDD database loaded")
|
| 86 |
+
} else {
|
| 87 |
+
FDD_V3 <- NULL
|
| 88 |
+
warning("FDA FDD database not found at: ", FDD_FILE)
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
# FooDB polyphenol content
|
| 92 |
+
if (file.exists(FOODB_CONTENT_FILE)) {
|
| 93 |
+
FooDB_mg_100g <- vroom(FOODB_CONTENT_FILE, show_col_types = FALSE) %>%
|
| 94 |
+
distinct(food_id, compound_public_id, .keep_all = TRUE) %>%
|
| 95 |
+
select(-c(food_public_id, food_name)) %>%
|
| 96 |
+
relocate(orig_content_avg, .before = citation) %>%
|
| 97 |
+
filter(!is.na(orig_content_avg_RFadj))
|
| 98 |
+
message(" - FooDB polyphenol content loaded")
|
| 99 |
+
} else {
|
| 100 |
+
FooDB_mg_100g <- NULL
|
| 101 |
+
warning("FooDB polyphenol content file not found")
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
# FooDB polyphenol class taxonomy
|
| 105 |
+
if (file.exists(CLASS_TAX_FILE)) {
|
| 106 |
+
class_tax <- vroom(CLASS_TAX_FILE, show_col_types = FALSE) %>%
|
| 107 |
+
select(c(compound_public_id, class))
|
| 108 |
+
message(" - FooDB class taxonomy loaded")
|
| 109 |
+
} else {
|
| 110 |
+
class_tax <- NULL
|
| 111 |
+
warning("FooDB class taxonomy file not found")
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
# FDA-FooDB Mapping
|
| 115 |
+
if (file.exists(MAPPING_FILE)) {
|
| 116 |
+
fdd_foodb_mapping <- vroom(MAPPING_FILE, show_col_types = FALSE) %>%
|
| 117 |
+
select(-c(method, score))
|
| 118 |
+
message(" - FDA-FooDB mapping loaded")
|
| 119 |
+
} else {
|
| 120 |
+
fdd_foodb_mapping <- NULL
|
| 121 |
+
warning("FDA-FooDB mapping file not found")
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
# FooDB Eugenol Content (for DII calculation)
|
| 125 |
+
if (file.exists(EUGENOL_FILE)) {
|
| 126 |
+
FooDB_eugenol <- vroom(EUGENOL_FILE, show_col_types = FALSE)
|
| 127 |
+
message(" - FooDB eugenol content loaded")
|
| 128 |
+
} else {
|
| 129 |
+
FooDB_eugenol <- NULL
|
| 130 |
+
warning("FooDB eugenol content file not found")
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
# FooDB DII polyphenol subclasses
|
| 134 |
+
if (file.exists(DII_SUBCLASSES_FILE)) {
|
| 135 |
+
FooDB_DII_subclasses <- vroom(DII_SUBCLASSES_FILE, show_col_types = FALSE)
|
| 136 |
+
message(" - FooDB DII subclasses loaded")
|
| 137 |
+
} else {
|
| 138 |
+
FooDB_DII_subclasses <- NULL
|
| 139 |
+
warning("FooDB DII subclasses file not found")
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
message("Database loading complete.")
|
| 143 |
+
|
| 144 |
+
# ------------------------------------------------------------------------------
|
| 145 |
+
# Helper Functions
|
| 146 |
+
# ------------------------------------------------------------------------------
|
| 147 |
+
|
| 148 |
+
# Check if all required databases are loaded
|
| 149 |
+
databases_ready <- function() {
|
| 150 |
+
all(!is.null(FDD_V3),
|
| 151 |
+
!is.null(FooDB_mg_100g),
|
| 152 |
+
!is.null(class_tax),
|
| 153 |
+
!is.null(fdd_foodb_mapping))
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
# Validate ASA24 dietary input data
|
| 157 |
+
validate_asa24_data <- function(data) {
|
| 158 |
+
required_cols <- c("UserName", "FoodCode", "RecallNo", "FoodAmt")
|
| 159 |
+
missing <- setdiff(required_cols, names(data))
|
| 160 |
+
if (length(missing) > 0) {
|
| 161 |
+
return(list(valid = FALSE, message = paste("Missing required columns:", paste(missing, collapse = ", "))))
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
recalls_per_user <- data %>%
|
| 165 |
+
group_by(UserName) %>%
|
| 166 |
+
summarise(n_recalls = n_distinct(RecallNo), .groups = "drop")
|
| 167 |
+
|
| 168 |
+
if (max(recalls_per_user$n_recalls, na.rm = TRUE) < 2) {
|
| 169 |
+
return(list(valid = FALSE, message = "Data must contain multiple recalls per participant."))
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
return(list(valid = TRUE, message = "Data validation passed."))
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
# Validate NHANES dietary input data
|
| 176 |
+
validate_nhanes_data <- function(data) {
|
| 177 |
+
required_cols <- c("SEQN", "DRXIFDCD", "RecallNo")
|
| 178 |
+
missing <- setdiff(required_cols, names(data))
|
| 179 |
+
if (length(missing) > 0) {
|
| 180 |
+
return(list(valid = FALSE, message = paste("Missing required columns:", paste(missing, collapse = ", "))))
|
| 181 |
+
}
|
| 182 |
+
return(list(valid = TRUE, message = "Data validation passed."))
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
# Apply coffee/tea brewing adjustment
|
| 186 |
+
# This adjustment accounts for the brewing process where polyphenols from
|
| 187 |
+
# coffee or tea are extracted into water
|
| 188 |
+
apply_brewing_adjustment <- function(fdd_data) {
|
| 189 |
+
fdd_data %>%
|
| 190 |
+
group_by(wweia_food_code) %>%
|
| 191 |
+
mutate(
|
| 192 |
+
has_tea = any(str_detect(fdd_ingredient, regex("Tea", ignore_case = TRUE))),
|
| 193 |
+
has_coffee = any(str_detect(fdd_ingredient, regex("Coffee", ignore_case = TRUE))),
|
| 194 |
+
has_water = any(str_detect(fdd_ingredient, regex("Water", ignore_case = TRUE))),
|
| 195 |
+
brewing_adjustment_total = case_when(
|
| 196 |
+
has_tea & has_water ~ sum(
|
| 197 |
+
ingredient_percent[str_detect(fdd_ingredient, regex("Tea|Water", ignore_case = TRUE))],
|
| 198 |
+
na.rm = TRUE),
|
| 199 |
+
has_coffee & has_water ~ sum(
|
| 200 |
+
ingredient_percent[str_detect(fdd_ingredient, regex("Coffee|Water", ignore_case = TRUE))],
|
| 201 |
+
na.rm = TRUE),
|
| 202 |
+
TRUE ~ NA_real_),
|
| 203 |
+
brewing_adjustment_percentage = if_else(
|
| 204 |
+
str_detect(fdd_ingredient, regex("Coffee|Tea", ignore_case = TRUE)),
|
| 205 |
+
brewing_adjustment_total,
|
| 206 |
+
NA_real_)
|
| 207 |
+
) %>%
|
| 208 |
+
select(-c(has_tea, has_coffee, has_water, brewing_adjustment_total)) %>%
|
| 209 |
+
ungroup()
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
# Color palette for visualizations
|
| 213 |
+
get_viz_colors <- function(n) {
|
| 214 |
+
if (n <= 8) {
|
| 215 |
+
return(c("#6f42c1", "#28a745", "#fd7e14", "#17a2b8", "#dc3545",
|
| 216 |
+
"#ffc107", "#6610f2", "#e83e8c")[1:n])
|
| 217 |
+
}
|
| 218 |
+
colorRampPalette(c("#6f42c1", "#28a745", "#fd7e14", "#17a2b8"))(n)
|
| 219 |
+
}
|
server.R
ADDED
|
@@ -0,0 +1,1184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ==============================================================================
|
| 2 |
+
# Polyphenol Estimation Pipeline - Shiny Application Server
|
| 3 |
+
# ==============================================================================
|
| 4 |
+
#
|
| 5 |
+
# PIPELINE DEVELOPED BY:
|
| 6 |
+
# Stephanie M.G. Wilson
|
| 7 |
+
# University of California, Davis
|
| 8 |
+
# Contact: smgwilson@ucdavis.edu
|
| 9 |
+
# Repository: https://github.com/SWi1/polyphenol_pipeline/
|
| 10 |
+
# License: MIT
|
| 11 |
+
#
|
| 12 |
+
# SHINY APP DEVELOPED BY:
|
| 13 |
+
# Richard Stoker
|
| 14 |
+
# United States Department of Agriculture - Agricultural Research Service
|
| 15 |
+
# Contact: Richard.Stoker@usda.gov
|
| 16 |
+
# License: CC0 (Public Domain)
|
| 17 |
+
#
|
| 18 |
+
# VERSION: 0.1 Alpha
|
| 19 |
+
# DATE: November 2025
|
| 20 |
+
# ==============================================================================
|
| 21 |
+
|
| 22 |
+
server <- function(input, output, session) {
|
| 23 |
+
|
| 24 |
+
# --------------------------------------------------------------------------
|
| 25 |
+
# Reactive Values
|
| 26 |
+
# --------------------------------------------------------------------------
|
| 27 |
+
rv <- reactiveValues(
|
| 28 |
+
raw_data = NULL,
|
| 29 |
+
data_source = NULL,
|
| 30 |
+
data_validated = FALSE,
|
| 31 |
+
analysis_complete = FALSE,
|
| 32 |
+
results = list(),
|
| 33 |
+
unmapped_foods = NULL,
|
| 34 |
+
processing = FALSE
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
# --------------------------------------------------------------------------
|
| 38 |
+
# Splash Screen Modal
|
| 39 |
+
# --------------------------------------------------------------------------
|
| 40 |
+
observeEvent(TRUE, {
|
| 41 |
+
showModal(modalDialog(
|
| 42 |
+
title = NULL,
|
| 43 |
+
size = "l",
|
| 44 |
+
easyClose = FALSE,
|
| 45 |
+
footer = NULL,
|
| 46 |
+
tags$div(
|
| 47 |
+
class = "splash-content text-center",
|
| 48 |
+
|
| 49 |
+
# Logo container with video and static image overlay
|
| 50 |
+
tags$div(
|
| 51 |
+
class = "splash-logo-container",
|
| 52 |
+
# Video plays first
|
| 53 |
+
tags$video(
|
| 54 |
+
id = "splash-video",
|
| 55 |
+
class = "splash-video",
|
| 56 |
+
autoplay = NA,
|
| 57 |
+
muted = NA,
|
| 58 |
+
playsinline = NA,
|
| 59 |
+
tags$source(src = "splash_animation.mp4", type = "video/mp4")
|
| 60 |
+
),
|
| 61 |
+
# Static logo (hidden initially, shown after video ends)
|
| 62 |
+
tags$img(
|
| 63 |
+
id = "splash-logo",
|
| 64 |
+
class = "splash-logo hidden",
|
| 65 |
+
src = "PEPlogo_720x720.jpeg",
|
| 66 |
+
alt = "Polyphenol Estimation Pipeline"
|
| 67 |
+
)
|
| 68 |
+
),
|
| 69 |
+
|
| 70 |
+
# Attribution section
|
| 71 |
+
tags$div(
|
| 72 |
+
class = "splash-info",
|
| 73 |
+
tags$p(
|
| 74 |
+
class = "splash-attribution",
|
| 75 |
+
tags$span(class = "attribution-label", "Pipeline Developed by"),
|
| 76 |
+
tags$br(),
|
| 77 |
+
tags$span(class = "developer-name", "Stephanie M.G. Wilson"),
|
| 78 |
+
tags$br(),
|
| 79 |
+
tags$span(class = "developer-affiliation", "University of California, Davis"),
|
| 80 |
+
tags$br(),
|
| 81 |
+
tags$a(
|
| 82 |
+
href = "https://github.com/SWi1/polyphenol_pipeline",
|
| 83 |
+
target = "_blank",
|
| 84 |
+
class = "repo-link",
|
| 85 |
+
"View Pipeline Repository"
|
| 86 |
+
)
|
| 87 |
+
)
|
| 88 |
+
),
|
| 89 |
+
|
| 90 |
+
# Get Started button
|
| 91 |
+
tags$div(
|
| 92 |
+
class = "splash-buttons",
|
| 93 |
+
actionButton("splash_start", "Get Started", class = "btn-primary btn-lg splash-btn")
|
| 94 |
+
),
|
| 95 |
+
|
| 96 |
+
# JavaScript for video control - stop 1 second before end to avoid fade-out
|
| 97 |
+
tags$script(HTML("
|
| 98 |
+
(function() {
|
| 99 |
+
// Wait for modal to be fully rendered
|
| 100 |
+
var checkVideo = setInterval(function() {
|
| 101 |
+
var video = document.getElementById('splash-video');
|
| 102 |
+
var logo = document.getElementById('splash-logo');
|
| 103 |
+
|
| 104 |
+
if (video) {
|
| 105 |
+
clearInterval(checkVideo);
|
| 106 |
+
|
| 107 |
+
// Stop video 1 second before end to avoid the fade-out frames
|
| 108 |
+
video.addEventListener('timeupdate', function() {
|
| 109 |
+
if (video.duration && video.currentTime >= video.duration - 1.0) {
|
| 110 |
+
video.pause();
|
| 111 |
+
}
|
| 112 |
+
});
|
| 113 |
+
|
| 114 |
+
// Fallback: if video fails to load, show static logo
|
| 115 |
+
video.addEventListener('error', function() {
|
| 116 |
+
video.style.display = 'none';
|
| 117 |
+
if (logo) {
|
| 118 |
+
logo.classList.remove('hidden');
|
| 119 |
+
logo.classList.add('fade-in');
|
| 120 |
+
}
|
| 121 |
+
});
|
| 122 |
+
|
| 123 |
+
// If video can't play (e.g., autoplay blocked), show static logo
|
| 124 |
+
var playPromise = video.play();
|
| 125 |
+
if (playPromise !== undefined) {
|
| 126 |
+
playPromise.catch(function(error) {
|
| 127 |
+
video.style.display = 'none';
|
| 128 |
+
if (logo) {
|
| 129 |
+
logo.classList.remove('hidden');
|
| 130 |
+
logo.classList.add('fade-in');
|
| 131 |
+
}
|
| 132 |
+
});
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
}, 100);
|
| 136 |
+
})();
|
| 137 |
+
"))
|
| 138 |
+
)
|
| 139 |
+
))
|
| 140 |
+
}, once = TRUE)
|
| 141 |
+
|
| 142 |
+
observeEvent(input$splash_start, {
|
| 143 |
+
removeModal()
|
| 144 |
+
})
|
| 145 |
+
|
| 146 |
+
# Navigate to Input tab from Get Started page
|
| 147 |
+
observeEvent(input$go_to_input, {
|
| 148 |
+
updateNavbarPage(session, "main_nav", selected = "input")
|
| 149 |
+
})
|
| 150 |
+
|
| 151 |
+
# --------------------------------------------------------------------------
|
| 152 |
+
# Demo Data Loading
|
| 153 |
+
# --------------------------------------------------------------------------
|
| 154 |
+
load_demo_data <- function() {
|
| 155 |
+
if (file.exists(DEMO_DATA_FILE)) {
|
| 156 |
+
rv$raw_data <- vroom(DEMO_DATA_FILE, show_col_types = FALSE)
|
| 157 |
+
rv$data_source <- "ASA24"
|
| 158 |
+
rv$data_validated <- TRUE
|
| 159 |
+
updateRadioGroupButtons(session, "data_source", selected = "ASA24")
|
| 160 |
+
showNotification("Demo data loaded (ASA24 format)", type = "message")
|
| 161 |
+
} else {
|
| 162 |
+
showNotification("Demo data file not found", type = "error")
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
observeEvent(input$load_demo, {
|
| 167 |
+
load_demo_data()
|
| 168 |
+
})
|
| 169 |
+
|
| 170 |
+
# --------------------------------------------------------------------------
|
| 171 |
+
# File Upload Handler
|
| 172 |
+
# --------------------------------------------------------------------------
|
| 173 |
+
observeEvent(input$diet_file, {
|
| 174 |
+
req(input$diet_file)
|
| 175 |
+
|
| 176 |
+
tryCatch({
|
| 177 |
+
file_ext <- tools::file_ext(input$diet_file$name)
|
| 178 |
+
|
| 179 |
+
if (file_ext %in% c("csv", "CSV")) {
|
| 180 |
+
rv$raw_data <- vroom(input$diet_file$datapath, show_col_types = FALSE)
|
| 181 |
+
} else if (file_ext %in% c("xlsx", "xls")) {
|
| 182 |
+
rv$raw_data <- read_xlsx(input$diet_file$datapath)
|
| 183 |
+
} else {
|
| 184 |
+
showNotification("Unsupported file format. Use CSV or Excel.", type = "error")
|
| 185 |
+
return()
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
rv$data_source <- input$data_source
|
| 189 |
+
|
| 190 |
+
if (input$data_source == "ASA24") {
|
| 191 |
+
validation <- validate_asa24_data(rv$raw_data)
|
| 192 |
+
} else {
|
| 193 |
+
validation <- validate_nhanes_data(rv$raw_data)
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
rv$data_validated <- validation$valid
|
| 197 |
+
if (!validation$valid) {
|
| 198 |
+
showNotification(validation$message, type = "error", duration = 10)
|
| 199 |
+
} else {
|
| 200 |
+
showNotification("Data loaded and validated", type = "message")
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
}, error = function(e) {
|
| 204 |
+
showNotification(paste("Error reading file:", e$message), type = "error")
|
| 205 |
+
rv$raw_data <- NULL
|
| 206 |
+
rv$data_validated <- FALSE
|
| 207 |
+
})
|
| 208 |
+
})
|
| 209 |
+
|
| 210 |
+
# --------------------------------------------------------------------------
|
| 211 |
+
# Reset Handler with Confirmation
|
| 212 |
+
# --------------------------------------------------------------------------
|
| 213 |
+
observeEvent(input$reset_btn, {
|
| 214 |
+
showModal(modalDialog(
|
| 215 |
+
title = "Confirm Reset",
|
| 216 |
+
"Are you sure you want to clear all data and results? This cannot be undone.",
|
| 217 |
+
footer = tagList(
|
| 218 |
+
modalButton("Cancel"),
|
| 219 |
+
actionButton("confirm_reset", "Yes, Reset", class = "btn-danger")
|
| 220 |
+
)
|
| 221 |
+
))
|
| 222 |
+
})
|
| 223 |
+
|
| 224 |
+
observeEvent(input$confirm_reset, {
|
| 225 |
+
rv$raw_data <- NULL
|
| 226 |
+
rv$data_source <- NULL
|
| 227 |
+
rv$data_validated <- FALSE
|
| 228 |
+
rv$analysis_complete <- FALSE
|
| 229 |
+
rv$results <- list()
|
| 230 |
+
rv$unmapped_foods <- NULL
|
| 231 |
+
removeModal()
|
| 232 |
+
showNotification("All data cleared", type = "message")
|
| 233 |
+
})
|
| 234 |
+
|
| 235 |
+
# --------------------------------------------------------------------------
|
| 236 |
+
# Data Status Output
|
| 237 |
+
# --------------------------------------------------------------------------
|
| 238 |
+
output$data_status <- renderUI({
|
| 239 |
+
if (is.null(rv$raw_data)) {
|
| 240 |
+
tags$div(
|
| 241 |
+
class = "alert alert-info",
|
| 242 |
+
icon("info-circle"),
|
| 243 |
+
" Upload a dietary data file or load demo data to begin."
|
| 244 |
+
)
|
| 245 |
+
} else if (!rv$data_validated) {
|
| 246 |
+
tags$div(
|
| 247 |
+
class = "alert alert-warning",
|
| 248 |
+
icon("triangle-exclamation"),
|
| 249 |
+
" Data validation failed. Check that your file has the required columns."
|
| 250 |
+
)
|
| 251 |
+
} else {
|
| 252 |
+
n_subjects <- length(unique(rv$raw_data[[if(rv$data_source == "ASA24") "UserName" else "SEQN"]]))
|
| 253 |
+
n_rows <- nrow(rv$raw_data)
|
| 254 |
+
|
| 255 |
+
tags$div(
|
| 256 |
+
class = "alert alert-success",
|
| 257 |
+
icon("check-circle"),
|
| 258 |
+
sprintf(" Data loaded: %d subjects, %d food records", n_subjects, n_rows)
|
| 259 |
+
)
|
| 260 |
+
}
|
| 261 |
+
})
|
| 262 |
+
|
| 263 |
+
# --------------------------------------------------------------------------
|
| 264 |
+
# Data Preview Table
|
| 265 |
+
# --------------------------------------------------------------------------
|
| 266 |
+
output$data_preview <- renderDT({
|
| 267 |
+
req(rv$raw_data)
|
| 268 |
+
|
| 269 |
+
preview_cols <- if (rv$data_source == "ASA24") {
|
| 270 |
+
intersect(c("UserName", "RecallNo", "FoodCode", "Food_Description", "FoodAmt", "KCAL"),
|
| 271 |
+
names(rv$raw_data))
|
| 272 |
+
} else {
|
| 273 |
+
intersect(c("SEQN", "RecallNo", "DRXIFDCD", "DRXIKCAL"),
|
| 274 |
+
names(rv$raw_data))
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
datatable(
|
| 278 |
+
head(rv$raw_data[, preview_cols, drop = FALSE], 100),
|
| 279 |
+
options = list(
|
| 280 |
+
pageLength = 10,
|
| 281 |
+
scrollX = TRUE,
|
| 282 |
+
dom = 'tip'
|
| 283 |
+
),
|
| 284 |
+
class = "display compact",
|
| 285 |
+
rownames = FALSE
|
| 286 |
+
)
|
| 287 |
+
})
|
| 288 |
+
|
| 289 |
+
# --------------------------------------------------------------------------
|
| 290 |
+
# Main Pipeline Execution
|
| 291 |
+
# --------------------------------------------------------------------------
|
| 292 |
+
observeEvent(input$run_pipeline, {
|
| 293 |
+
req(rv$raw_data, rv$data_validated)
|
| 294 |
+
|
| 295 |
+
if (!databases_ready()) {
|
| 296 |
+
showNotification("Reference databases not loaded. Cannot run pipeline.", type = "error")
|
| 297 |
+
return()
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
rv$processing <- TRUE
|
| 301 |
+
|
| 302 |
+
withProgress(message = "Running pipeline...", value = 0, {
|
| 303 |
+
|
| 304 |
+
tryCatch({
|
| 305 |
+
# Step 1: Prepare and clean data
|
| 306 |
+
incProgress(0.1, detail = "Preparing dietary data")
|
| 307 |
+
|
| 308 |
+
if (rv$data_source == "ASA24") {
|
| 309 |
+
input_data <- rv$raw_data %>%
|
| 310 |
+
rename(subject = UserName)
|
| 311 |
+
} else {
|
| 312 |
+
input_data <- rv$raw_data %>%
|
| 313 |
+
rename(subject = SEQN)
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
input_data_clean <- input_data %>%
|
| 317 |
+
group_by(subject) %>%
|
| 318 |
+
filter(n_distinct(RecallNo) > 1) %>%
|
| 319 |
+
ungroup()
|
| 320 |
+
|
| 321 |
+
if ("RecallStatus" %in% names(input_data_clean)) {
|
| 322 |
+
input_data_clean <- input_data_clean %>%
|
| 323 |
+
filter(RecallStatus != 5)
|
| 324 |
+
}
|
| 325 |
+
|
| 326 |
+
# Calculate total nutrients
|
| 327 |
+
incProgress(0.15, detail = "Calculating nutrient totals")
|
| 328 |
+
|
| 329 |
+
nutrient_cols <- if ("KCAL" %in% names(input_data_clean)) {
|
| 330 |
+
kcal_idx <- which(names(input_data_clean) == "KCAL")
|
| 331 |
+
b12_idx <- which(names(input_data_clean) == "B12_ADD")
|
| 332 |
+
if (length(b12_idx) > 0) {
|
| 333 |
+
names(input_data_clean)[kcal_idx:b12_idx]
|
| 334 |
+
} else {
|
| 335 |
+
"KCAL"
|
| 336 |
+
}
|
| 337 |
+
} else {
|
| 338 |
+
grep("^DRXI", names(input_data_clean), value = TRUE)
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
input_total_nutrients <- input_data_clean %>%
|
| 342 |
+
group_by(subject, RecallNo) %>%
|
| 343 |
+
summarize(across(any_of(nutrient_cols), ~ sum(.x, na.rm = TRUE), .names = "Total_{.col}"),
|
| 344 |
+
.groups = "drop")
|
| 345 |
+
|
| 346 |
+
if (rv$data_source == "ASA24") {
|
| 347 |
+
input_data_minimal <- input_data_clean %>%
|
| 348 |
+
rename(wweia_food_code = FoodCode, food_description = Food_Description) %>%
|
| 349 |
+
select(any_of(c("subject", "RecallNo", "wweia_food_code", "food_description", "FoodAmt")))
|
| 350 |
+
} else {
|
| 351 |
+
input_data_minimal <- input_data_clean %>%
|
| 352 |
+
rename(wweia_food_code = DRXIFDCD) %>%
|
| 353 |
+
select(any_of(c("subject", "RecallNo", "wweia_food_code", "DRXIGRMS"))) %>%
|
| 354 |
+
rename(FoodAmt = DRXIGRMS)
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
# Step 2: Apply brewing adjustment and disaggregate
|
| 358 |
+
incProgress(0.25, detail = "Disaggregating foods")
|
| 359 |
+
|
| 360 |
+
FDD_adjusted <- apply_brewing_adjustment(FDD_V3)
|
| 361 |
+
|
| 362 |
+
merged_data <- left_join(input_data_minimal, FDD_adjusted, by = "wweia_food_code",
|
| 363 |
+
relationship = "many-to-many") %>%
|
| 364 |
+
mutate(FoodAmt_Ing_g = FoodAmt * (
|
| 365 |
+
coalesce(brewing_adjustment_percentage, ingredient_percent) / 100))
|
| 366 |
+
|
| 367 |
+
# Step 3: Map to FooDB
|
| 368 |
+
incProgress(0.35, detail = "Mapping to FooDB database")
|
| 369 |
+
|
| 370 |
+
input_mapped <- merged_data %>%
|
| 371 |
+
left_join(fdd_foodb_mapping, by = "fdd_ingredient")
|
| 372 |
+
|
| 373 |
+
rv$unmapped_foods <- input_mapped %>%
|
| 374 |
+
filter(!is.na(fdd_ingredient) & is.na(orig_food_common_name)) %>%
|
| 375 |
+
distinct(fdd_ingredient) %>%
|
| 376 |
+
pull(fdd_ingredient)
|
| 377 |
+
|
| 378 |
+
# Step 4: Calculate polyphenol content
|
| 379 |
+
incProgress(0.45, detail = "Calculating polyphenol content")
|
| 380 |
+
|
| 381 |
+
input_mapped_content <- input_mapped %>%
|
| 382 |
+
left_join(FooDB_mg_100g, by = "food_id", relationship = "many-to-many") %>%
|
| 383 |
+
mutate(
|
| 384 |
+
pp_consumed = if_else(
|
| 385 |
+
compound_public_id %in% c("FDB000095", "FDB017114") & food_id == 38,
|
| 386 |
+
(orig_content_avg_RFadj * 0.01) * FoodAmt_Ing_g * (ingredient_percent / 100),
|
| 387 |
+
(orig_content_avg_RFadj * 0.01) * FoodAmt_Ing_g
|
| 388 |
+
)
|
| 389 |
+
)
|
| 390 |
+
|
| 391 |
+
input_kcal <- input_total_nutrients %>%
|
| 392 |
+
select(subject, RecallNo, any_of(c("Total_KCAL", "Total_DRXIKCAL")))
|
| 393 |
+
|
| 394 |
+
if (!"Total_KCAL" %in% names(input_kcal) && "Total_DRXIKCAL" %in% names(input_kcal)) {
|
| 395 |
+
input_kcal <- input_kcal %>% rename(Total_KCAL = Total_DRXIKCAL)
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
input_polyphenol_kcal <- left_join(input_mapped_content, input_kcal, by = c("subject", "RecallNo"))
|
| 399 |
+
|
| 400 |
+
# Step 5: Calculate total intake
|
| 401 |
+
incProgress(0.55, detail = "Summarizing total intake")
|
| 402 |
+
|
| 403 |
+
content_by_recall <- input_polyphenol_kcal %>%
|
| 404 |
+
group_by(subject, RecallNo) %>%
|
| 405 |
+
summarise(
|
| 406 |
+
pp_recallsum_mg = sum(pp_consumed, na.rm = TRUE),
|
| 407 |
+
Total_KCAL = first(Total_KCAL),
|
| 408 |
+
.groups = "drop"
|
| 409 |
+
) %>%
|
| 410 |
+
mutate(pp_recallsum_mg1000kcal = pp_recallsum_mg / (Total_KCAL / 1000))
|
| 411 |
+
|
| 412 |
+
content_by_subject <- content_by_recall %>%
|
| 413 |
+
group_by(subject) %>%
|
| 414 |
+
summarise(
|
| 415 |
+
pp_average_mg = mean(pp_recallsum_mg, na.rm = TRUE),
|
| 416 |
+
kcal_average = mean(Total_KCAL, na.rm = TRUE),
|
| 417 |
+
pp_average_mg_1000kcal = pp_average_mg / (kcal_average / 1000),
|
| 418 |
+
.groups = "drop"
|
| 419 |
+
)
|
| 420 |
+
|
| 421 |
+
rv$results$total_by_subject <- content_by_subject
|
| 422 |
+
rv$results$total_by_recall <- content_by_recall
|
| 423 |
+
|
| 424 |
+
# Step 6: Calculate class-level intake
|
| 425 |
+
incProgress(0.65, detail = "Calculating class-level intake")
|
| 426 |
+
|
| 427 |
+
input_with_class <- input_polyphenol_kcal %>%
|
| 428 |
+
left_join(class_tax, by = "compound_public_id")
|
| 429 |
+
|
| 430 |
+
class_by_recall <- input_with_class %>%
|
| 431 |
+
group_by(subject, RecallNo, class) %>%
|
| 432 |
+
summarise(
|
| 433 |
+
class_intake_mg = sum(pp_consumed, na.rm = TRUE),
|
| 434 |
+
Total_KCAL = first(Total_KCAL),
|
| 435 |
+
.groups = "drop"
|
| 436 |
+
) %>%
|
| 437 |
+
filter(!is.na(class)) %>%
|
| 438 |
+
mutate(class_intake_mg1000kcal = class_intake_mg / (Total_KCAL / 1000))
|
| 439 |
+
|
| 440 |
+
class_by_subject <- class_by_recall %>%
|
| 441 |
+
group_by(subject, class) %>%
|
| 442 |
+
summarise(
|
| 443 |
+
Avg_class_intake_mg = mean(class_intake_mg, na.rm = TRUE),
|
| 444 |
+
avg_Total_KCAL = mean(Total_KCAL, na.rm = TRUE),
|
| 445 |
+
.groups = "drop"
|
| 446 |
+
) %>%
|
| 447 |
+
mutate(class_intake_mg1000kcal = Avg_class_intake_mg / (avg_Total_KCAL / 1000))
|
| 448 |
+
|
| 449 |
+
rv$results$class_by_subject <- class_by_subject
|
| 450 |
+
rv$results$class_by_recall <- class_by_recall
|
| 451 |
+
|
| 452 |
+
# Step 7: Calculate food contributors
|
| 453 |
+
incProgress(0.75, detail = "Identifying food contributors")
|
| 454 |
+
|
| 455 |
+
food_contributors <- input_polyphenol_kcal %>%
|
| 456 |
+
group_by(fdd_ingredient) %>%
|
| 457 |
+
summarise(
|
| 458 |
+
food_pp_average_mg1000kcal = mean(pp_consumed, na.rm = TRUE) / mean(Total_KCAL, na.rm = TRUE) * 1000,
|
| 459 |
+
total_times_consumed = n(),
|
| 460 |
+
n_subjects = n_distinct(subject),
|
| 461 |
+
.groups = "drop"
|
| 462 |
+
) %>%
|
| 463 |
+
filter(!is.na(fdd_ingredient)) %>%
|
| 464 |
+
arrange(desc(food_pp_average_mg1000kcal))
|
| 465 |
+
|
| 466 |
+
rv$results$food_contributors <- food_contributors
|
| 467 |
+
|
| 468 |
+
# Step 8: DII calculation
|
| 469 |
+
if (input$calculate_dii) {
|
| 470 |
+
incProgress(0.85, detail = "Calculating DII scores")
|
| 471 |
+
|
| 472 |
+
# DII Step 1: Calculate eugenol intake
|
| 473 |
+
if (!is.null(FooDB_eugenol)) {
|
| 474 |
+
eugenol_intake <- input_mapped %>%
|
| 475 |
+
left_join(
|
| 476 |
+
FooDB_eugenol %>% select(-c(source_type, food_name, orig_food_common_name,
|
| 477 |
+
orig_food_scientific_name, orig_source_id,
|
| 478 |
+
orig_source_name, citation, preparation_type)),
|
| 479 |
+
by = "food_id"
|
| 480 |
+
) %>%
|
| 481 |
+
filter(!is.na(orig_content_avg)) %>%
|
| 482 |
+
mutate(eugenol_mg = (orig_content_avg * 0.01) * FoodAmt_Ing_g) %>%
|
| 483 |
+
group_by(subject, RecallNo) %>%
|
| 484 |
+
summarise(EUGENOL = sum(eugenol_mg, na.rm = TRUE), .groups = "drop")
|
| 485 |
+
} else {
|
| 486 |
+
eugenol_intake <- input_mapped %>%
|
| 487 |
+
distinct(subject, RecallNo) %>%
|
| 488 |
+
mutate(EUGENOL = 0)
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
# DII Step 2: Calculate polyphenol subclass intakes
|
| 492 |
+
if (!is.null(FooDB_DII_subclasses)) {
|
| 493 |
+
subclass_intake <- input_polyphenol_kcal %>%
|
| 494 |
+
filter(compound_public_id %in% FooDB_DII_subclasses$compound_public_id) %>%
|
| 495 |
+
left_join(FooDB_DII_subclasses %>% select(compound_public_id, component),
|
| 496 |
+
by = "compound_public_id") %>%
|
| 497 |
+
group_by(subject, RecallNo, component) %>%
|
| 498 |
+
summarise(component_sum = sum(pp_consumed, na.rm = TRUE), .groups = "drop") %>%
|
| 499 |
+
pivot_wider(names_from = component, values_from = component_sum, values_fill = 0) %>%
|
| 500 |
+
rename_with(~ case_when(
|
| 501 |
+
. == "Isoflavones" ~ "ISOFLAVONES",
|
| 502 |
+
. == "Flavan-3-ols" ~ "FLA3OL",
|
| 503 |
+
. == "Flavones" ~ "FLAVONES",
|
| 504 |
+
. == "Flavonols" ~ "FLAVONOLS",
|
| 505 |
+
. == "Flavanones" ~ "FLAVONONES",
|
| 506 |
+
. == "Anthocyanidins" ~ "ANTHOC",
|
| 507 |
+
TRUE ~ .
|
| 508 |
+
), .cols = -c(subject, RecallNo))
|
| 509 |
+
} else {
|
| 510 |
+
subclass_intake <- input_mapped %>%
|
| 511 |
+
distinct(subject, RecallNo) %>%
|
| 512 |
+
mutate(ISOFLAVONES = 0, FLA3OL = 0, FLAVONES = 0,
|
| 513 |
+
FLAVONOLS = 0, FLAVONONES = 0, ANTHOC = 0)
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
# DII Step 3: Calculate DII food component intakes
|
| 517 |
+
# Following the exact pattern matching from DII_STEP3_Food.Rmd
|
| 518 |
+
|
| 519 |
+
# Get unique FDD ingredients
|
| 520 |
+
fdd_unique <- FDD_V3 %>% distinct(fdd_ingredient)
|
| 521 |
+
|
| 522 |
+
# Simple single-pattern matches
|
| 523 |
+
garlic_ingredients <- fdd_unique %>%
|
| 524 |
+
filter(grepl("garlic", fdd_ingredient, ignore.case = TRUE)) %>%
|
| 525 |
+
mutate(component = "GARLIC")
|
| 526 |
+
|
| 527 |
+
ginger_ingredients <- fdd_unique %>%
|
| 528 |
+
filter(grepl("ginger", fdd_ingredient, ignore.case = TRUE)) %>%
|
| 529 |
+
mutate(component = "GINGER")
|
| 530 |
+
|
| 531 |
+
onion_ingredients <- fdd_unique %>%
|
| 532 |
+
filter(grepl("onion", fdd_ingredient, ignore.case = TRUE)) %>%
|
| 533 |
+
mutate(component = "ONION")
|
| 534 |
+
|
| 535 |
+
turmeric_ingredients <- fdd_unique %>%
|
| 536 |
+
filter(grepl("turmeric", fdd_ingredient, ignore.case = TRUE)) %>%
|
| 537 |
+
mutate(component = "TURMERIC")
|
| 538 |
+
|
| 539 |
+
# Tea: must contain "tea" AND ("black" OR "oolong" OR "green")
|
| 540 |
+
# Excludes herbal teas
|
| 541 |
+
tea_ingredients <- fdd_unique %>%
|
| 542 |
+
filter(grepl("tea", fdd_ingredient, ignore.case = TRUE)) %>%
|
| 543 |
+
filter(grepl("black|oolong|green", fdd_ingredient, ignore.case = TRUE)) %>%
|
| 544 |
+
mutate(component = "TEA")
|
| 545 |
+
|
| 546 |
+
# Pepper: must contain "pepper" AND "spices"
|
| 547 |
+
# Excludes fresh peppers (bell peppers, etc.)
|
| 548 |
+
pepper_ingredients <- fdd_unique %>%
|
| 549 |
+
filter(grepl("pepper", fdd_ingredient, ignore.case = TRUE)) %>%
|
| 550 |
+
filter(grepl("spices", fdd_ingredient, ignore.case = TRUE)) %>%
|
| 551 |
+
mutate(component = "PEPPER")
|
| 552 |
+
|
| 553 |
+
# Thyme or oregano
|
| 554 |
+
thyme_ingredients <- fdd_unique %>%
|
| 555 |
+
filter(grepl("thyme|oregano", fdd_ingredient, ignore.case = TRUE)) %>%
|
| 556 |
+
mutate(component = "THYME")
|
| 557 |
+
|
| 558 |
+
# Combine all DII food ingredients
|
| 559 |
+
dii_foods_df <- bind_rows(
|
| 560 |
+
garlic_ingredients,
|
| 561 |
+
ginger_ingredients,
|
| 562 |
+
onion_ingredients,
|
| 563 |
+
turmeric_ingredients,
|
| 564 |
+
tea_ingredients,
|
| 565 |
+
pepper_ingredients,
|
| 566 |
+
thyme_ingredients
|
| 567 |
+
)
|
| 568 |
+
|
| 569 |
+
if (nrow(dii_foods_df) > 0) {
|
| 570 |
+
food_component_intake <- input_mapped %>%
|
| 571 |
+
filter(fdd_ingredient %in% dii_foods_df$fdd_ingredient) %>%
|
| 572 |
+
left_join(dii_foods_df, by = "fdd_ingredient") %>%
|
| 573 |
+
group_by(subject, RecallNo, component) %>%
|
| 574 |
+
summarise(component_sum = sum(FoodAmt_Ing_g, na.rm = TRUE), .groups = "drop") %>%
|
| 575 |
+
pivot_wider(names_from = component, values_from = component_sum, values_fill = 0)
|
| 576 |
+
} else {
|
| 577 |
+
food_component_intake <- input_mapped %>%
|
| 578 |
+
distinct(subject, RecallNo)
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
# DII Step 4: Merge all components with total nutrients
|
| 582 |
+
dii_merge <- input_total_nutrients %>%
|
| 583 |
+
rename_with(~ gsub("^Total_", "", .x)) %>%
|
| 584 |
+
left_join(eugenol_intake, by = c("subject", "RecallNo")) %>%
|
| 585 |
+
left_join(subclass_intake, by = c("subject", "RecallNo")) %>%
|
| 586 |
+
left_join(food_component_intake, by = c("subject", "RecallNo"))
|
| 587 |
+
|
| 588 |
+
# Standardize nutrient names (ASA24 vs NHANES)
|
| 589 |
+
nutrient_mapping <- tribble(
|
| 590 |
+
~new_name, ~asa24_name, ~nhanes_name,
|
| 591 |
+
"ALCOHOL", "ALC", "DRXIALCO",
|
| 592 |
+
"VITB12", "VB12", "DRXIVB12",
|
| 593 |
+
"VITB6", "VB6", "DRXIVB6",
|
| 594 |
+
"BCAROTENE", "BCAR", "DRXIBCAR",
|
| 595 |
+
"CAFF", "CAFF", "DRXICAFF",
|
| 596 |
+
"CARB", "CARB", "DRXICARB",
|
| 597 |
+
"CHOLES", "CHOLE", "DRXICHOL",
|
| 598 |
+
"KCAL", "KCAL", "DRXIKCAL",
|
| 599 |
+
"TOTALFAT", "TFAT", "DRXITFAT",
|
| 600 |
+
"FIBER", "FIBE", "DRXIFIBE",
|
| 601 |
+
"FOLICACID", "FA", "DRXIFA",
|
| 602 |
+
"IRON", "IRON", "DRXIIRON",
|
| 603 |
+
"MG", "MAGN", "DRXIMAGN",
|
| 604 |
+
"MUFA", "MFAT", "DRXIMFAT",
|
| 605 |
+
"NIACIN", "NIAC", "DRXINIAC",
|
| 606 |
+
"P183", "P183", "DRXIP183",
|
| 607 |
+
"P184", "P184", "DRXIP184",
|
| 608 |
+
"P205", "P205", "DRXIP205",
|
| 609 |
+
"P225", "P225", "DRXIP225",
|
| 610 |
+
"P226", "P226", "DRXIP226",
|
| 611 |
+
"P182", "P182", "DRXIP182",
|
| 612 |
+
"P204", "P204", "DRXIP204",
|
| 613 |
+
"PROTEIN", "PROT", "DRXIPROT",
|
| 614 |
+
"PUFA", "PFAT", "DRXIPFAT",
|
| 615 |
+
"RIBOFLAVIN", "VB2", "DRXIVB2",
|
| 616 |
+
"SATFAT", "SFAT", "DRXISFAT",
|
| 617 |
+
"SE", "SELE", "DRXISELE",
|
| 618 |
+
"THIAMIN", "VB1", "DRXIVB1",
|
| 619 |
+
"VITA", "VARA", "DRXIVARA",
|
| 620 |
+
"VITC", "VC", "DRXIVC",
|
| 621 |
+
"VITD", "VITD", "DRXIVD",
|
| 622 |
+
"VITE", "ATOC", "DRXIATOC",
|
| 623 |
+
"ZN", "ZINC", "DRXIZINC"
|
| 624 |
+
)
|
| 625 |
+
|
| 626 |
+
is_nhanes <- any(startsWith(names(dii_merge), "DRX"))
|
| 627 |
+
old_names <- if (is_nhanes) nutrient_mapping$nhanes_name else nutrient_mapping$asa24_name
|
| 628 |
+
new_names <- nutrient_mapping$new_name
|
| 629 |
+
existing_idx <- which(old_names %in% names(dii_merge))
|
| 630 |
+
|
| 631 |
+
if (length(existing_idx) > 0) {
|
| 632 |
+
dii_merge <- dii_merge %>%
|
| 633 |
+
rename(!!!setNames(old_names[existing_idx], new_names[existing_idx]))
|
| 634 |
+
}
|
| 635 |
+
|
| 636 |
+
# Calculate derived components
|
| 637 |
+
dii_cohort <- dii_merge %>%
|
| 638 |
+
mutate(
|
| 639 |
+
CAFFEINE = if ("CAFF" %in% names(.)) CAFF / 1000 else 0,
|
| 640 |
+
N3FAT = rowSums(across(any_of(c("P183", "P184", "P205", "P225", "P226"))), na.rm = TRUE),
|
| 641 |
+
N6FAT = rowSums(across(any_of(c("P182", "P204"))), na.rm = TRUE),
|
| 642 |
+
TURMERIC = if ("TURMERIC" %in% names(.)) TURMERIC * 1000 else 0,
|
| 643 |
+
THYME = if ("THYME" %in% names(.)) THYME * 1000 else 0
|
| 644 |
+
)
|
| 645 |
+
|
| 646 |
+
# DII scoring parameters from Shivappa et al.
|
| 647 |
+
dii_params <- tribble(
|
| 648 |
+
~Variable, ~Overall_inflammatory_score, ~Global_mean, ~SD,
|
| 649 |
+
"ALCOHOL", -0.278, 13.98, 3.72,
|
| 650 |
+
"VITB12", 0.106, 5.15, 2.7,
|
| 651 |
+
"VITB6", -0.365, 1.47, 0.74,
|
| 652 |
+
"BCAROTENE", -0.584, 3718, 1720,
|
| 653 |
+
"CAFFEINE", -0.11, 8.05, 6.67,
|
| 654 |
+
"CARB", 0.097, 272.2, 40,
|
| 655 |
+
"CHOLES", 0.11, 279.4, 51.2,
|
| 656 |
+
"KCAL", 0.18, 2056, 338,
|
| 657 |
+
"EUGENOL", -0.14, 0.01, 0.08,
|
| 658 |
+
"TOTALFAT", 0.298, 71.4, 19.4,
|
| 659 |
+
"FIBER", -0.663, 18.8, 4.9,
|
| 660 |
+
"FOLICACID", -0.19, 273, 70.7,
|
| 661 |
+
"GARLIC", -0.412, 4.35, 2.9,
|
| 662 |
+
"GINGER", -0.453, 59, 63.2,
|
| 663 |
+
"IRON", 0.032, 13.35, 3.71,
|
| 664 |
+
"MG", -0.484, 310.1, 139.4,
|
| 665 |
+
"MUFA", -0.009, 27, 6.1,
|
| 666 |
+
"NIACIN", -0.246, 25.9, 11.77,
|
| 667 |
+
"N3FAT", -0.436, 1.06, 1.06,
|
| 668 |
+
"N6FAT", -0.159, 10.8, 7.5,
|
| 669 |
+
"ONION", -0.301, 35.9, 18.4,
|
| 670 |
+
"PROTEIN", 0.021, 79.4, 13.9,
|
| 671 |
+
"PUFA", -0.337, 13.88, 3.76,
|
| 672 |
+
"RIBOFLAVIN", -0.068, 1.7, 0.79,
|
| 673 |
+
"SATFAT", 0.373, 28.6, 8,
|
| 674 |
+
"SE", -0.191, 67, 25.1,
|
| 675 |
+
"THIAMIN", -0.098, 1.7, 0.66,
|
| 676 |
+
"VITA", -0.401, 983.9, 518.6,
|
| 677 |
+
"VITC", -0.424, 118.2, 43.46,
|
| 678 |
+
"VITD", -0.446, 6.26, 2.21,
|
| 679 |
+
"VITE", -0.419, 8.73, 1.49,
|
| 680 |
+
"ZN", -0.313, 9.84, 2.19,
|
| 681 |
+
"TEA", -0.536, 1.69, 1.53,
|
| 682 |
+
"FLA3OL", -0.415, 95.8, 85.9,
|
| 683 |
+
"FLAVONES", -0.616, 1.55, 0.07,
|
| 684 |
+
"FLAVONOLS", -0.467, 17.7, 6.79,
|
| 685 |
+
"FLAVONONES", -0.25, 11.7, 3.82,
|
| 686 |
+
"ANTHOC", -0.131, 18.05, 21.14,
|
| 687 |
+
"ISOFLAVONES", -0.593, 1.2, 0.2,
|
| 688 |
+
"PEPPER", -0.131, 10, 7.07,
|
| 689 |
+
"THYME", -0.102, 0.33, 0.99,
|
| 690 |
+
"TURMERIC", -0.785, 533.6, 754.3
|
| 691 |
+
)
|
| 692 |
+
|
| 693 |
+
# Pivot to long format for DII calculation
|
| 694 |
+
dii_long <- dii_cohort %>%
|
| 695 |
+
select(subject, RecallNo, any_of(dii_params$Variable)) %>%
|
| 696 |
+
pivot_longer(-c(subject, RecallNo), names_to = "Variable", values_to = "Value") %>%
|
| 697 |
+
left_join(dii_params, by = "Variable") %>%
|
| 698 |
+
filter(!is.na(Overall_inflammatory_score)) %>%
|
| 699 |
+
mutate(
|
| 700 |
+
Z_SCORE = (Value - Global_mean) / SD,
|
| 701 |
+
PERCENTILE = pnorm(Z_SCORE) * 2 - 1,
|
| 702 |
+
IND_DII_SCORE = PERCENTILE * Overall_inflammatory_score
|
| 703 |
+
)
|
| 704 |
+
|
| 705 |
+
# Calculate total DII scores
|
| 706 |
+
dii_scores <- dii_long %>%
|
| 707 |
+
group_by(subject, RecallNo) %>%
|
| 708 |
+
summarise(
|
| 709 |
+
DII_ALL = sum(IND_DII_SCORE, na.rm = TRUE),
|
| 710 |
+
DII_NOETOH = sum(IND_DII_SCORE[Variable != "ALCOHOL"], na.rm = TRUE),
|
| 711 |
+
n_components = n(),
|
| 712 |
+
.groups = "drop"
|
| 713 |
+
)
|
| 714 |
+
|
| 715 |
+
# Average by subject
|
| 716 |
+
dii_by_subject <- dii_scores %>%
|
| 717 |
+
group_by(subject) %>%
|
| 718 |
+
summarise(
|
| 719 |
+
DII_ALL_avg = mean(DII_ALL, na.rm = TRUE),
|
| 720 |
+
DII_NOETOH_avg = mean(DII_NOETOH, na.rm = TRUE),
|
| 721 |
+
n_recalls = n(),
|
| 722 |
+
.groups = "drop"
|
| 723 |
+
)
|
| 724 |
+
|
| 725 |
+
rv$results$dii_by_recall <- dii_scores
|
| 726 |
+
rv$results$dii_by_subject <- dii_by_subject
|
| 727 |
+
rv$results$dii_components <- dii_long %>%
|
| 728 |
+
select(subject, RecallNo, Variable, Value, IND_DII_SCORE) %>%
|
| 729 |
+
pivot_wider(names_from = Variable, values_from = c(Value, IND_DII_SCORE))
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
# Calculate missing food stats for QA/QC
|
| 733 |
+
incProgress(0.95, detail = "Generating QA/QC report")
|
| 734 |
+
|
| 735 |
+
missing_counts <- input_mapped %>%
|
| 736 |
+
group_by(subject, RecallNo) %>%
|
| 737 |
+
summarise(
|
| 738 |
+
missing = sum(is.na(orig_food_common_name)),
|
| 739 |
+
total = n(),
|
| 740 |
+
pct_missing = missing / total * 100,
|
| 741 |
+
.groups = "drop"
|
| 742 |
+
)
|
| 743 |
+
|
| 744 |
+
rv$results$missing_counts <- missing_counts
|
| 745 |
+
|
| 746 |
+
rv$analysis_complete <- TRUE
|
| 747 |
+
rv$processing <- FALSE
|
| 748 |
+
|
| 749 |
+
incProgress(1, detail = "Complete")
|
| 750 |
+
|
| 751 |
+
# Navigate to results tab using bslib function
|
| 752 |
+
nav_select("main_nav", selected = "results")
|
| 753 |
+
showNotification("Pipeline complete", type = "message")
|
| 754 |
+
|
| 755 |
+
}, error = function(e) {
|
| 756 |
+
rv$processing <- FALSE
|
| 757 |
+
showNotification(paste("Pipeline error:", e$message), type = "error", duration = 15)
|
| 758 |
+
})
|
| 759 |
+
})
|
| 760 |
+
})
|
| 761 |
+
|
| 762 |
+
# --------------------------------------------------------------------------
|
| 763 |
+
# Summary Cards
|
| 764 |
+
# --------------------------------------------------------------------------
|
| 765 |
+
output$summary_cards <- renderUI({
|
| 766 |
+
if (!rv$analysis_complete) {
|
| 767 |
+
return(tags$div(
|
| 768 |
+
class = "alert alert-info text-center",
|
| 769 |
+
icon("chart-bar"), " Run the pipeline to view results."
|
| 770 |
+
))
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
n_subjects <- nrow(rv$results$total_by_subject)
|
| 774 |
+
mean_intake <- mean(rv$results$total_by_subject$pp_average_mg, na.rm = TRUE)
|
| 775 |
+
n_classes <- length(unique(rv$results$class_by_subject$class))
|
| 776 |
+
n_unmapped <- length(rv$unmapped_foods)
|
| 777 |
+
|
| 778 |
+
layout_columns(
|
| 779 |
+
col_widths = c(3, 3, 3, 3),
|
| 780 |
+
value_box(
|
| 781 |
+
title = "Subjects Analyzed",
|
| 782 |
+
value = n_subjects,
|
| 783 |
+
theme = "primary",
|
| 784 |
+
showcase = icon("users")
|
| 785 |
+
),
|
| 786 |
+
value_box(
|
| 787 |
+
title = "Mean Polyphenol Intake",
|
| 788 |
+
value = paste(round(mean_intake, 1), "mg/day"),
|
| 789 |
+
theme = "success",
|
| 790 |
+
showcase = icon("leaf")
|
| 791 |
+
),
|
| 792 |
+
value_box(
|
| 793 |
+
title = "Polyphenol Classes",
|
| 794 |
+
value = n_classes,
|
| 795 |
+
theme = "info",
|
| 796 |
+
showcase = icon("layer-group")
|
| 797 |
+
),
|
| 798 |
+
value_box(
|
| 799 |
+
title = "Unmapped Foods",
|
| 800 |
+
value = n_unmapped,
|
| 801 |
+
theme = if (n_unmapped > 0) "warning" else "success",
|
| 802 |
+
showcase = icon("triangle-exclamation")
|
| 803 |
+
)
|
| 804 |
+
)
|
| 805 |
+
})
|
| 806 |
+
|
| 807 |
+
# --------------------------------------------------------------------------
|
| 808 |
+
# Visualizations
|
| 809 |
+
# --------------------------------------------------------------------------
|
| 810 |
+
output$plot_total_intake <- renderPlotly({
|
| 811 |
+
req(rv$analysis_complete, rv$results$total_by_subject)
|
| 812 |
+
|
| 813 |
+
df <- rv$results$total_by_subject %>%
|
| 814 |
+
arrange(desc(pp_average_mg)) %>%
|
| 815 |
+
head(30)
|
| 816 |
+
|
| 817 |
+
plot_ly(df, x = ~reorder(subject, pp_average_mg), y = ~pp_average_mg,
|
| 818 |
+
type = "bar", marker = list(color = PRIMARY_COLOR)) %>%
|
| 819 |
+
layout(
|
| 820 |
+
xaxis = list(title = "Subject", tickangle = -45),
|
| 821 |
+
yaxis = list(title = "Mean Polyphenol Intake (mg/day)"),
|
| 822 |
+
margin = list(b = 100)
|
| 823 |
+
)
|
| 824 |
+
})
|
| 825 |
+
|
| 826 |
+
output$plot_intake_distribution <- renderPlotly({
|
| 827 |
+
req(rv$analysis_complete, rv$results$total_by_subject)
|
| 828 |
+
|
| 829 |
+
plot_ly(rv$results$total_by_subject, x = ~pp_average_mg, type = "histogram",
|
| 830 |
+
marker = list(color = PRIMARY_COLOR, line = list(color = "white", width = 1))) %>%
|
| 831 |
+
layout(
|
| 832 |
+
xaxis = list(title = "Mean Polyphenol Intake (mg/day)"),
|
| 833 |
+
yaxis = list(title = "Count")
|
| 834 |
+
)
|
| 835 |
+
})
|
| 836 |
+
|
| 837 |
+
output$table_total_intake <- renderDT({
|
| 838 |
+
req(rv$analysis_complete, rv$results$total_by_subject)
|
| 839 |
+
|
| 840 |
+
datatable(
|
| 841 |
+
rv$results$total_by_subject %>%
|
| 842 |
+
mutate(across(where(is.numeric), ~ round(.x, 2))),
|
| 843 |
+
options = list(
|
| 844 |
+
pageLength = 25,
|
| 845 |
+
scrollX = TRUE,
|
| 846 |
+
scrollY = "400px",
|
| 847 |
+
dom = 'Bfrtip'
|
| 848 |
+
),
|
| 849 |
+
class = "display compact stripe",
|
| 850 |
+
rownames = FALSE
|
| 851 |
+
)
|
| 852 |
+
})
|
| 853 |
+
|
| 854 |
+
output$plot_class_intake <- renderPlotly({
|
| 855 |
+
req(rv$analysis_complete, rv$results$class_by_subject)
|
| 856 |
+
|
| 857 |
+
class_summary <- rv$results$class_by_subject %>%
|
| 858 |
+
group_by(class) %>%
|
| 859 |
+
summarise(mean_intake = mean(Avg_class_intake_mg, na.rm = TRUE), .groups = "drop") %>%
|
| 860 |
+
arrange(desc(mean_intake))
|
| 861 |
+
|
| 862 |
+
colors <- get_viz_colors(nrow(class_summary))
|
| 863 |
+
|
| 864 |
+
plot_ly(class_summary, x = ~reorder(class, mean_intake), y = ~mean_intake,
|
| 865 |
+
type = "bar", marker = list(color = colors)) %>%
|
| 866 |
+
layout(
|
| 867 |
+
xaxis = list(title = "Polyphenol Class", tickangle = -45),
|
| 868 |
+
yaxis = list(title = "Mean Intake (mg/day)"),
|
| 869 |
+
margin = list(b = 150)
|
| 870 |
+
)
|
| 871 |
+
})
|
| 872 |
+
|
| 873 |
+
output$table_class_intake <- renderDT({
|
| 874 |
+
req(rv$analysis_complete, rv$results$class_by_subject)
|
| 875 |
+
|
| 876 |
+
datatable(
|
| 877 |
+
rv$results$class_by_subject %>%
|
| 878 |
+
mutate(across(where(is.numeric), ~ round(.x, 2))),
|
| 879 |
+
options = list(
|
| 880 |
+
pageLength = 25,
|
| 881 |
+
scrollX = TRUE,
|
| 882 |
+
scrollY = "400px",
|
| 883 |
+
dom = 'Bfrtip'
|
| 884 |
+
),
|
| 885 |
+
class = "display compact stripe",
|
| 886 |
+
rownames = FALSE
|
| 887 |
+
)
|
| 888 |
+
})
|
| 889 |
+
|
| 890 |
+
output$plot_food_treemap <- renderPlotly({
|
| 891 |
+
req(rv$analysis_complete, rv$results$food_contributors)
|
| 892 |
+
|
| 893 |
+
top_foods <- rv$results$food_contributors %>%
|
| 894 |
+
filter(!is.na(fdd_ingredient), food_pp_average_mg1000kcal > 0) %>%
|
| 895 |
+
head(50)
|
| 896 |
+
|
| 897 |
+
if (nrow(top_foods) == 0) {
|
| 898 |
+
return(plotly_empty() %>% layout(title = "No data available"))
|
| 899 |
+
}
|
| 900 |
+
|
| 901 |
+
plot_ly(
|
| 902 |
+
top_foods,
|
| 903 |
+
labels = ~fdd_ingredient,
|
| 904 |
+
parents = "",
|
| 905 |
+
values = ~food_pp_average_mg1000kcal,
|
| 906 |
+
type = "treemap",
|
| 907 |
+
textinfo = "label+value",
|
| 908 |
+
marker = list(
|
| 909 |
+
colors = get_viz_colors(nrow(top_foods)),
|
| 910 |
+
line = list(width = 1, color = "white")
|
| 911 |
+
)
|
| 912 |
+
) %>%
|
| 913 |
+
layout(margin = list(l = 0, r = 0, t = 30, b = 0))
|
| 914 |
+
})
|
| 915 |
+
|
| 916 |
+
output$table_food_contributors <- renderDT({
|
| 917 |
+
req(rv$analysis_complete, rv$results$food_contributors)
|
| 918 |
+
|
| 919 |
+
datatable(
|
| 920 |
+
rv$results$food_contributors %>%
|
| 921 |
+
filter(!is.na(fdd_ingredient)) %>%
|
| 922 |
+
mutate(across(where(is.numeric), ~ round(.x, 2))) %>%
|
| 923 |
+
head(100),
|
| 924 |
+
options = list(
|
| 925 |
+
pageLength = 25,
|
| 926 |
+
scrollX = TRUE,
|
| 927 |
+
scrollY = "400px",
|
| 928 |
+
dom = 'Bfrtip'
|
| 929 |
+
),
|
| 930 |
+
class = "display compact stripe",
|
| 931 |
+
rownames = FALSE
|
| 932 |
+
)
|
| 933 |
+
})
|
| 934 |
+
|
| 935 |
+
output$dii_content <- renderUI({
|
| 936 |
+
if (!input$calculate_dii) {
|
| 937 |
+
return(tags$div(
|
| 938 |
+
class = "alert alert-secondary text-center my-4",
|
| 939 |
+
icon("info-circle"), " DII calculation was not enabled. Enable it in the Input tab and re-run the pipeline."
|
| 940 |
+
))
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
if (!rv$analysis_complete) {
|
| 944 |
+
return(tags$div(
|
| 945 |
+
class = "alert alert-info text-center my-4",
|
| 946 |
+
icon("chart-bar"), " Run the pipeline to view DII scores."
|
| 947 |
+
))
|
| 948 |
+
}
|
| 949 |
+
|
| 950 |
+
if (is.null(rv$results$dii_by_subject)) {
|
| 951 |
+
return(tags$div(
|
| 952 |
+
class = "alert alert-warning text-center my-4",
|
| 953 |
+
icon("triangle-exclamation"), " DII scores could not be calculated. Check that your data contains the required nutrient columns."
|
| 954 |
+
))
|
| 955 |
+
}
|
| 956 |
+
|
| 957 |
+
tagList(
|
| 958 |
+
layout_columns(
|
| 959 |
+
col_widths = c(6, 6),
|
| 960 |
+
card(
|
| 961 |
+
card_header("DII Score Distribution"),
|
| 962 |
+
card_body(
|
| 963 |
+
plotlyOutput("plot_dii_distribution", height = "350px")
|
| 964 |
+
)
|
| 965 |
+
),
|
| 966 |
+
card(
|
| 967 |
+
card_header("DII Scores by Subject"),
|
| 968 |
+
card_body(
|
| 969 |
+
plotlyOutput("plot_dii_by_subject", height = "350px")
|
| 970 |
+
)
|
| 971 |
+
)
|
| 972 |
+
),
|
| 973 |
+
card(
|
| 974 |
+
card_header(
|
| 975 |
+
class = "d-flex justify-content-between align-items-center",
|
| 976 |
+
tags$span("Subject-Level DII Scores"),
|
| 977 |
+
downloadButton("download_dii", "Export CSV", class = "btn-sm btn-outline-primary")
|
| 978 |
+
),
|
| 979 |
+
card_body(
|
| 980 |
+
DTOutput("table_dii_scores")
|
| 981 |
+
)
|
| 982 |
+
),
|
| 983 |
+
tags$div(
|
| 984 |
+
class = "alert alert-info mt-3",
|
| 985 |
+
tags$strong("About the DII: "),
|
| 986 |
+
"The Dietary Inflammatory Index (DII) is a literature-derived score that assesses the inflammatory ",
|
| 987 |
+
"potential of the diet. Negative scores indicate anti-inflammatory diets, while positive scores ",
|
| 988 |
+
"indicate pro-inflammatory diets. This calculation uses 42 components including nutrients, ",
|
| 989 |
+
"polyphenol subclasses, and specific anti-inflammatory foods."
|
| 990 |
+
)
|
| 991 |
+
)
|
| 992 |
+
})
|
| 993 |
+
|
| 994 |
+
# DII Plots
|
| 995 |
+
output$plot_dii_distribution <- renderPlotly({
|
| 996 |
+
req(rv$analysis_complete, rv$results$dii_by_subject)
|
| 997 |
+
|
| 998 |
+
plot_ly(rv$results$dii_by_subject, x = ~DII_ALL_avg, type = "histogram",
|
| 999 |
+
marker = list(color = PRIMARY_COLOR, line = list(color = "white", width = 1))) %>%
|
| 1000 |
+
layout(
|
| 1001 |
+
xaxis = list(title = "Average DII Score"),
|
| 1002 |
+
yaxis = list(title = "Number of Subjects"),
|
| 1003 |
+
shapes = list(
|
| 1004 |
+
list(type = "line", x0 = 0, x1 = 0, y0 = 0, y1 = 1, yref = "paper",
|
| 1005 |
+
line = list(color = "red", dash = "dash", width = 2))
|
| 1006 |
+
)
|
| 1007 |
+
)
|
| 1008 |
+
})
|
| 1009 |
+
|
| 1010 |
+
output$plot_dii_by_subject <- renderPlotly({
|
| 1011 |
+
req(rv$analysis_complete, rv$results$dii_by_subject)
|
| 1012 |
+
|
| 1013 |
+
df <- rv$results$dii_by_subject %>%
|
| 1014 |
+
arrange(DII_ALL_avg) %>%
|
| 1015 |
+
head(30)
|
| 1016 |
+
|
| 1017 |
+
colors <- ifelse(df$DII_ALL_avg < 0, "#28a745", "#dc3545")
|
| 1018 |
+
|
| 1019 |
+
plot_ly(df, x = ~reorder(subject, DII_ALL_avg), y = ~DII_ALL_avg,
|
| 1020 |
+
type = "bar", marker = list(color = colors)) %>%
|
| 1021 |
+
layout(
|
| 1022 |
+
xaxis = list(title = "Subject", tickangle = -45),
|
| 1023 |
+
yaxis = list(title = "Average DII Score"),
|
| 1024 |
+
margin = list(b = 100)
|
| 1025 |
+
)
|
| 1026 |
+
})
|
| 1027 |
+
|
| 1028 |
+
output$table_dii_scores <- renderDT({
|
| 1029 |
+
req(rv$analysis_complete, rv$results$dii_by_subject)
|
| 1030 |
+
|
| 1031 |
+
datatable(
|
| 1032 |
+
rv$results$dii_by_subject %>%
|
| 1033 |
+
mutate(across(where(is.numeric), ~ round(.x, 3))) %>%
|
| 1034 |
+
rename(
|
| 1035 |
+
Subject = subject,
|
| 1036 |
+
`DII (All Components)` = DII_ALL_avg,
|
| 1037 |
+
`DII (No Alcohol)` = DII_NOETOH_avg,
|
| 1038 |
+
`Number of Recalls` = n_recalls
|
| 1039 |
+
),
|
| 1040 |
+
options = list(
|
| 1041 |
+
pageLength = 25,
|
| 1042 |
+
scrollX = TRUE,
|
| 1043 |
+
scrollY = "400px",
|
| 1044 |
+
dom = 'Bfrtip'
|
| 1045 |
+
),
|
| 1046 |
+
class = "display compact stripe",
|
| 1047 |
+
rownames = FALSE
|
| 1048 |
+
)
|
| 1049 |
+
})
|
| 1050 |
+
|
| 1051 |
+
output$download_dii <- downloadHandler(
|
| 1052 |
+
filename = function() paste0("dii_scores_", Sys.Date(), ".csv"),
|
| 1053 |
+
content = function(file) {
|
| 1054 |
+
write_csv(rv$results$dii_by_subject, file)
|
| 1055 |
+
}
|
| 1056 |
+
)
|
| 1057 |
+
|
| 1058 |
+
# --------------------------------------------------------------------------
|
| 1059 |
+
# QA/QC Tab
|
| 1060 |
+
# --------------------------------------------------------------------------
|
| 1061 |
+
output$unmapped_summary <- renderUI({
|
| 1062 |
+
if (is.null(rv$unmapped_foods) || length(rv$unmapped_foods) == 0) {
|
| 1063 |
+
return(tags$div(
|
| 1064 |
+
class = "alert alert-success",
|
| 1065 |
+
icon("check-circle"), " All foods were successfully mapped to FooDB."
|
| 1066 |
+
))
|
| 1067 |
+
}
|
| 1068 |
+
|
| 1069 |
+
tags$div(
|
| 1070 |
+
class = "alert alert-warning",
|
| 1071 |
+
icon("triangle-exclamation"),
|
| 1072 |
+
sprintf(" %d unique food items could not be mapped.", length(rv$unmapped_foods))
|
| 1073 |
+
)
|
| 1074 |
+
})
|
| 1075 |
+
|
| 1076 |
+
output$table_unmapped_foods <- renderDT({
|
| 1077 |
+
req(rv$unmapped_foods)
|
| 1078 |
+
|
| 1079 |
+
if (length(rv$unmapped_foods) == 0) {
|
| 1080 |
+
return(NULL)
|
| 1081 |
+
}
|
| 1082 |
+
|
| 1083 |
+
datatable(
|
| 1084 |
+
data.frame(Unmapped_Food = rv$unmapped_foods),
|
| 1085 |
+
options = list(
|
| 1086 |
+
pageLength = 25,
|
| 1087 |
+
scrollX = TRUE,
|
| 1088 |
+
scrollY = "300px"
|
| 1089 |
+
),
|
| 1090 |
+
class = "display compact stripe",
|
| 1091 |
+
rownames = FALSE
|
| 1092 |
+
)
|
| 1093 |
+
})
|
| 1094 |
+
|
| 1095 |
+
output$plot_missing_distribution <- renderPlotly({
|
| 1096 |
+
req(rv$analysis_complete, rv$results$missing_counts)
|
| 1097 |
+
|
| 1098 |
+
plot_ly(rv$results$missing_counts, x = ~pct_missing, type = "histogram",
|
| 1099 |
+
marker = list(color = "#ffc107", line = list(color = "white", width = 1))) %>%
|
| 1100 |
+
layout(
|
| 1101 |
+
xaxis = list(title = "Unmapped Foods (%)"),
|
| 1102 |
+
yaxis = list(title = "Number of Recalls")
|
| 1103 |
+
)
|
| 1104 |
+
})
|
| 1105 |
+
|
| 1106 |
+
# --------------------------------------------------------------------------
|
| 1107 |
+
# Download Handlers
|
| 1108 |
+
# --------------------------------------------------------------------------
|
| 1109 |
+
output$download_total <- downloadHandler(
|
| 1110 |
+
filename = function() paste0("polyphenol_total_intake_", Sys.Date(), ".csv"),
|
| 1111 |
+
content = function(file) {
|
| 1112 |
+
write_csv(rv$results$total_by_subject, file)
|
| 1113 |
+
}
|
| 1114 |
+
)
|
| 1115 |
+
|
| 1116 |
+
output$download_class <- downloadHandler(
|
| 1117 |
+
filename = function() paste0("polyphenol_class_intake_", Sys.Date(), ".csv"),
|
| 1118 |
+
content = function(file) {
|
| 1119 |
+
write_csv(rv$results$class_by_subject, file)
|
| 1120 |
+
}
|
| 1121 |
+
)
|
| 1122 |
+
|
| 1123 |
+
output$download_foods <- downloadHandler(
|
| 1124 |
+
filename = function() paste0("polyphenol_food_contributors_", Sys.Date(), ".csv"),
|
| 1125 |
+
content = function(file) {
|
| 1126 |
+
write_csv(rv$results$food_contributors, file)
|
| 1127 |
+
}
|
| 1128 |
+
)
|
| 1129 |
+
|
| 1130 |
+
output$download_all <- downloadHandler(
|
| 1131 |
+
filename = function() paste0("polyphenol_results_", Sys.Date(), ".zip"),
|
| 1132 |
+
content = function(file) {
|
| 1133 |
+
# Create temp directory for files
|
| 1134 |
+
temp_dir <- tempdir()
|
| 1135 |
+
on.exit(unlink(file.path(temp_dir, "*.csv")))
|
| 1136 |
+
|
| 1137 |
+
files_to_zip <- c()
|
| 1138 |
+
|
| 1139 |
+
if (!is.null(rv$results$total_by_subject)) {
|
| 1140 |
+
f <- file.path(temp_dir, "total_intake_by_subject.csv")
|
| 1141 |
+
write_csv(rv$results$total_by_subject, f)
|
| 1142 |
+
files_to_zip <- c(files_to_zip, f)
|
| 1143 |
+
}
|
| 1144 |
+
if (!is.null(rv$results$total_by_recall)) {
|
| 1145 |
+
f <- file.path(temp_dir, "total_intake_by_recall.csv")
|
| 1146 |
+
write_csv(rv$results$total_by_recall, f)
|
| 1147 |
+
files_to_zip <- c(files_to_zip, f)
|
| 1148 |
+
}
|
| 1149 |
+
if (!is.null(rv$results$class_by_subject)) {
|
| 1150 |
+
f <- file.path(temp_dir, "class_intake_by_subject.csv")
|
| 1151 |
+
write_csv(rv$results$class_by_subject, f)
|
| 1152 |
+
files_to_zip <- c(files_to_zip, f)
|
| 1153 |
+
}
|
| 1154 |
+
if (!is.null(rv$results$class_by_recall)) {
|
| 1155 |
+
f <- file.path(temp_dir, "class_intake_by_recall.csv")
|
| 1156 |
+
write_csv(rv$results$class_by_recall, f)
|
| 1157 |
+
files_to_zip <- c(files_to_zip, f)
|
| 1158 |
+
}
|
| 1159 |
+
if (!is.null(rv$results$food_contributors)) {
|
| 1160 |
+
f <- file.path(temp_dir, "food_contributors.csv")
|
| 1161 |
+
write_csv(rv$results$food_contributors, f)
|
| 1162 |
+
files_to_zip <- c(files_to_zip, f)
|
| 1163 |
+
}
|
| 1164 |
+
if (!is.null(rv$unmapped_foods) && length(rv$unmapped_foods) > 0) {
|
| 1165 |
+
f <- file.path(temp_dir, "unmapped_foods.csv")
|
| 1166 |
+
write_csv(data.frame(unmapped_food = rv$unmapped_foods), f)
|
| 1167 |
+
files_to_zip <- c(files_to_zip, f)
|
| 1168 |
+
}
|
| 1169 |
+
if (!is.null(rv$results$dii_by_subject)) {
|
| 1170 |
+
f <- file.path(temp_dir, "dii_scores_by_subject.csv")
|
| 1171 |
+
write_csv(rv$results$dii_by_subject, f)
|
| 1172 |
+
files_to_zip <- c(files_to_zip, f)
|
| 1173 |
+
}
|
| 1174 |
+
if (!is.null(rv$results$dii_by_recall)) {
|
| 1175 |
+
f <- file.path(temp_dir, "dii_scores_by_recall.csv")
|
| 1176 |
+
write_csv(rv$results$dii_by_recall, f)
|
| 1177 |
+
files_to_zip <- c(files_to_zip, f)
|
| 1178 |
+
}
|
| 1179 |
+
|
| 1180 |
+
zip::zip(file, files_to_zip, mode = "cherry-pick")
|
| 1181 |
+
},
|
| 1182 |
+
contentType = "application/zip"
|
| 1183 |
+
)
|
| 1184 |
+
}
|
ui.R
ADDED
|
@@ -0,0 +1,708 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ==============================================================================
|
| 2 |
+
# Polyphenol Estimation Pipeline - Shiny Application UI
|
| 3 |
+
# ==============================================================================
|
| 4 |
+
#
|
| 5 |
+
# PIPELINE DEVELOPED BY:
|
| 6 |
+
# Stephanie M.G. Wilson
|
| 7 |
+
# University of California, Davis
|
| 8 |
+
# Contact: smgwilson@ucdavis.edu
|
| 9 |
+
# Repository: https://github.com/SWi1/polyphenol_pipeline/
|
| 10 |
+
# License: MIT
|
| 11 |
+
#
|
| 12 |
+
# SHINY APP DEVELOPED BY:
|
| 13 |
+
# Richard Stoker
|
| 14 |
+
# United States Department of Agriculture - Agricultural Research Service
|
| 15 |
+
# Contact: Richard.Stoker@usda.gov
|
| 16 |
+
# License: CC0 (Public Domain)
|
| 17 |
+
#
|
| 18 |
+
# VERSION: 0.1 Alpha
|
| 19 |
+
# DATE: November 2025
|
| 20 |
+
# ==============================================================================
|
| 21 |
+
|
| 22 |
+
# Custom theme using bslib
|
| 23 |
+
app_theme <- bs_theme(
|
| 24 |
+
version = 5,
|
| 25 |
+
primary = "#6f42c1",
|
| 26 |
+
secondary = "#5c2d91",
|
| 27 |
+
success = "#28a745",
|
| 28 |
+
info = "#17a2b8",
|
| 29 |
+
warning = "#ffc107",
|
| 30 |
+
danger = "#dc3545",
|
| 31 |
+
base_font = font_google("Lato"),
|
| 32 |
+
heading_font = font_google("Lato"),
|
| 33 |
+
font_scale = 1.0,
|
| 34 |
+
bootswatch = NULL
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
# Main UI Definition
|
| 38 |
+
ui <- page_navbar(
|
| 39 |
+
id = "main_nav",
|
| 40 |
+
title = tags$span(
|
| 41 |
+
class = "navbar-title-text",
|
| 42 |
+
"Polyphenol Estimation Pipeline"
|
| 43 |
+
),
|
| 44 |
+
theme = app_theme,
|
| 45 |
+
fillable = TRUE,
|
| 46 |
+
header = tags$head(
|
| 47 |
+
tags$link(rel = "stylesheet", type = "text/css", href = "custom.css"),
|
| 48 |
+
tags$link(rel = "preconnect", href = "https://fonts.googleapis.com"),
|
| 49 |
+
tags$link(rel = "preconnect", href = "https://fonts.gstatic.com", crossorigin = NA),
|
| 50 |
+
tags$link(href = "https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap", rel = "stylesheet")
|
| 51 |
+
),
|
| 52 |
+
footer = tags$footer(
|
| 53 |
+
class = "app-footer",
|
| 54 |
+
tags$div(
|
| 55 |
+
class = "footer-content",
|
| 56 |
+
tags$span(
|
| 57 |
+
class = "footer-main",
|
| 58 |
+
"Pipeline developed by Stephanie M.G. Wilson | ",
|
| 59 |
+
tags$a(href = "https://github.com/SWi1/polyphenol_pipeline", target = "_blank", "GitHub Repository")
|
| 60 |
+
),
|
| 61 |
+
tags$span(
|
| 62 |
+
class = "footer-secondary",
|
| 63 |
+
"Shiny app by Richard Stoker"
|
| 64 |
+
)
|
| 65 |
+
)
|
| 66 |
+
),
|
| 67 |
+
|
| 68 |
+
# --------------------------------------------------------------------------
|
| 69 |
+
# Tab 1: Get Started
|
| 70 |
+
# --------------------------------------------------------------------------
|
| 71 |
+
nav_panel(
|
| 72 |
+
title = "Get Started",
|
| 73 |
+
value = "get_started",
|
| 74 |
+
icon = icon("circle-play"),
|
| 75 |
+
class = "fade-in",
|
| 76 |
+
layout_columns(
|
| 77 |
+
col_widths = c(12),
|
| 78 |
+
|
| 79 |
+
# Main welcome card
|
| 80 |
+
card(
|
| 81 |
+
card_header(
|
| 82 |
+
class = "card-header-primary",
|
| 83 |
+
tags$h4("Welcome to the Polyphenol Estimation Pipeline", class = "mb-0 text-white")
|
| 84 |
+
),
|
| 85 |
+
card_body(
|
| 86 |
+
class = "p-4",
|
| 87 |
+
layout_columns(
|
| 88 |
+
col_widths = c(5, 7),
|
| 89 |
+
# Pipeline diagram
|
| 90 |
+
tags$div(
|
| 91 |
+
class = "text-center",
|
| 92 |
+
tags$img(
|
| 93 |
+
src = "Polyphenol_Estimation_Pipeline_Overview.png",
|
| 94 |
+
alt = "Polyphenol Estimation Pipeline Overview",
|
| 95 |
+
class = "img-fluid pipeline-diagram",
|
| 96 |
+
style = "max-width: 100%; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1);"
|
| 97 |
+
)
|
| 98 |
+
),
|
| 99 |
+
# What this app does
|
| 100 |
+
tags$div(
|
| 101 |
+
class = "getting-started-content",
|
| 102 |
+
tags$h5("What This App Does"),
|
| 103 |
+
tags$p(
|
| 104 |
+
"Polyphenols are plant-derived compounds associated with reduced risk of chronic diseases, ",
|
| 105 |
+
"but estimating intake from dietary data has traditionally required manual processing of ",
|
| 106 |
+
"multiple databases. This pipeline automates that process."
|
| 107 |
+
),
|
| 108 |
+
tags$p(
|
| 109 |
+
"The app takes your 24-hour diet recall data and:"
|
| 110 |
+
),
|
| 111 |
+
tags$ul(
|
| 112 |
+
tags$li("Disaggregates reported foods into underlying ingredients"),
|
| 113 |
+
tags$li("Maps ingredients to polyphenol content from FooDB"),
|
| 114 |
+
tags$li("Calculates intake at total, class, and compound levels"),
|
| 115 |
+
tags$li("Identifies which foods contribute most to polyphenol consumption"),
|
| 116 |
+
tags$li("Computes a 42-component Dietary Inflammatory Index (DII)")
|
| 117 |
+
),
|
| 118 |
+
tags$p(class = "text-muted small mt-3 mb-0",
|
| 119 |
+
"Results are provided in both absolute amounts (mg) and energy-standardized values (mg/1000 kcal)."
|
| 120 |
+
)
|
| 121 |
+
)
|
| 122 |
+
)
|
| 123 |
+
)
|
| 124 |
+
),
|
| 125 |
+
|
| 126 |
+
# How to use section
|
| 127 |
+
card(
|
| 128 |
+
card_header(tags$h5("How to Use This App", class = "mb-0")),
|
| 129 |
+
card_body(
|
| 130 |
+
layout_columns(
|
| 131 |
+
col_widths = c(4, 4, 4),
|
| 132 |
+
# Step 1
|
| 133 |
+
tags$div(
|
| 134 |
+
class = "text-center p-3",
|
| 135 |
+
tags$div(
|
| 136 |
+
style = "background: var(--primary-color); color: white; width: 44px; height: 44px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-weight: 700; font-size: 1.3rem; margin-bottom: 0.75rem;",
|
| 137 |
+
"1"
|
| 138 |
+
),
|
| 139 |
+
tags$h6("Upload Your Data"),
|
| 140 |
+
tags$p(class = "text-muted small",
|
| 141 |
+
"Upload your diet recall file, or click \"Load Demo Data\" to explore with sample data first."
|
| 142 |
+
)
|
| 143 |
+
),
|
| 144 |
+
# Step 2
|
| 145 |
+
tags$div(
|
| 146 |
+
class = "text-center p-3",
|
| 147 |
+
tags$div(
|
| 148 |
+
style = "background: var(--primary-color); color: white; width: 44px; height: 44px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-weight: 700; font-size: 1.3rem; margin-bottom: 0.75rem;",
|
| 149 |
+
"2"
|
| 150 |
+
),
|
| 151 |
+
tags$h6("Run the Pipeline"),
|
| 152 |
+
tags$p(class = "text-muted small",
|
| 153 |
+
"Select your data format and click \"Run Pipeline\" to process your data."
|
| 154 |
+
)
|
| 155 |
+
),
|
| 156 |
+
# Step 3
|
| 157 |
+
tags$div(
|
| 158 |
+
class = "text-center p-3",
|
| 159 |
+
tags$div(
|
| 160 |
+
style = "background: var(--primary-color); color: white; width: 44px; height: 44px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-weight: 700; font-size: 1.3rem; margin-bottom: 0.75rem;",
|
| 161 |
+
"3"
|
| 162 |
+
),
|
| 163 |
+
tags$h6("View & Export"),
|
| 164 |
+
tags$p(class = "text-muted small",
|
| 165 |
+
"Explore interactive charts and tables. Download results as CSV files."
|
| 166 |
+
)
|
| 167 |
+
)
|
| 168 |
+
),
|
| 169 |
+
tags$div(
|
| 170 |
+
class = "text-center pt-3",
|
| 171 |
+
actionButton(
|
| 172 |
+
inputId = "go_to_input",
|
| 173 |
+
label = tags$span(icon("arrow-right"), " Go to Input"),
|
| 174 |
+
class = "btn-primary btn-lg"
|
| 175 |
+
)
|
| 176 |
+
)
|
| 177 |
+
)
|
| 178 |
+
)
|
| 179 |
+
)
|
| 180 |
+
),
|
| 181 |
+
|
| 182 |
+
# --------------------------------------------------------------------------
|
| 183 |
+
# Tab 2: Input & Configuration
|
| 184 |
+
# --------------------------------------------------------------------------
|
| 185 |
+
nav_panel(
|
| 186 |
+
title = "Input",
|
| 187 |
+
value = "input",
|
| 188 |
+
icon = icon("upload"),
|
| 189 |
+
class = "fade-in",
|
| 190 |
+
layout_columns(
|
| 191 |
+
col_widths = c(4, 8),
|
| 192 |
+
card(
|
| 193 |
+
card_header(
|
| 194 |
+
class = "card-header-primary",
|
| 195 |
+
tags$h5("Configuration", class = "mb-0 text-white")
|
| 196 |
+
),
|
| 197 |
+
card_body(
|
| 198 |
+
# Data source selection with context
|
| 199 |
+
radioGroupButtons(
|
| 200 |
+
inputId = "data_source",
|
| 201 |
+
label = tags$span(icon("database"), " Data Source"),
|
| 202 |
+
choices = c("ASA24" = "ASA24", "NHANES" = "NHANES"),
|
| 203 |
+
selected = "ASA24",
|
| 204 |
+
justified = TRUE,
|
| 205 |
+
status = "primary"
|
| 206 |
+
),
|
| 207 |
+
tags$p(class = "text-muted small mt-2 mb-0",
|
| 208 |
+
"ASA24: Items File from researcher site. NHANES: Individual Foods file."
|
| 209 |
+
),
|
| 210 |
+
|
| 211 |
+
tags$hr(),
|
| 212 |
+
|
| 213 |
+
# File upload
|
| 214 |
+
tags$div(
|
| 215 |
+
class = "upload-zone",
|
| 216 |
+
fileInput(
|
| 217 |
+
inputId = "diet_file",
|
| 218 |
+
label = tags$span(icon("file-csv"), " Upload Diet Data"),
|
| 219 |
+
accept = c(".csv", ".xlsx", ".xls"),
|
| 220 |
+
placeholder = "CSV or Excel file",
|
| 221 |
+
width = "100%"
|
| 222 |
+
),
|
| 223 |
+
tags$details(
|
| 224 |
+
class = "mt-2",
|
| 225 |
+
tags$summary(class = "text-muted small", style = "cursor: pointer;", icon("circle-info"), " Data format help"),
|
| 226 |
+
tags$div(
|
| 227 |
+
class = "small text-muted mt-2 ps-2",
|
| 228 |
+
style = "font-size: 0.78rem; line-height: 1.6;",
|
| 229 |
+
tags$div(tags$strong("ASA24:"), " UserName, RecallNo, FoodCode, FoodAmt, KCAL, ..."),
|
| 230 |
+
tags$div(tags$strong("NHANES:"), " SEQN, RecallNo, DRXIFDCD, DRXIGRMS, DRXIKCAL, ..."),
|
| 231 |
+
tags$div(class = "mt-2 fst-italic", "Each participant must have 2+ recalls.")
|
| 232 |
+
)
|
| 233 |
+
)
|
| 234 |
+
),
|
| 235 |
+
|
| 236 |
+
tags$hr(),
|
| 237 |
+
|
| 238 |
+
# Demo data button
|
| 239 |
+
tags$div(
|
| 240 |
+
class = "text-center mb-3",
|
| 241 |
+
actionButton(
|
| 242 |
+
inputId = "load_demo",
|
| 243 |
+
label = tags$span(icon("flask"), " Load Demo Data"),
|
| 244 |
+
class = "btn-outline-secondary w-100"
|
| 245 |
+
),
|
| 246 |
+
tags$small(class = "text-muted d-block mt-2", "Sample ASA24 data to try the pipeline")
|
| 247 |
+
),
|
| 248 |
+
|
| 249 |
+
tags$hr(),
|
| 250 |
+
|
| 251 |
+
# DII calculation option
|
| 252 |
+
checkboxInput(
|
| 253 |
+
inputId = "calculate_dii",
|
| 254 |
+
label = "Include DII Calculation",
|
| 255 |
+
value = TRUE
|
| 256 |
+
),
|
| 257 |
+
|
| 258 |
+
tags$hr(),
|
| 259 |
+
|
| 260 |
+
# Action buttons
|
| 261 |
+
layout_columns(
|
| 262 |
+
col_widths = c(6, 6),
|
| 263 |
+
actionButton(
|
| 264 |
+
inputId = "reset_btn",
|
| 265 |
+
label = tags$span(icon("rotate-left"), " Reset"),
|
| 266 |
+
class = "btn-outline-danger w-100"
|
| 267 |
+
),
|
| 268 |
+
actionButton(
|
| 269 |
+
inputId = "run_pipeline",
|
| 270 |
+
label = tags$span(icon("play"), " Run Pipeline"),
|
| 271 |
+
class = "btn-primary btn-lg w-100 run-btn"
|
| 272 |
+
)
|
| 273 |
+
)
|
| 274 |
+
)
|
| 275 |
+
),
|
| 276 |
+
|
| 277 |
+
card(
|
| 278 |
+
card_header(
|
| 279 |
+
class = "bg-light",
|
| 280 |
+
tags$h5("Data Preview", class = "mb-0")
|
| 281 |
+
),
|
| 282 |
+
card_body(
|
| 283 |
+
uiOutput("data_status"),
|
| 284 |
+
tags$hr(),
|
| 285 |
+
tags$div(
|
| 286 |
+
class = "data-preview-container",
|
| 287 |
+
DTOutput("data_preview")
|
| 288 |
+
)
|
| 289 |
+
)
|
| 290 |
+
)
|
| 291 |
+
)
|
| 292 |
+
),
|
| 293 |
+
|
| 294 |
+
# --------------------------------------------------------------------------
|
| 295 |
+
# Tab 3: Results Dashboard
|
| 296 |
+
# --------------------------------------------------------------------------
|
| 297 |
+
nav_panel(
|
| 298 |
+
title = "Results",
|
| 299 |
+
value = "results",
|
| 300 |
+
icon = icon("chart-bar"),
|
| 301 |
+
class = "fade-in",
|
| 302 |
+
|
| 303 |
+
layout_columns(
|
| 304 |
+
col_widths = c(12),
|
| 305 |
+
|
| 306 |
+
# Summary cards row
|
| 307 |
+
uiOutput("summary_cards"),
|
| 308 |
+
|
| 309 |
+
# Main results tabs
|
| 310 |
+
navset_card_tab(
|
| 311 |
+
id = "results_tabs",
|
| 312 |
+
full_screen = TRUE,
|
| 313 |
+
|
| 314 |
+
# Total Intake Tab
|
| 315 |
+
nav_panel(
|
| 316 |
+
title = "Total Intake",
|
| 317 |
+
icon = icon("chart-simple"),
|
| 318 |
+
card(
|
| 319 |
+
card_header(
|
| 320 |
+
class = "d-flex justify-content-between align-items-center",
|
| 321 |
+
tags$span("Total Polyphenol Intake"),
|
| 322 |
+
tags$div(
|
| 323 |
+
class = "d-flex align-items-center gap-2",
|
| 324 |
+
tags$div(
|
| 325 |
+
class = "btn-group btn-group-sm view-toggle",
|
| 326 |
+
role = "group",
|
| 327 |
+
`aria-label` = "Toggle between chart and table view",
|
| 328 |
+
tags$button(type = "button", class = "btn btn-outline-primary active", id = "total_chart_btn",
|
| 329 |
+
icon("chart-bar"), " Chart"),
|
| 330 |
+
tags$button(type = "button", class = "btn btn-outline-primary", id = "total_table_btn",
|
| 331 |
+
icon("table"), " Table")
|
| 332 |
+
),
|
| 333 |
+
downloadButton("download_total", NULL, class = "btn-sm btn-outline-primary", icon = icon("download"))
|
| 334 |
+
)
|
| 335 |
+
),
|
| 336 |
+
card_body(
|
| 337 |
+
class = "results-view-container",
|
| 338 |
+
tags$div(id = "total_chart_view", class = "results-view-visible",
|
| 339 |
+
layout_columns(
|
| 340 |
+
col_widths = c(6, 6),
|
| 341 |
+
plotlyOutput("plot_total_intake", height = "400px"),
|
| 342 |
+
plotlyOutput("plot_intake_distribution", height = "400px")
|
| 343 |
+
)
|
| 344 |
+
),
|
| 345 |
+
tags$div(id = "total_table_view", class = "results-view-hidden",
|
| 346 |
+
DTOutput("table_total_intake")
|
| 347 |
+
)
|
| 348 |
+
)
|
| 349 |
+
)
|
| 350 |
+
),
|
| 351 |
+
|
| 352 |
+
# Class-Level Tab
|
| 353 |
+
nav_panel(
|
| 354 |
+
title = "By Polyphenol Class",
|
| 355 |
+
icon = icon("layer-group"),
|
| 356 |
+
card(
|
| 357 |
+
card_header(
|
| 358 |
+
class = "d-flex justify-content-between align-items-center",
|
| 359 |
+
tags$span("Polyphenol Intake by Chemical Class"),
|
| 360 |
+
tags$div(
|
| 361 |
+
class = "d-flex align-items-center gap-2",
|
| 362 |
+
tags$div(
|
| 363 |
+
class = "btn-group btn-group-sm view-toggle",
|
| 364 |
+
role = "group",
|
| 365 |
+
`aria-label` = "Toggle between chart and table view",
|
| 366 |
+
tags$button(type = "button", class = "btn btn-outline-primary active", id = "class_chart_btn",
|
| 367 |
+
icon("chart-bar"), " Chart"),
|
| 368 |
+
tags$button(type = "button", class = "btn btn-outline-primary", id = "class_table_btn",
|
| 369 |
+
icon("table"), " Table")
|
| 370 |
+
),
|
| 371 |
+
downloadButton("download_class", NULL, class = "btn-sm btn-outline-primary", icon = icon("download"))
|
| 372 |
+
)
|
| 373 |
+
),
|
| 374 |
+
card_body(
|
| 375 |
+
class = "results-view-container",
|
| 376 |
+
tags$div(id = "class_chart_view", class = "results-view-visible",
|
| 377 |
+
plotlyOutput("plot_class_intake", height = "450px")
|
| 378 |
+
),
|
| 379 |
+
tags$div(id = "class_table_view", class = "results-view-hidden",
|
| 380 |
+
DTOutput("table_class_intake")
|
| 381 |
+
)
|
| 382 |
+
)
|
| 383 |
+
)
|
| 384 |
+
),
|
| 385 |
+
|
| 386 |
+
# Food Contributors Tab
|
| 387 |
+
nav_panel(
|
| 388 |
+
title = "Food Contributors",
|
| 389 |
+
icon = icon("apple-whole"),
|
| 390 |
+
card(
|
| 391 |
+
card_header(
|
| 392 |
+
class = "d-flex justify-content-between align-items-center",
|
| 393 |
+
tags$span("Top Food Contributors to Polyphenol Intake"),
|
| 394 |
+
tags$div(
|
| 395 |
+
class = "d-flex align-items-center gap-2",
|
| 396 |
+
tags$div(
|
| 397 |
+
class = "btn-group btn-group-sm view-toggle",
|
| 398 |
+
role = "group",
|
| 399 |
+
`aria-label` = "Toggle between chart and table view",
|
| 400 |
+
tags$button(type = "button", class = "btn btn-outline-primary active", id = "food_chart_btn",
|
| 401 |
+
icon("chart-bar"), " Chart"),
|
| 402 |
+
tags$button(type = "button", class = "btn btn-outline-primary", id = "food_table_btn",
|
| 403 |
+
icon("table"), " Table")
|
| 404 |
+
),
|
| 405 |
+
downloadButton("download_foods", NULL, class = "btn-sm btn-outline-primary", icon = icon("download"))
|
| 406 |
+
)
|
| 407 |
+
),
|
| 408 |
+
card_body(
|
| 409 |
+
class = "results-view-container",
|
| 410 |
+
tags$div(id = "food_chart_view", class = "results-view-visible",
|
| 411 |
+
plotlyOutput("plot_food_treemap", height = "500px")
|
| 412 |
+
),
|
| 413 |
+
tags$div(id = "food_table_view", class = "results-view-hidden",
|
| 414 |
+
DTOutput("table_food_contributors")
|
| 415 |
+
)
|
| 416 |
+
)
|
| 417 |
+
)
|
| 418 |
+
),
|
| 419 |
+
|
| 420 |
+
# DII Tab
|
| 421 |
+
nav_panel(
|
| 422 |
+
title = "DII Scores",
|
| 423 |
+
icon = icon("fire"),
|
| 424 |
+
uiOutput("dii_content")
|
| 425 |
+
)
|
| 426 |
+
),
|
| 427 |
+
|
| 428 |
+
# Download all results
|
| 429 |
+
tags$div(
|
| 430 |
+
class = "text-center py-3",
|
| 431 |
+
downloadButton(
|
| 432 |
+
outputId = "download_all",
|
| 433 |
+
label = tags$span(icon("download"), " Download All Results"),
|
| 434 |
+
class = "btn-primary"
|
| 435 |
+
)
|
| 436 |
+
),
|
| 437 |
+
|
| 438 |
+
# JavaScript for chart/table toggles
|
| 439 |
+
tags$script(HTML("
|
| 440 |
+
$(document).ready(function() {
|
| 441 |
+
// Helper function to toggle views using CSS classes
|
| 442 |
+
function toggleView(chartBtn, tableBtn, chartView, tableView) {
|
| 443 |
+
$(chartBtn).click(function() {
|
| 444 |
+
$(chartBtn).addClass('active');
|
| 445 |
+
$(tableBtn).removeClass('active');
|
| 446 |
+
$(chartView).removeClass('results-view-hidden').addClass('results-view-visible');
|
| 447 |
+
$(tableView).removeClass('results-view-visible').addClass('results-view-hidden');
|
| 448 |
+
});
|
| 449 |
+
$(tableBtn).click(function() {
|
| 450 |
+
$(tableBtn).addClass('active');
|
| 451 |
+
$(chartBtn).removeClass('active');
|
| 452 |
+
$(chartView).removeClass('results-view-visible').addClass('results-view-hidden');
|
| 453 |
+
$(tableView).removeClass('results-view-hidden').addClass('results-view-visible');
|
| 454 |
+
// Trigger resize to help DataTables adjust columns
|
| 455 |
+
setTimeout(function() {
|
| 456 |
+
$(window).trigger('resize');
|
| 457 |
+
// Also trigger DataTables column adjustment if available
|
| 458 |
+
var table = $(tableView).find('.dataTable').DataTable();
|
| 459 |
+
if (table && table.columns) {
|
| 460 |
+
table.columns.adjust();
|
| 461 |
+
}
|
| 462 |
+
}, 50);
|
| 463 |
+
});
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
// Set up toggles for each results section
|
| 467 |
+
toggleView('#total_chart_btn', '#total_table_btn', '#total_chart_view', '#total_table_view');
|
| 468 |
+
toggleView('#class_chart_btn', '#class_table_btn', '#class_chart_view', '#class_table_view');
|
| 469 |
+
toggleView('#food_chart_btn', '#food_table_btn', '#food_chart_view', '#food_table_view');
|
| 470 |
+
});
|
| 471 |
+
"))
|
| 472 |
+
)
|
| 473 |
+
),
|
| 474 |
+
|
| 475 |
+
# --------------------------------------------------------------------------
|
| 476 |
+
# Tab 4: QA/QC
|
| 477 |
+
# --------------------------------------------------------------------------
|
| 478 |
+
nav_panel(
|
| 479 |
+
title = "QA/QC",
|
| 480 |
+
value = "qaqc",
|
| 481 |
+
icon = icon("clipboard-check"),
|
| 482 |
+
class = "fade-in",
|
| 483 |
+
layout_columns(
|
| 484 |
+
col_widths = c(12),
|
| 485 |
+
card(
|
| 486 |
+
card_header(
|
| 487 |
+
class = "d-flex justify-content-between align-items-center bg-warning text-dark",
|
| 488 |
+
tags$span(icon("triangle-exclamation"), " Unmapped Foods Report"),
|
| 489 |
+
tags$div(
|
| 490 |
+
class = "btn-group btn-group-sm view-toggle",
|
| 491 |
+
role = "group",
|
| 492 |
+
`aria-label` = "Toggle between chart and table view",
|
| 493 |
+
tags$button(type = "button", class = "btn btn-outline-dark active", id = "qaqc_chart_btn",
|
| 494 |
+
icon("chart-bar"), " Chart"),
|
| 495 |
+
tags$button(type = "button", class = "btn btn-outline-dark", id = "qaqc_table_btn",
|
| 496 |
+
icon("table"), " Table")
|
| 497 |
+
)
|
| 498 |
+
),
|
| 499 |
+
card_body(
|
| 500 |
+
tags$p(
|
| 501 |
+
class = "text-muted small",
|
| 502 |
+
"Foods that could not be mapped to FooDB are listed below. ",
|
| 503 |
+
"These items will not contribute to polyphenol estimates. Review this list to assess data quality."
|
| 504 |
+
),
|
| 505 |
+
uiOutput("unmapped_summary"),
|
| 506 |
+
tags$div(class = "results-view-container",
|
| 507 |
+
tags$div(id = "qaqc_chart_view", class = "results-view-visible",
|
| 508 |
+
plotlyOutput("plot_missing_distribution", height = "400px")
|
| 509 |
+
),
|
| 510 |
+
tags$div(id = "qaqc_table_view", class = "results-view-hidden",
|
| 511 |
+
DTOutput("table_unmapped_foods")
|
| 512 |
+
)
|
| 513 |
+
)
|
| 514 |
+
)
|
| 515 |
+
),
|
| 516 |
+
|
| 517 |
+
# JavaScript for QA/QC toggle
|
| 518 |
+
tags$script(HTML("
|
| 519 |
+
$(document).ready(function() {
|
| 520 |
+
$('#qaqc_chart_btn').click(function() {
|
| 521 |
+
$(this).addClass('active');
|
| 522 |
+
$('#qaqc_table_btn').removeClass('active');
|
| 523 |
+
$('#qaqc_chart_view').removeClass('results-view-hidden').addClass('results-view-visible');
|
| 524 |
+
$('#qaqc_table_view').removeClass('results-view-visible').addClass('results-view-hidden');
|
| 525 |
+
});
|
| 526 |
+
$('#qaqc_table_btn').click(function() {
|
| 527 |
+
$(this).addClass('active');
|
| 528 |
+
$('#qaqc_chart_btn').removeClass('active');
|
| 529 |
+
$('#qaqc_chart_view').removeClass('results-view-visible').addClass('results-view-hidden');
|
| 530 |
+
$('#qaqc_table_view').removeClass('results-view-hidden').addClass('results-view-visible');
|
| 531 |
+
setTimeout(function() {
|
| 532 |
+
$(window).trigger('resize');
|
| 533 |
+
}, 50);
|
| 534 |
+
});
|
| 535 |
+
});
|
| 536 |
+
"))
|
| 537 |
+
)
|
| 538 |
+
),
|
| 539 |
+
|
| 540 |
+
# --------------------------------------------------------------------------
|
| 541 |
+
# Tab 5: About
|
| 542 |
+
# --------------------------------------------------------------------------
|
| 543 |
+
nav_panel(
|
| 544 |
+
title = "About",
|
| 545 |
+
value = "about",
|
| 546 |
+
icon = icon("info-circle"),
|
| 547 |
+
class = "fade-in",
|
| 548 |
+
|
| 549 |
+
# About content - use card to fill available space
|
| 550 |
+
card(
|
| 551 |
+
class = "about-page-card",
|
| 552 |
+
card_body(
|
| 553 |
+
class = "about-page-content",
|
| 554 |
+
|
| 555 |
+
# Header with animated logo
|
| 556 |
+
tags$div(
|
| 557 |
+
class = "text-center mb-4",
|
| 558 |
+
tags$div(
|
| 559 |
+
class = "about-logo-container mx-auto",
|
| 560 |
+
style = "width: 180px; height: 180px; position: relative;",
|
| 561 |
+
tags$video(
|
| 562 |
+
id = "about-video",
|
| 563 |
+
autoplay = NA,
|
| 564 |
+
muted = NA,
|
| 565 |
+
playsinline = NA,
|
| 566 |
+
style = "width: 100%; height: 100%; object-fit: cover; border-radius: 50%;",
|
| 567 |
+
tags$source(src = "splash_animation.mp4", type = "video/mp4")
|
| 568 |
+
)
|
| 569 |
+
),
|
| 570 |
+
tags$h3(class = "mt-3 mb-1", style = "color: var(--primary-color);", "Polyphenol Estimation Pipeline"),
|
| 571 |
+
tags$p(class = "text-muted", "Automated dietary polyphenol intake estimation")
|
| 572 |
+
),
|
| 573 |
+
|
| 574 |
+
# About the Pipeline
|
| 575 |
+
tags$div(
|
| 576 |
+
class = "card mb-3",
|
| 577 |
+
tags$div(class = "card-body",
|
| 578 |
+
tags$h5(class = "card-title", style = "color: var(--primary-color);", "About the Pipeline"),
|
| 579 |
+
tags$p(
|
| 580 |
+
"The Polyphenol Estimation Pipeline automates the estimation of dietary polyphenol intake ",
|
| 581 |
+
"from 24-hour diet recall data. It addresses the need for standardized, reproducible methods ",
|
| 582 |
+
"to quantify polyphenol consumption in epidemiological and nutrition research."
|
| 583 |
+
),
|
| 584 |
+
|
| 585 |
+
tags$h6(class = "mt-4", "How It Works"),
|
| 586 |
+
tags$div(class = "row",
|
| 587 |
+
tags$div(class = "col-md-4",
|
| 588 |
+
tags$div(class = "p-2",
|
| 589 |
+
tags$strong("1. Food Disaggregation"),
|
| 590 |
+
tags$p(class = "small text-muted mb-0",
|
| 591 |
+
"Reported foods are broken down into ingredients using the FDA Food Disaggregation Database (FDD v3.1)."
|
| 592 |
+
)
|
| 593 |
+
)
|
| 594 |
+
),
|
| 595 |
+
tags$div(class = "col-md-4",
|
| 596 |
+
tags$div(class = "p-2",
|
| 597 |
+
tags$strong("2. FooDB Mapping"),
|
| 598 |
+
tags$p(class = "small text-muted mb-0",
|
| 599 |
+
"Each ingredient is mapped to its polyphenol content using FooDB, covering 3,000+ compounds."
|
| 600 |
+
)
|
| 601 |
+
)
|
| 602 |
+
),
|
| 603 |
+
tags$div(class = "col-md-4",
|
| 604 |
+
tags$div(class = "p-2",
|
| 605 |
+
tags$strong("3. Intake Calculation"),
|
| 606 |
+
tags$p(class = "small text-muted mb-0",
|
| 607 |
+
"Polyphenol intake calculated at total, class, and compound levels (mg and mg/1000 kcal)."
|
| 608 |
+
)
|
| 609 |
+
)
|
| 610 |
+
)
|
| 611 |
+
),
|
| 612 |
+
|
| 613 |
+
tags$h6(class = "mt-4", "Dietary Inflammatory Index (DII)"),
|
| 614 |
+
tags$p(class = "small",
|
| 615 |
+
"The pipeline calculates a 42-component DII based on Shivappa et al. (2014). This extends ",
|
| 616 |
+
"previous 28-component calculations by adding 7 polyphenol subclasses (eugenol, isoflavones, ",
|
| 617 |
+
"flavan-3-ols, flavones, anthocyanidins, flavanones, flavonols) and 7 anti-inflammatory foods ",
|
| 618 |
+
"(garlic, ginger, onion, pepper, tea, turmeric, thyme/oregano)."
|
| 619 |
+
)
|
| 620 |
+
)
|
| 621 |
+
),
|
| 622 |
+
|
| 623 |
+
# Credits
|
| 624 |
+
tags$div(
|
| 625 |
+
class = "card mb-3",
|
| 626 |
+
tags$div(class = "card-body",
|
| 627 |
+
tags$h5(class = "card-title", style = "color: var(--primary-color);", "Credits"),
|
| 628 |
+
|
| 629 |
+
tags$div(class = "row",
|
| 630 |
+
tags$div(class = "col-md-8",
|
| 631 |
+
tags$h6("Pipeline Development"),
|
| 632 |
+
tags$p(class = "mb-1",
|
| 633 |
+
tags$strong("Stephanie M.G. Wilson")
|
| 634 |
+
),
|
| 635 |
+
tags$p(class = "small text-muted mb-2",
|
| 636 |
+
"University of California, Davis"
|
| 637 |
+
),
|
| 638 |
+
tags$p(class = "small mb-3",
|
| 639 |
+
"The polyphenol estimation methodology, database mappings, and DII calculations ",
|
| 640 |
+
"were developed by Dr. Wilson as part of her research at UC Davis."
|
| 641 |
+
),
|
| 642 |
+
tags$a(
|
| 643 |
+
href = "https://github.com/SWi1/polyphenol_pipeline",
|
| 644 |
+
target = "_blank",
|
| 645 |
+
class = "btn btn-outline-primary btn-sm",
|
| 646 |
+
icon("github"), " View Pipeline Repository"
|
| 647 |
+
)
|
| 648 |
+
),
|
| 649 |
+
tags$div(class = "col-md-4 text-md-end",
|
| 650 |
+
tags$p(class = "small text-muted mb-1", "Tutorial Draft Release"),
|
| 651 |
+
tags$p(class = "small text-muted", "November 2025")
|
| 652 |
+
)
|
| 653 |
+
),
|
| 654 |
+
|
| 655 |
+
tags$hr(),
|
| 656 |
+
|
| 657 |
+
tags$h6("Web Application"),
|
| 658 |
+
tags$p(class = "mb-1", tags$strong("Richard Stoker")),
|
| 659 |
+
tags$p(class = "small text-muted mb-1",
|
| 660 |
+
"United States Department of Agriculture - Agricultural Research Service"
|
| 661 |
+
),
|
| 662 |
+
tags$p(class = "small mb-2",
|
| 663 |
+
tags$a(href = "mailto:Richard.Stoker@usda.gov", "Richard.Stoker@usda.gov")
|
| 664 |
+
),
|
| 665 |
+
tags$p(class = "small text-muted mb-0",
|
| 666 |
+
"Shiny interface developed to provide researchers with a user-friendly way to run the pipeline ",
|
| 667 |
+
"without needing to execute R scripts directly."
|
| 668 |
+
)
|
| 669 |
+
)
|
| 670 |
+
),
|
| 671 |
+
|
| 672 |
+
# Citation
|
| 673 |
+
tags$div(
|
| 674 |
+
class = "card mb-3",
|
| 675 |
+
tags$div(class = "card-body",
|
| 676 |
+
tags$h5(class = "card-title", style = "color: var(--primary-color);", "Citation"),
|
| 677 |
+
tags$p(class = "small mb-2",
|
| 678 |
+
"If you use this pipeline in your research, please cite the pipeline repository. ",
|
| 679 |
+
"For DII calculations, please also cite:"
|
| 680 |
+
),
|
| 681 |
+
tags$div(
|
| 682 |
+
class = "bg-light p-3 rounded",
|
| 683 |
+
style = "border-left: 4px solid var(--primary-color);",
|
| 684 |
+
tags$p(class = "small mb-1", tags$strong("Shivappa, N., Steck, S. E., Hurley, T. G., Hussey, J. R., & Hebert, J. R.")),
|
| 685 |
+
tags$p(class = "small mb-1", "Designing and developing a literature-derived, population-based dietary inflammatory index."),
|
| 686 |
+
tags$p(class = "small mb-1 text-muted", tags$em("Public Health Nutrition"), ", 17(8), 1689-1696. (2014)"),
|
| 687 |
+
tags$a(href = "https://doi.org/10.1017/s1368980013002115", target = "_blank", class = "small", "https://doi.org/10.1017/s1368980013002115")
|
| 688 |
+
)
|
| 689 |
+
)
|
| 690 |
+
),
|
| 691 |
+
|
| 692 |
+
# JavaScript for about page video
|
| 693 |
+
tags$script(HTML("
|
| 694 |
+
$(document).ready(function() {
|
| 695 |
+
var aboutVideo = document.getElementById('about-video');
|
| 696 |
+
if (aboutVideo) {
|
| 697 |
+
aboutVideo.addEventListener('timeupdate', function() {
|
| 698 |
+
if (aboutVideo.duration && aboutVideo.currentTime >= aboutVideo.duration - 1.0) {
|
| 699 |
+
aboutVideo.pause();
|
| 700 |
+
}
|
| 701 |
+
});
|
| 702 |
+
}
|
| 703 |
+
});
|
| 704 |
+
"))
|
| 705 |
+
)
|
| 706 |
+
)
|
| 707 |
+
)
|
| 708 |
+
)
|
www/PEPlogo_720x720.jpeg
ADDED
|
Git LFS Details
|
www/Polyphenol_Estimation_Pipeline_Overview.png
ADDED
|
Git LFS Details
|
www/custom.css
ADDED
|
@@ -0,0 +1,748 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* ==============================================================================
|
| 2 |
+
Polyphenol Estimation Pipeline - Shiny App Stylesheet
|
| 3 |
+
Pipeline: Stephanie M.G. Wilson | App: Richard Stoker
|
| 4 |
+
Version: Alpha 0.1
|
| 5 |
+
============================================================================== */
|
| 6 |
+
|
| 7 |
+
/* Variables */
|
| 8 |
+
:root {
|
| 9 |
+
--primary-color: #6f42c1;
|
| 10 |
+
--primary-dark: #5c2d91;
|
| 11 |
+
--primary-light: #8b5cf6;
|
| 12 |
+
--secondary-color: #495057;
|
| 13 |
+
--success-color: #28a745;
|
| 14 |
+
--warning-color: #ffc107;
|
| 15 |
+
--danger-color: #dc3545;
|
| 16 |
+
--info-color: #17a2b8;
|
| 17 |
+
--light-bg: #f8f9fa;
|
| 18 |
+
--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
| 19 |
+
--card-shadow-hover: 0 4px 16px rgba(0, 0, 0, 0.12);
|
| 20 |
+
--transition-speed: 0.3s;
|
| 21 |
+
--border-radius: 8px;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
/* Base */
|
| 25 |
+
body {
|
| 26 |
+
font-family: 'Lato', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
| 27 |
+
background-color: var(--light-bg);
|
| 28 |
+
color: #333;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
/* Animations */
|
| 32 |
+
@keyframes fadeIn {
|
| 33 |
+
from { opacity: 0; transform: translateY(10px); }
|
| 34 |
+
to { opacity: 1; transform: translateY(0); }
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.fade-in {
|
| 38 |
+
animation: fadeIn 0.4s ease-out;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
/* Navigation - improved readability */
|
| 42 |
+
.navbar {
|
| 43 |
+
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
|
| 44 |
+
box-shadow: 0 2px 10px rgba(111, 66, 193, 0.3);
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.navbar-brand,
|
| 48 |
+
.navbar-title-text,
|
| 49 |
+
.navbar .navbar-brand {
|
| 50 |
+
font-weight: 700;
|
| 51 |
+
letter-spacing: -0.5px;
|
| 52 |
+
color: #ffffff !important;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
/* Navbar navigation links - white text on purple background */
|
| 56 |
+
.navbar .nav-link {
|
| 57 |
+
transition: all var(--transition-speed) ease;
|
| 58 |
+
border-radius: var(--border-radius);
|
| 59 |
+
margin: 0 4px;
|
| 60 |
+
color: rgba(255, 255, 255, 0.9) !important;
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
.navbar .nav-link:hover {
|
| 64 |
+
background-color: rgba(255, 255, 255, 0.15);
|
| 65 |
+
color: #ffffff !important;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.navbar .nav-link.active {
|
| 69 |
+
background-color: rgba(255, 255, 255, 0.25);
|
| 70 |
+
font-weight: 600;
|
| 71 |
+
color: #ffffff !important;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
/* Ensure nav item icons and text are inline */
|
| 76 |
+
.navbar .nav-link {
|
| 77 |
+
display: inline-flex !important;
|
| 78 |
+
align-items: center !important;
|
| 79 |
+
gap: 0.35rem;
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
.navbar .nav-link .fa,
|
| 83 |
+
.navbar .nav-link .fas,
|
| 84 |
+
.navbar .nav-link .far,
|
| 85 |
+
.navbar .nav-link i {
|
| 86 |
+
display: inline-block;
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
/* Card tabs (Results page) - dark text on light background */
|
| 90 |
+
.card .nav-tabs {
|
| 91 |
+
border-bottom: 2px solid #e9ecef;
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
.card .nav-tabs .nav-link {
|
| 95 |
+
color: var(--secondary-color);
|
| 96 |
+
background-color: transparent;
|
| 97 |
+
border: none;
|
| 98 |
+
border-bottom: 3px solid transparent;
|
| 99 |
+
margin-bottom: -2px;
|
| 100 |
+
padding: 0.75rem 1.25rem;
|
| 101 |
+
font-weight: 500;
|
| 102 |
+
transition: all var(--transition-speed) ease;
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
.card .nav-tabs .nav-link:hover {
|
| 106 |
+
color: var(--primary-color);
|
| 107 |
+
border-bottom-color: rgba(111, 66, 193, 0.3);
|
| 108 |
+
background-color: transparent;
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
.card .nav-tabs .nav-link.active {
|
| 112 |
+
color: var(--primary-color);
|
| 113 |
+
background-color: transparent;
|
| 114 |
+
border-bottom-color: var(--primary-color);
|
| 115 |
+
font-weight: 600;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
/* bslib navset card tabs */
|
| 119 |
+
.bslib-card .nav-link,
|
| 120 |
+
.navset-card-tab .nav-link {
|
| 121 |
+
color: var(--secondary-color) !important;
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
.bslib-card .nav-link:hover,
|
| 125 |
+
.navset-card-tab .nav-link:hover {
|
| 126 |
+
color: var(--primary-color) !important;
|
| 127 |
+
background-color: rgba(111, 66, 193, 0.05);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
.bslib-card .nav-link.active,
|
| 131 |
+
.navset-card-tab .nav-link.active {
|
| 132 |
+
color: var(--primary-color) !important;
|
| 133 |
+
font-weight: 600;
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
/* Cards */
|
| 137 |
+
.card {
|
| 138 |
+
border: none;
|
| 139 |
+
border-radius: var(--border-radius);
|
| 140 |
+
box-shadow: var(--card-shadow);
|
| 141 |
+
transition: box-shadow var(--transition-speed) ease;
|
| 142 |
+
animation: fadeIn 0.4s ease-out;
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
.card:hover {
|
| 146 |
+
box-shadow: var(--card-shadow-hover);
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
.card-header {
|
| 150 |
+
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
| 151 |
+
background-color: transparent;
|
| 152 |
+
padding: 1rem 1.25rem;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
/* Primary card header with white text */
|
| 156 |
+
.card-header-primary,
|
| 157 |
+
.card-header.bg-primary {
|
| 158 |
+
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%) !important;
|
| 159 |
+
color: #ffffff !important;
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
.card-header-primary h4,
|
| 163 |
+
.card-header-primary h5,
|
| 164 |
+
.card-header-primary .text-white {
|
| 165 |
+
color: #ffffff !important;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.card-body {
|
| 169 |
+
padding: 1.25rem;
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
/* Value Boxes */
|
| 173 |
+
.bslib-value-box {
|
| 174 |
+
border-radius: var(--border-radius);
|
| 175 |
+
transition: transform var(--transition-speed) ease, box-shadow var(--transition-speed) ease;
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
.bslib-value-box:hover {
|
| 179 |
+
transform: translateY(-2px);
|
| 180 |
+
box-shadow: var(--card-shadow-hover);
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
/* Buttons */
|
| 184 |
+
.btn {
|
| 185 |
+
border-radius: 6px;
|
| 186 |
+
font-weight: 500;
|
| 187 |
+
transition: all var(--transition-speed) ease;
|
| 188 |
+
padding: 0.5rem 1.25rem;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
.btn-primary {
|
| 192 |
+
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
|
| 193 |
+
border: none;
|
| 194 |
+
color: #ffffff;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.btn-primary:hover {
|
| 198 |
+
background: linear-gradient(135deg, var(--primary-dark) 0%, #4a2578 100%);
|
| 199 |
+
transform: translateY(-1px);
|
| 200 |
+
box-shadow: 0 4px 12px rgba(111, 66, 193, 0.35);
|
| 201 |
+
color: #ffffff;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
.btn-outline-primary {
|
| 205 |
+
color: var(--primary-color);
|
| 206 |
+
border-color: var(--primary-color);
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
.btn-outline-primary:hover {
|
| 210 |
+
background-color: var(--primary-color);
|
| 211 |
+
border-color: var(--primary-color);
|
| 212 |
+
color: #ffffff;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
.btn-lg {
|
| 216 |
+
padding: 0.75rem 2rem;
|
| 217 |
+
font-size: 1.1rem;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
.run-btn {
|
| 221 |
+
position: relative;
|
| 222 |
+
overflow: hidden;
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
/* Splash Screen */
|
| 226 |
+
.modal-content {
|
| 227 |
+
border: none;
|
| 228 |
+
border-radius: 16px;
|
| 229 |
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.25);
|
| 230 |
+
overflow: hidden;
|
| 231 |
+
}
|
| 232 |
+
|
| 233 |
+
.modal-dialog {
|
| 234 |
+
max-width: 540px;
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
.splash-content {
|
| 238 |
+
padding: 2rem 2rem 1.5rem;
|
| 239 |
+
background: #ffffff;
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
/* Logo container - holds both video and static image */
|
| 243 |
+
.splash-logo-container {
|
| 244 |
+
position: relative;
|
| 245 |
+
width: 380px;
|
| 246 |
+
height: 380px;
|
| 247 |
+
margin: 0 auto 1rem;
|
| 248 |
+
overflow: hidden;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
/* Video element - no border-radius so square video blends with white background */
|
| 252 |
+
.splash-video {
|
| 253 |
+
position: absolute;
|
| 254 |
+
top: 0;
|
| 255 |
+
left: 0;
|
| 256 |
+
width: 100%;
|
| 257 |
+
height: 100%;
|
| 258 |
+
object-fit: cover;
|
| 259 |
+
transition: opacity 0.5s ease-out;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
.splash-video.fade-out {
|
| 263 |
+
opacity: 0;
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
/* Static logo image - hidden by default, shown only if video fails or after video stops */
|
| 267 |
+
.splash-logo {
|
| 268 |
+
position: absolute;
|
| 269 |
+
top: 0;
|
| 270 |
+
left: 0;
|
| 271 |
+
width: 100%;
|
| 272 |
+
height: 100%;
|
| 273 |
+
object-fit: cover;
|
| 274 |
+
opacity: 0;
|
| 275 |
+
visibility: hidden;
|
| 276 |
+
transition: opacity 0.5s ease-in, visibility 0.5s ease-in;
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
.splash-logo.hidden {
|
| 280 |
+
display: none !important;
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
+
.splash-logo.fade-in {
|
| 284 |
+
opacity: 1;
|
| 285 |
+
visibility: visible;
|
| 286 |
+
display: block !important;
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
/* Attribution info section */
|
| 290 |
+
.splash-info {
|
| 291 |
+
margin-top: 0.5rem;
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
.splash-attribution {
|
| 295 |
+
margin: 0;
|
| 296 |
+
line-height: 1.8;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
.attribution-label {
|
| 300 |
+
font-size: 0.85rem;
|
| 301 |
+
font-weight: 500;
|
| 302 |
+
color: #6c757d;
|
| 303 |
+
text-transform: uppercase;
|
| 304 |
+
letter-spacing: 0.5px;
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
.developer-name {
|
| 308 |
+
font-size: 1.4rem;
|
| 309 |
+
font-weight: 700;
|
| 310 |
+
color: var(--primary-color);
|
| 311 |
+
margin-top: 0.25rem;
|
| 312 |
+
display: inline-block;
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
.developer-affiliation {
|
| 316 |
+
font-size: 0.95rem;
|
| 317 |
+
color: var(--secondary-color);
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
.repo-link {
|
| 321 |
+
display: inline-block;
|
| 322 |
+
margin-top: 0.5rem;
|
| 323 |
+
font-size: 0.9rem;
|
| 324 |
+
color: var(--primary-color);
|
| 325 |
+
text-decoration: none;
|
| 326 |
+
border-bottom: 1px solid transparent;
|
| 327 |
+
transition: all var(--transition-speed) ease;
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
.repo-link:hover {
|
| 331 |
+
color: var(--primary-dark);
|
| 332 |
+
border-bottom-color: var(--primary-dark);
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
/* Get Started button */
|
| 336 |
+
.splash-buttons {
|
| 337 |
+
margin-top: 1.75rem;
|
| 338 |
+
padding-top: 1.25rem;
|
| 339 |
+
border-top: 1px solid #e9ecef;
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
.splash-btn {
|
| 343 |
+
min-width: 180px;
|
| 344 |
+
padding: 0.75rem 2rem;
|
| 345 |
+
font-size: 1.1rem;
|
| 346 |
+
font-weight: 600;
|
| 347 |
+
letter-spacing: 0.25px;
|
| 348 |
+
border-radius: 8px;
|
| 349 |
+
box-shadow: 0 4px 14px rgba(111, 66, 193, 0.3);
|
| 350 |
+
transition: all var(--transition-speed) ease;
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
.splash-btn:hover {
|
| 354 |
+
transform: translateY(-2px);
|
| 355 |
+
box-shadow: 0 6px 20px rgba(111, 66, 193, 0.4);
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
/* Responsive adjustments */
|
| 359 |
+
@media (max-width: 580px) {
|
| 360 |
+
.modal-dialog {
|
| 361 |
+
max-width: 95%;
|
| 362 |
+
margin: 0.5rem auto;
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
.splash-content {
|
| 366 |
+
padding: 1rem 1rem;
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
.splash-logo-container {
|
| 370 |
+
width: 280px;
|
| 371 |
+
height: 280px;
|
| 372 |
+
}
|
| 373 |
+
|
| 374 |
+
.developer-name {
|
| 375 |
+
font-size: 1.2rem;
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
.splash-btn {
|
| 379 |
+
min-width: 150px;
|
| 380 |
+
padding: 0.6rem 1.5rem;
|
| 381 |
+
font-size: 1rem;
|
| 382 |
+
}
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
/* File Upload */
|
| 386 |
+
.upload-zone {
|
| 387 |
+
background-color: var(--light-bg);
|
| 388 |
+
border: 2px dashed #dee2e6;
|
| 389 |
+
border-radius: var(--border-radius);
|
| 390 |
+
padding: 1rem;
|
| 391 |
+
transition: all var(--transition-speed) ease;
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
.upload-zone:hover {
|
| 395 |
+
border-color: var(--primary-color);
|
| 396 |
+
background-color: rgba(111, 66, 193, 0.05);
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
/* DataTables - improved display */
|
| 400 |
+
.dataTables_wrapper {
|
| 401 |
+
padding: 0.5rem 0;
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
table.dataTable {
|
| 405 |
+
border-collapse: collapse !important;
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
table.dataTable thead th {
|
| 409 |
+
background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-dark) 100%);
|
| 410 |
+
color: white !important;
|
| 411 |
+
font-weight: 600;
|
| 412 |
+
border-bottom: none !important;
|
| 413 |
+
padding: 12px 15px;
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
table.dataTable tbody tr {
|
| 417 |
+
transition: background-color var(--transition-speed) ease;
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
table.dataTable tbody tr:hover {
|
| 421 |
+
background-color: rgba(111, 66, 193, 0.08) !important;
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
table.dataTable.stripe tbody tr.odd {
|
| 425 |
+
background-color: rgba(0, 0, 0, 0.02);
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
/* About Section */
|
| 429 |
+
.about-section {
|
| 430 |
+
padding: 1rem;
|
| 431 |
+
}
|
| 432 |
+
|
| 433 |
+
.about-section h5 {
|
| 434 |
+
color: var(--primary-color);
|
| 435 |
+
font-weight: 700;
|
| 436 |
+
margin-bottom: 1rem;
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
.about-section h6 {
|
| 440 |
+
color: var(--primary-dark);
|
| 441 |
+
font-weight: 600;
|
| 442 |
+
margin-top: 1.5rem;
|
| 443 |
+
margin-bottom: 0.75rem;
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
.about-section ul, .about-section ol {
|
| 447 |
+
padding-left: 1.5rem;
|
| 448 |
+
}
|
| 449 |
+
|
| 450 |
+
.about-section li {
|
| 451 |
+
margin-bottom: 0.5rem;
|
| 452 |
+
line-height: 1.6;
|
| 453 |
+
}
|
| 454 |
+
|
| 455 |
+
/* Pipeline Diagram */
|
| 456 |
+
.pipeline-diagram {
|
| 457 |
+
transition: transform var(--transition-speed) ease;
|
| 458 |
+
}
|
| 459 |
+
|
| 460 |
+
.pipeline-diagram:hover {
|
| 461 |
+
transform: scale(1.02);
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
/* Alerts */
|
| 465 |
+
.alert {
|
| 466 |
+
border: none;
|
| 467 |
+
border-radius: var(--border-radius);
|
| 468 |
+
padding: 1rem 1.25rem;
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
.alert-info {
|
| 472 |
+
background-color: rgba(23, 162, 184, 0.1);
|
| 473 |
+
color: #0c5460;
|
| 474 |
+
}
|
| 475 |
+
|
| 476 |
+
.alert-success {
|
| 477 |
+
background-color: rgba(40, 167, 69, 0.1);
|
| 478 |
+
color: #155724;
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
.alert-warning {
|
| 482 |
+
background-color: rgba(255, 193, 7, 0.15);
|
| 483 |
+
color: #856404;
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
.alert-danger {
|
| 487 |
+
background-color: rgba(220, 53, 69, 0.1);
|
| 488 |
+
color: #721c24;
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
/* Footer */
|
| 492 |
+
.app-footer {
|
| 493 |
+
background-color: #fff;
|
| 494 |
+
border-top: 1px solid #e9ecef;
|
| 495 |
+
padding: 1rem 2rem;
|
| 496 |
+
margin-top: auto;
|
| 497 |
+
}
|
| 498 |
+
|
| 499 |
+
.footer-content {
|
| 500 |
+
display: flex;
|
| 501 |
+
justify-content: space-between;
|
| 502 |
+
align-items: center;
|
| 503 |
+
flex-wrap: wrap;
|
| 504 |
+
gap: 0.5rem;
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
.footer-main {
|
| 508 |
+
color: var(--secondary-color);
|
| 509 |
+
font-size: 0.9rem;
|
| 510 |
+
}
|
| 511 |
+
|
| 512 |
+
.footer-main a {
|
| 513 |
+
color: var(--primary-color);
|
| 514 |
+
text-decoration: none;
|
| 515 |
+
transition: color var(--transition-speed) ease;
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
.footer-main a:hover {
|
| 519 |
+
color: var(--primary-dark);
|
| 520 |
+
text-decoration: underline;
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
.footer-secondary {
|
| 524 |
+
color: #adb5bd;
|
| 525 |
+
font-size: 0.8rem;
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
/* Progress Bar */
|
| 529 |
+
.progress {
|
| 530 |
+
height: 8px;
|
| 531 |
+
border-radius: 4px;
|
| 532 |
+
background-color: #e9ecef;
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
.progress-bar {
|
| 536 |
+
background: linear-gradient(90deg, var(--primary-color) 0%, var(--primary-light) 100%);
|
| 537 |
+
border-radius: 4px;
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
/* Notifications */
|
| 541 |
+
.shiny-notification {
|
| 542 |
+
border-radius: var(--border-radius);
|
| 543 |
+
border: none;
|
| 544 |
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
| 545 |
+
}
|
| 546 |
+
|
| 547 |
+
/* Tab Content */
|
| 548 |
+
.tab-content {
|
| 549 |
+
animation: fadeIn 0.3s ease-out;
|
| 550 |
+
}
|
| 551 |
+
|
| 552 |
+
.tab-pane {
|
| 553 |
+
padding-top: 1rem;
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
/* Checkbox styling */
|
| 557 |
+
.form-check-input:checked {
|
| 558 |
+
background-color: var(--primary-color);
|
| 559 |
+
border-color: var(--primary-color);
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
+
/* Radio Group */
|
| 563 |
+
.btn-group-container-sw {
|
| 564 |
+
margin-bottom: 0;
|
| 565 |
+
}
|
| 566 |
+
|
| 567 |
+
.btn-group > .btn.active {
|
| 568 |
+
background-color: var(--primary-color);
|
| 569 |
+
border-color: var(--primary-color);
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
/* Plotly */
|
| 573 |
+
.js-plotly-plot {
|
| 574 |
+
border-radius: var(--border-radius);
|
| 575 |
+
}
|
| 576 |
+
|
| 577 |
+
/* Data Preview */
|
| 578 |
+
.data-preview-container {
|
| 579 |
+
max-height: 400px;
|
| 580 |
+
overflow-y: auto;
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
/* Scrollbar */
|
| 584 |
+
::-webkit-scrollbar {
|
| 585 |
+
width: 8px;
|
| 586 |
+
height: 8px;
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
::-webkit-scrollbar-track {
|
| 590 |
+
background: #f1f1f1;
|
| 591 |
+
border-radius: 4px;
|
| 592 |
+
}
|
| 593 |
+
|
| 594 |
+
::-webkit-scrollbar-thumb {
|
| 595 |
+
background: #c1c1c1;
|
| 596 |
+
border-radius: 4px;
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
::-webkit-scrollbar-thumb:hover {
|
| 600 |
+
background: #a8a8a8;
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
/* About Page - scrollable card that fills space */
|
| 604 |
+
.about-page-wrapper {
|
| 605 |
+
height: 100%;
|
| 606 |
+
display: flex;
|
| 607 |
+
flex-direction: column;
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
.about-page-card {
|
| 611 |
+
flex: 1;
|
| 612 |
+
overflow-y: auto;
|
| 613 |
+
border: none !important;
|
| 614 |
+
scrollbar-width: thin;
|
| 615 |
+
scrollbar-color: transparent transparent;
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
.about-page-card:hover {
|
| 619 |
+
scrollbar-color: rgba(150, 150, 150, 0.4) transparent;
|
| 620 |
+
}
|
| 621 |
+
|
| 622 |
+
.about-page-card::-webkit-scrollbar {
|
| 623 |
+
width: 6px;
|
| 624 |
+
}
|
| 625 |
+
|
| 626 |
+
.about-page-card::-webkit-scrollbar-track {
|
| 627 |
+
background: transparent;
|
| 628 |
+
}
|
| 629 |
+
|
| 630 |
+
.about-page-card::-webkit-scrollbar-thumb {
|
| 631 |
+
background: transparent;
|
| 632 |
+
border-radius: 3px;
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
.about-page-card:hover::-webkit-scrollbar-thumb {
|
| 636 |
+
background: rgba(150, 150, 150, 0.4);
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
.about-page-content {
|
| 640 |
+
max-width: 900px;
|
| 641 |
+
margin: 0 auto;
|
| 642 |
+
padding: 1.5rem;
|
| 643 |
+
padding-bottom: 2rem;
|
| 644 |
+
}
|
| 645 |
+
|
| 646 |
+
.about-page-content .card {
|
| 647 |
+
border: none;
|
| 648 |
+
box-shadow: var(--card-shadow);
|
| 649 |
+
}
|
| 650 |
+
|
| 651 |
+
.about-page-content .card-body {
|
| 652 |
+
overflow: visible;
|
| 653 |
+
}
|
| 654 |
+
|
| 655 |
+
.about-logo-container {
|
| 656 |
+
background: #ffffff;
|
| 657 |
+
border-radius: 50%;
|
| 658 |
+
box-shadow: 0 4px 20px rgba(111, 66, 193, 0.15);
|
| 659 |
+
overflow: hidden;
|
| 660 |
+
}
|
| 661 |
+
|
| 662 |
+
/* Chart/Table Toggle Button Group */
|
| 663 |
+
.view-toggle {
|
| 664 |
+
border-radius: 6px;
|
| 665 |
+
}
|
| 666 |
+
|
| 667 |
+
.view-toggle .btn {
|
| 668 |
+
padding: 0.25rem 0.6rem;
|
| 669 |
+
font-size: 0.8rem;
|
| 670 |
+
font-weight: 500;
|
| 671 |
+
}
|
| 672 |
+
|
| 673 |
+
.view-toggle .btn.active {
|
| 674 |
+
background-color: var(--primary-color);
|
| 675 |
+
border-color: var(--primary-color);
|
| 676 |
+
color: #ffffff;
|
| 677 |
+
}
|
| 678 |
+
|
| 679 |
+
.view-toggle .btn:not(.active):hover {
|
| 680 |
+
background-color: rgba(111, 66, 193, 0.1);
|
| 681 |
+
}
|
| 682 |
+
|
| 683 |
+
/* Data Preview - taller on desktop */
|
| 684 |
+
.data-preview-container {
|
| 685 |
+
min-height: 400px;
|
| 686 |
+
max-height: 550px;
|
| 687 |
+
overflow-y: auto;
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
@media (min-width: 992px) {
|
| 691 |
+
.data-preview-container {
|
| 692 |
+
min-height: 500px;
|
| 693 |
+
max-height: 650px;
|
| 694 |
+
}
|
| 695 |
+
}
|
| 696 |
+
|
| 697 |
+
/* Results view containers - use visibility for proper DataTables rendering */
|
| 698 |
+
.results-view-container {
|
| 699 |
+
position: relative;
|
| 700 |
+
}
|
| 701 |
+
|
| 702 |
+
.results-view-hidden {
|
| 703 |
+
visibility: hidden;
|
| 704 |
+
position: absolute;
|
| 705 |
+
top: 0;
|
| 706 |
+
left: 0;
|
| 707 |
+
right: 0;
|
| 708 |
+
pointer-events: none;
|
| 709 |
+
height: 0;
|
| 710 |
+
overflow: hidden;
|
| 711 |
+
}
|
| 712 |
+
|
| 713 |
+
.results-view-visible {
|
| 714 |
+
visibility: visible;
|
| 715 |
+
position: relative;
|
| 716 |
+
pointer-events: auto;
|
| 717 |
+
}
|
| 718 |
+
|
| 719 |
+
/* Responsive */
|
| 720 |
+
@media (max-width: 768px) {
|
| 721 |
+
.splash-title {
|
| 722 |
+
font-size: 1.8rem;
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
.splash-buttons .btn {
|
| 726 |
+
display: block;
|
| 727 |
+
width: 100%;
|
| 728 |
+
margin: 0.5rem 0;
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
.footer-content {
|
| 732 |
+
flex-direction: column;
|
| 733 |
+
text-align: center;
|
| 734 |
+
}
|
| 735 |
+
|
| 736 |
+
.footer-secondary {
|
| 737 |
+
margin-top: 0.5rem;
|
| 738 |
+
}
|
| 739 |
+
|
| 740 |
+
.about-page-content {
|
| 741 |
+
padding: 1rem;
|
| 742 |
+
}
|
| 743 |
+
|
| 744 |
+
.about-logo-container {
|
| 745 |
+
width: 140px !important;
|
| 746 |
+
height: 140px !important;
|
| 747 |
+
}
|
| 748 |
+
}
|
www/splash_animation.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:250f4294a2e232df9e80a634bd9da079e9e042637197d5e69c6f9abd8e6cb5c8
|
| 3 |
+
size 256212
|