Duplicate from plotly/dash-app-template
Browse filesCo-authored-by: Nathan Drezner <[email protected]>
- .github/workflows/spaces_publish.yml +21 -0
- .gitignore +291 -0
- Dockerfile +16 -0
- README.md +93 -0
- app.py +332 -0
- assets/styles.css +107 -0
- requirements.txt +6 -0
.github/workflows/spaces_publish.yml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Sync to Hugging Face hub
|
| 2 |
+
on:
|
| 3 |
+
push:
|
| 4 |
+
branches: [main]
|
| 5 |
+
|
| 6 |
+
# to run this workflow manually from the Actions tab
|
| 7 |
+
workflow_dispatch:
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
jobs:
|
| 11 |
+
sync-to-hub:
|
| 12 |
+
runs-on: ubuntu-latest
|
| 13 |
+
steps:
|
| 14 |
+
- uses: actions/checkout@v3
|
| 15 |
+
with:
|
| 16 |
+
fetch-depth: 0
|
| 17 |
+
lfs: true
|
| 18 |
+
- name: Push to hub
|
| 19 |
+
env:
|
| 20 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 21 |
+
run: git push https://plotly:[email protected]/spaces/plotly/dash-app-template main
|
.gitignore
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.DS_Store
|
| 2 |
+
.vscode/
|
| 3 |
+
|
| 4 |
+
# Build artifacts
|
| 5 |
+
dash_ag_grid/*
|
| 6 |
+
!dash_ag_grid/__init__.py
|
| 7 |
+
R/
|
| 8 |
+
deps/
|
| 9 |
+
man/
|
| 10 |
+
inst/
|
| 11 |
+
DESCRIPTION
|
| 12 |
+
NAMESPACE
|
| 13 |
+
Project.toml
|
| 14 |
+
src/*.jl
|
| 15 |
+
src/jl/*.jl
|
| 16 |
+
lib/
|
| 17 |
+
|
| 18 |
+
# Created by .ignore support plugin (hsz.mobi)
|
| 19 |
+
### VisualStudioCode template
|
| 20 |
+
.vscode/*
|
| 21 |
+
!.vscode/settings.json
|
| 22 |
+
!.vscode/tasks.json
|
| 23 |
+
!.vscode/launch.json
|
| 24 |
+
!.vscode/extensions.json
|
| 25 |
+
### JetBrains template
|
| 26 |
+
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
| 27 |
+
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
| 28 |
+
|
| 29 |
+
# User-specific stuff
|
| 30 |
+
.idea/**/workspace.xml
|
| 31 |
+
.idea/**/tasks.xml
|
| 32 |
+
.idea/**/usage.statistics.xml
|
| 33 |
+
.idea/**/dictionaries
|
| 34 |
+
.idea/**/shelf
|
| 35 |
+
|
| 36 |
+
# Sensitive or high-churn files
|
| 37 |
+
.idea/**/dataSources/
|
| 38 |
+
.idea/**/dataSources.ids
|
| 39 |
+
.idea/**/dataSources.local.xml
|
| 40 |
+
.idea/**/sqlDataSources.xml
|
| 41 |
+
.idea/**/dynamic.xml
|
| 42 |
+
.idea/**/uiDesigner.xml
|
| 43 |
+
.idea/**/dbnavigator.xml
|
| 44 |
+
|
| 45 |
+
# Gradle
|
| 46 |
+
.idea/**/gradle.xml
|
| 47 |
+
.idea/**/libraries
|
| 48 |
+
|
| 49 |
+
# Gradle and Maven with auto-import
|
| 50 |
+
# When using Gradle or Maven with auto-import, you should exclude module files,
|
| 51 |
+
# since they will be recreated, and may cause churn. Uncomment if using
|
| 52 |
+
# auto-import.
|
| 53 |
+
# .idea/modules.xml
|
| 54 |
+
# .idea/*.iml
|
| 55 |
+
# .idea/modules
|
| 56 |
+
|
| 57 |
+
# CMake
|
| 58 |
+
cmake-build-*/
|
| 59 |
+
|
| 60 |
+
# Mongo Explorer plugin
|
| 61 |
+
.idea/**/mongoSettings.xml
|
| 62 |
+
|
| 63 |
+
# File-based project format
|
| 64 |
+
*.iws
|
| 65 |
+
|
| 66 |
+
# IntelliJ
|
| 67 |
+
out/
|
| 68 |
+
|
| 69 |
+
# mpeltonen/sbt-idea plugin
|
| 70 |
+
.idea_modules/
|
| 71 |
+
|
| 72 |
+
# JIRA plugin
|
| 73 |
+
atlassian-ide-plugin.xml
|
| 74 |
+
|
| 75 |
+
# Cursive Clojure plugin
|
| 76 |
+
.idea/replstate.xml
|
| 77 |
+
|
| 78 |
+
# Crashlytics plugin (for Android Studio and IntelliJ)
|
| 79 |
+
com_crashlytics_export_strings.xml
|
| 80 |
+
crashlytics.properties
|
| 81 |
+
crashlytics-build.properties
|
| 82 |
+
fabric.properties
|
| 83 |
+
|
| 84 |
+
# Editor-based Rest Client
|
| 85 |
+
.idea/httpRequests
|
| 86 |
+
### Node template
|
| 87 |
+
# Logs
|
| 88 |
+
logs
|
| 89 |
+
*.log
|
| 90 |
+
npm-debug.log*
|
| 91 |
+
yarn-debug.log*
|
| 92 |
+
yarn-error.log*
|
| 93 |
+
|
| 94 |
+
# Runtime data
|
| 95 |
+
pids
|
| 96 |
+
*.pid
|
| 97 |
+
*.seed
|
| 98 |
+
*.pid.lock
|
| 99 |
+
|
| 100 |
+
# Directory for instrumented libs generated by jscoverage/JSCover
|
| 101 |
+
lib-cov
|
| 102 |
+
|
| 103 |
+
# Coverage directory used by tools like istanbul
|
| 104 |
+
coverage
|
| 105 |
+
|
| 106 |
+
# nyc test coverage
|
| 107 |
+
.nyc_output
|
| 108 |
+
|
| 109 |
+
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
| 110 |
+
.grunt
|
| 111 |
+
|
| 112 |
+
# Bower dependency directory (https://bower.io/)
|
| 113 |
+
bower_components
|
| 114 |
+
|
| 115 |
+
# node-waf configuration
|
| 116 |
+
.lock-wscript
|
| 117 |
+
|
| 118 |
+
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
| 119 |
+
build/Release
|
| 120 |
+
|
| 121 |
+
# Dependency directories
|
| 122 |
+
node_modules/
|
| 123 |
+
jspm_packages/
|
| 124 |
+
|
| 125 |
+
# TypeScript v1 declaration files
|
| 126 |
+
typings/
|
| 127 |
+
|
| 128 |
+
# Optional npm cache directory
|
| 129 |
+
.npm
|
| 130 |
+
|
| 131 |
+
# Optional eslint cache
|
| 132 |
+
.eslintcache
|
| 133 |
+
|
| 134 |
+
# Optional REPL history
|
| 135 |
+
.node_repl_history
|
| 136 |
+
|
| 137 |
+
# Output of 'npm pack'
|
| 138 |
+
*.tgz
|
| 139 |
+
|
| 140 |
+
# Yarn Integrity file
|
| 141 |
+
.yarn-integrity
|
| 142 |
+
|
| 143 |
+
# dotenv environment variables file
|
| 144 |
+
.env
|
| 145 |
+
|
| 146 |
+
# parcel-bundler cache (https://parceljs.org/)
|
| 147 |
+
.cache
|
| 148 |
+
|
| 149 |
+
# next.js build output
|
| 150 |
+
.next
|
| 151 |
+
|
| 152 |
+
# nuxt.js build output
|
| 153 |
+
.nuxt
|
| 154 |
+
|
| 155 |
+
# vuepress build output
|
| 156 |
+
.vuepress/dist
|
| 157 |
+
|
| 158 |
+
# Serverless directories
|
| 159 |
+
.serverless
|
| 160 |
+
### Python template
|
| 161 |
+
# Byte-compiled / optimized / DLL files
|
| 162 |
+
__pycache__/
|
| 163 |
+
*.py[cod]
|
| 164 |
+
*$py.class
|
| 165 |
+
|
| 166 |
+
# C extensions
|
| 167 |
+
*.so
|
| 168 |
+
|
| 169 |
+
# Distribution / packaging
|
| 170 |
+
.Python
|
| 171 |
+
build/
|
| 172 |
+
develop-eggs/
|
| 173 |
+
dist/
|
| 174 |
+
downloads/
|
| 175 |
+
eggs/
|
| 176 |
+
.eggs/
|
| 177 |
+
lib64/
|
| 178 |
+
parts/
|
| 179 |
+
sdist/
|
| 180 |
+
var/
|
| 181 |
+
wheels/
|
| 182 |
+
*.egg-info/
|
| 183 |
+
.installed.cfg
|
| 184 |
+
*.egg
|
| 185 |
+
MANIFEST
|
| 186 |
+
|
| 187 |
+
# PyInstaller
|
| 188 |
+
# Usually these files are written by a python script from a template
|
| 189 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 190 |
+
*.manifest
|
| 191 |
+
*.spec
|
| 192 |
+
|
| 193 |
+
# Installer logs
|
| 194 |
+
pip-log.txt
|
| 195 |
+
pip-delete-this-directory.txt
|
| 196 |
+
|
| 197 |
+
# Unit test / coverage reports
|
| 198 |
+
htmlcov/
|
| 199 |
+
.tox/
|
| 200 |
+
.coverage
|
| 201 |
+
.coverage.*
|
| 202 |
+
nosetests.xml
|
| 203 |
+
coverage.xml
|
| 204 |
+
*.cover
|
| 205 |
+
.hypothesis/
|
| 206 |
+
.pytest_cache/
|
| 207 |
+
|
| 208 |
+
# Translations
|
| 209 |
+
*.mo
|
| 210 |
+
*.pot
|
| 211 |
+
|
| 212 |
+
# Django stuff:
|
| 213 |
+
local_settings.py
|
| 214 |
+
db.sqlite3
|
| 215 |
+
|
| 216 |
+
# Flask stuff:
|
| 217 |
+
instance/
|
| 218 |
+
.webassets-cache
|
| 219 |
+
|
| 220 |
+
# Scrapy stuff:
|
| 221 |
+
.scrapy
|
| 222 |
+
|
| 223 |
+
# Sphinx documentation
|
| 224 |
+
docs/_build/
|
| 225 |
+
|
| 226 |
+
# PyBuilder
|
| 227 |
+
target/
|
| 228 |
+
|
| 229 |
+
# Jupyter Notebook
|
| 230 |
+
.ipynb_checkpoints
|
| 231 |
+
|
| 232 |
+
# pyenv
|
| 233 |
+
.python-version
|
| 234 |
+
|
| 235 |
+
# celery beat schedule file
|
| 236 |
+
celerybeat-schedule
|
| 237 |
+
|
| 238 |
+
# SageMath parsed files
|
| 239 |
+
*.sage.py
|
| 240 |
+
|
| 241 |
+
# Environments
|
| 242 |
+
.venv
|
| 243 |
+
env/
|
| 244 |
+
venv/
|
| 245 |
+
ENV/
|
| 246 |
+
env.bak/
|
| 247 |
+
venv.bak/
|
| 248 |
+
|
| 249 |
+
# Spyder project settings
|
| 250 |
+
.spyderproject
|
| 251 |
+
.spyproject
|
| 252 |
+
|
| 253 |
+
# Rope project settings
|
| 254 |
+
.ropeproject
|
| 255 |
+
|
| 256 |
+
# mkdocs documentation
|
| 257 |
+
/site
|
| 258 |
+
|
| 259 |
+
# mypy
|
| 260 |
+
.mypy_cache/
|
| 261 |
+
### SublimeText template
|
| 262 |
+
# Cache files for Sublime Text
|
| 263 |
+
*.tmlanguage.cache
|
| 264 |
+
*.tmPreferences.cache
|
| 265 |
+
*.stTheme.cache
|
| 266 |
+
|
| 267 |
+
# Workspace files are user-specific
|
| 268 |
+
*.sublime-workspace
|
| 269 |
+
|
| 270 |
+
# Project files should be checked into the repository, unless a significant
|
| 271 |
+
# proportion of contributors will probably not be using Sublime Text
|
| 272 |
+
# *.sublime-project
|
| 273 |
+
|
| 274 |
+
# SFTP configuration file
|
| 275 |
+
sftp-config.json
|
| 276 |
+
|
| 277 |
+
# Package control specific files
|
| 278 |
+
Package Control.last-run
|
| 279 |
+
Package Control.ca-list
|
| 280 |
+
Package Control.ca-bundle
|
| 281 |
+
Package Control.system-ca-bundle
|
| 282 |
+
Package Control.cache/
|
| 283 |
+
Package Control.ca-certs/
|
| 284 |
+
Package Control.merged-ca-bundle
|
| 285 |
+
Package Control.user-ca-bundle
|
| 286 |
+
oscrypto-ca-bundle.crt
|
| 287 |
+
bh_unicode_properties.cache
|
| 288 |
+
|
| 289 |
+
# Sublime-github package stores a github token in this file
|
| 290 |
+
# https://packagecontrol.io/packages/sublime-github
|
| 291 |
+
GitHub.sublime-settings
|
Dockerfile
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.12
|
| 2 |
+
COPY --from=ghcr.io/astral-sh/uv:0.4.20 /uv /bin/uv
|
| 3 |
+
|
| 4 |
+
RUN useradd -m -u 1000 user
|
| 5 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
| 6 |
+
ENV UV_SYSTEM_PYTHON=1
|
| 7 |
+
|
| 8 |
+
WORKDIR /app
|
| 9 |
+
|
| 10 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
| 11 |
+
RUN uv pip install -r requirements.txt
|
| 12 |
+
|
| 13 |
+
COPY --chown=user . /app
|
| 14 |
+
USER user
|
| 15 |
+
|
| 16 |
+
CMD ["gunicorn", "app:server", "--workers", "4", "--bind", "0.0.0.0:7860"]
|
README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Dash App Template
|
| 3 |
+
emoji: 📊
|
| 4 |
+
sdk: docker
|
| 5 |
+
app_file: app.py
|
| 6 |
+
pinned: true
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 10 |
+
|
| 11 |
+
# Dash on Spaces
|
| 12 |
+
|
| 13 |
+

|
| 14 |
+
|
| 15 |
+
With Dash Open Source, you can create data apps on your laptop in pure Python, no JavaScript required.
|
| 16 |
+
|
| 17 |
+
Get familiar with Dash by building a [sample app](https://dash.plotly.com/tutorial) with open source. Scale up with [Dash Enterprise](https://plotly.com/dash/) when your Dash app is ready for department or company-wide consumption. Or, launch your initiative with Dash Enterprise from the start to unlock developer productivity gains and hands-on acceleration from Plotly's team.
|
| 18 |
+
|
| 19 |
+
## Deploy Dash on Spaces
|
| 20 |
+
|
| 21 |
+
To get started with Dash on Spaces, click the button below:
|
| 22 |
+
|
| 23 |
+
<a href="http://huggingface.co/new-space?template=plotly/dash-app-template" target="_blank">
|
| 24 |
+
<img src="https://huggingface.co/datasets/huggingface/badges/resolve/main/deploy-to-spaces-lg.svg" alt="">
|
| 25 |
+
</a>
|
| 26 |
+
|
| 27 |
+
This will start building your Space using Plotly's Dash Docker template. If successful, you should see a similar application to the [Dash template app](https://huggingface.co/spaces/dash/dash-app-template).
|
| 28 |
+
|
| 29 |
+
## Customizing your Dash app
|
| 30 |
+
|
| 31 |
+
If you have never built with Dash before, we recommend getting started with our [Dash in 20 minutes tutorial](https://dash.plotly.com/tutorial).
|
| 32 |
+
|
| 33 |
+
When you create a Dash Space, you'll get a few key files to help you get started:
|
| 34 |
+
|
| 35 |
+
### 1. app.py
|
| 36 |
+
|
| 37 |
+
This is the main app file that defines the core logic of your project. Dash apps are often structured as modules, and you can optionally seperate your layout, callbacks, and data into other files, like `layout.py`, etc.
|
| 38 |
+
|
| 39 |
+
Inside of `app.py` you will see:
|
| 40 |
+
|
| 41 |
+
1. `from dash import Dash, html`
|
| 42 |
+
We import the `Dash` object to define our app, and the `html` library, which gives us building blocks to assemble our project.
|
| 43 |
+
|
| 44 |
+
2. `app = Dash()`
|
| 45 |
+
Here, we define our app. Layout, server, and callbacks are _bound_ to the `app` object.
|
| 46 |
+
|
| 47 |
+
3. `server = app.server`
|
| 48 |
+
Here, we define our server variable, which is used to run the app in production.
|
| 49 |
+
|
| 50 |
+
4. `app.layout = `
|
| 51 |
+
The starter app layout is defined as a list of Dash components, an indivdual Dash component, or a function that returns either.
|
| 52 |
+
|
| 53 |
+
The `app.layout` is your initial layout that will be updated as a single-page application by callbacks and other logic in your project.
|
| 54 |
+
|
| 55 |
+
5. `if __name__ == '__main__': app.run(debug=True)`
|
| 56 |
+
If you are running your project locally with `python app.py`, `app.run(...)` will execute and start up a development server to work on your project, with features including hot reloading, the callback graph, and more.
|
| 57 |
+
|
| 58 |
+
In production, we recommend `gunicorn`, which is a production-grade server. Debug features will not be enabled when running your project with `gunicorn`, so this line will never be reached.
|
| 59 |
+
|
| 60 |
+
### 2. Dockerfile
|
| 61 |
+
|
| 62 |
+
The Dockerfile for a Dash app is minimal since Dash has few system dependencies. The key requirements are:
|
| 63 |
+
|
| 64 |
+
- It installs the dependencies listed in `requirements.txt` (using `uv`)
|
| 65 |
+
- It creates a non-root user for security
|
| 66 |
+
- It runs the app with `gunicorn` using `gunicorn app:server --workers 4`
|
| 67 |
+
|
| 68 |
+
You may need to modify this file if your application requires additional system dependencies, permissions, or other CLI flags.
|
| 69 |
+
|
| 70 |
+
### 3. requirements.txt
|
| 71 |
+
|
| 72 |
+
The Space will automatically install dependencies listed in the `requirements.txt` file. At minimum, you must include `dash` and `gunicorn` in this file. You will want to add any other required packages your app needs.
|
| 73 |
+
|
| 74 |
+
The Dash Space template provides a basic setup that you can extend based on your needs.
|
| 75 |
+
|
| 76 |
+
## Additional Resources and Support
|
| 77 |
+
|
| 78 |
+
- [Dash documentation](https://dash.plotly.com)
|
| 79 |
+
- [Dash GitHub repository](https://github.com/plotly/dash)
|
| 80 |
+
- [Dash Community Forums](https://community.plotly.com)
|
| 81 |
+
- [Dash Enterprise](https://plotly.com/dash)
|
| 82 |
+
- [Dash template Space](https://huggingface.co/spaces/plotly/dash-app-template)
|
| 83 |
+
|
| 84 |
+
## Troubleshooting
|
| 85 |
+
|
| 86 |
+
If you encounter issues:
|
| 87 |
+
|
| 88 |
+
1. Make sure your notebook runs locally in app mode using `python app.py`
|
| 89 |
+
2. Check that all required packages are listed in `requirements.txt`
|
| 90 |
+
3. Verify the port configuration matches (7860 is the default for Spaces)
|
| 91 |
+
4. Check Space logs for any Python errors
|
| 92 |
+
|
| 93 |
+
For more help, visit the [Plotly Community Forums](https://community.plotly.com) or [open an issue](https://github.com/plotly/dash/issues).
|
app.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import dash
|
| 2 |
+
import dash_mantine_components as dmc
|
| 3 |
+
import plotly.express as px
|
| 4 |
+
from dash import Input, Output, callback, dcc, html
|
| 5 |
+
from dash_iconify import DashIconify
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
app = dash.Dash(__name__)
|
| 9 |
+
server = app.server
|
| 10 |
+
|
| 11 |
+
df = px.data.gapminder()
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def create_scatter_plot(selected_year, selected_continent=None):
|
| 15 |
+
filtered_df = df[df["year"] == selected_year]
|
| 16 |
+
|
| 17 |
+
if selected_continent and selected_continent != "All":
|
| 18 |
+
filtered_df = filtered_df[filtered_df["continent"] == selected_continent]
|
| 19 |
+
|
| 20 |
+
fig = px.scatter(
|
| 21 |
+
filtered_df,
|
| 22 |
+
x="gdpPercap",
|
| 23 |
+
y="lifeExp",
|
| 24 |
+
size="pop",
|
| 25 |
+
color="continent",
|
| 26 |
+
hover_name="country",
|
| 27 |
+
log_x=True,
|
| 28 |
+
size_max=60,
|
| 29 |
+
title=f"Life Expectancy vs GDP per Capita ({selected_year})",
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
fig.update_layout(
|
| 33 |
+
template="plotly_dark",
|
| 34 |
+
paper_bgcolor="rgba(0,0,0,0)",
|
| 35 |
+
plot_bgcolor="rgba(0,0,0,0)",
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
return fig
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def create_line_chart(selected_country):
|
| 42 |
+
country_data = df[df["country"] == selected_country]
|
| 43 |
+
fig = px.line(
|
| 44 |
+
country_data,
|
| 45 |
+
x="year",
|
| 46 |
+
y="lifeExp",
|
| 47 |
+
title=f"{selected_country} - Life Expectancy",
|
| 48 |
+
)
|
| 49 |
+
fig.update_layout(
|
| 50 |
+
template="plotly_dark",
|
| 51 |
+
paper_bgcolor="rgba(0,0,0,0)",
|
| 52 |
+
plot_bgcolor="rgba(0,0,0,0)",
|
| 53 |
+
)
|
| 54 |
+
return fig
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def create_bar_chart(selected_year):
|
| 58 |
+
year_data = df[df["year"] == selected_year]
|
| 59 |
+
continent_stats = year_data.groupby("continent")["lifeExp"].mean().reset_index()
|
| 60 |
+
fig = px.bar(
|
| 61 |
+
continent_stats,
|
| 62 |
+
x="continent",
|
| 63 |
+
y="lifeExp",
|
| 64 |
+
color="continent",
|
| 65 |
+
title=f"Average Life Expectancy by Continent ({selected_year})",
|
| 66 |
+
)
|
| 67 |
+
fig.update_layout(
|
| 68 |
+
template="plotly_dark",
|
| 69 |
+
paper_bgcolor="rgba(0,0,0,0)",
|
| 70 |
+
plot_bgcolor="rgba(0,0,0,0)",
|
| 71 |
+
showlegend=False,
|
| 72 |
+
)
|
| 73 |
+
return fig
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def create_datacard(title, value, icon, color):
|
| 77 |
+
return dmc.Card(
|
| 78 |
+
[
|
| 79 |
+
dmc.Group(
|
| 80 |
+
[
|
| 81 |
+
DashIconify(icon=icon, width=30, color=color),
|
| 82 |
+
html.Div(
|
| 83 |
+
[
|
| 84 |
+
dmc.Text(value, size="xl", fw=700, c="white"),
|
| 85 |
+
dmc.Text(title, size="sm", c="dimmed"),
|
| 86 |
+
]
|
| 87 |
+
),
|
| 88 |
+
],
|
| 89 |
+
align="center",
|
| 90 |
+
gap="md",
|
| 91 |
+
)
|
| 92 |
+
],
|
| 93 |
+
p="md",
|
| 94 |
+
className="datacard",
|
| 95 |
+
)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
app.layout = dmc.MantineProvider(
|
| 99 |
+
[
|
| 100 |
+
html.Link(
|
| 101 |
+
href="https://fonts.googleapis.com/css2?family=Outfit:[email protected]&display=swap",
|
| 102 |
+
rel="stylesheet",
|
| 103 |
+
),
|
| 104 |
+
dmc.Group(
|
| 105 |
+
[
|
| 106 |
+
DashIconify(icon="twemoji:globe-with-meridians", width=45),
|
| 107 |
+
dmc.Text(
|
| 108 |
+
"Gapminder World Data Explorer", ml=10, size="xl", fw=900, c="white"
|
| 109 |
+
),
|
| 110 |
+
],
|
| 111 |
+
align="center",
|
| 112 |
+
className="header",
|
| 113 |
+
mb="md",
|
| 114 |
+
),
|
| 115 |
+
dmc.Grid(
|
| 116 |
+
[
|
| 117 |
+
dmc.GridCol(
|
| 118 |
+
[
|
| 119 |
+
dmc.Stack(
|
| 120 |
+
[
|
| 121 |
+
dmc.Card(
|
| 122 |
+
[
|
| 123 |
+
dmc.Text("Controls", size="lg", mb="md"),
|
| 124 |
+
dmc.Stack(
|
| 125 |
+
[
|
| 126 |
+
html.Div(
|
| 127 |
+
[
|
| 128 |
+
dmc.Text(
|
| 129 |
+
"Year:", size="sm", mb=5
|
| 130 |
+
),
|
| 131 |
+
dmc.Slider(
|
| 132 |
+
id="year-slider",
|
| 133 |
+
min=1952,
|
| 134 |
+
max=2007,
|
| 135 |
+
step=5,
|
| 136 |
+
value=2007,
|
| 137 |
+
marks=[
|
| 138 |
+
{
|
| 139 |
+
"value": year,
|
| 140 |
+
"label": str(year),
|
| 141 |
+
}
|
| 142 |
+
for year in [
|
| 143 |
+
1952,
|
| 144 |
+
1967,
|
| 145 |
+
1982,
|
| 146 |
+
1997,
|
| 147 |
+
2007,
|
| 148 |
+
]
|
| 149 |
+
],
|
| 150 |
+
),
|
| 151 |
+
]
|
| 152 |
+
),
|
| 153 |
+
html.Div(
|
| 154 |
+
[
|
| 155 |
+
dmc.Text(
|
| 156 |
+
"Continent Filter:",
|
| 157 |
+
size="sm",
|
| 158 |
+
mb=5,
|
| 159 |
+
),
|
| 160 |
+
dmc.Select(
|
| 161 |
+
id="continent-dropdown",
|
| 162 |
+
data=[
|
| 163 |
+
{
|
| 164 |
+
"value": "All",
|
| 165 |
+
"label": "All Continents",
|
| 166 |
+
}
|
| 167 |
+
]
|
| 168 |
+
+ [
|
| 169 |
+
{
|
| 170 |
+
"value": cont,
|
| 171 |
+
"label": cont,
|
| 172 |
+
}
|
| 173 |
+
for cont in sorted(
|
| 174 |
+
df[
|
| 175 |
+
"continent"
|
| 176 |
+
].unique()
|
| 177 |
+
)
|
| 178 |
+
],
|
| 179 |
+
value="All",
|
| 180 |
+
),
|
| 181 |
+
]
|
| 182 |
+
),
|
| 183 |
+
html.Div(
|
| 184 |
+
[
|
| 185 |
+
dmc.Text(
|
| 186 |
+
"Select Country:",
|
| 187 |
+
size="sm",
|
| 188 |
+
mb=5,
|
| 189 |
+
),
|
| 190 |
+
dmc.Select(
|
| 191 |
+
id="country-dropdown",
|
| 192 |
+
data=[
|
| 193 |
+
{
|
| 194 |
+
"value": country,
|
| 195 |
+
"label": country,
|
| 196 |
+
}
|
| 197 |
+
for country in sorted(
|
| 198 |
+
df[
|
| 199 |
+
"country"
|
| 200 |
+
].unique()
|
| 201 |
+
)
|
| 202 |
+
],
|
| 203 |
+
value="United States",
|
| 204 |
+
searchable=True,
|
| 205 |
+
),
|
| 206 |
+
]
|
| 207 |
+
),
|
| 208 |
+
],
|
| 209 |
+
gap="lg",
|
| 210 |
+
),
|
| 211 |
+
],
|
| 212 |
+
p="md",
|
| 213 |
+
className="control-card",
|
| 214 |
+
)
|
| 215 |
+
]
|
| 216 |
+
)
|
| 217 |
+
],
|
| 218 |
+
span=3,
|
| 219 |
+
),
|
| 220 |
+
dmc.GridCol(
|
| 221 |
+
[
|
| 222 |
+
dmc.Stack(
|
| 223 |
+
[
|
| 224 |
+
html.Div(id="stats-cards"),
|
| 225 |
+
dmc.Card(
|
| 226 |
+
[dcc.Graph(id="scatter-plot")],
|
| 227 |
+
p="sm",
|
| 228 |
+
className="chart-card",
|
| 229 |
+
),
|
| 230 |
+
],
|
| 231 |
+
gap="md",
|
| 232 |
+
)
|
| 233 |
+
],
|
| 234 |
+
span=9,
|
| 235 |
+
),
|
| 236 |
+
],
|
| 237 |
+
gutter="md",
|
| 238 |
+
),
|
| 239 |
+
dmc.Grid(
|
| 240 |
+
[
|
| 241 |
+
dmc.GridCol(
|
| 242 |
+
[
|
| 243 |
+
dmc.Card(
|
| 244 |
+
[dcc.Graph(id="line-chart")], p="sm", className="chart-card"
|
| 245 |
+
)
|
| 246 |
+
],
|
| 247 |
+
span=6,
|
| 248 |
+
),
|
| 249 |
+
dmc.GridCol(
|
| 250 |
+
[
|
| 251 |
+
dmc.Card(
|
| 252 |
+
[dcc.Graph(id="bar-chart")], p="sm", className="chart-card"
|
| 253 |
+
)
|
| 254 |
+
],
|
| 255 |
+
span=6,
|
| 256 |
+
),
|
| 257 |
+
],
|
| 258 |
+
gutter="md",
|
| 259 |
+
mt="md",
|
| 260 |
+
),
|
| 261 |
+
],
|
| 262 |
+
forceColorScheme="dark",
|
| 263 |
+
theme={"colorScheme": "dark"},
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
@callback(
|
| 268 |
+
Output("scatter-plot", "figure"),
|
| 269 |
+
[Input("year-slider", "value"), Input("continent-dropdown", "value")],
|
| 270 |
+
)
|
| 271 |
+
def update_scatter_plot(selected_year, selected_continent):
|
| 272 |
+
return create_scatter_plot(selected_year, selected_continent)
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
@callback(Output("line-chart", "figure"), Input("country-dropdown", "value"))
|
| 276 |
+
def update_line_chart(selected_country):
|
| 277 |
+
return create_line_chart(selected_country)
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
@callback(Output("bar-chart", "figure"), Input("year-slider", "value"))
|
| 281 |
+
def update_bar_chart(selected_year):
|
| 282 |
+
return create_bar_chart(selected_year)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
@callback(Output("stats-cards", "children"), Input("year-slider", "value"))
|
| 286 |
+
def update_stats(selected_year):
|
| 287 |
+
year_data = df[df["year"] == selected_year]
|
| 288 |
+
|
| 289 |
+
avg_life_exp = round(year_data["lifeExp"].mean(), 1)
|
| 290 |
+
total_pop = year_data["pop"].sum()
|
| 291 |
+
num_countries = len(year_data)
|
| 292 |
+
avg_gdp = round(year_data["gdpPercap"].mean(), 0)
|
| 293 |
+
|
| 294 |
+
return dmc.Grid(
|
| 295 |
+
[
|
| 296 |
+
dmc.GridCol(
|
| 297 |
+
create_datacard(
|
| 298 |
+
"Life Expectancy",
|
| 299 |
+
f"{avg_life_exp} years",
|
| 300 |
+
"mdi:heart-pulse",
|
| 301 |
+
"#ff6b35",
|
| 302 |
+
),
|
| 303 |
+
span=3,
|
| 304 |
+
),
|
| 305 |
+
dmc.GridCol(
|
| 306 |
+
create_datacard(
|
| 307 |
+
"Population",
|
| 308 |
+
f"{total_pop / 1e9:.1f}B",
|
| 309 |
+
"mdi:account-group",
|
| 310 |
+
"#1f77b4",
|
| 311 |
+
),
|
| 312 |
+
span=3,
|
| 313 |
+
),
|
| 314 |
+
dmc.GridCol(
|
| 315 |
+
create_datacard(
|
| 316 |
+
"Countries", str(num_countries), "mdi:earth", "#2ca02c"
|
| 317 |
+
),
|
| 318 |
+
span=3,
|
| 319 |
+
),
|
| 320 |
+
dmc.GridCol(
|
| 321 |
+
create_datacard(
|
| 322 |
+
"GDP per Capita", f"${avg_gdp:,.0f}", "mdi:currency-usd", "#d62728"
|
| 323 |
+
),
|
| 324 |
+
span=3,
|
| 325 |
+
),
|
| 326 |
+
],
|
| 327 |
+
gutter="sm",
|
| 328 |
+
)
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
if __name__ == "__main__":
|
| 332 |
+
app.run(debug=True, port=8050)
|
assets/styles.css
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
font-family: 'Outfit', sans-serif;
|
| 3 |
+
background: linear-gradient(135deg, #1e1e2e 0%, #2a2a3e 100%);
|
| 4 |
+
margin: 0;
|
| 5 |
+
padding: 20px;
|
| 6 |
+
min-height: 100vh;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
.header {
|
| 10 |
+
background: linear-gradient(135deg, #ff6b35, #d43425);
|
| 11 |
+
padding: 20px 30px;
|
| 12 |
+
border-radius: 15px;
|
| 13 |
+
box-shadow: 0 8px 25px rgba(255, 107, 53, 0.3);
|
| 14 |
+
margin-bottom: 20px;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
.control-card {
|
| 18 |
+
background: rgba(15, 15, 20, 0.9);
|
| 19 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 20 |
+
backdrop-filter: blur(10px);
|
| 21 |
+
border-radius: 15px;
|
| 22 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.chart-card {
|
| 26 |
+
background: rgba(15, 15, 20, 0.9);
|
| 27 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 28 |
+
backdrop-filter: blur(10px);
|
| 29 |
+
border-radius: 15px;
|
| 30 |
+
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
.datacard {
|
| 34 |
+
background: linear-gradient(135deg, rgba(255, 107, 53, 0.1), rgba(212, 52, 37, 0.1));
|
| 35 |
+
border: 1px solid rgba(255, 107, 53, 0.3);
|
| 36 |
+
border-radius: 12px;
|
| 37 |
+
transition: all 0.3s ease;
|
| 38 |
+
backdrop-filter: blur(10px);
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.datacard:hover {
|
| 42 |
+
transform: translateY(-2px);
|
| 43 |
+
box-shadow: 0 12px 30px rgba(255, 107, 53, 0.2);
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
.year-slider .mantine-Slider-track {
|
| 47 |
+
background: rgba(255, 255, 255, 0.2);
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
.year-slider .mantine-Slider-bar {
|
| 51 |
+
background: linear-gradient(90deg, #ff6b35, #d43425);
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
.year-slider .mantine-Slider-thumb {
|
| 55 |
+
background: #ff6b35;
|
| 56 |
+
border: 2px solid white;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.continent-select .mantine-Select-input,
|
| 60 |
+
.country-select .mantine-Select-input {
|
| 61 |
+
background: rgba(255, 255, 255, 0.1);
|
| 62 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 63 |
+
color: white;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.continent-select .mantine-Select-input:focus,
|
| 67 |
+
.country-select .mantine-Select-input:focus {
|
| 68 |
+
border-color: #ff6b35;
|
| 69 |
+
box-shadow: 0 0 10px rgba(255, 107, 53, 0.3);
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
/* Custom scrollbar for dropdowns */
|
| 73 |
+
.mantine-Select-dropdown {
|
| 74 |
+
background: rgba(15, 15, 20, 0.95);
|
| 75 |
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
| 76 |
+
backdrop-filter: blur(10px);
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
.mantine-Select-item {
|
| 80 |
+
color: white;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
.mantine-Select-item:hover {
|
| 84 |
+
background: rgba(255, 107, 53, 0.2);
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
/* Graph styling adjustments */
|
| 88 |
+
.js-plotly-plot {
|
| 89 |
+
border-radius: 10px;
|
| 90 |
+
overflow: hidden;
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
/* Responsive design */
|
| 94 |
+
@media (max-width: 768px) {
|
| 95 |
+
body {
|
| 96 |
+
padding: 10px;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.header {
|
| 100 |
+
padding: 15px 20px;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
.control-card,
|
| 104 |
+
.chart-card {
|
| 105 |
+
margin: 10px 0;
|
| 106 |
+
}
|
| 107 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
dash
|
| 2 |
+
dash-mantine-components
|
| 3 |
+
dash-iconify
|
| 4 |
+
plotly
|
| 5 |
+
pandas
|
| 6 |
+
gunicorn
|