richtext commited on
Commit
7e54fbe
·
1 Parent(s): 3556056

Initial release - 0.1 Alpha

Browse files

Polyphenol 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 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 Pipeline Shinyapp
3
- emoji: 👀
4
- colorFrom: yellow
5
- colorTo: purple
6
  sdk: docker
7
  pinned: false
8
  license: cc0-1.0
9
  ---
10
 
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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

  • SHA256: 948cae5040324b5eb6185a4f37591ee418447138bd178ca5e858ce9ca3c58b70
  • Pointer size: 130 Bytes
  • Size of remote file: 82.2 kB
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

  • SHA256: 948cae5040324b5eb6185a4f37591ee418447138bd178ca5e858ce9ca3c58b70
  • Pointer size: 130 Bytes
  • Size of remote file: 82.2 kB
www/Polyphenol_Estimation_Pipeline_Overview.png ADDED

Git LFS Details

  • SHA256: dc520a4d986bce88e420181fe3f9688a495401975afee2a578c2025bbb7bd236
  • Pointer size: 131 Bytes
  • Size of remote file: 146 kB
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