diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 36e23582c..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,112 +0,0 @@ -version: 2.1 - -jobs: - trigger-new-pipeline: - docker: - - image: cimg/base:2021.11 - resource_class: small - steps: - - run: - name: Ping another pipeline - command: | - CREATED_PIPELINE=$(curl --request POST \ - --url "$QA_PIPELINE_URL" \ - --header "Circle-Token: $CIRCLECI_API_KEY" \ - --header "content-type: application/json" \ - --data '{"branch":"<< pipeline.git.branch >>", "parameters":{"triggering-pipeline-id":"<< pipeline.id >>", "branch":"<< pipeline.git.branch >>", "vizro_branch": "<< pipeline.git.branch >>", "tag": "<< pipeline.git.tag >>"}}' \ - | jq -r '.id' - ) - echo "my created pipeline" - echo $CREATED_PIPELINE - if [[ "$CREATED_PIPELINE" == "null" ]]; then - CREATED_PIPELINE=$(curl --request POST \ - --url "$QA_PIPELINE_URL" \ - --header "Circle-Token: $CIRCLECI_API_KEY" \ - --header "content-type: application/json" \ - --data '{"branch":"main", "parameters":{"triggering-pipeline-id":"<< pipeline.id >>", "branch":"main", "vizro_branch": "<< pipeline.git.branch >>", "tag": "<< pipeline.git.tag >>"}}' \ - | jq -r '.id' - ) - fi - echo "my created pipeline" - echo $CREATED_PIPELINE - mkdir ~/workspace - echo $CREATED_PIPELINE > pipeline.txt - - persist_to_workspace: - root: . - paths: - - pipeline.txt - - check-status-of-triggered-pipeline: - docker: - - image: cimg/base:2021.11 - resource_class: small - steps: - - attach_workspace: - at: workspace - - run: - name: Check triggered workflow status - command: | - triggered_pipeline_id=$(cat workspace/pipeline.txt) - - created_workflow_status=$(curl --request GET \ - --url "https://circleci.com/api/v2/pipeline/${triggered_pipeline_id}/workflow" \ - --header "Circle-Token: $CIRCLECI_API_KEY" \ - --header "content-type: application/json" \ - | jq -r '.items[0].status' - ) - - echo $created_workflow_status - - if [[ "$created_workflow_status" == "running" ]]; then - created_workflow_status_updated=$(curl --request GET \ - --url "https://circleci.com/api/v2/pipeline/${triggered_pipeline_id}/workflow" \ - --header "Circle-Token: $CIRCLECI_API_KEY" \ - --header "content-type: application/json" \ - | jq -r '.items[0].status' - ) - while [[ "$created_workflow_status_updated" == "running" ]] - do - sleep 15 - created_workflow_status_updated=$(curl --request GET \ - --url "https://circleci.com/api/v2/pipeline/${triggered_pipeline_id}/workflow" \ - --header "Circle-Token: $CIRCLECI_API_KEY" \ - --header "content-type: application/json" \ - | jq -r '.items[0].status' - ) - echo $created_workflow_status_updated - done - - if [[ "$created_workflow_status_updated" != "success" ]]; then - echo "Workflow not successful - ${created_workflow_status_updated}" - (exit -1) - fi - - echo "Created workflow successful" - - elif [[ "$created_workflow_status" != "success" ]]; then - echo "Workflow not successful - ${created_workflow_status}" - (exit -1) - - elif [[ "$created_workflow_status" == "success" ]]; then - echo "Created workflow successful" - fi - -workflows: - version: 2 - vizro-core: - jobs: - - trigger-new-pipeline: - context: - - circleci-api - filters: - tags: - only: /(\d+\.\d+\.\d+)/ - - wait-for-triggered-pipeline: - type: approval - requires: - - trigger-new-pipeline - - check-status-of-triggered-pipeline: - requires: - - wait-for-triggered-pipeline - context: - - circleci-api diff --git a/.github/workflows/checks-vizro-ai.yml b/.github/workflows/checks-vizro-ai.yml index 1ee546121..5fd1799fa 100644 --- a/.github/workflows/checks-vizro-ai.yml +++ b/.github/workflows/checks-vizro-ai.yml @@ -26,7 +26,9 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} + # Use fetch-depth of 2 to enable the changelog fragment check, which only runs in a pull request, not on push. + # See https://stackoverflow.com/questions/74265821/get-modified-files-in-github-actions + fetch-depth: 2 - name: Set up Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v5 @@ -42,23 +44,19 @@ jobs: - name: Check requirements for Snyk are up to date run: hatch run all.py${{ env.PYTHON_VERSION }}:update-snyk-requirements --check - - name: Find added changelog fragments - id: added-files + - name: Find changed files to see if changelog fragment needed + id: changed-files + if: ${{ github.event_name == 'pull_request' }} + # --no-renames is required so that an empty changelog file added in a release PR always counts as added rather + # than a renamed version of an already-existing empty changelog file. run: | - if ${{ github.event_name == 'pull_request' }}; then - echo "added_files=$(git diff --name-only --diff-filter=A -r HEAD^1 HEAD -- changelog.d/*.md | xargs)" >> $GITHUB_OUTPUT - else - echo "added_files=$(git diff --name-only --diff-filter=A ${{ github.event.before }} ${{ github.event.after }} -- changelog.d/*.md | xargs)" >> $GITHUB_OUTPUT - fi + echo "changelog_fragment_added=$(git diff --name-only --no-renames --diff-filter=A HEAD^1 HEAD -- 'changelog.d/*.md' | xargs)" >> $GITHUB_OUTPUT + echo "files_outside_docs_changed=$(git diff --name-only HEAD^1 HEAD -- ':!docs' | xargs)" >> $GITHUB_OUTPUT - - name: Fail if no fragment added in PR + - name: Fail if changelog fragment needed and wasn't added + if: ${{ steps.changed-files.outcome != 'skipped' && steps.changed-files.outputs.files_outside_docs_changed && !steps.changed-files.outputs.changelog_fragment_added}} run: | - if [ -z "${{ steps.added-files.outputs.added_files }}" ]; - then - echo "No changelog fragment .md file within changelog.d was detected. Run 'hatch run changelog:add' to create such a fragment."; - echo "If your PR contains changes that should be mentioned in the CHANGELOG in the next release, please uncomment the relevant section in your created fragment and describe the changes to the user." - echo "If your changes are not relevant for the CHANGELOG, please save and commit the file as is." - exit 1 - else - echo "${{ steps.added-files.outputs.added_files }} was added - ready to go!"; - fi + echo "No changelog fragment .md file within changelog.d was detected. Run 'hatch run changelog:add' to create such a fragment." + echo "If your PR contains changes that should be mentioned in the CHANGELOG in the next release, please uncomment the relevant section in your created fragment and describe the changes to the user." + echo "If your changes are not relevant for the CHANGELOG, please save and commit the file as is." + exit 1 diff --git a/.github/workflows/checks-vizro-core.yml b/.github/workflows/checks-vizro-core.yml index 40929da53..f62835f40 100644 --- a/.github/workflows/checks-vizro-core.yml +++ b/.github/workflows/checks-vizro-core.yml @@ -26,7 +26,9 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} + # Use fetch-depth of 2 to enable the changelog fragment check, which only runs in a pull request, not on push. + # See https://stackoverflow.com/questions/74265821/get-modified-files-in-github-actions + fetch-depth: 2 - name: Set up Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v5 @@ -45,23 +47,19 @@ jobs: - name: Check requirements for Snyk are up to date run: hatch run all.py${{ env.PYTHON_VERSION }}:update-snyk-requirements --check - - name: Find added changelog fragments - id: added-files + - name: Find changed files to see if changelog fragment needed + id: changed-files + if: ${{ github.event_name == 'pull_request' }} + # --no-renames is required so that an empty changelog file added in a release PR always counts as added rather + # than a renamed version of an already-existing empty changelog file. run: | - if ${{ github.event_name == 'pull_request' }}; then - echo "added_files=$(git diff --name-only --diff-filter=A -r HEAD^1 HEAD -- changelog.d/*.md | xargs)" >> $GITHUB_OUTPUT - else - echo "added_files=$(git diff --name-only --diff-filter=A ${{ github.event.before }} ${{ github.event.after }} -- changelog.d/*.md | xargs)" >> $GITHUB_OUTPUT - fi + echo "changelog_fragment_added=$(git diff --name-only --no-renames --diff-filter=A HEAD^1 HEAD -- 'changelog.d/*.md' | xargs)" >> $GITHUB_OUTPUT + echo "files_outside_docs_changed=$(git diff --name-only HEAD^1 HEAD -- ':!docs' | xargs)" >> $GITHUB_OUTPUT - - name: Fail if no fragment added in PR + - name: Fail if changelog fragment needed and wasn't added + if: ${{ steps.changed-files.outcome != 'skipped' && steps.changed-files.outputs.files_outside_docs_changed && !steps.changed-files.outputs.changelog_fragment_added}} run: | - if [ -z "${{ steps.added-files.outputs.added_files }}" ]; - then - echo "No changelog fragment .md file within changelog.d was detected. Run 'hatch run changelog:add' to create such a fragment."; - echo "If your PR contains changes that should be mentioned in the CHANGELOG in the next release, please uncomment the relevant section in your created fragment and describe the changes to the user." - echo "If your changes are not relevant for the CHANGELOG, please save and commit the file as is." - exit 1 - else - echo "${{ steps.added-files.outputs.added_files }} was added - ready to go!"; - fi + echo "No changelog fragment .md file within changelog.d was detected. Run 'hatch run changelog:add' to create such a fragment." + echo "If your PR contains changes that should be mentioned in the CHANGELOG in the next release, please uncomment the relevant section in your created fragment and describe the changes to the user." + echo "If your changes are not relevant for the CHANGELOG, please save and commit the file as is." + exit 1 diff --git a/.github/workflows/circleci-trigger.yml b/.github/workflows/circleci-trigger.yml new file mode 100644 index 000000000..44100baf4 --- /dev/null +++ b/.github/workflows/circleci-trigger.yml @@ -0,0 +1,75 @@ +name: CircleCI tests trigger + +on: + push: + branches: [main] + pull_request: + branches: + - main + +env: + PYTHONUNBUFFERED: 1 + FORCE_COLOR: 1 + +jobs: + circleci-trigger-fork: + if: ${{ github.event.pull_request.head.repo.fork }} + name: CircleCI tests trigger + runs-on: ubuntu-latest + steps: + - name: Passed fork step + run: echo "Success!" + + circleci-trigger: + if: ${{ ! github.event.pull_request.head.repo.fork }} + name: CircleCI tests trigger + environment: circleci_secrets + runs-on: ubuntu-latest + steps: + - name: Start CircleCI pipeline + run: | + create_circleci_pipeline() { + local branch=$1 + + local json_data=$(jq -n --arg branch "$branch" --arg vizro_branch "${{ github.head_ref }}" '{branch: $branch, parameters: {branch: $branch, vizro_branch: $vizro_branch}}') + + curl --silent --request POST \ + --url "${{ secrets.QA_PIPELINE_URL }}" \ + --header "Circle-Token: ${{ secrets.CIRCLECI_API_KEY }}" \ + --header "content-type: application/json" \ + --data "$json_data" \ + | jq -r '.id' + } + + PIPELINE=$(create_circleci_pipeline "${{ github.head_ref }}") + + # If the above returns null then the QA repo doesn't contain current dev branch, so we use main branch. + if [[ "$PIPELINE" == "null" ]]; then + PIPELINE=$(create_circleci_pipeline "main") + fi + echo "Started pipeline with id $PIPELINE" + + echo "PIPELINE=$PIPELINE" >> $GITHUB_ENV + + - name: Wait for pipeline to run + run: sleep 60 + + - name: Check pipeline status + run: | + get_pipeline_status() { + curl --silent --request GET \ + --url "https://circleci.com/api/v2/pipeline/$PIPELINE/workflow" \ + --header "Circle-Token: ${{ secrets.CIRCLECI_API_KEY }}" \ + --header "content-type: application/json" \ + | jq -r '.items[0].status' + } + + while pipeline_status=$(get_pipeline_status); [[ "$pipeline_status" == "running" ]]; do + echo $pipeline_status + sleep 15 + done + + if [[ "$pipeline_status" != "success" ]]; then + echo "Pipeline not completed successfully - status was ${pipeline_status}" + exit 1 + fi diff --git a/.github/workflows/lint-vizro-all.yml b/.github/workflows/lint-vizro-all.yml index f38edf224..340784c53 100644 --- a/.github/workflows/lint-vizro-all.yml +++ b/.github/workflows/lint-vizro-all.yml @@ -22,8 +22,6 @@ jobs: steps: - uses: actions/checkout@v4 - with: - fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} - name: Set up Python ${{ env.PYTHON_VERSION }} uses: actions/setup-python@v5 diff --git a/.github/workflows/release-if-needed.yml b/.github/workflows/release-if-needed.yml index fc3e59894..310b20692 100644 --- a/.github/workflows/release-if-needed.yml +++ b/.github/workflows/release-if-needed.yml @@ -39,6 +39,7 @@ jobs: package_version: ${{ steps.version_check.outputs.package_version }} build-publish: + environment: vizro_secrets needs: [check-version] if: | needs.check-version.outputs.new_release == 'True' @@ -84,6 +85,7 @@ jobs: password: ${{ env.PYPI_TOKEN }} version-bump: + environment: vizro_secrets needs: [check-version, build-publish] if: | needs.check-version.outputs.new_release == 'True' @@ -101,6 +103,7 @@ jobs: run: | python -m pip install --upgrade pip pip install hatch + # if you want to change this step please contact tech lead - name: Push bumped version to main run: | cd "${{ needs.check-version.outputs.package_name }}" diff --git a/.github/workflows/test-integration-vizro-ai.yml b/.github/workflows/test-integration-vizro-ai.yml index 9d6e96cdc..6de08420d 100644 --- a/.github/workflows/test-integration-vizro-ai.yml +++ b/.github/workflows/test-integration-vizro-ai.yml @@ -7,17 +7,47 @@ defaults: on: push: branches: [main] - schedule: - - cron: "30 10 * * 1" # run every Monday at 10:30 UTC + pull_request: + branches: + - main + paths: + - "vizro-ai/**" + - "!vizro-ai/docs/**" env: PYTHONUNBUFFERED: 1 FORCE_COLOR: 1 jobs: - test-integration-vizro-ai: + test-integration-vizro-ai-fork: + if: ${{ github.event.pull_request.head.repo.fork }} name: test-integration-vizro-ai on Py${{ matrix.python-version }} ${{ matrix.label }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - python-version: "3.9" + hatch-env: all.py3.9 + - python-version: "3.10" + hatch-env: all.py3.10 + - python-version: "3.11" + hatch-env: all.py3.11 + - python-version: "3.12" + hatch-env: all.py3.12 + - python-version: "3.11" + hatch-env: lower-bounds + label: lower bounds + steps: + - uses: actions/checkout@v4 + + - name: Passed fork step + run: echo "Success!" + test-integration-vizro-ai: + if: ${{ ! github.event.pull_request.head.repo.fork }} + name: test-integration-vizro-ai on Py${{ matrix.python-version }} ${{ matrix.label }} + environment: vizro_ai_secrets runs-on: ubuntu-latest strategy: fail-fast: false @@ -37,8 +67,6 @@ jobs: steps: - uses: actions/checkout@v4 - with: - fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }} - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 diff --git a/.github/workflows/test-integration-vizro-core.yml b/.github/workflows/test-integration-vizro-core.yml index ba631fed5..abc2e3bb3 100644 --- a/.github/workflows/test-integration-vizro-core.yml +++ b/.github/workflows/test-integration-vizro-core.yml @@ -49,7 +49,7 @@ jobs: - name: Install Chrome and chromedriver run: | export chrome_version=119.0.6045.105 - sudo apt-get update + sudo apt-get update || true sudo apt-get install libu2f-udev -y wget "https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_$chrome_version-1_amd64.deb" sudo apt-get install -y --allow-downgrades ./google-chrome-stable_$chrome_version-1_amd64.deb diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8a7ceddc6..35ff4cfab 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-added-large-files - id: check-merge-conflict @@ -30,7 +30,7 @@ repos: pass_filenames: false - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell additional_dependencies: @@ -43,51 +43,71 @@ repos: - id: prettier - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks - rev: v2.12.0 + rev: v2.13.0 hooks: - id: pretty-format-toml args: [--autofix] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.0 + rev: v0.4.7 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black - rev: 24.1.1 + rev: 24.4.2 hooks: - id: black - repo: https://github.com/PyCQA/bandit - rev: 1.7.7 + rev: 1.7.8 hooks: - id: bandit args: [-c, pyproject.toml, -ll] additional_dependencies: ["bandit[toml]"] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.8.0 + rev: v1.10.0 hooks: - id: mypy files: ^vizro-core/src/ additional_dependencies: - - pydantic>=1.10.13, <2 # deliberately pinned to <2 until we bump our pydantic requirement to strictly >=2 + # Deliberately pinned to <2 until we bump our pydantic requirement to strictly >=2. + # pydantic>=1.10.15 includes this fix which flags some genuine type problems. These will take a while to fix + # or ignore so for now we just pin to 1.10.14 which doesn't flag the problems. + # https://github.com/pydantic/pydantic/pull/8765 + - pydantic==1.10.14 - repo: https://github.com/gitleaks/gitleaks - rev: v8.18.2 + rev: v8.18.3 hooks: - id: gitleaks name: gitleaks (protect) description: Detect hardcoded secrets using Gitleaks - entry: gitleaks protect --verbose --redact --staged + entry: gitleaks protect --verbose --staged language: golang - id: gitleaks name: gitleaks (detect) description: Detect hardcoded secrets using Gitleaks - entry: gitleaks detect --verbose --redact --baseline-path .gitleaks-report.json + entry: gitleaks detect --verbose --baseline-path gitleaks-report.json language: golang + - repo: https://github.com/awebdeveloper/pre-commit-stylelint + rev: "0.0.2" + hooks: + - id: stylelint + additional_dependencies: + - stylelint@16.2.1 + - stylelint-config-standard@36.0.0 + - stylelint-order@4.1.0 + args: ["--fix"] + + - repo: https://github.com/errata-ai/vale + rev: v3.4.2 + hooks: + - id: vale + args: [--config=.vale/.vale.ini] + # Configuration for https://pre-commit.ci/. ci: autoupdate_schedule: monthly @@ -105,3 +125,4 @@ ci: - bandit - mypy - gitleaks + - vale diff --git a/.prettierignore b/.prettierignore index 673199ae5..8981e2e60 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,4 @@ **/docs/ **/.devcontainer/devcontainer.json +**/static/css/vizro-bootstrap.min.css +**/static/css/vizro-bootstrap.min.css.map diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 000000000..554157e1f --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,2 @@ +**/static/css/vizro-bootstrap.min.css +**/static/css/vizro-bootstrap.min.css.map diff --git a/.vale/.vale.ini b/.vale/.vale.ini index 8554e2e24..3e05b0664 100644 --- a/.vale/.vale.ini +++ b/.vale/.vale.ini @@ -3,5 +3,10 @@ StylesPath = styles MinAlertLevel = suggestion -[*.md] +[vizro-core/docs/pages/*.md] +BasedOnStyles = Vale, Microsoft +Vale.Spelling = NO + +[vizro-ai/docs/pages/*.md] +BasedOnStyles = Vale, Microsoft Vale.Spelling = NO diff --git a/.vale/styles/Microsoft/Acronyms.yml b/.vale/styles/Microsoft/Acronyms.yml index 0c5af28f7..d5a689d40 100644 --- a/.vale/styles/Microsoft/Acronyms.yml +++ b/.vale/styles/Microsoft/Acronyms.yml @@ -15,6 +15,7 @@ exceptions: - CLI - CPU - CSS + - css - CSV - DEBUG - DOM @@ -24,6 +25,7 @@ exceptions: - GDB - GDP - GET + - GPT - GPU - GTK - GUI diff --git a/.vale/styles/Microsoft/Headings.yml b/.vale/styles/Microsoft/Headings.yml index c69fa22b1..df7dd2816 100644 --- a/.vale/styles/Microsoft/Headings.yml +++ b/.vale/styles/Microsoft/Headings.yml @@ -31,6 +31,7 @@ exceptions: - Azure App Service Plan - Azure Blob Storage - Azure Event Hub + - CSS - CI/CD - DataCatalog - Data Catalog @@ -42,6 +43,8 @@ exceptions: - Docker Compose - Docker Swarm - Dockerfile + - Filter + - Flask - FAQ - GitHub - GitHub Actions diff --git a/.vale/styles/Microsoft/Spellings.yml b/.vale/styles/Microsoft/Spellings.yml new file mode 100644 index 000000000..56e0322f4 --- /dev/null +++ b/.vale/styles/Microsoft/Spellings.yml @@ -0,0 +1,10 @@ +extends: spelling +message: "Did you really mean '%s'?" +level: error +ignorecase: true +ignore: + # Located at StylesPath/ + - Microsoft/ignore.txt + - Microsoft/ignore-names.txt +filters: + - '[vV]izro.*\b' diff --git a/.vale/styles/Microsoft/Terms.yml b/.vale/styles/Microsoft/Terms.yml index 3d9dec593..3e545bd11 100644 --- a/.vale/styles/Microsoft/Terms.yml +++ b/.vale/styles/Microsoft/Terms.yml @@ -45,7 +45,11 @@ swap: python: Python leverage: use leverages: uses + leveraging: using utilize: use utilizes: uses Markdown: markdown How-to guide: how-to guide + utilizing: using + utilising: using + dataset: data (or data source) diff --git a/.vale/styles/Microsoft/VizroCapitals.yml b/.vale/styles/Microsoft/VizroCapitals.yml new file mode 100644 index 000000000..db126cee6 --- /dev/null +++ b/.vale/styles/Microsoft/VizroCapitals.yml @@ -0,0 +1,15 @@ +extends: existence +message: Consider your capitalization of '%s' +level: suggestion +tokens: + - kedro + - kedro-viz + - vizro + - vizro-AI + - jupyter + - notebook + - github + - python + - quantumblack +exceptions: + - "vizro-*" diff --git a/.vale/styles/Microsoft/ignore-names.txt b/.vale/styles/Microsoft/ignore-names.txt new file mode 100644 index 000000000..b9ebfadc6 --- /dev/null +++ b/.vale/styles/Microsoft/ignore-names.txt @@ -0,0 +1,73 @@ +Alexey +Snigir +Anna +Xiong +Antony +Milne +Chiara +Pullem +Dan +Dumitriu +Huong +Li +Nguyen +Jo +Stichbury +Joseph +Perkins +Lingyi +Zhang +Maximilian +Schulz +Nadija +Graca +Petar +Pejovic +Ann +Marie +Ward +Ned +Letcher +Natalia +Kurakina, +Leon +Nallamuthu +axa99 +Juan +Luis +Cano +Rodríguez +Denis +Lebedev +Qiuyi +Chen +Elena +Fridman +Bo +Xu +Jingjing +Guo +Oleksandr +Serdiuk +Prateek +Bajaj +Nikolaos +Tsaousis +Annie +Wachsmuth +Hamza +Oza +Kee +Wen +Ng +Rashida +Kanchwala +Sam +Bourton +Kevin +Staight +Wesley +Leong +Jonas +Kemper +Kurakina diff --git a/.vale/styles/Microsoft/ignore.txt b/.vale/styles/Microsoft/ignore.txt new file mode 100644 index 000000000..2cb10f1f8 --- /dev/null +++ b/.vale/styles/Microsoft/ignore.txt @@ -0,0 +1,93 @@ +vm +px +vizro +vizro-ai +Jupyter +Kedro +kedro +Kedro's +Kubeflow +Databricks +Conda +conda +Cookiecutter +config +datetime +dotenv +Dockerfile +formatters +fsspec +globals +Globals +Kaggle +namespace +namespaces +namespaced +regressors +repo +Repo +syntaxes +Syntaxes +pydantic +json +dbx +MLflow +csv +yaml +matplotlib +Matplotlib +IPython +APIs +gapminder +networkx +Plotly +pyenv +Pylint +Serverless +serverless +SQLAlchemy +tracebacks +templated +Templated +VSCode +Astro +Xebia +pytest +transcoding +transcode +Claypot +virginica +plotly's +Plotly's +plotly +css +dataframes +Javascript +tooltip +Snyk +Codespaces +dev +mypy +javascript +gitleaks +resizable +LLMs +untrusted +Builtins +Redlisted +api +stylings +uncommenting +subgrids +favicon +scriv +toolchain +subpages +Plotly's +Gunicorn +dataframe +streamlit +memoization +setosa +versicolor +virginica diff --git a/CODEOWNERS b/CODEOWNERS index 83a984cd3..3c14a2e3b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,3 @@ -* @Joseph-Perkins @antonymilne @huong-li-nguyen @maxschulz-COL @Anna-Xiong -vizro-ai/docs/ @stichbury -vizro-core/docs/ @stichbury +* @Joseph-Perkins @antonymilne @huong-li-nguyen @maxschulz-COL +vizro-ai/ @Joseph-Perkins @Anna-Xiong @lingyielia @maxschulz-COL +docs/ @stichbury diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f804f7b5..5d76c4386 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing guidelines Contributions of all experience levels are welcome! If you're interested in making a contribution, -please refer to our [Contributing Guide](https://github.com/mckinsey/vizro/blob/main/vizro-core/docs/pages/development/contributing.md) for more information. +please refer to our [Contributing Guide](https://vizro.readthedocs.io/en/stable/pages/explanation/contributing/) for more information. The easiest way to get up and running quickly is to [open the repository in GitHub Codespaces](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=626855142). This will create a temporary development environment with all the necessary configurations, making it especially convenient for tasks like reviewing pull requests. diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index c61b66391..000000000 --- a/LICENSE.md +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 7949569c3..2636028df 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@

-Visual Intelligence. Beautifully engineered +Visual Intelligence. Beautifully Engineered

@@ -55,7 +55,7 @@ Vizro is a toolkit for creating modular data visualization applications

-Rapidly self-serve the assembly of customized dashboards in minutes - without the need for advanced coding or design experience - to create flexible and scalable, Python enabled data visualization applications +Rapidly self-serve the assembly of customized dashboards in minutes - without the need for advanced coding or design experience - to create flexible and scalable, Python-enabled data visualization applications.

@@ -63,24 +63,32 @@ Rapidly self-serve the assembly of customized dashboards in minutes - without th

-Use a few lines of simple configuration to create complex dashboards, which are automatically assembled utilizing libraries such as [**Plotly**](https://github.com/plotly/plotly.py) and [**Dash**](https://github.com/plotly/dash), with inbuilt coding and design best practices +Use a few lines of simple configuration to create complex dashboards, which are automatically assembled using libraries such as [**Plotly**](https://github.com/plotly/plotly.py) and [**Dash**](https://github.com/plotly/dash), with inbuilt coding and design best practices. -Define high level categories within the configuration, including: +Define high-level categories within the configuration, including: -- **components:** create charts, tables, input/output interfaces, and more -- **controls**: create filters, parameter inputs, and custom action controllers -- **pages, layouts and navigation**: create multiple pages, with customizable layouts and flexible navigation across them -- **actions and interactions**: create interactions between charts, and use pre-defined or customized actions (such as exporting) +- **Components:** create charts, tables, input/output interfaces, and more. +- **Controls**: create filters, parameter inputs, and custom action controllers. +- **Pages, layouts and navigation**: create multiple pages, with customizable layouts and flexible navigation across them. +- **Actions and interactions**: create interactions between charts, and use pre-defined or customized actions (such as exporting). -Configuration can be written in multiple formats including **Pydantic models**, **JSON**, **YAML** or **Python dictionaries** for added flexibility of implementation +Configuration can be written in multiple formats including **Pydantic models**, **JSON**, **YAML** or **Python dictionaries** for added flexibility of implementation. -Optional high-code extensions allow almost infinite customization in a modular way, combining the best of low-code and high-code - for flexible and scalable, Python enabled data visualization applications +Optional high-code extensions enable almost infinite customization in a modular way, combining the best of low-code and high-code - for flexible and scalable, Python enabled data visualization applications. -(Visit the ["Why Vizro"](https://vizro.readthedocs.io/en/latest/pages/explanation/why_vizro/) section to see a more detailed explanation of Vizro use cases) +Visit ["Why should I use Vizro?"](https://vizro.readthedocs.io/en/stable/pages/explanation/faq/#why-should-i-use-vizro) for a more detailed explanation of Vizro use cases. -
+### What is Vizro-AI? + +Vizro-AI is a separate package and extends Vizro to enable the use of natural language queries to build Plotly charts. + +

+Gif to show vizro-ai +

+ +See the [Vizro-AI documentation](https://vizro.readthedocs.io/projects/vizro-ai/en/latest/) for more details. -### Key benefits +## Key benefits of Vizro
@@ -90,69 +98,57 @@ Optional high-code extensions allow almost infinite customization in a modular w
-### Examples +## Live demo -

- -

- -### Live demo +You can see Vizro in action by clicking on the following image or by visiting [vizro.mckinsey.com](https://vizro.mckinsey.com).

-### Installation +## Examples + +

+ +

+ +## Installation and first steps ```console pip install vizro ``` -See the [Installation guide](https://vizro.readthedocs.io/en/stable/pages/user_guides/install/) for more information - -Please note this repository is a monorepo and the core `vizro` package can be found in [/vizro-core](https://github.com/mckinsey/vizro/tree/main/vizro-core) +See the [installation guide](https://vizro.readthedocs.io/en/stable/pages/user-guides/install/) for more information. -### Getting started +The [get started documentation](https://vizro.readthedocs.io/en/stable/pages/tutorials/first-dashboard/) explains how to create your first dashboard. -See the [Tutorials](https://vizro.readthedocs.io/en/stable/pages/tutorials/first_dashboard/) for creating your first dashboard +## Get hands on -### Documentation - -See the [Documentation](https://vizro.readthedocs.io/en/stable/) for more details +See the [how-to guides](https://vizro.readthedocs.io/en/stable/pages/user-guides/install/) for step-by-step instructions on the key Vizro features. ## Packages -| Folder | Version | Documentation | -| :------------------------: | :-----------------------------------------------------------------------------------------: | :---------------------------------------------------------: | -| [vizro-core](./vizro-core) | [![PyPI version](https://badge.fury.io/py/vizro.svg)](https://badge.fury.io/py/vizro) | [Vizro Docs](https://vizro.readthedocs.io/en/stable/) | -| [vizro-ai](./vizro-ai) | [![PyPI version](https://badge.fury.io/py/vizro-ai.svg)](https://badge.fury.io/py/vizro-ai) | [Vizro-AI Docs](https://vizro-ai.readthedocs.io/en/latest/) | - -### Vizro-AI - -[vizro-ai](https://vizro-ai.readthedocs.io/en/latest/) is an extension to [vizro-core](https://vizro.readthedocs.io/en/stable/) that is specifically designed to enrich the existing suite of tools of Vizro, introducing advanced artificial intelligence features for generating, analyzing, and manipulating visuals. -`vizro-ai` leverages natural language capabilities to empower users in creating charts effortlessly. - -

-Gif to demonstrate vizro-ai -

+This repository is a monorepo containing the following packages: -See [Vizro-AI Documentation](https://vizro-ai.readthedocs.io/en/latest/) for more details. +| Folder | Version | Documentation | +| :------------------------: | :-----------------------------------------------------------------------------------------: | :------------------------------------------------------------------------: | +| [vizro-core](./vizro-core) | [![PyPI version](https://badge.fury.io/py/vizro.svg)](https://badge.fury.io/py/vizro) | [Vizro Docs](https://vizro.readthedocs.io/en/stable/) | +| [vizro-ai](./vizro-ai) | [![PyPI version](https://badge.fury.io/py/vizro-ai.svg)](https://badge.fury.io/py/vizro-ai) | [Vizro-AI Docs](https://vizro.readthedocs.io/projects/vizro-ai/en/latest/) | -## Community and Development +## Community and development We encourage you to ask and answer technical questions via the [GitHub Issues](https://github.com/mckinsey/vizro/issues). This is also the place where you can submit bug reports or request new features. -## Contributing +## Want to contribute to Vizro? -To learn more about making a contribution, -please see the [Contributing Guide](https://vizro.readthedocs.io/en/stable/pages/development/contributing/) for more information +The [contribution guidelines](https://vizro.readthedocs.io/en/stable/pages/explanation/contributing/) explain how you can contribute to Vizro. -You can also view current and former [contributors](https://vizro.readthedocs.io/en/stable/pages/development/authors/) +You can also view current and former [contributors](https://vizro.readthedocs.io/en/stable/pages/explanation/authors/). -## Reporting a Security Vulnerability +## Want to report a security vulnerability? -Please see our [security policy](https://github.com/mckinsey/vizro/security/policy) +See our [security policy](https://github.com/mckinsey/vizro/security/policy). ## License diff --git a/pyproject.toml b/pyproject.toml index c75a049fd..194714c4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ target-version = ["py38"] [tool.codespell] builtin = "clear,rare,en-GB_to_en-US" ignore-words-list = "grey,ned,sav" +skip = "*.min.css.map,*.min.css,.vale/*" [tool.mypy] # strict checks : strict = true diff --git a/tools/check_for_datafiles.py b/tools/check_for_datafiles.py index eb97e3a1c..93f1d7740 100644 --- a/tools/check_for_datafiles.py +++ b/tools/check_for_datafiles.py @@ -18,7 +18,7 @@ "sqlite3", "orc", ] -whitelist_folders = ["/venv"] # starting from project root dir +whitelist_folders = ["/venv", "/vizro-core/docs"] # starting from project root dir def check_for_data_files(): diff --git a/vizro-ai/.readthedocs.yaml b/vizro-ai/.readthedocs.yaml index dd8f1f4ac..50675a2d8 100644 --- a/vizro-ai/.readthedocs.yaml +++ b/vizro-ai/.readthedocs.yaml @@ -11,6 +11,6 @@ build: python: "3.11" commands: - pip install hatch - - cd vizro-ai/ && hatch run docs:mkdocs build + - cd vizro-ai/ && hatch run docs:build && hatch run docs:link-check - mkdir --parents $READTHEDOCS_OUTPUT - mv vizro-ai/site/ $READTHEDOCS_OUTPUT/html diff --git a/vizro-ai/CHANGELOG.md b/vizro-ai/CHANGELOG.md index 4f1bc4fb9..d5bd0a439 100644 --- a/vizro-ai/CHANGELOG.md +++ b/vizro-ai/CHANGELOG.md @@ -11,6 +11,22 @@ See the fragment files in the [changelog.d directory](https://github.com/mckinse + + +# 0.2.0 — 2024-05-09 + +## Removed + +- Removed `temperature` and `model_name` arguments of `VizroAI` class. For current configuration options, visit the [Vizro-AI docs](https://vizro.readthedocs.io/projects/vizro-ai/en/latest/pages/explanation/faq/#what-parameters-does-vizro-ai-support) ([#423](https://github.com/mckinsey/vizro/pull/423)) + +## Added + +- Enable customization of LLM models provided to Vizro-AI class. ([#423](https://github.com/mckinsey/vizro/pull/423)) + +## Changed + +- `VizroAI.plot` now generates and returns a `plotly.graph_objects.Figure` object. ([#411](https://github.com/mckinsey/vizro/pull/441)). This facilitates the integration of Vizro-AI charts into the `vizro` dashboard, visit [Use Vizro-AI dynamically to return a fig object](https://vizro.readthedocs.io/projects/vizro-ai/en/latest/pages/user-guides/add-generated-chart-usecase/#use-vizro-ais-generated-code) for details. + # 0.1.2 — 2024-03-13 diff --git a/vizro-ai/LICENSE.txt b/vizro-ai/LICENSE.txt index c61b66391..d64569567 100644 --- a/vizro-ai/LICENSE.txt +++ b/vizro-ai/LICENSE.txt @@ -1,181 +1,182 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" @@ -186,16 +187,16 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright [yyyy] [name of copyright owner] + Copyright [yyyy] [name of copyright owner] -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vizro-ai/changelog.d/20240313_183650_maximilian_schulz_0_1_2.md b/vizro-ai/changelog.d/20240507_115440_nadija_ratkusic_graca_vizro_ai_add_chart_examples.md similarity index 100% rename from vizro-ai/changelog.d/20240313_183650_maximilian_schulz_0_1_2.md rename to vizro-ai/changelog.d/20240507_115440_nadija_ratkusic_graca_vizro_ai_add_chart_examples.md diff --git a/vizro-ai/changelog.d/20240313_190013_maximilian_schulz_version_bump.md b/vizro-ai/changelog.d/20240509_181708_runner.md similarity index 100% rename from vizro-ai/changelog.d/20240313_190013_maximilian_schulz_version_bump.md rename to vizro-ai/changelog.d/20240509_181708_runner.md diff --git a/vizro-ai/changelog.d/20240325_170323_jo_stichbury_revise_vizro_ai_docs.md b/vizro-ai/changelog.d/20240509_190629_nadija_ratkusic_graca_0_2_0.md similarity index 100% rename from vizro-ai/changelog.d/20240325_170323_jo_stichbury_revise_vizro_ai_docs.md rename to vizro-ai/changelog.d/20240509_190629_nadija_ratkusic_graca_0_2_0.md diff --git a/vizro-core/changelog.d/20240326_100046_nadija_ratkusic_graca_0_1_14.md b/vizro-ai/changelog.d/20240510_140409_anna_xiong_fix_vizro_ai_chart_example.md similarity index 100% rename from vizro-core/changelog.d/20240326_100046_nadija_ratkusic_graca_0_1_14.md rename to vizro-ai/changelog.d/20240510_140409_anna_xiong_fix_vizro_ai_chart_example.md diff --git a/vizro-core/changelog.d/20240326_105937_nadija_ratkusic_graca_0_1_15_dev0.md b/vizro-ai/changelog.d/20240513_162508_antony.milne_add_link_checker.md similarity index 100% rename from vizro-core/changelog.d/20240326_105937_nadija_ratkusic_graca_0_1_15_dev0.md rename to vizro-ai/changelog.d/20240513_162508_antony.milne_add_link_checker.md diff --git a/vizro-core/changelog.d/20240327_104803_huong_li_nguyen_improve_semantics_nav.md b/vizro-ai/changelog.d/20240514_232019_antony.milne_random_bits.md similarity index 100% rename from vizro-core/changelog.d/20240327_104803_huong_li_nguyen_improve_semantics_nav.md rename to vizro-ai/changelog.d/20240514_232019_antony.milne_random_bits.md diff --git a/vizro-core/changelog.d/20240328_163211_petar_pejovic_actions_assert_component_equal_tests.md b/vizro-ai/changelog.d/20240520_095517_jo_stichbury_483.md similarity index 100% rename from vizro-core/changelog.d/20240328_163211_petar_pejovic_actions_assert_component_equal_tests.md rename to vizro-ai/changelog.d/20240520_095517_jo_stichbury_483.md diff --git a/vizro-ai/changelog.d/20240522_095814_anna_xiong_vizro_ai_faq_links.md b/vizro-ai/changelog.d/20240522_095814_anna_xiong_vizro_ai_faq_links.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-ai/changelog.d/20240522_095814_anna_xiong_vizro_ai_faq_links.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-ai/docs/assets/tutorials/chart/GDP_Composition_Bar.png b/vizro-ai/docs/assets/tutorials/chart/GDP_Composition_Bar.png deleted file mode 100644 index bccb7cdf6..000000000 Binary files a/vizro-ai/docs/assets/tutorials/chart/GDP_Composition_Bar.png and /dev/null differ diff --git a/vizro-ai/docs/assets/tutorials/chart/GDP_Composition_Graph.png b/vizro-ai/docs/assets/tutorials/chart/GDP_Composition_Graph.png new file mode 100644 index 000000000..4573a4f24 Binary files /dev/null and b/vizro-ai/docs/assets/tutorials/chart/GDP_Composition_Graph.png differ diff --git a/vizro-ai/docs/assets/user_guides/chart_into_dashboard_2.gif b/vizro-ai/docs/assets/user_guides/chart_into_dashboard_2.gif new file mode 100644 index 000000000..d9375c8e3 Binary files /dev/null and b/vizro-ai/docs/assets/user_guides/chart_into_dashboard_2.gif differ diff --git a/vizro-ai/docs/assets/user_guides/chart_into_dashboard_large.gif b/vizro-ai/docs/assets/user_guides/chart_into_dashboard_large.gif new file mode 100644 index 000000000..2e6c2b8b9 Binary files /dev/null and b/vizro-ai/docs/assets/user_guides/chart_into_dashboard_large.gif differ diff --git a/vizro-ai/docs/assets/user_guides/map_chart.gif b/vizro-ai/docs/assets/user_guides/map_chart.gif new file mode 100644 index 000000000..d44569475 Binary files /dev/null and b/vizro-ai/docs/assets/user_guides/map_chart.gif differ diff --git a/vizro-ai/docs/assets/user_guides/polar_bar_chart.png b/vizro-ai/docs/assets/user_guides/polar_bar_chart.png new file mode 100644 index 000000000..8c1df391b Binary files /dev/null and b/vizro-ai/docs/assets/user_guides/polar_bar_chart.png differ diff --git a/vizro-ai/docs/assets/user_guides/surface_plot.gif b/vizro-ai/docs/assets/user_guides/surface_plot.gif new file mode 100644 index 000000000..9611b1218 Binary files /dev/null and b/vizro-ai/docs/assets/user_guides/surface_plot.gif differ diff --git a/vizro-ai/docs/assets/user_guides/vizro-ai-chart.gif b/vizro-ai/docs/assets/user_guides/vizro-ai-chart.gif new file mode 100644 index 000000000..69a2dfe68 Binary files /dev/null and b/vizro-ai/docs/assets/user_guides/vizro-ai-chart.gif differ diff --git a/vizro-ai/docs/index.md b/vizro-ai/docs/index.md index ff4a61fe0..3183aec1e 100644 --- a/vizro-ai/docs/index.md +++ b/vizro-ai/docs/index.md @@ -1,64 +1,53 @@ # Vizro-AI -Vizro-AI extends [Vizro](https://vizro.readthedocs.io) to enable a user to use English or other languages to effortlessly create create interactive charts with [Plotly](https://plotly.com/python/). +Vizro-AI extends [Vizro](https://vizro.readthedocs.io) to enable a user to use English or other languages to effortlessly create interactive charts with [Plotly](https://plotly.com/python/). If you're new to coding, Vizro-AI simplifies the process of creating charts that offer detailed insights about your data. Even if you're an experienced data practitioner, Vizro-AI optimizes how you create visually appealing charts. Vizro-AI uses a large language model and Plotly to generate code for an interactive chart that you can add into a [Vizro dashboard application](https://vizro.readthedocs.io). By using Vizro's themes, you can incorporate design best practices by default. -

-
- - -
-
-
Get started
-

- New to Vizro-AI? Tutorials to get started and to explore the key features. -

-
-
-
- - -
-
-
How-to guides
-

- How to guides about Vizro-AI and frequently asked questions. -

-
-
-
- - - -
-
-
Explanation
-

- The explanation section contains background information and a disclaimer. -

-
-
-
- - -
-
-
Contribute
-

- Contribution guidelines and all our current and past contributors. -

-
-
-
+Gif to demonstrate vizro-ai + +
+ +- :fontawesome-solid-forward-fast:{ .lg .middle } __New to Vizro-AI?__ + + --- + + [:octicons-arrow-right-24: Install Vizro-AI](pages/user-guides/install/)
+ [:octicons-arrow-right-24: Quickstart tutorial](pages/tutorials/quickstart/) + + +- :fontawesome-solid-keyboard:{ .lg .middle } __Get hands-on__ + + --- + + [:octicons-arrow-right-24: Ways to run Vizro-AI](pages/user-guides/run-vizro-ai/)
+ [:octicons-arrow-right-24: Customize models](pages/user-guides/customize-vizro-ai/)
+ [:octicons-arrow-right-24: Create advanced charts](pages/user-guides/create-advanced-charts/)
+ [:octicons-arrow-right-24: Use different languages](pages/user-guides/use-different-languages/)
+ [:octicons-arrow-right-24: Add charts to a dashboard](pages/user-guides/add-generated-chart-usecase/) + +- :material-format-font:{ .lg .middle } __Find out more__ + + --- + + [:octicons-arrow-right-24: FAQs](pages/explanation/faq/)
+ [:octicons-arrow-right-24: Safeguard dynamic code execution](pages/explanation/safeguard/)
+ [:octicons-arrow-right-24: Guidelines for use of LLMs](pages/explanation/safety-in-vizro-ai//) + +- :fontawesome-solid-chart-column:{ .lg .middle } __Vizro__ + + --- + + [:octicons-arrow-right-24: Vizro documentation](https://vizro.readthedocs.io/) + -
!!! notice "Notice" - Please review this [disclaimer](pages/explanation/disclaimer.md) + + Review the [disclaimer](pages/explanation/disclaimer.md) before using the `vizro-ai` package. Users must connect to large language models (LLMs) to use Vizro-AI. diff --git a/vizro-ai/docs/pages/API-reference/vizro-ai.md b/vizro-ai/docs/pages/API-reference/vizro-ai.md new file mode 100644 index 000000000..91baf4646 --- /dev/null +++ b/vizro-ai/docs/pages/API-reference/vizro-ai.md @@ -0,0 +1,10 @@ +# VizroAI + + +::: vizro_ai + options: + merge_init_into_class: false + docstring_options: + ignore_init_summary: false + + diff --git a/vizro-ai/docs/pages/contribute/authors.md b/vizro-ai/docs/pages/contribute/authors.md deleted file mode 100644 index 41d0e5a32..000000000 --- a/vizro-ai/docs/pages/contribute/authors.md +++ /dev/null @@ -1,17 +0,0 @@ -# Authors - -## Current team members -[Alexey Snigir](https://github.com/l0uden), -[Anna Xiong](https://github.com/Anna-Xiong), -[Antony Milne](https://github.com/antonymilne), -[Chiara Pullem](https://github.com/chiara-sophie), -[Dan Dumitriu](https://github.com/dandumitriu1), -[Huong Li Nguyen](https://github.com/huong-li-nguyen), -[Jo Stichbury](https://github.com/stichbury), -[Joseph Perkins](https://github.com/Joseph-Perkins), -[Lingyi Zhang](https://github.com/lingyielia), -[Maximilian Schulz](https://github.com/maxschulz-COL), -[Nadija Graca](https://github.com/nadijagraca), -[Petar Pejovic](https://github.com/petar-qb). - -With thanks to Sam Bourton and Stephen Xu for sponsorship, inspiration and guidance, plus everyone else who helped to test, guide, support and encourage development. diff --git a/vizro-ai/docs/pages/explanation/disclaimer.md b/vizro-ai/docs/pages/explanation/disclaimer.md index fc7e24d7a..05cd4d691 100644 --- a/vizro-ai/docs/pages/explanation/disclaimer.md +++ b/vizro-ai/docs/pages/explanation/disclaimer.md @@ -1,6 +1,6 @@ # Disclaimer -Users must select one of the [supported large language models (LLMs)](./faq.md#which-llms-are-supported-by-vizro-ai) to use the `vizro_ai` package, +Users must select one of the [supported large language models (LLMs)](../user-guides/customize-vizro-ai.md#supported-models) to use the `vizro_ai` package, and are responsible for obtaining their own suitable API key for the relevant model. diff --git a/vizro-ai/docs/pages/explanation/faq.md b/vizro-ai/docs/pages/explanation/faq.md index a9b8d30cd..46dfcd2ce 100644 --- a/vizro-ai/docs/pages/explanation/faq.md +++ b/vizro-ai/docs/pages/explanation/faq.md @@ -1,44 +1,22 @@ # Frequently asked questions -## Which LLMs are supported by Vizro-AI? -Vizro-AI currently supports OpenAI models as follows: - -- gpt-3.5-turbo-0613 (to be deprecated on June 13, 2024) -- gpt-4-0613 -- gpt-3.5-turbo-1106 (under testing) -- gpt-4-1106-preview (under testing, not suitable for production use) - -These models offer different levels of performance and -cost. In general, the `gpt-3.5-turbo` collection provides the most cost-effective models, -which would be a good starting point for most users. `gpt-4` models are more powerful than `gpt-3` models, for example, they use more tokens per request. You can refer to these models' [capabilities](https://platform.openai.com/docs/models/overview) -and [pricing](https://openai.com/pricing) for more information. - -We are working on supporting more models and more vendors. Stay tuned! - - -!!! Warning - - Users are recommended to exercise caution and to research and understand the selected large language model (LLM) before using `vizro_ai`. - Users should be cautious about sharing or inputting any personal or sensitive information. - - **Data is sent to model vendors if you connect to LLMs via their APIs.** - For example, if you specify model_name="gpt-3.5-turbo-0613", your data will be sent to OpenAI via their API. - - Users are also recommended to review the third party API key section of the [disclaimer](../explanation/disclaimer.md) documentation. - -## What parameters does Vizro-AI support? -Currently, Vizro-AI supports the following parameters: - -- `temperature`: A parameter for tuning the randomness of the output. It is set to 0 by - default. We recommend setting it to 0 for Vizro-AI usage, as it's mostly - deterministic. -- `model_name`: The name of the model to use. See above for [models currently supported by Vizro-AI](#which-llms-are-supported-by-vizro-ai) for the models supported. - -!!! example "Config and construct Vizro-AI" - - === "python" - ```py linenums="1" - from vizro_ai import VizroAI - - vizro_ai = VizroAI(model_name="gpt-3.5-turbo-0613", temperature=0) - ``` +## Who works on Vizro-AI? + +### Current team members +[Alexey Snigir](https://github.com/l0uden), +[Anna Xiong](https://github.com/Anna-Xiong), +[Antony Milne](https://github.com/antonymilne), +[Dan Dumitriu](https://github.com/dandumitriu1), +[Huong Li Nguyen](https://github.com/huong-li-nguyen), +[Jo Stichbury](https://github.com/stichbury), +[Joseph Perkins](https://github.com/Joseph-Perkins), +[Lingyi Zhang](https://github.com/lingyielia), +[Maximilian Schulz](https://github.com/maxschulz-COL), +[Nadija Graca](https://github.com/nadijagraca), +[Petar Pejovic](https://github.com/petar-qb). + +With thanks to Sam Bourton and Stephen Xu for sponsorship, inspiration and guidance, plus everyone else who helped to test, guide, support and encourage development. + + +## Which large language models are supported by vizro-ai? +Refer to [supported models](../user-guides/customize-vizro-ai.md#supported-models) in `vizro-ai` docs. diff --git a/vizro-ai/docs/pages/explanation/safeguard.md b/vizro-ai/docs/pages/explanation/safeguard.md index c7e96c524..447d1cc4f 100644 --- a/vizro-ai/docs/pages/explanation/safeguard.md +++ b/vizro-ai/docs/pages/explanation/safeguard.md @@ -72,8 +72,8 @@ The lists below are a reflection of the security and functionality we have imple ??? failure "Redlisted Data Handling Methods and Formats" - - Various data file formats (e.g., .csv, .tsv, .xlsx, .json, etc.) - - Specific methods related to data input/output operations (e.g., .to_csv, .read_excel, .loadtxt, etc.) + - Various data file formats (such as .csv, .tsv, .xlsx, .json, and so on) + - Specific methods related to data input/output operations (such as .to_csv, .read_excel, .loadtxt) ### Safeguard for user environment and input diff --git a/vizro-ai/docs/pages/explanation/safety-in-vizro-ai.md b/vizro-ai/docs/pages/explanation/safety-in-vizro-ai.md index 144b13397..bcdd08131 100644 --- a/vizro-ai/docs/pages/explanation/safety-in-vizro-ai.md +++ b/vizro-ai/docs/pages/explanation/safety-in-vizro-ai.md @@ -6,39 +6,44 @@ We recommend users research and understand the selected model before using `vizr Users are encouraged to treat AI-generated content as supplementary, **always apply human judgment**, approach with caution, review the relevant [disclaimer](disclaimer.md) page, and consider the following: - -### 1. Hallucination and misrepresentation +### 1. Hallucination and misrepresentation + Generative models can potentially generate information while appearing factual, being entirely fictitious or misleading. The vendor models might lack real-time knowledge or events beyond its last updates. `vizro_ai` output may vary and you should always verify critical information. It is the user's responsibility to discern the accuracy, consistent, and reliability of the generated content. + ### 2. Unintended and sensitive output - + The outputs from these models can be unexpected, inappropriate, or even harmful. Users as human in the loop is an essential part. Users must check and interpret the final output. It is necessary to approach the generated content with caution, especially when shared or applied in various contexts. + ### 3. Data privacy - + Your data is sent to model vendors if you connect to LLMs via their APIs. For example, if you connect to the model "gpt-3.5-turbo-0613" from Open AI, your data will be sent to OpenAI via their API. Users should be cautious about sharing or inputting any personal or sensitive information. + ### 4. Bias and fairness - + Generative AI can exhibit biases present in their training data. Users need to be aware of and navigate potential biases in generated outputs and be cautious when interpreting the generated content. + ### 5. Malicious use - + These models can be exploited for various malicious activities. Users should be cautious about how and where they deploy and access such models. It's crucial for users to remain informed, cautious, and ethical in their applications. -## Dependencies, code scanners and information security + +## Dependencies, code scanners, and information security It may occur that dependencies of `vizro_ai` get flagged by code scanners and other information security tools. As a consequence it may happen that `vizro_ai` also get flagged. @@ -48,6 +53,7 @@ to any functionality used in our code base or if they only concern functionality In case those issues are related to code execution, note that `vizro_ai` has its own process of executing dynamic code (see [Safeguard Execution of Dynamic Code](safeguard.md)), and does not rely on its dependencies to do so. + ## Execution of dynamic code in Vizro-AI The `exec()` statement is used in `vizro_ai`. It enables dynamic execution of Python programs which can be powerful, but can also pose security risk diff --git a/vizro-ai/docs/pages/get-started/quickstart.md b/vizro-ai/docs/pages/tutorials/quickstart.md similarity index 50% rename from vizro-ai/docs/pages/get-started/quickstart.md rename to vizro-ai/docs/pages/tutorials/quickstart.md index c3f94e9c6..958cbf7c4 100644 --- a/vizro-ai/docs/pages/get-started/quickstart.md +++ b/vizro-ai/docs/pages/tutorials/quickstart.md @@ -1,11 +1,15 @@ # Get started with Vizro-AI This tutorial introduces you to Vizro-AI, which is an English-to-visualization package. In a series of steps, we will explain the basics and set you up with the knowledge to explore the package further. + ### 1. Install Vizro and its dependencies + -If you haven't already installed Vizro-AI and set up the API key for OpenAI, follow the [installation guide](../get-started/install.md). +If you haven't already installed Vizro-AI and set up the API key for OpenAI, follow the [installation guide](../user-guides/install.md). + ### 2. Open a Jupyter Notebook + A good way to initially explore Vizro-AI is from inside a Jupyter Notebook. @@ -35,8 +39,9 @@ print(vizro_ai.__version__) You should see a return output of the form `x.y.z`. - + ### 3. Create your first chart using Vizro-AI + Let's create a chart to illustrate the GDP of various continents while including a reference line for the average. We give Vizro-AI the English language instruction "*describe the composition of GDP in continent and color by continent, and add a horizontal line for avg GDP*". @@ -55,14 +60,27 @@ Next, we instantiate `VizroAI`: ```python vizro_ai = VizroAI() ``` +To learn how to customize the `VizroAI` class, check out the guide on [how to customize models](../user-guides/customize-vizro-ai.md). Finally, we call the `plot()` method with our English language instruction, to generate the visualization: ```python -vizro_ai.plot(df, "describe the composition of GDP in continent and color by continent, and add a horizontal line for avg GDP") +vizro_ai.plot(df, "create a line graph for GDP per capita since 1950 for each continent. Mark the x axis as Year, y axis as GDP Per Cap and don't include a title") ``` -And that's it! By passing the prepared data and written visualization request, Vizro-AI takes care of the processing. It generates the necessary code for data manipulation and chart creation, and renders the chart by executing the generated code. +!!! warning "Help! The LLM request was unauthorized" + + If you see an error similar to this, your LLM API key is not valid: + + `INFO:httpx:HTTP Request: POST https://api.openai.com/v1/chat/completions "HTTP/1.1 401 Unauthorized"` + + Make sure you have [set up access to a large language model](../user-guides/install.md/#set-up-access-to-a-large-language-model). If you are confident that you have specified your API key correctly and have sufficient credits associated with it, check your environment. Some developers export the environment explicitly to ensure the API key is available at runtime. Call the following in your terminal: + + `export OPENAI_API_KEY="sk-YOURKEY"`. + + The call above makes the API key available from that terminal instance. If you want to access Vizro-AI from a Notebook, you should then run `jupyter notebook` (or just work within that terminal to run your Python script in `app.py`. When you restart the terminal, you'll need to call `export` again. + +And that's it! By passing the prepared data and written visualization request, Vizro-AI takes care of the processing. It generates the necessary code for data manipulation and chart creation, and returns the chart by executing the generated code. !!! example "Vizro AI Syntax" @@ -74,18 +92,37 @@ And that's it! By passing the prepared data and written visualization request, V df = px.data.gapminder() vizro_ai = VizroAI() - vizro_ai.plot(df, "describe the composition of GDP in continent and color by continent, and add a horizontal line for avg GDP") + vizro_ai.plot(df, "create a line graph for GDP per capita since 1950 for each continent. Mark the x axis as Year, y axis as GDP Per Cap and don't include a title", explain=True) ``` === "Result" - [![BarChart]][BarChart] + [![LineGraph]][LineGraph] - [BarChart]: ../../assets/tutorials/chart/GDP_Composition_Bar.png + [LineGraph]: ../../assets/tutorials/chart/GDP_Composition_Graph.png -The created chart is interactive: you can hover over the data for more information. +The chart created is interactive: you can hover over the data for more information. -### 5. Get an explanation with your chart +Passing `explain=True` to the `plot()` method returns the code to create the chart, along with a set of insights to explain the rendered chart in detail. You can then use the code within a Vizro dashboard as illustrated in the [Vizro documentation](https://vizro.readthedocs.io/en/stable/pages/tutorials/explore-components/#22-add-further-components). For the line graph above, the code returned is as follows: -Passing `explain=True` to the `plot()` method provides insights to explain the rendered chart in detail. Let's create another example to illustrate the information returned: +!!! example "Returned by Vizro-AI" + + ```python + from vizro.models.types import capture + import vizro.plotly.express as px + import pandas as pd + @capture('graph') + def custom_chart(data_frame): + df = data_frame.groupby(['year', 'continent'])['gdpPercap'].mean().unstack().reset_index() + fig = px.line(df, x='year', y=['Africa', 'Americas', 'Asia', 'Europe', 'Oceania']) + return fig + + fig = custom_chart(data_frame=df) + ``` + + +### 4. Get an explanation with your chart + + +Let's create another example to illustrate the code and insights returned when passing `explain=True` as a parameter to `plot()`: !!! example "Specify `explain=True`" @@ -98,7 +135,10 @@ Passing `explain=True` to the `plot()` method provides insights to explain the r [GeoDistribution]: ../../assets/tutorials/chart/GeoDistribution.png -### 6. Explore further + +### 5. Explore further + + Now, you have created your first charts with Vizro-AI you are ready to explore further. diff --git a/vizro-ai/docs/pages/user-guides/add-generated-chart-usecase.md b/vizro-ai/docs/pages/user-guides/add-generated-chart-usecase.md new file mode 100644 index 000000000..286357b4a --- /dev/null +++ b/vizro-ai/docs/pages/user-guides/add-generated-chart-usecase.md @@ -0,0 +1,159 @@ +# How to add Vizro-AI created charts into a Vizro dashboard + +This guide explains the different ways in which you can add a chart generated by Vizro-AI to your [Vizro](https://github.com/mckinsey/vizro/tree/main/vizro-core) dashboard. + +### Use Vizro-AI's generated code + +1. Create a chart with Vizro-AI that you would like to visualize in a dashboard. + + In this example, we aim to create a chart that illustrates the population development of each continent over time. To gain deeper insights and access the underlying code responsible for generating the chart, include `explain=True` in the `plot()` method. Let's execute the provided code and examine the outcome. + + !!! example "Vizro-AI chart" + + === "Code for the cell" + ```py + import vizro_ai + from vizro_ai import VizroAI + import vizro.plotly.express as px + from dotenv import load_dotenv + + load_dotenv() + + df = px.data.gapminder() + vizro_ai = VizroAI(model="gpt-4-0613") + + vizro_ai.plot(df, + """Plot a bubble chart to show the changes in life expectancy + and GDP per capita for each country over time. + Color the bubbles by continent. + Add animation on yearly basis, and do not use facets. + Put the legend on top""", explain=True) + ``` + === "Result" + [![VizroAIChart]][VizroAIChart] + + [VizroAIChart]: ../../assets/user_guides/vizro-ai-chart.gif + +2. Insert the resulting chart into a dashboard. + + Once you are satisfied with the chart, you can add it to a [Vizro](https://github.com/mckinsey/vizro/tree/main/vizro-core) dashboard. In the explanation section from the result of the example above, you'll find the code snippet used to generate the chart. + + ```py + @capture('graph') + def custom_chart(data_frame=pd.DataFrame()): + # Create a bubble chart + fig = px.scatter(data_frame, x='gdpPercap', y='lifeExp', animation_frame='year', animation_group='country', + size='pop', color='continent', hover_name='country', log_x=True, size_max=60, + range_y=[20,90], + labels={'gdpPercap':'GDP per Capita', 'lifeExp':'Life Expectancy', 'year':'Year', 'continent':'Continent', 'pop':'Population'}, + title='Life Expectancy v. Per Capita GDP, 1952-2007', + height=400) + # Put the legend on top + fig.update_layout(legend=dict(y=1, yanchor='top', x=0.5, xanchor='center')) + # Return the plot + return fig + ``` + +The provided `custom_chart` function is an example of the [custom chart](https://vizro.readthedocs.io/en/stable/pages/user-guides/custom-charts), and it returns a `go.Figure()` object. +This object must then be passed to the `figure` argument of the Vizro [Graph](https://vizro.readthedocs.io/en/stable/pages/user-guides/graph) model. + +!!! example "Vizro-AI chart within vizro dashboard" + === "Code for the cell" + ```py + from vizro import Vizro + import vizro.models as vm + from vizro.models.types import capture + import pandas as pd + import vizro.plotly.express as px + + + @capture('graph') + def custom_chart(data_frame=pd.DataFrame()): + # Create a bubble chart + fig = px.scatter(data_frame, x='gdpPercap', y='lifeExp', animation_frame='year', animation_group='country', + size='pop', color='continent', hover_name='country', log_x=True, size_max=60, + range_y=[20,90], + labels={'gdpPercap':'GDP per Capita', 'lifeExp':'Life Expectancy', 'year':'Year', 'continent':'Continent', 'pop':'Population'}, + title='Life Expectancy v. Per Capita GDP, 1952-2007', + height=400) + # Put the legend on top + fig.update_layout(legend=dict(y=1, yanchor='top', x=0.5, xanchor='center')) + # Return the plot + return fig + + + df = px.data.gapminder() + + page = vm.Page( + title = 'Demographics', + components = [ + vm.Graph(id='bubble chart', figure=custom_chart(df)), + vm.Graph(id='histogram', figure = px.box(df, + x='continent', + y='lifeExp', + color='continent', + title='Life Expectancy per Continent'))], + controls = [ + vm.Filter(column='country'), + vm.Filter(column='continent')]) + + Vizro().build(vm.Dashboard(pages=[page])).run(port=8000) + ``` + === "Result" + [![VizroDashboard]][VizroDashboard] + + [VizroDashboard]: ../../assets/user_guides/chart_into_dashboard_large.gif + + +### Use Vizro-AI dynamically to return a `fig` object + +Alternatively, we can use Vizro-AI dynamically and assign the output of `plot()` directly to the fig variable, enabling its reuse in the `vm.Graph.figure` argument. +This method offers streamlined efficiency, eliminating the need for code copying. +However, it is important to note that each dashboard run triggers an API call to OpenAI, potentially escalating costs, although this can be avoided if the `fig` object is stored and retrieved as needed, instead of making repeated calls to `plot()`. + +Executing the code below yields the identical dashboard as the example above. + + +!!! example "Use Vizro-AI fig directly in vizro dashboard" + === "Code for the cell" + ```py + from vizro import Vizro + import vizro.models as vm + import pandas as pd + import vizro.plotly.express as px + import vizro_ai + from vizro_ai import VizroAI + + from dotenv import load_dotenv + load_dotenv() + df = px.data.gapminder() + vizro_ai = VizroAI(model="gpt-4-0613") + + fig = vizro_ai.plot(df, + """Plot a bubble chart to show the changes in life expectancy + and GDP per capita for each country over time. + Color the bubbles by continent. + Add animation on yearly basis, and do not use facets. + Put the legend on top""") + + page = vm.Page( + title = 'Demographics', + components = [ + vm.Graph(id='bubble chart', figure=fig), + vm.Graph(id='histogram', figure = px.box(df, + x='continent', + y='lifeExp', + color='continent', + title='Life Expectancy per Continent'))], + controls = [ + vm.Filter(column='country'), + vm.Filter(column='continent')]) + + Vizro().build(vm.Dashboard(pages=[page])).run(port=8000) + ``` + === "Result" + [![VizroDashboard2]][VizroDashboard2] + + [VizroDashboard2]: ../../assets/user_guides/chart_into_dashboard_2.gif + +With Vizro-AI you can create visually captivating charts and plug them into your [Vizro](https://github.com/mckinsey/vizro/tree/main/vizro-core) dashboard in seconds! diff --git a/vizro-ai/docs/pages/user-guides/chart-examples.md b/vizro-ai/docs/pages/user-guides/chart-examples.md new file mode 100644 index 000000000..8287ede5b --- /dev/null +++ b/vizro-ai/docs/pages/user-guides/chart-examples.md @@ -0,0 +1,95 @@ +# Gallery of examples + +Take a look at some more advanced charts that can be created with Vizro-AI using data from [Plotly Express](https://plotly.com/python-api-reference/generated/plotly.express.data.html). The examples below use the OpenAI `"gpt-4-0613"` model as we are going to request specific updates to the layout of the charts, which are [more complex than the default GPT 3.5 model can handle](./customize-vizro-ai.md). + +### Polar bar chart + +A polar bar chart is a circular graph where each axis represents a different variable, typically used for displaying cyclical or directional data. +It's suitable for quickly comparing multiple variables across different categories or directions. Let's make one using Vizro-AI. + + +!!! example "Polar Bar Chart" + + === "Resulting chart" + [![VizroAIChart1]][VizroAIChart1] + + === "Code for the cell" + ```py + import vizro_ai + from vizro_ai import VizroAI + import plotly.express as px + + from dotenv import load_dotenv + load_dotenv() + + df = px.data.wind() + + vizro_ai = VizroAI(model="gpt-4-0613") + vizro_ai.plot(df, + """Describe wind frequency and direction using bar_polar chart. + Increase the width and height of the figure. + Improve layout by placing title to the left. Show legend""", explain=True) + + ``` + + [VizroAIChart1]: ../../assets/user_guides/polar_bar_chart.png + + +### Geographical map chart + +The next chart we'll look at is a geographical map chart to visualize spatial patterns in data, which often reveals insights not seen in other charts. + +!!! example "Map chart" + + === "Resulting chart" + [![VizroAIChart2]][VizroAIChart2] + + === "Code for the cell" + ```py + import vizro_ai + from vizro_ai import VizroAI + import plotly.express as px + + from dotenv import load_dotenv + load_dotenv() + + df = px.data.wind() + + vizro_ai = VizroAI(model="gpt-4-0613") + vizro_ai.plot(df, + """Visualize life expectancy over the years using map chart. Use life expectancy as the color dimension. + Improve layout by using Arial font. Increase the width and height of the map area. Outline continents on the map. + Show countries on the map. + Increase the width and height of the figure.""", explain=True) + + ``` + + [VizroAIChart2]: ../../assets/user_guides/map_chart.gif + + +### 3D surface plot + +Let's explore how to generate a 3-dimensional surface plot with VizroAI. + +!!! example "Surface plot" + + === "Resulting chart" + [![VizroAIChart3]][VizroAIChart3] + + === "Code for the cell" + ```py + import vizro_ai + from vizro_ai import VizroAI + import plotly.express as px + + from dotenv import load_dotenv + load_dotenv() + + df = px.data.gapminder() + + vizro_ai = VizroAI(model="gpt-4-0613") + vizro_ai.plot(df, "create a surface plot") + + ``` + + [VizroAIChart3]: ../../assets/user_guides/surface_plot.gif diff --git a/vizro-ai/docs/pages/user-guides/create-advanced-charts.md b/vizro-ai/docs/pages/user-guides/create-advanced-charts.md index 1318fbf9c..054b05ce5 100644 --- a/vizro-ai/docs/pages/user-guides/create-advanced-charts.md +++ b/vizro-ai/docs/pages/user-guides/create-advanced-charts.md @@ -1,7 +1,7 @@ # How to create advanced charts Now, let's explore more advanced visualizations and use Vizro-AI for enhanced formatting. -To begin, we'll create an animated bar chart illustrating the population development of each continent over time. Run the code below and look at the result. +We'll create an animated bar chart illustrating the population development of each continent over time. Run the code below and look at the result. !!! example "Vizro-AI animated chart" diff --git a/vizro-ai/docs/pages/user-guides/customize-vizro-ai.md b/vizro-ai/docs/pages/user-guides/customize-vizro-ai.md new file mode 100644 index 000000000..0ac4e4679 --- /dev/null +++ b/vizro-ai/docs/pages/user-guides/customize-vizro-ai.md @@ -0,0 +1,87 @@ +# How to customize model usage + +## Supported models +Vizro-AI currently supports [OpenAI models](https://platform.openai.com/docs/models) as follows, although we are working on supporting more vendors: + +- `gpt-3.5-turbo` (default model) +- `gpt-4-turbo` (recommended for best model performance) +- `gpt-3.5-turbo-0125` +- `gpt-3.5-turbo-1106` +- `gpt-4-0613` +- `gpt-4-1106-preview` +- `gpt-3.5-turbo-0613` (to be deprecated on June 13, 2024) + + +These models offer different levels of performance and cost to Vizro-AI users: + +* The **gpt-3.5** model series have lower price point and higher speeds for providing answers, but do not offer sophisticated charting. + +* Consider upgrading to the **gpt-4** models for more demanding tasks. While they are part of a more capable GPT model series, their response time is slower than gpt-3.5 models, and they come at a higher cost. + +Refer to the [OpenAI documentation for more about model capabilities](https://platform.openai.com/docs/models/overview) and [pricing](https://openai.com/pricing). + +## Default initialization +`VizroAI` can be initialized without any arguments, in which case it uses `"gpt-3.5-turbo"` by default, with a temperature setting of 0. `"gpt-3.5-turbo"` offers enhanced speed and accuracy, and generates responses in requested formats while maintaining cost-effective performance. + +## Customization at initialization +To customize the model, you can pass `VizroAI` a single argument named `model`, which can either be a string that specifies the name of a `ChatOpenAI` model or an instantiated [`ChatOpenAI`](https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html) model. + +The example below uses the OpenAI model name in a string form: + +!!! example "Customize with string" + + === "python" + ```py linenums="1" + from vizro_ai import VizroAI + + vizro_ai = VizroAI(model="gpt-3.5-turbo-0125") + ``` + +The example below customizes the `ChatOpenAI` instance further beyond the chosen default from the string instantiation. We pass the `"gpt-3.5-turbo-0125"` model from OpenAI as `model_name` for `ChatOpenAI`, which offers improved response accuracy, we also want to increase maximum number of retries. +It's important to mention that any parameter that could be used in the `openai.create` call is also usable in `ChatOpenAI`. For more customization options for `ChatOpenAI`, refer to the [LangChain ChatOpenAI docs](https://api.python.langchain.com/en/latest/chat_models/langchain_openai.chat_models.base.ChatOpenAI.html) + + +To ensure a deterministic answer to our queries, we've set the temperature to 0. If you prefer more creative (but potentially more unstable) responses, you can raise the temperature to a maximum of 1. + + +!!! example "Customize with instantiated model" + + === "python" + ```py linenums="1" + from vizro_ai import VizroAI + from langchain_openai import ChatOpenAI + + import vizro.plotly.express as px + df = px.data.gapminder() + + llm = ChatOpenAI( + model_name="gpt-3.5-turbo-0125", + temperature=0, + max_retries=3, + ) + vizro_ai = VizroAI(model=llm) + vizro_ai.plot(df, "describe the composition of gdp in continent") + ``` + +## Azure OpenAI models +To set up Azure OpenAI with VizroAI, you'll need to configure the `AzureOpenAI` instance by specifying your deployment name and model name using LangChain. You can also set your environment variables for API configuration, +such as `OPENAI_API_TYPE`, `OPENAI_API_VERSION`, `OPENAI_API_BASE` and `OPENAI_API_KEY`. +Authentication can be done via an API key directly or through Azure Active Directory (AAD) for enhanced security. +For a detailed walk-through, refer to the [LangChain documentation](https://python.langchain.com/docs/integrations/llms/azure_openai/). + +Here is an example of how to set the LLM model to be an AzureOpenAI model: +!!! example "Use Azure OpenAI model" + + === "python" + ```py linenums="1" + from langchain_openai import AzureOpenAI + from vizro_ai import VizroAI + + # Create an instance of Azure OpenAI + # Replace the deployment name with your own + llm = AzureOpenAI( + deployment_name="td2", + model_name="gpt-3.5-turbo-instruct", + ) + vizro_ai = VizroAI(model=llm) + ``` diff --git a/vizro-ai/docs/pages/get-started/install.md b/vizro-ai/docs/pages/user-guides/install.md similarity index 100% rename from vizro-ai/docs/pages/get-started/install.md rename to vizro-ai/docs/pages/user-guides/install.md diff --git a/vizro-ai/docs/pages/user-guides/run-vizro-ai.md b/vizro-ai/docs/pages/user-guides/run-vizro-ai.md index 420094007..39d56c865 100644 --- a/vizro-ai/docs/pages/user-guides/run-vizro-ai.md +++ b/vizro-ai/docs/pages/user-guides/run-vizro-ai.md @@ -6,7 +6,7 @@ This guide offers insights into different ways of running Vizro-AI code, includi To run Vizro-AI code in a Jupyter Notebook, create a new cell and execute the code below to render the described visualization. You should see the chart as output. ??? note "Note: API key" - Make sure you have followed the [LLM setup guide](../get-started/install.md#large-language-model) and + Make sure you have followed the [LLM setup guide](../user-guides/install.md#set-up-access-to-a-large-language-model) and your api key is set up in a `.env` file in the same folder as your Notebook file (`.ipynb`). !!! example "Bar chart" @@ -33,7 +33,7 @@ You can use Vizro-AI in any standard development environment by creating a `.py` ??? note "Note: API key" - Make sure you have followed [LLM setup guide](../get-started/install.md#large-language-model) and + Make sure you have followed [LLM setup guide](../user-guides/install.md#large-language-model) and your API key is set up in the environment where your `.py` script is running with command as below: ```bash @@ -50,7 +50,8 @@ You can use Vizro-AI in any standard development environment by creating a `.py` vizro_ai = VizroAI() df = px.data.gapminder() - vizro_ai.plot(df, "describe life expectancy per continent over time") + fig = vizro_ai.plot(df, "describe life expectancy per continent over time") + fig.show() ``` === "Result" [![LineChart]][LineChart] @@ -61,28 +62,46 @@ You can use Vizro-AI in any standard development environment by creating a `.py` You may prefer to integrate Vizro-AI into an application with a UI that users use to input prompts using a text field. -Vizro-AI's `_get_chart_code` method returns the Python code string that can be used to prepare the data and create the visualization. This code is validated and debugged to ensure that it is executable and ready to be integrated. +There are two ways to integrate Vizro-AI into an application, directly and by accessing the chart code behind a `fig` object. -!!! example "Application integration" +1. Vizro-AI's `plot` method returns a `plotly.graph_objects` object (`fig`) that can be used directly within a `Vizro` dashboard. - === "app.py" - ```py - import vizro.plotly.express as px - from vizro_ai import VizroAI + !!! example "Direct application integration" - vizro_ai = VizroAI() + === "app.py" + ```py + import vizro.plotly.express as px + from vizro_ai import VizroAI - df = px.data.gapminder() - code_string = vizro_ai._get_chart_code(df, "describe life expectancy per continent over time") - ``` - === "code_string" - [![ResultCode]][ResultCode] + vizro_ai = VizroAI() + + df = px.data.gapminder() + fig = vizro_ai.plot(df, "describe life expectancy per continent over time") + ``` + + +2. Vizro-AI's `_get_chart_code` method returns a string of Python code that manipulates the data and creates the visualization. Vizro-AI validates the code to ensure that it is executable and can be integrated. + + !!! example "Application integration via chart code" + + === "app.py" + ```py + import vizro.plotly.express as px + from vizro_ai import VizroAI + + vizro_ai = VizroAI() + + df = px.data.gapminder() + code_string = vizro_ai._get_chart_code(df, "describe life expectancy per continent over time") + ``` + === "code_string" + [![ResultCode]][ResultCode] - [ResultCode]: ../../assets/user_guides/code_string_app_integration.png + [ResultCode]: ../../assets/user_guides/code_string_app_integration.png -The returned `code_string` can be used to dynamically render charts within your application. You may have the option to encapsulate the chart within a `fig` object or convert the figure into a JSON string for further integration. + The returned `code_string` can be used to dynamically render charts within your application. You may have the option to encapsulate the chart within a `fig` object or convert the figure into a JSON string for further integration. -To use the insights or code explanation, you can use `vizro_ai._run_plot_tasks(df, ..., explain=True)`, which returns a dictionary containing the code explanation and chart insights alongside the code. + To use the insights or code explanation, you can use `vizro_ai._run_plot_tasks(df, ..., explain=True)`, which returns a dictionary containing the code explanation and chart insights alongside the code. ### How to use `max_debug_retry` parameter in plot function - Default Value: 3 diff --git a/vizro-ai/examples/example.ipynb b/vizro-ai/examples/example.ipynb index 88abcfc88..372a958c3 100644 --- a/vizro-ai/examples/example.ipynb +++ b/vizro-ai/examples/example.ipynb @@ -15,14 +15,17 @@ "vizro_ai = VizroAI()\n", "\n", "# uncomment below to update model to gpt4 if you would like to create more complex chart\n", - "# vizro_ai = VizroAI(model_name=\"gpt-4-0613\")" + "# vizro_ai = VizroAI(model=\"gpt-4-0613\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -36,7 +39,7 @@ "metadata": {}, "outputs": [], "source": [ - "import plotly.express as px\n", + "import vizro.plotly.express as px\n", "df = px.data.gapminder()\n", "df" ] @@ -45,7 +48,10 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": false + "collapsed": false, + "jupyter": { + "outputs_hidden": false + } }, "outputs": [], "source": [ @@ -87,7 +93,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.16" + "version": "3.9.12" } }, "nbformat": 4, diff --git a/vizro-ai/examples/example.py b/vizro-ai/examples/example.py index 6183fa9e6..1789f0256 100644 --- a/vizro-ai/examples/example.py +++ b/vizro-ai/examples/example.py @@ -1,8 +1,9 @@ """AI plot example.""" -import plotly.express as px +import vizro.plotly.express as px from vizro_ai import VizroAI vizro_ai = VizroAI() df = px.data.gapminder() -vizro_ai.plot(df, "describe the composition of gdp in continent,and horizontal line for avg gdp") +fig = vizro_ai.plot(df, "describe the composition of gdp in continent,and horizontal line for avg gdp") +fig.show() diff --git a/vizro-ai/hatch.toml b/vizro-ai/hatch.toml index 51f4cee25..daa68982c 100644 --- a/vizro-ai/hatch.toml +++ b/vizro-ai/hatch.toml @@ -29,7 +29,7 @@ prep-release = [ "hatch version release", "hatch run changelog:collect", "hatch run changelog:add", - 'echo "Now raise a PR to merge into main with title: Release of vizro-ai $(hatch version)"' + 'echo "Now raise a PR to merge into main with title: [Release] Release of vizro-ai $(hatch version)"' ] pypath = "hatch run python -c 'import sys; print(sys.executable)'" secrets = "pre-commit run gitleaks --all-files" @@ -48,10 +48,18 @@ dependencies = [ "mkdocs", "mkdocs-material", "mkdocs-git-revision-date-localized-plugin", - "mkdocstrings[python]" + "mkdocstrings[python]", + "linkchecker" ] detached = true -scripts = {serve = "mkdocs serve"} + +[envs.docs.scripts] +build = "mkdocs build --strict" +# Disable warnings on the linkcheck so that HTTP redirects are accepted. We could ignore only that warning and specify +# more advanced settings using a linkcheckerrc config file. openai.com doesn't seem to work well with linkchecking, +# throwing 403 errors, but these are not real errors. +link-check = "linkchecker site --check-extern --no-warnings --ignore=404.html --ignore-url=127.0.0.1 --ignore-url=https://platform.openai.com/docs/models --ignore-url=openai.com --ignore-url=https://openai.com/" +serve = "mkdocs serve --strict" [envs.lint] dependencies = [ diff --git a/vizro-ai/mkdocs.yml b/vizro-ai/mkdocs.yml index 4f7804f1e..bb4ddfd9a 100644 --- a/vizro-ai/mkdocs.yml +++ b/vizro-ai/mkdocs.yml @@ -2,21 +2,30 @@ site_name: Vizro-AI site_url: https://vizro-ai.readthedocs.io/en/latest/ nav: - Vizro-AI: index.md - - Get started: - - Install Vizro-AI: pages/get-started/install.md - - A first example: pages/get-started/quickstart.md + - Tutorials: + - A first example: pages/tutorials/quickstart.md - How-to guides: - - Run methods: pages/user-guides/run-vizro-ai.md - - Create advanced charts: pages/user-guides/create-advanced-charts.md - - Use different languages: pages/user-guides/use-different-languages.md + - FUNDAMENTALS: + - Install Vizro-AI: pages/user-guides/install.md + - Run methods: pages/user-guides/run-vizro-ai.md + - Customize models: pages/user-guides/customize-vizro-ai.md + - USAGE OPTIONS: + - Advanced charts: pages/user-guides/create-advanced-charts.md + - Different languages: pages/user-guides/use-different-languages.md + - Vizro-AI charts in dashboards: pages/user-guides/add-generated-chart-usecase.md + - API Reference: + - VizroAI: pages/API-reference/vizro-ai.md - Explanation: - FAQs: pages/explanation/faq.md - Disclaimer: pages/explanation/disclaimer.md - Safeguard code execution: pages/explanation/safeguard.md - Safety in Vizro-AI: pages/explanation/safety-in-vizro-ai.md - - Contribute: - # - Contributing: pages/contribute/contributing.md - - Authors: pages/contribute/authors.md + - Examples: + - Chart examples: pages/user-guides/chart-examples.md + #- Contribute: + # - Contributing: pages/contribute/contributing.md + - Vizro: + - Vizro: https://vizro.readthedocs.io # This infers the version number of the latest release of whole repo, so we're hiding it for sub-packed in extra.css repo_url: https://github.com/mckinsey/vizro/tree/main/vizro-ai @@ -28,8 +37,8 @@ theme: palette: - scheme: default font: - text: Roboto - code: Roboto Mono + text: Inter + code: Inter Regular icon: repo: fontawesome/brands/github features: @@ -64,6 +73,9 @@ markdown_extensions: - pymdownx.mark - attr_list - md_in_html + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg plugins: - search @@ -81,6 +93,7 @@ plugins: show_root_heading: true docstring_options: ignore_init_summary: true + warn_unknown_params: false paths: [src] - git-revision-date-localized: enable_creation_date: false diff --git a/vizro-ai/pyproject.toml b/vizro-ai/pyproject.toml index b08d14e1e..9ded3fca5 100644 --- a/vizro-ai/pyproject.toml +++ b/vizro-ai/pyproject.toml @@ -17,7 +17,7 @@ dependencies = [ "pandas", "tabulate", "openai>=1.0.0", - "langchain>=0.1.0", + "langchain>=0.1.0, <0.3.0", # TODO update all LLMChain class and remove upper bound "langchain-openai", "python-dotenv>=1.0.0", # TODO decide env var management to see if we need this "vizro>=0.1.4", # TODO set upper bound later @@ -58,5 +58,8 @@ filterwarnings = [ # Ignore until pandas is made compatible with Python 3.12: "ignore:.*utcfromtimestamp:DeprecationWarning", # Ignore until pandas 3 is released: - "ignore:(?s).*Pyarrow will become a required dependency of pandas:DeprecationWarning" + "ignore:(?s).*Pyarrow will become a required dependency of pandas:DeprecationWarning", + # TODO update all LLMChain class and remove this warning + # Ignore LLMchian deprecation warning: + "ignore:.*The class `LLMChain` was deprecated in LangChain 0.1.17" ] diff --git a/vizro-ai/snyk/requirements.txt b/vizro-ai/snyk/requirements.txt index a3d54cc78..ecc8cdf98 100644 --- a/vizro-ai/snyk/requirements.txt +++ b/vizro-ai/snyk/requirements.txt @@ -1,7 +1,7 @@ pandas tabulate openai>=1.0.0 -langchain>=0.1.0 +langchain>=0.1.0, <0.3.0 langchain-openai python-dotenv>=1.0.0 vizro>=0.1.4 diff --git a/vizro-ai/src/vizro_ai/__init__.py b/vizro-ai/src/vizro_ai/__init__.py index d85ad295d..595bb9d31 100644 --- a/vizro-ai/src/vizro_ai/__init__.py +++ b/vizro-ai/src/vizro_ai/__init__.py @@ -9,6 +9,6 @@ __all__ = ["VizroAI"] -__version__ = "0.1.3.dev0" +__version__ = "0.2.1.dev0" logging.basicConfig(level=os.getenv("VIZRO_AI_LOG_LEVEL", "INFO")) diff --git a/vizro-ai/src/vizro_ai/_vizro_ai.py b/vizro-ai/src/vizro_ai/_vizro_ai.py index 2d01fd341..8417ca583 100644 --- a/vizro-ai/src/vizro_ai/_vizro_ai.py +++ b/vizro-ai/src/vizro_ai/_vizro_ai.py @@ -1,13 +1,20 @@ import logging -from typing import Any, Dict, Union +from typing import Any, Dict, Optional, Union import pandas as pd +import plotly.graph_objects as go +from langchain_openai import ChatOpenAI -from vizro_ai.chains import ModelConstructor -from vizro_ai.chains._llm_models import LLM_MODELS +from vizro_ai.chains._llm_models import _get_llm_model from vizro_ai.components import GetCodeExplanation, GetDebugger from vizro_ai.task_pipeline._pipeline_manager import PipelineManager -from vizro_ai.utils.helper import DebugFailure, _debug_helper, _display_markdown_and_chart, _exec_code, _is_jupyter +from vizro_ai.utils.helper import ( + DebugFailure, + _debug_helper, + _exec_code_and_retrieve_fig, + _exec_fig_code_display_markdown, + _is_jupyter, +) logger = logging.getLogger(__name__) @@ -15,25 +22,22 @@ class VizroAI: """Vizro-AI main class.""" - model_constructor: ModelConstructor = ModelConstructor() pipeline_manager: PipelineManager = PipelineManager() _return_all_text: bool = False - def __init__(self, model_name: str = "gpt-3.5-turbo-0613", temperature: int = 0): + def __init__(self, model: Optional[Union[ChatOpenAI, str]] = None): """Initialization of VizroAI. Args: - model_name: Model name in string format. - temperature: Temperature parameter for LLM. + model: model instance or model name. """ - self.model_name = model_name - self.temperature = temperature + self.model = _get_llm_model(model=model) self.components_instances = {} - self._llm_to_use = None + # TODO add pending URL link to docs logger.info( - f"You have selected {self.model_name}," + f"You have selected {self.model.model_name}," f"Engaging with LLMs (Large Language Models) carries certain risks. " f"Users are advised to become familiar with these risks to make informed decisions, " f"and visit this page for detailed information: " @@ -41,19 +45,14 @@ def __init__(self, model_name: str = "gpt-3.5-turbo-0613", temperature: int = 0) ) self._set_task_pipeline_llm() - @property - def llm_to_use(self) -> LLM_MODELS: - _llm_to_use = self.model_constructor.get_llm_model(self.model_name, self.temperature) - return _llm_to_use - def _set_task_pipeline_llm(self) -> None: - self.pipeline_manager.llm = self.llm_to_use + self.pipeline_manager.llm = self.model # TODO delete after adding debug in pipeline def _lazy_get_component(self, component_class: Any) -> Any: # TODO configure component_class type """Lazy initialization of components.""" if component_class not in self.components_instances: - self.components_instances[component_class] = component_class(llm=self.llm_to_use) + self.components_instances[component_class] = component_class(llm=self.model) return self.components_instances[component_class] def _run_plot_tasks( @@ -105,7 +104,7 @@ def _get_chart_code(self, df: pd.DataFrame, user_input: str) -> str: def plot( self, df: pd.DataFrame, user_input: str, explain: bool = False, max_debug_retry: int = 3 - ) -> Union[None, Dict[str, Any]]: + ) -> Union[go.Figure, Dict[str, Any]]: """Plot visuals using vizro via english descriptions, english to chart translation. Args: @@ -114,6 +113,9 @@ def plot( explain: Flag to include explanation in response. max_debug_retry: Maximum number of retries to debug errors. Defaults to `3`. + Returns: + Plotly Figure object or a dictionary containing data + """ output_dict = self._run_plot_tasks(df, user_input, explain=explain, max_debug_retry=max_debug_retry) code_string = output_dict.get("code_string") @@ -125,12 +127,13 @@ def plot( "Chart creation failed. Retry debugging has reached maximum limit. Try to rephrase the prompt, " "or try to select a different model. Fallout response is provided: \n\n" + code_string ) + + # TODO Tentative for integration test + if self._return_all_text: + return output_dict if not explain: - _exec_code(code=code_string, local_args={"df": df}, show_fig=True, is_notebook_env=_is_jupyter()) + return _exec_code_and_retrieve_fig(code=code_string, local_args={"df": df}, is_notebook_env=_is_jupyter()) if explain: - _display_markdown_and_chart( + return _exec_fig_code_display_markdown( df=df, code_snippet=code_string, biz_insights=business_insights, code_explain=code_explanation ) - # TODO Tentative for integration test - if self._return_all_text: - return output_dict diff --git a/vizro-ai/src/vizro_ai/chains/__init__.py b/vizro-ai/src/vizro_ai/chains/__init__.py index 5471358ca..d55cc05c0 100644 --- a/vizro-ai/src/vizro_ai/chains/__init__.py +++ b/vizro-ai/src/vizro_ai/chains/__init__.py @@ -1,6 +1,5 @@ # TODO clean up latter for chain from ._llm_chain import FunctionCallChain -from ._llm_models import ModelConstructor -__all__ = ["FunctionCallChain", "ModelConstructor"] +__all__ = ["FunctionCallChain"] diff --git a/vizro-ai/src/vizro_ai/chains/_llm_models.py b/vizro-ai/src/vizro_ai/chains/_llm_models.py index 393b26e20..e9bd9f4e7 100644 --- a/vizro-ai/src/vizro_ai/chains/_llm_models.py +++ b/vizro-ai/src/vizro_ai/chains/_llm_models.py @@ -1,88 +1,69 @@ -from typing import Callable, Dict, List, Union +from typing import Dict, Optional, Union from langchain_openai import ChatOpenAI -try: - from pydantic.v1 import BaseModel, Field -except ImportError: # pragma: no cov - from pydantic import BaseModel, Field - # TODO add new wrappers in if new model support is added LLM_MODELS = Union[ChatOpenAI] # TODO constant of model inventory, can be converted to yaml and link to docs -PREDEFINED_MODELS: List[Dict[str, any]] = [ - { - "name": "gpt-3.5-turbo-0613", +PREDEFINED_MODELS: Dict[str, Dict[str, Union[int, LLM_MODELS]]] = { + "gpt-3.5-turbo-0613": { "max_tokens": 4096, "wrapper": ChatOpenAI, }, - { - "name": "gpt-4-0613", + "gpt-4-0613": { "max_tokens": 8192, "wrapper": ChatOpenAI, }, - { - "name": "gpt-3.5-turbo-1106", + "gpt-3.5-turbo-1106": { "max_tokens": 16385, "wrapper": ChatOpenAI, }, - { - "name": "gpt-4-1106-preview", + "gpt-4-1106-preview": { "max_tokens": 128000, "wrapper": ChatOpenAI, }, -] - + "gpt-3.5-turbo-0125": { + "max_tokens": 16385, + "wrapper": ChatOpenAI, + }, + "gpt-3.5-turbo": { + "max_tokens": 16385, + "wrapper": ChatOpenAI, + }, + "gpt-4-turbo": { + "max_tokens": 128000, + "wrapper": ChatOpenAI, + }, +} -class LLM(BaseModel): - """Represents a Language Learning Model (LLM). +DEFAULT_MODEL = "gpt-3.5-turbo" +DEFAULT_TEMPERATURE = 0 - Attributes - name (str): The name of the LLM. - max_tokens (int): The maximum number of tokens that the LLM can handle. - wrapper (callable): The langchain function used to instantiate the model. - """ +def _get_llm_model(model: Optional[Union[ChatOpenAI, str]] = None) -> LLM_MODELS: + """Fetches and initializes an instance of the LLM. - name: str - max_tokens: int - wrapper: Callable = Field(..., description="The langchain function used to instantiate the model.") + Args: + model: Model instance or model name. + Returns: + The initialized instance of the LLM. -class ModelConstructor: - """Manages available Language Learning Models (LLMs). + Raises: + ValueError: If the provided model string does not match any pre-defined model - Provides methods to fetch LLM details and instantiate appropriate wrappers. """ - - models: Dict[str, LLM] - - def __init__(self): - """Initializes the model manager with a set of predefined LLMs.""" - self.models = {model["name"]: LLM(**model) for model in PREDEFINED_MODELS} - - def get_llm_model(self, model_name: str, temperature: float = 0) -> LLM_MODELS: - """Fetches and initializes an instance of the LLM. - - Args: - model_name (str): The name of the LLM. - temperature (int, optional): A parameter for the wrapper. Defaults to 0. - - Returns: - The initialized instance of the LLM. - - Raises: - ValueError: If the model name is not found. - - """ - model = self.models.get(model_name.lower()) - if model: - return model.wrapper(model_name=model.name, temperature=temperature) - else: - raise ValueError(f"Model {model_name} not found!") + if not model: + return ChatOpenAI(model_name=DEFAULT_MODEL, temperature=DEFAULT_TEMPERATURE) + if isinstance(model, ChatOpenAI): + return model + if isinstance(model, str) and model in PREDEFINED_MODELS: + return PREDEFINED_MODELS.get(model)["wrapper"](model_name=model, temperature=DEFAULT_TEMPERATURE) + raise ValueError( + f"Model {model} not found! List of available model can be found at https://vizro.readthedocs.io/projects/vizro-ai/en/latest/pages/explanation/faq/#which-llms-are-supported-by-vizro-ai" + ) if __name__ == "__main__": - model_manager = ModelConstructor() - llm_chat_openai = model_manager.get_llm_model("gpt-3.5-turbo-0613", temperature=0) + llm_chat_openai = _get_llm_model() diff --git a/vizro-ai/src/vizro_ai/components/chart_selection.py b/vizro-ai/src/vizro_ai/components/chart_selection.py index aaa20a23a..a14353639 100644 --- a/vizro-ai/src/vizro_ai/components/chart_selection.py +++ b/vizro-ai/src/vizro_ai/components/chart_selection.py @@ -106,12 +106,11 @@ def _chart_to_use(load_args) -> str: if __name__ == "__main__": - import plotly.express as px + import vizro.plotly.express as px - from vizro_ai.chains import ModelConstructor + from vizro_ai.chains._llm_models import _get_llm_model - model_constructor = ModelConstructor() - llm_to_use = model_constructor.get_llm_model("gpt-3.5-turbo-0613", temperature=0) + llm_to_use = _get_llm_model() df = px.data.gapminder() diff --git a/vizro-ai/src/vizro_ai/components/code_validation.py b/vizro-ai/src/vizro_ai/components/code_validation.py index b0ac1db77..ab2cbd650 100644 --- a/vizro-ai/src/vizro_ai/components/code_validation.py +++ b/vizro-ai/src/vizro_ai/components/code_validation.py @@ -86,12 +86,11 @@ def run(self, code_snippet: str, chain_input: str = "") -> str: if __name__ == "__main__": - import plotly.express as px + import vizro.plotly.express as px - from vizro_ai.chains import ModelConstructor + from vizro_ai.chains._llm_models import _get_llm_model - model_constructor = ModelConstructor() - llm_to_use = model_constructor.get_llm_model("gpt-3.5-turbo-0613", temperature=0) + llm_to_use = _get_llm_model() df = px.data.gapminder() test_code_snippet = "import numpy as np\n" "import pandas as pd\n" "\n" "print(df['country', 'continent'])\n" diff --git a/vizro-ai/src/vizro_ai/components/custom_chart_wrap.py b/vizro-ai/src/vizro_ai/components/custom_chart_wrap.py index f0c52a816..5ff938368 100644 --- a/vizro-ai/src/vizro_ai/components/custom_chart_wrap.py +++ b/vizro-ai/src/vizro_ai/components/custom_chart_wrap.py @@ -103,12 +103,11 @@ def _add_capture_code(code_string: str) -> str: if __name__ == "__main__": - import plotly.express as px + import vizro.plotly.express as px - from vizro_ai.chains import ModelConstructor + from vizro_ai.chains._llm_models import _get_llm_model - model_constructor = ModelConstructor() - llm_to_use = model_constructor.get_llm_model("gpt-3.5-turbo-0613", temperature=0) + llm_to_use = _get_llm_model() df = px.data.gapminder() outcome_visual_tool = """ diff --git a/vizro-ai/src/vizro_ai/components/dataframe_craft.py b/vizro-ai/src/vizro_ai/components/dataframe_craft.py index 460b02e84..37741a4f6 100755 --- a/vizro-ai/src/vizro_ai/components/dataframe_craft.py +++ b/vizro-ai/src/vizro_ai/components/dataframe_craft.py @@ -123,12 +123,11 @@ def _format_dataframe_string(s: str) -> str: if __name__ == "__main__": - import plotly.express as px + import vizro.plotly.express as px - from vizro_ai.chains import ModelConstructor + from vizro_ai.chains._llm_models import _get_llm_model - model_manager = ModelConstructor() - llm_to_use = model_manager.get_llm_model("gpt-3.5-turbo-0613", temperature=0) + llm_to_use = _get_llm_model() df = px.data.gapminder() test_df_crafter = GetDataFrameCraft(llm=llm_to_use) diff --git a/vizro-ai/src/vizro_ai/components/explanation.py b/vizro-ai/src/vizro_ai/components/explanation.py index 5cc2e9873..f8a918aac 100644 --- a/vizro-ai/src/vizro_ai/components/explanation.py +++ b/vizro-ai/src/vizro_ai/components/explanation.py @@ -97,10 +97,9 @@ def _text_cleanup(load_args) -> str: if __name__ == "__main__": - from vizro_ai.chains import ModelConstructor + from vizro_ai.chains._llm_models import _get_llm_model - model_constructor = ModelConstructor() - llm_to_use = model_constructor.get_llm_model("gpt-3.5-turbo-0613", temperature=0) + llm_to_use = _get_llm_model() code_snippet = """ from vizro.models.types import capture diff --git a/vizro-ai/src/vizro_ai/components/visual_code.py b/vizro-ai/src/vizro_ai/components/visual_code.py index aeadb884e..137f15bc7 100644 --- a/vizro-ai/src/vizro_ai/components/visual_code.py +++ b/vizro-ai/src/vizro_ai/components/visual_code.py @@ -101,12 +101,11 @@ def _clean_visual_code(raw_code: str) -> str: if __name__ == "__main__": - import plotly.express as px + import vizro.plotly.express as px - from vizro_ai.chains import ModelConstructor + from vizro_ai.chains._llm_models import _get_llm_model - model_constructor = ModelConstructor() - llm_to_use = model_constructor.get_llm_model("gpt-3.5-turbo-0613", temperature=0) + llm_to_use = _get_llm_model() df = px.data.gapminder() # df_code output from df craft: diff --git a/vizro-ai/src/vizro_ai/utils/helper.py b/vizro-ai/src/vizro_ai/utils/helper.py index 5f0f130ea..c7b6744f7 100644 --- a/vizro-ai/src/vizro_ai/utils/helper.py +++ b/vizro-ai/src/vizro_ai/utils/helper.py @@ -4,6 +4,7 @@ from typing import Callable, Dict, Optional import pandas as pd +import plotly.graph_objects as go from .safeguard import _safeguard_check @@ -34,7 +35,7 @@ def _debug_helper( is_jupyter = _is_jupyter() for attempt in range(max_debug_retry): try: - _exec_code(code=code_string, local_args={"df": df}, is_notebook_env=is_jupyter) + _exec_code_and_retrieve_fig(code=code_string, local_args={"df": df}, is_notebook_env=is_jupyter) retry_success = True break except Exception as e: @@ -47,16 +48,22 @@ def _debug_helper( return {"debug_status": retry_success, "code_string": code_string} -def _exec_code( - code: str, local_args: Optional[Dict] = None, show_fig: bool = False, is_notebook_env: bool = True -) -> None: - """Execute code in notebook with correct namespace.""" +def _exec_code_and_retrieve_fig( + code: str, local_args: Optional[Dict] = None, is_notebook_env: bool = True +) -> go.Figure: + """Execute code in notebook with correct namespace and return fig object. + + Args: + code: code string to be executed + local_args: additional local arguments + is_notebook_env: boolean flag indicating if code is run in Jupyter notebook + + Returns: + go.Figure + + """ from IPython import get_ipython - if show_fig and "\nfig.show()" not in code: - code += "\nfig.show()" - elif not show_fig: - code = code.replace("fig.show()", "") namespace = get_ipython().user_ns if is_notebook_env else globals() if local_args: namespace.update(local_args) @@ -64,10 +71,26 @@ def _exec_code( exec(code, namespace) # nosec + dashboard_ready_fig = namespace["fig"] + return dashboard_ready_fig + -def _display_markdown_and_chart(df: pd.DataFrame, code_snippet: str, biz_insights: str, code_explain: str) -> None: +def _exec_fig_code_display_markdown( + df: pd.DataFrame, code_snippet: str, biz_insights: str, code_explain: str +) -> go.Figure: # TODO change default test str to other - """Display chart and Markdown format description in jupyter.""" + """Display chart and Markdown format description in jupyter and returns fig object. + + Args: + df: The dataframe to be analyzed. + code_snippet: code string to be executed + biz_insights: business insights to be displayed in markdown cell + code_explain: code explanation to be displayed in markdown cell + + Returns: + go.Figure + + """ try: # pylint: disable=import-outside-toplevel from IPython.display import Markdown, display @@ -77,7 +100,7 @@ def _display_markdown_and_chart(df: pd.DataFrame, code_snippet: str, biz_insight markdown_code = f"```\n{code_snippet}\n```" output_text = f"

Insights:

\n\n{biz_insights}\n

Code:

\n\n{code_explain}\n{markdown_code}" display(Markdown(output_text)) - _exec_code(code_snippet, local_args={"df": df}, show_fig=True, is_notebook_env=_is_jupyter()) + return _exec_code_and_retrieve_fig(code_snippet, local_args={"df": df}, is_notebook_env=_is_jupyter()) class DebugFailure(Exception): diff --git a/vizro-ai/tests/integration/test_example.py b/vizro-ai/tests/integration/test_example.py index 86c12d9b0..a81ddb79b 100644 --- a/vizro-ai/tests/integration/test_example.py +++ b/vizro-ai/tests/integration/test_example.py @@ -1,4 +1,4 @@ -import plotly.express as px +import vizro.plotly.express as px from hamcrest import all_of, any_of, assert_that, contains_string, equal_to from vizro_ai import VizroAI @@ -11,7 +11,15 @@ def test_chart(): resp = vizro_ai.plot(df, "describe the composition of scatter chart with gdp in continent") assert_that( resp["code_string"], - all_of(contains_string("px.scatter"), contains_string("x='continent'"), contains_string("y='gdpPercap'")), + all_of(contains_string("px.scatter")), + ) + assert_that( + resp["code_string"], + any_of(contains_string("x='continent'"), contains_string("x='gdpPercap'")), + ) + assert_that( + resp["code_string"], + any_of(contains_string("y='count'"), contains_string("y='gdpPercap'"), contains_string("y='continent'")), ) assert_that(resp["business_insights"], equal_to(None)) assert_that(resp["code_explanation"], equal_to(None)) @@ -22,10 +30,19 @@ def test_chart_with_explanation(): resp = vizro_ai.plot(df, "describe the composition of gdp in US", explain=True) assert_that( resp["code_string"], - all_of(contains_string("px.bar"), contains_string("x='year'"), contains_string("y='gdpPercap'")), + all_of(contains_string("px.bar"), contains_string("x='year'")), + ) + assert_that( + resp["code_string"], + any_of(contains_string("y='gdpPercap'"), contains_string("y='total_gdp'")), ) assert_that( - resp["business_insights"], any_of(contains_string("GDP in the United States"), contains_string("GDP in the US")) + resp["business_insights"], + any_of( + contains_string("GDP per capita in the United States"), + contains_string("GDP in the United States"), + contains_string("GDP in the US"), + ), ) assert_that( resp["code_explanation"], diff --git a/vizro-ai/tests/unit/vizro-ai/chains/test_llm_models.py b/vizro-ai/tests/unit/vizro-ai/chains/test_llm_models.py new file mode 100644 index 000000000..07d3822e5 --- /dev/null +++ b/vizro-ai/tests/unit/vizro-ai/chains/test_llm_models.py @@ -0,0 +1,14 @@ +import pytest +from vizro_ai.chains._llm_models import _get_llm_model + + +@pytest.mark.parametrize( + "model", + [ + "gpt-3.5-turbo-1" # invalid model name + "gpt-3.5-turbo-instruct" # model not supported + ], +) +def test_get_llm_model_invalid_model(model): + with pytest.raises(ValueError, match=f"Model {model} not found!"): + _get_llm_model(model=model) diff --git a/vizro-core/.readthedocs.yaml b/vizro-core/.readthedocs.yaml index ac4729794..4282838d3 100644 --- a/vizro-core/.readthedocs.yaml +++ b/vizro-core/.readthedocs.yaml @@ -11,6 +11,6 @@ build: python: "3.11" commands: - pip install hatch - - cd vizro-core/ && hatch run docs:mkdocs build + - cd vizro-core/ && hatch run docs:build && hatch run docs:link-check - mkdir --parents $READTHEDOCS_OUTPUT - mv vizro-core/site/ $READTHEDOCS_OUTPUT/html diff --git a/vizro-core/CHANGELOG.md b/vizro-core/CHANGELOG.md index 79a8b45c6..a18a08216 100644 --- a/vizro-core/CHANGELOG.md +++ b/vizro-core/CHANGELOG.md @@ -11,6 +11,66 @@ See the fragment files in the [changelog.d directory](https://github.com/mckinse + + +# 0.1.17 — 2024-05-30 + +## Highlights ✨ + +- Enable dynamic data parametrization, so that different data can be loaded while the dashboard is running. ([#482](https://github.com/mckinsey/vizro/pull/482)) + +## Removed + +- Remove all CSS classes with suffix `_outer` from components. Visit the user guide on [how to customize CSS in selected components](https://vizro.readthedocs.io/en/stable/pages/user-guides/assets/#overwrite-css-properties-in-selective-components) for more details. ([#456](https://github.com/mckinsey/vizro/pull/456)) + +## Changed + +- Store `page.id` in outer page container to enable page specific styling. ([#455](https://github.com/mckinsey/vizro/pull/455)) + +- Change the default of the `dash_ag_grid` function to the default of the underlying Dash `dag.AgGrid`. ([#446](https://github.com/mckinsey/vizro/pull/446)) + +## Fixed + +- Fix `dash_data_table` cell dropdown menu to open on click. ([#481](https://github.com/mckinsey/vizro/pull/481)) + +- Fix bug that [prevented referring to columns in custom grids/tables](https://github.com/mckinsey/vizro/issues/435). ([#439](https://github.com/mckinsey/vizro/pull/439)) + +- Fix an issue that prevented the column widths of the AgGrid to render correctly upon reloading a page. ([#446](https://github.com/mckinsey/vizro/pull/446)) + +- Fix actions associated with a manually added control. ([#478](https://github.com/mckinsey/vizro/pull/478)) + + + +# 0.1.16 — 2024-04-30 + +## Removed + +- Remove class names `checkboxes-list`, `radio-items-list` and `input-container` from Vizro stylesheets. ([#414](https://github.com/mckinsey/vizro/pull/414)) + +## Changed + +- Update design of dashboard layout. ([#433](https://github.com/mckinsey/vizro/pull/433)) + + + +# 0.1.15 — 2024-04-15 + +## Highlights ✨ + +- Add dynamic data, which can be reloaded while the dashboard is running. An optional caching layer enables efficient refreshes with per-data source timeouts. Visit the [user guide on data](https://vizro.readthedocs.io/en/stable/pages/user-guides/data/) for more details. ([#398](https://github.com/mckinsey/vizro/pull/398)) + +## Changed + +- Replace default bootstrap stylesheet with `vizro-bootstrap` stylesheet. ([#384](https://github.com/mckinsey/vizro/pull/384)) + +- Refactor code and remove custom classNames from `Button`, `Card`, `NavBar` and `NavLink`. ([#384](https://github.com/mckinsey/vizro/pull/384)) + +- Change default continuous color scale to `SEQUENTIAL_CYAN`. ([#407](https://github.com/mckinsey/vizro/pull/407)) + +## Fixed + +- Fix CSS for `floatingFilter` in `AgGrid`. ([#388](https://github.com/mckinsey/vizro/pull/388)) + # 0.1.14 — 2024-03-26 diff --git a/vizro-core/LICENSE.txt b/vizro-core/LICENSE.txt index c61b66391..d64569567 100644 --- a/vizro-core/LICENSE.txt +++ b/vizro-core/LICENSE.txt @@ -1,181 +1,182 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" @@ -186,16 +187,16 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright [yyyy] [name of copyright owner] + Copyright [yyyy] [name of copyright owner] -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vizro-core/changelog.d/20240523_094341_maximilian_schulz_clean_up_api_docs.md b/vizro-core/changelog.d/20240523_094341_maximilian_schulz_clean_up_api_docs.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-core/changelog.d/20240523_094341_maximilian_schulz_clean_up_api_docs.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-core/changelog.d/20240523_144536_jo_stichbury_update_layout_page.md b/vizro-core/changelog.d/20240523_144536_jo_stichbury_update_layout_page.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-core/changelog.d/20240523_144536_jo_stichbury_update_layout_page.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-core/changelog.d/20240527_100151_petar_pejovic_fix_data_manager_flaky_tests.md b/vizro-core/changelog.d/20240527_100151_petar_pejovic_fix_data_manager_flaky_tests.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-core/changelog.d/20240527_100151_petar_pejovic_fix_data_manager_flaky_tests.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-core/changelog.d/20240528_153301_nadija_ratkusic_graca_standardize_conf_in_tests.md b/vizro-core/changelog.d/20240528_153301_nadija_ratkusic_graca_standardize_conf_in_tests.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-core/changelog.d/20240528_153301_nadija_ratkusic_graca_standardize_conf_in_tests.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-core/changelog.d/20240530_092703_runner.md b/vizro-core/changelog.d/20240530_092703_runner.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-core/changelog.d/20240530_092703_runner.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-core/changelog.d/20240530_095818_petar_pejovic_0_1_17.md b/vizro-core/changelog.d/20240530_095818_petar_pejovic_0_1_17.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-core/changelog.d/20240530_095818_petar_pejovic_0_1_17.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-core/changelog.d/20240326_111333_huong_li_nguyen_add_css_floatingfilter.md b/vizro-core/changelog.d/20240603_161127_huong_li_nguyen_fix_mobile_charts.md similarity index 92% rename from vizro-core/changelog.d/20240326_111333_huong_li_nguyen_add_css_floatingfilter.md rename to vizro-core/changelog.d/20240603_161127_huong_li_nguyen_fix_mobile_charts.md index 99f2e607a..666988724 100644 --- a/vizro-core/changelog.d/20240326_111333_huong_li_nguyen_add_css_floatingfilter.md +++ b/vizro-core/changelog.d/20240603_161127_huong_li_nguyen_fix_mobile_charts.md @@ -37,7 +37,7 @@ Uncomment the section that is right (remove the HTML comment wrapper). ### Fixed -- Fix CSS for `floatingFilter` in `AgGrid`. ([#388](https://github.com/mckinsey/vizro/pull/388)) +- Fix disappearance of charts after theme switch on mobile. ([#511](https://github.com/mckinsey/vizro/pull/511)) API reference for all pre-defined action functions. ::: vizro.actions + + diff --git a/vizro-core/docs/pages/API-reference/captured-callables.md b/vizro-core/docs/pages/API-reference/captured-callables.md index 6204a72cb..f3b054e25 100644 --- a/vizro-core/docs/pages/API-reference/captured-callables.md +++ b/vizro-core/docs/pages/API-reference/captured-callables.md @@ -1,9 +1,14 @@ # Table functions + + API reference for all pre-defined [`CapturedCallable`][vizro.models.types.CapturedCallable] table functions to be used in the -[`AgGrid`][vizro.models.AgGrid] and [`Table`][vizro.models.Table] models. Visit the How-to guide on [tables](../user-guides/table.md) +[`AgGrid`][vizro.models.AgGrid] and [`Table`][vizro.models.Table] models. Visit the how-to guide on [tables](../user-guides/table.md) for more information. ::: vizro.tables options: show_source: true + + + diff --git a/vizro-core/docs/pages/API-reference/manager.md b/vizro-core/docs/pages/API-reference/manager.md deleted file mode 100644 index 73f8c4440..000000000 --- a/vizro-core/docs/pages/API-reference/manager.md +++ /dev/null @@ -1,3 +0,0 @@ -# Data Manager - -::: vizro.managers._data_manager diff --git a/vizro-core/docs/pages/API-reference/models.md b/vizro-core/docs/pages/API-reference/models.md index f0891934d..1297586c0 100644 --- a/vizro-core/docs/pages/API-reference/models.md +++ b/vizro-core/docs/pages/API-reference/models.md @@ -1,10 +1,15 @@ # Models + API reference for all [`pydantic`](https://docs.pydantic.dev/latest/) models used. ::: vizro.models + options: + filters: ["!^_","!build"] # Don't show underscore methods and build method ::: vizro.models.types options: filters: ["!^_"] # Don't show dunder methods as well as single underscore ones merge_init_into_class: false + + diff --git a/vizro-core/docs/pages/API-reference/vizro.md b/vizro-core/docs/pages/API-reference/vizro.md index 182ba849a..32b4e03ef 100644 --- a/vizro-core/docs/pages/API-reference/vizro.md +++ b/vizro-core/docs/pages/API-reference/vizro.md @@ -1,7 +1,10 @@ # Vizro + ::: vizro options: merge_init_into_class: false docstring_options: ignore_init_summary: false + + diff --git a/vizro-core/docs/pages/examples/examples.md b/vizro-core/docs/pages/examples/examples.md new file mode 100644 index 000000000..3c79f50da --- /dev/null +++ b/vizro-core/docs/pages/examples/examples.md @@ -0,0 +1,31 @@ +# Example dashboard code + +This page explains the dashboard code examples found in the `examples` folder of the [Vizro GitHub repository](https://github.com/mckinsey/vizro/tree/main/vizro-core/examples). + +## Vizro live demo +For an example of Vizro in action, take a look at the live demo running at [vizro.mckinsey.com](https://vizro.mckinsey.com), which uses the [Plotly gapminder data](https://plotly.com/python-api-reference/generated/plotly.express.data.html#plotly.express.data.gapminder). + +The dashboard launches with a home page that offers four other pages: + +* Variable analysis: Analyzes population, GDP per capita and life expectancy on country and continent level. +* Relationship analysis: Investigates the interconnection between population, GDP per capita and life expectancy. +* Benchmark analysis: Explores how the metrics differ for each country and offers the option to export data for further investigation. +* Continent summary: Summarizes the main findings for each continent. + + +You can find the code for each of the charts, for each page of the dashboard, in the `examples` folder of the `vizro-core` package, within [Vizro's GitHub repository](https://github.com/mckinsey/vizro). The code is available as a [`.py` file](https://github.com/mckinsey/vizro/blob/main/vizro-core/examples/demo/app.py) or as a [Jupyter Notebook](https://github.com/mckinsey/vizro/tree/main/vizro-core/examples/demo/jupyter_version). + +!!! info + + If you have any problems running the example code, please [raise an issue](https://github.com/mckinsey/vizro/issues) on the Vizro repository. + +## Vizro features +The [`examples/features` folder](https://github.com/mckinsey/vizro/tree/main/vizro-core/examples/features) of the `vizro-core` package within [Vizro's GitHub repository](https://github.com/mckinsey/vizro) contains an example that illustrates Vizro's features. The code is available as a Python script, plus there is an alternative `yaml_version` that offers the same example as the pydantic model but via YAML configuration. + +!!! info "Ways to configure a dashboard" + + In most cases, the pydantic model should be your preferred method of dashboard configuration, but you can also [define a dashboard with YAML, JSON, or as a Python dictionary](../user-guides/dashboard.md). + +## Examples from Vizro users + +We maintain a separate, curated page of [videos, blog posts, and examples of Vizro usage from our community](your-examples.md). diff --git a/vizro-core/docs/pages/examples/your-examples.md b/vizro-core/docs/pages/examples/your-examples.md new file mode 100644 index 000000000..f485642b0 --- /dev/null +++ b/vizro-core/docs/pages/examples/your-examples.md @@ -0,0 +1,31 @@ +# Examples from Vizro users + +This page lists videos, blog posts, and examples of Vizro usage in repositories on GitHub. We've curated the list so it is a snapshot of the best projects and content that Vizro users have created. + +If you have something you'd like us to include on the list, or spot something that we should include, let us know: + +* by emailing [vizro@mckinsey.com](mailto: vizro@mckinsey.com), +* or you can [raise an issue](https://github.com/mckinsey/vizro/issues) on the Vizro repository, +* better still, you can [make a PR to contribute](../explanation/contributing.md) to this page. + + +!!! note + + The Vizro team and QuantumBlack, AI by McKinsey, do not take responsibility for third party content. In curating the list below, we may inspect or test an example at time of inclusion, but cannot guarantee the content thereafter. + +## Videos + +* From [Charming Data on YouTube](https://www.youtube.com/@CharmingData): + * [Build Python Data Apps with Vizro Dash](https://www.youtube.com/watch?v=wmQ6_GZ0zSk). + * [Introduction to Vizro Actions - Plotly Dash](https://www.youtube.com/watch?v=bom-9275Cic&t=8s). + * [Use Dash AG Grid within a Vizro app](https://www.youtube.com/watch?v=YvtVcXwQw0E). + +## Blog posts +* [Introducing Vizro](https://quantumblack.medium.com/introducing-vizro-a-toolkit-for-creating-modular-data-visualization-applications-3a42f2bec4db). + +## Examples on GitHub + +* [Personal Vizro app demos by Vizro team member `huong-li-nguyen`](https://github.com/huong-li-nguyen/vizro-app-demos). +* [Proof of concept example by `viiviandias`](https://github.com/viiviandias/poc-vizro/blob/main/brasil_stocks.ipynb). +* [Amazon sales analysis by `Bottleneck44`](https://github.com/Bottleneck44/Amazon-Sales-Analysis/blob/main/Amazon-analysis.ipynb). + diff --git a/vizro-core/docs/pages/development/authors.md b/vizro-core/docs/pages/explanation/authors.md similarity index 95% rename from vizro-core/docs/pages/development/authors.md rename to vizro-core/docs/pages/explanation/authors.md index c687a66a6..3f70cd44e 100644 --- a/vizro-core/docs/pages/development/authors.md +++ b/vizro-core/docs/pages/explanation/authors.md @@ -36,7 +36,7 @@ Natalia Kurakina, [Kee Wen Ng](https://github.com/KeeWenNgQB), [Rashida Kanchwala](https://github.com/rashidakanchwala), -with thanks to Sam Bourton and Kevin Staight for sponsorhip, inspiration and guidance, +with thanks to Sam Bourton and Kevin Staight for sponsorship, inspiration and guidance, and special thanks to [Wesley Leong](https://github.com/wesleyleong), [Jonas Kemper](https://github.com/jonasrk) and team for origination and support diff --git a/vizro-core/docs/pages/explanation/compatibility.md b/vizro-core/docs/pages/explanation/compatibility.md deleted file mode 100644 index 8988151c7..000000000 --- a/vizro-core/docs/pages/explanation/compatibility.md +++ /dev/null @@ -1,12 +0,0 @@ -# Compatibility - -## Supported Browsers -Vizro supports the [Chrome Browser](https://www.google.com/intl/en_us/chrome/). -Other browsers may work, but are not officially supported. - -## Versioning policy -This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -We do not consider frontend changes (such as changing the appearance of a component) to be breaking changes. - -!!! note - While being in version `0.x.x`, we may introduce breaking changes in minor versions. diff --git a/vizro-core/docs/pages/development/contributing.md b/vizro-core/docs/pages/explanation/contributing.md similarity index 64% rename from vizro-core/docs/pages/development/contributing.md rename to vizro-core/docs/pages/explanation/contributing.md index b7a6de863..346702688 100644 --- a/vizro-core/docs/pages/development/contributing.md +++ b/vizro-core/docs/pages/explanation/contributing.md @@ -1,50 +1,42 @@ -## Contributing guidelines +# Contributing guidelines -Contributions of all experience levels are welcome! There are many ways to contribute, and we appreciate all of them. Please use our [issues page](https://github.com/mckinsey/vizro/issues) to discuss any contributions. Before opening a pull request, please ensure you've first opened an issue to discuss the contribution. +Contributions of all experience levels are welcome! There are many ways to contribute, and we appreciate any help. Use our [issues page](https://github.com/mckinsey/vizro/issues) to discuss any contributions. Before opening a pull request, ensure you've first opened an issue to discuss the contribution. -### Found a bug? +## Found a bug? -Great! We would appreciate if you could head to our [issues page](https://github.com/mckinsey/vizro/issues) and raise a ticket in the category `bug report`. It would greatly assist us if you could first check if there are any existing issues with a similar description before submitting a new ticket. We will promptly work on reproducing the bug you've reported and will follow up with the next steps. +Head over to our [issues page](https://github.com/mckinsey/vizro/issues) and raise a ticket in the category `bug report`. It would help us if you could first check if there are any existing issues with a similar description before submitting a new ticket. We will try to reproduce the bug you've reported and follow up with the next steps. -### Request a feature +## Want to request a feature? -Splendid! In order to raise a feature request, please head to our [issues page](https://github.com/mckinsey/vizro/issues) and raise a ticket in the category `feature request`. We would appreciate if you searched the existing issues for a similar description before raising a new ticket. The team will then try to understand the request in more detail, explore the feasibility and prioritize it in relation to the current roadmap. We will get back to you as soon as possible with an estimate of whether and when this feature could be released. +To raise a feature request, head to our [issues page](https://github.com/mckinsey/vizro/issues) and raise a ticket in the category `feature request`. We would appreciate if you searched the existing issues for a similar description before raising a new ticket. The team will then try to understand the request in more detail, explore the feasibility and prioritize it in relation to the current roadmap. We will get back to you as soon as possible with an estimate of whether and when this feature could be released. -### General question +## Got a Vizro question? -Nice! We are happy to receive general questions around Vizro. Please head to our [issues page](https://github.com/mckinsey/vizro/issues) and raise a ticket in the category `general question`. We would be grateful if you could check for any similar descriptions in the existing issues before opening a new ticket. +We are happy to receive general questions around Vizro. Take a look at our [issues page](https://github.com/mckinsey/vizro/issues) and raise a ticket in the category `general question`. We would be grateful if you could check for any similar descriptions in the existing issues before opening a new ticket. + +If you are already active on the Plotly Community Forum, you can alternatively ask your question over there in the [Dash Python category](https://community.plotly.com/c/python/25). ## How to interact with the repository -The easiest way to get up and running quickly is to [open the repository in GitHub Codespaces](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=626855142). This will create a temporary development environment with all the necessary configurations, making it especially convenient for tasks like reviewing pull requests. +The easiest way to get up and running is to [open the repository in GitHub Codespaces](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=626855142). This will create a temporary development environment with all the necessary configurations, making it especially convenient for tasks like reviewing pull requests. We use [Hatch](https://hatch.pypa.io/) as a project management tool. To get started on your own machine, you should complete the following steps. Note there is _no need to set up your own virtual environment_ since Hatch takes care of that for you. 1. [Install `hatch`](https://hatch.pypa.io/latest/install/) by running `brew install hatch` or `pipx install hatch` (preferable to `pip install hatch`). 2. Clone this repository. -3. Run `hatch -v env create` from the `vizro-core` folder of your cloned repository. This creates Hatch's `default` environment with dependencies installed and the project installed in development mode (i.e. using `pip install --editable`). It will take a few minutes to complete. All following commands should be executed from this folder as well. -4. Run `hatch run example` to open an [example Vizro dashboard](#examples) with [Dash dev tools](https://dash.plotly.com/devtools) enabled. -5. Edit the code to your heart's desire! Thanks to Dash dev tools' hot reloading, any changes to the example app or `vizro` source code should automatically show in your dashboard without needing refresh or restart any process. +3. Run `hatch -v env create` from the `vizro-core` folder of your cloned repository. This creates Hatch's `default` environment with dependencies installed and the project installed in development mode (that is, using `pip install --editable`). It will take a few minutes to complete. All following commands should be executed from this folder as well. +4. Run `hatch run example` to [open an example Vizro dashboard](#examples) with [Dash dev tools](https://dash.plotly.com/devtools) enabled. +5. Edit the code! Thanks to Dash dev tools' hot reloading, any changes to the example app or `vizro` source code should automatically show in your dashboard without needing refresh or restart any process. !!!note The above steps are all automated in GitHub Codespaces thanks to the [devcontainer configuration](https://github.com/mckinsey/vizro/blob/main/.devcontainer/devcontainer.json), and the example dashboard should already be running on port `8050`. - If you haven't used Hatch before, it's well worth skimming through [their documentation](https://hatch.pypa.io/), in particular the page on [environments](https://hatch.pypa.io/latest/environment/). Run `hatch env show` to show all of Hatch's environments and available scripts, and take a look at [`hatch.toml`](https://github.com/mckinsey/vizro/tree/main/vizro-core/hatch.toml) to see our Hatch configuration. It is useful handy to [Hatch's tab completion](https://hatch.pypa.io/latest/cli/about/#tab-completion) to explore the Hatch CLI. + If you haven't used Hatch before, it's well worth skimming through [their documentation](https://hatch.pypa.io/), in particular the page on [environments](https://hatch.pypa.io/latest/environment/). Run `hatch env show` to show all Hatch's environments and available scripts, and take a look at [`hatch.toml`](https://github.com/mckinsey/vizro/tree/main/vizro-core/hatch.toml) to see our Hatch configuration. [Hatch's tab completion](https://hatch.pypa.io/latest/cli/about/#tab-completion) is useful to explore the Hatch CLI. --- -## Examples - -Several example dashboards are given in [examples](https://github.com/mckinsey/vizro/tree/main/vizro-core/examples). To run, for instance, the `features/yaml_version` example, execute: - -```console -hatch run example features/yaml_version -``` - -If no example is specified (`hatch run example`) then the [`_dev` example](https://github.com/mckinsey/vizro/tree/main/vizro-core/examples/_dev/app.py) is used. - -## Documentation +## Contribute to documentation If you're modifying documentation, the following will do a hot-reloading build of the rendered docs: @@ -59,16 +51,16 @@ hatch run docs:serve ## Testing -Tests are handled using the [`pytest`](https://docs.pytest.org/) and [`jest`](https://jestjs.io/) frameworks, and test environments are managed by Hatch. To run all python tests, run +Tests are handled using the [`pytest`](https://docs.pytest.org/) and [`jest`](https://jestjs.io/) frameworks, and test environments are managed by Hatch. To run all Python tests, run ```console hatch run test ``` -To run only python unit tests, run `hatch run test-unit`, and for python integration tests only run `hatch run test-integration`. +To run only Python unit tests, run `hatch run test-unit`, and for Python integration tests only run `hatch run test-integration`. -Arguments are passed through to the underlying `pytest` command, e.g. +Arguments are passed through to the underlying `pytest` command: ```console hatch run test -vv @@ -92,7 +84,7 @@ To run jest unit tests for javascript functions, run `hatch run test-js`. Note that Node.js is required to run tests written in the jest framework. If you don't have `Node.js` installed, guidelines on how to install Node.js will appear when you run the command: `hatch run test-js`. Otherwise, if `Node.js` is installed, then the same command (`hatch run test-js`) runs jest unit tests. -Arguments are passed through to the underlying `npx jest` command, e.g. +Arguments are passed through to the underlying `npx jest` command: ```console hatch run test-js --help @@ -110,7 +102,7 @@ The JSON schema in [`schemas`](https://github.com/mckinsey/vizro/tree/main/vizro ## Pre-commit hooks (for linting etc.) -All linting and associated dependencies are controlled by [pre-commit](https://pre-commit.com/) hooks and specified in [.pre-commit-config.yaml](https://github.com/mckinsey/vizro/blob/main/.pre-commit-config.yaml). Configuration for tools is additionally given in [`pyproject.toml`](https://github.com/mckinsey/vizro/blob/main/pyproject.toml), e.g. +All linting and associated dependencies are controlled by [pre-commit](https://pre-commit.com/) hooks and specified in [.pre-commit-config.yaml](https://github.com/mckinsey/vizro/blob/main/.pre-commit-config.yaml). Configuration for tools is additionally given in [`pyproject.toml`](https://github.com/mckinsey/vizro/blob/main/pyproject.toml): ```toml [tool.black] @@ -118,12 +110,12 @@ target-version = ["py37"] line-length = 120 ``` -We use [`pre-commit ci`](https://pre-commit.ci/) to automatically fix all the linting checks that we can (e.g. `black` formatting) when a PR is pushed. Other linting failures (e.g. `mypy`) need manual intervention from the developer. +We use [`pre-commit ci`](https://pre-commit.ci/) to automatically fix all the linting checks that we can (with `black` formatting) when a PR is pushed. Other linting failures (such as `mypy`) need manual intervention from the developer. To run pre-commit hooks locally, there are two options: -1. Run `hatch run pre-commit install` to automatically run the hooks on every commit (you can always skip the checks with `git commit --no-verify`). In case this fails due to `gitleaks`, please read below for an explanation and how to install `go`. -2. Run `hatch run lint` to run `pre-commit` hooks on all files. (You can run e.g. `hatch run lint mypy -a` to only run specific linters, here mypy, on all files.) +1. Run `hatch run pre-commit install` to automatically run the hooks on every commit (you can always skip the checks with `git commit --no-verify`). In case this fails due to `gitleaks`, you should read below for an explanation and how to install `go`. +2. Run `hatch run lint` to run `pre-commit` hooks on all files. (You can run `hatch run lint mypy -a` to only run specific linters, here mypy, on all files.) Note that Hatch's `default` environment specifies `pre-commit` as a dependency but otherwise _does not_ specify dependencies for linting tools such as `black`. These are controlled by [.pre-commit-config.yaml](https://github.com/mckinsey/vizro/blob/main/.pre-commit-config.yaml) and can be updated when required with `pre-commit autoupdate`. Once per month, `pre-commit ci` raises a PR to do so. @@ -133,7 +125,7 @@ We use [gitleaks](https://github.com/gitleaks/gitleaks) for secret scanning. We 1. Using `gitleaks` may require an installation of `go` on the developer machine. This is easy and explained in the [Go documentation](https://go.dev/doc/install). 2. For that reason `hatch run lint` skips the secret scans, to function on all machines. -3. To run a secret-scan, simply run `hatch run secrets`. +3. To run a secret-scan, run `hatch run secrets`. 4. Secret scans will run on CI, but it is highly recommended to check for secrets **before pushing to the remote repository** and ideally also before even committing. When executing the secret scan, there are two modes: `protect` can discover secrets in staged files, `detect` does so in the commit history. @@ -161,17 +153,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 Vizro uses [scriv](https://pypi.org/project/scriv/) to build and maintain a meaningful `CHANGELOG.md`. When creating a PR, the developer needs to ensure that a changelog fragment has been created in the folder `changelog.d`. This fragment is a small `.md` file describing the changes of the current PR that should be mentioned in the `CHANGELOG.md` entry of the next release. -You can easily create such a fragment by running +You can create such a fragment by running ```bash hatch run changelog:add ``` -Please begin by uncommenting the relevant section(s) you wish to describe. If your PR includes changes that are not relevant to `CHANGELOG.md`, please leave everything commented out. If you are uncertain about what to add or whether to add anything, please refer to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The rule of thumb should be, if in doubt, or if the user is affected in any way, it should be described in the `CHANGELOG.md`. +Begin by uncommenting the relevant section(s) you wish to describe. If your PR includes changes that are not relevant to `CHANGELOG.md`, leave everything commented out. If you are uncertain about what to add or whether to add anything, refer to [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The rule of thumb should be, if in doubt, or if the user is affected in any way, it should be described in the `CHANGELOG.md`. ## Releases -Vizro's version is given by `__version__` in [`src/vizro/__init__.py`](https://github.com/mckinsey/vizro/blob/main/vizro-core/src/vizro/__init__.py). To bump the version, run, e.g. `hatch version minor`. See [Hatch's documentation](https://hatch.pypa.io/latest/version/) for more details. +Vizro's version is given by `__version__` in [`src/vizro/__init__.py`](https://github.com/mckinsey/vizro/blob/main/vizro-core/src/vizro/__init__.py). To bump the version, run `hatch version minor`. See [Hatch's documentation](https://hatch.pypa.io/latest/version/) for more details. To build the source distribution and wheel, run `hatch build`. @@ -179,23 +171,25 @@ To build the source distribution and wheel, run `hatch build`. The Vizro team pledges to foster and maintain a friendly community. We enforce a [Code of Conduct](https://github.com/mckinsey/vizro/tree/main/CODE_OF_CONDUCT.md) to ensure every Vizro contributor is welcomed and treated with respect. -## Frequently asked questions +## Frequently asked contribution questions + ### How do I add a dependency? + -Add it to the list of `dependencies` in `hatch.toml` (if you are adding a dependency for development) or in `pyproject.toml` (if you are adding a dependency for the actual package). The next time the `default` environment is used (e.g. with `hatch shell`), the dependency will be automatically installed. +Add it to the list of `dependencies` in `hatch.toml` (if you are adding a dependency for development) or in `pyproject.toml` (if you are adding a dependency for the actual package). The next time the `default` environment is used (with `hatch shell`), the dependency will be automatically installed. ### What about a lock file? -We do not have and should not need a dependency lock file (see [this Hatch FAQ](https://hatch.pypa.io/latest/meta/faq/#libraries-vs-applications)). If one is for some reason eventually required, good options would be [pip-tools](https://github.com/jazzband/pip-tools), [`hatch-pip-deepfreeze`](https://github.com/sbidoul/hatch-pip-deepfreeze) or just `pip freeze`. - +We do not have and should not need a dependency lock file (see [this Hatch FAQ](https://hatch.pypa.io/latest/meta/faq/#libraries-vs-applications)). If one is for some reason eventually required, good options would be [pip-tools](https://github.com/jazzband/pip-tools), [`hatch-pip-deepfreeze`](https://github.com/sbidoul/hatch-pip-deepfreeze) or `pip freeze`. + ### How do I find the path to the Python executable used? - -`hatch run pypath` displays the path to the Python executable used in the `default` environment. This is useful e.g. for setting up a run configuration in PyCharm. + +`hatch run pypath` displays the path to the Python executable used in the `default` environment. This is useful in cases such as when you are setting up a run configuration in PyCharm. ### Why are we using a line length of 120 characters? -This is the default value set in the Hatch template, and it feels sensible in the era of big screens. Line lengths can be discussed endlessly but ultimately this number should be agreed on by the Vizro team. See also [this article](https://knox.codes/posts/line-length-limits). +This is the default value set in the Hatch template, and it feels sensible in the era of big screens. Line lengths can be discussed endlessly but the number should be agreed on by the Vizro team. See also [this article](https://knox.codes/posts/line-length-limits). ## Further reading and credits diff --git a/vizro-core/docs/pages/development/documentation-style-guide.md b/vizro-core/docs/pages/explanation/documentation-style-guide.md similarity index 78% rename from vizro-core/docs/pages/development/documentation-style-guide.md rename to vizro-core/docs/pages/explanation/documentation-style-guide.md index fc02cf2f8..e13120529 100644 --- a/vizro-core/docs/pages/development/documentation-style-guide.md +++ b/vizro-core/docs/pages/explanation/documentation-style-guide.md @@ -1,16 +1,21 @@ # Vizro documentation style guide + + This is the style guide we apply to the [Vizro documentation](https://vizro.readthedocs.io/en/stable/). We ask anyone kind enough to contribute documentation changes to follow this style for consistency and simplicity. -What follows is a set of lightweight guidelines rather than rules. There are always edge cases and exceptions, and if it's not obvious what the style should be, consult the [Microsoft style guide](https://docs.microsoft.com/en-gb/style-guide/welcome/) for an example of good practice. We also use the [INCITS Inclusive Terminology Guidelines](https://standards.incits.org/apps/group_public/download.php/131246/eb-2021-00288-001-INCITS-Inclusive-Terminology-Guidelines.pdf). +What follows is a set of lightweight guidelines rather than rules. There are always edge cases and exceptions, and if it's not obvious what the style should be, consult the [Microsoft style guide](https://docs.microsoft.com/en-gb/style-guide/welcome/) for an example of good practice. We also [ensure inclusive terminology](https://developers.google.com/style/inclusive-documentation) with the Google developer documentation style guide. ## Vizro lexicon The names of our products are **Vizro** and **Vizro-AI**. -We refer to other products using their preferred capitalization. For example, Dash and Pydantic are always capitalized, except where given as Python package names `dash` and `pydantic`. +We refer to other products using their preferred capitalization. For example: + +* Dash and Pydantic are always capitalized, except where given as Python package names `dash` and `pydantic`. +* pandas DataFrame has a lowercase "p" and camelcase "DataFrame". Vizro components are named using lower case: @@ -20,6 +25,8 @@ Use code font when referring to the component as a class or object: > To add a `Container` to your page... +Avoid referring to data using terms like "dataset" or "connector". Prefer to use just "data" or, where that does not feel natural, "data source". + ## Bullets * Capitalize the first word, and end the bullet with a period. * Don't use numbered bullets except for a sequence of instructions, or where you have to refer back to one of them in the text (or a diagram). @@ -48,7 +55,7 @@ Callout boxes can be made collapsible: if you use them, add them to the page so Don't use expanded-on-load collapsibles. If the callout contains important information and needs to be shown as expanded on page load, it should simply be non-collapsible. ## Capitalization -* Only capitalize proper nouns e.g. names of technology products, other tools and services. +* Only capitalize proper nouns such as the names of technology products, other tools and services. * Don't capitalize cloud, internet, machine learning, or advanced analytics. Take a look at the [Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/accessibility-terms) if you're unsure. * Follow sentence case, which capitalizes only the first word of a title/subtitle. We prefer "An introduction to data visualization" to "An Introduction to Data Visualization". @@ -58,6 +65,25 @@ Callout boxes can be made collapsible: if you use them, add them to the page so * We use a `bash` lexer for all codeblocks that represent the terminal, and we don't include the prompt. * Use the code format for Python package names such as `pandas` or `pydantic`. +## Headings and subheadings + +Choosing a heading is sometimes the hardest part of the writing process. Take your time! + +Aim to avoid use of gerunds (verb+ing) where you can. So your page should be "Get started" rather than "Getting started" and "Customize a layout" rather than "Customizing a layout". Keep the title short and descriptive, ideally think about a reader who may be using a search engine to find an answer -- structure your title to give them the best chance of understanding what the page tells them. + +In Vizro, when you are working on a how-to guide, there are a few additional guidelines to follow for consistency: + +* Don't use "how to" in the file name: keep that as short as possible. +* The title (H1 header) should start with "How to". +* Don't use "how to" in the subsections that follow (H2 - H5) unless you consider the alternative to be confusing. +* Don't use gerund form in the subsections either. + +Example: In a page called "Filters" you would have the following: + +* H1: "How to use filters" +* H2 subsection: "Use a custom filter" and not "How to use a custom filter" nor "Using a custom filter". + + ## Instructions Prefer to use imperatives to make instructions. For example: @@ -82,7 +108,6 @@ Don't use the passive tense: ## Language * Use US English. - ## Links * Make hyperlink descriptions as descriptive as you can. This is a good description: @@ -94,16 +119,13 @@ This is less helpful: Don't write this: - - > To learn how to contribute to Vizro, see [here](https://vizro.readthedocs.io/en/stable/pages/development/contributing/). - ### Internal cross-referencing We use internal cross-references as follows: -* For each documentation page, if it helps the reader, we link to narrative documentation (non-API documentation) about each Vizro concept where it is first introduced. +* For each documentation page, if it helps the reader, we link to narrative documentation (non-API documentation) about each Vizro topic where it is first introduced. * On any single page, we limit the repetition of links: do not re-link to the same page again unless there is good reason to do so (for example, linking to a specific sub-section to illustrate a point). * Add links to relevant API documentation where it is useful for the reader, and consider how they will navigate from where they land in the API documentation back to the narrative content. Consider adding a link in the relevant docstring back to your page. @@ -157,3 +179,4 @@ Functional is not try-hard, cliched or hyperbolic: * **Colloquialisms**. Avoid them "like the plague" because they may not translate to other regions/languages. * **Technical terminology**. This applies particularly to acronyms that do not pass the "Google test". If it is not possible to find their meaning from a simple Google search, don't use them, or explain them with a link or some text. * **Business speak**. You can explain simply without using words like "leverage", "utilize" or "facilitate" and still sound clever. + diff --git a/vizro-core/docs/pages/explanation/why-vizro.md b/vizro-core/docs/pages/explanation/faq.md similarity index 65% rename from vizro-core/docs/pages/explanation/why-vizro.md rename to vizro-core/docs/pages/explanation/faq.md index 9bd656b6a..cc20c8232 100644 --- a/vizro-core/docs/pages/explanation/why-vizro.md +++ b/vizro-core/docs/pages/explanation/faq.md @@ -3,7 +3,42 @@ hide: - toc --- -# Why Vizro? +# FAQs + +Here are some answers to frequently asked questions: + + + +* [Which browsers does Vizro support?](#which-browsers-does-vizro-support) +* [What is the Vizro versioning policy?](#what-is-the-vizro-versioning-policy) +* [Where can I find example dashboards?](#where-i-can-find-example-dashboards) +* [Why should I use Vizro?](#why-should-i-use-vizro) +* [How does Vizro differ from Dash or Streamlit?](#how-does-vizro-differ-from-dash-or-streamlit) +* [How does Vizro compare with Python packages and business intelligence (BI) tools?](#how-does-vizro-compare-with-python-packages-and-business-intelligence-bi-tools) +* [When would an alternative to Vizro be more suitable?](#when-would-an-alternative-to-vizro-be-more-suitable) +* [I want to contribute to Vizro, how can I find out more?](contributing.md) +* [I still have a question. Where can I ask it?](contributing.md/#got-a-vizro-question) + + + +## Which browsers does Vizro support? +Vizro supports the [Chrome browser](https://www.google.com/intl/en_us/chrome/). +Other browsers may work, but are not officially supported. + +## What is the Vizro versioning policy? +This project adheres to [semantic versioning](https://semver.org/spec/v2.0.0.html). +We do not consider frontend changes (such as changing the appearance of a component) to be breaking changes. + +!!! note + + While being in version `0.x.x`, we may introduce breaking changes in minor versions. + +## Where I can find example dashboards? + +Check out our [examples section](../examples/examples.md) which describes the contents of the `examples` folder of the [Vizro GitHub repository](https://github.com/mckinsey/vizro/tree/main/vizro-core/examples). + + +## Why should I use Vizro? Vizro is a high-level framework built on top of Dash and Pydantic, which makes it easier to build advanced dashboards @@ -11,12 +46,12 @@ since it automates many of the otherwise complex and time-consuming tasks traditionally associated with designing, building and deploying front-end applications, from prototypes to production. -### 1) Build beautiful & powerful dashboards, quickly & easily +### You can build beautiful & powerful dashboards, quickly & easily Users can configure Vizro dashboards without needing to know advanced software development principles, nor how to build front-end applications. -???+ details "See more details" +??? details "See more details"
Image title @@ -30,7 +65,7 @@ Users can configure Vizro dashboards without needing to know advanced software d This removes most of the "glue code" that would otherwise need to be written. Thousands of lines of code are reduced to tens of lines of configuration. Users can configure Vizro dashboards without needing to know any advanced software development principles of how to build front-end applications. -### 2) Extend and customize infinitely +### You can extend and customize infinitely Users benefit from the power of the Dash framework and the flexibility of React. @@ -41,7 +76,7 @@ Users benefit from the power of the Dash framework and the flexibility of React. - **React** - since Dash enables JavaScript React components to be incorporated into Dash applications, Vizro users can create custom charts and UI components which offer the infinite flexibility of React. - **Vizro extensions** - adding extensions such as user defined custom charts, components, actions and data connectors is intuitively incorporated into the configuration language of Vizro. -### 3) Rapidly scale from prototypes to production deployment +### You can rapidly scale from prototypes to production deployment Consistency and re-usability designed for scale. @@ -50,7 +85,7 @@ Consistency and re-usability designed for scale. - **Prototype rapidly** - even complex dashboards can be created within minutes using Vizro, which enables prototype dashboards to be created and iterated on quickly and easily, with very low barrier to entry. - **Deploy easily** - since Vizro is built on Dash which uses Flask, it is simple to deploy Vizro like any other Dash application, and use application servers such as Gunicorn to scale to multiple users. - - **Scale** - since Vizro provides standardization of visual design, application architecture and configuration language, + - **Scale** - since Vizro offers standardization of visual design, application architecture and configuration language, it is easier to scale across multiple developers, projects and implementations in a consistent and reusable way. ## How does Vizro differ from Dash or Streamlit? @@ -60,7 +95,7 @@ In many ways a direct comparison is not possible as these products tackle somewh Any attempt at a high-level explanation must rely on an oversimplification that misses many important nuances. With the caveat that it's not possible to "compare apples with pears", and that any comparison will have a different conclusion for different users, an oversimplified view could be: -??? info "Streamlit is great for rapid prototyping" +??? details "Streamlit is great for rapid prototyping" - **rapid prototyping** - Streamlit's architecture allows you to write apps the same way you write plain Python scripts. To unlock this, Streamlit apps have a unique data flow: any time something must be updated on the screen, Streamlit reruns your entire Python script from top to bottom. [[1]](https://docs.streamlit.io/library/get-started/main-concepts) @@ -68,7 +103,7 @@ Any attempt at a high-level explanation must rely on an oversimplification that Adding a widget is the same as declaring a variable. (No need to write a backend, define routes, handle HTTP requests, connect a frontend, write HTML, CSS, JavaScript, etc. [[3]](https://streamlit.io/)) -??? info "Dash is great for customization and scalability" +??? details "Dash is great for customization and scalability" - **customization** - one of the great things about Dash is that it is built on top of React.js, a JavaScript library for building web components. Thousands of components have been built and released with open source licenses by the React community, any of which could be adapted into a Dash component. [[4]](https://dash.plotly.com/plugins) @@ -77,7 +112,7 @@ Any attempt at a high-level explanation must rely on an oversimplification that - **scalability** - based on Flask which is widely adopted by the Python community and deployed in production environments everywhere [[7]](https://medium.com/plotly/introducing-dash-5ecf7191b503) Dash was designed to be a stateless framework. Stateless frameworks are more scalable and robust [[8]](https://dash.plotly.com/sharing-data-between-callbacks#why-share-state?) -??? info "Vizro is great for combining rapid prototyping with customization and scalability" +??? details "Vizro is great for combining rapid prototyping with customization and scalability" - **rapid prototyping** - since Vizro is a high-level framework providing declarative configuration, it is quick and easy to create powerful interactive apps in minutes, without needing to write callbacks, HTML, CSS, or JavaScript. Key topics such as applying state management, application architecture, and testing are done automatically by Vizro. - **customization and scalability** - since Vizro is built on top of Dash, then users benefit from all the underlying power of the Dash framework for customization and scalability @@ -91,32 +126,31 @@ If you prefer a configuration approach with in-built best practices, and the pot For a more detailed comparison, it may help to visit the introductory articles of [Dash](https://medium.com/plotly/introducing-dash-5ecf7191b503), [Streamlit](https://towardsdatascience.com/coding-ml-tools-like-you-code-ml-models-ddba3357eace) and [Vizro](https://quantumblack.medium.com/introducing-vizro-a-toolkit-for-creating-modular-data-visualization-applications-3a42f2bec4db), to see how each tool serves a distinct purpose, and could be the best tool of choice. -## Comparing Vizro with Python packages and business intelligence (BI) tools such as Streamlit, Tableau and PowerBI - -There are a number of Python packages and BI tools which provide support for visualization applications (such as Streamlit, Plotly/Dash, Tableau and PowerBI). +## How does Vizro compare with Python packages and business intelligence (BI) tools? +There are a number of Python packages and BI tools which offer support for visualization applications (such as Streamlit, Plotly/Dash, Tableau and PowerBI). -Vizro is intended to support several niches between the benefits provided by those tools, rather than being in direct comparison with any single tool. Therefore, direct comparisons are often only partially suitable, given the many features offered across this landscape. +Vizro is intended to support several niches between the benefits from those tools, rather than being in direct comparison with any single tool. Therefore, direct comparisons are often only partially suitable, given the many features offered across this landscape. -However, in general, there are several areas of functionality where Vizro can be particularly useful, e.g., in providing a simple configuration to speed up the assembly of components, leveraging inbuilt visual design, application architecture, and coding standards, +However, in general, there are several areas of functionality where Vizro can be particularly useful, such as in providing a simple configuration to speed up the assembly of components, leveraging inbuilt visual design, application architecture, and coding standards, along with the ability to scale easily across multiple developers and implementations. ??? details "See more details" | Functionality | Benefits | In context of Python packages | In context of BI tools | | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | **Assembly system** | | Many Python packages still require a moderate understanding of the coding required to assemble higher-level dashboard concepts, which necessitates the creation of “glue code” to combine lower-level components.

Vizro primarily provides a configuration to simplify that assembly of components offered by existing packages (currently leveraging Plotly/Dash), and so occupies a slightly different niche from libraries offering primarily the lower-level components themselves. It also removes the requirement from users to implement certain code standards for the assembled code themselves, and therefore saves time on often time consuming things such as writing unit tests and ensuring linting coverage. | Many BI tools incorporate the assembly of higher-level concepts automatically from GUI (drop-and-drag) interfaces, and occupy a slightly different niche to the configuration driven assembly provided by Vizro. | - | **Inbuilt visual design decisions** | | Many Python packages provide inbuilt color choices, and visual design choices for certain components.

Vizro applies that to a wide range of component combinations and complex user flows, offering a holistic and comprehensive approach to automatically enable beautiful visual design best practices, whilst allowing customization and flexibility. | Many BI tools provide inbuilt color choices and visual design choices for certain components.

Vizro provides inbuilt visual design for components in addition to the ability to customize them in a flexible way where needed (for example through CSS), and provides automatic arrangement of components on the screen. | - | **Inbuilt application architecture decisions** | | Many Python packages provide inbuilt application architecture choices for certain functionalities.

Vizro applies that to a wide range of component combinations and complex user flows, offering a holistic and comprehensive approach to the entire application architecture, whilst allowing customization and flexibility where relevant. | Many BI tools apply proprietary application architecture by default.

Vizro enables the user to view and understand the application architecture by directly viewing the code. | - | **Declarative configuration, in multiple formats** | | Many Python packages provide a mostly declarative configuration, with limited ability to leverage multiple formats such as Pydantic models and JSON.

Vizro simplifies this approach, and facilitates the extension of the assembly system to enable integration with other tools such as Kedro to programmatically and dynamically generate the relevant dashboard configurations in a streamlined way. | Many BI tools solely use a GUI and/or drag-and-drop interface for defining dashboards, and leverage proprietary configuration formats.

Vizro enables the user to view and understand the configuration by viewing it directly, in addition to being able to edit it directly. | - | **Mostly tech agnostic “grammar of dashboards”** | | Many Python packages provide an effective “grammar of charts” and Python specific declaration.

Vizro provides a “grammar of dashboards” with declaration which is largely tech agnostic and can be extended to non-Python languages. | Many BI tools utilize an implicit internal “grammar of dashboards” which is specific to the proprietary language(s) on with which they are built.

Vizro provides an explicit configuration grammar and allows users to leverage a mostly tech agnostic approach (which can be extended in future). | - | **Inbuilt validation** | | Many Python packages provide individual components or non-Pydantic based models.

Vizro provides the advantages of Pydantic based models for many elements of the configuration process, which leverages the validation and guidance inherent in that process to facilitate implementation by users. | Many BI tools have inbuilt guideline systems to ensure components are combined in a valid way, and provide feedback to the manual user to help guide through that process.

Vizro utilizes a flexible system which can be extended to provide feedback to programmatic generation of configuration. | - | **Modularity** | | Many Python packages provide modularity of components such as charts and controls.

Vizro supports modularity of groups of components (such as their implementation together as a dashboard screen), which can be easily transferred between implementations as configuration. | Many BI tools provide extensions and plugins which support modularity of visualizations.

Vizro supports modularity of groups of components (such as their implementation together as a dashboard screen), which can be easily transferred between implementations as configuration. | - | **Flexibility** | | Many Python packages provide some low-code and/or high-code approaches, which offer varying degrees of flexibility.

Vizro provides a holistic combination of low-code and high-code approaches which support less technical and more technical users individually while allowing them much flexibility in implementation according to their technical level. | Many BI tools offer a no-code (or at least low-code) approach to creating charts and dashboards, along with varying forms of plugins to increase flexibility.

Vizro unlocks Python custom functions to be able to power this user driven flexibility in a technically advanced way, with a high degree of control and visibility over the code, whilst also supporting less technical users through low-code configuration. | - | **Scaling** | | Many Python packages benefit from the ability to propagate updates programmatically, while applying that to “glue code”.

Vizro makes it easy to scale by replicating the relatively small amount of configuration in 1 file between usages, rather than replicating a large amount of code across many files between usages.

Updating configuration programmatically from a central location is often easier than updating code. When sharing between users, it can be easier to inherit and understand configuration than the underlying code. The visual consistency makes it easier to scale within or between projects while maintaining visual coherence. | Many BI tools allow scaling through GUI drag-and-drop interfaces (having some functionality for duplication or propagating changes) for single users.

Vizro makes it easy to leverage tools such as Git to enable almost any number of users to collaborate effectively, therefore allowing the number of developers on a single project to scale easily.

Since updates can be propagated programmatically easily from a central location, it allows scaling across almost any number of related implementations which can be kept up to date and aligned without the need to manually adjust each implementation when updates are required. | - | **Python first** | | Python packages are already Python focused.

Vizro is no different in this respect. By leveraging Plotly/Dash, Vizro is also able to benefit from the power and flexibility offered by JavaScript via React, whilst still presenting a format that is Python first to the user (by making use of the ability offered by Dash to effectively wrap those components into Python). | Many BI tools do not provide direct or full integration with Python.

Vizro provides a Python first approach which leverages the power and flexibility of Python, and the open source community supporting that wide ranging functionality. | - | **Open source** | | Many Python packages also use an open source license, and provide ongoing development and maintenance.

Vizro is no different in this respect. | Many BI tools follow a license fee model and/or charge for ongoing development and maintenance.

Vizro requires no license fee, and provides ongoing development and support, which helps to remove some barriers to usage. | - -## Cases where alternatives to Vizro are potentially more suitable + | **Assembly system** | | Many Python packages still require a moderate understanding of the coding required to assemble higher-level dashboard concepts, which necessitates the creation of “glue code” to combine lower-level components.

Vizro primarily offers configuration to simplify that assembly of components offered by existing packages (currently leveraging Plotly/Dash), and so occupies a slightly different niche from libraries offering primarily the lower-level components themselves. It also removes the requirement from users to implement certain code standards for the assembled code themselves, and therefore saves time on often time consuming things such as writing unit tests and ensuring linting coverage. | Many BI tools incorporate the assembly of higher-level concepts automatically from GUI (drop-and-drag) interfaces, and occupy a slightly different niche to the configuration driven assembly offered by Vizro. | + | **Inbuilt visual design decisions** | | Many Python packages offer inbuilt color choices, and visual design choices for certain components.

Vizro applies that to a wide range of component combinations and complex user flows, offering a holistic and comprehensive approach to automatically enable beautiful visual design best practices, whilst allowing customization and flexibility. | Many BI tools supply inbuilt color choices and visual design choices for certain components.

Vizro offers inbuilt visual design for components in addition to the ability to customize them in a flexible way where needed (for example through CSS), and automatic arrangement of components on the screen. | + | **Inbuilt application architecture decisions** | | Many Python packages supply inbuilt application architecture choices for certain functionalities.

Vizro applies that to a wide range of component combinations and complex user flows, offering a holistic and comprehensive approach to the entire application architecture, whilst allowing customization and flexibility where relevant. | Many BI tools apply proprietary application architecture by default.

Vizro enables the user to view and understand the application architecture by directly viewing the code. | + | **Declarative configuration, in multiple formats** | | Many Python packages give a mostly declarative configuration, with limited ability to leverage multiple formats such as Pydantic models and JSON.

Vizro simplifies this approach, and facilitates the extension of the assembly system to enable integration with other tools such as Kedro to programmatically and dynamically generate the relevant dashboard configurations in a streamlined way. | Many BI tools solely use a GUI and/or drag-and-drop interface for defining dashboards, and leverage proprietary configuration formats.

Vizro enables the user to view and understand the configuration by viewing it directly, in addition to being able to edit it directly. | + | **Mostly tech agnostic “grammar of dashboards”** | | Many Python packages supply an effective “grammar of charts” and Python specific declaration.

Vizro offers a “grammar of dashboards” with declaration which is largely tech agnostic and can be extended to non-Python languages. | Many BI tools utilize an implicit internal “grammar of dashboards” which is specific to the proprietary language(s) on with which they are built.

Vizro offers an explicit configuration grammar and allows users to leverage a mostly tech agnostic approach (which can be extended in future). | + | **Inbuilt validation** | | Many Python packages supply individual components or non-Pydantic based models.

Vizro offers the advantages of Pydantic based models for many elements of the configuration process, which leverages the validation and guidance inherent in that process to facilitate implementation by users. | Many BI tools have inbuilt guideline systems to ensure components are combined in a valid way, and give feedback to the manual user to help guide through that process.

Vizro utilizes a flexible system which can be extended to give feedback to programmatic generation of configuration. | + | **Modularity** | | Many Python packages support modularity of components such as charts and controls.

Vizro supports modularity of groups of components (such as their implementation together as a dashboard screen), which can be easily transferred between implementations as configuration. | Many BI tools supply extensions and plugins which support modularity of visualizations.

Vizro supports modularity of groups of components (such as their implementation together as a dashboard screen), which can be easily transferred between implementations as configuration. | + | **Flexibility** | | Many Python packages offer some low-code and/or high-code approaches, which offer varying degrees of flexibility.

Vizro supports a holistic combination of low-code and high-code approaches which support less technical and more technical users individually while allowing them much flexibility in implementation according to their technical level. | Many BI tools offer a no-code (or at least low-code) approach to creating charts and dashboards, along with varying forms of plugins to increase flexibility.

Vizro unlocks Python custom functions to be able to power this user driven flexibility in a technically advanced way, with a high degree of control and visibility over the code, whilst also supporting less technical users through low-code configuration. | + | **Scaling** | | Many Python packages benefit from the ability to propagate updates programmatically, while applying that to “glue code”.

Vizro makes it easy to scale by replicating the relatively small amount of configuration in 1 file between usages, rather than replicating a large amount of code across many files between usages.

Updating configuration programmatically from a central location is often easier than updating code. When sharing between users, it can be easier to inherit and understand configuration than the underlying code. The visual consistency makes it easier to scale within or between projects while maintaining visual coherence. | Many BI tools allow scaling through GUI drag-and-drop interfaces (having some functionality for duplication or propagating changes) for single users.

Vizro makes it easy to leverage tools such as Git to enable almost any number of users to collaborate effectively, therefore allowing the number of developers on a single project to scale easily.

Since updates can be propagated programmatically easily from a central location, it allows scaling across almost any number of related implementations which can be kept up to date and aligned without the need to manually adjust each implementation when updates are required. | + | **Python first** | | Python packages are already Python focused.

Vizro is no different in this respect. By leveraging Plotly/Dash, Vizro is also able to benefit from the power and flexibility offered by JavaScript via React, whilst still presenting a format that is Python first to the user (by making use of the ability offered by Dash to effectively wrap those components into Python). | Many BI tools do not offer direct or full integration with Python.

Vizro supports a Python first approach which leverages the power and flexibility of Python, and the open source community supporting that wide ranging functionality. | + | **Open source** | | Many Python packages also use an open source license, and offer ongoing development and maintenance.

Vizro is no different in this respect. | Many BI tools follow a license fee model and/or charge for ongoing development and maintenance.

Vizro requires no license fee, and offers ongoing development and support, which helps to remove some barriers to usage. | + +## When would an alternative to Vizro be more suitable? There are a number of cases where alternatives to Vizro may be more suitable, including: @@ -125,3 +159,5 @@ There are a number of cases where alternatives to Vizro may be more suitable, in - where development teams already contain designers and JavaScript (or equivalent) engineers with bandwidth to build bespoke applications (though Vizro can sometimes still be used effectively to rapidly prototype potential solutions) - where Python developers are already very comfortable leveraging other Python packages + + diff --git a/vizro-core/docs/pages/tutorials/explore-components.md b/vizro-core/docs/pages/tutorials/explore-components.md index 8dc993c11..cce9ddb05 100644 --- a/vizro-core/docs/pages/tutorials/explore-components.md +++ b/vizro-core/docs/pages/tutorials/explore-components.md @@ -6,13 +6,13 @@ If you haven't yet done so, you may want to review the [first dashboard tutorial ## 1. Install Vizro and get ready to run your code -To get started with this tutorial, make sure you have [installed Vizro](../user-guides/install.md), and can run the dashboard code [within a Jupyter Notebook cell](../first-dashboard/#2-open-a-jupyter-notebook), or from a Python script. +To get started with this tutorial, make sure you have [installed Vizro](../user-guides/install.md), and can run the dashboard code within a Jupyter Notebook cell or from a Python script. ## 2. Create a first dashboard page In this section we create a new [`Page`][vizro.models.Page] called `first_page`. -The foundation of every Vizro dashboard is a [`Page`][vizro.models.Page] object. A page uses a set of [component types](../user-guides/components/) to display the content of the page. These components can be objects such as [`Graph`][vizro.models.Graph], [`Table`][vizro.models.Table], [`Card`][vizro.models.Card], [`Button`][vizro.models.Button], [`Container`][vizro.models.Container], or [`Tabs`][vizro.models.Tabs]. +The foundation of every Vizro dashboard is a [`Page`][vizro.models.Page] object. A page uses a set of [component types](../user-guides/components.md) to display the content of the page. These components can be objects such as [`Graph`][vizro.models.Graph], [`Table`][vizro.models.Table], [`Card`][vizro.models.Card], [`Button`][vizro.models.Button], [`Container`][vizro.models.Container], or [`Tabs`][vizro.models.Tabs]. ### 2.1. Add the first figure @@ -42,7 +42,7 @@ The code below shows the steps necessary to add a box plot to the page: vm.Graph( id="box_cont", figure=px.box(gapminder_data, x="continent", y="lifeExp", color="continent", - labels={"lifeExp": "Life Expectancy", "continent":"Continent"}), + labels={"lifeExp": "Life Expectancy", "continent": "Continent"}), ), ], ) @@ -77,8 +77,8 @@ You can combine and arrange various types of `components` on a dashboard page. T The code below adds two components to the page: -* A [`Card`][vizro.models.Card] to insert Markdown text into the dashboard. -* A [`Graph`][vizro.models.Graph] to illusrate GDP development per continent since 1952 as a bar chart. +* A [`Card`][vizro.models.Card] to insert markdown text into the dashboard. +* A [`Graph`][vizro.models.Graph] to illustrate GDP development per continent since 1952 as a line graph. !!! warning "Before you run this code in a Jupyter Notebook" @@ -104,7 +104,7 @@ The code below adds two components to the page: id="line_gdp", figure=px.line(gapminder_data, x="year", y="gdpPercap", color="continent", labels={"year": "Year", "continent": "Continent", - "gdpPercap":"GDP Per Cap"}), + "gdpPercap":"GDP Per Cap"}, title=''), ) ``` @@ -133,13 +133,13 @@ The code below adds two components to the page: vm.Graph( id="box_cont", figure=px.box(gapminder_data, x="continent", y="lifeExp", color="continent", - labels={"lifeExp": "Life Expectancy", "continent":"Continent"}), + labels={"lifeExp": "Life Expectancy", "continent": "Continent"}), ), vm.Graph( id="line_gdp", figure=px.line(gapminder_data, x="year", y="gdpPercap", color="continent", labels={"year": "Year", "continent": "Continent", - "gdpPercap":"GDP Per Cap"}), + "gdpPercap":"GDP Per Cap"}, title=''), ), ], @@ -155,6 +155,13 @@ The code below adds two components to the page: As you explore the dashboard, you may notice that the current layout could be further enhanced. The charts may appear cramped, while the text component has ample unused space. The next section explains how to configure the layout and arrange the components. +!!! tip "An introduction to Vizro-AI" + + In the example above, the code to create the line graph was generated using [Vizro-AI](https://vizro.readthedocs.io/en/latest/pages/tutorials/first-dashboard/). Vizro-AI enables you to use English, or other languages, to create interactive charts with [Plotly](https://plotly.com/python/) by simplifying the process through use of a large language model. In essence, Vizro-AI generates code from natural language instructions so that you can add it into a Vizro dashboard, such as in the example above. + + Find out more in the [Vizro-AI documentation](https://vizro.readthedocs.io/projects/vizro-ai/en/latest/)! + + ### 2.3. Configure the layout By default, Vizro places each element in the order it was added to `components` list, and spaces them equally. @@ -222,7 +229,7 @@ Run the code below to apply the layout to the dashboard page: vm.Graph( id="box_cont", figure=px.box(gapminder_data, x="continent", y="lifeExp", color="continent", - labels={"lifeExp": "Life Expectancy", "continent":"Continent"}), + labels={"lifeExp": "Life Expectancy", "continent": "Continent"}), ), vm.Graph( id="line_gdp", @@ -253,14 +260,14 @@ There are two types of control: * [`Parameters`][vizro.models.Parameter] enable users to change arguments or properties of the components, such as adjusting colors. -The guides for [Filters](../user-guides/filters.md) and [Parameters](../user-guides/parameters.md) explain how to apply [`Filters`][vizro.models.Filter] and [`Parameters`][vizro.models.Parameter]. For further customization, refer to the [guide on selectors](../user-guides/selectors.md). +The guides on [`How to use Filters`](../user-guides/filters.md) and [`How to use Parameters`](../user-guides/parameters.md) offer instructions on their application. For further customization, refer to the guide on [`How to use selectors`](../user-guides/selectors.md). To link a control to a component, use the id assigned to the component, which is unique across all dashboard pages and serves as a reference to target it. To illustrate, let's add a [`Filter`][vizro.models.Filter] on specific continents of the underlying gapminder data. The [`Filter`][vizro.models.Filter] requires the `column` argument, that denotes the target column to be filtered. Each `control` also has a `targets` parameter, to specify the -datasets and components targeted by the `control`. For this dashboard, both charts +data and components targeted by the `control`. For this dashboard, both charts are listed in the `targets` parameter, meaning that the filter is be applied to both charts. However, you can apply the [`Filter`][vizro.models.Filter] to only one specific chart if required. !!! example "Configure filter" @@ -296,7 +303,7 @@ are listed in the `targets` parameter, meaning that the filter is be applied to vm.Graph( id="box_cont", figure=px.box(gapminder_data, x="continent", y="lifeExp", color="continent", - labels={"lifeExp": "Life Expectancy", "continent":"Continent"}), + labels={"lifeExp": "Life Expectancy", "continent": "Continent"}), ), vm.Graph( id="line_gdp", @@ -318,17 +325,22 @@ are listed in the `targets` parameter, meaning that the filter is be applied to [FirstPage4]: ../../assets/tutorials/dashboard/dashboard24.png -Fantastic job! You have completed first dashboard page and gained valuable skills to [create an initial figure on a dashboard page](#2-create-a-first-dashboard-page), [add extra components](#22-add-further-components), [arrange them in a layout configuration](/#23-configure-the-layout), and [set up an interactive dashboard control](#24-add-a-control-for-dashboard-interactivity). +Fantastic job! You have completed first dashboard page and gained valuable skills to: + +1. [Create an initial figure on a dashboard page](#2-create-a-first-dashboard-page) +2. [Add extra components](#22-add-further-components) +3. [Arrange them in a layout configuration](#23-configure-the-layout) +4. [Set up an interactive dashboard control](#24-add-a-control-for-dashboard-interactivity). ## 3. Create a second dashboard page -This section adds a second dashboard page and explains how to use controls and selectors. The new page is structured similarly to the page you just created, but contains two charts that visualize the [iris dataset](https://plotly.com/python-api-reference/generated/plotly.express.data.html#plotly.express.data.iris). +This section adds a second dashboard page and explains how to use controls and selectors. The new page is structured similarly to the page you created, but contains two charts that visualize the [iris data](https://plotly.com/python-api-reference/generated/plotly.express.data.html#plotly.express.data.iris). The code below illustrates how to add the page, titled `second_page` to the dashboard by calling `vm.Dashboard(pages=[first_page,second_page])`. There are two `Graph` objects added to the list of components. To enable interactivity on those components, we add two [`Parameters`][vizro.models.Parameter] to the list of `controls`. In creating a [`Parameter`][vizro.models.Parameter] object, you define the `target` it applies to. In the code below: -* The first parameter enables the user to change the color mapping for the `virginica` category of the iris data set, targeting both charts. +* The first parameter enables the user to change the color mapping for the `virginica` category of the iris data, targeting both charts. * The second parameter adjusts the opacity of the first chart alone, through `scatter_iris.opacity`. @@ -402,7 +414,7 @@ for parameters](../user-guides/parameters.md). vm.Graph( id="box_cont", figure=px.box(gapminder_data, x="continent", y="lifeExp", color="continent", - labels={"lifeExp": "Life Expectancy", "continent":"Continent"}), + labels={"lifeExp": "Life Expectancy", "continent": "Continent"}), ), vm.Graph( id="line_gdp", @@ -461,7 +473,7 @@ for parameters](../user-guides/parameters.md). ### 3.1. Customize with selectors -The code in the example above uses two different types of [`selector` objects](../user-guides/selectors/), namely +The code in the example above uses two different types of [`selector`](../user-guides/selectors.md) objects, namely [`Dropdown`][vizro.models.Dropdown] and [`Slider`][vizro.models.Slider] upon the [`Parameters`][vizro.models.Parameter]. The `selectors` enable configuration of the controls to customize their behavior and appearance. @@ -498,6 +510,7 @@ The code below illustrates a functional dashboard where you can navigate from th of the subpages. Additionally, you can use the navigation panel on the left side to switch between the three pages. !!! example "Final dashboard" + === "Code" ```py home_page = vm.Page( @@ -580,7 +593,7 @@ of the subpages. Additionally, you can use the navigation panel on the left side vm.Graph( id="box_cont", figure=px.box(gapminder_data, x="continent", y="lifeExp", color="continent", - labels={"lifeExp": "Life Expectancy", "continent":"Continent"}), + labels={"lifeExp": "Life Expectancy", "continent": "Continent"}), ), vm.Graph( id="line_gdp", @@ -661,6 +674,6 @@ to [Navigation](../user-guides/navigation.md). Vizro doesn't end here, and we only covered the key features, but there is still much more to explore! You can learn: - How to create you own components under [custom components](../user-guides/custom-components.md). -- How to add custom styling using [custom css](../user-guides/assets.md). +- How to add custom styling using [static assets](../user-guides/assets.md) such as custom css or JavaScript files. - How to use [Actions](../user-guides/actions.md) for example, for chart interaction or custom controls. - How to create dashboards from `yaml`, `dict` or `json` following the [dashboard guide](../user-guides/dashboard.md). diff --git a/vizro-core/docs/pages/tutorials/first-dashboard.md b/vizro-core/docs/pages/tutorials/first-dashboard.md index 61b477665..02c44685c 100644 --- a/vizro-core/docs/pages/tutorials/first-dashboard.md +++ b/vizro-core/docs/pages/tutorials/first-dashboard.md @@ -2,17 +2,19 @@ This is a short tutorial for you to create your first dashboard, showing you the basic setup so you can explore Vizro further. -Once you've completed this tutorial, the following [Explore Vizro tutorial](../tutorials/explore-components.md) creates a more complex dashboard so you can explore Vizro's features. +Once you've completed this tutorial, the following ["Explore Vizro" tutorial](explore-components.md) creates a more complex dashboard so you can explore Vizro's features. ## Get started + ### 1. Install Vizro and its dependencies - + If you haven't already installed Vizro, follow the [installation guide](../user-guides/install.md). + ### 2. Open a Jupyter Notebook - + A good way to initially explore Vizro is from inside a Jupyter Notebook. ??? "Install and run Jupyter" @@ -48,8 +50,9 @@ You should see a return output of the form `x.y.z`. If you are following this tutorial in a Jupyter Notebook, you need to restart the kernel each time you evaluate the code. If you do not, you will see error messages such as "Components must uniquely map..." because those components already exist from the previous evaluation. + ### 3. Create your first dashboard - + Paste the following example into a Notebook cell, run it, and view the generated dashboard by typing `localhost:8050` into your browser. !!! example "Dashboard Configuration Syntaxes" @@ -81,7 +84,7 @@ Paste the following example into a Notebook cell, run it, and view the generated [Dashboard]: ../../assets/user_guides/dashboard/dashboard.png - + ### 4. Explore further - -You are now ready to explore Vizro further, by working through the [Explore Vizro tutorial](../tutorials/explore-components.md) or by consulting the [how-to guides](../first-dashboard/). + +You are now ready to explore Vizro further, by working through the ["Explore Vizro" tutorial](explore-components.md) or by consulting the [how-to guides](../user-guides/dashboard.md). diff --git a/vizro-core/docs/pages/user-guides/actions.md b/vizro-core/docs/pages/user-guides/actions.md index 114747eb5..7b561d337 100644 --- a/vizro-core/docs/pages/user-guides/actions.md +++ b/vizro-core/docs/pages/user-guides/actions.md @@ -1,10 +1,11 @@ # How to use actions -This guide shows you how to use actions, a concept that is similar, but not identical, to [callbacks](https://dash.plotly.com/basic-callbacks) in `Dash`. -Many components of a dashboard (e.g. [`Graph`][vizro.models.Graph] or [`Button`][vizro.models.Button]) have an optional `actions` argument, where you can enter the [`Action`][vizro.models.Action] model. +This guide shows you how to use actions, an idea that is similar to [callbacks](https://dash.plotly.com/basic-callbacks) in `Dash`. +Many components of a dashboard (for example, [`Graph`][vizro.models.Graph] or [`Button`][vizro.models.Button]) have an optional `actions` argument, where you can enter the [`Action`][vizro.models.Action] model. -In a nutshell, using the [`Action`][vizro.models.Action] model together with an action function allows you to create complex functionality on a variety of triggers in your dashboard. -There is already a range of reusable action functions available. +By combining the [`Action`][vizro.models.Action] model with an action function, you can create complex dashboard interactions triggered by various events. + +There are already a few action functions you can reuse. ???+ info "Overview of currently available pre-defined action functions" @@ -17,16 +18,21 @@ To attach an action to a component, you must enter the [`Action`][vizro.models.A add a desired pre-defined action function into the `function` argument of the [`Action`][vizro.models.Action]. ??? note "Note on `Trigger`" - Currently each component has one pre-defined trigger property. A trigger property is an attribute of the component that triggers a configured action (e.g. for the `Button` it is `n_click`). + Currently each component has one pre-defined trigger property. A trigger property is an attribute of the component that triggers a configured action (for example, for the `Button` it is `n_click`). -The below sections are guides on how to leverage pre-defined action functions. +The below sections are guides on how to use pre-defined action functions. ### Export data -In order to enable downloading data, you can add the [`export_data`][vizro.actions.export_data] action function to the [`Button`][vizro.models.Button] component. Hence, as -a result, when a dashboard user now clicks the button, all data on the page will be downloaded. +To enable downloading data, you can add the [`export_data`][vizro.actions.export_data] action function to the [`Button`][vizro.models.Button] component. +Hence, as a result, when a dashboard user now clicks the button, all data on the page will be downloaded. + +When data from a [custom chart](custom-charts.md) is exported it is the contents of the `data_frame` input argument that is exported. +Therefore, the exported data will reflect any native filters and parameters, but no transformations to the `data_frame` done inside the chart function. + !!! example "`export_data`" + === "app.py" ```py import vizro.models as vm @@ -65,7 +71,7 @@ a result, when a dashboard user now clicks the button, all data on the page will ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -117,7 +123,8 @@ actions=[vm.Action(function=filter_interaction(targets=["scatter_relation_2007"] ```py Graph(figure=px.scatter(..., custom_data=["continent"])) ``` -Selecting a data point with a corresponding value of "Africa" in the continent column will result in filtering the dataset of target charts to show only entries with "Africa" in the continent column. The same applies when providing multiple columns in `custom_data`. + +Selecting a data point with a corresponding value of "Africa" in the continent column will result in filtering the data of target charts to show only entries with "Africa" in the continent column. The same applies when providing multiple columns in `custom_data`. !!! tip - You can reset your chart interaction filters by refreshing the page @@ -126,6 +133,7 @@ Selecting a data point with a corresponding value of "Africa" in the continent c Here is an example of how to configure a chart interaction when the source is a [`Graph`][vizro.models.Graph] component. !!! example "Graph `filter_interaction`" + === "app.py" ```py import vizro.models as vm @@ -170,7 +178,7 @@ Here is an example of how to configure a chart interaction when the source is a ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -210,6 +218,7 @@ Here is an example of how to configure a chart interaction when the source is a Here is an example of how to configure a chart interaction when the source is an [`AgGrid`][vizro.models.AgGrid] component. !!! example "AgGrid `filter_interaction`" + === "app.py" ```py import vizro.models as vm @@ -251,7 +260,7 @@ Here is an example of how to configure a chart interaction when the source is an ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -283,17 +292,18 @@ Here is an example of how to configure a chart interaction when the source is an [Table]: ../../assets/user_guides/actions/actions_table_filter_interaction.png -## How to customize pre-defined actions -Many pre-defined actions are customizable which helps to achieve more specific desired goal. For specific options, please -refer to the [API reference][vizro.actions] on this topic. -### Chaining actions +## Customize pre-defined actions +Many pre-defined actions are customizable which helps to achieve a more specific goal. Refer to the [API reference][vizro.actions] for the options available. + +### Chain actions The `actions` parameter for the different screen components accepts a `List` of [`Action`][vizro.models.Action] models. -This means that it's possible to set a list of actions that will be executed by triggering only one component. +This means that it's possible to chain together a list of actions that are executed by triggering only one component. The order of action execution is guaranteed, and the next action in the list will start executing only when the previous one is completed. !!! example "Actions chaining" + === "app.py" ```py import vizro.models as vm @@ -382,4 +392,4 @@ The order of action execution is guaranteed, and the next action in the list wil [Graph3]: ../../assets/user_guides/actions/actions_chaining.png -To enhance existing actions, please see our How-to-guide on creating [custom actions](custom-actions.md). +To enhance existing actions, see our how-to-guide on creating [custom actions](custom-actions.md). diff --git a/vizro-core/docs/pages/user-guides/assets.md b/vizro-core/docs/pages/user-guides/assets.md index ade36ab14..0b4c82b67 100644 --- a/vizro-core/docs/pages/user-guides/assets.md +++ b/vizro-core/docs/pages/user-guides/assets.md @@ -5,7 +5,7 @@ with which you would like to enhance/change the appearance of your dashboard. To add images, custom CSS or JS files, create a folder named `assets` in the root of your app directory and insert your files. Assets included in that folder are automatically served after serving Vizro's static files via the `external_stylesheets` and `external_scripts` arguments of [Dash](https://dash.plotly.com/external-resources#adding-external-css/javascript). -The user-provided `assets` folder thus always takes precedence. +The user's `assets` folder thus always takes precedence. ```text title="Example folder structure" ├── app.py @@ -22,14 +22,49 @@ The user-provided `assets` folder thus always takes precedence. !!! warning "Dash Bootstrap Themes" - Please note that Vizro is currently not compatible with [Dash Bootstrap Themes](https://dash-bootstrap-components.opensource.faculty.ai/docs/themes/). + Note that Vizro is currently not compatible with [Dash Bootstrap Themes](https://dash-bootstrap-components.opensource.faculty.ai/docs/themes/). Adding a Bootstrap stylesheet will have no visual effect on the [components](https://vizro.readthedocs.io/en/stable/pages/user_guides/components/) included in Vizro. -## How to change the favicon +## Change the favicon To change the default favicon (website icon appearing in the browser tab), add a file named `favicon.ico` to your `assets` folder. For more information, see the [Dash documentation](https://dash.plotly.com/external-resources#changing-the-favicon). -## How to overwrite global CSS properties + +## Change the `assets` folder path +If you do not want to place your `assets` folder in the root directory of your app, you can +specify an alternative path through the `assets_folder` argument of the [`Vizro`][vizro.Vizro] class. + +```python +from vizro import Vizro +import vizro.models as vm + +page = +dashboard = vm.Dashboard(pages=[page]) + +app = Vizro(assets_folder="path/to/assets/folder").build(dashboard).run() + +``` + +Note that in the example above, you still need to configure your [`Page`][vizro.models.Page]. +See more information in the [Pages User Guide](pages.md). + + +## Include a meta tags image + +Vizro automatically adds [meta tags](https://metatags.io/) to display a preview card when your app is shared on social media and chat +clients. To include an image in the preview, place an image file in the assets folder named `app.` or +`logo.`. Vizro searches the assets folder and uses the first one it finds. + +Image types of `apng`, `avif`, `gif`, `jpeg`, `jpg`, `png`, `svg`, and `webp` are supported. + +## Add a logo image + +Vizro will automatically incorporate the dashboard logo in the top-left corner of each page if an image named `logo.` is present within the assets folder. + +Image types of `apng`, `avif`, `gif`, `jpeg`, `jpg`, `png`, `svg`, and `webp` are supported. + + +## Overwrite global CSS properties To overwrite any global CSS properties of existing components, target the right CSS property and place your CSS files in the `assets` folder. This will overwrite any existing defaults for that CSS property. For reference, see the [Vizro CSS files](https://github.com/mckinsey/vizro/tree/main/vizro-core/src/vizro/static/css). @@ -68,7 +103,7 @@ For reference, see the [Vizro CSS files](https://github.com/mckinsey/vizro/tree/ ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -87,42 +122,119 @@ For reference, see the [Vizro CSS files](https://github.com/mckinsey/vizro/tree/ [AssetsCSS]: ../../assets/user_guides/assets/css_change.png -## How to overwrite CSS properties in selective components -To overwrite CSS properties of selective components, provide an ID to the relevant component and target the right CSS property. -For more information, see this [CSS selectors tutorial](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors/Selector_structure). +## Overwrite CSS properties for specific pages +To style components for a specific page, use the page's `id` in CSS selectors. By default, this is the [same as the page `title`](pages.md), but such a value might not be a valid CSS identifier. +A suitable `id` must be unique across all models in the dashboard and should contain only alphanumeric characters, hyphens (`-`) and underscores (`_`). In particular, note that spaces are _not_ allowed. + +Suppose you want to hide the page title on one page only. Here's how you can achieve this: + +1. Give a valid `id` to the `Page`, for example `Page(id="page-with-hidden-title", title="Page with hidden title", ...)`. +2. Identify the CSS class or CSS `id` you need to target. To hide the page title, you need to hide the container with the `id `right-header`. +3. Use the `id` in combination with CSS selectors to change the relevant CSS properties. +4. Add your custom css file to the `assets` folder as explained above. + + +!!! example "Hide page title on selected pages" + === "my_css_file.css" + ```css + #page-with-hidden-title #right-header { + display: none; + } + ``` + === "app.py" + ```py + import vizro.models as vm + from vizro import Vizro + + page_one = vm.Page( + id="page-with-hidden-title", + title="Page with hidden title", + components=[vm.Card(text="""# Placeholder""")] + ) + + page_two = vm.Page( + title="Page with shown title", + components=[vm.Card(text="""# Placeholder""")] + ) + + dashboard = vm.Dashboard(pages=[page_one, page_two]) + Vizro().build(dashboard).run() + ``` + === "app.yaml" + ```yaml + # Still requires a .py to add data to the data manager and parse YAML configuration + # See yaml_version example + pages: + - components: + - text: | + # Placeholder + type: card + title: Page with hidden title + id: page-with-hidden-title + - components: + - text: | + # Placeholder + type: card + title: Page with shown title + ``` + === "Result" + [![PageTitle]][PageTitle] + + [PageTitle]: ../../assets/user_guides/assets/css_page_title.png + + +## Overwrite CSS properties in selective components +To adjust CSS properties for specific components, like altering the appearance of a selected [`Card`][vizro.models.Card] rather than all `Card`s, +you need to use a CSS selector that targets the right CSS property. +If you're unfamiliar with CSS selectors, you can refer to this [tutorial](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_selectors/Selector_structure) for guidance. + +Let's say we want to change the background and font-color of a specific `Card`. + +Here's how you can do it: + +1. Assign a unique `id` to the relevant `Card`, for example: `Card(id="custom-card", ...)` +2. Run your dashboard and open it in your browser +3. View the HTML source to find the appropriate CSS class or element you need to target. For example, if you inspect a page with [Chrome DevTools](https://developer.chrome.com/docs/devtools/) then you can [directly copy the CSS selector](https://stackoverflow.com/questions/4500572/how-can-i-get-the-css-selector-in-chrome). -Let's say we want to change the background and font-color of one [`Card`][vizro.models.Card] instead of all existing Cards in the Dashboard. -We can use the ID of the outermost Div to target the inner sub-components of the card. Note that all our components have an ID attached to the outermost Div, -following the pattern `"{component_id}_outer"`. +It's essential to understand the relationship between the targeted CSS class or element and the component assigned the `id`, for example: -To achieve this, do the following: + +```html title="HTML structure of a `Card`" +
+
+

Lorem ipsum dolor sit amet consectetur adipisicing elit.

+
+
+``` + -1. Provide a custom `id` to the relevant `Card` e.g `Card(id="my_card", ...)` -2. Take a look at the source code of the component to see which CSS Class you need to target e.g. `"card"` or `"card_text"` -3. Use CSS selectors to target the right property e.g. by leveraging the ID of the outermost Div `"my_card_outer"` +* **Main element with `id`:** There is a `
` with our `id="custom-card"`. +* **Parent element:** That `
` is wrapped inside a parent `
` with the class name `"card"`. This is the element we need to target to change the background color. +* **Child element:** The card text is wrapped inside a `

` that is a child of the `

` with our `id`. This is the element we need to target to change the font color. !!! example "Customizing CSS properties in selective components" === "my_css_file.css" ```css - #my_card_outer.card { + /* Apply styling to parent */ + .card:has(#custom-card) { background-color: white; } - #my_card_outer .card_text p { + /* Apply styling to child */ + #custom-card p { color: black; } ``` === "app.py" ```py - import os import vizro.models as vm from vizro import Vizro page = vm.Page( title="Changing the card color", components=[ - vm.Card(id="my_card", text="""Lorem ipsum dolor sit amet consectetur adipisicing elit."""), + vm.Card(id="custom-card", text="""Lorem ipsum dolor sit amet consectetur adipisicing elit."""), vm.Card(text="""Lorem ipsum dolor sit amet consectetur adipisicing elit.""") ], ) @@ -133,14 +245,14 @@ To achieve this, do the following: ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: - text: | Lorem ipsum dolor sit amet consectetur adipisicing elit. type: card - id: my_card + id: custom-card - text: | Lorem ipsum dolor sit amet consectetur adipisicing elit. type: card @@ -152,43 +264,12 @@ To achieve this, do the following: [CardCSS]: ../../assets/user_guides/assets/css_change_card.png -CSS properties will be applied with the last served file taking precedence. The order of serving is: - -1. Dash built-in stylesheets -2. Vizro built-in stylesheets -3. User assets folder stylesheets - -Within each of these categories, individual files are served in alphanumerical order. - -## How to change the `assets` folder path -If you do not want to place your `assets` folder in the root directory of your app, you can -specify an alternative path through the `assets_folder` argument of the [`Vizro`][vizro.Vizro] class. - -```python -from vizro import Vizro -import vizro.models as vm +??? note "Order of serving CSS files" -page = -dashboard = vm.Dashboard(pages=[page]) + CSS properties will be applied with the last served file taking precedence. The order of serving is: -app = Vizro(assets_folder="path/to/assets/folder").build(dashboard).run() - -``` - -Note that in the example above, you still need to configure your [`Page`][vizro.models.Page]. -See more information in the [Pages User Guide](pages.md). + 1. Dash built-in stylesheets + 2. Vizro built-in stylesheets + 3. User assets folder stylesheets - -## How to include a meta tags image - -Vizro automatically adds [meta tags](https://metatags.io/) to display a preview card when your app is shared on social media and chat -clients. To include an image in the preview, place an image file in the assets folder named `app.` or -`logo.`. Vizro searches the assets folder and uses the first one it finds. - -Image types of `apng`, `avif`, `gif`, `jpeg`, `jpg`, `png`, `svg`, and `webp` are supported. - -## How to add a logo image - -Vizro will automatically incorporate the dashboard logo in the top-left corner of each page if an image named `logo.` is present within the assets folder. - -Image types of `apng`, `avif`, `gif`, `jpeg`, `jpg`, `png`, `svg`, and `webp` are supported. + Within each of these categories, individual files are served in alphanumeric order. diff --git a/vizro-core/docs/pages/user-guides/card-button.md b/vizro-core/docs/pages/user-guides/card-button.md index 0be49793f..6c495e5bb 100755 --- a/vizro-core/docs/pages/user-guides/card-button.md +++ b/vizro-core/docs/pages/user-guides/card-button.md @@ -4,7 +4,7 @@ This guide shows you how to use cards and buttons to visualize and interact with ## Cards -The [`Card`][vizro.models.Card] is a flexible and extensible component, allowing for customization via Markdown text. +The [`Card`][vizro.models.Card] is a flexible and extensible component that enables customization via markdown text. Refer to any online guide for [basic markdown usage](https://markdown-guide.readthedocs.io/en/latest/). You can add a [`Card`][vizro.models.Card] to your dashboard by inserting the [`Card`][vizro.models.Card] into the `components` argument of the [`Page`][vizro.models.Page]. @@ -34,7 +34,7 @@ You can add a [`Card`][vizro.models.Card] to your dashboard by inserting the [`C ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: @@ -49,11 +49,11 @@ You can add a [`Card`][vizro.models.Card] to your dashboard by inserting the [`C [Card]: ../../assets/user_guides/components/card.png -### How to customize card text +### Customize card text -The [`Card`][vizro.models.Card] utilizes the `dcc.Markdown` component from Dash as its underlying text component. +The [`Card`][vizro.models.Card] uses the `dcc.Markdown` component from Dash as its underlying text component. For more details on customizing the markdown text, refer to the [`dcc.Markdown` component documentation](https://dash.plotly.com/dash-core-components/markdown). -Based on the provided examples from Dash, the [`Card`][vizro.models.Card] model supports the following: +Based on examples from Dash, the [`Card`][vizro.models.Card] model supports the following: - Headers - Emphasis @@ -132,7 +132,7 @@ Based on the provided examples from Dash, the [`Card`][vizro.models.Card] model ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: @@ -183,13 +183,13 @@ Based on the provided examples from Dash, the [`Card`][vizro.models.Card] model [CardText]: ../../assets/user_guides/components/card_text.png -### How to place an image on a card +### Place an image on a card Images can be added to the `text` parameter by using the standard markdown syntax: `![Image ALT text](Image URL)` -An image ALT text provides a description to your image and serves e.g. as a text placeholder or to improve the +An image ALT text offers a description to your image and serves as a text placeholder or to improve the accessibility of your app. Providing an image ALT text is optional. 1. To use a relative Image URL, place an image of your choice into your `assets` folder first @@ -227,7 +227,7 @@ accessibility of your app. Providing an image ALT text is optional. ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: @@ -253,16 +253,17 @@ accessibility of your app. Providing an image ALT text is optional. Note that inserting images using html is by default turned off by the `dcc.Markdown` to prevent users being exposed to cross-site scripting attacks. If you need to turn it on, a custom component would have to be created. -You might notice that the image is quite large, find out how to style images (e.g. position and size) in the next section! +You might notice that the image is quite large. You'll find out how to style images in terms of their position and size in the next section. -### How to style a card image -To change the styling of the image (e.g. size or position), add a URL hash to your image like this: +### Style a card image + +To change the size or position of the image, add a URL hash to your image like this: `![Image ALT text](Image URL#my-image)` Note the added URL hash `#my-image`. Now create a CSS file placed in your `assets` folder -and provide an attribute selector to select images with that matching URL hash. +and give an attribute selector to select images with that matching URL hash. !!! example "Card with styled image" === "images.css" @@ -302,7 +303,7 @@ and provide an attribute selector to select images with that matching URL hash. ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: @@ -368,7 +369,7 @@ and provide an attribute selector to select images with that matching URL hash. ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: @@ -395,7 +396,7 @@ and provide an attribute selector to select images with that matching URL hash. [CardImageFloating]: ../../assets/user_guides/components/card_image_floating.png -### How to create a navigation card +### Create a navigation card !!! note @@ -404,12 +405,12 @@ and provide an attribute selector to select images with that matching URL hash. [guide on navigation](navigation.md). -A navigation card allows you to navigate to a different page via a click on the card area. +A navigation card enables you to navigate to a different page via a click on the card area. To create a navigation card, do the following: - Insert the [`Card`][vizro.models.Card] into the `components` argument of the [`Page`][vizro.models.Page] -- Provide the `text` parameter with a title and some description -- Provide the `href` parameter (relative or absolute URL) +- Pass your markdown text to the `Card.text` +- Pass a relative or absolute URL to the `Card.href` !!! example "Navigation Card" === "app.py" @@ -455,7 +456,7 @@ To create a navigation card, do the following: ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: @@ -494,11 +495,13 @@ If you now click on the card area, you should automatically be redirected to the When using the [`Card`][vizro.models.Card], keep the following considerations in mind: - - If the href provided is a relative URL, it should match the `path` of the [`Page`][vizro.models.Page] that the [`Card`][vizro.models.Card] should navigate to. - - If the href provided is an absolute link, it should start with `https://` or an equivalent protocol. + - If the href given is a relative URL, it should match the `path` of the [`Page`][vizro.models.Page] that the [`Card`][vizro.models.Card] should navigate to. + - If the href given is an absolute link, it should start with `https://` or an equivalent protocol. + + +### Add an icon +If you want to add an icon to your card, add your image as described in the [earlier section](#placing-images) -### How to add an icon -If you want to add an icon to your card, just add your image as described in the [previous section](#placing-images) If you use the image URL hash `icon-top`, the image will be styled according to our default icon styling. !!! example "Navigation Card with Icon" @@ -550,7 +553,7 @@ If you use the image URL hash `icon-top`, the image will be styled according to ``` === "app.yaml" ```yaml hl_lines="5 13" - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: @@ -590,13 +593,33 @@ If you use the image URL hash `icon-top`, the image will be styled according to Note that in the above example the first [`Card`][vizro.models.Card] navigates to an existing [`Page`][vizro.models.Page] in the app with `path = filters-and-parameters` and the second one to an external link. +#### Add an icon responsive to theme switch + +To add an icon that is responsive to the theme switch, you will need to override the value of the [`filter` CSS property](https://developer.mozilla.org/en-US/docs/Web/CSS/filter). + +The `filter` CSS property lets you add visual effects to elements using different functions. In our example, we're using the `--inverse-color` CSS variable from the Vizro theme. + +It uses the CSS `invert()` function to flip the color of the icon when you switch themes. Note that this only works if your initial icon has a white fill color. If your icon is not white, you can change its color by adding `fill="white"` to the SVG code. + +Assign the predefined CSS variable `--inverse-color` to the `filter` property of your selected icon. + +```css +img[src*="#my-image"] { + filter: var(--inverse-color); +} +``` + +??? example "Responsive icon" + ![responsive icon](../../assets/user_guides/components/responsive_icon.gif) + + ## Buttons To enhance dashboard interactions, you can use the [`Button`][vizro.models.Button] component to trigger any pre-defined -action functions such as e.g. exporting chart data. Please refer to the [user guide][vizro.actions] on -[`Actions`][vizro.models.Action] for currently available options. +action functions such as exporting chart data. To use the currently available options for the [`Actions`][vizro.models.Action] +component, check out the [API reference][vizro.actions]. -To add a [`Button`][vizro.models.Button], simply insert it into the `components` argument of the +To add a [`Button`][vizro.models.Button], insert it into the `components` argument of the [`Page`][vizro.models.Page]. You can configure the `text` argument to alter the display text of the [`Button`][vizro.models.Button] and the @@ -607,6 +630,7 @@ In the below example we show how to configure a button to export the filtered da !!! example "Button" + === "app.py" ```py import vizro.models as vm @@ -644,7 +668,7 @@ In the below example we show how to configure a button to export the filtered da ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: @@ -688,7 +712,7 @@ In the below example we show how to configure a button to export the filtered da The [`Button`][vizro.models.Button] component is currently reserved to be used inside the main panel (right-side) of the dashboard. However, there might be use cases where one would like to place the `Button` inside the control panel (left-side) with the other controls. -In this case, simply follow the user-guide outlined for [custom components](custom-components.md) and manually add the `Button` as a valid type to the `controls` argument by running the following lines before your dashboard configurations: +In this case, follow the user-guide outlined for [creating custom components](custom-components.md) and manually add the `Button` as a valid type to the `controls` argument by running the following lines before your dashboard configurations: ```python from vizro import Vizro diff --git a/vizro-core/docs/pages/user-guides/container.md b/vizro-core/docs/pages/user-guides/container.md index efffb2fa8..8d7266268 100755 --- a/vizro-core/docs/pages/user-guides/container.md +++ b/vizro-core/docs/pages/user-guides/container.md @@ -2,10 +2,10 @@ This guide shows you how to use containers to group your components into sections and subsections within the page. -A [`Container`][vizro.models.Container] complements the concept of a [`Page`][vizro.models.Page], and the two models have almost identical arguments. - [`Page.layout`](layouts.md) provides a way to structure the overall layout of the page, and a `Container` allows for more granular control within a specific section of that page. +A [`Container`][vizro.models.Container] complements the idea of a [`Page`][vizro.models.Page], and the two models have almost identical arguments. + [`Page.layout`](layouts.md) offers a way to structure the overall layout of the page, and a `Container` enables more granular control within a specific section of that page. -While there is currently no apparent difference in rendering, additional functionality will be added to the `Container` soon (e.g. controls specific to that container), +While there is currently no clear difference in rendering, extra functionality will be added to the `Container` soon (including controls specific to that container), enhancing the ability to manage related components. ??? note "Displaying multiple containers inside Tabs" @@ -20,12 +20,12 @@ enhancing the ability to manage related components. ## When to use containers -In general, any arbitrarily granular layout can already be achieved using [`Page.layout`](layouts.md) alone and is our -recommended approach if you just want to arrange components on a page with consistent row and/or column spacing. +In general, any arbitrarily granular layout can already be achieved by [using `Page.layout`](layouts.md) alone and is our +recommended approach if you want to arrange components on a page with consistent row and/or column spacing. `Page.layout` has a `grid` argument that sets the overall layout of the page. `Container.layout` also has a `grid` argument. This enables you to insert a further `grid` into a component's space on the page, -allowing for more granular control by breaking the overall page grid into subgrids. +enabling more granular control by breaking the overall page grid into subgrids. Here are a few cases where you might want to use a `Container` instead of `Page.layout`: @@ -39,8 +39,8 @@ Here are a few cases where you might want to use a `Container` instead of `Page. To add a [`Container`][vizro.models.Container] to your page, do the following: 1. Insert the `Container` into the `components` argument of the [`Page`][vizro.models.Page] -2. Provide a `title` to your `Container` -3. Configure your `components`, see [the overview page on the various options](components.md) +2. Set a `title` for your `Container` +3. Configure your `components`, [read the overview page for various options](components.md) 4. (optional) Configure your `layout`, see [the guide on `Layout`](layouts.md) !!! example "Container" @@ -109,7 +109,7 @@ To add a [`Container`][vizro.models.Container] to your page, do the following: === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: @@ -156,14 +156,13 @@ To add a [`Container`][vizro.models.Container] to your page, do the following: !!! note - Note that an almost identical layout can also be achieved using solely the [`Page.layout`](layouts.md) - e.g. by configuring the `Page.layout` as `vm.Layout(grid = [[0, 1], [2, 2]])`. + Note that an almost identical layout can also be achieved using solely the [`Page.layout`](layouts.md) by configuring the `Page.layout` as `vm.Layout(grid = [[0, 1], [2, 2]])`. ## Nested containers Containers can be nested, providing a hierarchical structure for organizing components. -This nesting capability allows users to create more complex layouts and manage related components at any level of granularity. +This nesting capability enables users to create more complex layouts and manage related components at any level of granularity. -To create nested containers, simply add a `Container` to the `components` argument of another `Container`. +To create nested containers, add a `Container` to the `components` argument of another `Container`. ```python title="Example" vm.Container( diff --git a/vizro-core/docs/pages/user-guides/custom-actions.md b/vizro-core/docs/pages/user-guides/custom-actions.md index 8ec3d9bec..f0ec47a97 100644 --- a/vizro-core/docs/pages/user-guides/custom-actions.md +++ b/vizro-core/docs/pages/user-guides/custom-actions.md @@ -1,9 +1,10 @@ # How to create custom actions -This guide demonstrates the usage of custom actions, a concept that shares similarities with, but is not identical to [callbacks](https://dash.plotly.com/basic-callbacks) in `Dash`. +This guide demonstrates the usage of custom actions, an idea that shares similarities with, but is not identical to [callbacks](https://dash.plotly.com/basic-callbacks) in `Dash`. If you want to use the [`Action`][vizro.models.Action] model to perform functions that are not available in the [pre-defined action functions][vizro.actions], you can create your own custom action. Like other [actions](actions.md), custom actions could also be added as an element inside the [actions chain](actions.md#actions-chaining), and it can be triggered with one of many dashboard components. + ## Simple custom actions Custom actions enable you to implement your own action function. Simply do the following: @@ -61,17 +62,19 @@ The following example shows how to create a custom action that postpones executi ```yaml # Custom actions are currently only possible via python configuration ``` + -## Interacting with dashboard inputs and outputs +## Interact with dashboard inputs and outputs When a custom action needs to interact with the dashboard, it is possible to define `inputs` and `outputs` for the custom action. -- `inputs` represents dashboard component properties whose values are passed to the custom action function as arguments. It is a list of strings in the format `"."` (e.g. `"scatter_chart.clickData`"). -- `outputs` represents dashboard component properties corresponding to the custom action function return value(s). Similar to `inputs`, it is a list of strings in the format `"."` (e.g. `"my_card.children"`). +- `inputs` represents dashboard component properties whose values are passed to the custom action function as arguments. It is a list of strings in the format `"."` (for example, `"scatter_chart.clickData`"). +- `outputs` represents dashboard component properties corresponding to the custom action function return value(s). Similar to `inputs`, it is a list of strings in the format `"."` (for example, `"my_card.children"`). The following example shows how to create a custom action that shows the clicked chart data in a [`Card`][vizro.models.Card] component. For further information on the structure and content of the `clickData` property, refer to the Dash documentation on [interactive visualizations](https://dash.plotly.com/interactive-graphing). !!! example "Custom action with dashboard inputs and outputs" + === "app.py" ```py import vizro.models as vm @@ -134,7 +137,7 @@ The following example shows how to create a custom action that shows the clicked Vizro().build(dashboard).run() ``` - 1. Just as for a normal Python function, the names of the arguments `show_species` and `points_data` are arbitrary and do not need to match on to the names of `inputs` in any particular way. + 1. Just as for any Python function, the names of the arguments `show_species` and `points_data` are arbitrary and do not need to match on to the names of `inputs` in any particular way. 2. We _bind_ (set) the argument `show_species` to the value `True` in the initial specification of the `function` field. These are static values that are fixed when the dashboard is _built_. 3. The content of `inputs` will "fill in the gaps" by setting values for the remaining unbound arguments in `my_custom_action`. Here there is one such argument, named `points_data`. Values for these are bound _dynamically at runtime_ to reflect the live state of your dashboard. === "app.yaml" @@ -149,7 +152,7 @@ The following example shows how to create a custom action that shows the clicked ## Multiple return values The return value of the custom action function is propagated to the dashboard components that are defined in the `outputs` argument of the [`Action`][vizro.models.Action] model. If there is a single `output` defined then the function return value is directly assigned to the component property. -If there are multiple `outputs` defined then the return value is iterated through and each part is assigned to each component property given in `outputs` in turn. This behavior is identical to Python's normal flexibility in managing multiple return values. +If there are multiple `outputs` defined then the return value is iterated through and each part is assigned to each component property given in `outputs` in turn. This behavior is identical to Python's flexibility in managing multiple return values. !!! example "Custom action with multiple return values" === "app.py" @@ -218,6 +221,7 @@ If there are multiple `outputs` defined then the return value is iterated throug [CustomAction2]: ../../assets/user_guides/custom_actions/custom_action_multiple_return_values.png + !!! warning - Please note that users of this package are responsible for the content of any custom action function that they write - especially with regard to leaking any sensitive information or exposing to any security threat during implementation. You should always [treat the content of user input as untrusted](https://community.plotly.com/t/writing-secure-dash-apps-community-thread/54619). + Note that users of this package are responsible for the content of any custom action function that they write - especially with regard to leaking any sensitive information or exposing to any security threat during implementation. You should always [treat the content of user input as untrusted](https://community.plotly.com/t/writing-secure-dash-apps-community-thread/54619). diff --git a/vizro-core/docs/pages/user-guides/custom-charts.md b/vizro-core/docs/pages/user-guides/custom-charts.md index 919441483..31a312ecb 100644 --- a/vizro-core/docs/pages/user-guides/custom-charts.md +++ b/vizro-core/docs/pages/user-guides/custom-charts.md @@ -9,7 +9,7 @@ In general, the usage of the custom chart decorator `@capture("graph")` is requi ### When to use a custom chart -- If you want to use any of the post figure update calls by `plotly` e.g., `update_layout`, `update_xaxes`, `update_traces`, etc. (for more details, see the docs on [plotly's update calls](https://plotly.com/python/creating-and-updating-figures/#other-update-methods)) +- If you want to use any of the post figure update calls by `plotly` such as `update_layout`, `update_xaxes`, `update_traces` (for more details, see the docs on [plotly's update calls](https://plotly.com/python/creating-and-updating-figures/#other-update-methods)) - If you want to use a custom-created [`plotly.graph_objects.Figure()`](https://plotly.com/python/graph-objects/) object (in short, `go.Figure()`) and add traces yourself via [`add_trace`](https://plotly.com/python/creating-and-updating-figures/#adding-traces) ### Requirements of a custom chart function @@ -17,7 +17,7 @@ In general, the usage of the custom chart decorator `@capture("graph")` is requi - a `go.Figure()` object is returned by the function - the function must be decorated with the `@capture("graph")` decorator - the function accepts a `data_frame` argument (of type `pandas.DataFrame`) -- the visualization is derived from and requires only one `pandas.DataFrame` (e.g. any further dataframes added through other arguments will not react to dashboard components such as `Filter`) +- the visualization is derived from and requires only one `pandas.DataFrame` (for example, any further dataframes added through other arguments will not react to dashboard components such as `Filter`) The below minimal example can be used as a base to build more sophisticated charts. @@ -31,15 +31,16 @@ def minimal_example(data_frame:pd.DataFrame=None): Building on the above, there are several routes one can take. The following examples are guides on the most common custom requests, but also serve as an illustration of more general principles. +To alter the data in the `data_frame` argument, consider using a [Filter](filters.md) or [parametrized data loading](data.md/#parametrize-data-loading) and [dynamic data](data.md/#dynamic-data). The `data_frame` argument input to a custom chart contains the data **after** filters and parameters have been applied. + !!! tip Custom charts can be targeted by [Filters](filters.md) or [Parameters](parameters.md) without any additional configuration. We will showcase both possibilities in the following examples. In particular the `Parameters` in combination with custom charts can be highly versatile in achieving custom functionality. - ## Enhanced `plotly.express` chart with reference line The below examples shows a case where we enhance an existing `plotly.express` chart. We add a new argument (`hline`), that is used to draw a grey reference line at the height determined by the value of `hline`. The important thing to note is that we then -add a `Parameter` that allows the dashboard user to interact with the argument, and hence move the line in this case. See the `Result` tab for an animation. +add a `Parameter` that enables the dashboard user to interact with the argument, and hence move the line in this case. See the `Result` tab for an animation. !!! example "Custom `plotly.express` scatter chart with a `Parameter`" === "app.py" diff --git a/vizro-core/docs/pages/user-guides/custom-components.md b/vizro-core/docs/pages/user-guides/custom-components.md index a1f90e16d..121209040 100644 --- a/vizro-core/docs/pages/user-guides/custom-components.md +++ b/vizro-core/docs/pages/user-guides/custom-components.md @@ -1,41 +1,41 @@ # How to create custom components -Vizro's public API is deliberately kept small in order to facilitate quick and easy configuration of a dashboard. However, -at the same time, Vizro is easily extensible, so that you can tweak any component to your liking or even create entirely new ones. +Vizro's public API is kept small to enable quick and easy configuration of a dashboard. However, +at the same time, Vizro is extensible, so that you can tweak any component to your liking or even create entirely new ones. If you can't find a component that you would like to have in the code basis, or if you would like to alter/enhance an existing component, then you are in the right place. This guide shows you how to create custom components that are completely new, or enhancements of existing ones. -In general, you can create a custom component based on any dash-compatible component (e.g. [dash-core-components](https://dash.plotly.com/dash-core-components), -[dash-bootstrap-components](https://dash-bootstrap-components.opensource.faculty.ai/), [dash-html-components](https://github.com/plotly/dash/tree/dev/components/dash-html-components), etc.). +In general, you can create a custom component based on any dash-compatible component (for example, [dash-core-components](https://dash.plotly.com/dash-core-components), +[dash-bootstrap-components](https://dash-bootstrap-components.opensource.faculty.ai/), [dash-html-components](https://github.com/plotly/dash/tree/dev/components/dash-html-components)). All our components are based on `Dash`, and they are shipped with a set of sensible defaults that can be modified. If you would like to overwrite one of those defaults, -or if you would like to use additional `args` or `kwargs` of those components, then this is the correct way to include those. You can very easily use any existing attribute of any underlying Dash component with this method. +or if you would like to use extra `args` or `kwargs` of those components, then this is the correct way to include those. You can use any existing attribute of any underlying Dash component with this method. !!!note - There are always **three general steps** to consider in order to create a custom component: + There are always **three general steps** to consider to create a custom component: 1. **Sub-class to create** your component - 2. **Enhance or build** the component (e.g. add/change model fields, overwrite pre-build/build method, etc.) to your desire + 2. **Enhance or build** the component (for example, to add/change model fields, overwrite pre-build/build method) to your desire 3. **Check** if your component will be part of a discriminated union[^1]. If yes, then - you must ensure your component has a `type` field - you must register the new type with its parent model's relevant field (where the new component is entered into) with [`add_type`][vizro.models.VizroBaseModel.add_type] We will refer back to these three steps in the two examples below. -[^1]: You can easily check if your new component will be part of a discriminated union by consulting our [API reference on models](../API-reference/models.md). Check whether the relevant model field (e.g. `selectors` in [`Filter`][vizro.models.Filter] or [`Parameter`][vizro.models.Parameter]) is described as a discriminated union (in this case the [`SelectorType`][vizro.models.types.SelectorType] is, but for example [`OptionsType`][vizro.models.types.OptionsType] is not). +[^1]: You can check if your new component will be part of a discriminated union by consulting our [API reference on models](../API-reference/models.md). Check whether the relevant model field (for example, `selectors` in [`Filter`][vizro.models.Filter] or [`Parameter`][vizro.models.Parameter]) is described as a discriminated union (in this case the [`SelectorType`][vizro.models.types.SelectorType] is, but for example [`OptionsType`][vizro.models.types.OptionsType] is not). -## How to extend an existing component +## Extend an existing component ??? info "When to choose this strategy" You may want to use this strategy to: - - extend an existing component (e.g. adding a button to [`Card`][vizro.models.Card]) - - change configurations we have set by default (e.g. setting `allowCross=False` in [`RangeSlider`][vizro.models.RangeSlider]) - - change any fields of any models (e.g. changing the title field from `Optional` to have a default) + - extend an existing component (for example, to add a button to [`Card`][vizro.models.Card]) + - change configurations we have set by default (for example, to set `allowCross=False` in [`RangeSlider`][vizro.models.RangeSlider]) + - change any fields of any models (for example, to change the title field from `Optional` to have a default) You can extend an existing component by sub-classing the component you want to alter. Remember that when sub-classing a component @@ -147,7 +147,7 @@ vm.Parameter.add_type("selector", TooltipNonCrossRangeSlider) [CustomComponent1]: ../../assets/user_guides/custom_components/customcomponent_1.png -## How to create a new component +## Create a new component ??? info "When to choose this strategy" @@ -164,10 +164,10 @@ The aim of the example is to create a [`Jumbotron`](https://dash-bootstrap-compo ???note "Note on `build` and `pre_build` methods" Note that when creating new components, you will need to define a `build` method like in the below example if it is a visual component that is rendered on the page. Examples of components with a `build` method are: - - `selector` type: [`Checklist`][vizro.models.Checklist], [`Dropdown`][vizro.models.Dropdown], [`RadioItems`][vizro.models.RadioItems], etc. - - `component` type: [`Graph`][vizro.models.Graph], [`Card`][vizro.models.Card], etc. + - `selector` type: [`Checklist`][vizro.models.Checklist], [`Dropdown`][vizro.models.Dropdown], [`RadioItems`][vizro.models.RadioItems]. + - `component` type: [`Graph`][vizro.models.Graph], [`Card`][vizro.models.Card]. - For components that only create other components, you do not need to define a `build` method, e.g. for [`Filter`][vizro.models.Filter] and [`Parameter`][vizro.models.Parameter]. + For components that only create other components, you do not need to define a `build` method, for example, for [`Filter`][vizro.models.Filter] and [`Parameter`][vizro.models.Parameter]. If you would like to have access to other components, you may want to define a `pre_build` method. This method is automatically run for all models and makes them internally consistent. Notable existing models with `pre_build` methods are [`Filter`][vizro.models.Filter] and [`Parameter`][vizro.models.Parameter]. @@ -266,6 +266,224 @@ vm.Page.add_type("components", Jumbotron) [CustomComponent2]: ../../assets/user_guides/custom_components/customcomponent_2.png +## Using custom components with custom actions + +Custom components can be used as `inputs` to, `outputs` of, or as a `trigger` of custom actions. In the examples below we will explore both options. + +### Custom components as inputs/outputs of custom actions + +Following the instructions above to create a custom component, results in this `OffCanvas` component: + +```py +class OffCanvas(vm.VizroBaseModel): + type: Literal["offcanvas"] = "offcanvas" + title: str + content: str + + def build(self): + return html.Div( + [ + dbc.Offcanvas( + children=html.P(self.content), + id=self.id, + title=self.title, + is_open=False, + ), + ] + ) +``` + +After you have completed the steps above, it is time to write your [custom action](../user-guides/custom-actions.md). + + ```py + @capture("action") + def open_offcanvas(n_clicks, is_open): + if n_clicks: + return not is_open + return is_open + ``` + +Add the custom action `open_offcanvas` as a `function` argument inside the [`Action`][vizro.models.Action] model. + + +??? example "Example of the use of custom component with actions" + + === "app.py" + ``` py + from typing import List, Literal + + import dash_bootstrap_components as dbc + import vizro.models as vm + import vizro.plotly.express as px + from dash import html + from vizro import Vizro + + from vizro.models import Action + from vizro.models.types import capture + + + # 1. Create new custom component + class OffCanvas(vm.VizroBaseModel): + type: Literal["offcanvas"] = "offcanvas" + title: str + content: str + + def build(self): + return html.Div( + [ + dbc.Offcanvas( + children=html.P(self.content), + id=self.id, + title=self.title, + is_open=False, + ), + ] + ) + + + # 2. Add new components to expected type - here the selector of the parent components + vm.Page.add_type("components", OffCanvas) + + # 3. Create custom action + @capture("action") + def open_offcanvas(n_clicks, is_open): + if n_clicks: + return not is_open + return is_open + + page = vm.Page( + title="Custom Component", + components=[ + vm.Button( + text="Open Offcanvas", + id="open_button", + actions=[ + vm.Action( + function=open_offcanvas(), + inputs=["open_button.n_clicks", "offcanvas.is_open"], + outputs=["offcanvas.is_open"], + ) + ], + ), + OffCanvas( + id="offcanvas", + content="OffCanvas content", + title="Offcanvas Title", + ), + ], + ) + + dashboard = vm.Dashboard(pages=[page]) + + Vizro().build(dashboard).run() + + ``` + === "yaml" + ```yaml + # Custom components are currently only possible via python configuration + ``` + === "Result" + [![CustomComponent3]][CustomComponent3] + + [CustomComponent3]: ../../assets/user_guides/custom_components/customcomponent_3.gif + + +### Trigger actions with a custom component + +As mentioned above, custom components can trigger action. To enable the custom component to trigger the action, we need to add some extra code: + +1. **Add the `actions` argument to your custom component**. The type of the `actions` argument is `List[Action]`. + ```py + actions: List[Action] = [] + ``` +2. **Set the action through `_set_actions`**. In doing so, any change in the `"active_index"` property of the custom component triggers the action. + ```py + _set_actions = _action_validator_factory("active_index") + ``` + + +??? example "Example of triggering action with custom component" + + === "app.py" + ``` py + from typing import List, Literal + + import dash_bootstrap_components as dbc + import vizro.models as vm + from dash import html + from vizro import Vizro + + try: + from pydantic.v1 import Field, PrivateAttr + except ImportError: + from pydantic import PrivateAttr + + from vizro.models import Action + from vizro.models._action._actions_chain import _action_validator_factory + from vizro.models.types import capture + + + # 1. Create new custom component + class Carussel(vm.VizroBaseModel): + type: Literal["carussel"] = "carussel" + items: List + actions: List[Action] = [] + + _set_actions = _action_validator_factory("active_index") # (1)! + + def build(self): + return dbc.Carousel( + id=self.id, + items=self.items, + ) + + + # 2. Add new components to expected type - here the selector of the parent components + vm.Page.add_type("components", Carussel) + + # 3. Create custom action + @capture("action") + def carussel(active_index): + if active_index: + return "Second slide" + + return "First slide + + page = vm.Page( + title="Custom Component", + components=[ + vm.Card(text="First slide", id="carussel-card"), + Carussel( + id="carrusel", + items=[ + {"key": "1", "src": "path_to_your_image"}, + {"key": "2", "src": "path_to_your_image"}, + ], + actions=[ + vm.Action( + function=carussel(), + inputs=["carrusel.active_index"], + outputs=["carussel-card.children"] + ) + ] + ), + ], + ) + + dashboard = vm.Dashboard(pages=[page]) + + Vizro().build(dashboard).run() + ``` + + 1. Here we set the action so a change in the `active_index` property of the custom component triggers the action. + === "yaml" + ```yaml + # Custom components are currently only possible via python configuration + ``` + === "Result" + [![CustomComponent4]][CustomComponent4] + + [CustomComponent4]: ../../assets/user_guides/custom_components/customcomponent_4.gif ???+ warning diff --git a/vizro-core/docs/pages/user-guides/custom-tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md index 2a58bcb50..9a50816a5 100644 --- a/vizro-core/docs/pages/user-guides/custom-tables.md +++ b/vizro-core/docs/pages/user-guides/custom-tables.md @@ -5,12 +5,12 @@ you can create a custom Dash AG Grid or Dash DataTable. One reason could be that you want to create a table/grid that requires computations that can be controlled by parameters (see the example below). -For this, similar to how one would create a [custom chart](../user-guides/custom-charts.md), simply do the following: +For this, similar to how one would create a [custom chart](../user-guides/custom-charts.md), do the following: - Define a function that returns a `dash_ag_grid.AgGrid` or `dash_table.DataTable` object. - Decorate it with the `@capture("ag_grid")` or `@capture("table")` decorator respectively. - The function must accept a `data_frame` argument (of type `pandas.DataFrame`). -- The table should be derived from and require only one `pandas.DataFrame` (e.g. any further dataframes added through other arguments will not react to dashboard components such as `Filter`). +- The table should be derived from and require only one `pandas.DataFrame` (for example, any further dataframes added through other arguments will not react to dashboard components such as `Filter`). The following examples show a possible version of a custom table. In this case the argument `chosen_columns` was added, which you can control with a parameter: diff --git a/vizro-core/docs/pages/user-guides/dashboard.md b/vizro-core/docs/pages/user-guides/dashboard.md index 91a21b1f9..401953ece 100644 --- a/vizro-core/docs/pages/user-guides/dashboard.md +++ b/vizro-core/docs/pages/user-guides/dashboard.md @@ -1,18 +1,18 @@ # How to create a dashboard This guide shows you how to configure and call a [`Dashboard`][vizro.models.Dashboard] using either -pydantic models, python dictionaries, yaml or json. +pydantic models, Python dictionaries, YAML, or JSON. To create a dashboard: 1. Choose one of the possible configuration syntaxes -2. Create your `pages`, see our guide on [Pages](pages.md) -3. (optional) Choose a `theme`, see our guide on [Themes](themes.md) -4. (optional) Customize your `navigation`, see our guide on [Navigation](navigation.md) -5. (optional) Provide a `title` to your dashboard +2. Create your `pages`, see our [guide on Pages](pages.md) +3. (optional) Choose a `theme`, see our [guide on Themes](themes.md) +4. (optional) Customize your `navigation`, see our [guide on Navigation](navigation.md) +5. (optional) Set a `title` for your dashboard 6. Add your `dashboard` to the `build` call of Vizro -## Using dashboard configuration options +## Use dashboard configuration options !!! example "Dashboard Configuration Syntaxes" === "app.py - pydantic models" @@ -79,7 +79,7 @@ To create a dashboard: ``` === "dashboard.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -143,9 +143,9 @@ To create a dashboard: [Dashboard]: ../../assets/user_guides/dashboard/dashboard.png -???+ note "Additional `.py` files for `yaml` and `json` required" +???+ note "Extra `.py` files for `yaml` and `json` required" - Note that in the `yaml` and `json` example an additional `.py` is required to register the data and parse the yaml/json configuration. + Note that in the `yaml` and `json` example an extra `.py` is required to register the data and parse the yaml/json configuration. === "app.py for yaml" ```py @@ -158,7 +158,7 @@ To create a dashboard: from vizro.managers import data_manager from vizro.models import Dashboard - data_manager["iris"] = px.data.iris + data_manager["iris"] = px.data.iris() dashboard = yaml.safe_load(Path("dashboard.yaml").read_text(encoding="utf-8")) dashboard = Dashboard(**dashboard) @@ -174,7 +174,7 @@ To create a dashboard: from vizro.managers import data_manager from vizro.models import Dashboard - data_manager["iris"] = px.data.iris + data_manager["iris"] = px.data.iris() dashboard = json.loads(Path("dashboard.json").read_text(encoding="utf-8")) dashboard = Dashboard(**dashboard) @@ -183,24 +183,24 @@ To create a dashboard: After running the dashboard, you can access the dashboard via `localhost:8050`. -## How to add a dashboard title +## Add a dashboard title If supplied, the `title` of the [`Dashboard`][vizro.models.Dashboard] displays a heading at the top of every page. -## How to add a dashboard logo +## Add a dashboard logo -Vizro will automatically incorporate the dashboard [logo](assets.md/#logo-image) in the top-left corner of each page if an image named `logo.` is present within the assets folder. +Vizro will [automatically incorporate the dashboard logo](assets.md/#logo-image) in the top-left corner of each page if an image named `logo.` is present within the assets folder. ![Dashboard with logo](../../assets/user_guides/dashboard/dashboard_with_logo.png) ## Browser title The [website icon](assets.md/#changing-the-favicon), Dashboard `title` (if supplied) and [Page `title`][vizro.models.Page] are displayed in the browser's -title bar. For example, if your Dashboard `title` is "Vizro Demo" and the Page `title` is "Homepage", then the title in the browser tab will be "Vizro Demo: Homepage". +title bar. For example, if your Dashboard `title` is "Vizro Demo" and the Page `title` is "Homepage", then the title in the browser tab will be "Vizro Demo: Homepage". ## Meta tags for social media Vizro automatically adds [meta tags](https://metatags.io/) to display a preview card when your app is shared on social media and chat -clients. The preview includes the `URL`, `title`, plus an [image](assets.md/#meta-tags-image) and +clients. The preview includes the `URL`, `title`, plus an [image](assets.md/#meta-tags-image) and [Page `description`][vizro.models.Page] (if supplied). To see an example, try sharing the [Vizro demo app](https://vizro.mckinsey.com/). diff --git a/vizro-core/docs/pages/user-guides/data.md b/vizro-core/docs/pages/user-guides/data.md index d29a5c049..c670c7655 100644 --- a/vizro-core/docs/pages/user-guides/data.md +++ b/vizro-core/docs/pages/user-guides/data.md @@ -1,199 +1,346 @@ # How to connect your dashboard to data -This guide shows you how to connect your dashboard and your charts within the dashboard to data. +Vizro supports two different types of data: -Vizro provides two ways to connect your charts to data. This section shows you how to use both. +* [Static data](#static-data): pandas DataFrame. This is the simplest method and best to use if you do not need the more advanced functionality of dynamic data. +* [Dynamic data](#dynamic-data): function that returns a pandas DataFrame. This is a bit more complex to understand but has more advanced functionality such as the ability to refresh data while the dashboard is running. -## Directly feed a Pandas DataFrame to your chart +The following flowchart shows what you need to consider when choosing how to set up your data. +``` mermaid +graph TD + refresh["`Do you need your data to refresh while the dashboard is running?`"] + specification["`Do you need to specify your dashboard through a configuration language like YAML?`"] + dynamic([Use dynamic data referenced by name]) + static-direct([Use static data supplied directly]) + static-name([Use static data referenced by name]) -You can directly feed a Pandas DataFrame to your chart. This is the simplest way to connect your charts to data. + refresh -- No --> specification + refresh -- Yes --> dynamic + specification -- No --> static-direct + specification -- Yes --> static-name -!!! example "Feed a Pandas DataFrame" + click static-direct href "#supply-directly" + click static-name href "#reference-by-name" + click dynamic href "#dynamic-data" + + classDef clickable color:#4051b5; +``` + +??? note "Static vs. dynamic data comparison" + + This table gives a full comparison between static and dynamic data. Do not worry if you do not yet understand everything in it; it will become clearer after reading more about [static data](#static-data) and [dynamic data](#dynamic-data)! + + | | Static | Dynamic | + |---------------------------------------------------------------|------------------|------------------------------------------| + | Required Python type | pandas DataFrame | Function that returns a pandas DataFrame | + | Can be supplied directly in `data_frame` argument of `figure` | Yes | No | + | Can be referenced by name after adding to data manager | Yes | Yes | + | Can be refreshed while dashboard is running | No | Yes | + | Production-ready | Yes | Yes | + +If you have a [Kedro](https://kedro.org/) project or would like to use the [Kedro Data Catalog](https://docs.kedro.org/en/stable/data/index.html) to manage your data independently of a Kedro project then you should use Vizro's [integration with the Kedro Data Catalog](kedro-data-catalog.md). This offers helper functions to add [`kedro_datasets.pandas`](https://docs.kedro.org/en/stable/kedro_datasets.html) as dynamic data in the Vizro data manager. + +## Static data + +A static data source is the simplest way to send data to your dashboard and should be used for any data that does not need to be reloaded while the dashboard is running. It is production-ready and works out of the box in a multi-process deployment. If you need data to be refreshed without restarting the dashboard then you should instead use [dynamic data](#dynamic-data). + +### Supply directly + +You can directly supply a pandas DataFrame into components such as [graphs](graph.md) and [tables](table.md). + +The below example uses the Iris data saved to a file `iris.csv` in the same directory as `app.py`. This data can be generated using `px.data.iris()` or [downloaded](../../assets/user_guides/data/iris.csv). + +!!! example "Static data supplied directly" === "app.py" - ```py linenums="1" + ```py from vizro import Vizro + import pandas as pd import vizro.plotly.express as px import vizro.models as vm - iris = px.data.iris() + iris = pd.read_csv("iris.csv") # (1)! page = vm.Page( - title="My first page", + title="Static data example", components=[ - vm.Graph(id="scatter_chart", figure=px.scatter(iris, x="sepal_length", y="petal_width", color="species")), - ], - controls=[vm.Filter(column="species")], + vm.Graph(figure=px.box(iris, x="species", y="petal_width", color="species")), + ] ) dashboard = vm.Dashboard(pages=[page]) Vizro().build(dashboard).run() ``` + + 1. `iris` is a pandas DataFrame created by reading from the CSV file `iris.csv`. === "Result" [![DataBasic]][DataBasic] [DataBasic]: ../../assets/user_guides/data/data_pandas_dataframe.png -Here `px.data.iris()` returns a Pandas DataFrame. We then pass this DataFrame to the `figure` argument of the `Graph` component. - +The [`Graph`][vizro.models.Graph], [`AgGrid`][vizro.models.AgGrid] and [`Table`][vizro.models.Table] models all have an argument called `figure`. This accepts a function (in the above example, `px.scatter`) that takes a pandas DataFrame as its first argument. The name of this argument is always `data_frame`. When configuring the dashboard using Python, it is optional to give the name of the argument: if you like, you could write `data_frame=iris` instead of `iris`. !!! note - If you are using JSON or YAML to define your dashboard, you can only use the - data connector approach to connect your data. - - - -## How to use a data connector + With static data, once the dashboard is running, the data shown in the dashboard cannot change even if the source data in `iris.csv` changes. The code `iris = pd.read_csv("iris.csv")` is only executed once when the dashboard is first started. If you would like changes to source data to flow through to the dashboard then you must use [dynamic data](#dynamic-data). -You can also connect your charts with a data connector. To use a data connector with -Vizro, you need: +### Reference by name -1. Define a data connector. A data connector is a function - that returns a Pandas DataFrame. In this function, you can define how to load your - data and then convert it to a Pandas DataFrame if necessary. +If you would like to specify your dashboard configuration through YAML then you must first add your data to the data manager, importable as `vizro.managers.data_manager`. The value of the `data_frame` argument in the YAML configuration should then refer to the name of your data in the data manager. -2. Register this function with the Vizro [Data Manager][vizro.managers._data_manager]. - This allows you to use this data connector - in your dashboard. - -!!! example "Use a data connector" +!!! example "Static data referenced by name" === "app.py" - ```py linenums="1" hl_lines="18" + ```py + import yaml + from vizro import Vizro import vizro.plotly.express as px import vizro.models as vm + import pandas as pd from vizro.managers import data_manager + data_manager["iris"] = pd.read_csv("iris.csv") # (1)! - # define a data connector - def retrieve_iris(): - """This is a function that returns a Pandas DataFrame.""" - return px.data.iris() - - # register the data connector with Vizro Data Manager - data_manager["iris"] = retrieve_iris - - page = vm.Page( - title="My first page", - components=[ - vm.Graph(id="scatter_chart", figure=px.scatter("iris", x="sepal_length", y="petal_width", color="species")), - ], - controls=[vm.Filter(column="species")], - ) - - dashboard = vm.Dashboard(pages=[page]) + dashboard = yaml.safe_load(Path("dashboard.yaml").read_text(encoding="utf-8")) + dashboard = Dashboard(**dashboard) Vizro().build(dashboard).run() ``` - === "app.yaml" - ```yaml linenums="1" hl_lines="6" - # Still requires a .py to register data connector in Data Manager and parse yaml configuration - see yaml_version example + + 1. `"iris"` is the name of a data source added to the data manager. This data is a pandas DataFrame created by reading from the CSV file `iris.csv`. + === "dashboard.yaml" + ```yaml pages: - components: - figure: - _target_: scatter - data_frame: iris - x: sepal_length + _target_: box + data_frame: iris # (1)! + x: species y: petal_width color: species - id: scatter_chart type: graph - controls: - - column: species - type: filter - title: My first page + title: Static data example ``` + + 1. Refer to the `"iris"` data source in the data manager. === "Result" - [![DataConnector]][DataConnector] + [![DataBasic]][DataBasic] - [DataConnector]: ../../assets/user_guides/data/data_pandas_dataframe.png + [DataBasic]: ../../assets/user_guides/data/data_pandas_dataframe.png -!!! note +It is also possible to refer to a named data source using the Python API: `px.scatter("iris", ...)` or `px.scatter(data_frame="iris", ...)` would work if the `"iris"` data source has been registered in the data manager. - When you use a data connector, you reference the data by string. In the example, we - use `px.scatter("iris", x="sepal_length", y="petal_width", color="species")` to reference - the data. The string `"iris"` is the dataset name registered in Data Manager. This is - how Vizro knows which data connector to use. +## Dynamic data +A dynamic data source is a Python function that returns a pandas DataFrame. This function is executed when the dashboard is initially started and _can be executed again while the dashboard is running_. This makes it possible to refresh the data shown in your dashboard without restarting the dashboard itself. If you do not require this functionality then you should use [static data](#static-data) instead. -### How to use a data connector with arguments +Unlike static data, dynamic data cannot be supplied directly into the `data_frame` argument of a `figure`. Instead, it must first be added to the data manager and then referenced by name. -You can also define a data connector with arguments. This is useful when you want to -use the same data connector to load different data. For example, when you want to -retrieve data from different tables in a database, you can define a data connector -that accepts different SQL queries as arguments. +The example below shows how data is fetched dynamically every time the page is refreshed. When you run the code and refresh the page the function `load_iris_data` is re-run, which returns different data each time. The example uses the Iris data saved to a file `iris.csv` in the same directory as `app.py`. This data can be generated using `px.data.iris()` or [downloaded](../../assets/user_guides/data/iris.csv). -!!! example "Use a data connector with arguments" - === "app.py (use lambda)" - ```py linenums="1" +!!! example "Dynamic data" + === "app.py" + ```py from vizro import Vizro + import pandas as pd import vizro.plotly.express as px import vizro.models as vm - from vizro.managers import data_manager - - def retrieve_one_species(species): - df = px.data.iris() - subset = df[df["species"] == species].copy() - return subset + from vizro.managers import data_manager + def load_iris_data(): + iris = pd.read_csv("iris.csv") # (1)! + return iris.sample(50) # (2)! - data_manager["species_setosa"] = lambda: retrieve_one_species("setosa") + data_manager["iris"] = load_iris_data # (3)! page = vm.Page( - title="My first page", + title="Update the chart on page refresh", components=[ - vm.Graph(id="scatter_chart", figure=px.scatter("species_setosa", x="sepal_length", y="petal_width", color="species")), + vm.Graph(figure=px.box("iris", x="species", y="petal_width", color="species")) # (4)! ], - controls=[vm.Filter(column="species")], ) dashboard = vm.Dashboard(pages=[page]) Vizro().build(dashboard).run() ``` - === "app.py (use functools.partial)" - ```py linenums="1" - from functools import partial + 1. `iris` is a pandas DataFrame created by reading from the CSV file `iris.csv`. + 2. To demonstrate that dynamic data can change when the page is refreshed, select 50 points at random. This simulates what would happen if your file `iris.csv` were constantly changing. + 3. To use `load_iris_data` as dynamic data it must be added to the data manager. You should **not** actually call the function as `load_iris_data()`; doing so would result in static data that cannot be reloaded. + 4. Dynamic data is referenced by the name of the data source `"iris"`. + + === "Result" + [![DynamicData]][DynamicData] + + [DynamicData]: ../../assets/user_guides/data/dynamic_data.gif + +Since dynamic data sources must always be added to the data manager and referenced by name, they may be used in YAML configuration [exactly the same way as for static data sources](#reference-by-name). + +### Configure cache + +By default, each time the dashboard is refreshed a dynamic data function executes again. In fact, if there are multiple graphs on the same page using the same dynamic data source then the loading function executes _multiple_ times, once for each graph on the page. Hence, if loading your data is a slow operation, your dashboard performance may suffer. + +The Vizro data manager has a server-side caching mechanism to help solve this. Vizro's cache uses [Flask-Caching](https://flask-caching.readthedocs.io/en/latest/), which supports a number of possible cache backends and [configuration options](https://flask-caching.readthedocs.io/en/latest/#configuring-flask-caching). By default, the cache is turned off. + + +In a development environment the easiest way to enable caching is to use a [simple memory cache](https://cachelib.readthedocs.io/en/stable/simple/) with the default configuration options. This is achieved by adding one line to the above example to set `data_manager.cache`: + +!!! example "Simple cache with default timeout of 5 minutes" + + ```py hl_lines="13" + from flask_caching import Cache + from vizro import Vizro + import pandas as pd + import vizro.plotly.express as px + import vizro.models as vm + + from vizro.managers import data_manager + + def load_iris_data(): + iris = pd.read_csv("iris.csv") + return iris.sample(50) + + data_manager.cache = Cache(config={"CACHE_TYPE": "SimpleCache"}) + data_manager["iris"] = load_iris_data + + page = vm.Page( + title="Update the chart on page refresh", + components=[ + vm.Graph(figure=px.box("iris", x="species", y="petal_width", color="species")) + ], + ) + + dashboard = vm.Dashboard(pages=[page]) + + Vizro().build(dashboard).run() + ``` + +By default, when caching is turned on, dynamic data is cached in the data manager for 5 minutes. A refresh of the dashboard within this time interval will fetch the pandas DataFrame from the cache and _not_ re-run the data loading function. Once the cache timeout period has elapsed, the next refresh of the dashboard will re-execute the dynamic data loading function. The resulting pandas DataFrame will again be put into the cache and not expire until another 5 minutes has elapsed. + +If you would like to alter some options, such as the default cache timeout, then you can specify a different cache configuration: + +```py title="Simple cache with timeout set to 10 minutes" +data_manager.cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 600}) +``` + +!!! warning + + Simple cache exists purely for single-process development purposes and is not intended to be used in production. If you deploy with multiple workers, [for example with gunicorn](run.md/#gunicorn), then you should use a production-ready cache backend. All of Flask-Caching's [built-in backends](https://flask-caching.readthedocs.io/en/latest/#built-in-cache-backends) other than `SimpleCache` are suitable for production. In particular, you might like to use [`FileSystemCache`](https://cachelib.readthedocs.io/en/stable/file/) or [`RedisCache`](https://cachelib.readthedocs.io/en/stable/redis/): + + ```py title="Production-ready caches" + # Store cached data in CACHE_DIR + data_manager.cache = Cache(config={"CACHE_TYPE": "FileSystemCache", "CACHE_DIR": "cache"}) + + # Use Redis key-value store + data_manager.cache = Cache(config={"CACHE_TYPE": "RedisCache", "CACHE_REDIS_HOST": "localhost", "CACHE_REDIS_PORT": 6379}) + ``` + + Since Flask-Caching relies on [`pickle`](https://docs.python.org/3/library/pickle.html), which can execute arbitrary code during unpickling, you should not cache data from untrusted sources. Doing so [could be unsafe](https://github.com/pallets-eco/flask-caching/pull/209). + +Note that when a production-ready cache backend is used, the cache is persisted beyond the Vizro process and is not cleared by restarting your server. If you wish to clear the cache then you must do so manually, for example, if you use `FileSystemCache` then you would delete your `cache` directory. Persisting the cache can also be useful for development purposes when handling data that takes a long time to load: even if you do not need the data to refresh while your dashboard is running, it can speed up your development loop to use dynamic data with a cache that is persisted between repeated runs of Vizro. + +#### Set timeouts + +You can change the timeout of the cache independently for each dynamic data source in the data manager using the `timeout` setting (measured in seconds). A `timeout` of 0 indicates that the cache does not expire. This is effectively the same as using [static data](#static-data). + +```py title="Set the cache timeout for each dynamic data source" +from vizro.managers import data_manager +from flask_caching import Cache + +data_manager.cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 600}) + +# Cache of default_expire_data expires every 10 minutes, the default set by CACHE_DEFAULT_TIMEOUT +data_manager["default_expire_data"] = load_iris_data + +# Set cache of fast_expire_data to expire every 10 seconds +data_manager["fast_expire_data"] = load_iris_data +data_manager["fast_expire_data"].timeout = 10 + +# Set cache of slow_expire_data to expires every hour +data_manager["slow_expire_data"] = load_iris_data +data_manager["slow_expire_data"].timeout = 60 * 60 + +# Set cache of no_expire_data to never expire +data_manager["no_expire_data"] = load_iris_data +data_manager["no_expire_data"].timeout = 0 +``` + +### Parametrize data loading + +You can supply arguments to your dynamic data loading function that can be modified from the dashboard. +For example, if you are handling big data then you can use an argument to specify the number of entries or size of chunk of data. + +To add a parameter to control a dynamic data source, do the following: + +1. add the appropriate argument to your dynamic data function and specify a default value for the argument. +2. give an `id` to all components that have the data source you wish to alter through a parameter. +3. [add a parameter](parameters.md) with `targets` of the form `.data_frame.` and a suitable [selector](selectors.md). + +For example, let us extend the [dynamic data example](#dynamic-data) above to show how the `load_iris_data` can take an argument `number_of_points` controlled from the dashboard with a [`Slider`][vizro.models.Slider]. + +!!! example "Parametrized dynamic data" + === "app.py" + ```py hl_lines="8 10 20-23" from vizro import Vizro + import pandas as pd import vizro.plotly.express as px import vizro.models as vm - from vizro.managers import data_manager + from vizro.managers import data_manager - def retrieve_one_species(species): - df = px.data.iris() - subset = df[df["species"] == species].copy() - return subset - + def load_iris_data(number_of_points=10): # (1)! + iris = pd.read_csv("iris.csv") # (2)! + return iris.sample(number_of_points) # (3)! - data_manager["species_setosa"] = partial(retrieve_one_species, "setosa") + data_manager["iris"] = load_iris_data # (4)! page = vm.Page( - title="My first page", + title="Update the chart on page refresh", components=[ - vm.Graph(id="scatter_chart", figure=px.scatter("species_setosa", x="sepal_length", y="petal_width", color="species")), + vm.Graph(id="graph", figure=px.box("iris", x="species", y="petal_width", color="species")) # (5)! + ], + controls=[ + vm.Parameter( + targets=["graph.data_frame.number_of_points"], # (6)! + selector=vm.Slider(min=10, max=100, step=10, value=10), + ) ], - controls=[vm.Filter(column="species")], ) dashboard = vm.Dashboard(pages=[page]) Vizro().build(dashboard).run() ``` + + 1. `load_iris_data` takes a single argument, `number_of_points`, with a default value of 10. + 2. `iris` is a pandas DataFrame created by reading from the CSV file `iris.csv`. + 3. Sample points at random, where `number_of_points` gives the number of points selected. + 4. To use `load_iris_data` as dynamic data it must be added to the data manager. You should **not** actually call the function as `load_iris_data()` or `load_iris_data(number_of_points=...)`; doing so would result in static data that cannot be reloaded. + 5. Give the `vm.Graph` component `id="graph"` so that the `vm.Parameter` can target it. Dynamic data is referenced by the name of the data source `"iris"`. + 6. Create a `vm.Parameter` to target the `number_of_points` argument for the `data_frame` used in `graph`. + === "Result" - [![DataConnector]][DataConnector] + [![ParametrizedDynamicData]][ParametrizedDynamicData] + + [ParametrizedDynamicData]: ../../assets/user_guides/data/parametrized_dynamic_data.gif + +Parametrized data loading is compatible with [caching](#configure-cache). The cache uses [memoization](https://flask-caching.readthedocs.io/en/latest/#memoization), so that the dynamic data function's arguments are included in the cache key. This means that `load_iris_data(number_of_points=10)` is cached independently of `load_iris_data(number_of_points=20)`. + +!!! warning + + You should always [treat the content of user input as untrusted](https://community.plotly.com/t/writing-secure-dash-apps-community-thread/54619). For example, you should not expose a filepath to load without passing it through a function like [`werkzeug.utils.secure_filename`](https://werkzeug.palletsprojects.com/en/3.0.x/utils/#werkzeug.utils.secure_filename), or you might enable arbitrary access to files on your server. + +It is not possible to pass [nested parameters](parameters.md#nested-parameters) to dynamic data. You can only target top-level arguments of the data loading function and not address nested keys in a dictionary. - [DataConnector]: ../../assets/user_guides/data/data_selected_from_source.png +### Filter update limitation +If your dashboard includes a [filter](filters.md) then the values shown on a filter's [selector](selectors.md) _do not_ update while the dashboard is running. This is a known limitation that will be lifted in future releases, but if is problematic for you already then [raise an issue on our GitHub repo](https://github.com/mckinsey/vizro/issues/). -### Kedro integration +This limitation is why all arguments of your dynamic data loading function must have a default value. Regardless of the value of the `vm.Parameter` selected in the dashboard, these default parameter values are used when the `vm.Filter` is built. This determines the type of selector used in a filter and the options shown, which cannot currently be changed while the dashboard is running. -If the data you are visualizing is a [`kedro_datasets.pandas`](https://docs.kedro. -org/en/stable/kedro_datasets.html) type from a Kedro data catalog, you can use -Vizro's [Kedro integration](integration.md#kedro) to connect your charts to [Kedro's Data Catalog](https://docs.kedro.org/en/stable/data/index.html). +Although a selector is automatically chosen for you in a filter when your dashboard is built, remember that [you can change this choice](filters.md#changing-selectors). For example, we could ensure that a dropdown always contains the options "setosa", "versicolor" and "virginica" by explicitly specifying your filter as follows. -If it is not a `kedro_datasets.pandas` type, you need to build a -data connector to load the data from the data catalog and convert it to a Pandas -DataFrame, before you can register it with Vizro Data Manager. +```py +vm.Filter(column="species", selector=vm.Dropdown(options=["setosa", "versicolor", "virginica"]) +``` diff --git a/vizro-core/docs/pages/user-guides/filters.md b/vizro-core/docs/pages/user-guides/filters.md index 0266ce7ef..7a904e813 100644 --- a/vizro-core/docs/pages/user-guides/filters.md +++ b/vizro-core/docs/pages/user-guides/filters.md @@ -3,7 +3,7 @@ This guide shows you how to add filters to your dashboard. One main way to interact with the charts/components on your page is by filtering the underlying data. A filter selects a subset of rows of a component's underlying DataFrame which alters the appearance of that component on the page. The [`Page`][vizro.models.Page] model accepts the `controls` argument, where you can enter a [`Filter`][vizro.models.Filter] model. -This model allows the automatic creation of [selectors](../user-guides/selectors.md) (e.g. Dropdown, RadioItems, Slider, ...) that operate upon the charts/components on the screen. +This model enables the automatic creation of [selectors](../user-guides/selectors.md) (such as Dropdown, RadioItems, Slider, ...) that operate upon the charts/components on the screen. ## Basic filters @@ -14,7 +14,7 @@ To add a filter to your page, do the following: - configure the `column` argument, which denotes the target column to be filtered By default, all components on a page with such a `column` present will be filtered. The selector type will be chosen -automatically based on the target column, e.g. a dropdown for categorical data, a range slider for numerical data, or a date picker for temporal data. +automatically based on the target column, for example, a dropdown for categorical data, a range slider for numerical data, or a date picker for temporal data. !!! example "Basic Filter" === "app.py" @@ -41,7 +41,7 @@ automatically based on the target column, e.g. a dropdown for categorical data, ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -64,7 +64,7 @@ automatically based on the target column, e.g. a dropdown for categorical data, ## Changing selectors -If you want to have a different selector for your filter, you can provide the `selector` argument of the [`Filter`][vizro.models.Filter] with a different selector model. +If you want to have a different selector for your filter, you can give the `selector` argument of the [`Filter`][vizro.models.Filter] a different selector model. Currently available selectors are [`Checklist`][vizro.models.Checklist], [`Dropdown`][vizro.models.Dropdown], [`RadioItems`][vizro.models.RadioItems], [`RangeSlider`][vizro.models.RangeSlider], [`Slider`][vizro.models.Slider], and [`DatePicker`][vizro.models.DatePicker]. !!! example "Filter with custom Selector" @@ -82,7 +82,7 @@ Currently available selectors are [`Checklist`][vizro.models.Checklist], [`Dropd vm.Graph(figure=px.scatter(iris, x="sepal_length", y="petal_width")), ], controls=[ - vm.Filter(column="species",selector=vm.RadioItems()), + vm.Filter(column="species", selector=vm.RadioItems()), ], ) @@ -92,7 +92,7 @@ Currently available selectors are [`Checklist`][vizro.models.Checklist], [`Dropd ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -118,8 +118,8 @@ Currently available selectors are [`Checklist`][vizro.models.Checklist], [`Dropd For further customizations, you can always refer to the [`Filter`][vizro.models.Filter] reference. Some popular choices are: -- determine which component the filter will apply to by using `targets` -- determine what the target column type is, hence choosing the default selector by using `column_type` +- select which component the filter will apply to by using `targets` +- select what the target column type is, hence choosing the default selector by using `column_type` - choose options of lower level components, such as the `selector` models Below is an advanced example where we only target one page component, and where we further customize the chosen `selector`. @@ -150,7 +150,7 @@ Below is an advanced example where we only target one page component, and where ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: diff --git a/vizro-core/docs/pages/user-guides/graph.md b/vizro-core/docs/pages/user-guides/graph.md index be61152fd..ce2a529ea 100755 --- a/vizro-core/docs/pages/user-guides/graph.md +++ b/vizro-core/docs/pages/user-guides/graph.md @@ -16,9 +16,8 @@ To add a [`Graph`][vizro.models.Graph] to your page, do the following: This leaves any of the [`plotly.express`](https://plotly.com/python/plotly-express/) functionality untouched, but allows _direct insertion_ into the [`Graph`][vizro.models.Graph] model _as is_. Note also that the `plotly.express` chart needs to have a `data_frame` argument. In case you require a chart without - a `data_frame` argument (e.g. the [`imshow` chart](https://plotly.com/python/imshow/)), please refer to our + a `data_frame` argument (for example, the [`imshow` chart](https://plotly.com/python/imshow/)), refer to our [guide on custom charts](custom-charts.md). - . @@ -49,7 +48,7 @@ To add a [`Graph`][vizro.models.Graph] to your page, do the following: ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -72,7 +71,8 @@ To add a [`Graph`][vizro.models.Graph] to your page, do the following: [Graph]: ../../assets/user_guides/components/graph.png -Note that in the above example we directly inserted the chart into the `figure` argument for the `.py` version. This is also the simplest way to connect your chart to a Pandas `DataFrame` - for other connections, please refer to [this guide on data connections](data.md). For the `yaml` version, we simply referred to the [`plotly.express`](https://plotly.com/python/plotly-express/) name by string. + +In the Python example we directly inserted the pandas DataFrame `df` into `figure=px.scatter_matrix(df, ...)`. This is [one way to supply data to a chart](data.md#supply-directly). For the YAML version, we [refer to the data source by name](data.md#reference-by-name) as `data_frame: iris`. For a full explanation of the different methods you can use to send data to your dashboard, see [our guide to using data in Vizro](data.md). ??? info "Vizro automatically sets the plotly default template" diff --git a/vizro-core/docs/pages/user-guides/install.md b/vizro-core/docs/pages/user-guides/install.md index 46f195296..f23786c7d 100644 --- a/vizro-core/docs/pages/user-guides/install.md +++ b/vizro-core/docs/pages/user-guides/install.md @@ -2,7 +2,7 @@ This guide shows you how to install Vizro, how to verify the installation succeeded and find the version of Vizro, and how to update Vizro. -If you already have a virtual environment setup in Python then you can skip this page and just install Vizro straight away by running: +If you already have a virtual environment setup in Python then you can skip this page and install Vizro straight away by running: ```bash pip install vizro ``` @@ -65,12 +65,12 @@ To completely avoid terminal usage, follow these steps to work with Vizro: 4. Similarly, search `jupyter` and install it. -5. [Launch a Jupyter notebook](https://problemsolvingwithpython.com/02-Jupyter-Notebooks/02.04-Opening-a-Jupyter-Notebook/#open-a-jupyter-notebook-with-anaconda-navigator) to work with Vizro. +5. [Launch a Jupyter Notebook](https://problemsolvingwithpython.com/02-Jupyter-Notebooks/02.04-Opening-a-Jupyter-Notebook/#open-a-jupyter-notebook-with-anaconda-navigator) to work with Vizro. ## Confirm a successful installation -To confirm the installation was successful, and verify the version of Vizro installed, call the following. You can do this from within a Jupyter notebook cell, or run the following as a Python script: +To confirm the installation was successful, and verify the version of Vizro installed, call the following. You can do this from within a Jupyter Notebook cell, or run the following as a Python script: ```py import vizro diff --git a/vizro-core/docs/pages/user-guides/integration.md b/vizro-core/docs/pages/user-guides/kedro-data-catalog.md similarity index 78% rename from vizro-core/docs/pages/user-guides/integration.md rename to vizro-core/docs/pages/user-guides/kedro-data-catalog.md index b60a35365..0e2b0954e 100644 --- a/vizro-core/docs/pages/user-guides/integration.md +++ b/vizro-core/docs/pages/user-guides/kedro-data-catalog.md @@ -1,17 +1,16 @@ -# How to integrate Vizro with Kedro +# How to integrate Vizro with Kedro Data Catalog -This page describes how to integrate Vizro with [Kedro](https://docs.kedro.org/en/stable/index.html), an open-source Python framework to create reproducible, maintainable, and modular data science code. For Pandas datasets registered in a Kedro data catalog, -Vizro provides a convenient way to visualize them. +This page describes how to integrate Vizro with [Kedro](https://docs.kedro.org/en/stable/index.html), an open-source Python framework to create reproducible, maintainable, and modular data science code. For Pandas datasets registered in a Kedro data catalog, Vizro provides a convenient way to visualize them. ## Installation -To install Vizro with Kedro support, run: +If you already have Kedro installed then you do not need to install any extra dependencies. If you do not have Kedro installed then you should run: ```bash pip install vizro[kedro] ``` -## Using datasets from the Kedro Data Catalog -`vizro.integrations.kedro` provides functions to help generate and process a [Kedro Data Catalog](https://docs.kedro.org/en/stable/data/index.html). Given a Kedro Data Catalog `catalog`, the general pattern to add datasets into the [Vizro Data Manager][vizro.managers._data_manager] is: +## Use datasets from the Kedro Data Catalog +`vizro.integrations.kedro` provides functions to help generate and process a [Kedro Data Catalog](https://docs.kedro.org/en/stable/data/index.html). Given a Kedro Data Catalog `catalog`, the general pattern to add datasets into the Vizro data manager is: ```python from vizro.integrations import kedro as kedro_integration from vizro.managers import data_manager @@ -27,11 +26,12 @@ The `catalog` variable may have been created in a number of different ways: 1. Kedro project path. Vizro exposes a helper function `vizro.integrations.kedro.catalog_from_project` to generate a `catalog` given the path to a Kedro project. 2. [Kedro Jupyter session](https://docs.kedro.org/en/stable/notebooks_and_ipython/kedro_and_notebooks.html). This automatically exposes `catalog`. -3. Data Catalog configuration file (e.g. `catalog.yaml`). This can create a `catalog` entirely independently of a Kedro project using [`kedro.io.DataCatalog.from_config`](https://docs.kedro.org/en/stable/kedro.io.DataCatalog.html#kedro.io.DataCatalog.from_config). +3. Data Catalog configuration file (`catalog.yaml`). This can create a `catalog` entirely independently of a Kedro project using [`kedro.io.DataCatalog.from_config`](https://docs.kedro.org/en/stable/kedro.io.DataCatalog.html#kedro.io.DataCatalog.from_config). The full code for these different cases is given below. -!!! example "Import a Kedro Data Catalog into the Vizro Data Manager" +!!! example "Import a Kedro Data Catalog into the Vizro data manager" + === "app.py (Kedro project path)" ```py from vizro.integrations import kedro as kedro_integration diff --git a/vizro-core/docs/pages/user-guides/layouts.md b/vizro-core/docs/pages/user-guides/layouts.md index baa3b5af0..2e2397cea 100644 --- a/vizro-core/docs/pages/user-guides/layouts.md +++ b/vizro-core/docs/pages/user-guides/layouts.md @@ -1,16 +1,13 @@ # How to use layouts -This guide shows you how to use the [`Layout`][vizro.models.Layout] to arrange charts/components on the screen and customize the grid specifications. +The [`Page`][vizro.models.Page] model accepts a `layout` argument that allows for custom arrangement of charts and components on the screen. This guide shows how to customize the grid specifications in the [`Layout`][vizro.models.Layout]. -The [`Page`][vizro.models.Page] model accepts the `layout` argument, where you can input your [`Layout`][vizro.models.Layout] with a custom grid. - - -## Using the default layout -The `layout` argument of the [`Page`][vizro.models.Page] model is optional. If no layout is specified, all charts/components -will automatically be [**vertically stacked**](layouts.md#stacking-components) down the page in one column. -If that is your desired layout, you can create your charts/components without providing a [`Layout`][vizro.models.Layout]. +## The default layout +The `layout` argument of the [`Page`][vizro.models.Page] model is optional. If no layout is specified, all charts/components are automatically [**stacked vertically**](layouts.md#stack-components) on the page in one column. +If you are happy with that arrangement, you can create your charts/components without providing a [`Layout`][vizro.models.Layout]. !!! example "Default Layout" + === "app.py" ```py from vizro import Vizro @@ -28,7 +25,7 @@ If that is your desired layout, you can create your charts/components without pr ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -47,43 +44,53 @@ If that is your desired layout, you can create your charts/components without pr -## Configuring the grid -To customize the grid arrangement, you can configure the `grid` parameter of the [`Layout`][vizro.models.Layout] model. -The example below shows how the grid works and how to specify a valid one: +## Configure the grid +To customize the grid arrangement, configure the `grid` parameter of the [`Layout`][vizro.models.Layout] model. -```python title="Basic Example" +The example below shows an example of a valid `grid`: + +```python title="Basic example" grid = [[0, 1], [0, 2]] ``` -- The `grid` needs to be provided as `List[List[int]]` (e.g. `grid = [[0, 1], [0, 2]]`) -- The integers in the `grid` correspond to the index of the chart/component inside the list of `components` provided to [`Page`][vizro.models.Page] -- The number of integers in the `grid` needs to match the number of chart/components provided -- Each sub-list corresponds to a grid row (e.g. row 1 = `[0, 1]` and row 2 = `[0, 2]`) -- Each element inside the sub-list corresponds to a grid column (e.g. column 1 = `[0, 0]` and column 2 = `[1, 2]`) -- The integers in the `grid` need to be consecutive integers starting with 0 (e.g. `0`, `1`, `2`) -- Each chart/component will take the entire space of its grid area (empty spaces are currently not enabled) -- The area spanned by a chart/component in the grid must be rectangular -- The grid can be arbitrarily large, allowing arbitrarily granular control of the grid: - -```python title="Advanced Example" -grid=[[0, 1, 3, 4], - [2, 2, 3, 4]] +- The `grid` must be provided as `List[List[int]]` (for example, `grid = [[0, 1], [0, 2]]`). +- The integers in the `grid` must be consecutive integers starting with 0 (for example, `0`, `1`, `2`). + - The integers correspond to the index of the chart/component inside the list of `components` provided to [`Page`][vizro.models.Page]. + - The number of integers in the `grid` needs to match the number of chart/components provided. +- Each sub-list corresponds to a grid row (in the example above, row 1 = `[0, 1]` and row 2 = `[0, 2]`) +- Each element inside the sub-list corresponds to a grid column (for example, column 1 = `[0, 0]` and column 2 = `[1, 2]`) +- Each chart/component will take the entire space of its grid area but you can use [empty sections](#add-empty-sections-to-the-grid) for extra separation. +- The area spanned by a chart/component in the grid must be rectangular. +- The grid can be arbitrarily large, allowing arbitrarily granular control of the grid. + + +## Vertical and horizontal stacking +As described above, when no `Layout` is specified, components are presented **vertically** as a single-column stack. If you have three components, the default `Layout.grid` will be as follows, with three equally sized rows, each containing a component spanning the entire width: + +```python title="Vertical stacking" +grid = [ + [0], + [1], + [2] + ] +``` + +To present components **horizontally** in one row: + +```python title="Horizontal stacking" +grid = [[0, 1, 2]] ``` -### Stacking components -- When no `Layout` is specified, components will automatically be **stacked vertically** down the page in one column. -For instance, if you have three components, the default `Layout.grid` will be `grid = [[0], [1], [2]]`. -This means three equally sized rows, each containing a component spanning the entire width. -- To **stack components horizontally**, set the grid as `grid = [[0, 1, 2]]`. This defines a single row that occupies the entire width and height, divided into three equal columns.
![Stacking components](../../assets/user_guides/layout/stacking.png){ width="680" }
-### Grid - basic example +## Grid - basic example !!! example "Grid Arrangement - Basic Example" + === "app.py" ```py import vizro.models as vm @@ -104,7 +111,7 @@ This defines a single row that occupies the entire width and height, divided int ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -126,14 +133,17 @@ This defines a single row that occupies the entire width and height, divided int [Grid]: ../../assets/user_guides/layout/one_left_two_right.png -### Grid - advanced example -Generally, the `Layout` provides full control over the arrangement of top-level components within a page, -allowing arbitrarily granular control of the grid by creating larger grids. +## Grid - advanced example + +!!! tip "When to use Containers" -If you want to divide the grid into subgrids with finer control over these, you can use [`Containers`](container.md). -See our section on [when to use `Containers` vs. `Page.layout`](container.md#when-to-use-containers) for more information. + If you want to divide the grid into subgrids with finer control over these, you can use [`Containers`](container.md). See our section on [when to use `Containers` vs. `Page.layout`](container.md#when-to-use-containers) for more information. + +The `Layout` provides full control over the arrangement of top-level components within a page, +allowing arbitrarily granular control of the grid by creating larger grids. !!! example "Grid Arrangement - Advanced Example" + === "app.py" ```py import vizro.models as vm @@ -202,7 +212,7 @@ See our section on [when to use `Containers` vs. `Page.layout`](container.md#whe ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -257,28 +267,31 @@ See our section on [when to use `Containers` vs. `Page.layout`](container.md#whe [GridAdv]: ../../assets/user_guides/layout/grid_advanced.png -## Using custom layout examples -Below is a table of examples you can take as a reference to create some selected layouts: - -| Configuration | Description | Image | -|------------------------------------------------------------|:-----------------------|:-------------------------------------------------------------------------------------| -| `layout=Layout(grid=[[0]])` or
`layout=None` | one_left | | -| `layout=Layout(grid=[[0],[1]])` or
`layout=None` | two_left | | -| `layout=Layout(grid=[[0,1]])` | two_top | | -| `layout=Layout(grid=[[0],[1],[2]])` or
`layout=None` | three_left | | -| `layout=Layout(grid=[[0,1],[0,2]])` | one_left_two_right | | -| `layout=Layout(grid=[[0,0],[1,2]])` | one_top_two_bottom | | -| `layout=Layout(grid=[[0,1],[2,2]])` | two_top_one_bottom | | -| `layout=Layout(grid=[[0,1],[0,2],[0,3]])` | one_left_three_right | | -| `layout=Layout(grid=[[0,1],[2,3]])` | two_left_two_right | | -| `layout=Layout(grid=[[0,3],[1,3],[2,3]])` | three_left_one_right | | -| `layout=Layout(grid=[[0,0,0],[1,2,3]])` | one_top_three_bottom | | -| `layout=Layout(grid=[[0,1,2],[3,3,3]])` | three_top_one_bottom | | - - -## Adding empty spaces to the grid -One approach to organize the dashboard's layout involves integrating empty spaces. -This can be achieved by specifying `-1` within your grid layout. +## Custom layout examples +Here is a reference table of example layouts: + + +one row with one component, second row with two components stacked horizontally + +| Layout needed | Grid | Code | +|--------------------------------|----------------------------|-------------------------------------------------------------------------------------| +| one component | layout=vm.Layout(grid=[[0]]) | `layout=vm.Layout(grid=[[0]])` | +| two horizontally stacked rows, each with one component | | `layout=vm.Layout(grid=[[0],[1]])` | +| one row with two components set horizontally | layout=vm.Layout(grid=[[0],[1]]) | `layout=vm.Layout(grid=[[0,1]])` | +| three horizontally stacked rows, each with one component | layout=vm.Layout(grid=[[0],[1],[2]] | `layout=vm.Layout(grid=[[0],[1],[2]])` or
`layout=None` | +| one row divided into two separate columns where the left column is one component and the right is two stacked components | layout=vm.Layout(grid=[[0,1],[0,2]]) | `layout=vm.Layout(grid=[[0,1],[0,2]])` | +| two rows with the top as a single component and the bottom divided into two components | layout=vm.Layout(grid=[[0,0],[1,2]]) | `layout=vm.Layout(grid=[[0,0],[1,2]])` | +| two rows with the top divided into two columns where each holds one component, and the bottom as a single component | layout=vm.Layout(grid=[[0,1],[2,2]]) | `layout=vm.Layout(grid=[[0,1],[2,2]])` | +| two columns where the left is a single component and the right is a set of three horizontally stacked components | layout=vm.Layout(grid=[[0,1],[0,2],[0,3]]) | `layout=vm.Layout(grid=[[0,1],[0,2],[0,3]])` | +| two rows where each row is two components | layout=vm.Layout(grid=[[0,1],[2,3]]) | `layout=vm.Layout(grid=[[0,1],[2,3]])` | +| two columns where the left is a set of three horizontally stacked components and the right is a single component | layout=vm.Layout(grid=[[0,3],[1,3],[2,3]]) | `layout=vm.Layout(grid=[[0,3],[1,3],[2,3]])` | +| two rows where the top is a single component and the bottom is three separate components | layout=vm.Layout(grid=[[0,0,0],[1,2,3]]) | `layout=vm.Layout(grid=[[0,0,0],[1,2,3]])` | +| two rows where the top is three separate components and the bottom is a single component | layout=vm.Layout(grid=[[0,1,2],[3,3,3]]) | `layout=vm.Layout(grid=[[0,1,2],[3,3,3]])` | + + + +## Add empty sections to the grid +One approach to organize the dashboard's layout involves integrating empty sections by specifying `-1` within the grid layout. ```python title="Example" grid = [[0, 1, -1], @@ -286,6 +299,7 @@ grid = [[0, 1, -1], ``` !!! example "Adding Empty Spaces" + === "app.py" ```py import vizro.models as vm @@ -306,7 +320,7 @@ grid = [[0, 1, -1], ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -328,14 +342,14 @@ grid = [[0, 1, -1], [GridEmpty]: ../../assets/user_guides/layout/layout_empty_spaces.png -## Controlling the scroll behavior -By default, the grid will try to fit all charts/components on the screen. This can lead to distortions of the chart/component looking -squeezed in. You can control the scroll behavior of the grid by specifying the following: +## Control the scroll behavior +By default, the grid fits all charts/components on the screen. This can lead to distortions such that the chart/component looks squashed. To control the scroll behavior, you can specify the following: - `row_min_height`: Sets a chart/component's minimum height. Defaults to 0px. - `col_min_width`: Sets a chart/component's minimum width. Defaults to 0px. !!! example "Activate Scrolling" + === "app.py" ```py import vizro.models as vm @@ -361,7 +375,7 @@ squeezed in. You can control the scroll behavior of the grid by specifying the f ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -400,13 +414,12 @@ squeezed in. You can control the scroll behavior of the grid by specifying the f [GridScroll]: ../../assets/user_guides/layout/grid_scroll.png -## Further customizations -For further customizations, such as changing the gap between row and column, please refer to the +## Further customization +For further customization, such as changing the gap between row and column, refer to the documentation of the [`Layout`][vizro.models.Layout] model. ## Alternative layout approaches -In general, any arbitrarily granular layout can already be achieved using [`Page.layout`](layouts.md) alone and is our -recommended approach if you just want to arrange components on a page with consistent row and/or column spacing. +In general, any arbitrarily granular layout can already be achieved using [`Page.layout`](layouts.md) alone and is our recommended approach if you want to arrange components on a page with consistent row and/or column spacing. !!! note "Alternative layout approaches: `Tabs` and `Containers`" diff --git a/vizro-core/docs/pages/user-guides/navigation.md b/vizro-core/docs/pages/user-guides/navigation.md index a9b768901..c212b96b4 100644 --- a/vizro-core/docs/pages/user-guides/navigation.md +++ b/vizro-core/docs/pages/user-guides/navigation.md @@ -3,11 +3,11 @@ This guide shows you how to use and customize the navigation that appears on the left of your dashboard. The [`Dashboard`][vizro.models.Dashboard] model accepts a `navigation` argument, where you can enter a [`Navigation`][vizro.models.Navigation] model. This enables you to group pages together and customize how they appear in your navigation. -The dashboard includes a collapsible side panel that users can easily minimize or expand by a button click. The collapse button, located in the top right corner of the side panel, is visible by default for user convenience. +The dashboard includes a collapsible side panel that users can minimize or expand by a button click. The collapse button, located in the top right corner of the side panel, is visible by default for user convenience. -## Using the default navigation +## Use the default navigation -By default, if the `navigation` argument is not specified, Vizro creates a navigation panel which lists all the pages in your dashboard into a collapsible accordion menu with title "SELECT PAGE". +By default, if the `navigation` argument is not specified, Vizro creates a navigation panel which lists all the pages in your dashboard into a collapsible accordion menu with title `SELECT PAGE`. !!! example "Default navigation" === "app.py" @@ -43,7 +43,7 @@ By default, if the `navigation` argument is not specified, Vizro creates a navig === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -74,9 +74,9 @@ By default, if the `navigation` argument is not specified, Vizro creates a navig [DefaultNavigation]: ../../assets/user_guides/navigation/default_navigation.png -## Including a subset of pages +## Include a subset of pages -If you wish to include only some of your dashboard pages in your navigation then list them in the `pages` argument of the `Navigation` model. To refer to a page inside the `Navigation` model, you should always use the page's `id`, which [defaults to the page `title`](pages.md#customizing-the-page-url), e.g. we specify `pages = ["My first page", "My second page"]` rather than `pages=[page_1, page_2]`. +If you wish to include only some of your dashboard pages in your navigation then list them in the `pages` argument of the `Navigation` model. To refer to a page inside the `Navigation` model, you should always use the page's `id`, which [defaults to the page `title`](pages.md#customizing-the-page-url), we specify `pages = ["My first page", "My second page"]` rather than `pages=[page_1, page_2]`. !!! example "Navigation with only some pages" === "app.py" @@ -90,7 +90,7 @@ If you wish to include only some of your dashboard pages in your navigation then === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example # pages defined as in default example navigation: @@ -103,7 +103,7 @@ If you wish to include only some of your dashboard pages in your navigation then [OnlySomePages]: ../../assets/user_guides/navigation/only_some_pages.png -## Grouping pages +## Group pages You can also group your pages together by specifying `pages` as a dictionary: @@ -120,7 +120,7 @@ You can also group your pages together by specifying `pages` as a dictionary: === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example # pages defined as in default example navigation: @@ -137,7 +137,7 @@ You can also group your pages together by specifying `pages` as a dictionary: [GroupedNavigation]: ../../assets/user_guides/navigation/grouped_navigation.png -## Using a navigation bar with icons +## Use a navigation bar with icons Another way to group together pages in the navigation is to use a [`NavBar`][vizro.models.NavBar] with icons. The simplest way to use this is to change the `nav_selector` specified in [`Navigation`][vizro.models.Navigation]: @@ -156,7 +156,7 @@ Another way to group together pages in the navigation is to use a [`NavBar`][viz === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example # pages defined as in default example navigation: @@ -178,7 +178,7 @@ Another way to group together pages in the navigation is to use a [`NavBar`][viz Here, the first level of the navigation hierarchy ("Group A" and "Group B") is represented by an icon in a navigation bar, and the second level of the navigation (the pages) is represented by an accordion. By default, the set of icons used are the [`filter` icons from the Google Material icons library](https://fonts.google.com/icons?icon.query=filter). The icon label ("Group A" and "Group B") appears as a tooltip on hovering over the icon. -## Customizing the navigation bar +## Customize the navigation bar Under the hood, [`NavBar`][vizro.models.NavBar] uses [`NavLink`][vizro.models.NavLink] to build the icons in the navigation bar. It is possible to customize the navigation further by providing the `NavLink` models yourself. @@ -208,7 +208,7 @@ The same configuration for [grouping pages](#grouping-your-pages) applies inside === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example # pages defined as in default example navigation: @@ -253,7 +253,7 @@ You can alter the icons used by specifying the name of the icon in the [Google M === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example # pages defined as in default example navigation: diff --git a/vizro-core/docs/pages/user-guides/pages.md b/vizro-core/docs/pages/user-guides/pages.md index b87962759..695f6945d 100644 --- a/vizro-core/docs/pages/user-guides/pages.md +++ b/vizro-core/docs/pages/user-guides/pages.md @@ -1,16 +1,16 @@ # How to use pages This guide shows you how to add pages to your dashboard and customize the URL paths if needed. -A [`Page`][vizro.models.Page] lets you place and arrange your dashboard content (e.g., chart/components, tables, and text) -and configure your dashboard interactions (e.g. filters and parameters). +A [`Page`][vizro.models.Page] lets you place and arrange your dashboard content (for example, chart/components, tables, and text) +and configure your dashboard interactions (such as filters and parameters). The [`Dashboard`][vizro.models.Dashboard] model accepts the `pages` argument, where you can insert your [`Page`][vizro.models.Page]. -## Creating a page +## Create a page A [`Page`][vizro.models.Page] is split up into four main containers: 1. The **navigation container** where you can customize your `navigation` (see [Dashboard](dashboard.md) and [Navigation](navigation.md) for more information) -2. The **control container** where you can add your `controls` (see e.g. [Filters](filters.md) or [Parameters](parameters.md)) to interact with the dashboard +2. The **control container** where you can add your `controls` (see [Filters](filters.md) or [Parameters](parameters.md)) to interact with the dashboard 3. The **page header** that contains the page title and the theme toggle switch button 4. The **component container** where you can add your [components](components.md) to visualize your data @@ -21,11 +21,11 @@ A [`Page`][vizro.models.Page] is split up into four main containers: To create and add a page to your dashboard, do the following steps: -1. Provide a `title` to your [`Page`][vizro.models.Page] +1. Set a `title` for your [`Page`][vizro.models.Page] 2. Configure your `components`, see our guide on the [various options](components.md) 3. (optional) Configure your `controls` , see our guides on [Filters](filters.md) and [Parameters](parameters.md) 4. (optional) Configure your `layout` , see our guide on [Layouts](layouts.md) -5. (optional) Provide a `description` of your `Page` for the app's [meta tags](https://metatags.io/) +5. (optional) Give a `description` of your `Page` to the app's [meta tags](https://metatags.io/) !!! example "Page" === "app.py" @@ -56,7 +56,7 @@ To create and add a page to your dashboard, do the following steps: ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -98,8 +98,8 @@ To create and add a page to your dashboard, do the following steps: You can additionally navigate through the different pages by going directly to the relevant page URL (more details in next section). -## Customizing the page URL -By default, the page URL is automatically generated based on the `id` of the page e.g., if `id="This is my first page"` +## Customize the page URL +By default, the page URL is automatically generated based on the `id` of the page. For example, if `id="This is my first page"` the generated page URL will be `path=this-is-my-first-page`. You can then access the page via `localhost:/this-is-my-first-page`. Note that the page `id` defaults to be the same as the page `title` if not set. @@ -107,7 +107,7 @@ If you have multiple pages with the same `title` then you must assign a unique ` The first page always has the URL prefix `/` assigned. A custom URL can, therefore, not be created for the first page. -To customize the page URL, provide a valid URL name to the `path` argument of [`Page`][vizro.models.Page]: +To customize the page URL, pass a valid URL name to the `path` argument of [`Page`][vizro.models.Page]: !!! example "Page" === "app.py" @@ -147,7 +147,7 @@ To customize the page URL, provide a valid URL name to the `path` argument of [` ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: diff --git a/vizro-core/docs/pages/user-guides/parameters.md b/vizro-core/docs/pages/user-guides/parameters.md index 073e5879c..da007497d 100644 --- a/vizro-core/docs/pages/user-guides/parameters.md +++ b/vizro-core/docs/pages/user-guides/parameters.md @@ -1,8 +1,8 @@ # How to use parameters -This guide shows you how to add parameters to your dashboard. One main way to interact with the charts/components on your page is by changing the parameters of the underlying function that creates the chart/component. +This guide shows you how to add parameters to your dashboard. One main way to interact with the charts/components on your page is by changing the parameters of the underlying function (`figure` argument) that creates the chart/component. Parameters can also be used to [modify the data loaded into the dashboard itself](data.md/#parametrize-data-loading). -The [`Page`][vizro.models.Page] model accepts the `controls` argument, where you can enter a [`Parameter`][vizro.models.Parameter] model. If e.g. the charting function has a `title` argument, you could configure a parameter that allows the user to select the chart title via e.g. a dropdown. +The [`Page`][vizro.models.Page] model accepts the `controls` argument, where you can enter a [`Parameter`][vizro.models.Parameter] model. For example, if the charting function has a `title` argument, you could configure a parameter that enables the user to select the chart title with a dropdown. ## Basic parameters @@ -12,7 +12,7 @@ To add a parameter to your page, do the following: - add the `targets` argument - add a selector model to the `selector` argument. -In the `targets` argument, you can specify the component and function argument that the parameter should be applied to in the form of `.` (eg. `scatter_chart.title`). +In the `targets` argument, you can specify the component and function argument that the parameter should be applied to in the form of `.` (for example, `scatter_chart.title`). Unlike for the [`Filter`][vizro.models.Filter] model, you also have to configure the `selector` argument, by providing it with an appropriate model and the desired options/numeric ranges. @@ -50,7 +50,7 @@ Unlike for the [`Filter`][vizro.models.Filter] model, you also have to configure ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -78,11 +78,11 @@ Unlike for the [`Filter`][vizro.models.Filter] model, you also have to configure [Parameter]: ../../assets/user_guides/control/control4.png !!! tip - If you would like to pass `None` as a parameter, e.g. in order to make a parameter optional, you can specify the string `"NONE"` in the `options` or `value` field. + If you would like to pass `None` as a parameter and make a parameter optional, you can specify the string `"NONE"` in the `options` or `value` field. ## Nested parameters -If you want to modify nested parameters, you can specify the `targets` argument with a dot separated string like `..`. +If you want to change nested parameters, you can specify the `targets` argument with a dot separated string like `..`. !!! example "Nested Parameters for multiple targets" === "app.py" @@ -136,7 +136,7 @@ If you want to modify nested parameters, you can specify the `targets` argument ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -174,6 +174,10 @@ If you want to modify nested parameters, you can specify the `targets` argument [Nested]: ../../assets/user_guides/control/control5.png -In the above example, the object passed to the function argument `color_discrete_map` is a dictionary which maps the different flower species to fixed colors (eg. `{"virginica":"blue"}`). In this case, only the value `blue` should be changed instead of the entire dictionary. This can be achieved by specifying a target as `scatter.color_discrete_map.virginica`. +In the above example, the object passed to the function argument `color_discrete_map` is a dictionary which maps the different flower species to fixed colors (for example, `{"virginica":"blue"}`). In this case, only the value `blue` should be changed instead of the entire dictionary. This can be achieved by specifying a target as `scatter.color_discrete_map.virginica`. Note that in the above example, one parameter affects multiple targets. + +## Dynamic data parameters + +If you use [dynamic data](data.md/#dynamic-data) that can be updated while the dashboard is running then you can pass parameters to the dynamic data function to alter the data loaded into your dashboard. For detailed instructions, refer to the section on [parametrized data loading](data.md/#parametrize-data-loading). diff --git a/vizro-core/docs/pages/user-guides/run.md b/vizro-core/docs/pages/user-guides/run.md index 5479d58be..a80387b35 100644 --- a/vizro-core/docs/pages/user-guides/run.md +++ b/vizro-core/docs/pages/user-guides/run.md @@ -1,10 +1,10 @@ # How to launch the dashboard -This guide shows you how to launch your dashboard in different ways. By default, your dashboard apps run on localhost. +This guide shows you how to launch your dashboard in different ways. By default, your dashboard apps run on localhost port 8050 so is accessible at [http://127.0.0.1:8050/](http://127.0.0.1:8050/). -## Default built-in Flask web server +## Default built-in Flask development server -!!! example "Default built-in Flask web server" +!!! example "Default built-in Flask development server" === "app.py" ```py from vizro import Vizro @@ -24,22 +24,28 @@ This guide shows you how to launch your dashboard in different ways. By default, Vizro().build(dashboard).run() ``` -- create a python file named app.py. +- create a Python file named `app.py`. - type the command `python app.py` into your terminal. -- information below will be displayed in your terminal, go to the http link. +- information below will be displayed in your terminal, go to [http://127.0.0.1:8050/](http://127.0.0.1:8050/). + ``` Dash is running on http://127.0.0.1:8050/ * Serving Flask app 'app' - * Debug mode: on + * Debug mode: off +INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. ``` +!!! warning "In production" + + As per the above warning message, which is [further explained in the Flask documentation](https://flask.palletsprojects.com/en/3.0.x/deploying/), the Flask development server is intended for use only during local development and **should not** be used when deploying to production. Instead, you should instead use a production-ready solution such as [gunicorn](#gunicorn). + ??? info "Automatic reloading and debugging" You can set up the front-end to automatically refresh whenever dashboard configuration updates are made, as described in the ["Code Reloading and Hot Reloading" section of the Dash Dev Tools documentation](https://dash.plotly.com/devtools#code-reloading-&-hot-reloading). - This is turned off by default in Vizro apps but can be enabled by using `debug=True` in the `run()` method, eg. + This is turned off by default in Vizro apps but can be enabled by using `debug=True` in the `run()` method: `Vizro().build(dashboard).run(debug=True)` @@ -50,7 +56,8 @@ Dash is running on http://127.0.0.1:8050/ ## Jupyter The dashboard application can be launched in a Jupyter environment in `inline`, `external`, and `jupyterlab` mode. -!!! example "Run in jupyter notebook in inline mode" +!!! example "Run in a Jupyter Notebook in inline mode" + === "app.ipynb" ```py linenums="1" from vizro import Vizro @@ -69,18 +76,18 @@ The dashboard application can be launched in a Jupyter environment in `inline`, dashboard = vm.Dashboard(pages=[page]) Vizro().build(dashboard).run(jupyter_mode="external") ``` -- by default, the mode is set to `inline` in `run()` and the dashboard will be displayed inside your jupyter environment. +- by default, the mode is set to `inline` in `run()` and the dashboard will be displayed inside your Jupyter environment. - you can specify `jupyter_mode="external"` and a link will be displayed to direct you to the localhost where the dashboard is running. - you can use tab mode by `jupyter_mode="tab"` to automatically open the app in a new browser ??? info "Reloading and debugging" + When working in a Jupyter Notebook, only some of the [Dash Dev Tools](https://dash.plotly.com/devtools) functionality is enabled by using `run(debug=True)`. + In particular, code reloading and hot reloading do not work from a Jupyter Notebook. Instead, you must restart the entire Jupyter kernel to reload the dashboard and reflect changes in the dashboard configuration. - When working in a Jupyter notebook, only some of the [Dash Dev Tools](https://dash.plotly.com/devtools) functionality is enabled by using `run(debug=True)`. - In particular, code reloading and hot reloading do not work from a Jupyter notebook. Instead, you must restart the entire Jupyter kernel to reload the dashboard and reflect changes in the dashboard configuration. ## Gunicorn -!!!warning "In production" - In production, it is recommended **not** to use the default Flask server. One of the options here is Gunicorn. It is easy to scale the application to serve more users or run more computations, run more "copies" of the app in separate processes. + +[Gunicorn](https://gunicorn.org/) is a production-ready Python WSGI server for deploying an app over multiple worker processes. It can be installed with `pip install gunicorn`. !!! example "Use Gunicorn" === "app.py" @@ -113,7 +120,11 @@ To run using Gunicorn with four worker processes, execute ```bash gunicorn app:server --workers 4 ``` -in the command line. For more Gunicorn configuration options, please refer to [Gunicorn documentation](https://docs.gunicorn.org/). +in the command line. For more Gunicorn configuration options, refer to [Gunicorn documentation](https://docs.gunicorn.org/). + +!!! warning "In production" + + If your dashboard uses [dynamic data](data.md#dynamic-data) that can be refreshed while the dashboard is running then you should [configure your data manager cache](data.md#configure-cache) to use a backend that supports multiple processes. ## Deployment @@ -122,6 +133,6 @@ A Vizro app wraps a Dash app, which itself wraps a Flask app. Hence to deploy a - [Flask deployment documentation](https://flask.palletsprojects.com/en/2.0.x/deploying/) - [Dash deployment documentation](https://dash.plotly.com/deployment) -In particular, `app = Vizro()` exposes the Flask app through `app.dash.server`. As in the [above example with Gunicorn](#gunicorn), this provides the application instance to a WSGI server. +In particular, `app = Vizro()` exposes the Flask app through `app.dash.server`. As in the [above example with Gunicorn](#gunicorn), this provides the application instance to a [WSGI](https://werkzeug.palletsprojects.com/en/3.0.x/terms/#wsgi) server. -[`Vizro`][vizro.Vizro] accepts `**kwargs` that are passed through to `Dash`. This allows you to configure the underlying Dash app using the same [arguments that are available](https://dash.plotly.com/reference#dash.dash) in `Dash`. For example, in a deployment context, you might like to specify a custom `url_base_pathname` to serve your Vizro app at a specific URL rather than at your domain root. +[`Vizro`][vizro.Vizro] accepts `**kwargs` that are passed through to `Dash`. This enables you to configure the underlying Dash app using the same [arguments that are available](https://dash.plotly.com/reference#dash.dash) in `Dash`. For example, in a deployment context, you might like to specify a custom `url_base_pathname` to serve your Vizro app at a specific URL rather than at your domain root. diff --git a/vizro-core/docs/pages/user-guides/selectors.md b/vizro-core/docs/pages/user-guides/selectors.md index 408b8a563..9e1dcb14c 100644 --- a/vizro-core/docs/pages/user-guides/selectors.md +++ b/vizro-core/docs/pages/user-guides/selectors.md @@ -1,6 +1,6 @@ # How to use selectors -This guide highlights different selectors that can be used in a dashboard. Selectors do not serve a purpose on their own, but they allow to change how the input is given to other models, e.g. the [`Filter`][vizro.models.Filter] or the [`Parameter`][vizro.models.Parameter] model. +This guide highlights different selectors that can be used in a dashboard. Selectors do not serve a purpose on their own, but they enable you to change how the input is given to other models, for example, the [`Filter`][vizro.models.Filter] or the [`Parameter`][vizro.models.Parameter] model. The [`Filter`][vizro.models.Filter] or the [`Parameter`][vizro.models.Parameter] model accept the `selector` argument, where a selector model can be entered to choose how the user should input their choices for the respective models. @@ -21,8 +21,8 @@ For more information, refer to the API reference of the selector, or the documen When configuring the `options` of the categorical selectors, you can either provide: - - a list of values e.g. `options = ['Value A', 'Value B', 'Value C']` - - or a dictionary of label-value mappings e.g. `options=[{'label': 'True', 'value': True}, {'label': 'False', 'value': False}]` + - a list of values `options = ['Value A', 'Value B', 'Value C']` + - or a dictionary of label-value mappings `options=[{'label': 'True', 'value': True}, {'label': 'False', 'value': False}]` The later is required if you want to provide different display labels to your option values or in case you want to provide boolean values as options. In this case, you need to provide a string label for your boolean values as @@ -42,7 +42,7 @@ For more information, refer to the API reference of the selector, or the documen To our knowledge, this is a current bug in the underlying [`dcc.Slider`](https://dash.plotly.com/dash-core-components/slider) and [`dcc.RangeSlider`](https://dash.plotly.com/dash-core-components/rangeslider) component, which you can circumvent by adapting the `step` size accordingly. -## Temporal Selectors +## Temporal selectors For more information, refer to the API reference of the selector, or the documentation of its underlying Dash component: @@ -52,7 +52,7 @@ For more information, refer to the API reference of the selector, or the documen When the [`DatePicker`][vizro.models.DatePicker] is configured with `range=True` (the default), the underlying component is `dmc.DateRangePicker`. When `range=False` the underlying component is `dmc.DatePicker`. - When configuring the [`DatePicker`][vizro.models.DatePicker] make sure to provide your dates for `min`, `max` and `value` arguments in `"yyyy-mm-dd"` format or as `datetime` type (e.g. `datetime.datetime(2024, 01, 01)`). + When configuring the [`DatePicker`][vizro.models.DatePicker] make sure to provide your dates for `min`, `max` and `value` arguments in `"yyyy-mm-dd"` format or as `datetime` type (for example, `datetime.datetime(2024, 01, 01)`). ## Default selectors @@ -117,7 +117,7 @@ indicating that [pandas.api.types.is_datetime64_any_dtype()](https://pandas.pyda ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -143,4 +143,4 @@ indicating that [pandas.api.types.is_datetime64_any_dtype()](https://pandas.pyda [Filter]: ../../assets/user_guides/selectors/default_filter_selectors.png -To enhance existing selectors, please see our How-to-guide on creating [custom components](custom_components.md). +To enhance existing selectors, see our [how-to-guide on creating custom components](custom-components.md). diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index 9f8584910..122305743 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -3,9 +3,9 @@ This guide shows you how to visualize tables in Vizro. There are two ways to visualize tables in Vizro, using either [AG Grid](#ag-grid) or [Dash DataTable](#dash-datatable). -In general, [AG Grid](#ag-grid) is Vizro's recommended table implementation, but in some cases it may make sense to use the [Dash DataTable](#dash-datatable) instead. +In general, [AG Grid](#ag-grid) is Vizro's recommended table implementation, but sometimes it may make sense to use the [Dash DataTable](#dash-datatable) instead. -## How to choose between AG Grid and Dash DataTable +## Choose between AG Grid and Dash DataTable Vizro offers two models - the [`AgGrid`][vizro.models.AgGrid] model and the [`Table`][vizro.models.Table] model - for the above two approaches respectively. They both visualize tabular data in similar ways. @@ -34,14 +34,14 @@ To add a [`AgGrid`][vizro.models.AgGrid] to your page, do the following: [`Page`][vizro.models.Page] model. - Enter the `dash_ag_grid` function under the `figure` argument (imported via `from vizro.tables import dash_ag_grid`). -The Vizro version of this AG Grid differs in one way from the original Dash AG Grid: it requires the user to provide a pandas dataframe as source of data. -This must be entered under the argument `data_frame`. Most other [parameters of the Dash AG Grid](https://dash.plotly.com/dash-ag-grid/reference) can be entered as keyword arguments. -Note that some defaults are set for some of the arguments (e.g. for `columnDefs`) to help with styling and usability. -In some cases a parameter may not work because it e.g. requires an additional callback to function. In that case you can try -creating a [custom AG Grid callable](custom-tables.md) or reach out to the Vizro team for help. +The Vizro version of this AG Grid differs in one way from the original Dash AG Grid: it requires the user to pass a pandas DataFrame as the source of data. +As explained in [our guide to using data in Vizro](data.md), this must be entered under the argument `data_frame`. Most other [parameters of the Dash AG Grid](https://dash.plotly.com/dash-ag-grid/reference) can be entered as keyword arguments. +Note that some defaults are set for some arguments (for example, for `columnDefs`) to help with styling and usability. +Sometimes a parameter may not work because it requires a callback to function. In that case you can try [creating a custom AG Grid callable](custom-tables.md) or reach out to the [Vizro team](mailto:vizro@mckinsey.com) for help. !!! example "Basic Dash AG Grid" + === "app.py" ```py import vizro.models as vm @@ -64,7 +64,7 @@ creating a [custom AG Grid callable](custom-tables.md) or reach out to the Vizro ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: @@ -88,21 +88,22 @@ creating a [custom AG Grid callable](custom-tables.md) or reach out to the Vizro #### Numbers One of the most common tasks when working with tables is to format the columns so that displayed numbers are more readable. -In order to do this, you can use the native functionality of [Value Formatters](https://dash.plotly.com/dash-ag-grid/value-formatters) -or the Vizro pre-defined [Custom Cell Data Types](https://dash.plotly.com/dash-ag-grid/cell-data-types#providing-custom-cell-data-types) as shown below. +To do this, you can use the native functionality of [value formatters](https://dash.plotly.com/dash-ag-grid/value-formatters) +or the Vizro pre-defined [custom cell data types](https://dash.plotly.com/dash-ag-grid/cell-data-types#providing-custom-cell-data-types) as shown below. The available custom cell types for Vizro are `dollar`, `euro`, `percentage` and `numeric`. -In order to use these, define your desired `` alongside the chosen `cellDataType` in +To use these, define your desired `` alongside the chosen `cellDataType` in the `columnDefs` argument of your `dash_ag_grid` function: ```py columnDefs = [{"field": "", "cellDataType": "euro"}] ``` -In the example below we select and format some columns of the gapminder dataset. +In the example below we select and format some columns of the gapminder data. ??? example "AG Grid with formatted columns" + === "app.py" ```py import vizro.models as vm @@ -134,7 +135,7 @@ In the example below we select and format some columns of the gapminder dataset. ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: @@ -165,19 +166,20 @@ For the [`AgGrid`][vizro.models.AgGrid] model to sort and filter dates correctly string format `yyyy-mm-dd` (see [Dash AG Grid docs](https://dash.plotly.com/dash-ag-grid/date-filters#example:-date-filter)) or a pandas datetime object. Any pandas datetime column will be transformed into the `yyyy-mm-dd` format automatically. -#### Objects/Strings +#### Objects and strings No specific formatting is available for custom objects and strings, however you can make use of [Value Formatters](https://dash.plotly.com/dash-ag-grid/value-formatters) -to format e.g. displayed strings automatically. +to format displayed strings automatically. ### Styling and modifying the AG Grid As mentioned above, all [parameters of the Dash AG Grid](https://dash.plotly.com/dash-ag-grid/reference) can be entered as keyword arguments. Below you can find an example of a styled AG Grid where some conditional formatting is applied, and where the columns are editable, but not filterable or resizable. -There are many more ways to alter the grid beyond this showcase. +There are more ways to alter the grid beyond this showcase. AG Grid, like any other Vizro component, can be customized using custom CSS. You can find information in the [guide to overwriting CSS properties](./assets.md#Overwrite CSS properties in selective components). ??? example "Styled and modified Dash AG Grid" + === "app.py" ```py import vizro.models as vm @@ -247,7 +249,7 @@ There are many more ways to alter the grid beyond this showcase. ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: @@ -295,7 +297,7 @@ There are many more ways to alter the grid beyond this showcase. [AGGrid3]: ../../assets/user_guides/table/styled_aggrid.png -If the available arguments are not sufficient, there is always the option to create a [custom AG Grid callable](custom-tables.md). +If the available arguments are not sufficient, there is always the option to [create a custom AG Grid callable](custom-tables.md). ## Dash DataTable @@ -313,12 +315,15 @@ To add a [`Table`][vizro.models.Table] to your page, do the following: [`Page`][vizro.models.Page] model. - Enter the `dash_data_table` function under the `figure` argument (imported via `from vizro.tables import dash_data_table`). -The Vizro version of this table differs in one way from the original table: it requires the user to provide a pandas dataframe as source of data. -This must be entered under the argument `data_frame`. + +The Vizro version of this table differs in one way from the original table: it requires the user to pass a pandas DataFrame as the source of data. +As explained in [our guide to using data in Vizro](data.md), this must be entered under the argument `data_frame`. + All other [parameters of the Dash DataTable](https://dash.plotly.com/datatable/reference) can be entered as keyword arguments. Note that we are -setting some defaults for some of the arguments to help with styling. +setting some defaults for some arguments to help with styling. !!! example "Dash DataTable" + === "app.py" ```py import vizro.models as vm @@ -340,7 +345,7 @@ setting some defaults for some of the arguments to help with styling. ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: @@ -362,6 +367,7 @@ As mentioned above, all [parameters of the Dash DataTable](https://dash.plotly.c an example of a styled table where some conditional formatting is applied. There are many more ways to alter the table beyond this showcase. ??? example "Styled Dash DataTable" + === "app.py" ```py import vizro.models as vm @@ -426,7 +432,7 @@ an example of a styled table where some conditional formatting is applied. There ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: diff --git a/vizro-core/docs/pages/user-guides/tabs.md b/vizro-core/docs/pages/user-guides/tabs.md index cd3285524..cc813e84d 100755 --- a/vizro-core/docs/pages/user-guides/tabs.md +++ b/vizro-core/docs/pages/user-guides/tabs.md @@ -2,15 +2,14 @@ [`Tabs`][vizro.models.Tabs] organize and separate groups of related content in a dashboard, letting users switch between different sections or views. They are essentially a way of putting multiple [`Containers`][vizro.models.Container] in the same screen space, and letting the user switch between them. -`Containers` enable the grouping of page components into sections and subsections; see our [user guide on `Containers`](container.md) for more information. +`Containers` enable the grouping of page components into sections and subsections. See our [user guide on `Containers`](container.md) for more information.
![tabs](../../assets/user_guides/components/tabs-info.png){ width="400"}
Displaying multiple containers in Tabs
-Both `Tabs` and `Containers` are a more advanced technique for customizing your page layout. If you simply want to arrange components on a page, -we recommend reading our [user guide on `Layout`](layouts.md) first. +Both `Tabs` and `Containers` are a more advanced technique for customizing your page layout. If you want to arrange components on a page, we recommend reading our [user guide on `Layout`](layouts.md) first. This guide shows you how to use tabs to organize your `Containers` into subsections inside the dashboard. @@ -91,7 +90,7 @@ To add [`Tabs`][vizro.models.Tabs] to your page, do the following: ``` === "app.yaml" ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See from_yaml example pages: - components: diff --git a/vizro-core/docs/pages/user-guides/themes.md b/vizro-core/docs/pages/user-guides/themes.md index ae36c3b96..aeea99906 100644 --- a/vizro-core/docs/pages/user-guides/themes.md +++ b/vizro-core/docs/pages/user-guides/themes.md @@ -35,7 +35,7 @@ you can still switch between the themes via the toggle button in the upper-right ``` === "app.yaml" ```yaml hl_lines="11" - # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # Still requires a .py to add data to the data manager and parse YAML configuration # See yaml_version example pages: - components: diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index 11f21e685..623e86254 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -3,30 +3,23 @@ import vizro.models as vm import vizro.plotly.express as px from vizro import Vizro -from vizro.tables import dash_ag_grid -df = px.data.gapminder() +df = px.data.iris() page = vm.Page( - title="Enhanced AG Grid", + title="My first dashboard", components=[ - vm.AgGrid( - title="Dash AG Grid", - figure=dash_ag_grid( - data_frame=df, - columnDefs=[ - {"field": "country", "floatingFilter": True, "suppressHeaderMenuButton": True}, - {"field": "continent", "floatingFilter": True, "suppressHeaderMenuButton": True}, - {"field": "year"}, - {"field": "lifeExp", "cellDataType": "numeric"}, - {"field": "pop", "cellDataType": "numeric"}, - {"field": "gdpPercap", "cellDataType": "euro"}, - ], - ), - ), + # components consist of vm.Graph or vm.Table + vm.Graph(id="scatter_chart", figure=px.scatter(df, x="sepal_length", y="petal_width", color="species")), + vm.Graph(id="hist_chart", figure=px.histogram(df, x="sepal_width", color="species")), + ], + controls=[ + # controls consist of vm.Filter or vm.Parameter + # filter the dataframe (df) of the target graph (histogram), by column sepal_width, using the dropdown + vm.Filter(column="sepal_width", selector=vm.Dropdown(), targets=["hist_chart"]), ], - controls=[vm.Filter(column="continent")], ) + dashboard = vm.Dashboard(pages=[page]) if __name__ == "__main__": diff --git a/vizro-core/examples/_dev/assets/css/custom.css b/vizro-core/examples/_dev/assets/css/custom.css index 7b3d7ecf2..956c8f7b3 100644 --- a/vizro-core/examples/_dev/assets/css/custom.css +++ b/vizro-core/examples/_dev/assets/css/custom.css @@ -3,16 +3,12 @@ margin-left: -8px; } -#container-id .grid-layout, -#container-id-2 .grid-layout, -#container-id-3 .grid-layout { - display: flex; - flex-direction: column; - gap: 24px !important; - width: 40%; +/* Apply styling to parent */ +.card:has(#my-card) { + background-color: white; } -#container-id-2 .grid-layout, -#container-id-3 .grid-layout { - width: 70%; +/* Apply styling to children */ +#my-card p { + color: black; } diff --git a/vizro-core/examples/_dev/yaml_version/app.py b/vizro-core/examples/_dev/yaml_version/app.py index 8da1c9697..808cdae9b 100644 --- a/vizro-core/examples/_dev/yaml_version/app.py +++ b/vizro-core/examples/_dev/yaml_version/app.py @@ -9,8 +9,8 @@ from vizro.managers import data_manager from vizro.models import Dashboard -data_manager["iris"] = px.data.iris -data_manager["gapminder"] = px.data.gapminder +data_manager["iris"] = px.data.iris() +data_manager["gapminder"] = px.data.gapminder() data_manager["gapminder_2007"] = px.data.gapminder().query("year == 2007") diff --git a/vizro-core/examples/demo/app.py b/vizro-core/examples/demo/app.py index 5fa97b690..aaf026efc 100644 --- a/vizro-core/examples/demo/app.py +++ b/vizro-core/examples/demo/app.py @@ -16,6 +16,7 @@ .agg({"lifeExp": "mean", "pop": "mean", "gdpPercap": "mean"}) .reset_index() ) +gapminder_mean_2007 = gapminder_mean.query("year == 2007") gapminder_transformed = gapminder.copy() gapminder_transformed["lifeExp"] = gapminder.groupby(by=["continent", "year"])["lifeExp"].transform("mean") @@ -84,7 +85,7 @@ def variable_boxplot(y: str, data_frame: pd.DataFrame = None): def variable_bar(x: str, data_frame: pd.DataFrame = None): """Custom bar figure that needs post update calls.""" fig = px.bar( - data_frame.query("year == 2007"), + data_frame, x=x, y="continent", orientation="h", @@ -254,7 +255,7 @@ def create_variable_analysis(): ), vm.Graph( id="variable_bar", - figure=variable_bar(data_frame=gapminder_mean, x="lifeExp"), + figure=variable_bar(data_frame=gapminder_mean_2007, x="lifeExp"), ), ], controls=[ @@ -434,11 +435,11 @@ def create_benchmark_analysis(): page_country = vm.Page( title="Benchmark Analysis", description="Discovering how the metrics differ for each country and export data for further investigation", - layout=vm.Layout(grid=[[0, 1]] * 5 + [[2, -1]], col_gap="32px", row_gap="60px"), + layout=vm.Layout(grid=[[0, 1]] * 5 + [[2, -1]]), components=[ vm.AgGrid( title="Click on a cell in country column:", - figure=dash_ag_grid(id="dash_ag_grid_country", data_frame=gapminder, columnDefs=columnsDefs), + figure=dash_ag_grid(data_frame=gapminder, columnDefs=columnsDefs), actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], ), vm.Graph( @@ -476,7 +477,7 @@ def create_home_page(): page_home = vm.Page( title="Homepage", description="Vizro demo app for studying gapminder data", - layout=vm.Layout(grid=[[0, 1], [2, 3]], row_gap="16px", col_gap="24px"), + layout=vm.Layout(grid=[[0, 1], [2, 3]]), components=[ vm.Card( text=""" diff --git a/vizro-core/examples/demo/assets/css/custom.css b/vizro-core/examples/demo/assets/css/custom.css index b1cfc7f41..8658c2b57 100644 --- a/vizro-core/examples/demo/assets/css/custom.css +++ b/vizro-core/examples/demo/assets/css/custom.css @@ -5,7 +5,6 @@ img[src*="#my-image"] { width: 100px; } -#logo { - height: 40px; - margin-left: -8px; +#page-header { + padding-left: 8px; } diff --git a/vizro-core/examples/demo/jupyter_version/app.ipynb b/vizro-core/examples/demo/jupyter_version/app.ipynb index dc5de4e87..cb2d8e196 100644 --- a/vizro-core/examples/demo/jupyter_version/app.ipynb +++ b/vizro-core/examples/demo/jupyter_version/app.ipynb @@ -2,9 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "import pandas as pd\n", "\n", @@ -13,13 +11,13 @@ "from vizro import Vizro\n", "from vizro.actions import export_data, filter_interaction\n", "from vizro.tables import dash_data_table" - ] + ], + "outputs": [], + "execution_count": null }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "gapminder = px.data.gapminder()\n", "gapminder_mean = (\n", @@ -35,13 +33,13 @@ "gapminder_concat = pd.concat(\n", " [gapminder_transformed.assign(color=\"Continent Avg.\"), gapminder.assign(color=\"Country\")], ignore_index=True\n", ")" - ] + ], + "outputs": [], + "execution_count": null }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def create_variable_analysis():\n", " \"\"\"Function returns a page with gapminder data to do variable analysis.\"\"\"\n", @@ -442,12 +440,11 @@ " page_country = vm.Page(\n", " title=\"Benchmark Analysis\",\n", " description=\"Discovering how the metrics differ for each country and export data for further investigation\",\n", - " layout=vm.Layout(grid=[[0, 1]] * 5 + [[2, -1]], col_gap=\"32px\", row_gap=\"60px\"),\n", + " layout=vm.Layout(grid=[[0, 1]] * 5 + [[2, -1]]),\n", " components=[\n", " vm.Table(\n", " title=\"Click on a cell in country column:\",\n", " figure=dash_data_table(\n", - " id=\"dash_data_table_country\",\n", " data_frame=gapminder,\n", " columns=columns,\n", " page_size=30,\n", @@ -524,7 +521,7 @@ " page_home = vm.Page(\n", " title=\"Homepage\",\n", " description=\"Vizro demo app for studying gapminder data\",\n", - " layout=vm.Layout(grid=[[0, 1], [2, 3]], row_gap=\"16px\", col_gap=\"24px\"),\n", + " layout=vm.Layout(grid=[[0, 1], [2, 3]]),\n", " components=[\n", " vm.Card(\n", " text=\"\"\"\n", @@ -570,13 +567,13 @@ " ],\n", " )\n", " return page_home" - ] + ], + "outputs": [], + "execution_count": null }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "dashboard = vm.Dashboard(\n", " title=\"Vizro Demo\",\n", @@ -601,16 +598,18 @@ " ),\n", " ),\n", ")" - ] + ], + "outputs": [], + "execution_count": null }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "Vizro(assets_folder=\"../assets\").build(dashboard).run()\n" - ] + ], + "outputs": [], + "execution_count": null } ], "metadata": { diff --git a/vizro-core/examples/features/assets/css/custom.css b/vizro-core/examples/features/assets/css/custom.css index a993dd77f..f8c8df785 100644 --- a/vizro-core/examples/features/assets/css/custom.css +++ b/vizro-core/examples/features/assets/css/custom.css @@ -1,4 +1,3 @@ -#logo { - height: 40px; - margin-left: -8px; +#page-header { + padding-left: 8px; } diff --git a/vizro-core/hatch.toml b/vizro-core/hatch.toml index 1af59d88b..b98a80e9e 100644 --- a/vizro-core/hatch.toml +++ b/vizro-core/hatch.toml @@ -21,8 +21,9 @@ dependencies = [ "coverage[toml]>=6.5", "pytest", "pytest-mock", + "freezegun>=1.5.0", "dash[testing]", - "chromedriver-autoinstaller-fix", + "chromedriver-autoinstaller>=0.6.4", "toml", "pyyaml", "openpyxl" @@ -42,11 +43,14 @@ prep-release = [ "rm -rf schemas/*json", "schema", "git add schemas", - 'echo "Now raise a PR to merge into main with title: Release of vizro-core $(hatch version)"' + 'echo "Now raise a PR to merge into main with title: [Release] Release of vizro $(hatch version)"' ] pypath = "python -c 'import sys; print(sys.executable)'" schema = ["python schemas/generate.py {args}", '- hatch run lint --files="schemas/$(hatch version).json" > /dev/null'] -secrets = "pre-commit run gitleaks --all-files" +secrets = "hatch run lint:pre-commit run gitleaks --all-files" +# Note `hatch run test` currently fails due to interference between integration tests and unit tests. Ideally we would +# fix this, but we don't actually use `hatch run test` anywhere right now. +# See comments added in https://github.com/mckinsey/vizro/pull/444. test = "pytest tests --headless {args}" test-integration = "pytest tests/integration --headless {args}" test-js = "./tools/run_jest.sh {args}" @@ -63,10 +67,17 @@ dependencies = [ "mkdocs", "mkdocs-material", "mkdocs-git-revision-date-localized-plugin", - "mkdocstrings[python]" + "mkdocstrings[python]", + "linkchecker" ] detached = true -scripts = {serve = "mkdocs serve"} + +[envs.docs.scripts] +build = "mkdocs build --strict" +# Disable warnings on the linkcheck so that HTTP redirects are accepted. We could ignore only that warning and specify +# more advanced settings using a linkcheckerrc config file. +link-check = "linkchecker site --check-extern --no-warnings --ignore=404.html --ignore-url=127.0.0.1" +serve = "mkdocs serve --strict" [envs.lint] dependencies = [ @@ -78,7 +89,7 @@ scripts = {lint = "SKIP=gitleaks pre-commit run {args:--all-files}"} [envs.lower-bounds] extra-dependencies = [ "pydantic==1.10.13", - "dash==2.14.1" + "dash==2.17.0" ] [publish.index] diff --git a/vizro-core/mkdocs.yml b/vizro-core/mkdocs.yml index d47de9eaa..8b442b9e4 100644 --- a/vizro-core/mkdocs.yml +++ b/vizro-core/mkdocs.yml @@ -2,12 +2,12 @@ site_name: Vizro site_url: https://vizro.readthedocs.io/en/stable/ nav: - Vizro: index.md - - Get started: - - Install Vizro: pages/user-guides/install.md + - Tutorials: - A first dashboard: pages/tutorials/first-dashboard.md - Explore Vizro: pages/tutorials/explore-components.md - How-to guides: - FUNDAMENTALS: + - Install Vizro: pages/user-guides/install.md - Dashboards: pages/user-guides/dashboard.md - Pages: pages/user-guides/pages.md - Run methods: pages/user-guides/run.md @@ -30,9 +30,9 @@ nav: - Assets: pages/user-guides/assets.md - ACTIONS: - Actions: pages/user-guides/actions.md - - DATA CONNECTIONS: + - DATA: - Data: pages/user-guides/data.md - - Integrations: pages/user-guides/integration.md + - Kedro Data Catalog: pages/user-guides/kedro-data-catalog.md - EXTENSIONS: - Custom charts: pages/user-guides/custom-charts.md - Custom tables: pages/user-guides/custom-tables.md @@ -41,16 +41,18 @@ nav: - API Reference: - Vizro: pages/API-reference/vizro.md - Models: pages/API-reference/models.md - - Data manager: pages/API-reference/manager.md - Actions: pages/API-reference/actions.md - Table functions: pages/API-reference/captured-callables.md - Explanation: - - Why Vizro?: pages/explanation/why-vizro.md - - Compatibility: pages/explanation/compatibility.md - - Contribute: - - Contribute to Vizro: pages/development/contributing.md - - Documentation style: pages/development/documentation-style-guide.md - - Authors: pages/development/authors.md + - FAQs: pages/explanation/faq.md + - Contribute to Vizro: pages/explanation/contributing.md + - Documentation style: pages/explanation/documentation-style-guide.md + - Authors: pages/explanation/authors.md + - Examples: + - Example dashboard code: pages/examples/examples.md + - Examples from Vizro users: pages/examples/your-examples.md + - Vizro-AI: + - Vizro-AI: https://vizro.readthedocs.io/projects/vizro-ai/en/latest/ repo_url: https://github.com/mckinsey/vizro repo_name: mckinsey/vizro @@ -61,8 +63,8 @@ theme: palette: - scheme: default font: - text: Roboto - code: Roboto Mono + text: Inter + code: Inter Regular icon: repo: fontawesome/brands/github features: @@ -91,7 +93,11 @@ markdown_extensions: generic: true - footnotes - pymdownx.details - - pymdownx.superfences + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.tabbed: alternate_style: true - pymdownx.mark @@ -117,6 +123,8 @@ plugins: show_root_heading: true docstring_options: ignore_init_summary: true + warn_unknown_params: false + returns_named_value: false paths: [src] - git-revision-date-localized: enable_creation_date: false diff --git a/vizro-core/pyproject.toml b/vizro-core/pyproject.toml index b695b2093..24a31da4d 100644 --- a/vizro-core/pyproject.toml +++ b/vizro-core/pyproject.toml @@ -16,12 +16,14 @@ classifiers = [ ] dependencies = [ # Temporarily set an upper limit on dash: https://github.com/plotly/dash/issues/2778 - "dash>=2.14.1, !=2.16.0", # 2.14.1 needed for compatibility with werkzeug + "dash>=2.17.0", # 2.17.0 needed for new features on loading spinner "dash_bootstrap_components", "dash-ag-grid>=31.0.0", "pandas", "pydantic>=1.10.13", # must be synced with pre-commit mypy hook manually - "dash_mantine_components<0.13.0", # 0.13.0 is not compatible with 0.12 + "dash_mantine_components<0.13.0", # 0.13.0 is not compatible with 0.12, + "flask_caching>=2", + "wrapt>=1", "numpy>=1.22.2", # not directly required, pinned by Snyk to avoid a vulnerability: https://security.snyk.io/vuln/SNYK-PYTHON-NUMPY-2321970 "setuptools>=65.5.1", # not directly required, pinned by Snyk to avoid a vulnerability: https://security.snyk.io/vuln/SNYK-PYTHON-SETUPTOOLS-3180412 "werkzeug>=3.0.1" # not directly required, pinned by Snyk to avoid a vulnerability: https://security.snyk.io/vuln/SNYK-PYTHON-WERKZEUG-6035177 diff --git a/vizro-core/schemas/0.1.15.dev0.json b/vizro-core/schemas/0.1.17.json similarity index 98% rename from vizro-core/schemas/0.1.15.dev0.json rename to vizro-core/schemas/0.1.17.json index b019142e0..eb7c6878b 100644 --- a/vizro-core/schemas/0.1.15.dev0.json +++ b/vizro-core/schemas/0.1.17.json @@ -287,14 +287,14 @@ "row_gap": { "title": "Row Gap", "description": "Gap between rows in px. Defaults to 12px.", - "default": "12px", + "default": "24px", "pattern": "[0-9]+px", "type": "string" }, "col_gap": { "title": "Col Gap", "description": "Gap between columns in px. Defaults to 12px.", - "default": "12px", + "default": "24px", "pattern": "[0-9]+px", "type": "string" }, @@ -1012,7 +1012,7 @@ }, "Parameter": { "title": "Parameter", - "description": "Alter the arguments supplied to any `targets` on the [`Page`][vizro.models.Page].\n\nExamples:\n >>> print(repr(Parameter(\n >>> targets=[\"scatter.x\"], selector=Slider(min=0, max=1, default=0.8, title=\"Bubble opacity\"))))\n\nArgs:\n type (Literal[\"parameter\"]): Defaults to `\"parameter\"`.\n targets (List[str]): Targets in the form of `.`.\n selector (SelectorType): See [SelectorType][vizro.models.types.SelectorType]. Converts selector value\n `\"NONE\"` into `None` to allow optional parameters.", + "description": "Alter the arguments supplied to any `targets` on the [`Page`][vizro.models.Page].\n\nExamples:\n >>> Parameter(targets=[\"scatter.x\"], selector=Slider(min=0, max=1, default=0.8, title=\"Bubble opacity\"))\n\nArgs:\n type (Literal[\"parameter\"]): Defaults to `\"parameter\"`.\n targets (List[str]): Targets in the form of `.`.\n selector (SelectorType): See [SelectorType][vizro.models.types.SelectorType]. Converts selector value\n `\"NONE\"` into `None` to allow optional parameters.", "type": "object", "properties": { "id": { diff --git a/vizro-core/schemas/0.1.14.json b/vizro-core/schemas/0.1.18.dev0.json similarity index 98% rename from vizro-core/schemas/0.1.14.json rename to vizro-core/schemas/0.1.18.dev0.json index b019142e0..eb7c6878b 100644 --- a/vizro-core/schemas/0.1.14.json +++ b/vizro-core/schemas/0.1.18.dev0.json @@ -287,14 +287,14 @@ "row_gap": { "title": "Row Gap", "description": "Gap between rows in px. Defaults to 12px.", - "default": "12px", + "default": "24px", "pattern": "[0-9]+px", "type": "string" }, "col_gap": { "title": "Col Gap", "description": "Gap between columns in px. Defaults to 12px.", - "default": "12px", + "default": "24px", "pattern": "[0-9]+px", "type": "string" }, @@ -1012,7 +1012,7 @@ }, "Parameter": { "title": "Parameter", - "description": "Alter the arguments supplied to any `targets` on the [`Page`][vizro.models.Page].\n\nExamples:\n >>> print(repr(Parameter(\n >>> targets=[\"scatter.x\"], selector=Slider(min=0, max=1, default=0.8, title=\"Bubble opacity\"))))\n\nArgs:\n type (Literal[\"parameter\"]): Defaults to `\"parameter\"`.\n targets (List[str]): Targets in the form of `.`.\n selector (SelectorType): See [SelectorType][vizro.models.types.SelectorType]. Converts selector value\n `\"NONE\"` into `None` to allow optional parameters.", + "description": "Alter the arguments supplied to any `targets` on the [`Page`][vizro.models.Page].\n\nExamples:\n >>> Parameter(targets=[\"scatter.x\"], selector=Slider(min=0, max=1, default=0.8, title=\"Bubble opacity\"))\n\nArgs:\n type (Literal[\"parameter\"]): Defaults to `\"parameter\"`.\n targets (List[str]): Targets in the form of `.`.\n selector (SelectorType): See [SelectorType][vizro.models.types.SelectorType]. Converts selector value\n `\"NONE\"` into `None` to allow optional parameters.", "type": "object", "properties": { "id": { diff --git a/vizro-core/snyk/requirements.txt b/vizro-core/snyk/requirements.txt index 2da9cb17c..f302ba00c 100644 --- a/vizro-core/snyk/requirements.txt +++ b/vizro-core/snyk/requirements.txt @@ -1,9 +1,11 @@ -dash>=2.14.1, !=2.16.0 +dash>=2.17.0 dash_bootstrap_components dash-ag-grid>=31.0.0 pandas pydantic>=1.10.13 dash_mantine_components<0.13.0 +flask_caching>=2 +wrapt>=1 numpy>=1.22.2 setuptools>=65.5.1 werkzeug>=3.0.1 diff --git a/vizro-core/src/vizro/__init__.py b/vizro-core/src/vizro/__init__.py index 73d49adfe..906982e1a 100644 --- a/vizro-core/src/vizro/__init__.py +++ b/vizro-core/src/vizro/__init__.py @@ -5,6 +5,6 @@ __all__ = ["Vizro"] -__version__ = "0.1.15.dev0" +__version__ = "0.1.18.dev0" logging.basicConfig(level=os.getenv("VIZRO_LOG_LEVEL", "WARNING")) diff --git a/vizro-core/src/vizro/_themes/_templates/common_values.py b/vizro-core/src/vizro/_themes/_templates/common_values.py index 2bfb1ad22..a38e71fbe 100644 --- a/vizro-core/src/vizro/_themes/_templates/common_values.py +++ b/vizro-core/src/vizro/_themes/_templates/common_values.py @@ -29,9 +29,9 @@ def create_template_common(): title_y=1, title_xanchor="left", title_yanchor="top", - title_pad_l=80, + title_pad_l=0, title_pad_r=0, - title_pad_t=24, + title_pad_t=6, title_pad_b=0, showlegend=True, legend_font_family=primary_font, @@ -42,12 +42,13 @@ def create_template_common(): legend_title_font_size=14, legend_title_text="", legend_bgcolor="rgba(0,0,0,0)", - margin_l=80, - margin_r=12, + margin_l=24, + margin_r=24, margin_t=64, margin_b=64, margin_pad=0, margin_autoexpand=True, + coloraxis_autocolorscale=False, # Set to False as otherwise users cannot customize via `color_continous_scale` coloraxis_colorbar_outlinewidth=0, coloraxis_colorbar_thickness=20, coloraxis_colorbar_showticklabels=True, diff --git a/vizro-core/src/vizro/_themes/_templates/template_dark.py b/vizro-core/src/vizro/_themes/_templates/template_dark.py index 25d20d79f..9a694efd4 100644 --- a/vizro-core/src/vizro/_themes/_templates/template_dark.py +++ b/vizro-core/src/vizro/_themes/_templates/template_dark.py @@ -42,8 +42,11 @@ def create_template_dark() -> Template: template_dark["layout"]["coloraxis"]["colorbar"]["tickcolor"] = COLORS["WHITE_30"] template_dark["layout"]["coloraxis"]["colorbar"]["tickfont"]["color"] = COLORS["WHITE_55"] template_dark["layout"]["coloraxis"]["colorbar"]["title"]["font"]["color"] = COLORS["WHITE_55"] - template_dark["layout"]["colorscale"]["diverging"] = COLORS["DIVERGING_RED_CYAN"][::-1] - template_dark["layout"]["colorscale"]["sequential"] = COLORS["DIVERGING_RED_CYAN"] # default for continuous + # Diverging, sequential and sequentialminus colorscale will only be applied automatically if + # `coloraxis_autocolorscale=True`. Otherwise, they have no effect, and the default for continuous color scales + # will be the color sequence applied to ["colorscale"]["sequential"]. + template_dark["layout"]["colorscale"]["diverging"] = COLORS["DIVERGING_RED_CYAN"] + template_dark["layout"]["colorscale"]["sequential"] = COLORS["SEQUENTIAL_CYAN"] template_dark["layout"]["colorscale"]["sequentialminus"] = COLORS["SEQUENTIAL_RED"][::-1] template_dark["layout"]["colorway"] = COLORS["DISCRETE_10"] diff --git a/vizro-core/src/vizro/_themes/_templates/template_light.py b/vizro-core/src/vizro/_themes/_templates/template_light.py index 805979b42..664bf151a 100644 --- a/vizro-core/src/vizro/_themes/_templates/template_light.py +++ b/vizro-core/src/vizro/_themes/_templates/template_light.py @@ -43,8 +43,8 @@ def create_template_light() -> Template: template_light["layout"]["coloraxis"]["colorbar"]["tickcolor"] = COLORS["BLACK_30"] template_light["layout"]["coloraxis"]["colorbar"]["tickfont"]["color"] = COLORS["BLACK_55"] template_light["layout"]["coloraxis"]["colorbar"]["title"]["font"]["color"] = COLORS["BLACK_55"] - template_light["layout"]["colorscale"]["diverging"] = COLORS["DIVERGING_RED_CYAN"][::-1] - template_light["layout"]["colorscale"]["sequential"] = COLORS["DIVERGING_RED_CYAN"] # default for continuous + template_light["layout"]["colorscale"]["diverging"] = COLORS["DIVERGING_RED_CYAN"] + template_light["layout"]["colorscale"]["sequential"] = COLORS["SEQUENTIAL_CYAN"] template_light["layout"]["colorscale"]["sequentialminus"] = COLORS["SEQUENTIAL_RED"][::-1] template_light["layout"]["colorway"] = COLORS["DISCRETE_10"] diff --git a/vizro-core/src/vizro/_vizro.py b/vizro-core/src/vizro/_vizro.py index d9703c247..ea34aa16e 100644 --- a/vizro-core/src/vizro/_vizro.py +++ b/vizro-core/src/vizro/_vizro.py @@ -1,10 +1,11 @@ import logging +import warnings from pathlib import Path from typing import List import dash -import dash_bootstrap_components as dbc import flask +from flask_caching import SimpleCache from vizro._constants import STATIC_URL_PREFIX from vizro.managers import data_manager, model_manager @@ -20,7 +21,7 @@ def __init__(self, **kwargs): """Initializes Dash app, stored in `self.dash`. Args: - kwargs: Passed through to `Dash.__init__`, e.g. `assets_folder`, `url_base_pathname`. See + **kwargs : Passed through to `Dash.__init__`, e.g. `assets_folder`, `url_base_pathname`. See [Dash documentation](https://dash.plotly.com/reference#dash.dash) for possible arguments. """ @@ -28,9 +29,6 @@ def __init__(self, **kwargs): self.dash.config.external_stylesheets.extend( [ "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined", - # Bootstrap theme has no effect on styling as it gets overwritten by our CSS. However, it is - # necessary to add a default theme here so that added dbc components work properly. - dbc.themes.BOOTSTRAP, ] ) @@ -39,6 +37,10 @@ def __init__(self, **kwargs): vizro_assets_folder = Path(__file__).with_name("static") requests_pathname_prefix = self.dash.config.requests_pathname_prefix vizro_css = [requests_pathname_prefix + path for path in self._get_external_assets(vizro_assets_folder, "css")] + + # Ensure vizro-bootstrap.min.css is loaded in first to allow overwrites + vizro_css.sort(key=lambda x: not x.endswith("vizro-bootstrap.min.css")) + vizro_js = [ {"src": requests_pathname_prefix + path, "type": "module"} for path in self._get_external_assets(vizro_assets_folder, "js") @@ -60,6 +62,8 @@ def __init__(self, **kwargs): ) ) + data_manager.cache.init_app(self.dash.server) + def build(self, dashboard: Dashboard): """Builds the `dashboard`. @@ -67,7 +71,7 @@ def build(self, dashboard: Dashboard): dashboard (Dashboard): [`Dashboard`][vizro.models.Dashboard] object. Returns: - Vizro: App object + self: Vizro app """ # Note Dash.index uses self.dash.title instead of self.dash.app.config.title. @@ -85,13 +89,20 @@ def run(self, *args, **kwargs): # if type annotated, mkdocstring stops seeing t """Runs the dashboard. Args: - args: Passed through to `dash.run`. - kwargs: Passed through to `dash.run`. + *args : Passed through to `dash.run`. + **kwargs : Passed through to `dash.run`. """ data_manager._frozen_state = True model_manager._frozen_state = True + if kwargs.get("processes", 1) > 1 and type(data_manager.cache.cache) is SimpleCache: + warnings.warn( + "`SimpleCache` is designed to support only single process environments. If you would like to use" + " multiple processes then you should change to a cache that supports it such as `FileSystemCache` or " + "`RedisCache`." + ) + self.dash.run(*args, **kwargs) @staticmethod @@ -109,7 +120,11 @@ def _pre_build(): @staticmethod def _reset(): - """Private method that clears all state in the `Vizro` app.""" + """Private method that clears all state in the `Vizro` app. + + This deliberately does not clear the data manager cache - see comments in data_manager._clear for + explanation. + """ data_manager._clear() model_manager._clear() dash._callback.GLOBAL_CALLBACK_LIST = [] diff --git a/vizro-core/src/vizro/actions/_actions_utils.py b/vizro-core/src/vizro/actions/_actions_utils.py index 9eb153997..6f0c327bb 100644 --- a/vizro-core/src/vizro/actions/_actions_utils.py +++ b/vizro-core/src/vizro/actions/_actions_utils.py @@ -128,61 +128,71 @@ def _update_nested_graph_properties( return graph_config -def _get_parametrized_config( - targets: List[ModelID], parameters: List[CallbackTriggerDict] -) -> Dict[ModelID, Dict[str, Any]]: - parameterized_config = {} - for target in targets: - # TODO - avoid calling _captured_callable. Once we have done this we can remove _arguments from - # CapturedCallable entirely. - graph_config = deepcopy(model_manager[target].figure._arguments) - if "data_frame" in graph_config: - graph_config.pop("data_frame") - - for ctd in parameters: - selector_value = ctd[ - "value" - ] # TODO: needs to be refactored so that it is independent of implementation details - if hasattr(selector_value, "__iter__") and ALL_OPTION in selector_value: # type: ignore[operator] - selector: SelectorType = model_manager[ctd["id"]] - selector_value = selector.options - selector_value = _validate_selector_value_none(selector_value) - selector_actions = _get_component_actions(model_manager[ctd["id"]]) +def _get_parametrized_config(target: ModelID, ctd_parameters: List[CallbackTriggerDict]) -> Dict[str, Any]: + # TODO - avoid calling _captured_callable. Once we have done this we can remove _arguments from + # CapturedCallable entirely. + config = deepcopy(model_manager[target].figure._arguments) + + # It's not possible to address nested argument of data_frame like data_frame.x.y, just top-level ones like + # data_frame.x. + config["data_frame"] = {} + + for ctd in ctd_parameters: + selector_value = ctd[ + "value" + ] # TODO: needs to be refactored so that it is independent of implementation details + if hasattr(selector_value, "__iter__") and ALL_OPTION in selector_value: # type: ignore[operator] + selector: SelectorType = model_manager[ctd["id"]] + selector_value = selector.options + selector_value = _validate_selector_value_none(selector_value) + selector_actions = _get_component_actions(model_manager[ctd["id"]]) - for action in selector_actions: - action_targets = _create_target_arg_mapping(action.function["targets"]) + for action in selector_actions: + if action.function._function.__name__ != "_parameter": + continue - if action.function._function.__name__ != "_parameter" or target not in action_targets: - continue + action_targets = _create_target_arg_mapping(action.function["targets"]) - for action_targets_arg in action_targets[target]: - graph_config = _update_nested_graph_properties( - graph_config=graph_config, dot_separated_string=action_targets_arg, value=selector_value - ) + if target not in action_targets: + continue - parameterized_config[target] = graph_config + for action_targets_arg in action_targets[target]: + config = _update_nested_graph_properties( + graph_config=config, dot_separated_string=action_targets_arg, value=selector_value + ) - return parameterized_config + return config # Helper functions used in pre-defined actions ---- -def _get_filtered_data( - targets: List[ModelID], - ctds_filters: List[CallbackTriggerDict], +def _get_targets_data_and_config( + ctds_filter: List[CallbackTriggerDict], ctds_filter_interaction: List[Dict[str, CallbackTriggerDict]], -) -> Dict[ModelID, pd.DataFrame]: - filtered_data = {} - for target in targets: - data_frame = data_manager._get_component_data(target) + ctds_parameters: List[CallbackTriggerDict], + targets: List[ModelID], +): + all_filtered_data = {} + all_parameterized_config = {} - data_frame = _apply_filters(data_frame=data_frame, ctds_filters=ctds_filters, target=target) - data_frame = _apply_filter_interaction( - data_frame=data_frame, ctds_filter_interaction=ctds_filter_interaction, target=target + for target in targets: + # parametrized_config includes a key "data_frame" that is used in the data loading function. + parameterized_config = _get_parametrized_config(target=target, ctd_parameters=ctds_parameters) + data_source_name = model_manager[target]["data_frame"] + data_frame = data_manager[data_source_name].load(**parameterized_config["data_frame"]) + + filtered_data = _apply_filters(data_frame=data_frame, ctds_filters=ctds_filter, target=target) + filtered_data = _apply_filter_interaction( + data_frame=filtered_data, ctds_filter_interaction=ctds_filter_interaction, target=target ) - filtered_data[target] = data_frame + # Parameters affecting data_frame have already been used above in data loading and so are excluded from + # all_parameterized_config. + all_filtered_data[target] = filtered_data + all_parameterized_config[target] = { + key: value for key, value in parameterized_config.items() if key != "data_frame" + } - return filtered_data + return all_filtered_data, all_parameterized_config def _get_modified_page_figures( @@ -191,13 +201,14 @@ def _get_modified_page_figures( ctds_parameters: List[CallbackTriggerDict], targets: Optional[List[ModelID]] = None, ) -> Dict[str, Any]: - if not targets: - targets = [] - filtered_data = _get_filtered_data( - targets=targets, ctds_filters=ctds_filter, ctds_filter_interaction=ctds_filter_interaction - ) + targets = targets or [] - parameterized_config = _get_parametrized_config(targets=targets, parameters=ctds_parameters) + filtered_data, parameterized_config = _get_targets_data_and_config( + ctds_filter=ctds_filter, + ctds_filter_interaction=ctds_filter_interaction, + ctds_parameters=ctds_parameters, + targets=targets, + ) outputs: Dict[str, Any] = {} for target in targets: diff --git a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py index 97bcfe664..574ec4296 100644 --- a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py +++ b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py @@ -4,7 +4,7 @@ from dash import Output, State, dcc -from vizro.actions import _parameter, export_data, filter_interaction +from vizro.actions import _parameter, filter_interaction from vizro.managers import model_manager from vizro.managers._model_manager import ModelID from vizro.models import Action, Page @@ -62,26 +62,16 @@ def _get_inputs_of_figure_interactions( # TODO: Refactor this and util functions once we implement "_get_input_property" method in VizroBaseModel models def _get_action_callback_inputs(action_id: ModelID) -> Dict[str, List[Union[State, Dict[str, State]]]]: """Creates mapping of pre-defined action names and a list of `States`.""" - action_function = model_manager[action_id].function._function page: Page = model_manager[model_manager._get_model_page_id(model_id=action_id)] - if action_function == export_data.__wrapped__: - include_inputs = ["filters", "filter_interaction"] - else: - include_inputs = ["filters", "parameters", "filter_interaction", "theme_selector"] - action_input_mapping = { - "filters": (_get_inputs_of_controls(page=page, control_type=Filter) if "filters" in include_inputs else []), - "parameters": ( - _get_inputs_of_controls(page=page, control_type=Parameter) if "parameters" in include_inputs else [] - ), + "filters": _get_inputs_of_controls(page=page, control_type=Filter), + "parameters": _get_inputs_of_controls(page=page, control_type=Parameter), # TODO: Probably need to adjust other inputs to follow the same structure List[Dict[str, State]] - "filter_interaction": ( - _get_inputs_of_figure_interactions(page=page, action_function=filter_interaction.__wrapped__) - if "filter_interaction" in include_inputs - else [] + "filter_interaction": _get_inputs_of_figure_interactions( + page=page, action_function=filter_interaction.__wrapped__ ), - "theme_selector": State("theme_selector", "checked") if "theme_selector" in include_inputs else [], + "theme_selector": State("theme_selector", "checked"), } return action_input_mapping diff --git a/vizro-core/src/vizro/actions/export_data_action.py b/vizro-core/src/vizro/actions/export_data_action.py index 086ad053f..5a1769801 100644 --- a/vizro-core/src/vizro/actions/export_data_action.py +++ b/vizro-core/src/vizro/actions/export_data_action.py @@ -5,7 +5,7 @@ from dash import ctx, dcc from typing_extensions import Literal -from vizro.actions._actions_utils import _get_filtered_data +from vizro.actions._actions_utils import _get_targets_data_and_config from vizro.managers import model_manager from vizro.managers._model_manager import ModelID from vizro.models.types import capture @@ -41,10 +41,11 @@ def export_data( if target not in model_manager: raise ValueError(f"Component '{target}' does not exist.") - data_frames = _get_filtered_data( + data_frames, _ = _get_targets_data_and_config( targets=targets, - ctds_filters=ctx.args_grouping["external"]["filters"], + ctds_filter=ctx.args_grouping["external"]["filters"], ctds_filter_interaction=ctx.args_grouping["external"]["filter_interaction"], + ctds_parameters=ctx.args_grouping["external"]["parameters"], ) outputs = {} diff --git a/vizro-core/src/vizro/integrations/kedro/_data_manager.py b/vizro-core/src/vizro/integrations/kedro/_data_manager.py index bfd5e02c2..aec2b8f18 100644 --- a/vizro-core/src/vizro/integrations/kedro/_data_manager.py +++ b/vizro-core/src/vizro/integrations/kedro/_data_manager.py @@ -5,7 +5,7 @@ from kedro.framework.startup import bootstrap_project from kedro.io import DataCatalog -from vizro.managers._data_manager import pd_LazyDataFrame +from vizro.managers._data_manager import pd_DataFrameCallable def catalog_from_project( @@ -18,7 +18,7 @@ def catalog_from_project( return session.load_context().catalog -def datasets_from_catalog(catalog: DataCatalog) -> Dict[str, pd_LazyDataFrame]: +def datasets_from_catalog(catalog: DataCatalog) -> Dict[str, pd_DataFrameCallable]: datasets = {} for name in catalog.list(): dataset = catalog._get_dataset(name, suggest=False) diff --git a/vizro-core/src/vizro/managers/_data_manager.py b/vizro-core/src/vizro/managers/_data_manager.py index ebdfd5b35..6287f629d 100644 --- a/vizro-core/src/vizro/managers/_data_manager.py +++ b/vizro-core/src/vizro/managers/_data_manager.py @@ -1,126 +1,229 @@ """The data manager handles access to all DataFrames used in a Vizro app.""" -from typing import Callable, Dict, Union +from __future__ import annotations + +import functools +import logging +import os +import warnings +from functools import partial +from typing import Callable, Dict, Optional, Union import pandas as pd +import wrapt +from flask_caching import Cache from vizro.managers._managers_utils import _state_modifier -# Really ComponentID and DatasetName should be NewType and not just aliases but then for a user's code to type check +logger = logging.getLogger(__name__) + + +# Really DataSourceName should be NewType and not just aliases but then for a user's code to type check # correctly they would need to cast all strings to these types. -ComponentID = str -DatasetName = str -pd_LazyDataFrame = Callable[[], pd.DataFrame] +DataSourceName = str +pd_DataFrameCallable = Callable[..., pd.DataFrame] -class DataManager: - """Object to handle all data for the `vizro` application. +# TODO: consider merging with model_utils _log_call. Using wrapt.decorator is probably better than functools here. +# Might need messages that run before/after the wrapped function call. +# Follows the pattern recommended in https://wrapt.readthedocs.io/en/latest/decorators.html#decorators-with-arguments +# for making a wrapt.decorator with arguments. +def _log_call(message: str): + @wrapt.decorator + def wrapper(wrapped, instance, args, kwargs): + logger.debug(message) + # Might be useful if merged with model_utils _log_call: + # logging.debug(message.format(wrapped=wrapped, instance=instance, args=args, kwargs=kwargs)) + return wrapped(*args, **kwargs) - Examples - >>> import plotly.express as px - >>> data_manager["iris"] = px.data.iris() + return wrapper - """ - def __init__(self): - self.__lazy_data: Dict[DatasetName, pd_LazyDataFrame] = {} - self.__original_data: Dict[DatasetName, pd.DataFrame] = {} - self.__component_to_original: Dict[ComponentID, DatasetName] = {} - self._frozen_state = False +class _DynamicData: + """Wrapper for a pd_DataFrameCallable, i.e. a function that produces a pandas DataFrame. - @_state_modifier - def __setitem__(self, dataset_name: DatasetName, data: Union[pd.DataFrame, pd_LazyDataFrame]): - """Adds `data` to the `DataManager` with key `dataset_name`. + Crucially this means that the data can be refreshed during runtime, since the loading function can be re-run. - This is the only user-facing function when configuring a simple dashboard. Others are only used internally - in Vizro or advanced users who write their own actions. - """ - if dataset_name in self.__original_data or dataset_name in self.__lazy_data: - raise ValueError(f"Dataset {dataset_name} already exists.") + This is currently private since it's not expected that a user would instantiate it directly. Instead, you would use + through the data_manager interface as follows: + >>> def dynamic_data(): + >>> return pd.read_csv("dynamic_data.csv") + >>> data_manager["dynamic_data"] = dynamic_data + >>> data_manager["dynamic_data"].timeout = 5 # if you want to change the cache timeout to 5 seconds - if callable(data): - self.__lazy_data[dataset_name] = data - elif isinstance(data, pd.DataFrame): - self.__original_data[dataset_name] = data + Possibly in future, this will become a public class so you could directly do: + >>> data_manager["dynamic_data"] = DynamicData(dynamic_data, timeout=5) + + At this point we might like to disable the behavior so that data_manager setitem and getitem handle the same + object rather than doing an implicit conversion to _DynamicData. + """ + + def __init__(self, load_data: pd_DataFrameCallable): + self.__load_data: pd_DataFrameCallable = load_data + self.timeout: Optional[int] = None + # We might also want a self.cache_arguments dictionary in future that allows user to customize more than just + # timeout, but no rush to do this since other arguments are unlikely to be useful. + + def load(self, *args, **kwargs) -> pd.DataFrame: + """Loads data.""" + # Data source name can be extracted from the function's name since it was added there in DataManager.__setitem__ + logger.debug( + "Looking in cache for data source %s on process %s", + self.__load_data.__name__.rpartition(".")[-1], + os.getpid(), + ) + # We don't memoize the load method itself as this is tricky to get working fully when load is called with + # arguments, since we need the signature of the memoized function to match that of load_data. See + # https://github.com/GrahamDumpleton/wrapt/issues/263. + # It's also difficult to get memoize working correctly with bound methods anyway - see comment in + # DataManager.__setitem__. It's much easier to ensure that self.__load_data is always just a function. + if data_manager._cache_has_app: + # This includes the case of NullCache. + load_data = _log_call("Cache miss; reloading data")(self.__load_data) + load_data = data_manager.cache.memoize(timeout=self.timeout)(load_data) else: - raise TypeError( - f"Dataset {dataset_name} must be a pandas DataFrame or callable that returns pandas DataFrame." - ) + logger.debug("Cache not active; reloading data") + load_data = self.__load_data + return load_data(*args, **kwargs) - @_state_modifier - def _add_component(self, component_id: ComponentID, dataset_name: DatasetName): - """Adds a mapping from `component_id` to `dataset_name`.""" - if dataset_name not in self.__original_data and dataset_name not in self.__lazy_data: - raise KeyError(f"Dataset {dataset_name} does not exist.") - if component_id in self.__component_to_original: - raise ValueError( - f"Component with id={component_id} already exists and is mapped to dataset " - f"{self.__component_to_original[component_id]}. Components must uniquely map to a dataset across the " - f"whole dashboard. If you are working from a Jupyter Notebook, please either restart the kernel, or " - f"use 'from vizro import Vizro; Vizro._reset()`." - ) - self.__component_to_original[component_id] = dataset_name - def _get_component_data(self, component_id: ComponentID) -> pd.DataFrame: - """Returns the original data for `component_id`.""" - if component_id not in self.__component_to_original: - raise KeyError(f"Component {component_id} does not exist. You need to call add_component first.") - dataset_name = self.__component_to_original[component_id] +class _StaticData: + """Wrapper for a pd.DataFrame. This data cannot be updated during runtime. + + This is currently private since it's not expected that a user would instantiate it directly. Instead, you would use + through the data_manager interface as follows: + >>> data_manager["static_data"] = pd.read_csv("static_data.csv") - # Populate original data on first access only - if dataset_name not in self.__original_data: - self.__original_data[dataset_name] = self.__lazy_data[dataset_name]() + This class does not have much functionality but exists for a couple of reasons: + 1. to align interface with _DynamicData by providing a load method so that fetching data from data_manager can + transparently handle loading both _StaticData and _DynamicData without needing switching logic + 2. to raise a clear error message if a user tries to set a timeout on the data source + """ - # Return a copy so that the original data cannot be modified. This is not necessary if we are careful - # to not do any inplace=True operations, but probably safest to leave it here. - return self.__original_data[dataset_name].copy() + def __init__(self, data: pd.DataFrame): + self.__data = data - def _clear(self): - self.__init__() # type: ignore[misc] + def load(self) -> pd.DataFrame: + """Loads data. + Returns a copy of the data. This is not necessary if we are careful to not do any inplace=True operations, + but safest to leave it here, e.g. in case a user-defined action mutates the data. To be even safer we could + additionally (but not instead) copy data when setting it in __init__ but this consumes more memory and is not + necessary so long as data is only ever accessed through the intended API of data_manager["static_data"].load(). + """ + return self.__data.copy() + + def __setattr__(self, name, value): + # Any attributes that are only relevant for _DynamicData should go here to raise a clear error message. + if name in ["timeout"]: + raise AttributeError( + f"Static data that is a pandas.DataFrame itself does not support {name}; you should instead use a " + "dynamic data source that is a function that returns a pandas.DataFrame." + ) + super().__setattr__(name, value) -data_manager = DataManager() +class DataManager: + """Object to handle all data for the `vizro` application. -if __name__ == "__main__": - from functools import partial + Examples + >>> # Static data that cannot be refreshed during runtime + >>> data_manager["data"] = pd.read_csv("data.csv") + >>> # Data that can be refreshed during runtime + >>> def dynamic_data(): + >>> return pd.read_csv("dynamic_data.csv") + >>> data_manager["dynamic_data"] = dynamic_data + >>> data_manager["dynamic_data"].timeout = 5 # if you want to change the cache timeout to 5 seconds - import vizro.plotly.express as px + """ - dm = data_manager - dm["iris"] = px.data.iris() + def __init__(self): + self.__data: Dict[DataSourceName, Union[_DynamicData, _StaticData]] = {} + self._frozen_state = False + self.cache = Cache(config={"CACHE_TYPE": "NullCache"}) + # In future, possibly we will accept just a config dict. Would need to work out whether to handle merging with + # default values though. We would do this with something like this: + # def __set_cache(self, cache_config): + # if not isinstance(cache, Cache): + # self._cache = Cache(**cache) + # self._cache = value + # _cache = property(fset=__set_cache) - dm._add_component("component_id_a", "iris") - print(len(dm._get_component_data("component_id_a"))) # 150 # noqa: T201 + @_state_modifier + def __setitem__(self, name: DataSourceName, data: Union[pd.DataFrame, pd_DataFrameCallable]): + """Adds `data` to the `DataManager` with key `name`.""" + if callable(data): + # __qualname__ is required by flask-caching (even if we specify our own make_name) but + # not defined for partial functions and just '' for lambda functions. Defining __qualname__ + # means it's possible to have non-interfering caches for lambda functions (similarly if we + # end up using CapturedCallable, or that could instead set its own __qualname__). + # We handle __name__ the same way even though it's not currently essential to functioning of flask-caching + # in case they change the underlying implementation to use it. + # We use partial to effectively make an independent copy of the underlying data function. This means that + # it's possible to set __qualname__ independently for each data source. This is not essential for + # functions other than lambda, but it is essential for bound methods, as flask-caching cannot easily + # independently timeout different instances with different bound methods but the same underlying function + # data.__func__. If we don't do this then the bound method case needs some uglier hacking to make work + # correctly - see https://github.com/mckinsey/vizro/blob/abb7eebb230ba7e6cfdf6150dc56b211a78b1cd5/ + # vizro-core/src/vizro/managers/_data_manager.py. + # Once partial has been used, all dynamic data sources are on equal footing since they're all treated as + # functions rather than bound methods, e.g. by flask_caching.utils.function_namespace. This makes it much + # simpler to use flask-caching reliably. + # It's important the __qualname__ is the same across all workers, so use the data source name rather than + # e.g. the repr method that includes the id of the instance so would only work in the case that gunicorn is + # running with --preload. + # __module__ is also required in flask_caching.utils.function_namespace and not defined for partial + # functions in some versions of Python. + # update_wrapper ensures that __module__, __name__, __qualname__, __annotations__ and __doc__ are + # assigned to the new partial(data) the same as they were in data. This isn't strictly necessary but makes + # inspecting these functions easier. + data = functools.update_wrapper(partial(data), data) + data.__module__ = getattr(data, "__module__", "") + data.__name__ = ".".join([getattr(data, "__name__", ""), name]) + data.__qualname__ = ".".join([getattr(data, "__qualname__", ""), name]) + self.__data[name] = _DynamicData(data) + elif isinstance(data, pd.DataFrame): + self.__data[name] = _StaticData(data) + else: + raise TypeError( + f"Data source {name} must be a pandas DataFrame or function that returns a pandas DataFrame." + ) - dm._add_component("component_id_b", "iris") - df_a = dm._get_component_data("component_id_a") - df_a.drop(columns="species", inplace=True) - print(df_a.shape) # (150, 5) # noqa: T201 - df_b = dm._get_component_data("component_id_b") - print(df_b.shape) # (150, 6) # noqa: T201 + def __getitem__(self, name: DataSourceName) -> Union[_DynamicData, _StaticData]: + """Returns the `_DynamicData` or `_StaticData` object associated with `name`.""" + try: + return self.__data[name] + except KeyError as exc: + raise KeyError(f"Data source {name} does not exist.") from exc - # Lazy loading example 1 - def retrieve_iris(): - df = px.data.iris() - subset = df.query("species == 'setosa'") - return subset + def _clear(self): + # We do not actually call self.cache.clear() because (a) it would only work when self._cache_has_app is True, + # which is not the case when e.g. Vizro._reset is called, and (b) because we do not want to accidentally + # clear the cache if we eventually put a call to Vizro._reset inside Vizro(), since cache should be persisted + # across server restarts. + self.__init__() # type: ignore[misc] - dm["iris_subset"] = retrieve_iris - dm._add_component("component_id_c", "iris_subset") - print(len(dm._get_component_data("component_id_c"))) # 50 # noqa: T201 + @property + def _cache_has_app(self) -> bool: + """Detects whether self.cache.init_app has been called (as it is in Vizro) to attach a Flask app to the cache. - # Lazy loading example 2 - def retrieve_one_species(species): - df = px.data.iris() - subset = df[df["species"] == species].copy() - return subset + Note that even NullCache needs to have an app attached before it can be "used". The only time the cache would + not have an app attached is if the user tries to interact with the cache before Vizro() has been called. + """ + cache_has_app = hasattr(self.cache, "app") + if not cache_has_app and self.cache.config["CACHE_TYPE"] != "NullCache": + # Try to prevent anyone from setting data_manager.cache after they've instantiated Vizro(). + # No need to emit a warning if the cache is left as NullCache; we only care about this if someone has + # explicitly set a cache. + # Eventually Vizro should probably have init_app method explicitly to clear this up so the order of + # operations is more reliable. Alternatively we could just initialize cache at run time rather than build + # time, which is what Flask-Caching is really designed for. This would require an extra step for users + # though, since it could not go in Vizro.run() since that is not used in the case of gunicorn. + warnings.warn( + "Cache does not have Vizro app attached and so is not operational. Make sure you call " + "Vizro() after you set data_manager.cache." + ) + return cache_has_app - dm["data_from_external_1"] = lambda: retrieve_one_species("setosa") - dm._add_component("component_id_d", "data_from_external_1") - print(len(dm._get_component_data("component_id_d"))) # 50 # noqa: T201 - # Lazy loading example 3 - dm["data_from_external_2"] = partial(retrieve_one_species, "setosa") - dm._add_component("component_id_e", "data_from_external_2") - print(len(dm._get_component_data("component_id_e"))) # 50 # noqa: T201 +data_manager: DataManager = DataManager() diff --git a/vizro-core/src/vizro/managers/_model_manager.py b/vizro-core/src/vizro/managers/_model_manager.py index c2b154417..22dced235 100644 --- a/vizro-core/src/vizro/managers/_model_manager.py +++ b/vizro-core/src/vizro/managers/_model_manager.py @@ -12,6 +12,8 @@ from vizro.models import VizroBaseModel from vizro.models._action._actions_chain import ActionsChain +# As done for Dash components in dash.development.base_component, fixing the random seed is required to make sure that +# the randomly generated model ID for the same model matches up across workers when running gunicorn without --preload. rd = random.Random(0) ModelID = NewType("ModelID", str) @@ -107,7 +109,9 @@ def _get_page_actions_chains(self, page_id: ModelID) -> List[ActionsChain]: page_actions_chains.extend(model.actions) for control in page.controls: - if hasattr(control, "selector") and control.selector: + if hasattr(control, "actions") and control.actions: + page_actions_chains.extend(control.actions) + if hasattr(control, "selector") and control.selector and hasattr(control.selector, "actions"): page_actions_chains.extend(control.selector.actions) return page_actions_chains diff --git a/vizro-core/src/vizro/models/_action/_action.py b/vizro-core/src/vizro/models/_action/_action.py index e55a8501b..48d83585b 100644 --- a/vizro-core/src/vizro/models/_action/_action.py +++ b/vizro-core/src/vizro/models/_action/_action.py @@ -145,11 +145,11 @@ def _action_callback_function( return return_value @_log_call - def build(self): + def build(self) -> html.Div: """Builds a callback for the Action model and returns required components for the callback. Returns - List of required components (e.g. dcc.Download) for the Action model added to the `Dashboard` container. + Div containing a list of required components (e.g. dcc.Download) for the Action model """ external_callback_inputs, external_callback_outputs, action_components = self._get_callback_mapping() @@ -185,4 +185,4 @@ def callback_wrapper(external: Union[List[Any], Dict[str, Any]], internal: Dict[ return {"internal": {"action_finished": None}, "external": return_value} return {"internal": {"action_finished": None}} - return html.Div(children=action_components, id=f"{self.id}_action_model_components_div", hidden=True) + return html.Div(id=f"{self.id}_action_model_components_div", children=action_components, hidden=True) diff --git a/vizro-core/src/vizro/models/_components/_components_utils.py b/vizro-core/src/vizro/models/_components/_components_utils.py index 2a5871d94..51b01dbd2 100644 --- a/vizro-core/src/vizro/models/_components/_components_utils.py +++ b/vizro-core/src/vizro/models/_components/_components_utils.py @@ -1,4 +1,5 @@ import logging +import uuid from functools import partial from vizro.managers import data_manager @@ -23,29 +24,30 @@ def _callable_mode_validator_factory(mode: str): return validator("figure", allow_reuse=True)(check_callable_mode) -def _process_callable_data_frame(captured_callable, values): +def _process_callable_data_frame(captured_callable): + # Possibly all this validator's functionality should move into CapturedCallable (or a subclass of it) in the + # future. This would mean that data is added to the data manager outside the context of a dashboard though, + # which might not be desirable. data_frame = captured_callable["data_frame"] - # Enable running e.g. px.scatter("iris") from the Python API and specification of "data_frame": "iris" through JSON. - # In these cases, data already exists in the data manager and just needs to be linked to the component. if isinstance(data_frame, str): - data_manager._add_component(values["id"], data_frame) + # Named data source, which could be dynamic or static. This means px.scatter("iris") from the Python API and + # specification of "data_frame": "iris" through JSON. In these cases, data already exists in the data manager. return captured_callable - # Standard case for px.scatter(df: pd.DataFrame). - # Extract dataframe from the captured function and put it into the data manager. - dataset_name = str(id(data_frame)) + # Unnamed data source, which must be a pd.DataFrame and hence static data. This means px.scatter(pd.DataFrame()) + # and is only possible from the Python API. Extract dataframe from the captured function and put it into the + # data manager. + # Unlike with model_manager, it doesn't matter if the random seed is different across workers here. So long as + # we always fetch static data from the data manager by going through the appropriate Figure component, the right + # data source name will be fetched. It also doesn't matter if multiple Figures with the same underlying data + # each have their own entry in the data manager, since the underlying pd.DataFrame will still be the same and + # not copied into each one, so no memory is wasted. + # Replace the "data_frame" argument in the captured callable with the data_source_name for consistency with + # dynamic data and to save memory. This way we always access data via the same interface regardless of whether it's + # static or dynamic. + data_source_name = str(uuid.uuid4()) + data_manager[data_source_name] = data_frame + captured_callable["data_frame"] = data_source_name - logger.debug("Adding data to data manager for Figure with id %s", values["id"]) - # If the dataset already exists in the data manager then it's not a problem, it just means that we don't need - # to duplicate it. Just log the exception for debugging purposes. - try: - data_manager[dataset_name] = data_frame - except ValueError as exc: - logger.debug(exc) - - data_manager._add_component(values["id"], dataset_name) - - # No need to keep the data in the captured function any more so remove it to save memory. - del captured_callable["data_frame"] return captured_callable diff --git a/vizro-core/src/vizro/models/_components/_form.py b/vizro-core/src/vizro/models/_components/_form.py index a5d96d396..248f66f80 100644 --- a/vizro-core/src/vizro/models/_components/_form.py +++ b/vizro-core/src/vizro/models/_components/_form.py @@ -52,4 +52,4 @@ def build(self): components_container = self.layout.build() for component_idx, component in enumerate(self.components): components_container[f"{self.layout.id}_{component_idx}"].children = component.build() - return html.Div(components_container, id=self.id) + return html.Div(id=self.id, children=components_container) diff --git a/vizro-core/src/vizro/models/_components/ag_grid.py b/vizro-core/src/vizro/models/_components/ag_grid.py index 0ec0c4e15..d7ad57817 100644 --- a/vizro-core/src/vizro/models/_components/ag_grid.py +++ b/vizro-core/src/vizro/models/_components/ag_grid.py @@ -51,7 +51,7 @@ class AgGrid(VizroBaseModel): # Convenience wrapper/syntactic sugar. def __call__(self, **kwargs): - kwargs.setdefault("data_frame", data_manager._get_component_data(self.id)) + kwargs.setdefault("data_frame", data_manager[self["data_frame"]].load()) figure = self.figure(**kwargs) figure.id = self._input_component_id return figure @@ -105,15 +105,17 @@ def build(self): ) return dcc.Loading( - [ + children=[ html.H3(self.title, className="table-title") if self.title else None, # The pagination setting (and potentially others) of the initially built AgGrid (in the build method # here) must have the same setting as the object that is built by the on-page-load mechanism using # with the user settings and rendered finally. Otherwise the grid is not rendered correctly. + # Additionally, we cannot remove the DF from the ag grid object before returning it (to save sending + # data over the network), because it breaks filter persistence settings on page change. # Hence be careful when editing the line below. - html.Div(self.__call__(data_frame=pd.DataFrame()), id=self.id, className="table-container"), + html.Div(self.__call__(), id=self.id, className="table-container"), ], - id=f"{self.id}_outer", color="grey", parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, ) diff --git a/vizro-core/src/vizro/models/_components/button.py b/vizro-core/src/vizro/models/_components/button.py index 016d0db02..1e6f47da8 100644 --- a/vizro-core/src/vizro/models/_components/button.py +++ b/vizro-core/src/vizro/models/_components/button.py @@ -1,7 +1,6 @@ from typing import List, Literal import dash_bootstrap_components as dbc -from dash import html try: from pydantic.v1 import Field @@ -32,8 +31,4 @@ class Button(VizroBaseModel): @_log_call def build(self): - return html.Div( - dbc.Button(id=self.id, children=self.text, className="button_primary"), - className="button_container", - id=f"{self.id}_outer", - ) + return dbc.Button(id=self.id, children=self.text) diff --git a/vizro-core/src/vizro/models/_components/card.py b/vizro-core/src/vizro/models/_components/card.py index 29c3e254c..fc2f93fe8 100644 --- a/vizro-core/src/vizro/models/_components/card.py +++ b/vizro-core/src/vizro/models/_components/card.py @@ -34,15 +34,15 @@ class Card(VizroBaseModel): @_log_call def build(self): - text = dcc.Markdown(self.text, className="card_text", dangerously_allow_html=False, id=self.id) + text = dcc.Markdown(id=self.id, children=self.text, dangerously_allow_html=False) card_content = ( dbc.NavLink( - text, + children=text, href=get_relative_path(self.href) if self.href.startswith("/") else self.href, - className="card-link", ) if self.href else text ) - card_class = "nav-card" if self.href else "card" - return dbc.Card(card_content, className=card_class, id=f"{self.id}_outer") + + card_class = "card-nav" if self.href else "" + return dbc.Card(children=card_content, className=card_class) diff --git a/vizro-core/src/vizro/models/_components/container.py b/vizro-core/src/vizro/models/_components/container.py index 7aff4ffe3..d42d62d8d 100644 --- a/vizro-core/src/vizro/models/_components/container.py +++ b/vizro-core/src/vizro/models/_components/container.py @@ -52,7 +52,7 @@ def build(self): for component_idx, component in enumerate(self.components): components_container[f"{self.layout.id}_{component_idx}"].children = component.build() return html.Div( - children=[html.H3(self.title, className="container__title"), components_container], - className="page-component-container", id=self.id, + children=[html.H3(children=self.title, className="container__title"), components_container], + className="page-component-container", ) diff --git a/vizro-core/src/vizro/models/_components/form/_alert.py b/vizro-core/src/vizro/models/_components/form/_alert.py index 1a0804a2f..4a1bf360d 100644 --- a/vizro-core/src/vizro/models/_components/form/_alert.py +++ b/vizro-core/src/vizro/models/_components/form/_alert.py @@ -33,7 +33,7 @@ class Alert(VizroBaseModel): @_log_call def build(self): return html.Div( - [ + children=[ dbc.Alert( id=self.id, children=[html.P(self.text)], diff --git a/vizro-core/src/vizro/models/_components/form/_text_area.py b/vizro-core/src/vizro/models/_components/form/_text_area.py index a0c8ddb5b..a359baa40 100644 --- a/vizro-core/src/vizro/models/_components/form/_text_area.py +++ b/vizro-core/src/vizro/models/_components/form/_text_area.py @@ -40,17 +40,14 @@ class TextArea(VizroBaseModel): @_log_call def build(self): return html.Div( - [ - html.Label(self.title, htmlFor=self.id) if self.title else None, + children=[ + dbc.Label(self.title, html_for=self.id) if self.title else None, dbc.Textarea( id=self.id, placeholder=self.placeholder, persistence=True, persistence_type="session", debounce=True, - className="text-area", ), ], - className="input-container", - id=f"{self.id}_outer", ) diff --git a/vizro-core/src/vizro/models/_components/form/_user_input.py b/vizro-core/src/vizro/models/_components/form/_user_input.py index c074c4985..a784bb2cd 100644 --- a/vizro-core/src/vizro/models/_components/form/_user_input.py +++ b/vizro-core/src/vizro/models/_components/form/_user_input.py @@ -40,8 +40,8 @@ class UserInput(VizroBaseModel): @_log_call def build(self): return html.Div( - [ - html.Label(self.title, htmlFor=self.id) if self.title else None, + children=[ + dbc.Label(children=self.title, html_for=self.id) if self.title else None, dbc.Input( id=self.id, placeholder=self.placeholder, @@ -49,9 +49,6 @@ def build(self): persistence=True, persistence_type="session", debounce=True, - className="user_input", ), ], - className="input-container", - id=f"{self.id}_outer", ) diff --git a/vizro-core/src/vizro/models/_components/form/checklist.py b/vizro-core/src/vizro/models/_components/form/checklist.py index e37180b6c..3279f940d 100644 --- a/vizro-core/src/vizro/models/_components/form/checklist.py +++ b/vizro-core/src/vizro/models/_components/form/checklist.py @@ -1,12 +1,14 @@ from typing import List, Literal, Optional -from dash import dcc, html +from dash import html try: from pydantic.v1 import Field, PrivateAttr, root_validator, validator except ImportError: # pragma: no cov from pydantic import Field, PrivateAttr, root_validator, validator +import dash_bootstrap_components as dbc + from vizro.models import Action, VizroBaseModel from vizro.models._action._actions_chain import _action_validator_factory from vizro.models._components.form._form_utils import get_options_and_default, validate_options_dict, validate_value @@ -48,18 +50,15 @@ class Checklist(VizroBaseModel): def build(self): full_options, default_value = get_options_and_default(options=self.options, multi=True) - return html.Div( - [ - html.Label(self.title, htmlFor=self.id) if self.title else None, - dcc.Checklist( + return html.Fieldset( + children=[ + html.Legend(children=self.title, className="form-label") if self.title else None, + dbc.Checklist( id=self.id, options=full_options, value=self.value if self.value is not None else [default_value], persistence=True, persistence_type="session", - className="checkboxes-list", ), - ], - className="input-container", - id=f"{self.id}_outer", + ] ) diff --git a/vizro-core/src/vizro/models/_components/form/date_picker.py b/vizro-core/src/vizro/models/_components/form/date_picker.py index bb70e9c47..fa494e1ed 100644 --- a/vizro-core/src/vizro/models/_components/form/date_picker.py +++ b/vizro-core/src/vizro/models/_components/form/date_picker.py @@ -12,6 +12,8 @@ import datetime from datetime import date +import dash_bootstrap_components as dbc + from vizro.models import Action, VizroBaseModel from vizro.models._action._actions_chain import _action_validator_factory from vizro.models._components.form._form_utils import validate_date_picker_range, validate_max, validate_range_value @@ -101,11 +103,10 @@ def build(self): ) return html.Div( - [ - html.Label(self.title, htmlFor=self.id) if self.title else None, + children=[ + dbc.Label(children=self.title, html_for=self.id) if self.title else None, date_picker, dcc.Store(id=f"{self.id}_input_store", storage_type="session", data=init_value), ], className="selector_container", - id=f"{self.id}_outer", ) diff --git a/vizro-core/src/vizro/models/_components/form/dropdown.py b/vizro-core/src/vizro/models/_components/form/dropdown.py index 1cf2f1099..0b9ea4c6b 100755 --- a/vizro-core/src/vizro/models/_components/form/dropdown.py +++ b/vizro-core/src/vizro/models/_components/form/dropdown.py @@ -7,6 +7,8 @@ except ImportError: # pragma: no cov from pydantic import Field, PrivateAttr, root_validator, validator +import dash_bootstrap_components as dbc + from vizro.models import Action, VizroBaseModel from vizro.models._action._actions_chain import _action_validator_factory from vizro.models._components.form._form_utils import get_options_and_default, validate_options_dict, validate_value @@ -61,8 +63,8 @@ def validate_multi(cls, multi, values): def build(self): full_options, default_value = get_options_and_default(options=self.options, multi=self.multi) return html.Div( - [ - html.Label(self.title, htmlFor=self.id) if self.title else None, + children=[ + dbc.Label(self.title, html_for=self.id) if self.title else None, dcc.Dropdown( id=self.id, options=full_options, @@ -70,9 +72,6 @@ def build(self): multi=self.multi, persistence=True, persistence_type="session", - className="selector_body_dropdown", ), - ], - className="input-container", - id=f"{self.id}_outer", + ] ) diff --git a/vizro-core/src/vizro/models/_components/form/radio_items.py b/vizro-core/src/vizro/models/_components/form/radio_items.py index 7e8f9a993..e91d53f51 100644 --- a/vizro-core/src/vizro/models/_components/form/radio_items.py +++ b/vizro-core/src/vizro/models/_components/form/radio_items.py @@ -1,12 +1,14 @@ from typing import List, Literal, Optional -from dash import dcc, html +from dash import html try: from pydantic.v1 import Field, PrivateAttr, root_validator, validator except ImportError: # pragma: no cov from pydantic import Field, PrivateAttr, root_validator, validator +import dash_bootstrap_components as dbc + from vizro.models import Action, VizroBaseModel from vizro.models._action._actions_chain import _action_validator_factory from vizro.models._components.form._form_utils import get_options_and_default, validate_options_dict, validate_value @@ -49,18 +51,15 @@ class RadioItems(VizroBaseModel): def build(self): full_options, default_value = get_options_and_default(options=self.options, multi=False) - return html.Div( - [ - html.Label(self.title, htmlFor=self.id) if self.title else None, - dcc.RadioItems( + return html.Fieldset( + children=[ + html.Legend(children=self.title, className="form-label") if self.title else None, + dbc.RadioItems( id=self.id, options=full_options, value=self.value if self.value is not None else default_value, persistence=True, persistence_type="session", - className="radio-items-list", ), - ], - className="input-container", - id=f"{self.id}_outer", + ] ) diff --git a/vizro-core/src/vizro/models/_components/form/range_slider.py b/vizro-core/src/vizro/models/_components/form/range_slider.py index 8f7277856..c1ef438da 100644 --- a/vizro-core/src/vizro/models/_components/form/range_slider.py +++ b/vizro-core/src/vizro/models/_components/form/range_slider.py @@ -7,6 +7,8 @@ except ImportError: # pragma: no cov from pydantic import Field, PrivateAttr, validator +import dash_bootstrap_components as dbc + from vizro.models import Action, VizroBaseModel from vizro.models._action._actions_chain import _action_validator_factory from vizro.models._components.form._form_utils import ( @@ -82,22 +84,12 @@ def build(self): ) return html.Div( - [ + children=[ dcc.Store(f"{self.id}_callback_data", data={"id": self.id, "min": self.min, "max": self.max}), html.Label(self.title, htmlFor=self.id) if self.title else None, html.Div( - [ - dcc.RangeSlider( - id=self.id, - min=self.min, - max=self.max, - step=self.step, - marks=self.marks, - value=init_value, - persistence=True, - persistence_type="session", - className="range_slider_control" if self.step else "range_slider_control_no_space", - ), + children=[ + dbc.Label(children=self.title, html_for=self.id) if self.title else None, html.Div( [ dcc.Input( @@ -136,7 +128,5 @@ def build(self): ], className="range_slider_inner_container", ), - ], - className="selector_container", - id=f"{self.id}_outer", + ] ) diff --git a/vizro-core/src/vizro/models/_components/form/slider.py b/vizro-core/src/vizro/models/_components/form/slider.py index 3675cb663..33dcc4057 100644 --- a/vizro-core/src/vizro/models/_components/form/slider.py +++ b/vizro-core/src/vizro/models/_components/form/slider.py @@ -7,6 +7,8 @@ except ImportError: # pragma: no cov from pydantic import Field, PrivateAttr, validator +import dash_bootstrap_components as dbc + from vizro.models import Action, VizroBaseModel from vizro.models._action._actions_chain import _action_validator_factory from vizro.models._components.form._form_utils import ( @@ -79,11 +81,11 @@ def build(self): ) return html.Div( - [ + children=[ dcc.Store(f"{self.id}_callback_data", data={"id": self.id, "min": self.min, "max": self.max}), html.Div( - [ - html.Label(self.title, htmlFor=self.id) if self.title else None, + children=[ + dbc.Label(children=self.title, html_for=self.id) if self.title else None, html.Div( [ dcc.Input( @@ -117,7 +119,5 @@ def build(self): persistence_type="session", className="slider-track-without-marks" if self.marks is None else "slider-track-with-marks", ), - ], - className="input-container", - id=f"{self.id}_outer", + ] ) diff --git a/vizro-core/src/vizro/models/_components/graph.py b/vizro-core/src/vizro/models/_components/graph.py index 64f83d93a..687e398a2 100644 --- a/vizro-core/src/vizro/models/_components/graph.py +++ b/vizro-core/src/vizro/models/_components/graph.py @@ -50,7 +50,10 @@ class Graph(VizroBaseModel): # Convenience wrapper/syntactic sugar. def __call__(self, **kwargs): - kwargs.setdefault("data_frame", data_manager._get_component_data(str(self.id))) + # This default value is not actually used anywhere at the moment since __call__ is always used with data_frame + # specified. It's here to match Table and AgGrid and because we might want to use __call__ more in future. + # If the functionality of process_callable_data_frame moves to CapturedCallable then this would move there too. + kwargs.setdefault("data_frame", data_manager[self["data_frame"]].load()) fig = self.figure(**kwargs) # Remove top margin if title is provided @@ -133,6 +136,7 @@ def build(self): ), color="grey", parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, ) @staticmethod diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 094f0ca0c..9cf7cded6 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -50,7 +50,7 @@ class Table(VizroBaseModel): # Convenience wrapper/syntactic sugar. def __call__(self, **kwargs): - kwargs.setdefault("data_frame", data_manager._get_component_data(self.id)) + kwargs.setdefault("data_frame", data_manager[self["data_frame"]].load()) figure = self.figure(**kwargs) figure.id = self._input_component_id return figure @@ -105,14 +105,17 @@ def pre_build(self): def build(self): return dcc.Loading( - html.Div( - [ + children=html.Div( + children=[ html.H3(self.title, className="table-title") if self.title else None, - html.Div(self.__call__(data_frame=pd.DataFrame()), id=self.id), + # Please see vm.AgGrid build method as to why we are returning the call with the full data here + # Most of the comments may not apply to the data table, but in order to be consistent, we are + # handling the build method in the exact same way here + html.Div(self.__call__(), id=self.id), ], className="table-container", - id=f"{self.id}_outer", ), color="grey", parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, ) diff --git a/vizro-core/src/vizro/models/_controls/filter.py b/vizro-core/src/vizro/models/_controls/filter.py index 26d808f5b..5ae2e0f13 100644 --- a/vizro-core/src/vizro/models/_controls/filter.py +++ b/vizro-core/src/vizro/models/_controls/filter.py @@ -112,14 +112,20 @@ def _set_targets(self): for component_id in model_manager._get_page_model_ids_with_figure( page_id=model_manager._get_model_page_id(model_id=ModelID(str(self.id))) ): - data_frame = data_manager._get_component_data(component_id) + # TODO: consider making a helper method in data_manager or elsewhere to reduce this operation being + # duplicated across Filter so much, and/or consider storing the result to avoid repeating it. + # Need to think about this in connection with how to update filters on the fly and duplicated calls + # issue outlined in https://github.com/mckinsey/vizro/pull/398#discussion_r1559120849. + data_source_name = model_manager[component_id]["data_frame"] + data_frame = data_manager[data_source_name].load() if self.column in data_frame.columns: self.targets.append(component_id) if not self.targets: raise ValueError(f"Selected column {self.column} not found in any dataframe on this page.") def _set_column_type(self): - data_frame = data_manager._get_component_data(self.targets[0]) + data_source_name = model_manager[self.targets[0]]["data_frame"] + data_frame = data_manager[data_source_name].load() if is_numeric_dtype(data_frame[self.column]): self._column_type = "numerical" @@ -146,7 +152,8 @@ def _set_numerical_and_temporal_selectors_values(self): min_values = [] max_values = [] for target_id in self.targets: - data_frame = data_manager._get_component_data(target_id) + data_source_name = model_manager[target_id]["data_frame"] + data_frame = data_manager[data_source_name].load() min_values.append(data_frame[self.column].min()) max_values.append(data_frame[self.column].max()) @@ -173,7 +180,8 @@ def _set_categorical_selectors_options(self): if isinstance(self.selector, SELECTORS["categorical"]) and not self.selector.options: options = set() for target_id in self.targets: - data_frame = data_manager._get_component_data(target_id) + data_source_name = model_manager[target_id]["data_frame"] + data_frame = data_manager[data_source_name].load() options |= set(data_frame[self.column]) self.selector.options = sorted(options) @@ -189,7 +197,7 @@ def _set_actions(self): self.selector.actions = [ Action( - function=_filter(filter_column=self.column, targets=self.targets, filter_function=filter_function), id=f"{FILTER_ACTION_PREFIX}_{self.id}", + function=_filter(filter_column=self.column, targets=self.targets, filter_function=filter_function), ) ] diff --git a/vizro-core/src/vizro/models/_controls/parameter.py b/vizro-core/src/vizro/models/_controls/parameter.py index 51a97b884..748a669b0 100644 --- a/vizro-core/src/vizro/models/_controls/parameter.py +++ b/vizro-core/src/vizro/models/_controls/parameter.py @@ -18,8 +18,7 @@ class Parameter(VizroBaseModel): """Alter the arguments supplied to any `targets` on the [`Page`][vizro.models.Page]. Examples: - >>> print(repr(Parameter( - >>> targets=["scatter.x"], selector=Slider(min=0, max=1, default=0.8, title="Bubble opacity")))) + >>> Parameter(targets=["scatter.x"], selector=Slider(min=0, max=1, default=0.8, title="Bubble opacity")) Args: type (Literal["parameter"]): Defaults to `"parameter"`. @@ -37,8 +36,7 @@ class Parameter(VizroBaseModel): def check_dot_notation(cls, target): if "." not in target: raise ValueError( - f"Invalid target {target}. Targets must be supplied in the from of " - "." + f"Invalid target {target}. Targets must be supplied in the form ." ) return target @@ -49,6 +47,16 @@ def check_target_present(cls, target): raise ValueError(f"Target {target_id} not found in model_manager.") return target + @validator("targets", each_item=True) + def check_data_frame_as_target_argument(cls, target): + targeted_argument = target.split(".", 1)[1] + if targeted_argument.startswith("data_frame") and targeted_argument.count(".") != 1: + raise ValueError( + f"Invalid target {target}. 'data_frame' target must be supplied in the form " + ".data_frame." + ) + return target + @validator("targets") def check_duplicate_parameter_target(cls, targets): all_targets = targets.copy() diff --git a/vizro-core/src/vizro/models/_dashboard.py b/vizro-core/src/vizro/models/_dashboard.py index 1f58fba67..ea65d5ebf 100644 --- a/vizro-core/src/vizro/models/_dashboard.py +++ b/vizro-core/src/vizro/models/_dashboard.py @@ -53,9 +53,9 @@ def _all_hidden(components: List[Component]): "page-title": html.H2, "nav-bar": dbc.Navbar, "nav-panel": dbc.Nav, + "logo": html.Div, "control-panel": html.Div, "page-components": html.Div, - "logo": html.Div, }, ) @@ -152,10 +152,12 @@ def build(self): def _get_page_divs(self, page: Page) -> _PageDivsType: # Identical across pages dashboard_title = ( - html.H2(self.title, id="dashboard-title") if self.title else html.H2(hidden=True, id="dashboard-title") + html.H2(id="dashboard-title", children=self.title) + if self.title + else html.H2(id="dashboard-title", hidden=True) ) settings = html.Div( - dmc.Switch( + children=dmc.Switch( id="theme_selector", checked=self.theme == "vizro_light", persistence=True, @@ -166,11 +168,11 @@ def _get_page_divs(self, page: Page) -> _PageDivsType: ) logo_img = self._infer_image(filename="logo") path_to_logo = get_asset_url(logo_img) if logo_img else None - logo = html.Img(src=path_to_logo, id="logo", hidden=not path_to_logo) + logo = html.Img(id="logo", src=path_to_logo, hidden=not path_to_logo) # Shared across pages but slightly differ in content. These could possibly be done by a clientside # callback instead. - page_title = html.H2(page.title, id="page-title") + page_title = html.H2(id="page-title", children=page.title) navigation: _NavBuildType = self.navigation.build(active_page_id=page.id) nav_bar = navigation["nav-bar"] nav_panel = navigation["nav-panel"] @@ -179,13 +181,14 @@ def _get_page_divs(self, page: Page) -> _PageDivsType: page_content: _PageBuildType = page.build() control_panel = page_content["control-panel"] page_components = page_content["page-components"] + return html.Div( - [dashboard_title, settings, page_title, nav_bar, nav_panel, control_panel, page_components, logo] + [dashboard_title, settings, page_title, nav_bar, nav_panel, logo, control_panel, page_components] ) def _arrange_page_divs(self, page_divs: _PageDivsType): logo_title = [page_divs["logo"], page_divs["dashboard-title"]] - page_header_divs = [html.Div(logo_title, id="logo-and-title", hidden=_all_hidden(logo_title))] + page_header_divs = [html.Div(id="logo-and-title", children=logo_title, hidden=_all_hidden(logo_title))] left_sidebar_divs = [page_divs["nav-bar"]] left_main_divs = [page_divs["nav-panel"], page_divs["control-panel"]] right_header_divs = [page_divs["page-title"]] @@ -198,14 +201,15 @@ def _arrange_page_divs(self, page_divs: _PageDivsType): collapsable_icon = ( html.Div( - [ - html.Span("keyboard_double_arrow_left", className="material-symbols-outlined", id="collapse-icon"), + children=[ + html.Span( + id="collapse-icon", children="keyboard_double_arrow_left", className="material-symbols-outlined" + ), dbc.Tooltip( - "Hide Menu", id="collapse-tooltip", + children="Hide Menu", placement="right", target="collapse-icon", - className="collapse-button-tooltip", ), ], className="collapse-icon-div", @@ -214,23 +218,27 @@ def _arrange_page_divs(self, page_divs: _PageDivsType): else None ) - left_sidebar = html.Div(left_sidebar_divs, id="left-sidebar", hidden=_all_hidden(left_sidebar_divs)) - left_main = html.Div(left_main_divs, id="left-main", hidden=_all_hidden(left_main_divs)) - left_side = html.Div([left_sidebar, left_main], id="left-side") + left_sidebar = html.Div(id="left-sidebar", children=left_sidebar_divs, hidden=_all_hidden(left_sidebar_divs)) + left_main = html.Div(id="left-main", children=left_main_divs, hidden=_all_hidden(left_main_divs)) + left_side = html.Div(id="left-side", children=[left_sidebar, left_main]) - collapsable_left_side = dbc.Collapse(left_side, id="collapsable-left-side", is_open=True, dimension="width") + collapsable_left_side = dbc.Collapse( + id="collapsable-left-side", children=left_side, is_open=True, dimension="width" + ) - right_header = html.Div(right_header_divs, id="right-header") + right_header = html.Div(id="right-header", children=right_header_divs) right_main = page_divs["page-components"] - right_side = html.Div([right_header, right_main], id="right-side") + right_side = html.Div(id="right-side", children=[right_header, right_main]) - page_header = html.Div(page_header_divs, id="page-header", hidden=_all_hidden(page_header_divs)) - page_main = html.Div([collapsable_left_side, collapsable_icon, right_side], id="page-main") - return html.Div([page_header, page_main], id="page-container") + page_header = html.Div(id="page-header", children=page_header_divs, hidden=_all_hidden(page_header_divs)) + page_main = html.Div(id="page-main", children=[collapsable_left_side, collapsable_icon, right_side]) + return html.Div(children=[page_header, page_main], className="page-container") def _make_page_layout(self, page: Page): page_divs = self._get_page_divs(page=page) - return self._arrange_page_divs(page_divs=page_divs) + page_layout = self._arrange_page_divs(page_divs=page_divs) + page_layout.id = page.id + return page_layout @staticmethod def _make_page_404_layout(): @@ -240,13 +248,13 @@ def _make_page_404_layout(): html.Div( [ html.Div( - [ + children=[ html.H3("This page could not be found.", className="heading-3-600"), html.P("Make sure the URL you entered is correct."), ], className="error_text_container", ), - dbc.Button("Take me home", href=get_relative_path("/"), className="button_primary"), + dbc.Button(children="Take me home", href=get_relative_path("/")), ], className="error_content_container", ), diff --git a/vizro-core/src/vizro/models/_layout.py b/vizro-core/src/vizro/models/_layout.py index a4c9bdb21..2249f355a 100644 --- a/vizro-core/src/vizro/models/_layout.py +++ b/vizro-core/src/vizro/models/_layout.py @@ -13,7 +13,7 @@ from vizro.models import VizroBaseModel from vizro.models._models_utils import _log_call -GAP_DEFAULT = "12px" +GAP_DEFAULT = "24px" MIN_DEFAULT = "0px" @@ -204,6 +204,8 @@ def build(self): style={ "gridColumn": f"{grid_coord.col_start}/{grid_coord.col_end}", "gridRow": f"{grid_coord.row_start}/{grid_coord.row_end}", + "height": "100%", + "width": "100%", }, ) for component_idx, grid_coord in enumerate(self.component_grid_lines) diff --git a/vizro-core/src/vizro/models/_navigation/nav_bar.py b/vizro-core/src/vizro/models/_navigation/nav_bar.py index 64e9c0c9d..0f41b94e3 100644 --- a/vizro-core/src/vizro/models/_navigation/nav_bar.py +++ b/vizro-core/src/vizro/models/_navigation/nav_bar.py @@ -3,6 +3,7 @@ from collections.abc import Mapping from typing import Dict, List, Literal +import dash_bootstrap_components as dbc from dash import html try: @@ -10,7 +11,6 @@ except ImportError: # pragma: no cov from pydantic import Field, validator -import dash_bootstrap_components as dbc from vizro.models import VizroBaseModel from vizro.models._models_utils import _log_call @@ -75,4 +75,6 @@ def build(self, *, active_page_id=None) -> _NavBuildType: # Active page is not in navigation at all, so hide navigation panel. nav_panel = dbc.Nav(id="nav-panel", className="d-none invisible") - return html.Div([dbc.Navbar(nav_links, id="nav-bar"), nav_panel]) + # `flex-column` ensures that we return a vertical NavBar. In the future, we could use that className + # to create a horizontal NavBar. + return html.Div(children=[dbc.Navbar(id="nav-bar", children=nav_links, className="flex-column"), nav_panel]) diff --git a/vizro-core/src/vizro/models/_navigation/nav_link.py b/vizro-core/src/vizro/models/_navigation/nav_link.py index 0041a188e..2177bd078 100644 --- a/vizro-core/src/vizro/models/_navigation/nav_link.py +++ b/vizro-core/src/vizro/models/_navigation/nav_link.py @@ -67,7 +67,6 @@ def build(self, *, active_page_id=None): ), ], id=self.id, - className="nav-bar-icon-link", href=get_relative_path(first_page.path), # `active` is required to keep the icon highlighted when navigating through different pages inside # the nested accordion diff --git a/vizro-core/src/vizro/models/_navigation/navigation.py b/vizro-core/src/vizro/models/_navigation/navigation.py index b50fa192a..2bfd2b549 100644 --- a/vizro-core/src/vizro/models/_navigation/navigation.py +++ b/vizro-core/src/vizro/models/_navigation/navigation.py @@ -49,6 +49,6 @@ def build(self, *, active_page_id=None) -> _NavBuildType: # e.g. nav_selector is Accordion and nav_selector.build returns single html.Div with id="nav-panel". # This will make it match the case e.g. nav_selector is NavBar and nav_selector.build returns html.Div # containing children with id="nav-bar" and id="nav-panel" - nav_selector = html.Div([dbc.Navbar(className="d-none invisible", id="nav-bar"), nav_selector]) + nav_selector = html.Div(children=[dbc.Navbar(id="nav-bar", className="d-none invisible"), nav_selector]) return nav_selector diff --git a/vizro-core/src/vizro/models/_page.py b/vizro-core/src/vizro/models/_page.py index 38340f983..9fc6340c5 100644 --- a/vizro-core/src/vizro/models/_page.py +++ b/vizro-core/src/vizro/models/_page.py @@ -110,7 +110,7 @@ def pre_build(self): def build(self) -> _PageBuildType: self._update_graph_theme() controls_content = [control.build() for control in self.controls] - control_panel = html.Div(children=controls_content, id="control-panel", hidden=not controls_content) + control_panel = html.Div(id="control-panel", children=controls_content, hidden=not controls_content) components_container = self.layout.build() for component_idx, component in enumerate(self.components): @@ -119,7 +119,7 @@ def build(self) -> _PageBuildType: # Page specific CSS ID and Stores components_container.children.append(dcc.Store(id=f"{ON_PAGE_LOAD_ACTION_PREFIX}_trigger_{self.id}")) components_container.id = "page-components" - return html.Div([control_panel, components_container], id=self.id) + return html.Div([control_panel, components_container]) def _update_graph_theme(self): # The obvious way to do this would be to alter pio.templates.default, but this changes global state and so is diff --git a/vizro-core/src/vizro/models/types.py b/vizro-core/src/vizro/models/types.py index a3e809666..41e9f0657 100644 --- a/vizro-core/src/vizro/models/types.py +++ b/vizro-core/src/vizro/models/types.py @@ -22,7 +22,7 @@ from vizro.charts._charts_utils import _DashboardReadyFigure -# Used to describe _DashboardReadyFigure so we can keep CapturedCallable generic rather than referring to +# Used to describe _DashboardReadyFigure, so we can keep CapturedCallable generic rather than referring to # _DashboardReadyFigure explicitly. @runtime_checkable class _SupportsCapturedCallable(Protocol): @@ -138,9 +138,9 @@ def __getitem__(self, arg_name: str): """Gets the value of a bound argument.""" return self.__bound_arguments[arg_name] - def __delitem__(self, arg_name: str): - """Deletes a bound argument.""" - del self.__bound_arguments[arg_name] + def __setitem__(self, arg_name: str, value): + """Sets the value of a bound argument.""" + self.__bound_arguments[arg_name] = value @property def _arguments(self): @@ -282,10 +282,8 @@ def wrapped(*args, **kwargs) -> _DashboardReadyFigure: if isinstance(captured_callable["data_frame"], str): # Enable running e.g. px.scatter("iris") from the Python API. Don't actually run the function - # because it won't get work as there's no data. It's vital we don't fetch data from the data manager - # yet either, because otherwise all lazy data will be loaded before the dashboard is started. - # This case is not relevant for the JSON/YAML API, which is handled separately through validation of - # CapturedCallable. + # because it won't work as there's no data. This case is not relevant for the JSON/YAML API, + # which is handled separately through validation of CapturedCallable. fig = _DashboardReadyFigure() else: # Standard case for px.scatter(df: pd.DataFrame). diff --git a/vizro-core/src/vizro/static/css/accordion.css b/vizro-core/src/vizro/static/css/accordion.css deleted file mode 100644 index 6ec9d3a56..000000000 --- a/vizro-core/src/vizro/static/css/accordion.css +++ /dev/null @@ -1,116 +0,0 @@ -#page-container .accordion-button { - align-items: center; - background-color: inherit; - background-image: none; - border-radius: 0; - box-shadow: none; - color: var(--text-secondary); - display: flex; - font-size: var(--text-size-02); - height: 56px; - letter-spacing: -0.014px; - line-height: var(--text-size-03); - overflow-anchor: none; - padding: 16px 8px; - position: relative; - text-align: left; - width: 100%; -} - -#page-container .accordion-button:not(.collapsed) { - color: var(--text-primary); -} - -#page-container .accordion-button::after { - background-image: none; - background-repeat: no-repeat; - content: "\2304"; - flex-shrink: 0; - float: left !important; - font-size: 16px; - font-weight: 600; - height: 24px; - margin-left: auto; - transform: scaleX(1.3); - transform-origin: center center; - transition: transform 0.3s ease-in-out; - width: auto; -} - -#page-container .accordion-button:not(.collapsed)::after { - background-position: left; - font-size: 16px; - font-weight: 600; - transform: scaleX(1.3) rotate(180deg); -} - -#page-container .accordion-header { - font-size: var(--text-size-02); -} - -#page-container .accordion { - display: flex; - flex-direction: column; - margin-top: -10px; - overflow-x: hidden; -} - -#page-container .accordion-item-header { - background-color: inherit; - border: none; - border-bottom: 1px solid var(--border-subtle-alpha-01); - display: flex; - flex-direction: column; - gap: var(--spacing-01); - width: 100%; -} - -#page-container .accordion-item-header:last-child { - border-bottom: none; -} - -#page-container .accordion-body { - align-items: start; - display: flex; - flex-direction: column; - gap: var(--spacing-01); - padding: 0; - width: 100%; -} - -.accordion-item-link { - align-items: center; - background: transparent; - border: none; - border-radius: 0; - color: var(--text-secondary); - display: flex; - font-size: var(--text-size-02); - height: 40px; - line-height: var(--text-size-03); - padding: var(--spacing-02); - text-transform: none; - width: 100%; -} - -.accordion-item-link.active { - background-color: var(--state-overlays-selected); - color: var(--text-primary); -} - -.accordion-item-link:hover { - background-color: var(--state-overlays-selected-hover); - color: var(--text-primary); -} - -#page-container .accordion-button:hover { - color: var(--text-primary); -} - -.material-symbols-outlined { - color: var(--text-secondary); -} - -.accordion-body .accordion-item-link:last-child { - margin-bottom: var(--spacing-03); -} diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 3bec7dcc0..81d463b9b 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -27,20 +27,20 @@ } /* Header ------- */ -#page-container .ag-header-row { +#dashboard-container .ag-header-row { align-items: flex-start; border-bottom: 1px solid var(--border-subtle-alpha-02); display: flex; } -#page-container .ag-header-cell { +#dashboard-container .ag-header-cell { align-items: center; display: flex; height: 40px; padding: 0 var(--spacing-03); } -#page-container .ag-header-cell-text { +#dashboard-container .ag-header-cell-text { letter-spacing: -0.112px; line-height: var(--spacing-04); overflow: hidden; @@ -48,32 +48,32 @@ white-space: nowrap; } -#page-container .ag-header-cell:hover { +#dashboard-container .ag-header-cell:hover { border-bottom: 1px solid var(--border-hover); color: var(--text-primary); } /* Rows ------- */ -#page-container .ag-cell { +#dashboard-container .ag-cell { padding: 0 var(--spacing-03); } -#page-container .ag-cell-focus { +#dashboard-container .ag-cell-focus { border: none; } -#page-container .ag-cell-focus:not(.ag-cell-range-selected):focus-within { +#dashboard-container .ag-cell-focus:not(.ag-cell-range-selected):focus-within { background: var(--state-overlays-selected); } /* Pop up menu ----- */ -#page-container .ag-menu { +#dashboard-container .ag-menu { background-color: var(--surfaces-bg-02); border: 1px solid var(--border-subtle-alpha-02); color: var(--text-primary); } -#page-container .ag-ltr .ag-filter-filter input { +#dashboard-container .ag-ltr .ag-filter-filter input { background-color: var(--field-enabled); box-shadow: var(--box-shadow-elevation-0); display: flex; @@ -85,19 +85,19 @@ } /* Filter fields and pagination */ -#page-container .ag-picker-field-wrapper { +#dashboard-container .ag-picker-field-wrapper { border: none; border-radius: 0; box-shadow: none; font-size: var(--text-size-02); } -#page-container .ag-filter-select .ag-picker-field-wrapper { +#dashboard-container .ag-filter-select .ag-picker-field-wrapper { background-color: var(--field-enabled); box-shadow: var(--box-shadow-elevation-0); } -#page-container .ag-select-list { +#dashboard-container .ag-select-list { background-color: var(--field-enabled); box-shadow: var(--box-shadow-elevation-0); color: var(--text-primary); @@ -105,34 +105,34 @@ line-height: var(--text-size-05); } -#page-container .ag-select-list-item.ag-active-item { +#dashboard-container .ag-select-list-item.ag-active-item { background-color: var(--state-overlays-hover); } -#page-container .ag-radio-button-input-wrapper { +#dashboard-container .ag-radio-button-input-wrapper { background-color: var(--field-enabled); } -#page-container .ag-radio-button-input-wrapper:focus-within { +#dashboard-container .ag-radio-button-input-wrapper:focus-within { box-shadow: none; } -#page-container .ag-text-field-input::placeholder { +#dashboard-container .ag-text-field-input::placeholder { color: var(--text-secondary); } -#page-container .ag-filter-apply-panel { +#dashboard-container .ag-filter-apply-panel { justify-content: flex-start; padding: var(--spacing-02) 0 var(--spacing-03) 0; } -#page-container .ag-filter-condition { +#dashboard-container .ag-filter-condition { justify-content: flex-start; padding: var(--spacing-02) var(--spacing-01); } /* Scroll Bar */ -#page-container .ag-body-horizontal-scroll-viewport { +#dashboard-container .ag-body-horizontal-scroll-viewport { height: 4px !important; max-height: 4px !important; min-height: 4px !important; @@ -146,11 +146,39 @@ } /* Floating Filter */ -#page-container .ag-floating-filter-input { +#dashboard-container .ag-floating-filter-input { color: var(--text-primary); height: 40px; } -#page-container .ag-floating-filter-input input[class^="ag-"][type="text"] { +#dashboard-container + .ag-floating-filter-input + input[class^="ag-"][type="text"] { padding-left: 0; } + +/* Buttons */ +#dashboard-container .ag-standard-button { + background: var(--fill-active); + border: none; + border-radius: 0; + box-shadow: var(--box-shadow-elevation-0); + color: var(--text-contrast-primary); + font-size: var(--text-size-02); + font-weight: var(--text-weight-semibold); + height: 32px; + letter-spacing: var(--letter-spacing-body-link-02); + line-height: var(--text-size-05); + padding: var(--spacing-01) var(--spacing-03); + text-transform: none; +} + +#dashboard-container .ag-standard-button:hover { + background: linear-gradient( + var(--state-overlays-contrast-hover), + var(--state-overlays-contrast-hover) + ), + var(--fill-active); + color: var(--text-contrast-primary); + text-decoration-line: underline; +} diff --git a/vizro-core/src/vizro/static/css/bootstrap_overwrites.css b/vizro-core/src/vizro/static/css/bootstrap_overwrites.css new file mode 100644 index 000000000..f69a5728b --- /dev/null +++ b/vizro-core/src/vizro/static/css/bootstrap_overwrites.css @@ -0,0 +1,23 @@ +/* This file contains overwrites, which we want to have as defaults for vizro +but do not want to take over to `vizro-bootstrap` as these settings might not be generic enough. */ + +.card .nav-link { + height: 100%; +} + +.form-check:last-of-type { + margin-bottom: 0; +} + +/* TO DO: Check if this should live in vizro-bootstrap */ +.card p:last-of-type { + margin-bottom: 0; +} + +.accordion-item .nav-link { + padding: 0.5rem 1rem; +} + +.accordion-item .nav-link.active { + border-left: 2px solid var(--border-enabled); +} diff --git a/vizro-core/src/vizro/static/css/button.css b/vizro-core/src/vizro/static/css/button.css deleted file mode 100644 index aa7b6abb4..000000000 --- a/vizro-core/src/vizro/static/css/button.css +++ /dev/null @@ -1,43 +0,0 @@ -#dashboard-container .button_primary, -#dashboard-container .ag-standard-button { - background: var(--fill-active); - border: none; - border-radius: 0; - box-shadow: var(--box-shadow-elevation-0); - color: var(--text-contrast-primary); - font-size: var(--text-size-02); - font-weight: var(--text-weight-semibold); - height: 32px; - letter-spacing: var(--letter-spacing-body-link-02); - line-height: var(--text-size-05); - padding: var(--spacing-01) var(--spacing-03); - text-transform: none; -} - -#dashboard-container .button_primary:hover, -#dashboard-container .ag-standard-button:hover { - background: linear-gradient( - var(--state-overlays-contrast-hover), - var(--state-overlays-contrast-hover) - ), - var(--fill-active); - color: var(--text-contrast-primary); - text-decoration-line: underline; -} - -#dashboard-container .button_primary:active { - background: var(--fill-active); - color: var(--text-contrast-primary); -} - -#dashboard-container .button_primary:visited { - background: var(--fill-active); - color: var(--text-contrast-primary); -} - -.button_container { - align-items: flex-start; - display: flex; - flex-direction: column; - justify-content: center; -} diff --git a/vizro-core/src/vizro/static/css/card.css b/vizro-core/src/vizro/static/css/card.css deleted file mode 100644 index be7afbf52..000000000 --- a/vizro-core/src/vizro/static/css/card.css +++ /dev/null @@ -1,48 +0,0 @@ -.nav-card, -.card { - background-color: var(--surfaces-bg-card); - border: none; - border-radius: 0; - box-shadow: var(--box-shadow-elevation-card); - display: flex; - flex-direction: column; - height: 100%; - overflow: auto; -} - -.nav-card:hover { - background-color: var(--field-enabled); - box-shadow: var(--box-shadow-elevation-card-hover); - transform: translate3d(0, -8px, 0); - transition: - transform 0.3s, - box-shadow 0.2s; - will-change: transform; -} - -.card-link { - color: transparent; - display: flex; - height: 100%; - left: 0; - position: relative; - top: 0; - width: 100%; -} - -.card_text { - height: 100%; - padding: var(--spacing-04); - width: 100%; -} - -.card_text h1, -h2, -h3, -h4, -h5, -h6, -p, -ul { - margin-bottom: var(--spacing-02); -} diff --git a/vizro-core/src/vizro/static/css/checklist.css b/vizro-core/src/vizro/static/css/checklist.css deleted file mode 100644 index 9212769b6..000000000 --- a/vizro-core/src/vizro/static/css/checklist.css +++ /dev/null @@ -1,27 +0,0 @@ -input[type="checkbox" i] { - align-items: center; - appearance: none; - border: 1px solid var(--text-secondary); - display: inline-flex; - height: 16px; - margin: var(--spacing-01); - width: 16px; -} - -input[type="checkbox"]:checked::before { - color: var(--text-primary); - content: "✓"; - display: flex; - justify-content: center; - margin-bottom: 1px; - margin-left: 1px; -} - -input[type="checkbox"]:hover { - border: 1px solid var(--text-primary); -} - -input[type="checkbox"]:focus { - border: 1px solid var(--text-primary); - box-shadow: 0 0 0 2px var(--text-secondary); -} diff --git a/vizro-core/src/vizro/static/css/dropdown.css b/vizro-core/src/vizro/static/css/dropdown.css index 8cb919200..25d8ef943 100644 --- a/vizro-core/src/vizro/static/css/dropdown.css +++ b/vizro-core/src/vizro/static/css/dropdown.css @@ -1,5 +1,5 @@ /* Outer layout for dash dropdown */ -#page-container .dash-dropdown { +#dashboard-container .dash-dropdown { background-color: var(--field-enabled); border: none; border-radius: 0; @@ -8,7 +8,7 @@ } /* Dropdown menu when clicking on expand arrow */ -#page-container .Select-menu-outer { +#dashboard-container .Select-menu-outer { background-color: var(--field-enabled); border: none; border-radius: 0; @@ -18,30 +18,30 @@ } /* Dropdown menu options */ -#page-container .VirtualizedSelectOption { +#dashboard-container .VirtualizedSelectOption { color: var(--text-primary); font-weight: var(--text-weight-regular); } /* Dropdown menu hover effect */ -#page-container .VirtualizedSelectFocusedOption { +#dashboard-container .VirtualizedSelectFocusedOption { background-color: var(--state-overlays-hover); } /* Input box for existing values and user input */ -#page-container .Select-control { +#dashboard-container .Select-control { background-color: inherit; border: inherit; border-radius: inherit; height: 32px; } -#page-container .Select.is-focused > .Select-control { +#dashboard-container .Select.is-focused > .Select-control { background-color: var(--field-enabled); } /* User input */ -#page-container .dash-dropdown .Select-input { +#dashboard-container .dash-dropdown .Select-input { display: block; height: var(--tag-height); margin-left: unset; @@ -51,18 +51,18 @@ padding: 0 !important; /* Required so tags don't jump caused by adding table */ } -#page-container.Select-input > input { +#dashboard-container.Select-input > input { height: 100%; padding: 0; } /* Border on focus */ -#page-container .is-focused:not(.is-open) > .Select-control { +#dashboard-container .is-focused:not(.is-open) > .Select-control { box-shadow: 0 0 0 2px var(--focus-focus) inset; } /* Single-select dropdown only ------------------- */ -#page-container .Select--single .Select-value { +#dashboard-container .Select--single .Select-value { padding-left: var(--spacing-02); } @@ -77,7 +77,7 @@ } /* Tags --------------------------- */ -#page-container .Select--multi .Select-value { +#dashboard-container .Select--multi .Select-value { background-color: var(--tags-bg-color); border: 0; border-radius: 0; @@ -93,14 +93,14 @@ } /* Tag: Label */ -#page-container .Select--multi .Select-value-label { +#dashboard-container .Select--multi .Select-value-label { color: var(--text-primary); line-height: var(--text-size-05); padding: 0 4px; } /* Tag: Cross */ -#page-container .Select--multi .Select-value-icon { +#dashboard-container .Select--multi .Select-value-icon { background: inherit; border: none; color: var(--text-secondary); @@ -110,7 +110,7 @@ } /* Tag: Cross on hover */ -#page-container .Select--multi .Select-value-icon:hover { +#dashboard-container .Select--multi .Select-value-icon:hover { background: inherit; color: var(--text-secondary); } @@ -121,13 +121,8 @@ width: 16px; } -#nav-panel a { - font-weight: var(--text-weight-regular); - line-height: var(--text-size-07); -} - /* Page Selector Dropdown */ -#page-container +#dashboard-container .dash-dropdown .Select-menu-outer .VirtualizedSelectSelectedOption @@ -135,12 +130,16 @@ pointer-events: none; } -#page-container .dash-dropdown .Select-menu-outer .VirtualizedSelectOption > a { +#dashboard-container + .dash-dropdown + .Select-menu-outer + .VirtualizedSelectOption + > a { height: 100%; width: 100%; } -#page-container .Select--multi .Select-clear-zone { +#dashboard-container .Select--multi .Select-clear-zone { color: var(--text-secondary); width: 16px; } diff --git a/vizro-core/src/vizro/static/css/layout.css b/vizro-core/src/vizro/static/css/layout.css index 641eb1bae..3f2ef4a45 100644 --- a/vizro-core/src/vizro/static/css/layout.css +++ b/vizro-core/src/vizro/static/css/layout.css @@ -1,4 +1,4 @@ -#page-container { +.page-container { display: flex; flex-direction: column; height: 100vh; @@ -12,8 +12,8 @@ flex-direction: column; gap: var(--spacing-06); overflow: auto; - padding: 32px 32px 0; - width: 352px; + padding: 24px 24px 0; + width: 324px; } #left-side { @@ -27,8 +27,8 @@ background: var(--main-container-bg-color); display: flex; flex-direction: column; - gap: var(--spacing-04); - padding: 32px; + gap: var(--spacing-05); + padding: 24px; width: 100%; } @@ -45,10 +45,10 @@ background-color: var(--surfaces-bg-02); display: flex; flex-direction: row; - height: 64px; + height: 60px; justify-content: space-between; min-height: 0; - padding: 0 32px; + padding: 0 24px; width: 100%; } @@ -58,7 +58,7 @@ #page-components { overflow: auto; - padding-top: var(--spacing-02); + padding-top: var(--spacing-01); } .grid-layout { @@ -68,15 +68,10 @@ } #nav-panel { - display: flex; - flex-direction: column; + margin-top: -4px; width: 100%; } -#nav-panel:not(:empty) { - border-bottom: 1px solid var(--border-subtle-alpha-01); -} - #control-panel { align-self: stretch; display: flex; @@ -122,29 +117,6 @@ width: 100%; } -#nav-bar { - align-items: center; - background: inherit !important; - display: inline-flex; - flex-direction: column; - padding: 0; -} - -.nav-bar-icon-link { - align-items: center; - background: transparent; - border: none; - box-shadow: none; - display: flex; - height: 80px; - justify-content: center; - width: 100%; -} - -.nav-bar-icon-link.active .material-symbols-outlined { - color: var(--text-active); -} - .loading-container { height: 100%; width: 100%; @@ -155,11 +127,10 @@ display: flex; flex-direction: column; gap: 40px; - padding-top: 10px; - width: 80px; + width: 64px; } -#page-container div[hidden] { +#dashboard-container div[hidden] { display: none; } @@ -171,19 +142,20 @@ overflow: auto; } -#dashboard-title { +#dashboard-title, +#page-title { margin: 0; } #logo { - height: 100%; + height: 48px; } #logo-and-title { align-items: center; display: flex; flex-direction: row; - gap: 16px; + gap: 8px; height: 100%; } @@ -199,17 +171,10 @@ color: var(--text-active); } -.collapse-button-tooltip { - height: 24px; - left: -12px; - position: relative; - top: 36px; -} - .collapse-icon-div { display: flex; justify-content: center; - padding-top: 36px; + padding-top: 28px; width: 0; } @@ -225,12 +190,8 @@ flex-direction: column; } - .collapse-button-tooltip { - top: 28px; - } - .collapse-icon-div { - padding-top: 28px; + padding-top: 20px; } #page-header { @@ -247,12 +208,8 @@ } @media (height <= 576px) { - .collapse-button-tooltip { - top: 28px; - } - .collapse-icon-div { - padding-top: 28px; + padding-top: 20px; } #page-header { diff --git a/vizro-core/src/vizro/static/css/radio_item.css b/vizro-core/src/vizro/static/css/radio_item.css deleted file mode 100644 index 4f7d044b0..000000000 --- a/vizro-core/src/vizro/static/css/radio_item.css +++ /dev/null @@ -1,27 +0,0 @@ -input[type="radio" i] { - appearance: none; - border: 1px solid var(--text-secondary); - border-radius: 50%; - height: 16px; - margin: var(--spacing-01); - width: 16px; -} - -input[type="radio"]:hover { - border: 1px solid var(--text-primary); -} - -input[type="radio"]:focus { - border: 1px solid var(--text-primary); - box-shadow: 0 0 0 2px var(--text-secondary); -} - -input[type="radio"]:checked::before { - background-color: var(--text-primary); - border-radius: 50%; - content: ""; - float: left; - height: 100%; - transform: scale(0.5); - width: 100%; -} diff --git a/vizro-core/src/vizro/static/css/scroll_bar.css b/vizro-core/src/vizro/static/css/scroll_bar.css index cd6878341..41c85f9b9 100644 --- a/vizro-core/src/vizro/static/css/scroll_bar.css +++ b/vizro-core/src/vizro/static/css/scroll_bar.css @@ -20,7 +20,7 @@ border-color: var(--surfaces-bg-card); } -.nav-card::-webkit-scrollbar-thumb { +.card-nav::-webkit-scrollbar-thumb { border-color: var(--surfaces-bg-card); } diff --git a/vizro-core/src/vizro/static/css/selectors.css b/vizro-core/src/vizro/static/css/selectors.css deleted file mode 100644 index a6d0a4e99..000000000 --- a/vizro-core/src/vizro/static/css/selectors.css +++ /dev/null @@ -1,27 +0,0 @@ -.checkboxes-list, -.radio-items-list { - align-self: stretch; - color: var(--text-secondary); - display: flex; - flex-direction: column; - font-size: var(--text-size-02); - gap: var(--spacing-02); - letter-spacing: var(--letter-spacing-body-ui-02); - line-height: var(--text-size-04); -} - -.checkboxes-list label, -.radio-items-list label { - align-items: center; - align-self: stretch; - display: flex !important; - gap: var(--spacing-01); - height: var(--spacing-05); -} - -.input-container { - display: flex; - flex-direction: column; - gap: var(--spacing-03); - width: 100%; -} diff --git a/vizro-core/src/vizro/static/css/slider.css b/vizro-core/src/vizro/static/css/slider.css index 8d2a5fed4..c3e936efc 100644 --- a/vizro-core/src/vizro/static/css/slider.css +++ b/vizro-core/src/vizro/static/css/slider.css @@ -108,6 +108,7 @@ input.dash-input:invalid { display: flex; flex-direction: row; gap: var(--spacing-01); + margin-bottom: var(--spacing-03); } .slider-text-input-field { diff --git a/vizro-core/src/vizro/static/css/toggle.css b/vizro-core/src/vizro/static/css/toggle.css index 9fc650ff2..5158e4c16 100644 --- a/vizro-core/src/vizro/static/css/toggle.css +++ b/vizro-core/src/vizro/static/css/toggle.css @@ -2,7 +2,7 @@ width: 32px; } -#page-container .mantine-Switch-track { +#dashboard-container .mantine-Switch-track { background-color: var(--fill-subtle); border: 1px solid var(--border-enabled); border-radius: 16px; @@ -11,33 +11,33 @@ width: 32px; } -#page-container .mantine-Switch-track:focus { +#dashboard-container .mantine-Switch-track:focus { border: 2px solid var(--focus-focus); } -#page-container .mantine-Switch-input { +#dashboard-container .mantine-Switch-input { margin: 0; } -#page-container .mantine-Switch-trackLabel { +#dashboard-container .mantine-Switch-trackLabel { height: 16px; margin: 0; width: 32px; } -#page-container .mantine-Switch-thumb { +#dashboard-container .mantine-Switch-thumb { background-color: var(--fill-medium-emphasis); border: none; height: 10px; width: 10px; } -#page-container input:checked + * > .mantine-11dx59s { +#dashboard-container input:checked + * > .mantine-11dx59s { background: var(--text-contrast-primary); left: calc(100% - 12px); } -#page-container input:checked + * > .mantine-69c9zd { +#dashboard-container input:checked + * > .mantine-69c9zd { background: var(--text-primary); border-color: var(--border-enabled); } diff --git a/vizro-core/src/vizro/static/css/types.css b/vizro-core/src/vizro/static/css/types.css deleted file mode 100644 index 40ca267c9..000000000 --- a/vizro-core/src/vizro/static/css/types.css +++ /dev/null @@ -1,15 +0,0 @@ -hr { - border-bottom: 1px solid var(--border-subtle-alpha-01); - width: 100%; -} - -blockquote { - border-left: var(--spacing-01) var(--text-secondary) solid; - margin: 0 0 var(--spacing-02); - padding-left: var(--spacing-01); -} - -img, -svg { - vertical-align: unset; -} diff --git a/vizro-core/src/vizro/static/css/typography.css b/vizro-core/src/vizro/static/css/typography.css deleted file mode 100644 index 7c3176ba4..000000000 --- a/vizro-core/src/vizro/static/css/typography.css +++ /dev/null @@ -1,118 +0,0 @@ -h1, -h2, -.heading-1-400, -.heading-2-400 { - color: var(--text-primary); - font-size: var(--text-size-05); - font-weight: var(--text-weight-regular); - letter-spacing: var(--letter-spacing-heading-h1); - line-height: var(--text-size-07); -} - -h3, -.heading-3-400 { - color: var(--text-primary); - font-size: var(--text-size-04); - font-weight: var(--text-weight-regular); - line-height: var(--text-size-06); -} - -.heading-3-600 { - font-weight: var(--text-weight-semibold); -} - -h4, -.heading-4-400 { - color: var(--text-primary); - font-size: var(--text-size-03); - font-weight: var(--text-weight-regular); - letter-spacing: var(--letter-spacing-heading-h4); - line-height: var(--text-size-04); -} - -.heading-4-600 { - font-weight: var(--text-weight-semibold); -} - -.body-ui-01, -.body-ui-link-01 { - color: var(--text-secondary); - font-size: var(--text-size-03); - font-weight: var(--text-weight-regular); - letter-spacing: var(--letter-spacing-body-ui-01); - line-height: var(--text-size-04); -} - -.body-ui-link-01 { - font-weight: var(--text-weight-semibold); - text-decoration: underline; -} - -p, -label, -h5, -h6, -ul, -li, -.body-ui-02, -.body-ui-link-02 { - color: var(--text-secondary); - font-size: var(--text-size-02); - font-weight: var(--text-weight-regular); - letter-spacing: var(--letter-spacing-body-ui-02); - line-height: var(--text-size-03); -} - -.body-ui-link-02 { - font-weight: var(--text-weight-semibold); -} - -.body-editorial-01 { - color: var(--text-secondary); - font-size: var(--text-size-03); - font-weight: var(--text-weight-regular); - letter-spacing: var(--letter-spacing-body-edit-01); - line-height: var(--text-size-05); - text-decoration: underline; -} - -.body-editorial-02 { - color: var(--text-secondary); - font-size: var(--text-size-02); - font-weight: var(--text-weight-regular); - letter-spacing: var(--letter-spacing-body-edit-02); - line-height: var(--text-size-04); -} - -.help-text { - color: var(--text-secondary); - font-size: var(--text-size-01); - font-weight: var(--text-weight-light); - letter-spacing: var(--letter-spacing-help-text); - line-height: var(--text-size-03); -} - -code, -.hljs, -.code-text { - background: var(--surfaces-bg-card); - border-radius: 2px; - color: var(--text-primary); - font-family: RobotoMono, Inter, sans-serif, Arial, serif; - font-size: var(--text-size-01); - font-weight: var(--text-weight-regular); - line-height: var(--text-size-03); - padding: 0 4px; - width: max-content; -} - -.hljs-section, -.hljs-quote, -.hljs-bullet, -.hljs-name { - color: var(--text-secondary); -} - -.card_text span { - color: var(--text-secondary); -} diff --git a/vizro-core/src/vizro/static/css/user_input.css b/vizro-core/src/vizro/static/css/user_input.css deleted file mode 100644 index b2e6a71ba..000000000 --- a/vizro-core/src/vizro/static/css/user_input.css +++ /dev/null @@ -1,62 +0,0 @@ -.user_input, -.text-area, -textarea { - background-color: var(--field-enabled); - border: none; - border-radius: 0; - box-shadow: var(--box-shadow-elevation-0); - display: flex; - flex-direction: column; - font-size: var(--text-size-02); - font-weight: var(--text-weight-regular); - letter-spacing: var(--letter-spacing-body-ui-02); - line-height: var(--text-size-03); - text-overflow: ellipsis; -} - -textarea, -.text-area { - padding: var(--spacing-03) var(--spacing-03) var(--spacing-04); -} - -.user_input { - gap: var(--spacing-02); - height: 32px; - padding: 8px 8px 8px 12px; -} - -.user_input:empty { - color: var(--text-placeholder); -} - -.user_input:enabled, -.text-area:enabled, -textarea:enabled { - color: var(--text-primary); -} - -.user_input:hover, -.text-area:hover, -textarea:hover { - background: var(--field-hover); - box-shadow: var(--box-shadow-elevation-1); -} - -.user_input:focus, -.text-area:focus, -textarea:focus { - background: var(--field-enabled); - box-shadow: 0 0 0 2px var(--focus-focus) inset; - color: var(--text-primary); - outline: none; -} - -/* Should be replaced by a custom icon at some point */ -input[type="search"]::-webkit-search-cancel-button { - appearance: none; -} - -.form-control::placeholder { - color: var(--text-placeholder); - font-size: var(--text-size-02); -} diff --git a/vizro-core/src/vizro/static/css/variables.css b/vizro-core/src/vizro/static/css/variables.css index 8a8b519cd..a447adfb4 100644 --- a/vizro-core/src/vizro/static/css/variables.css +++ b/vizro-core/src/vizro/static/css/variables.css @@ -153,4 +153,5 @@ --status-success: var(--status-light-mode-success); --tags-text-color: var(--tags-light-mode-text-color); --status-warning: var(--status-light-mode-warning); + --inverse-color: invert(100%); } diff --git a/vizro-core/src/vizro/static/css/vizro-bootstrap.min.css b/vizro-core/src/vizro/static/css/vizro-bootstrap.min.css new file mode 100644 index 000000000..ade1b31c1 --- /dev/null +++ b/vizro-core/src/vizro/static/css/vizro-bootstrap.min.css @@ -0,0 +1,12900 @@ +@charset "UTF-8"; +/** This is an auto-generated file, don't edit it. **/ +/** This is an auto-generated file, don't edit it. **/ +.vizro_dark, [data-bs-theme=dark] { + --elevation-0: 0 1px 1px 0 rgba(20, 23, 33, 0.8784313725), 0 0 1px 0 rgba(20, 23, 33, 0.8784313725); + --elevation-0-inverted: 0 1px 1px 0 rgba(20, 23, 33, 0.0784313725), 0 0 1px 0 rgba(20, 23, 33, 0.3803921569); + --elevation-1: 0 2px 4px -1px rgba(20, 23, 33, 0.3803921569), 0 1px 2px -1px rgba(20, 23, 33, 0.8784313725); + --elevation-1-inverted: 0 2px 4px 1px rgba(20, 23, 33, 0.1215686275), 0 1px 2px 0 rgba(20, 23, 33, 0.1215686275); + --elevation-2: 0 4px 8px 0 rgba(20, 23, 33, 0.3803921569), 0 2px 4px -1px rgba(20, 23, 33, 0.8784313725); + --elevation-2-inverted: 0 4px 8px 0 rgba(20, 23, 33, 0.1215686275), 0 2px 4px -1px rgba(20, 23, 33, 0.0784313725); + --elevation-3: 0 8px 12px 1px rgba(20, 23, 33, 0.3803921569), 0 4px 8px -1px rgba(20, 23, 33, 0.8784313725); + --elevation-3-inverted: 0 8px 12px 1px rgba(20, 23, 33, 0.1215686275), 0 4px 8px -1px rgba(20, 23, 33, 0.0784313725); + --elevation-4: 0 16px 32px 2px rgba(20, 23, 33, 0.3803921569), 0 8px 16px -2px rgba(20, 23, 33, 0.8784313725); + --elevation-4-inverted: 0 16px 32px 2px rgba(20, 23, 33, 0.1215686275), 0 8px 16px -2px rgba(20, 23, 33, 0.0784313725); + --primary-50: #373a44; + --primary-50-inverted: white; + --primary-100: #333640; + --primary-100-inverted: #fafafb; + --primary-200: #2f323c; + --primary-200-inverted: #f5f6f6; + --primary-300: #2b2e39; + --primary-300-inverted: #f2f3f4; + --primary-400: #272a35; + --primary-400-inverted: #ebedee; + --primary-500: #232632; + --primary-500-inverted: #e6e8ea; + --primary-600: #1f222e; + --primary-600-inverted: #e2e4e6; + --primary-700: #1b1e2a; + --primary-700-inverted: #dddfe1; + --primary-800: #181b26; + --primary-800-inverted: #d8dadd; + --primary-900: #141721; + --primary-900-inverted: #d3d6d9; + --text-placeholder: rgba(255, 255, 255, 0.3803921569); + --text-placeholder-inverted: rgba(20, 23, 33, 0.3803921569); + --text-secondary: rgba(255, 255, 255, 0.6); + --text-secondary-inverted: rgba(20, 23, 33, 0.6); + --text-primary: rgba(255, 255, 255, 0.8784313725); + --text-primary-inverted: rgba(20, 23, 33, 0.8784313725); + --text-primaryHover: white; + --text-primaryHover-inverted: #141721; + --text-disabled: rgba(255, 255, 255, 0.3019607843); + --text-disabled-inverted: rgba(20, 23, 33, 0.3019607843); + --fill-subtle: rgba(255, 255, 255, 0.1019607843); + --fill-subtle-inverted: rgba(20, 23, 33, 0.1019607843); + --fill-secondary: rgba(255, 255, 255, 0.6); + --fill-secondary-inverted: rgba(20, 23, 33, 0.6); + --fill-primary: rgba(255, 255, 255, 0.8784313725); + --fill-primary-inverted: rgba(20, 23, 33, 0.8784313725); + --fill-enabled: rgba(255, 255, 255, 0.6); + --fill-enabled-inverted: rgba(20, 23, 33, 0.6); + --fill-active: rgba(255, 255, 255, 0.8784313725); + --fill-active-inverted: rgba(20, 23, 33, 0.8784313725); + --fill-hoverSelected: white; + --fill-hoverSelected-inverted: #141721; + --fill-disabled: rgba(255, 255, 255, 0.3019607843); + --fill-disabled-inverted: rgba(20, 23, 33, 0.3019607843); + --border-subtleAlpha01: rgba(255, 255, 255, 0.1019607843); + --border-subtleAlpha01-inverted: rgba(20, 23, 33, 0.1019607843); + --border-subtleAlpha02: rgba(255, 255, 255, 0.1607843137); + --border-subtleAlpha02-inverted: rgba(20, 23, 33, 0.1607843137); + --border-subtleAlpha03: rgba(255, 255, 255, 0.2392156863); + --border-subtleAlpha03-inverted: rgba(20, 23, 33, 0.2392156863); + --border-enabled: rgba(255, 255, 255, 0.6); + --border-enabled-inverted: rgba(20, 23, 33, 0.6); + --border-hover: rgba(255, 255, 255, 0.8784313725); + --border-hover-inverted: rgba(20, 23, 33, 0.8784313725); + --border-disabled: rgba(255, 255, 255, 0.3019607843); + --border-disabled-inverted: rgba(20, 23, 33, 0.3019607843); + --border-selected: white; + --border-selected-inverted: #141721; + --border-selectedInverse: #141721; + --border-selectedInverse-inverted: white; + --stateOverlays-enabled: rgba(255, 255, 255, 0); + --stateOverlays-enabled-inverted: rgba(20, 23, 33, 0); + --stateOverlays-hover: rgba(255, 255, 255, 0.0392156863); + --stateOverlays-hover-inverted: rgba(20, 23, 33, 0.0588235294); + --stateOverlays-active: rgba(255, 255, 255, 0.0784313725); + --stateOverlays-active-inverted: rgba(20, 23, 33, 0.1215686275); + --stateOverlays-disabled: rgba(4, 19, 31, 0.1607843137); + --stateOverlays-disabled-inverted: rgba(20, 23, 33, 0.1607843137); + --stateOverlays-selected: #2b2e39; + --stateOverlays-selected-inverted: #fafafb; + --stateOverlays-selectedHover: rgba(255, 255, 255, 0.1607843137); + --stateOverlays-selectedHover-inverted: rgba(20, 23, 33, 0.2392156863); + --stateOverlays-selectedInverse: white; + --stateOverlays-selectedInverse-inverted: #141721; + --stateOverlays-selectedRange: rgba(255, 255, 255, 0.1019607843); + --stateOverlays-selectedRange-inverted: rgba(20, 23, 33, 0.0784313725); + --field-enabled: #2b2e39; + --field-enabled-inverted: #fafafb; + --field-hover: #373a44; + --field-hover-inverted: white; + --field-disabled: #272a35; + --field-disabled-inverted: #f0f1f2; + --status-success: #40d86e; + --status-success-inverted: #26bf56; + --status-error: #f56565; + --status-error-inverted: #f03b3a; + --status-information: #00b4ff; + --status-information-inverted: #009eff; + --status-warning: #ffc107; + --status-warning-inverted: #f17c02; + --focus: rgba(0, 133, 255, 0.6); + --focus-inverted: rgba(0, 133, 255, 0.6); + --surfaces-bg01: #232632; + --surfaces-bg01-inverted: white; + --surfaces-bg02: #1b1e2a; + --surfaces-bg02-inverted: #f5f6f6; + --surfaces-bg03: #141721; + --surfaces-bg03-inverted: #e6e8ea; + --categorical-01Cyan: #00b4ff; + --categorical-01Cyan-inverted: #00b4ff; + --categorical-02Orange: #ff9222; + --categorical-02Orange-inverted: #ff9222; + --categorical-03Purple: #3949ab; + --categorical-03Purple-inverted: #3949ab; + --categorical-04Red: #ff5267; + --categorical-04Red-inverted: #ff5267; + --categorical-05Teal: #08bdba; + --categorical-05Teal-inverted: #08bdba; + --categorical-06Amber: #fdc935; + --categorical-06Amber-inverted: #fdc935; + --categorical-07Green: #689f38; + --categorical-07Green-inverted: #689f38; + --categorical-08Purple: #976fd1; + --categorical-08Purple-inverted: #976fd1; + --categorical-09Pink: #f781bf; + --categorical-09Pink-inverted: #f781bf; + --categorical-10DarkGreen: #52733e; + --categorical-10DarkGreen-inverted: #52733e; + --sequentialCyan-100: #afe7f9; + --sequentialCyan-100-inverted: #afe7f9; + --sequentialCyan-200: #8bd0f6; + --sequentialCyan-200-inverted: #8bd0f6; + --sequentialCyan-300: #6cbaec; + --sequentialCyan-300-inverted: #6cbaec; + --sequentialCyan-400: #52a3dd; + --sequentialCyan-400-inverted: #52a3dd; + --sequentialCyan-500: #3b8dcb; + --sequentialCyan-500-inverted: #3b8dcb; + --sequentialCyan-600: #2777b7; + --sequentialCyan-600-inverted: #2777b7; + --sequentialCyan-700: #1661a2; + --sequentialCyan-700-inverted: #1661a2; + --sequentialCyan-800: #074c8c; + --sequentialCyan-800-inverted: #074c8c; + --sequentialCyan-900: #003875; + --sequentialCyan-900-inverted: #003875; + --sequentialOrange-100: #f9d8ac; + --sequentialOrange-100-inverted: #f9d8ac; + --sequentialOrange-200: #feb85b; + --sequentialOrange-200-inverted: #feb85b; + --sequentialOrange-300: #f09b32; + --sequentialOrange-300-inverted: #f09b32; + --sequentialOrange-400: #db811e; + --sequentialOrange-400-inverted: #db811e; + --sequentialOrange-500: #c76809; + --sequentialOrange-500-inverted: #c76809; + --sequentialOrange-600: #b05000; + --sequentialOrange-600-inverted: #b05000; + --sequentialOrange-700: #973a00; + --sequentialOrange-700-inverted: #973a00; + --sequentialOrange-800: #7e2400; + --sequentialOrange-800-inverted: #7e2400; + --sequentialOrange-900: #640d00; + --sequentialOrange-900-inverted: #640d00; + --sequentialIndigo-100: #dfd8fa; + --sequentialIndigo-100-inverted: #dfd8fa; + --sequentialIndigo-200: #c3c1ed; + --sequentialIndigo-200-inverted: #c3c1ed; + --sequentialIndigo-300: #aba8e0; + --sequentialIndigo-300-inverted: #aba8e0; + --sequentialIndigo-400: #9390d2; + --sequentialIndigo-400-inverted: #9390d2; + --sequentialIndigo-500: #7a79c4; + --sequentialIndigo-500-inverted: #7a79c4; + --sequentialIndigo-600: #6163b5; + --sequentialIndigo-600-inverted: #6163b5; + --sequentialIndigo-700: #474ea6; + --sequentialIndigo-700-inverted: #474ea6; + --sequentialIndigo-800: #2a3994; + --sequentialIndigo-800-inverted: #2a3994; + --sequentialIndigo-900: #002680; + --sequentialIndigo-900-inverted: #002680; + --sequentialYellow-100: #fff7cd; + --sequentialYellow-100-inverted: #fff7cd; + --sequentialYellow-200: #ffed9b; + --sequentialYellow-200-inverted: #ffed9b; + --sequentialYellow-300: #ffe16a; + --sequentialYellow-300-inverted: #ffe16a; + --sequentialYellow-400: #ffd545; + --sequentialYellow-400-inverted: #ffd545; + --sequentialYellow-500: #ffc107; + --sequentialYellow-500-inverted: #ffc107; + --sequentialYellow-600: #dba005; + --sequentialYellow-600-inverted: #dba005; + --sequentialYellow-700: #b78103; + --sequentialYellow-700-inverted: #b78103; + --sequentialYellow-800: #936402; + --sequentialYellow-800-inverted: #936402; + --sequentialYellow-900: #7a4f01; + --sequentialYellow-900-inverted: #7a4f01; + --sequentialTeal-100: #a5eae8; + --sequentialTeal-100-inverted: #a5eae8; + --sequentialTeal-200: #7dd5d3; + --sequentialTeal-200-inverted: #7dd5d3; + --sequentialTeal-300: #5ebfbc; + --sequentialTeal-300-inverted: #5ebfbc; + --sequentialTeal-400: #44a8a6; + --sequentialTeal-400-inverted: #44a8a6; + --sequentialTeal-500: #2e9190; + --sequentialTeal-500-inverted: #2e9190; + --sequentialTeal-600: #1b7b7a; + --sequentialTeal-600-inverted: #1b7b7a; + --sequentialTeal-700: #0c6565; + --sequentialTeal-700-inverted: #0c6565; + --sequentialTeal-800: #025050; + --sequentialTeal-800-inverted: #025050; + --sequentialTeal-900: #003b3c; + --sequentialTeal-900-inverted: #003b3c; + --sequentialRed-100: #f8d6da; + --sequentialRed-100-inverted: #f8d6da; + --sequentialRed-200: #fcb6ba; + --sequentialRed-200-inverted: #fcb6ba; + --sequentialRed-300: #f8989b; + --sequentialRed-300-inverted: #f8989b; + --sequentialRed-400: #ed7b7f; + --sequentialRed-400-inverted: #ed7b7f; + --sequentialRed-500: #dd6065; + --sequentialRed-500-inverted: #dd6065; + --sequentialRed-600: #c9474c; + --sequentialRed-600-inverted: #c9474c; + --sequentialRed-700: #b22f36; + --sequentialRed-700-inverted: #b22f36; + --sequentialRed-800: #981822; + --sequentialRed-800-inverted: #981822; + --sequentialRed-900: #7d000f; + --sequentialRed-900-inverted: #7d000f; + --surfaces-bg-card: #232632; + --surfaces-bg-card-inverted: #F5F6F6; + /* How do we keep these in sync with the tokens? */ + --bs-primary-rgb: #ffffffe0; + --bs-secondary-color: #ffffff99; + --bs-nav-link-color: #ffffff99; /*QQ: This doesn't seem to work*/ + --bs-form-valid-color: #40d86eff; /*"status-success"*/ + --bs-form-invalid-color: #f56565ff; /*"status-error"*/ +} + +.vizro_light, [data-bs-theme=light] { + --elevation-0: 0 1px 1px 0 rgba(20, 23, 33, 0.0784313725), 0 0 1px 0 rgba(20, 23, 33, 0.3803921569); + --elevation-0-inverted: 0 1px 1px 0 rgba(20, 23, 33, 0.8784313725), 0 0 1px 0 rgba(20, 23, 33, 0.8784313725); + --elevation-1: 0 2px 4px 1px rgba(20, 23, 33, 0.1215686275), 0 1px 2px 0 rgba(20, 23, 33, 0.1215686275); + --elevation-1-inverted: 0 2px 4px -1px rgba(20, 23, 33, 0.3803921569), 0 1px 2px -1px rgba(20, 23, 33, 0.8784313725); + --elevation-2: 0 4px 8px 0 rgba(20, 23, 33, 0.1215686275), 0 2px 4px -1px rgba(20, 23, 33, 0.0784313725); + --elevation-2-inverted: 0 4px 8px 0 rgba(20, 23, 33, 0.3803921569), 0 2px 4px -1px rgba(20, 23, 33, 0.8784313725); + --elevation-3: 0 8px 12px 1px rgba(20, 23, 33, 0.1215686275), 0 4px 8px -1px rgba(20, 23, 33, 0.0784313725); + --elevation-3-inverted: 0 8px 12px 1px rgba(20, 23, 33, 0.3803921569), 0 4px 8px -1px rgba(20, 23, 33, 0.8784313725); + --elevation-4: 0 16px 32px 2px rgba(20, 23, 33, 0.1215686275), 0 8px 16px -2px rgba(20, 23, 33, 0.0784313725); + --elevation-4-inverted: 0 16px 32px 2px rgba(20, 23, 33, 0.3803921569), 0 8px 16px -2px rgba(20, 23, 33, 0.8784313725); + --primary-50: white; + --primary-50-inverted: #373a44; + --primary-100: #fafafb; + --primary-100-inverted: #333640; + --primary-200: #f5f6f6; + --primary-200-inverted: #2f323c; + --primary-300: #f2f3f4; + --primary-300-inverted: #2b2e39; + --primary-400: #ebedee; + --primary-400-inverted: #272a35; + --primary-500: #e6e8ea; + --primary-500-inverted: #232632; + --primary-600: #e2e4e6; + --primary-600-inverted: #1f222e; + --primary-700: #dddfe1; + --primary-700-inverted: #1b1e2a; + --primary-800: #d8dadd; + --primary-800-inverted: #181b26; + --primary-900: #d3d6d9; + --primary-900-inverted: #141721; + --text-placeholder: rgba(20, 23, 33, 0.3803921569); + --text-placeholder-inverted: rgba(255, 255, 255, 0.3803921569); + --text-secondary: rgba(20, 23, 33, 0.6); + --text-secondary-inverted: rgba(255, 255, 255, 0.6); + --text-primary: rgba(20, 23, 33, 0.8784313725); + --text-primary-inverted: rgba(255, 255, 255, 0.8784313725); + --text-primaryHover: #141721; + --text-primaryHover-inverted: white; + --text-disabled: rgba(20, 23, 33, 0.3019607843); + --text-disabled-inverted: rgba(255, 255, 255, 0.3019607843); + --fill-subtle: rgba(20, 23, 33, 0.1019607843); + --fill-subtle-inverted: rgba(255, 255, 255, 0.1019607843); + --fill-secondary: rgba(20, 23, 33, 0.6); + --fill-secondary-inverted: rgba(255, 255, 255, 0.6); + --fill-primary: rgba(20, 23, 33, 0.8784313725); + --fill-primary-inverted: rgba(255, 255, 255, 0.8784313725); + --fill-enabled: rgba(20, 23, 33, 0.6); + --fill-enabled-inverted: rgba(255, 255, 255, 0.6); + --fill-active: rgba(20, 23, 33, 0.8784313725); + --fill-active-inverted: rgba(255, 255, 255, 0.8784313725); + --fill-hoverSelected: #141721; + --fill-hoverSelected-inverted: white; + --fill-disabled: rgba(20, 23, 33, 0.3019607843); + --fill-disabled-inverted: rgba(255, 255, 255, 0.3019607843); + --border-subtleAlpha01: rgba(20, 23, 33, 0.1019607843); + --border-subtleAlpha01-inverted: rgba(255, 255, 255, 0.1019607843); + --border-subtleAlpha02: rgba(20, 23, 33, 0.1607843137); + --border-subtleAlpha02-inverted: rgba(255, 255, 255, 0.1607843137); + --border-subtleAlpha03: rgba(20, 23, 33, 0.2392156863); + --border-subtleAlpha03-inverted: rgba(255, 255, 255, 0.2392156863); + --border-enabled: rgba(20, 23, 33, 0.6); + --border-enabled-inverted: rgba(255, 255, 255, 0.6); + --border-hover: rgba(20, 23, 33, 0.8784313725); + --border-hover-inverted: rgba(255, 255, 255, 0.8784313725); + --border-disabled: rgba(20, 23, 33, 0.3019607843); + --border-disabled-inverted: rgba(255, 255, 255, 0.3019607843); + --border-selected: #141721; + --border-selected-inverted: white; + --border-selectedInverse: white; + --border-selectedInverse-inverted: #141721; + --stateOverlays-enabled: rgba(20, 23, 33, 0); + --stateOverlays-enabled-inverted: rgba(255, 255, 255, 0); + --stateOverlays-hover: rgba(20, 23, 33, 0.0588235294); + --stateOverlays-hover-inverted: rgba(255, 255, 255, 0.0392156863); + --stateOverlays-active: rgba(20, 23, 33, 0.1215686275); + --stateOverlays-active-inverted: rgba(255, 255, 255, 0.0784313725); + --stateOverlays-disabled: rgba(20, 23, 33, 0.1607843137); + --stateOverlays-disabled-inverted: rgba(4, 19, 31, 0.1607843137); + --stateOverlays-selected: #fafafb; + --stateOverlays-selected-inverted: #2b2e39; + --stateOverlays-selectedHover: rgba(20, 23, 33, 0.2392156863); + --stateOverlays-selectedHover-inverted: rgba(255, 255, 255, 0.1607843137); + --stateOverlays-selectedInverse: #141721; + --stateOverlays-selectedInverse-inverted: white; + --stateOverlays-selectedRange: rgba(20, 23, 33, 0.0784313725); + --stateOverlays-selectedRange-inverted: rgba(255, 255, 255, 0.1019607843); + --field-enabled: #fafafb; + --field-enabled-inverted: #2b2e39; + --field-hover: white; + --field-hover-inverted: #373a44; + --field-disabled: #f0f1f2; + --field-disabled-inverted: #272a35; + --status-success: #26bf56; + --status-success-inverted: #40d86e; + --status-error: #f03b3a; + --status-error-inverted: #f56565; + --status-information: #009eff; + --status-information-inverted: #00b4ff; + --status-warning: #f17c02; + --status-warning-inverted: #ffc107; + --focus: rgba(0, 133, 255, 0.6); + --focus-inverted: rgba(0, 133, 255, 0.6); + --surfaces-bg01: white; + --surfaces-bg01-inverted: #232632; + --surfaces-bg02: #f5f6f6; + --surfaces-bg02-inverted: #1b1e2a; + --surfaces-bg03: #e6e8ea; + --surfaces-bg03-inverted: #141721; + --categorical-01Cyan: #00b4ff; + --categorical-01Cyan-inverted: #00b4ff; + --categorical-02Orange: #ff9222; + --categorical-02Orange-inverted: #ff9222; + --categorical-03Purple: #3949ab; + --categorical-03Purple-inverted: #3949ab; + --categorical-04Red: #ff5267; + --categorical-04Red-inverted: #ff5267; + --categorical-05Teal: #08bdba; + --categorical-05Teal-inverted: #08bdba; + --categorical-06Amber: #fdc935; + --categorical-06Amber-inverted: #fdc935; + --categorical-07Green: #689f38; + --categorical-07Green-inverted: #689f38; + --categorical-08Purple: #976fd1; + --categorical-08Purple-inverted: #976fd1; + --categorical-09Pink: #f781bf; + --categorical-09Pink-inverted: #f781bf; + --categorical-10DarkGreen: #52733e; + --categorical-10DarkGreen-inverted: #52733e; + --sequentialCyan-100: #afe7f9; + --sequentialCyan-100-inverted: #afe7f9; + --sequentialCyan-200: #8bd0f6; + --sequentialCyan-200-inverted: #8bd0f6; + --sequentialCyan-300: #6cbaec; + --sequentialCyan-300-inverted: #6cbaec; + --sequentialCyan-400: #52a3dd; + --sequentialCyan-400-inverted: #52a3dd; + --sequentialCyan-500: #3b8dcb; + --sequentialCyan-500-inverted: #3b8dcb; + --sequentialCyan-600: #2777b7; + --sequentialCyan-600-inverted: #2777b7; + --sequentialCyan-700: #1661a2; + --sequentialCyan-700-inverted: #1661a2; + --sequentialCyan-800: #074c8c; + --sequentialCyan-800-inverted: #074c8c; + --sequentialCyan-900: #003875; + --sequentialCyan-900-inverted: #003875; + --sequentialOrange-100: #f9d8ac; + --sequentialOrange-100-inverted: #f9d8ac; + --sequentialOrange-200: #feb85b; + --sequentialOrange-200-inverted: #feb85b; + --sequentialOrange-300: #f09b32; + --sequentialOrange-300-inverted: #f09b32; + --sequentialOrange-400: #db811e; + --sequentialOrange-400-inverted: #db811e; + --sequentialOrange-500: #c76809; + --sequentialOrange-500-inverted: #c76809; + --sequentialOrange-600: #b05000; + --sequentialOrange-600-inverted: #b05000; + --sequentialOrange-700: #973a00; + --sequentialOrange-700-inverted: #973a00; + --sequentialOrange-800: #7e2400; + --sequentialOrange-800-inverted: #7e2400; + --sequentialOrange-900: #640d00; + --sequentialOrange-900-inverted: #640d00; + --sequentialIndigo-100: #dfd8fa; + --sequentialIndigo-100-inverted: #dfd8fa; + --sequentialIndigo-200: #c3c1ed; + --sequentialIndigo-200-inverted: #c3c1ed; + --sequentialIndigo-300: #aba8e0; + --sequentialIndigo-300-inverted: #aba8e0; + --sequentialIndigo-400: #9390d2; + --sequentialIndigo-400-inverted: #9390d2; + --sequentialIndigo-500: #7a79c4; + --sequentialIndigo-500-inverted: #7a79c4; + --sequentialIndigo-600: #6163b5; + --sequentialIndigo-600-inverted: #6163b5; + --sequentialIndigo-700: #474ea6; + --sequentialIndigo-700-inverted: #474ea6; + --sequentialIndigo-800: #2a3994; + --sequentialIndigo-800-inverted: #2a3994; + --sequentialIndigo-900: #002680; + --sequentialIndigo-900-inverted: #002680; + --sequentialYellow-100: #fff7cd; + --sequentialYellow-100-inverted: #fff7cd; + --sequentialYellow-200: #ffed9b; + --sequentialYellow-200-inverted: #ffed9b; + --sequentialYellow-300: #ffe16a; + --sequentialYellow-300-inverted: #ffe16a; + --sequentialYellow-400: #ffd545; + --sequentialYellow-400-inverted: #ffd545; + --sequentialYellow-500: #ffc107; + --sequentialYellow-500-inverted: #ffc107; + --sequentialYellow-600: #dba005; + --sequentialYellow-600-inverted: #dba005; + --sequentialYellow-700: #b78103; + --sequentialYellow-700-inverted: #b78103; + --sequentialYellow-800: #936402; + --sequentialYellow-800-inverted: #936402; + --sequentialYellow-900: #7a4f01; + --sequentialYellow-900-inverted: #7a4f01; + --sequentialTeal-100: #a5eae8; + --sequentialTeal-100-inverted: #a5eae8; + --sequentialTeal-200: #7dd5d3; + --sequentialTeal-200-inverted: #7dd5d3; + --sequentialTeal-300: #5ebfbc; + --sequentialTeal-300-inverted: #5ebfbc; + --sequentialTeal-400: #44a8a6; + --sequentialTeal-400-inverted: #44a8a6; + --sequentialTeal-500: #2e9190; + --sequentialTeal-500-inverted: #2e9190; + --sequentialTeal-600: #1b7b7a; + --sequentialTeal-600-inverted: #1b7b7a; + --sequentialTeal-700: #0c6565; + --sequentialTeal-700-inverted: #0c6565; + --sequentialTeal-800: #025050; + --sequentialTeal-800-inverted: #025050; + --sequentialTeal-900: #003b3c; + --sequentialTeal-900-inverted: #003b3c; + --sequentialRed-100: #f8d6da; + --sequentialRed-100-inverted: #f8d6da; + --sequentialRed-200: #fcb6ba; + --sequentialRed-200-inverted: #fcb6ba; + --sequentialRed-300: #f8989b; + --sequentialRed-300-inverted: #f8989b; + --sequentialRed-400: #ed7b7f; + --sequentialRed-400-inverted: #ed7b7f; + --sequentialRed-500: #dd6065; + --sequentialRed-500-inverted: #dd6065; + --sequentialRed-600: #c9474c; + --sequentialRed-600-inverted: #c9474c; + --sequentialRed-700: #b22f36; + --sequentialRed-700-inverted: #b22f36; + --sequentialRed-800: #981822; + --sequentialRed-800-inverted: #981822; + --sequentialRed-900: #7d000f; + --sequentialRed-900-inverted: #7d000f; + --surfaces-bg-card: #F5F6F6; + --surfaces-bg-card-inverted: #232632; + --bs-primary-rgb: #141721e0; + --bs-secondary-color: #14172199; + --bs-nav-link-color: #14172199; /*QQ: This doesn't seem to work*/ + --bs-form-valid-color: #26bf56ff; /*"status-success"*/ + --bs-form-invalid-color: #f03b3aff; /*"status-error"*/ +} + +/* 14px */ +/* 12px */ +/* 16px */ +/* 4px */ +/* 8px */ +/*! + * Bootstrap v5.3.3 (https://getbootstrap.com/) + * Copyright 2011-2024 The Bootstrap Authors + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +:root, +[data-bs-theme=light] { + --bs-blue: #3949ab; + --bs-indigo: #6610f2; + --bs-purple: #976fd1; + --bs-pink: #f781bf; + --bs-red: #ff5267; + --bs-orange: #ff9222; + --bs-yellow: #fdc935; + --bs-green: #689f38; + --bs-teal: #08bdba; + --bs-cyan: #00b4ff; + --bs-black: #000; + --bs-white: #fff; + --bs-gray: #6c757d; + --bs-gray-dark: #343a40; + --bs-gray-100: #f8f9fa; + --bs-gray-200: #e9ecef; + --bs-gray-300: #dee2e6; + --bs-gray-400: #ced4da; + --bs-gray-500: #adb5bd; + --bs-gray-600: #6c757d; + --bs-gray-700: #495057; + --bs-gray-800: #343a40; + --bs-gray-900: #212529; + --bs-primary: #3949ab; + --bs-secondary: #6c757d; + --bs-success: #689f38; + --bs-info: #00b4ff; + --bs-warning: #fdc935; + --bs-danger: #ff5267; + --bs-light: #f8f9fa; + --bs-dark: #212529; + --bs-primary-rgb: 57, 73, 171; + --bs-secondary-rgb: 108, 117, 125; + --bs-success-rgb: 104, 159, 56; + --bs-info-rgb: 0, 180, 255; + --bs-warning-rgb: 253, 201, 53; + --bs-danger-rgb: 255, 82, 103; + --bs-light-rgb: 248, 249, 250; + --bs-dark-rgb: 33, 37, 41; + --bs-primary-text-emphasis: #171d44; + --bs-secondary-text-emphasis: #2b2f32; + --bs-success-text-emphasis: #2a4016; + --bs-info-text-emphasis: #004866; + --bs-warning-text-emphasis: #655015; + --bs-danger-text-emphasis: #662129; + --bs-light-text-emphasis: #495057; + --bs-dark-text-emphasis: #495057; + --bs-primary-bg-subtle: #d7dbee; + --bs-secondary-bg-subtle: #e2e3e5; + --bs-success-bg-subtle: #e1ecd7; + --bs-info-bg-subtle: #ccf0ff; + --bs-warning-bg-subtle: #fff4d7; + --bs-danger-bg-subtle: #ffdce1; + --bs-light-bg-subtle: #fcfcfd; + --bs-dark-bg-subtle: #ced4da; + --bs-primary-border-subtle: #b0b6dd; + --bs-secondary-border-subtle: #c4c8cb; + --bs-success-border-subtle: #c3d9af; + --bs-info-border-subtle: #99e1ff; + --bs-warning-border-subtle: #fee9ae; + --bs-danger-border-subtle: #ffbac2; + --bs-light-border-subtle: #e9ecef; + --bs-dark-border-subtle: #adb5bd; + --bs-white-rgb: 255, 255, 255; + --bs-black-rgb: 0, 0, 0; + --bs-font-sans-serif: inter, sans-serif, arial, serif, "Segoe UI", roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); + --bs-body-font-family: var(--bs-font-sans-serif); + --bs-body-font-size: 0.875rem; + --bs-body-font-weight: 400; + --bs-body-line-height: 1.5; + --bs-body-color: #212529; + --bs-body-color-rgb: 33, 37, 41; + --bs-body-bg: #fff; + --bs-body-bg-rgb: 255, 255, 255; + --bs-emphasis-color: #000; + --bs-emphasis-color-rgb: 0, 0, 0; + --bs-secondary-color: rgba(33, 37, 41, 0.75); + --bs-secondary-color-rgb: 33, 37, 41; + --bs-secondary-bg: #e9ecef; + --bs-secondary-bg-rgb: 233, 236, 239; + --bs-tertiary-color: rgba(33, 37, 41, 0.5); + --bs-tertiary-color-rgb: 33, 37, 41; + --bs-tertiary-bg: #f8f9fa; + --bs-tertiary-bg-rgb: 248, 249, 250; + --bs-heading-color: inherit; + --bs-link-color: #3949ab; + --bs-link-color-rgb: 57, 73, 171; + --bs-link-decoration: underline; + --bs-link-hover-color: #2e3a89; + --bs-link-hover-color-rgb: 46, 58, 137; + --bs-code-color: #f781bf; + --bs-highlight-color: #212529; + --bs-highlight-bg: #fff4d7; + --bs-border-width: 1px; + --bs-border-style: solid; + --bs-border-color: #dee2e6; + --bs-border-color-translucent: rgba(0, 0, 0, 0.175); + --bs-border-radius: 0; + --bs-border-radius-sm: 0; + --bs-border-radius-lg: 0; + --bs-border-radius-xl: 1rem; + --bs-border-radius-xxl: 2rem; + --bs-border-radius-2xl: var(--bs-border-radius-xxl); + --bs-border-radius-pill: 50rem; + --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); + --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); + --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175); + --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075); + --bs-focus-ring-width: 0.25rem; + --bs-focus-ring-opacity: 0.25; + --bs-focus-ring-color: rgba(57, 73, 171, 0.25); + --bs-form-valid-color: #689f38; + --bs-form-valid-border-color: #689f38; + --bs-form-invalid-color: #ff5267; + --bs-form-invalid-border-color: #ff5267; +} + +[data-bs-theme=dark] { + color-scheme: dark; + --bs-body-color: #dee2e6; + --bs-body-color-rgb: 222, 226, 230; + --bs-body-bg: #212529; + --bs-body-bg-rgb: 33, 37, 41; + --bs-emphasis-color: #fff; + --bs-emphasis-color-rgb: 255, 255, 255; + --bs-secondary-color: rgba(222, 226, 230, 0.75); + --bs-secondary-color-rgb: 222, 226, 230; + --bs-secondary-bg: #343a40; + --bs-secondary-bg-rgb: 52, 58, 64; + --bs-tertiary-color: rgba(222, 226, 230, 0.5); + --bs-tertiary-color-rgb: 222, 226, 230; + --bs-tertiary-bg: #2b3035; + --bs-tertiary-bg-rgb: 43, 48, 53; + --bs-primary-text-emphasis: #8892cd; + --bs-secondary-text-emphasis: #a7acb1; + --bs-success-text-emphasis: #a4c588; + --bs-info-text-emphasis: #66d2ff; + --bs-warning-text-emphasis: #fedf86; + --bs-danger-text-emphasis: #ff97a4; + --bs-light-text-emphasis: #f8f9fa; + --bs-dark-text-emphasis: #dee2e6; + --bs-primary-bg-subtle: #0b0f22; + --bs-secondary-bg-subtle: #161719; + --bs-success-bg-subtle: #15200b; + --bs-info-bg-subtle: #002433; + --bs-warning-bg-subtle: #33280b; + --bs-danger-bg-subtle: #331015; + --bs-light-bg-subtle: #343a40; + --bs-dark-bg-subtle: #1a1d20; + --bs-primary-border-subtle: #222c67; + --bs-secondary-border-subtle: #41464b; + --bs-success-border-subtle: #3e5f22; + --bs-info-border-subtle: #006c99; + --bs-warning-border-subtle: #987920; + --bs-danger-border-subtle: #99313e; + --bs-light-border-subtle: #495057; + --bs-dark-border-subtle: #343a40; + --bs-heading-color: inherit; + --bs-link-color: #8892cd; + --bs-link-hover-color: #a0a8d7; + --bs-link-color-rgb: 136, 146, 205; + --bs-link-hover-color-rgb: 160, 168, 215; + --bs-code-color: #fab3d9; + --bs-highlight-color: #dee2e6; + --bs-highlight-bg: #655015; + --bs-border-color: #495057; + --bs-border-color-translucent: rgba(255, 255, 255, 0.15); + --bs-form-valid-color: #a4c588; + --bs-form-valid-border-color: #a4c588; + --bs-form-invalid-color: #ff97a4; + --bs-form-invalid-border-color: #ff97a4; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +@media (prefers-reduced-motion: no-preference) { + :root { + scroll-behavior: smooth; + } +} + +body { + margin: 0; + font-family: var(--bs-body-font-family); + font-size: var(--bs-body-font-size); + font-weight: var(--bs-body-font-weight); + line-height: var(--bs-body-line-height); + color: var(--bs-body-color); + text-align: var(--bs-body-text-align); + background-color: var(--bs-body-bg); + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +hr { + margin: 1rem 0; + color: inherit; + border: 0; + border-top: var(--bs-border-width) solid; + opacity: 0.25; +} + +h6, .h6, h5, .h5, h4, .h4, h3, .h3, h2, .h2, h1, .h1 { + margin-top: 0; + margin-bottom: 0.5rem; + font-weight: 500; + line-height: 1.2; + color: var(--bs-heading-color); +} + +h1, .h1 { + font-size: calc(1.34375rem + 1.125vw); +} +@media (min-width: 1200px) { + h1, .h1 { + font-size: 2.1875rem; + } +} + +h2, .h2 { + font-size: calc(1.3rem + 0.6vw); +} +@media (min-width: 1200px) { + h2, .h2 { + font-size: 1.75rem; + } +} + +h3, .h3 { + font-size: calc(1.278125rem + 0.3375vw); +} +@media (min-width: 1200px) { + h3, .h3 { + font-size: 1.53125rem; + } +} + +h4, .h4 { + font-size: calc(1.25625rem + 0.075vw); +} +@media (min-width: 1200px) { + h4, .h4 { + font-size: 1.3125rem; + } +} + +h5, .h5 { + font-size: 1.09375rem; +} + +h6, .h6 { + font-size: 0.875rem; +} + +p { + margin-top: 0; + margin-bottom: 0.5rem; +} + +abbr[title] { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + -webkit-text-decoration-skip-ink: none; + text-decoration-skip-ink: none; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, +ul { + padding-left: 2rem; +} + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; +} + +dt { + font-weight: 700; +} + +dd { + margin-bottom: 0.5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +b, +strong { + font-weight: bolder; +} + +small, .small { + font-size: 0.875em; +} + +mark, .mark { + padding: 0.1875em; + color: var(--bs-highlight-color); + background-color: var(--bs-highlight-bg); +} + +sub, +sup { + position: relative; + font-size: 0.75em; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +a { + color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); + text-decoration: underline; +} +a:hover { + --bs-link-color-rgb: var(--bs-link-hover-color-rgb); +} + +a:not([href]):not([class]), a:not([href]):not([class]):hover { + color: inherit; + text-decoration: none; +} + +pre, +code, +kbd, +samp { + font-family: var(--bs-font-monospace); + font-size: 1em; +} + +pre { + display: block; + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; + font-size: 0.875em; +} +pre code { + font-size: inherit; + color: inherit; + word-break: normal; +} + +code { + font-size: 0.875em; + color: var(--bs-code-color); + word-wrap: break-word; +} +a > code { + color: inherit; +} + +kbd { + padding: 0.1875rem 0.375rem; + font-size: 0.875em; + color: var(--bs-body-bg); + background-color: var(--bs-body-color); +} +kbd kbd { + padding: 0; + font-size: 1em; +} + +figure { + margin: 0 0 1rem; +} + +img, +svg { + vertical-align: middle; +} + +table { + caption-side: bottom; + border-collapse: collapse; +} + +caption { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + color: var(--bs-secondary-color); + text-align: left; +} + +th { + text-align: inherit; + text-align: -webkit-match-parent; +} + +thead, +tbody, +tfoot, +tr, +td, +th { + border-color: inherit; + border-style: solid; + border-width: 0; +} + +label { + display: inline-block; +} + +button { + border-radius: 0; +} + +button:focus:not(:focus-visible) { + outline: 0; +} + +input, +button, +select, +optgroup, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +select { + text-transform: none; +} + +[role=button] { + cursor: pointer; +} + +select { + word-wrap: normal; +} +select:disabled { + opacity: 1; +} + +[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator { + display: none !important; +} + +button, +[type=button], +[type=reset], +[type=submit] { + -webkit-appearance: button; +} +button:not(:disabled), +[type=button]:not(:disabled), +[type=reset]:not(:disabled), +[type=submit]:not(:disabled) { + cursor: pointer; +} + +::-moz-focus-inner { + padding: 0; + border-style: none; +} + +textarea { + resize: vertical; +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} + +legend { + float: left; + width: 100%; + padding: 0; + margin-bottom: 0.5rem; + font-size: calc(1.275rem + 0.3vw); + line-height: inherit; +} +@media (min-width: 1200px) { + legend { + font-size: 1.5rem; + } +} +legend + * { + clear: left; +} + +::-webkit-datetime-edit-fields-wrapper, +::-webkit-datetime-edit-text, +::-webkit-datetime-edit-minute, +::-webkit-datetime-edit-hour-field, +::-webkit-datetime-edit-day-field, +::-webkit-datetime-edit-month-field, +::-webkit-datetime-edit-year-field { + padding: 0; +} + +::-webkit-inner-spin-button { + height: auto; +} + +[type=search] { + -webkit-appearance: textfield; + outline-offset: -2px; +} + +/* rtl:raw: +[type="tel"], +[type="url"], +[type="email"], +[type="number"] { + direction: ltr; +} +*/ +::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-color-swatch-wrapper { + padding: 0; +} + +::file-selector-button { + font: inherit; + -webkit-appearance: button; +} + +output { + display: inline-block; +} + +iframe { + border: 0; +} + +summary { + display: list-item; + cursor: pointer; +} + +progress { + vertical-align: baseline; +} + +[hidden] { + display: none !important; +} + +.lead { + font-size: 1.09375rem; + font-weight: 300; +} + +.display-1 { + font-size: calc(1.625rem + 4.5vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-1 { + font-size: 5rem; + } +} + +.display-2 { + font-size: calc(1.575rem + 3.9vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-2 { + font-size: 4.5rem; + } +} + +.display-3 { + font-size: calc(1.525rem + 3.3vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-3 { + font-size: 4rem; + } +} + +.display-4 { + font-size: calc(1.475rem + 2.7vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-4 { + font-size: 3.5rem; + } +} + +.display-5 { + font-size: calc(1.425rem + 2.1vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-5 { + font-size: 3rem; + } +} + +.display-6 { + font-size: calc(1.375rem + 1.5vw); + font-weight: 300; + line-height: 1.2; +} +@media (min-width: 1200px) { + .display-6 { + font-size: 2.5rem; + } +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} + +.list-inline { + padding-left: 0; + list-style: none; +} + +.list-inline-item { + display: inline-block; +} +.list-inline-item:not(:last-child) { + margin-right: 0.5rem; +} + +.initialism { + font-size: 0.875em; + text-transform: uppercase; +} + +.blockquote { + margin-bottom: 1rem; + font-size: 1.09375rem; +} +.blockquote > :last-child { + margin-bottom: 0; +} + +.blockquote-footer { + margin-top: -1rem; + margin-bottom: 1rem; + font-size: 0.875em; + color: #6c757d; +} +.blockquote-footer::before { + content: "— "; +} + +.img-fluid { + max-width: 100%; + height: auto; +} + +.img-thumbnail { + padding: 0.25rem; + background-color: var(--bs-body-bg); + border: var(--bs-border-width) solid var(--bs-border-color); + max-width: 100%; + height: auto; +} + +.figure { + display: inline-block; +} + +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; +} + +.figure-caption { + font-size: 0.875em; + color: var(--bs-secondary-color); +} + +.container, +.container-fluid, +.container-xxl, +.container-xl, +.container-lg, +.container-md, +.container-sm { + --bs-gutter-x: 1.5rem; + --bs-gutter-y: 0; + width: 100%; + padding-right: calc(var(--bs-gutter-x) * 0.5); + padding-left: calc(var(--bs-gutter-x) * 0.5); + margin-right: auto; + margin-left: auto; +} + +@media (min-width: 576px) { + .container-sm, .container { + max-width: 540px; + } +} +@media (min-width: 768px) { + .container-md, .container-sm, .container { + max-width: 720px; + } +} +@media (min-width: 992px) { + .container-lg, .container-md, .container-sm, .container { + max-width: 960px; + } +} +@media (min-width: 1200px) { + .container-xl, .container-lg, .container-md, .container-sm, .container { + max-width: 1140px; + } +} +@media (min-width: 1400px) { + .container-xxl, .container-xl, .container-lg, .container-md, .container-sm, .container { + max-width: 1320px; + } +} +:root { + --bs-breakpoint-xs: 0; + --bs-breakpoint-sm: 576px; + --bs-breakpoint-md: 768px; + --bs-breakpoint-lg: 992px; + --bs-breakpoint-xl: 1200px; + --bs-breakpoint-xxl: 1400px; +} + +.row { + --bs-gutter-x: 1.5rem; + --bs-gutter-y: 0; + display: flex; + flex-wrap: wrap; + margin-top: calc(-1 * var(--bs-gutter-y)); + margin-right: calc(-0.5 * var(--bs-gutter-x)); + margin-left: calc(-0.5 * var(--bs-gutter-x)); +} +.row > * { + flex-shrink: 0; + width: 100%; + max-width: 100%; + padding-right: calc(var(--bs-gutter-x) * 0.5); + padding-left: calc(var(--bs-gutter-x) * 0.5); + margin-top: var(--bs-gutter-y); +} + +.col { + flex: 1 0 0%; +} + +.row-cols-auto > * { + flex: 0 0 auto; + width: auto; +} + +.row-cols-1 > * { + flex: 0 0 auto; + width: 100%; +} + +.row-cols-2 > * { + flex: 0 0 auto; + width: 50%; +} + +.row-cols-3 > * { + flex: 0 0 auto; + width: 33.33333333%; +} + +.row-cols-4 > * { + flex: 0 0 auto; + width: 25%; +} + +.row-cols-5 > * { + flex: 0 0 auto; + width: 20%; +} + +.row-cols-6 > * { + flex: 0 0 auto; + width: 16.66666667%; +} + +.col-auto { + flex: 0 0 auto; + width: auto; +} + +.col-1 { + flex: 0 0 auto; + width: 8.33333333%; +} + +.col-2 { + flex: 0 0 auto; + width: 16.66666667%; +} + +.col-3 { + flex: 0 0 auto; + width: 25%; +} + +.col-4 { + flex: 0 0 auto; + width: 33.33333333%; +} + +.col-5 { + flex: 0 0 auto; + width: 41.66666667%; +} + +.col-6 { + flex: 0 0 auto; + width: 50%; +} + +.col-7 { + flex: 0 0 auto; + width: 58.33333333%; +} + +.col-8 { + flex: 0 0 auto; + width: 66.66666667%; +} + +.col-9 { + flex: 0 0 auto; + width: 75%; +} + +.col-10 { + flex: 0 0 auto; + width: 83.33333333%; +} + +.col-11 { + flex: 0 0 auto; + width: 91.66666667%; +} + +.col-12 { + flex: 0 0 auto; + width: 100%; +} + +.offset-1 { + margin-left: 8.33333333%; +} + +.offset-2 { + margin-left: 16.66666667%; +} + +.offset-3 { + margin-left: 25%; +} + +.offset-4 { + margin-left: 33.33333333%; +} + +.offset-5 { + margin-left: 41.66666667%; +} + +.offset-6 { + margin-left: 50%; +} + +.offset-7 { + margin-left: 58.33333333%; +} + +.offset-8 { + margin-left: 66.66666667%; +} + +.offset-9 { + margin-left: 75%; +} + +.offset-10 { + margin-left: 83.33333333%; +} + +.offset-11 { + margin-left: 91.66666667%; +} + +.g-0, +.gx-0 { + --bs-gutter-x: 0; +} + +.g-0, +.gy-0 { + --bs-gutter-y: 0; +} + +.g-1, +.gx-1 { + --bs-gutter-x: 0.25rem; +} + +.g-1, +.gy-1 { + --bs-gutter-y: 0.25rem; +} + +.g-2, +.gx-2 { + --bs-gutter-x: 0.5rem; +} + +.g-2, +.gy-2 { + --bs-gutter-y: 0.5rem; +} + +.g-3, +.gx-3 { + --bs-gutter-x: 1rem; +} + +.g-3, +.gy-3 { + --bs-gutter-y: 1rem; +} + +.g-4, +.gx-4 { + --bs-gutter-x: 1.5rem; +} + +.g-4, +.gy-4 { + --bs-gutter-y: 1.5rem; +} + +.g-5, +.gx-5 { + --bs-gutter-x: 3rem; +} + +.g-5, +.gy-5 { + --bs-gutter-y: 3rem; +} + +@media (min-width: 576px) { + .col-sm { + flex: 1 0 0%; + } + .row-cols-sm-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-sm-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-sm-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-sm-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-sm-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-sm-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-sm-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-sm-auto { + flex: 0 0 auto; + width: auto; + } + .col-sm-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-sm-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-sm-3 { + flex: 0 0 auto; + width: 25%; + } + .col-sm-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-sm-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-sm-6 { + flex: 0 0 auto; + width: 50%; + } + .col-sm-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-sm-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-sm-9 { + flex: 0 0 auto; + width: 75%; + } + .col-sm-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-sm-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-sm-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-sm-0 { + margin-left: 0; + } + .offset-sm-1 { + margin-left: 8.33333333%; + } + .offset-sm-2 { + margin-left: 16.66666667%; + } + .offset-sm-3 { + margin-left: 25%; + } + .offset-sm-4 { + margin-left: 33.33333333%; + } + .offset-sm-5 { + margin-left: 41.66666667%; + } + .offset-sm-6 { + margin-left: 50%; + } + .offset-sm-7 { + margin-left: 58.33333333%; + } + .offset-sm-8 { + margin-left: 66.66666667%; + } + .offset-sm-9 { + margin-left: 75%; + } + .offset-sm-10 { + margin-left: 83.33333333%; + } + .offset-sm-11 { + margin-left: 91.66666667%; + } + .g-sm-0, + .gx-sm-0 { + --bs-gutter-x: 0; + } + .g-sm-0, + .gy-sm-0 { + --bs-gutter-y: 0; + } + .g-sm-1, + .gx-sm-1 { + --bs-gutter-x: 0.25rem; + } + .g-sm-1, + .gy-sm-1 { + --bs-gutter-y: 0.25rem; + } + .g-sm-2, + .gx-sm-2 { + --bs-gutter-x: 0.5rem; + } + .g-sm-2, + .gy-sm-2 { + --bs-gutter-y: 0.5rem; + } + .g-sm-3, + .gx-sm-3 { + --bs-gutter-x: 1rem; + } + .g-sm-3, + .gy-sm-3 { + --bs-gutter-y: 1rem; + } + .g-sm-4, + .gx-sm-4 { + --bs-gutter-x: 1.5rem; + } + .g-sm-4, + .gy-sm-4 { + --bs-gutter-y: 1.5rem; + } + .g-sm-5, + .gx-sm-5 { + --bs-gutter-x: 3rem; + } + .g-sm-5, + .gy-sm-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 768px) { + .col-md { + flex: 1 0 0%; + } + .row-cols-md-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-md-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-md-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-md-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-md-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-md-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-md-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-md-auto { + flex: 0 0 auto; + width: auto; + } + .col-md-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-md-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-md-3 { + flex: 0 0 auto; + width: 25%; + } + .col-md-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-md-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-md-6 { + flex: 0 0 auto; + width: 50%; + } + .col-md-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-md-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-md-9 { + flex: 0 0 auto; + width: 75%; + } + .col-md-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-md-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-md-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-md-0 { + margin-left: 0; + } + .offset-md-1 { + margin-left: 8.33333333%; + } + .offset-md-2 { + margin-left: 16.66666667%; + } + .offset-md-3 { + margin-left: 25%; + } + .offset-md-4 { + margin-left: 33.33333333%; + } + .offset-md-5 { + margin-left: 41.66666667%; + } + .offset-md-6 { + margin-left: 50%; + } + .offset-md-7 { + margin-left: 58.33333333%; + } + .offset-md-8 { + margin-left: 66.66666667%; + } + .offset-md-9 { + margin-left: 75%; + } + .offset-md-10 { + margin-left: 83.33333333%; + } + .offset-md-11 { + margin-left: 91.66666667%; + } + .g-md-0, + .gx-md-0 { + --bs-gutter-x: 0; + } + .g-md-0, + .gy-md-0 { + --bs-gutter-y: 0; + } + .g-md-1, + .gx-md-1 { + --bs-gutter-x: 0.25rem; + } + .g-md-1, + .gy-md-1 { + --bs-gutter-y: 0.25rem; + } + .g-md-2, + .gx-md-2 { + --bs-gutter-x: 0.5rem; + } + .g-md-2, + .gy-md-2 { + --bs-gutter-y: 0.5rem; + } + .g-md-3, + .gx-md-3 { + --bs-gutter-x: 1rem; + } + .g-md-3, + .gy-md-3 { + --bs-gutter-y: 1rem; + } + .g-md-4, + .gx-md-4 { + --bs-gutter-x: 1.5rem; + } + .g-md-4, + .gy-md-4 { + --bs-gutter-y: 1.5rem; + } + .g-md-5, + .gx-md-5 { + --bs-gutter-x: 3rem; + } + .g-md-5, + .gy-md-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 992px) { + .col-lg { + flex: 1 0 0%; + } + .row-cols-lg-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-lg-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-lg-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-lg-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-lg-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-lg-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-lg-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-lg-auto { + flex: 0 0 auto; + width: auto; + } + .col-lg-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-lg-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-lg-3 { + flex: 0 0 auto; + width: 25%; + } + .col-lg-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-lg-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-lg-6 { + flex: 0 0 auto; + width: 50%; + } + .col-lg-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-lg-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-lg-9 { + flex: 0 0 auto; + width: 75%; + } + .col-lg-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-lg-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-lg-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-lg-0 { + margin-left: 0; + } + .offset-lg-1 { + margin-left: 8.33333333%; + } + .offset-lg-2 { + margin-left: 16.66666667%; + } + .offset-lg-3 { + margin-left: 25%; + } + .offset-lg-4 { + margin-left: 33.33333333%; + } + .offset-lg-5 { + margin-left: 41.66666667%; + } + .offset-lg-6 { + margin-left: 50%; + } + .offset-lg-7 { + margin-left: 58.33333333%; + } + .offset-lg-8 { + margin-left: 66.66666667%; + } + .offset-lg-9 { + margin-left: 75%; + } + .offset-lg-10 { + margin-left: 83.33333333%; + } + .offset-lg-11 { + margin-left: 91.66666667%; + } + .g-lg-0, + .gx-lg-0 { + --bs-gutter-x: 0; + } + .g-lg-0, + .gy-lg-0 { + --bs-gutter-y: 0; + } + .g-lg-1, + .gx-lg-1 { + --bs-gutter-x: 0.25rem; + } + .g-lg-1, + .gy-lg-1 { + --bs-gutter-y: 0.25rem; + } + .g-lg-2, + .gx-lg-2 { + --bs-gutter-x: 0.5rem; + } + .g-lg-2, + .gy-lg-2 { + --bs-gutter-y: 0.5rem; + } + .g-lg-3, + .gx-lg-3 { + --bs-gutter-x: 1rem; + } + .g-lg-3, + .gy-lg-3 { + --bs-gutter-y: 1rem; + } + .g-lg-4, + .gx-lg-4 { + --bs-gutter-x: 1.5rem; + } + .g-lg-4, + .gy-lg-4 { + --bs-gutter-y: 1.5rem; + } + .g-lg-5, + .gx-lg-5 { + --bs-gutter-x: 3rem; + } + .g-lg-5, + .gy-lg-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 1200px) { + .col-xl { + flex: 1 0 0%; + } + .row-cols-xl-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-xl-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-xl-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-xl-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-xl-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-xl-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-xl-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xl-auto { + flex: 0 0 auto; + width: auto; + } + .col-xl-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-xl-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xl-3 { + flex: 0 0 auto; + width: 25%; + } + .col-xl-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-xl-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-xl-6 { + flex: 0 0 auto; + width: 50%; + } + .col-xl-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-xl-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-xl-9 { + flex: 0 0 auto; + width: 75%; + } + .col-xl-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-xl-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-xl-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-xl-0 { + margin-left: 0; + } + .offset-xl-1 { + margin-left: 8.33333333%; + } + .offset-xl-2 { + margin-left: 16.66666667%; + } + .offset-xl-3 { + margin-left: 25%; + } + .offset-xl-4 { + margin-left: 33.33333333%; + } + .offset-xl-5 { + margin-left: 41.66666667%; + } + .offset-xl-6 { + margin-left: 50%; + } + .offset-xl-7 { + margin-left: 58.33333333%; + } + .offset-xl-8 { + margin-left: 66.66666667%; + } + .offset-xl-9 { + margin-left: 75%; + } + .offset-xl-10 { + margin-left: 83.33333333%; + } + .offset-xl-11 { + margin-left: 91.66666667%; + } + .g-xl-0, + .gx-xl-0 { + --bs-gutter-x: 0; + } + .g-xl-0, + .gy-xl-0 { + --bs-gutter-y: 0; + } + .g-xl-1, + .gx-xl-1 { + --bs-gutter-x: 0.25rem; + } + .g-xl-1, + .gy-xl-1 { + --bs-gutter-y: 0.25rem; + } + .g-xl-2, + .gx-xl-2 { + --bs-gutter-x: 0.5rem; + } + .g-xl-2, + .gy-xl-2 { + --bs-gutter-y: 0.5rem; + } + .g-xl-3, + .gx-xl-3 { + --bs-gutter-x: 1rem; + } + .g-xl-3, + .gy-xl-3 { + --bs-gutter-y: 1rem; + } + .g-xl-4, + .gx-xl-4 { + --bs-gutter-x: 1.5rem; + } + .g-xl-4, + .gy-xl-4 { + --bs-gutter-y: 1.5rem; + } + .g-xl-5, + .gx-xl-5 { + --bs-gutter-x: 3rem; + } + .g-xl-5, + .gy-xl-5 { + --bs-gutter-y: 3rem; + } +} +@media (min-width: 1400px) { + .col-xxl { + flex: 1 0 0%; + } + .row-cols-xxl-auto > * { + flex: 0 0 auto; + width: auto; + } + .row-cols-xxl-1 > * { + flex: 0 0 auto; + width: 100%; + } + .row-cols-xxl-2 > * { + flex: 0 0 auto; + width: 50%; + } + .row-cols-xxl-3 > * { + flex: 0 0 auto; + width: 33.33333333%; + } + .row-cols-xxl-4 > * { + flex: 0 0 auto; + width: 25%; + } + .row-cols-xxl-5 > * { + flex: 0 0 auto; + width: 20%; + } + .row-cols-xxl-6 > * { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xxl-auto { + flex: 0 0 auto; + width: auto; + } + .col-xxl-1 { + flex: 0 0 auto; + width: 8.33333333%; + } + .col-xxl-2 { + flex: 0 0 auto; + width: 16.66666667%; + } + .col-xxl-3 { + flex: 0 0 auto; + width: 25%; + } + .col-xxl-4 { + flex: 0 0 auto; + width: 33.33333333%; + } + .col-xxl-5 { + flex: 0 0 auto; + width: 41.66666667%; + } + .col-xxl-6 { + flex: 0 0 auto; + width: 50%; + } + .col-xxl-7 { + flex: 0 0 auto; + width: 58.33333333%; + } + .col-xxl-8 { + flex: 0 0 auto; + width: 66.66666667%; + } + .col-xxl-9 { + flex: 0 0 auto; + width: 75%; + } + .col-xxl-10 { + flex: 0 0 auto; + width: 83.33333333%; + } + .col-xxl-11 { + flex: 0 0 auto; + width: 91.66666667%; + } + .col-xxl-12 { + flex: 0 0 auto; + width: 100%; + } + .offset-xxl-0 { + margin-left: 0; + } + .offset-xxl-1 { + margin-left: 8.33333333%; + } + .offset-xxl-2 { + margin-left: 16.66666667%; + } + .offset-xxl-3 { + margin-left: 25%; + } + .offset-xxl-4 { + margin-left: 33.33333333%; + } + .offset-xxl-5 { + margin-left: 41.66666667%; + } + .offset-xxl-6 { + margin-left: 50%; + } + .offset-xxl-7 { + margin-left: 58.33333333%; + } + .offset-xxl-8 { + margin-left: 66.66666667%; + } + .offset-xxl-9 { + margin-left: 75%; + } + .offset-xxl-10 { + margin-left: 83.33333333%; + } + .offset-xxl-11 { + margin-left: 91.66666667%; + } + .g-xxl-0, + .gx-xxl-0 { + --bs-gutter-x: 0; + } + .g-xxl-0, + .gy-xxl-0 { + --bs-gutter-y: 0; + } + .g-xxl-1, + .gx-xxl-1 { + --bs-gutter-x: 0.25rem; + } + .g-xxl-1, + .gy-xxl-1 { + --bs-gutter-y: 0.25rem; + } + .g-xxl-2, + .gx-xxl-2 { + --bs-gutter-x: 0.5rem; + } + .g-xxl-2, + .gy-xxl-2 { + --bs-gutter-y: 0.5rem; + } + .g-xxl-3, + .gx-xxl-3 { + --bs-gutter-x: 1rem; + } + .g-xxl-3, + .gy-xxl-3 { + --bs-gutter-y: 1rem; + } + .g-xxl-4, + .gx-xxl-4 { + --bs-gutter-x: 1.5rem; + } + .g-xxl-4, + .gy-xxl-4 { + --bs-gutter-y: 1.5rem; + } + .g-xxl-5, + .gx-xxl-5 { + --bs-gutter-x: 3rem; + } + .g-xxl-5, + .gy-xxl-5 { + --bs-gutter-y: 3rem; + } +} +.table { + --bs-table-color-type: initial; + --bs-table-bg-type: initial; + --bs-table-color-state: initial; + --bs-table-bg-state: initial; + --bs-table-color: var(--bs-emphasis-color); + --bs-table-bg: var(--bs-body-bg); + --bs-table-border-color: var(--bs-border-color); + --bs-table-accent-bg: transparent; + --bs-table-striped-color: var(--bs-emphasis-color); + --bs-table-striped-bg: rgba(var(--bs-emphasis-color-rgb), 0.05); + --bs-table-active-color: var(--bs-emphasis-color); + --bs-table-active-bg: rgba(var(--bs-emphasis-color-rgb), 0.1); + --bs-table-hover-color: var(--bs-emphasis-color); + --bs-table-hover-bg: rgba(var(--bs-emphasis-color-rgb), 0.075); + width: 100%; + margin-bottom: 1rem; + vertical-align: top; + border-color: var(--bs-table-border-color); +} +.table > :not(caption) > * > * { + padding: 0.5rem 0.5rem; + color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color))); + background-color: var(--bs-table-bg); + border-bottom-width: var(--bs-border-width); + box-shadow: inset 0 0 0 9999px var(--bs-table-bg-state, var(--bs-table-bg-type, var(--bs-table-accent-bg))); +} +.table > tbody { + vertical-align: inherit; +} +.table > thead { + vertical-align: bottom; +} + +.table-group-divider { + border-top: calc(var(--bs-border-width) * 2) solid currentcolor; +} + +.caption-top { + caption-side: top; +} + +.table-sm > :not(caption) > * > * { + padding: 0.25rem 0.25rem; +} + +.table-bordered > :not(caption) > * { + border-width: var(--bs-border-width) 0; +} +.table-bordered > :not(caption) > * > * { + border-width: 0 var(--bs-border-width); +} + +.table-borderless > :not(caption) > * > * { + border-bottom-width: 0; +} +.table-borderless > :not(:first-child) { + border-top-width: 0; +} + +.table-striped > tbody > tr:nth-of-type(odd) > * { + --bs-table-color-type: var(--bs-table-striped-color); + --bs-table-bg-type: var(--bs-table-striped-bg); +} + +.table-striped-columns > :not(caption) > tr > :nth-child(even) { + --bs-table-color-type: var(--bs-table-striped-color); + --bs-table-bg-type: var(--bs-table-striped-bg); +} + +.table-active { + --bs-table-color-state: var(--bs-table-active-color); + --bs-table-bg-state: var(--bs-table-active-bg); +} + +.table-hover > tbody > tr:hover > * { + --bs-table-color-state: var(--bs-table-hover-color); + --bs-table-bg-state: var(--bs-table-hover-bg); +} + +.table-primary { + --bs-table-color: #000; + --bs-table-bg: #d7dbee; + --bs-table-border-color: #acafbe; + --bs-table-striped-bg: #ccd0e2; + --bs-table-striped-color: #000; + --bs-table-active-bg: #c2c5d6; + --bs-table-active-color: #000; + --bs-table-hover-bg: #c7cbdc; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-secondary { + --bs-table-color: #000; + --bs-table-bg: #e2e3e5; + --bs-table-border-color: #b5b6b7; + --bs-table-striped-bg: #d7d8da; + --bs-table-striped-color: #000; + --bs-table-active-bg: #cbccce; + --bs-table-active-color: #000; + --bs-table-hover-bg: #d1d2d4; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-success { + --bs-table-color: #000; + --bs-table-bg: #e1ecd7; + --bs-table-border-color: #b4bdac; + --bs-table-striped-bg: #d6e0cc; + --bs-table-striped-color: #000; + --bs-table-active-bg: #cbd4c2; + --bs-table-active-color: #000; + --bs-table-hover-bg: #d0dac7; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-info { + --bs-table-color: #000; + --bs-table-bg: #ccf0ff; + --bs-table-border-color: #a3c0cc; + --bs-table-striped-bg: #c2e4f2; + --bs-table-striped-color: #000; + --bs-table-active-bg: #b8d8e6; + --bs-table-active-color: #000; + --bs-table-hover-bg: #bddeec; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-warning { + --bs-table-color: #000; + --bs-table-bg: #fff4d7; + --bs-table-border-color: #ccc3ac; + --bs-table-striped-bg: #f2e8cc; + --bs-table-striped-color: #000; + --bs-table-active-bg: #e6dcc2; + --bs-table-active-color: #000; + --bs-table-hover-bg: #ece2c7; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-danger { + --bs-table-color: #000; + --bs-table-bg: #ffdce1; + --bs-table-border-color: #ccb0b4; + --bs-table-striped-bg: #f2d1d6; + --bs-table-striped-color: #000; + --bs-table-active-bg: #e6c6cb; + --bs-table-active-color: #000; + --bs-table-hover-bg: #ecccd0; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-light { + --bs-table-color: #000; + --bs-table-bg: #f8f9fa; + --bs-table-border-color: #c6c7c8; + --bs-table-striped-bg: #ecedee; + --bs-table-striped-color: #000; + --bs-table-active-bg: #dfe0e1; + --bs-table-active-color: #000; + --bs-table-hover-bg: #e5e6e7; + --bs-table-hover-color: #000; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-dark { + --bs-table-color: #fff; + --bs-table-bg: #212529; + --bs-table-border-color: #4d5154; + --bs-table-striped-bg: #2c3034; + --bs-table-striped-color: #fff; + --bs-table-active-bg: #373b3e; + --bs-table-active-color: #fff; + --bs-table-hover-bg: #323539; + --bs-table-hover-color: #fff; + color: var(--bs-table-color); + border-color: var(--bs-table-border-color); +} + +.table-responsive { + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +@media (max-width: 575.98px) { + .table-responsive-sm { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 767.98px) { + .table-responsive-md { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 991.98px) { + .table-responsive-lg { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 1199.98px) { + .table-responsive-xl { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +@media (max-width: 1399.98px) { + .table-responsive-xxl { + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } +} +.form-label { + margin-bottom: 0.5rem; +} + +.col-form-label { + padding-top: calc(0.25rem + var(--bs-border-width)); + padding-bottom: calc(0.25rem + var(--bs-border-width)); + margin-bottom: 0; + font-size: inherit; + line-height: 1.6; +} + +.col-form-label-lg { + padding-top: calc(0.5rem + var(--bs-border-width)); + padding-bottom: calc(0.5rem + var(--bs-border-width)); + font-size: 0.875rem; +} + +.col-form-label-sm { + padding-top: calc(0.25rem + var(--bs-border-width)); + padding-bottom: calc(0.25rem + var(--bs-border-width)); + font-size: 0.65625rem; +} + +.form-text { + margin-top: 0.25rem; + font-size: 0.875em; + color: var(--bs-secondary-color); +} + +.form-control { + display: block; + width: 100%; + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + font-weight: 400; + line-height: 1.6; + color: var(--bs-body-color); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-body-bg); + background-clip: padding-box; + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: 0; +} +.form-control[type=file] { + overflow: hidden; +} +.form-control[type=file]:not(:disabled):not([readonly]) { + cursor: pointer; +} +.form-control:focus { + color: var(--bs-body-color); + background-color: var(--bs-body-bg); + border-color: #9ca4d5; + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(57, 73, 171, 0.25); +} +.form-control::-webkit-date-and-time-value { + min-width: 85px; + height: 1.6em; + margin: 0; +} +.form-control::-webkit-datetime-edit { + display: block; + padding: 0; +} +.form-control::-moz-placeholder { + color: var(--bs-secondary-color); + opacity: 1; +} +.form-control::placeholder { + color: var(--bs-secondary-color); + opacity: 1; +} +.form-control:disabled { + background-color: var(--bs-secondary-bg); + opacity: 1; +} +.form-control::file-selector-button { + padding: 0.25rem 0.5rem; + margin: -0.25rem -0.5rem; + margin-inline-end: 0.5rem; + color: var(--bs-body-color); + background-color: var(--bs-tertiary-bg); + pointer-events: none; + border-color: inherit; + border-style: solid; + border-width: 0; + border-inline-end-width: var(--bs-border-width); + border-radius: 0; +} +.form-control:hover:not(:disabled):not([readonly])::file-selector-button { + background-color: var(--bs-secondary-bg); +} + +.form-control-plaintext { + display: block; + width: 100%; + padding: 0.25rem 0; + margin-bottom: 0; + line-height: 1.6; + color: var(--bs-body-color); + background-color: transparent; + border: solid transparent; + border-width: var(--bs-border-width) 0; +} +.form-control-plaintext:focus { + outline: 0; +} +.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg { + padding-right: 0; + padding-left: 0; +} + +.form-control-sm { + min-height: calc(1.6em + 0.5rem + calc(var(--bs-border-width) * 2)); + padding: 0.25rem 0.5rem; + font-size: 0.65625rem; +} +.form-control-sm::file-selector-button { + padding: 0.25rem 0.5rem; + margin: -0.25rem -0.5rem; + margin-inline-end: 0.5rem; +} + +.form-control-lg { + min-height: calc(1.6em + 1rem + calc(var(--bs-border-width) * 2)); + padding: 0.5rem 1rem; + font-size: 0.875rem; +} +.form-control-lg::file-selector-button { + padding: 0.5rem 1rem; + margin: -0.5rem -1rem; + margin-inline-end: 1rem; +} + +textarea.form-control { + min-height: calc(1.6em + 0.5rem + calc(var(--bs-border-width) * 2)); +} +textarea.form-control-sm { + min-height: calc(1.6em + 0.5rem + calc(var(--bs-border-width) * 2)); +} +textarea.form-control-lg { + min-height: calc(1.6em + 1rem + calc(var(--bs-border-width) * 2)); +} + +.form-control-color { + width: 3rem; + height: calc(1.6em + 0.5rem + calc(var(--bs-border-width) * 2)); + padding: 0.25rem; +} +.form-control-color:not(:disabled):not([readonly]) { + cursor: pointer; +} +.form-control-color::-moz-color-swatch { + border: 0 !important; +} +.form-control-color::-webkit-color-swatch { + border: 0 !important; +} +.form-control-color.form-control-sm { + height: calc(1.6em + 0.5rem + calc(var(--bs-border-width) * 2)); +} +.form-control-color.form-control-lg { + height: calc(1.6em + 1rem + calc(var(--bs-border-width) * 2)); +} + +.form-select { + --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); + display: block; + width: 100%; + padding: 0.25rem 1.5rem 0.25rem 0.5rem; + font-size: 0.875rem; + font-weight: 400; + line-height: 1.6; + color: var(--bs-body-color); + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-body-bg); + background-image: var(--bs-form-select-bg-img), var(--bs-form-select-bg-icon, none); + background-repeat: no-repeat; + background-position: right 0.5rem center; + background-size: 16px 12px; + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: 0; +} +.form-select:focus { + border-color: #9ca4d5; + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(57, 73, 171, 0.25); +} +.form-select[multiple], .form-select[size]:not([size="1"]) { + padding-right: 0.5rem; + background-image: none; +} +.form-select:disabled { + background-color: var(--bs-secondary-bg); +} +.form-select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 var(--bs-body-color); +} + +.form-select-sm { + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-left: 0.5rem; + font-size: 0.65625rem; +} + +.form-select-lg { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + font-size: 0.875rem; +} + +[data-bs-theme=dark] .form-select { + --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dee2e6' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); +} + +.form-check { + display: block; + min-height: 1.3125rem; + padding-left: 1.5em; + margin-bottom: 0.125rem; +} +.form-check .form-check-input { + float: left; + margin-left: -1.5em; +} + +.form-check-reverse { + padding-right: 1.5em; + padding-left: 0; + text-align: right; +} +.form-check-reverse .form-check-input { + float: right; + margin-right: -1.5em; + margin-left: 0; +} + +.form-check-input { + --bs-form-check-bg: var(--bs-body-bg); + flex-shrink: 0; + width: 1em; + height: 1em; + margin-top: 0.25em; + vertical-align: top; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: var(--bs-form-check-bg); + background-image: var(--bs-form-check-bg-image); + background-repeat: no-repeat; + background-position: center; + background-size: contain; + border: var(--bs-border-width) solid var(--bs-border-color); + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} +.form-check-input[type=radio] { + border-radius: 50%; +} +.form-check-input:active { + filter: brightness(90%); +} +.form-check-input:focus { + border-color: #9ca4d5; + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(57, 73, 171, 0.25); +} +.form-check-input:checked { + background-color: #3949ab; + border-color: #3949ab; +} +.form-check-input:checked[type=checkbox] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/%3e%3c/svg%3e"); +} +.form-check-input:checked[type=radio] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e"); +} +.form-check-input[type=checkbox]:indeterminate { + background-color: #3949ab; + border-color: #3949ab; + --bs-form-check-bg-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e"); +} +.form-check-input:disabled { + pointer-events: none; + filter: none; + opacity: 0.5; +} +.form-check-input[disabled] ~ .form-check-label, .form-check-input:disabled ~ .form-check-label { + cursor: default; + opacity: 0.5; +} + +.form-switch { + padding-left: 2.5em; +} +.form-switch .form-check-input { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e"); + width: 2em; + margin-left: -2.5em; + background-image: var(--bs-form-switch-bg); + background-position: left center; + border-radius: 0; +} +.form-switch .form-check-input:focus { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%239ca4d5'/%3e%3c/svg%3e"); +} +.form-switch .form-check-input:checked { + background-position: right center; + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); +} +.form-switch.form-check-reverse { + padding-right: 2.5em; + padding-left: 0; +} +.form-switch.form-check-reverse .form-check-input { + margin-right: -2.5em; + margin-left: 0; +} + +.form-check-inline { + display: inline-block; + margin-right: 1rem; +} + +.btn-check { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.btn-check[disabled] + .btn, .btn-check:disabled + .btn { + pointer-events: none; + filter: none; + opacity: 1; +} + +[data-bs-theme=dark] .form-switch .form-check-input:not(:checked):not(:focus) { + --bs-form-switch-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%28255, 255, 255, 0.25%29'/%3e%3c/svg%3e"); +} + +.form-range { + width: 100%; + height: 1.5rem; + padding: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: transparent; +} +.form-range:focus { + outline: 0; +} +.form-range:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(57, 73, 171, 0.25); +} +.form-range:focus::-moz-range-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.25rem rgba(57, 73, 171, 0.25); +} +.form-range::-moz-focus-outer { + border: 0; +} +.form-range::-webkit-slider-thumb { + width: 1rem; + height: 1rem; + margin-top: -0.25rem; + -webkit-appearance: none; + appearance: none; + background-color: #3949ab; + border: 0; +} +.form-range::-webkit-slider-thumb:active { + background-color: #c4c8e6; +} +.form-range::-webkit-slider-runnable-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: var(--bs-secondary-bg); + border-color: transparent; +} +.form-range::-moz-range-thumb { + width: 1rem; + height: 1rem; + -moz-appearance: none; + appearance: none; + background-color: #3949ab; + border: 0; +} +.form-range::-moz-range-thumb:active { + background-color: #c4c8e6; +} +.form-range::-moz-range-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: var(--bs-secondary-bg); + border-color: transparent; +} +.form-range:disabled { + pointer-events: none; +} +.form-range:disabled::-webkit-slider-thumb { + background-color: var(--bs-secondary-color); +} +.form-range:disabled::-moz-range-thumb { + background-color: var(--bs-secondary-color); +} + +.form-floating { + position: relative; +} +.form-floating > .form-control, +.form-floating > .form-control-plaintext, +.form-floating > .form-select { + height: calc(3.5rem + calc(var(--bs-border-width) * 2)); + min-height: calc(3.5rem + calc(var(--bs-border-width) * 2)); + line-height: 1.25; +} +.form-floating > label { + position: absolute; + top: 0; + left: 0; + z-index: 2; + height: 100%; + padding: 1rem 0.5rem; + overflow: hidden; + text-align: start; + text-overflow: ellipsis; + white-space: nowrap; + pointer-events: none; + border: var(--bs-border-width) solid transparent; + transform-origin: 0 0; +} +.form-floating > .form-control, +.form-floating > .form-control-plaintext { + padding: 1rem 0.5rem; +} +.form-floating > .form-control::-moz-placeholder, .form-floating > .form-control-plaintext::-moz-placeholder { + color: transparent; +} +.form-floating > .form-control::placeholder, +.form-floating > .form-control-plaintext::placeholder { + color: transparent; +} +.form-floating > .form-control:not(:-moz-placeholder-shown), .form-floating > .form-control-plaintext:not(:-moz-placeholder-shown) { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-control:focus, .form-floating > .form-control:not(:placeholder-shown), +.form-floating > .form-control-plaintext:focus, +.form-floating > .form-control-plaintext:not(:placeholder-shown) { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-control:-webkit-autofill, +.form-floating > .form-control-plaintext:-webkit-autofill { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-select { + padding-top: 1.625rem; + padding-bottom: 0.625rem; +} +.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label { + color: rgba(var(--bs-body-color-rgb), 0.65); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} +.form-floating > .form-control:focus ~ label, +.form-floating > .form-control:not(:placeholder-shown) ~ label, +.form-floating > .form-control-plaintext ~ label, +.form-floating > .form-select ~ label { + color: rgba(var(--bs-body-color-rgb), 0.65); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} +.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label::after { + position: absolute; + inset: 1rem 0.25rem; + z-index: -1; + height: 1.5em; + content: ""; + background-color: var(--bs-body-bg); +} +.form-floating > .form-control:focus ~ label::after, +.form-floating > .form-control:not(:placeholder-shown) ~ label::after, +.form-floating > .form-control-plaintext ~ label::after, +.form-floating > .form-select ~ label::after { + position: absolute; + inset: 1rem 0.25rem; + z-index: -1; + height: 1.5em; + content: ""; + background-color: var(--bs-body-bg); +} +.form-floating > .form-control:-webkit-autofill ~ label { + color: rgba(var(--bs-body-color-rgb), 0.65); + transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); +} +.form-floating > .form-control-plaintext ~ label { + border-width: var(--bs-border-width) 0; +} +.form-floating > :disabled ~ label, +.form-floating > .form-control:disabled ~ label { + color: #6c757d; +} +.form-floating > :disabled ~ label::after, +.form-floating > .form-control:disabled ~ label::after { + background-color: var(--bs-secondary-bg); +} + +.input-group { + position: relative; + display: flex; + flex-wrap: wrap; + align-items: stretch; + width: 100%; +} +.input-group > .form-control, +.input-group > .form-select, +.input-group > .form-floating { + position: relative; + flex: 1 1 auto; + width: 1%; + min-width: 0; +} +.input-group > .form-control:focus, +.input-group > .form-select:focus, +.input-group > .form-floating:focus-within { + z-index: 5; +} +.input-group .btn { + position: relative; + z-index: 2; +} +.input-group .btn:focus { + z-index: 5; +} + +.input-group-text { + display: flex; + align-items: center; + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + font-weight: 400; + line-height: 1.6; + color: var(--bs-body-color); + text-align: center; + white-space: nowrap; + background-color: var(--bs-tertiary-bg); + border: var(--bs-border-width) solid var(--bs-border-color); +} + +.input-group-lg > .form-control, +.input-group-lg > .form-select, +.input-group-lg > .input-group-text, +.input-group-lg > .btn { + padding: 0.5rem 1rem; + font-size: 0.875rem; +} + +.input-group-sm > .form-control, +.input-group-sm > .form-select, +.input-group-sm > .input-group-text, +.input-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.65625rem; +} + +.input-group-lg > .form-select, +.input-group-sm > .form-select { + padding-right: 2rem; +} + +.input-group > :not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback) { + margin-left: calc(var(--bs-border-width) * -1); +} +.valid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 0.875em; + color: var(--bs-form-valid-color); +} + +.valid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: 0.1rem; + font-size: 0.65625rem; + color: #fff; + background-color: var(--bs-success); +} + +.was-validated :valid ~ .valid-feedback, +.was-validated :valid ~ .valid-tooltip, +.is-valid ~ .valid-feedback, +.is-valid ~ .valid-tooltip { + display: block; +} + +.was-validated .form-control:valid, .form-control.is-valid { + border-color: var(--bs-form-valid-border-color); + padding-right: calc(1.6em + 0.5rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23689f38' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.4em + 0.125rem) center; + background-size: calc(0.8em + 0.25rem) calc(0.8em + 0.25rem); +} +.was-validated .form-control:valid:focus, .form-control.is-valid:focus { + border-color: var(--bs-form-valid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); +} + +.was-validated textarea.form-control:valid, textarea.form-control.is-valid { + padding-right: calc(1.6em + 0.5rem); + background-position: top calc(0.4em + 0.125rem) right calc(0.4em + 0.125rem); +} + +.was-validated .form-select:valid, .form-select.is-valid { + border-color: var(--bs-form-valid-border-color); +} +.was-validated .form-select:valid:not([multiple]):not([size]), .was-validated .form-select:valid:not([multiple])[size="1"], .form-select.is-valid:not([multiple]):not([size]), .form-select.is-valid:not([multiple])[size="1"] { + --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23689f38' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + padding-right: 2.75rem; + background-position: right 0.5rem center, center right 1.5rem; + background-size: 16px 12px, calc(0.8em + 0.25rem) calc(0.8em + 0.25rem); +} +.was-validated .form-select:valid:focus, .form-select.is-valid:focus { + border-color: var(--bs-form-valid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); +} + +.was-validated .form-control-color:valid, .form-control-color.is-valid { + width: calc(3rem + calc(1.6em + 0.5rem)); +} + +.was-validated .form-check-input:valid, .form-check-input.is-valid { + border-color: var(--bs-form-valid-border-color); +} +.was-validated .form-check-input:valid:checked, .form-check-input.is-valid:checked { + background-color: var(--bs-form-valid-color); +} +.was-validated .form-check-input:valid:focus, .form-check-input.is-valid:focus { + box-shadow: 0 0 0 0.25rem rgba(var(--bs-success-rgb), 0.25); +} +.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { + color: var(--bs-form-valid-color); +} + +.form-check-inline .form-check-input ~ .valid-feedback { + margin-left: 0.5em; +} + +.was-validated .input-group > .form-control:not(:focus):valid, .input-group > .form-control:not(:focus).is-valid, +.was-validated .input-group > .form-select:not(:focus):valid, +.input-group > .form-select:not(:focus).is-valid, +.was-validated .input-group > .form-floating:not(:focus-within):valid, +.input-group > .form-floating:not(:focus-within).is-valid { + z-index: 3; +} + +.invalid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 0.875em; + color: var(--bs-form-invalid-color); +} + +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: 0.1rem; + font-size: 0.65625rem; + color: #fff; + background-color: var(--bs-danger); +} + +.was-validated :invalid ~ .invalid-feedback, +.was-validated :invalid ~ .invalid-tooltip, +.is-invalid ~ .invalid-feedback, +.is-invalid ~ .invalid-tooltip { + display: block; +} + +.was-validated .form-control:invalid, .form-control.is-invalid { + border-color: var(--bs-form-invalid-border-color); + padding-right: calc(1.6em + 0.5rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff5267'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff5267' stroke='none'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.4em + 0.125rem) center; + background-size: calc(0.8em + 0.25rem) calc(0.8em + 0.25rem); +} +.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus { + border-color: var(--bs-form-invalid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); +} + +.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid { + padding-right: calc(1.6em + 0.5rem); + background-position: top calc(0.4em + 0.125rem) right calc(0.4em + 0.125rem); +} + +.was-validated .form-select:invalid, .form-select.is-invalid { + border-color: var(--bs-form-invalid-border-color); +} +.was-validated .form-select:invalid:not([multiple]):not([size]), .was-validated .form-select:invalid:not([multiple])[size="1"], .form-select.is-invalid:not([multiple]):not([size]), .form-select.is-invalid:not([multiple])[size="1"] { + --bs-form-select-bg-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23ff5267'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23ff5267' stroke='none'/%3e%3c/svg%3e"); + padding-right: 2.75rem; + background-position: right 0.5rem center, center right 1.5rem; + background-size: 16px 12px, calc(0.8em + 0.25rem) calc(0.8em + 0.25rem); +} +.was-validated .form-select:invalid:focus, .form-select.is-invalid:focus { + border-color: var(--bs-form-invalid-border-color); + box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); +} + +.was-validated .form-control-color:invalid, .form-control-color.is-invalid { + width: calc(3rem + calc(1.6em + 0.5rem)); +} + +.was-validated .form-check-input:invalid, .form-check-input.is-invalid { + border-color: var(--bs-form-invalid-border-color); +} +.was-validated .form-check-input:invalid:checked, .form-check-input.is-invalid:checked { + background-color: var(--bs-form-invalid-color); +} +.was-validated .form-check-input:invalid:focus, .form-check-input.is-invalid:focus { + box-shadow: 0 0 0 0.25rem rgba(var(--bs-danger-rgb), 0.25); +} +.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { + color: var(--bs-form-invalid-color); +} + +.form-check-inline .form-check-input ~ .invalid-feedback { + margin-left: 0.5em; +} + +.was-validated .input-group > .form-control:not(:focus):invalid, .input-group > .form-control:not(:focus).is-invalid, +.was-validated .input-group > .form-select:not(:focus):invalid, +.input-group > .form-select:not(:focus).is-invalid, +.was-validated .input-group > .form-floating:not(:focus-within):invalid, +.input-group > .form-floating:not(:focus-within).is-invalid { + z-index: 4; +} + +.btn { + --bs-btn-padding-x: 0.5rem; + --bs-btn-padding-y: 0.25rem; + --bs-btn-font-family: ; + --bs-btn-font-size: 0.875rem; + --bs-btn-font-weight: 400; + --bs-btn-line-height: 1.6; + --bs-btn-color: var(--bs-body-color); + --bs-btn-bg: transparent; + --bs-btn-border-width: var(--bs-border-width); + --bs-btn-border-color: transparent; + --bs-btn-border-radius: var(--bs-border-radius); + --bs-btn-hover-border-color: transparent; + --bs-btn-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075); + --bs-btn-disabled-opacity: 1; + --bs-btn-focus-box-shadow: 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), .5); + display: inline-block; + padding: var(--bs-btn-padding-y) var(--bs-btn-padding-x); + font-family: var(--bs-btn-font-family); + font-size: var(--bs-btn-font-size); + font-weight: var(--bs-btn-font-weight); + line-height: var(--bs-btn-line-height); + color: var(--bs-btn-color); + text-align: center; + text-decoration: none; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + border: var(--bs-btn-border-width) solid var(--bs-btn-border-color); + background-color: var(--bs-btn-bg); +} +.btn:hover { + color: var(--bs-btn-hover-color); + background-color: var(--bs-btn-hover-bg); + border-color: var(--bs-btn-hover-border-color); +} +.btn-check + .btn:hover { + color: var(--bs-btn-color); + background-color: var(--bs-btn-bg); + border-color: var(--bs-btn-border-color); +} +.btn:focus-visible { + color: var(--bs-btn-hover-color); + background-color: var(--bs-btn-hover-bg); + border-color: var(--bs-btn-hover-border-color); + outline: 0; + box-shadow: var(--bs-btn-focus-box-shadow); +} +.btn-check:focus-visible + .btn { + border-color: var(--bs-btn-hover-border-color); + outline: 0; + box-shadow: var(--bs-btn-focus-box-shadow); +} +.btn-check:checked + .btn, :not(.btn-check) + .btn:active, .btn:first-child:active, .btn.active, .btn.show { + color: var(--bs-btn-active-color); + background-color: var(--bs-btn-active-bg); + border-color: var(--bs-btn-active-border-color); +} +.btn-check:checked + .btn:focus-visible, :not(.btn-check) + .btn:active:focus-visible, .btn:first-child:active:focus-visible, .btn.active:focus-visible, .btn.show:focus-visible { + box-shadow: var(--bs-btn-focus-box-shadow); +} +.btn-check:checked:focus-visible + .btn { + box-shadow: var(--bs-btn-focus-box-shadow); +} +.btn:disabled, .btn.disabled, fieldset:disabled .btn { + color: var(--bs-btn-disabled-color); + pointer-events: none; + background-color: var(--bs-btn-disabled-bg); + border-color: var(--bs-btn-disabled-border-color); + opacity: var(--bs-btn-disabled-opacity); +} + +.btn-primary { + --bs-btn-color: #fff; + --bs-btn-bg: #3949ab; + --bs-btn-border-color: #3949ab; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #303e91; + --bs-btn-hover-border-color: #2e3a89; + --bs-btn-focus-shadow-rgb: 87, 100, 184; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #2e3a89; + --bs-btn-active-border-color: #2b3780; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #3949ab; + --bs-btn-disabled-border-color: #3949ab; +} + +.btn-secondary { + --bs-btn-color: #fff; + --bs-btn-bg: #6c757d; + --bs-btn-border-color: #6c757d; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #5c636a; + --bs-btn-hover-border-color: #565e64; + --bs-btn-focus-shadow-rgb: 130, 138, 145; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #565e64; + --bs-btn-active-border-color: #51585e; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #6c757d; + --bs-btn-disabled-border-color: #6c757d; +} + +.btn-success { + --bs-btn-color: #000; + --bs-btn-bg: #689f38; + --bs-btn-border-color: #689f38; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #7fad56; + --bs-btn-hover-border-color: #77a94c; + --bs-btn-focus-shadow-rgb: 88, 135, 48; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #86b260; + --bs-btn-active-border-color: #77a94c; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #689f38; + --bs-btn-disabled-border-color: #689f38; +} + +.btn-info { + --bs-btn-color: #000; + --bs-btn-bg: #00b4ff; + --bs-btn-border-color: #00b4ff; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #26bfff; + --bs-btn-hover-border-color: #1abcff; + --bs-btn-focus-shadow-rgb: 0, 153, 217; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #33c3ff; + --bs-btn-active-border-color: #1abcff; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #00b4ff; + --bs-btn-disabled-border-color: #00b4ff; +} + +.btn-warning { + --bs-btn-color: #000; + --bs-btn-bg: #fdc935; + --bs-btn-border-color: #fdc935; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #fdd153; + --bs-btn-hover-border-color: #fdce49; + --bs-btn-focus-shadow-rgb: 215, 171, 45; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #fdd45d; + --bs-btn-active-border-color: #fdce49; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #fdc935; + --bs-btn-disabled-border-color: #fdc935; +} + +.btn-danger { + --bs-btn-color: #000; + --bs-btn-bg: #ff5267; + --bs-btn-border-color: #ff5267; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #ff6c7e; + --bs-btn-hover-border-color: #ff6376; + --bs-btn-focus-shadow-rgb: 217, 70, 88; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #ff7585; + --bs-btn-active-border-color: #ff6376; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #ff5267; + --bs-btn-disabled-border-color: #ff5267; +} + +.btn-light { + --bs-btn-color: #000; + --bs-btn-bg: #f8f9fa; + --bs-btn-border-color: #f8f9fa; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #d3d4d5; + --bs-btn-hover-border-color: #c6c7c8; + --bs-btn-focus-shadow-rgb: 211, 212, 213; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #c6c7c8; + --bs-btn-active-border-color: #babbbc; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #000; + --bs-btn-disabled-bg: #f8f9fa; + --bs-btn-disabled-border-color: #f8f9fa; +} + +.btn-dark { + --bs-btn-color: #fff; + --bs-btn-bg: #212529; + --bs-btn-border-color: #212529; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #424649; + --bs-btn-hover-border-color: #373b3e; + --bs-btn-focus-shadow-rgb: 66, 70, 73; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #4d5154; + --bs-btn-active-border-color: #373b3e; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fff; + --bs-btn-disabled-bg: #212529; + --bs-btn-disabled-border-color: #212529; +} + +.btn-outline-primary { + --bs-btn-color: #3949ab; + --bs-btn-border-color: #3949ab; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #3949ab; + --bs-btn-hover-border-color: #3949ab; + --bs-btn-focus-shadow-rgb: 57, 73, 171; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #3949ab; + --bs-btn-active-border-color: #3949ab; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #3949ab; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #3949ab; + --bs-gradient: none; +} + +.btn-outline-secondary { + --bs-btn-color: #6c757d; + --bs-btn-border-color: #6c757d; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #6c757d; + --bs-btn-hover-border-color: #6c757d; + --bs-btn-focus-shadow-rgb: 108, 117, 125; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #6c757d; + --bs-btn-active-border-color: #6c757d; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #6c757d; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #6c757d; + --bs-gradient: none; +} + +.btn-outline-success { + --bs-btn-color: #689f38; + --bs-btn-border-color: #689f38; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #689f38; + --bs-btn-hover-border-color: #689f38; + --bs-btn-focus-shadow-rgb: 104, 159, 56; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #689f38; + --bs-btn-active-border-color: #689f38; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #689f38; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #689f38; + --bs-gradient: none; +} + +.btn-outline-info { + --bs-btn-color: #00b4ff; + --bs-btn-border-color: #00b4ff; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #00b4ff; + --bs-btn-hover-border-color: #00b4ff; + --bs-btn-focus-shadow-rgb: 0, 180, 255; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #00b4ff; + --bs-btn-active-border-color: #00b4ff; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #00b4ff; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #00b4ff; + --bs-gradient: none; +} + +.btn-outline-warning { + --bs-btn-color: #fdc935; + --bs-btn-border-color: #fdc935; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #fdc935; + --bs-btn-hover-border-color: #fdc935; + --bs-btn-focus-shadow-rgb: 253, 201, 53; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #fdc935; + --bs-btn-active-border-color: #fdc935; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #fdc935; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #fdc935; + --bs-gradient: none; +} + +.btn-outline-danger { + --bs-btn-color: #ff5267; + --bs-btn-border-color: #ff5267; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #ff5267; + --bs-btn-hover-border-color: #ff5267; + --bs-btn-focus-shadow-rgb: 255, 82, 103; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #ff5267; + --bs-btn-active-border-color: #ff5267; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #ff5267; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #ff5267; + --bs-gradient: none; +} + +.btn-outline-light { + --bs-btn-color: #f8f9fa; + --bs-btn-border-color: #f8f9fa; + --bs-btn-hover-color: #000; + --bs-btn-hover-bg: #f8f9fa; + --bs-btn-hover-border-color: #f8f9fa; + --bs-btn-focus-shadow-rgb: 248, 249, 250; + --bs-btn-active-color: #000; + --bs-btn-active-bg: #f8f9fa; + --bs-btn-active-border-color: #f8f9fa; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #f8f9fa; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #f8f9fa; + --bs-gradient: none; +} + +.btn-outline-dark { + --bs-btn-color: #212529; + --bs-btn-border-color: #212529; + --bs-btn-hover-color: #fff; + --bs-btn-hover-bg: #212529; + --bs-btn-hover-border-color: #212529; + --bs-btn-focus-shadow-rgb: 33, 37, 41; + --bs-btn-active-color: #fff; + --bs-btn-active-bg: #212529; + --bs-btn-active-border-color: #212529; + --bs-btn-active-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); + --bs-btn-disabled-color: #212529; + --bs-btn-disabled-bg: transparent; + --bs-btn-disabled-border-color: #212529; + --bs-gradient: none; +} + +.btn-link { + --bs-btn-font-weight: 400; + --bs-btn-color: var(--bs-link-color); + --bs-btn-bg: transparent; + --bs-btn-border-color: transparent; + --bs-btn-hover-color: var(--bs-link-hover-color); + --bs-btn-hover-border-color: transparent; + --bs-btn-active-color: var(--bs-link-hover-color); + --bs-btn-active-border-color: transparent; + --bs-btn-disabled-color: #6c757d; + --bs-btn-disabled-border-color: transparent; + --bs-btn-box-shadow: 0 0 0 #000; + --bs-btn-focus-shadow-rgb: 87, 100, 184; + text-decoration: underline; +} +.btn-link:focus-visible { + color: var(--bs-btn-color); +} +.btn-link:hover { + color: var(--bs-btn-hover-color); +} + +.btn-lg, .btn-group-lg > .btn { + --bs-btn-padding-y: 0.5rem; + --bs-btn-padding-x: 1rem; + --bs-btn-font-size: 0.875rem; + --bs-btn-border-radius: var(--bs-border-radius-lg); +} + +.btn-sm, .btn-group-sm > .btn { + --bs-btn-padding-y: 0.25rem; + --bs-btn-padding-x: 0.5rem; + --bs-btn-font-size: 0.65625rem; + --bs-btn-border-radius: var(--bs-border-radius-sm); +} + +.fade:not(.show) { + opacity: 0; +} + +.collapse:not(.show) { + display: none; +} + +.collapsing { + height: 0; + overflow: hidden; +} +.collapsing.collapse-horizontal { + width: 0; + height: auto; +} + +.dropup, +.dropend, +.dropdown, +.dropstart, +.dropup-center, +.dropdown-center { + position: relative; +} + +.dropdown-toggle { + white-space: nowrap; +} +.dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; +} +.dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropdown-menu { + --bs-dropdown-zindex: 1000; + --bs-dropdown-min-width: 10rem; + --bs-dropdown-padding-x: 0; + --bs-dropdown-padding-y: 0.5rem; + --bs-dropdown-spacer: 0.125rem; + --bs-dropdown-font-size: 0.875rem; + --bs-dropdown-color: var(--bs-body-color); + --bs-dropdown-bg: var(--bs-body-bg); + --bs-dropdown-border-color: var(--bs-border-color-translucent); + --bs-dropdown-border-radius: var(--bs-border-radius); + --bs-dropdown-border-width: var(--bs-border-width); + --bs-dropdown-inner-border-radius: calc(var(--bs-border-radius) - var(--bs-border-width)); + --bs-dropdown-divider-bg: var(--bs-border-color-translucent); + --bs-dropdown-divider-margin-y: 0.5rem; + --bs-dropdown-box-shadow: var(--bs-box-shadow); + --bs-dropdown-link-color: var(--bs-body-color); + --bs-dropdown-link-hover-color: var(--bs-body-color); + --bs-dropdown-link-hover-bg: var(--bs-tertiary-bg); + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #3949ab; + --bs-dropdown-link-disabled-color: var(--bs-tertiary-color); + --bs-dropdown-item-padding-x: 1rem; + --bs-dropdown-item-padding-y: 0.25rem; + --bs-dropdown-header-color: #6c757d; + --bs-dropdown-header-padding-x: 1rem; + --bs-dropdown-header-padding-y: 0.5rem; + position: absolute; + z-index: var(--bs-dropdown-zindex); + display: none; + min-width: var(--bs-dropdown-min-width); + padding: var(--bs-dropdown-padding-y) var(--bs-dropdown-padding-x); + margin: 0; + font-size: var(--bs-dropdown-font-size); + color: var(--bs-dropdown-color); + text-align: left; + list-style: none; + background-color: var(--bs-dropdown-bg); + background-clip: padding-box; + border: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color); +} +.dropdown-menu[data-bs-popper] { + top: 100%; + left: 0; + margin-top: var(--bs-dropdown-spacer); +} + +.dropdown-menu-start { + --bs-position: start; +} +.dropdown-menu-start[data-bs-popper] { + right: auto; + left: 0; +} + +.dropdown-menu-end { + --bs-position: end; +} +.dropdown-menu-end[data-bs-popper] { + right: 0; + left: auto; +} + +@media (min-width: 576px) { + .dropdown-menu-sm-start { + --bs-position: start; + } + .dropdown-menu-sm-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-sm-end { + --bs-position: end; + } + .dropdown-menu-sm-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 768px) { + .dropdown-menu-md-start { + --bs-position: start; + } + .dropdown-menu-md-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-md-end { + --bs-position: end; + } + .dropdown-menu-md-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 992px) { + .dropdown-menu-lg-start { + --bs-position: start; + } + .dropdown-menu-lg-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-lg-end { + --bs-position: end; + } + .dropdown-menu-lg-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 1200px) { + .dropdown-menu-xl-start { + --bs-position: start; + } + .dropdown-menu-xl-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-xl-end { + --bs-position: end; + } + .dropdown-menu-xl-end[data-bs-popper] { + right: 0; + left: auto; + } +} +@media (min-width: 1400px) { + .dropdown-menu-xxl-start { + --bs-position: start; + } + .dropdown-menu-xxl-start[data-bs-popper] { + right: auto; + left: 0; + } + .dropdown-menu-xxl-end { + --bs-position: end; + } + .dropdown-menu-xxl-end[data-bs-popper] { + right: 0; + left: auto; + } +} +.dropup .dropdown-menu[data-bs-popper] { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: var(--bs-dropdown-spacer); +} +.dropup .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0; + border-right: 0.3em solid transparent; + border-bottom: 0.3em solid; + border-left: 0.3em solid transparent; +} +.dropup .dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropend .dropdown-menu[data-bs-popper] { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: var(--bs-dropdown-spacer); +} +.dropend .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0; + border-bottom: 0.3em solid transparent; + border-left: 0.3em solid; +} +.dropend .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropend .dropdown-toggle::after { + vertical-align: 0; +} + +.dropstart .dropdown-menu[data-bs-popper] { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: var(--bs-dropdown-spacer); +} +.dropstart .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; +} +.dropstart .dropdown-toggle::after { + display: none; +} +.dropstart .dropdown-toggle::before { + display: inline-block; + margin-right: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0.3em solid; + border-bottom: 0.3em solid transparent; +} +.dropstart .dropdown-toggle:empty::after { + margin-left: 0; +} +.dropstart .dropdown-toggle::before { + vertical-align: 0; +} + +.dropdown-divider { + height: 0; + margin: var(--bs-dropdown-divider-margin-y) 0; + overflow: hidden; + border-top: 1px solid var(--bs-dropdown-divider-bg); + opacity: 1; +} + +.dropdown-item { + display: block; + width: 100%; + padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); + clear: both; + font-weight: 400; + color: var(--bs-dropdown-link-color); + text-align: inherit; + text-decoration: none; + white-space: nowrap; + background-color: transparent; + border: 0; +} +.dropdown-item:hover, .dropdown-item:focus { + color: var(--bs-dropdown-link-hover-color); + background-color: var(--bs-dropdown-link-hover-bg); +} +.dropdown-item.active, .dropdown-item:active { + color: var(--bs-dropdown-link-active-color); + text-decoration: none; + background-color: var(--bs-dropdown-link-active-bg); +} +.dropdown-item.disabled, .dropdown-item:disabled { + color: var(--bs-dropdown-link-disabled-color); + pointer-events: none; + background-color: transparent; +} + +.dropdown-menu.show { + display: block; +} + +.dropdown-header { + display: block; + padding: var(--bs-dropdown-header-padding-y) var(--bs-dropdown-header-padding-x); + margin-bottom: 0; + font-size: 0.65625rem; + color: var(--bs-dropdown-header-color); + white-space: nowrap; +} + +.dropdown-item-text { + display: block; + padding: var(--bs-dropdown-item-padding-y) var(--bs-dropdown-item-padding-x); + color: var(--bs-dropdown-link-color); +} + +.dropdown-menu-dark { + --bs-dropdown-color: #dee2e6; + --bs-dropdown-bg: #343a40; + --bs-dropdown-border-color: var(--bs-border-color-translucent); + --bs-dropdown-box-shadow: ; + --bs-dropdown-link-color: #dee2e6; + --bs-dropdown-link-hover-color: #fff; + --bs-dropdown-divider-bg: var(--bs-border-color-translucent); + --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.15); + --bs-dropdown-link-active-color: #fff; + --bs-dropdown-link-active-bg: #3949ab; + --bs-dropdown-link-disabled-color: #adb5bd; + --bs-dropdown-header-color: #adb5bd; +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-flex; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + flex: 1 1 auto; +} +.btn-group > .btn-check:checked + .btn, +.btn-group > .btn-check:focus + .btn, +.btn-group > .btn:hover, +.btn-group > .btn:focus, +.btn-group > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn-check:checked + .btn, +.btn-group-vertical > .btn-check:focus + .btn, +.btn-group-vertical > .btn:hover, +.btn-group-vertical > .btn:focus, +.btn-group-vertical > .btn:active, +.btn-group-vertical > .btn.active { + z-index: 1; +} + +.btn-toolbar { + display: flex; + flex-wrap: wrap; + justify-content: flex-start; +} +.btn-toolbar .input-group { + width: auto; +} + +.btn-group > :not(.btn-check:first-child) + .btn, +.btn-group > .btn-group:not(:first-child) { + margin-left: calc(var(--bs-border-width) * -1); +} +.dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; +} +.dropdown-toggle-split::after, .dropup .dropdown-toggle-split::after, .dropend .dropdown-toggle-split::after { + margin-left: 0; +} +.dropstart .dropdown-toggle-split::before { + margin-right: 0; +} + +.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; +} + +.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; +} + +.btn-group-vertical { + flex-direction: column; + align-items: flex-start; + justify-content: center; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group { + width: 100%; +} +.btn-group-vertical > .btn:not(:first-child), +.btn-group-vertical > .btn-group:not(:first-child) { + margin-top: calc(var(--bs-border-width) * -1); +} +.nav { + --bs-nav-link-padding-x: 0.5rem; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-font-weight: ; + --bs-nav-link-color: var(--bs-link-color); + --bs-nav-link-hover-color: var(--bs-link-hover-color); + --bs-nav-link-disabled-color: var(--bs-secondary-color); + display: flex; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav-link { + display: block; + padding: var(--bs-nav-link-padding-y) var(--bs-nav-link-padding-x); + font-size: var(--bs-nav-link-font-size); + font-weight: var(--bs-nav-link-font-weight); + color: var(--bs-nav-link-color); + text-decoration: none; + background: none; + border: 0; +} +.nav-link:hover, .nav-link:focus { + color: var(--bs-nav-link-hover-color); +} +.nav-link:focus-visible { + outline: 0; + box-shadow: 0 0 0 0.25rem rgba(57, 73, 171, 0.25); +} +.nav-link.disabled, .nav-link:disabled { + color: var(--bs-nav-link-disabled-color); + pointer-events: none; + cursor: default; +} + +.nav-tabs { + --bs-nav-tabs-border-width: var(--bs-border-width); + --bs-nav-tabs-border-color: var(--bs-border-color); + --bs-nav-tabs-border-radius: var(--bs-border-radius); + --bs-nav-tabs-link-hover-border-color: var(--bs-secondary-bg) var(--bs-secondary-bg) var(--bs-border-color); + --bs-nav-tabs-link-active-color: var(--bs-emphasis-color); + --bs-nav-tabs-link-active-bg: var(--bs-body-bg); + --bs-nav-tabs-link-active-border-color: var(--bs-border-color) var(--bs-border-color) var(--bs-body-bg); + border-bottom: var(--bs-nav-tabs-border-width) solid var(--bs-nav-tabs-border-color); +} +.nav-tabs .nav-link { + margin-bottom: calc(-1 * var(--bs-nav-tabs-border-width)); + border: var(--bs-nav-tabs-border-width) solid transparent; +} +.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { + isolation: isolate; + border-color: var(--bs-nav-tabs-link-hover-border-color); +} +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + color: var(--bs-nav-tabs-link-active-color); + background-color: var(--bs-nav-tabs-link-active-bg); + border-color: var(--bs-nav-tabs-link-active-border-color); +} +.nav-tabs .dropdown-menu { + margin-top: calc(-1 * var(--bs-nav-tabs-border-width)); +} + +.nav-pills { + --bs-nav-pills-border-radius: var(--bs-border-radius); + --bs-nav-pills-link-active-color: #fff; + --bs-nav-pills-link-active-bg: #3949ab; +} +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: var(--bs-nav-pills-link-active-color); + background-color: var(--bs-nav-pills-link-active-bg); +} + +.nav-underline { + --bs-nav-underline-gap: 1rem; + --bs-nav-underline-border-width: 0.125rem; + --bs-nav-underline-link-active-color: var(--bs-emphasis-color); + gap: var(--bs-nav-underline-gap); +} +.nav-underline .nav-link { + padding-right: 0; + padding-left: 0; + border-bottom: var(--bs-nav-underline-border-width) solid transparent; +} +.nav-underline .nav-link:hover, .nav-underline .nav-link:focus { + border-bottom-color: currentcolor; +} +.nav-underline .nav-link.active, +.nav-underline .show > .nav-link { + font-weight: 700; + color: var(--bs-nav-underline-link-active-color); + border-bottom-color: currentcolor; +} + +.nav-fill > .nav-link, +.nav-fill .nav-item { + flex: 1 1 auto; + text-align: center; +} + +.nav-justified > .nav-link, +.nav-justified .nav-item { + flex-basis: 0; + flex-grow: 1; + text-align: center; +} + +.nav-fill .nav-item .nav-link, +.nav-justified .nav-item .nav-link { + width: 100%; +} + +.tab-content > .tab-pane { + display: none; +} +.tab-content > .active { + display: block; +} + +.navbar { + --bs-navbar-padding-x: 0; + --bs-navbar-padding-y: 0.5rem; + --bs-navbar-color: rgba(var(--bs-emphasis-color-rgb), 0.65); + --bs-navbar-hover-color: rgba(var(--bs-emphasis-color-rgb), 0.8); + --bs-navbar-disabled-color: rgba(var(--bs-emphasis-color-rgb), 0.3); + --bs-navbar-active-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-brand-padding-y: 0.5rem; + --bs-navbar-brand-margin-end: 1rem; + --bs-navbar-brand-font-size: 0.875rem; + --bs-navbar-brand-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-brand-hover-color: rgba(var(--bs-emphasis-color-rgb), 1); + --bs-navbar-nav-link-padding-x: 0.5rem; + --bs-navbar-toggler-padding-y: 0.25rem; + --bs-navbar-toggler-padding-x: 0.75rem; + --bs-navbar-toggler-font-size: 0.875rem; + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%2833, 37, 41, 0.75%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); + --bs-navbar-toggler-border-color: rgba(var(--bs-emphasis-color-rgb), 0.15); + --bs-navbar-toggler-border-radius: var(--bs-border-radius); + --bs-navbar-toggler-focus-width: 0.25rem; + --bs-navbar-toggler-transition: box-shadow 0.15s ease-in-out; + position: relative; + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + padding: var(--bs-navbar-padding-y) var(--bs-navbar-padding-x); +} +.navbar > .container, +.navbar > .container-fluid, +.navbar > .container-sm, +.navbar > .container-md, +.navbar > .container-lg, +.navbar > .container-xl, +.navbar > .container-xxl { + display: flex; + flex-wrap: inherit; + align-items: center; + justify-content: space-between; +} +.navbar-brand { + padding-top: var(--bs-navbar-brand-padding-y); + padding-bottom: var(--bs-navbar-brand-padding-y); + margin-right: var(--bs-navbar-brand-margin-end); + font-size: var(--bs-navbar-brand-font-size); + color: var(--bs-navbar-brand-color); + text-decoration: none; + white-space: nowrap; +} +.navbar-brand:hover, .navbar-brand:focus { + color: var(--bs-navbar-brand-hover-color); +} + +.navbar-nav { + --bs-nav-link-padding-x: 0; + --bs-nav-link-padding-y: 0.5rem; + --bs-nav-link-font-weight: ; + --bs-nav-link-color: var(--bs-navbar-color); + --bs-nav-link-hover-color: var(--bs-navbar-hover-color); + --bs-nav-link-disabled-color: var(--bs-navbar-disabled-color); + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.navbar-nav .nav-link.active, .navbar-nav .nav-link.show { + color: var(--bs-navbar-active-color); +} +.navbar-nav .dropdown-menu { + position: static; +} + +.navbar-text { + padding-top: 0.5rem; + padding-bottom: 0.5rem; + color: var(--bs-navbar-color); +} +.navbar-text a, +.navbar-text a:hover, +.navbar-text a:focus { + color: var(--bs-navbar-active-color); +} + +.navbar-collapse { + flex-basis: 100%; + flex-grow: 1; + align-items: center; +} + +.navbar-toggler { + padding: var(--bs-navbar-toggler-padding-y) var(--bs-navbar-toggler-padding-x); + font-size: var(--bs-navbar-toggler-font-size); + line-height: 1; + color: var(--bs-navbar-color); + background-color: transparent; + border: var(--bs-border-width) solid var(--bs-navbar-toggler-border-color); +} +.navbar-toggler:hover { + text-decoration: none; +} +.navbar-toggler:focus { + text-decoration: none; + outline: 0; + box-shadow: 0 0 0 var(--bs-navbar-toggler-focus-width); +} + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + background-image: var(--bs-navbar-toggler-icon-bg); + background-repeat: no-repeat; + background-position: center; + background-size: 100%; +} + +.navbar-nav-scroll { + max-height: var(--bs-scroll-height, 75vh); + overflow-y: auto; +} + +@media (min-width: 576px) { + .navbar-expand-sm { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-sm .navbar-nav { + flex-direction: row; + } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-sm .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-sm .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-sm .navbar-toggler { + display: none; + } + .navbar-expand-sm .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + } + .navbar-expand-sm .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-sm .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 768px) { + .navbar-expand-md { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-md .navbar-nav { + flex-direction: row; + } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-md .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-md .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-md .navbar-toggler { + display: none; + } + .navbar-expand-md .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + } + .navbar-expand-md .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-md .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 992px) { + .navbar-expand-lg { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-lg .navbar-nav { + flex-direction: row; + } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-lg .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-lg .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-lg .navbar-toggler { + display: none; + } + .navbar-expand-lg .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + } + .navbar-expand-lg .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-lg .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 1200px) { + .navbar-expand-xl { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-xl .navbar-nav { + flex-direction: row; + } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-xl .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-xl .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-xl .navbar-toggler { + display: none; + } + .navbar-expand-xl .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + } + .navbar-expand-xl .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-xl .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +@media (min-width: 1400px) { + .navbar-expand-xxl { + flex-wrap: nowrap; + justify-content: flex-start; + } + .navbar-expand-xxl .navbar-nav { + flex-direction: row; + } + .navbar-expand-xxl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xxl .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); + } + .navbar-expand-xxl .navbar-nav-scroll { + overflow: visible; + } + .navbar-expand-xxl .navbar-collapse { + display: flex !important; + flex-basis: auto; + } + .navbar-expand-xxl .navbar-toggler { + display: none; + } + .navbar-expand-xxl .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; + } + .navbar-expand-xxl .offcanvas .offcanvas-header { + display: none; + } + .navbar-expand-xxl .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + } +} +.navbar-expand { + flex-wrap: nowrap; + justify-content: flex-start; +} +.navbar-expand .navbar-nav { + flex-direction: row; +} +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute; +} +.navbar-expand .navbar-nav .nav-link { + padding-right: var(--bs-navbar-nav-link-padding-x); + padding-left: var(--bs-navbar-nav-link-padding-x); +} +.navbar-expand .navbar-nav-scroll { + overflow: visible; +} +.navbar-expand .navbar-collapse { + display: flex !important; + flex-basis: auto; +} +.navbar-expand .navbar-toggler { + display: none; +} +.navbar-expand .offcanvas { + position: static; + z-index: auto; + flex-grow: 1; + width: auto !important; + height: auto !important; + visibility: visible !important; + background-color: transparent !important; + border: 0 !important; + transform: none !important; +} +.navbar-expand .offcanvas .offcanvas-header { + display: none; +} +.navbar-expand .offcanvas .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; +} + +.navbar-dark, +.navbar[data-bs-theme=dark] { + --bs-navbar-color: rgba(255, 255, 255, 0.55); + --bs-navbar-hover-color: rgba(255, 255, 255, 0.75); + --bs-navbar-disabled-color: rgba(255, 255, 255, 0.25); + --bs-navbar-active-color: #fff; + --bs-navbar-brand-color: #fff; + --bs-navbar-brand-hover-color: #fff; + --bs-navbar-toggler-border-color: rgba(255, 255, 255, 0.1); + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} + +[data-bs-theme=dark] .navbar-toggler-icon { + --bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} + +.card { + --bs-card-spacer-y: 1rem; + --bs-card-spacer-x: 1rem; + --bs-card-title-spacer-y: 0.5rem; + --bs-card-title-color: ; + --bs-card-subtitle-color: ; + --bs-card-border-width: var(--bs-border-width); + --bs-card-border-color: var(--bs-border-color-translucent); + --bs-card-border-radius: var(--bs-border-radius); + --bs-card-box-shadow: ; + --bs-card-inner-border-radius: calc(var(--bs-border-radius) - (var(--bs-border-width))); + --bs-card-cap-padding-y: 0.5rem; + --bs-card-cap-padding-x: 1rem; + --bs-card-cap-bg: rgba(var(--bs-body-color-rgb), 0.03); + --bs-card-cap-color: ; + --bs-card-height: ; + --bs-card-color: ; + --bs-card-bg: var(--bs-body-bg); + --bs-card-img-overlay-padding: 1rem; + --bs-card-group-margin: 0.75rem; + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + height: var(--bs-card-height); + color: var(--bs-body-color); + word-wrap: break-word; + background-color: var(--bs-card-bg); + background-clip: border-box; + border: var(--bs-card-border-width) solid var(--bs-card-border-color); +} +.card > hr { + margin-right: 0; + margin-left: 0; +} +.card > .list-group { + border-top: inherit; + border-bottom: inherit; +} +.card > .list-group:first-child { + border-top-width: 0; +} +.card > .list-group:last-child { + border-bottom-width: 0; +} +.card > .card-header + .list-group, +.card > .list-group + .card-footer { + border-top: 0; +} + +.card-body { + flex: 1 1 auto; + padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x); + color: var(--bs-card-color); +} + +.card-title { + margin-bottom: var(--bs-card-title-spacer-y); + color: var(--bs-card-title-color); +} + +.card-subtitle { + margin-top: calc(-0.5 * var(--bs-card-title-spacer-y)); + margin-bottom: 0; + color: var(--bs-card-subtitle-color); +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link + .card-link { + margin-left: var(--bs-card-spacer-x); +} + +.card-header { + padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x); + margin-bottom: 0; + color: var(--bs-card-cap-color); + background-color: var(--bs-card-cap-bg); + border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color); +} +.card-footer { + padding: var(--bs-card-cap-padding-y) var(--bs-card-cap-padding-x); + color: var(--bs-card-cap-color); + background-color: var(--bs-card-cap-bg); + border-top: var(--bs-card-border-width) solid var(--bs-card-border-color); +} +.card-header-tabs { + margin-right: calc(-0.5 * var(--bs-card-cap-padding-x)); + margin-bottom: calc(-1 * var(--bs-card-cap-padding-y)); + margin-left: calc(-0.5 * var(--bs-card-cap-padding-x)); + border-bottom: 0; +} +.card-header-tabs .nav-link.active { + background-color: var(--bs-card-bg); + border-bottom-color: var(--bs-card-bg); +} + +.card-header-pills { + margin-right: calc(-0.5 * var(--bs-card-cap-padding-x)); + margin-left: calc(-0.5 * var(--bs-card-cap-padding-x)); +} + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: var(--bs-card-img-overlay-padding); +} + +.card-img, +.card-img-top, +.card-img-bottom { + width: 100%; +} + +.card-group > .card { + margin-bottom: var(--bs-card-group-margin); +} +@media (min-width: 576px) { + .card-group { + display: flex; + flex-flow: row wrap; + } + .card-group > .card { + flex: 1 0 0%; + margin-bottom: 0; + } + .card-group > .card + .card { + margin-left: 0; + border-left: 0; + } +} + +.accordion { + --bs-accordion-color: var(--bs-body-color); + --bs-accordion-bg: transparent; + --bs-accordion-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, border-radius 0.15s ease; + --bs-accordion-border-color: var(--bs-border-color); + --bs-accordion-border-width: 0; + --bs-accordion-border-radius: var(--bs-border-radius); + --bs-accordion-inner-border-radius: calc(var(--bs-border-radius) - 0); + --bs-accordion-btn-padding-x: 0; + --bs-accordion-btn-padding-y: 1rem; + --bs-accordion-btn-color: var(--bs-body-color); + --bs-accordion-btn-bg: var(--bs-accordion-bg); + --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23212529' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e"); + --bs-accordion-btn-icon-width: 0.75rem; + --bs-accordion-btn-icon-transform: rotate(-180deg); + --bs-accordion-btn-icon-transition: transform 0.2s ease-in-out; + --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%23171d44' stroke-linecap='round' stroke-linejoin='round'%3e%3cpath d='M2 5L8 11L14 5'/%3e%3c/svg%3e"); + --bs-accordion-btn-focus-box-shadow: 0 0 0 0.25rem rgba(57, 73, 171, 0.25); + --bs-accordion-body-padding-x: 0; + --bs-accordion-body-padding-y: 0; + --bs-accordion-active-color: var(--bs-primary-text-emphasis); + --bs-accordion-active-bg: var(--bs-primary-bg-subtle); +} + +.accordion-button { + position: relative; + display: flex; + align-items: center; + width: 100%; + padding: var(--bs-accordion-btn-padding-y) var(--bs-accordion-btn-padding-x); + font-size: 0.875rem; + color: var(--bs-accordion-btn-color); + text-align: left; + background-color: var(--bs-accordion-btn-bg); + border: 0; + overflow-anchor: none; +} +.accordion-button:not(.collapsed) { + color: var(--bs-accordion-active-color); + background-color: var(--bs-accordion-active-bg); + box-shadow: inset 0 calc(-1 * var(--bs-accordion-border-width)) 0 var(--bs-accordion-border-color); +} +.accordion-button:not(.collapsed)::after { + background-image: var(--bs-accordion-btn-active-icon); + transform: var(--bs-accordion-btn-icon-transform); +} +.accordion-button::after { + flex-shrink: 0; + width: var(--bs-accordion-btn-icon-width); + height: var(--bs-accordion-btn-icon-width); + margin-left: auto; + content: ""; + background-image: var(--bs-accordion-btn-icon); + background-repeat: no-repeat; + background-size: var(--bs-accordion-btn-icon-width); +} +.accordion-button:hover { + z-index: 2; +} +.accordion-button:focus { + z-index: 3; + outline: 0; + box-shadow: var(--bs-accordion-btn-focus-box-shadow); +} + +.accordion-header { + margin-bottom: 0; +} + +.accordion-item { + color: var(--bs-accordion-color); + background-color: var(--bs-accordion-bg); + border: var(--bs-accordion-border-width) solid var(--bs-accordion-border-color); +} +.accordion-item:not(:first-of-type) { + border-top: 0; +} +.accordion-body { + padding: var(--bs-accordion-body-padding-y) var(--bs-accordion-body-padding-x); +} + +.accordion-flush > .accordion-item { + border-right: 0; + border-left: 0; +} +.accordion-flush > .accordion-item:first-child { + border-top: 0; +} +.accordion-flush > .accordion-item:last-child { + border-bottom: 0; +} +[data-bs-theme=dark] .accordion-button::after { + --bs-accordion-btn-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%238892cd'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); + --bs-accordion-btn-active-icon: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%238892cd'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e"); +} + +.breadcrumb { + --bs-breadcrumb-padding-x: 0; + --bs-breadcrumb-padding-y: 0; + --bs-breadcrumb-margin-bottom: 1rem; + --bs-breadcrumb-bg: ; + --bs-breadcrumb-border-radius: ; + --bs-breadcrumb-divider-color: var(--bs-secondary-color); + --bs-breadcrumb-item-padding-x: 0.5rem; + --bs-breadcrumb-item-active-color: var(--bs-secondary-color); + display: flex; + flex-wrap: wrap; + padding: var(--bs-breadcrumb-padding-y) var(--bs-breadcrumb-padding-x); + margin-bottom: var(--bs-breadcrumb-margin-bottom); + font-size: var(--bs-breadcrumb-font-size); + list-style: none; + background-color: var(--bs-breadcrumb-bg); +} + +.breadcrumb-item + .breadcrumb-item { + padding-left: var(--bs-breadcrumb-item-padding-x); +} +.breadcrumb-item + .breadcrumb-item::before { + float: left; + padding-right: var(--bs-breadcrumb-item-padding-x); + color: var(--bs-breadcrumb-divider-color); + content: var(--bs-breadcrumb-divider, "/") /* rtl: var(--bs-breadcrumb-divider, "/") */; +} +.breadcrumb-item.active { + color: var(--bs-breadcrumb-item-active-color); +} + +.pagination { + --bs-pagination-padding-x: 0.75rem; + --bs-pagination-padding-y: 0.375rem; + --bs-pagination-font-size: 0.875rem; + --bs-pagination-color: var(--bs-link-color); + --bs-pagination-bg: var(--bs-body-bg); + --bs-pagination-border-width: var(--bs-border-width); + --bs-pagination-border-color: var(--bs-border-color); + --bs-pagination-border-radius: var(--bs-border-radius); + --bs-pagination-hover-color: var(--bs-link-hover-color); + --bs-pagination-hover-bg: var(--bs-tertiary-bg); + --bs-pagination-hover-border-color: var(--bs-border-color); + --bs-pagination-focus-color: var(--bs-link-hover-color); + --bs-pagination-focus-bg: var(--bs-secondary-bg); + --bs-pagination-focus-box-shadow: 0 0 0 0.25rem rgba(57, 73, 171, 0.25); + --bs-pagination-active-color: #fff; + --bs-pagination-active-bg: #3949ab; + --bs-pagination-active-border-color: #3949ab; + --bs-pagination-disabled-color: var(--bs-secondary-color); + --bs-pagination-disabled-bg: var(--bs-secondary-bg); + --bs-pagination-disabled-border-color: var(--bs-border-color); + display: flex; + padding-left: 0; + list-style: none; +} + +.page-link { + position: relative; + display: block; + padding: var(--bs-pagination-padding-y) var(--bs-pagination-padding-x); + font-size: var(--bs-pagination-font-size); + color: var(--bs-pagination-color); + text-decoration: none; + background-color: var(--bs-pagination-bg); + border: var(--bs-pagination-border-width) solid var(--bs-pagination-border-color); +} +.page-link:hover { + z-index: 2; + color: var(--bs-pagination-hover-color); + background-color: var(--bs-pagination-hover-bg); + border-color: var(--bs-pagination-hover-border-color); +} +.page-link:focus { + z-index: 3; + color: var(--bs-pagination-focus-color); + background-color: var(--bs-pagination-focus-bg); + outline: 0; + box-shadow: var(--bs-pagination-focus-box-shadow); +} +.page-link.active, .active > .page-link { + z-index: 3; + color: var(--bs-pagination-active-color); + background-color: var(--bs-pagination-active-bg); + border-color: var(--bs-pagination-active-border-color); +} +.page-link.disabled, .disabled > .page-link { + color: var(--bs-pagination-disabled-color); + pointer-events: none; + background-color: var(--bs-pagination-disabled-bg); + border-color: var(--bs-pagination-disabled-border-color); +} + +.page-item:not(:first-child) .page-link { + margin-left: calc(var(--bs-border-width) * -1); +} +.pagination-lg { + --bs-pagination-padding-x: 1.5rem; + --bs-pagination-padding-y: 0.75rem; + --bs-pagination-font-size: 0.875rem; + --bs-pagination-border-radius: var(--bs-border-radius-lg); +} + +.pagination-sm { + --bs-pagination-padding-x: 0.5rem; + --bs-pagination-padding-y: 0.25rem; + --bs-pagination-font-size: 0.65625rem; + --bs-pagination-border-radius: var(--bs-border-radius-sm); +} + +.badge { + --bs-badge-padding-x: 0.65em; + --bs-badge-padding-y: 0.35em; + --bs-badge-font-size: 0.75em; + --bs-badge-font-weight: 700; + --bs-badge-color: #fff; + --bs-badge-border-radius: var(--bs-border-radius); + display: inline-block; + padding: var(--bs-badge-padding-y) var(--bs-badge-padding-x); + font-size: var(--bs-badge-font-size); + font-weight: var(--bs-badge-font-weight); + line-height: 1; + color: var(--bs-badge-color); + text-align: center; + white-space: nowrap; + vertical-align: baseline; +} +.badge:empty { + display: none; +} + +.btn .badge { + position: relative; + top: -1px; +} + +.alert { + --bs-alert-bg: transparent; + --bs-alert-padding-x: 1rem; + --bs-alert-padding-y: 1rem; + --bs-alert-margin-bottom: 1rem; + --bs-alert-color: inherit; + --bs-alert-border-color: transparent; + --bs-alert-border: var(--bs-border-width) solid var(--bs-alert-border-color); + --bs-alert-border-radius: var(--bs-border-radius); + --bs-alert-link-color: inherit; + position: relative; + padding: var(--bs-alert-padding-y) var(--bs-alert-padding-x); + margin-bottom: var(--bs-alert-margin-bottom); + color: var(--bs-alert-color); + background-color: var(--bs-alert-bg); + border: var(--bs-alert-border); +} + +.alert-heading { + color: inherit; +} + +.alert-link { + font-weight: 700; + color: var(--bs-alert-link-color); +} + +.alert-dismissible { + padding-right: 3rem; +} +.alert-dismissible .btn-close { + position: absolute; + top: 0; + right: 0; + z-index: 2; + padding: 1.25rem 1rem; +} + +.alert-primary { + --bs-alert-color: var(--bs-primary-text-emphasis); + --bs-alert-bg: var(--bs-primary-bg-subtle); + --bs-alert-border-color: var(--bs-primary-border-subtle); + --bs-alert-link-color: var(--bs-primary-text-emphasis); +} + +.alert-secondary { + --bs-alert-color: var(--bs-secondary-text-emphasis); + --bs-alert-bg: var(--bs-secondary-bg-subtle); + --bs-alert-border-color: var(--bs-secondary-border-subtle); + --bs-alert-link-color: var(--bs-secondary-text-emphasis); +} + +.alert-success { + --bs-alert-color: var(--bs-success-text-emphasis); + --bs-alert-bg: var(--bs-success-bg-subtle); + --bs-alert-border-color: var(--bs-success-border-subtle); + --bs-alert-link-color: var(--bs-success-text-emphasis); +} + +.alert-info { + --bs-alert-color: var(--bs-info-text-emphasis); + --bs-alert-bg: var(--bs-info-bg-subtle); + --bs-alert-border-color: var(--bs-info-border-subtle); + --bs-alert-link-color: var(--bs-info-text-emphasis); +} + +.alert-warning { + --bs-alert-color: var(--bs-warning-text-emphasis); + --bs-alert-bg: var(--bs-warning-bg-subtle); + --bs-alert-border-color: var(--bs-warning-border-subtle); + --bs-alert-link-color: var(--bs-warning-text-emphasis); +} + +.alert-danger { + --bs-alert-color: var(--bs-danger-text-emphasis); + --bs-alert-bg: var(--bs-danger-bg-subtle); + --bs-alert-border-color: var(--bs-danger-border-subtle); + --bs-alert-link-color: var(--bs-danger-text-emphasis); +} + +.alert-light { + --bs-alert-color: var(--bs-light-text-emphasis); + --bs-alert-bg: var(--bs-light-bg-subtle); + --bs-alert-border-color: var(--bs-light-border-subtle); + --bs-alert-link-color: var(--bs-light-text-emphasis); +} + +.alert-dark { + --bs-alert-color: var(--bs-dark-text-emphasis); + --bs-alert-bg: var(--bs-dark-bg-subtle); + --bs-alert-border-color: var(--bs-dark-border-subtle); + --bs-alert-link-color: var(--bs-dark-text-emphasis); +} + +.progress, +.progress-stacked { + --bs-progress-height: 1rem; + --bs-progress-font-size: 0.65625rem; + --bs-progress-bg: var(--bs-secondary-bg); + --bs-progress-border-radius: var(--bs-border-radius); + --bs-progress-box-shadow: var(--bs-box-shadow-inset); + --bs-progress-bar-color: #fff; + --bs-progress-bar-bg: #3949ab; + --bs-progress-bar-transition: width 0.6s ease; + display: flex; + height: var(--bs-progress-height); + overflow: hidden; + font-size: var(--bs-progress-font-size); + background-color: var(--bs-progress-bg); +} + +.progress-bar { + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + color: var(--bs-progress-bar-color); + text-align: center; + white-space: nowrap; + background-color: var(--bs-progress-bar-bg); +} + +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: var(--bs-progress-height) var(--bs-progress-height); +} + +.progress-stacked > .progress { + overflow: visible; +} + +.progress-stacked > .progress > .progress-bar { + width: 100%; +} + +.list-group { + --bs-list-group-color: var(--bs-body-color); + --bs-list-group-bg: var(--bs-body-bg); + --bs-list-group-border-color: var(--bs-border-color); + --bs-list-group-border-width: var(--bs-border-width); + --bs-list-group-border-radius: var(--bs-border-radius); + --bs-list-group-item-padding-x: 1rem; + --bs-list-group-item-padding-y: 0.5rem; + --bs-list-group-action-color: var(--bs-secondary-color); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-tertiary-bg); + --bs-list-group-action-active-color: var(--bs-body-color); + --bs-list-group-action-active-bg: var(--bs-secondary-bg); + --bs-list-group-disabled-color: var(--bs-secondary-color); + --bs-list-group-disabled-bg: var(--bs-body-bg); + --bs-list-group-active-color: #fff; + --bs-list-group-active-bg: #3949ab; + --bs-list-group-active-border-color: #3949ab; + display: flex; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; +} + +.list-group-numbered { + list-style-type: none; + counter-reset: section; +} +.list-group-numbered > .list-group-item::before { + content: counters(section, ".") ". "; + counter-increment: section; +} + +.list-group-item-action { + width: 100%; + color: var(--bs-list-group-action-color); + text-align: inherit; +} +.list-group-item-action:hover, .list-group-item-action:focus { + z-index: 1; + color: var(--bs-list-group-action-hover-color); + text-decoration: none; + background-color: var(--bs-list-group-action-hover-bg); +} +.list-group-item-action:active { + color: var(--bs-list-group-action-active-color); + background-color: var(--bs-list-group-action-active-bg); +} + +.list-group-item { + position: relative; + display: block; + padding: var(--bs-list-group-item-padding-y) var(--bs-list-group-item-padding-x); + color: var(--bs-list-group-color); + text-decoration: none; + background-color: var(--bs-list-group-bg); + border: var(--bs-list-group-border-width) solid var(--bs-list-group-border-color); +} +.list-group-item.disabled, .list-group-item:disabled { + color: var(--bs-list-group-disabled-color); + pointer-events: none; + background-color: var(--bs-list-group-disabled-bg); +} +.list-group-item.active { + z-index: 2; + color: var(--bs-list-group-active-color); + background-color: var(--bs-list-group-active-bg); + border-color: var(--bs-list-group-active-border-color); +} +.list-group-item + .list-group-item { + border-top-width: 0; +} +.list-group-item + .list-group-item.active { + margin-top: calc(-1 * var(--bs-list-group-border-width)); + border-top-width: var(--bs-list-group-border-width); +} + +.list-group-horizontal { + flex-direction: row; +} +.list-group-horizontal > .list-group-item.active { + margin-top: 0; +} +.list-group-horizontal > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; +} +.list-group-horizontal > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); +} + +@media (min-width: 576px) { + .list-group-horizontal-sm { + flex-direction: row; + } + .list-group-horizontal-sm > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-sm > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-sm > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 768px) { + .list-group-horizontal-md { + flex-direction: row; + } + .list-group-horizontal-md > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-md > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-md > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 992px) { + .list-group-horizontal-lg { + flex-direction: row; + } + .list-group-horizontal-lg > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-lg > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-lg > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 1200px) { + .list-group-horizontal-xl { + flex-direction: row; + } + .list-group-horizontal-xl > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-xl > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-xl > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +@media (min-width: 1400px) { + .list-group-horizontal-xxl { + flex-direction: row; + } + .list-group-horizontal-xxl > .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-xxl > .list-group-item + .list-group-item { + border-top-width: var(--bs-list-group-border-width); + border-left-width: 0; + } + .list-group-horizontal-xxl > .list-group-item + .list-group-item.active { + margin-left: calc(-1 * var(--bs-list-group-border-width)); + border-left-width: var(--bs-list-group-border-width); + } +} +.list-group-flush > .list-group-item { + border-width: 0 0 var(--bs-list-group-border-width); +} +.list-group-flush > .list-group-item:last-child { + border-bottom-width: 0; +} + +.list-group-item-primary { + --bs-list-group-color: var(--bs-primary-text-emphasis); + --bs-list-group-bg: var(--bs-primary-bg-subtle); + --bs-list-group-border-color: var(--bs-primary-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-primary-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-primary-border-subtle); + --bs-list-group-active-color: var(--bs-primary-bg-subtle); + --bs-list-group-active-bg: var(--bs-primary-text-emphasis); + --bs-list-group-active-border-color: var(--bs-primary-text-emphasis); +} + +.list-group-item-secondary { + --bs-list-group-color: var(--bs-secondary-text-emphasis); + --bs-list-group-bg: var(--bs-secondary-bg-subtle); + --bs-list-group-border-color: var(--bs-secondary-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-secondary-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-secondary-border-subtle); + --bs-list-group-active-color: var(--bs-secondary-bg-subtle); + --bs-list-group-active-bg: var(--bs-secondary-text-emphasis); + --bs-list-group-active-border-color: var(--bs-secondary-text-emphasis); +} + +.list-group-item-success { + --bs-list-group-color: var(--bs-success-text-emphasis); + --bs-list-group-bg: var(--bs-success-bg-subtle); + --bs-list-group-border-color: var(--bs-success-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-success-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-success-border-subtle); + --bs-list-group-active-color: var(--bs-success-bg-subtle); + --bs-list-group-active-bg: var(--bs-success-text-emphasis); + --bs-list-group-active-border-color: var(--bs-success-text-emphasis); +} + +.list-group-item-info { + --bs-list-group-color: var(--bs-info-text-emphasis); + --bs-list-group-bg: var(--bs-info-bg-subtle); + --bs-list-group-border-color: var(--bs-info-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-info-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-info-border-subtle); + --bs-list-group-active-color: var(--bs-info-bg-subtle); + --bs-list-group-active-bg: var(--bs-info-text-emphasis); + --bs-list-group-active-border-color: var(--bs-info-text-emphasis); +} + +.list-group-item-warning { + --bs-list-group-color: var(--bs-warning-text-emphasis); + --bs-list-group-bg: var(--bs-warning-bg-subtle); + --bs-list-group-border-color: var(--bs-warning-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-warning-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-warning-border-subtle); + --bs-list-group-active-color: var(--bs-warning-bg-subtle); + --bs-list-group-active-bg: var(--bs-warning-text-emphasis); + --bs-list-group-active-border-color: var(--bs-warning-text-emphasis); +} + +.list-group-item-danger { + --bs-list-group-color: var(--bs-danger-text-emphasis); + --bs-list-group-bg: var(--bs-danger-bg-subtle); + --bs-list-group-border-color: var(--bs-danger-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-danger-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-danger-border-subtle); + --bs-list-group-active-color: var(--bs-danger-bg-subtle); + --bs-list-group-active-bg: var(--bs-danger-text-emphasis); + --bs-list-group-active-border-color: var(--bs-danger-text-emphasis); +} + +.list-group-item-light { + --bs-list-group-color: var(--bs-light-text-emphasis); + --bs-list-group-bg: var(--bs-light-bg-subtle); + --bs-list-group-border-color: var(--bs-light-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-light-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-light-border-subtle); + --bs-list-group-active-color: var(--bs-light-bg-subtle); + --bs-list-group-active-bg: var(--bs-light-text-emphasis); + --bs-list-group-active-border-color: var(--bs-light-text-emphasis); +} + +.list-group-item-dark { + --bs-list-group-color: var(--bs-dark-text-emphasis); + --bs-list-group-bg: var(--bs-dark-bg-subtle); + --bs-list-group-border-color: var(--bs-dark-border-subtle); + --bs-list-group-action-hover-color: var(--bs-emphasis-color); + --bs-list-group-action-hover-bg: var(--bs-dark-border-subtle); + --bs-list-group-action-active-color: var(--bs-emphasis-color); + --bs-list-group-action-active-bg: var(--bs-dark-border-subtle); + --bs-list-group-active-color: var(--bs-dark-bg-subtle); + --bs-list-group-active-bg: var(--bs-dark-text-emphasis); + --bs-list-group-active-border-color: var(--bs-dark-text-emphasis); +} + +.btn-close { + --bs-btn-close-color: #000; + --bs-btn-close-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/%3e%3c/svg%3e"); + --bs-btn-close-opacity: 0.5; + --bs-btn-close-hover-opacity: 0.75; + --bs-btn-close-focus-shadow: 0 0 0 0.25rem rgba(57, 73, 171, 0.25); + --bs-btn-close-focus-opacity: 1; + --bs-btn-close-disabled-opacity: 0.25; + --bs-btn-close-white-filter: invert(1) grayscale(100%) brightness(200%); + box-sizing: content-box; + width: 1em; + height: 1em; + padding: 0.25em 0.25em; + color: var(--bs-btn-close-color); + background: transparent var(--bs-btn-close-bg) center/1em auto no-repeat; + border: 0; + opacity: var(--bs-btn-close-opacity); +} +.btn-close:hover { + color: var(--bs-btn-close-color); + text-decoration: none; + opacity: var(--bs-btn-close-hover-opacity); +} +.btn-close:focus { + outline: 0; + box-shadow: var(--bs-btn-close-focus-shadow); + opacity: var(--bs-btn-close-focus-opacity); +} +.btn-close:disabled, .btn-close.disabled { + pointer-events: none; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + opacity: var(--bs-btn-close-disabled-opacity); +} + +.btn-close-white { + filter: var(--bs-btn-close-white-filter); +} + +[data-bs-theme=dark] .btn-close { + filter: var(--bs-btn-close-white-filter); +} + +.toast { + --bs-toast-zindex: 1090; + --bs-toast-padding-x: 0.75rem; + --bs-toast-padding-y: 0.5rem; + --bs-toast-spacing: 1.5rem; + --bs-toast-max-width: 350px; + --bs-toast-font-size: 0.875rem; + --bs-toast-color: ; + --bs-toast-bg: rgba(var(--bs-body-bg-rgb), 0.85); + --bs-toast-border-width: var(--bs-border-width); + --bs-toast-border-color: var(--bs-border-color-translucent); + --bs-toast-border-radius: var(--bs-border-radius); + --bs-toast-box-shadow: var(--bs-box-shadow); + --bs-toast-header-color: var(--bs-secondary-color); + --bs-toast-header-bg: rgba(var(--bs-body-bg-rgb), 0.85); + --bs-toast-header-border-color: var(--bs-border-color-translucent); + width: var(--bs-toast-max-width); + max-width: 100%; + font-size: var(--bs-toast-font-size); + color: var(--bs-toast-color); + pointer-events: auto; + background-color: var(--bs-toast-bg); + background-clip: padding-box; + border: var(--bs-toast-border-width) solid var(--bs-toast-border-color); + box-shadow: var(--bs-toast-box-shadow); +} +.toast.showing { + opacity: 0; +} +.toast:not(.show) { + display: none; +} + +.toast-container { + --bs-toast-zindex: 1090; + position: absolute; + z-index: var(--bs-toast-zindex); + width: -moz-max-content; + width: max-content; + max-width: 100%; + pointer-events: none; +} +.toast-container > :not(:last-child) { + margin-bottom: var(--bs-toast-spacing); +} + +.toast-header { + display: flex; + align-items: center; + padding: var(--bs-toast-padding-y) var(--bs-toast-padding-x); + color: var(--bs-toast-header-color); + background-color: var(--bs-toast-header-bg); + background-clip: padding-box; + border-bottom: var(--bs-toast-border-width) solid var(--bs-toast-header-border-color); +} +.toast-header .btn-close { + margin-right: calc(-0.5 * var(--bs-toast-padding-x)); + margin-left: var(--bs-toast-padding-x); +} + +.toast-body { + padding: var(--bs-toast-padding-x); + word-wrap: break-word; +} + +.modal { + --bs-modal-zindex: 1055; + --bs-modal-width: 500px; + --bs-modal-padding: 1rem; + --bs-modal-margin: 0.5rem; + --bs-modal-color: ; + --bs-modal-bg: var(--bs-body-bg); + --bs-modal-border-color: var(--bs-border-color-translucent); + --bs-modal-border-width: var(--bs-border-width); + --bs-modal-border-radius: var(--bs-border-radius-lg); + --bs-modal-box-shadow: var(--bs-box-shadow-sm); + --bs-modal-inner-border-radius: calc(var(--bs-border-radius-lg) - (var(--bs-border-width))); + --bs-modal-header-padding-x: 1rem; + --bs-modal-header-padding-y: 1rem; + --bs-modal-header-padding: 1rem 1rem; + --bs-modal-header-border-color: var(--bs-border-color); + --bs-modal-header-border-width: var(--bs-border-width); + --bs-modal-title-line-height: 1.5; + --bs-modal-footer-gap: 0.5rem; + --bs-modal-footer-bg: ; + --bs-modal-footer-border-color: var(--bs-border-color); + --bs-modal-footer-border-width: var(--bs-border-width); + position: fixed; + top: 0; + left: 0; + z-index: var(--bs-modal-zindex); + display: none; + width: 100%; + height: 100%; + overflow-x: hidden; + overflow-y: auto; + outline: 0; +} + +.modal-dialog { + position: relative; + width: auto; + margin: var(--bs-modal-margin); + pointer-events: none; +} +.modal.fade .modal-dialog { + transform: translate(0, -50px); +} +.modal.show .modal-dialog { + transform: none; +} +.modal.modal-static .modal-dialog { + transform: scale(1.02); +} + +.modal-dialog-scrollable { + height: calc(100% - var(--bs-modal-margin) * 2); +} +.modal-dialog-scrollable .modal-content { + max-height: 100%; + overflow: hidden; +} +.modal-dialog-scrollable .modal-body { + overflow-y: auto; +} + +.modal-dialog-centered { + display: flex; + align-items: center; + min-height: calc(100% - var(--bs-modal-margin) * 2); +} + +.modal-content { + position: relative; + display: flex; + flex-direction: column; + width: 100%; + color: var(--bs-modal-color); + pointer-events: auto; + background-color: var(--bs-modal-bg); + background-clip: padding-box; + border: var(--bs-modal-border-width) solid var(--bs-modal-border-color); + outline: 0; +} + +.modal-backdrop { + --bs-backdrop-zindex: 1050; + --bs-backdrop-bg: #000; + --bs-backdrop-opacity: 0.5; + position: fixed; + top: 0; + left: 0; + z-index: var(--bs-backdrop-zindex); + width: 100vw; + height: 100vh; + background-color: var(--bs-backdrop-bg); +} +.modal-backdrop.fade { + opacity: 0; +} +.modal-backdrop.show { + opacity: var(--bs-backdrop-opacity); +} + +.modal-header { + display: flex; + flex-shrink: 0; + align-items: center; + padding: var(--bs-modal-header-padding); + border-bottom: var(--bs-modal-header-border-width) solid var(--bs-modal-header-border-color); +} +.modal-header .btn-close { + padding: calc(var(--bs-modal-header-padding-y) * 0.5) calc(var(--bs-modal-header-padding-x) * 0.5); + margin: calc(-0.5 * var(--bs-modal-header-padding-y)) calc(-0.5 * var(--bs-modal-header-padding-x)) calc(-0.5 * var(--bs-modal-header-padding-y)) auto; +} + +.modal-title { + margin-bottom: 0; + line-height: var(--bs-modal-title-line-height); +} + +.modal-body { + position: relative; + flex: 1 1 auto; + padding: var(--bs-modal-padding); +} + +.modal-footer { + display: flex; + flex-shrink: 0; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + padding: calc(var(--bs-modal-padding) - var(--bs-modal-footer-gap) * 0.5); + background-color: var(--bs-modal-footer-bg); + border-top: var(--bs-modal-footer-border-width) solid var(--bs-modal-footer-border-color); +} +.modal-footer > * { + margin: calc(var(--bs-modal-footer-gap) * 0.5); +} + +@media (min-width: 576px) { + .modal { + --bs-modal-margin: 1.75rem; + --bs-modal-box-shadow: var(--bs-box-shadow); + } + .modal-dialog { + max-width: var(--bs-modal-width); + margin-right: auto; + margin-left: auto; + } + .modal-sm { + --bs-modal-width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg, + .modal-xl { + --bs-modal-width: 800px; + } +} +@media (min-width: 1200px) { + .modal-xl { + --bs-modal-width: 1140px; + } +} +.modal-fullscreen { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; +} +.modal-fullscreen .modal-content { + height: 100%; + border: 0; +} +.modal-fullscreen .modal-body { + overflow-y: auto; +} + +@media (max-width: 575.98px) { + .modal-fullscreen-sm-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-sm-down .modal-content { + height: 100%; + border: 0; + } + .modal-fullscreen-sm-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 767.98px) { + .modal-fullscreen-md-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-md-down .modal-content { + height: 100%; + border: 0; + } + .modal-fullscreen-md-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 991.98px) { + .modal-fullscreen-lg-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-lg-down .modal-content { + height: 100%; + border: 0; + } + .modal-fullscreen-lg-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 1199.98px) { + .modal-fullscreen-xl-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-xl-down .modal-content { + height: 100%; + border: 0; + } + .modal-fullscreen-xl-down .modal-body { + overflow-y: auto; + } +} +@media (max-width: 1399.98px) { + .modal-fullscreen-xxl-down { + width: 100vw; + max-width: none; + height: 100%; + margin: 0; + } + .modal-fullscreen-xxl-down .modal-content { + height: 100%; + border: 0; + } + .modal-fullscreen-xxl-down .modal-body { + overflow-y: auto; + } +} +.tooltip { + --bs-tooltip-zindex: 1080; + --bs-tooltip-max-width: 200px; + --bs-tooltip-padding-x: 0.5rem; + --bs-tooltip-padding-y: 0.25rem; + --bs-tooltip-margin: ; + --bs-tooltip-font-size: 0.65625rem; + --bs-tooltip-color: var(--bs-body-bg); + --bs-tooltip-bg: var(--bs-emphasis-color); + --bs-tooltip-border-radius: var(--bs-border-radius); + --bs-tooltip-opacity: 0.9; + --bs-tooltip-arrow-width: 0.8rem; + --bs-tooltip-arrow-height: 0.4rem; + z-index: var(--bs-tooltip-zindex); + display: block; + margin: var(--bs-tooltip-margin); + font-family: var(--bs-font-sans-serif); + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + white-space: normal; + word-spacing: normal; + line-break: auto; + font-size: var(--bs-tooltip-font-size); + word-wrap: break-word; + opacity: 0; +} +.tooltip.show { + opacity: var(--bs-tooltip-opacity); +} +.tooltip .tooltip-arrow { + display: block; + width: var(--bs-tooltip-arrow-width); + height: var(--bs-tooltip-arrow-height); +} +.tooltip .tooltip-arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; +} + +.bs-tooltip-top .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow { + bottom: calc(-1 * var(--bs-tooltip-arrow-height)); +} +.bs-tooltip-top .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before { + top: -1px; + border-width: var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0; + border-top-color: var(--bs-tooltip-bg); +} + +/* rtl:begin:ignore */ +.bs-tooltip-end .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow { + left: calc(-1 * var(--bs-tooltip-arrow-height)); + width: var(--bs-tooltip-arrow-height); + height: var(--bs-tooltip-arrow-width); +} +.bs-tooltip-end .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before { + right: -1px; + border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height) calc(var(--bs-tooltip-arrow-width) * 0.5) 0; + border-right-color: var(--bs-tooltip-bg); +} + +/* rtl:end:ignore */ +.bs-tooltip-bottom .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow { + top: calc(-1 * var(--bs-tooltip-arrow-height)); +} +.bs-tooltip-bottom .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before { + bottom: -1px; + border-width: 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height); + border-bottom-color: var(--bs-tooltip-bg); +} + +/* rtl:begin:ignore */ +.bs-tooltip-start .tooltip-arrow, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow { + right: calc(-1 * var(--bs-tooltip-arrow-height)); + width: var(--bs-tooltip-arrow-height); + height: var(--bs-tooltip-arrow-width); +} +.bs-tooltip-start .tooltip-arrow::before, .bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before { + left: -1px; + border-width: calc(var(--bs-tooltip-arrow-width) * 0.5) 0 calc(var(--bs-tooltip-arrow-width) * 0.5) var(--bs-tooltip-arrow-height); + border-left-color: var(--bs-tooltip-bg); +} + +/* rtl:end:ignore */ +.tooltip-inner { + max-width: var(--bs-tooltip-max-width); + padding: var(--bs-tooltip-padding-y) var(--bs-tooltip-padding-x); + color: var(--bs-tooltip-color); + text-align: center; + background-color: var(--bs-tooltip-bg); +} + +.popover { + --bs-popover-zindex: 1070; + --bs-popover-max-width: 276px; + --bs-popover-font-size: 0.65625rem; + --bs-popover-bg: var(--bs-body-bg); + --bs-popover-border-width: var(--bs-border-width); + --bs-popover-border-color: var(--bs-border-color-translucent); + --bs-popover-border-radius: var(--bs-border-radius-lg); + --bs-popover-inner-border-radius: calc(var(--bs-border-radius-lg) - var(--bs-border-width)); + --bs-popover-box-shadow: var(--bs-box-shadow); + --bs-popover-header-padding-x: 1rem; + --bs-popover-header-padding-y: 0.5rem; + --bs-popover-header-font-size: 0.875rem; + --bs-popover-header-color: inherit; + --bs-popover-header-bg: var(--bs-secondary-bg); + --bs-popover-body-padding-x: 1rem; + --bs-popover-body-padding-y: 1rem; + --bs-popover-body-color: var(--bs-body-color); + --bs-popover-arrow-width: 1rem; + --bs-popover-arrow-height: 0.5rem; + --bs-popover-arrow-border: var(--bs-popover-border-color); + z-index: var(--bs-popover-zindex); + display: block; + max-width: var(--bs-popover-max-width); + font-family: var(--bs-font-sans-serif); + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + white-space: normal; + word-spacing: normal; + line-break: auto; + font-size: var(--bs-popover-font-size); + word-wrap: break-word; + background-color: var(--bs-popover-bg); + background-clip: padding-box; + border: var(--bs-popover-border-width) solid var(--bs-popover-border-color); +} +.popover .popover-arrow { + display: block; + width: var(--bs-popover-arrow-width); + height: var(--bs-popover-arrow-height); +} +.popover .popover-arrow::before, .popover .popover-arrow::after { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; + border-width: 0; +} + +.bs-popover-top > .popover-arrow, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow { + bottom: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); +} +.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before, .bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after { + border-width: var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0; +} +.bs-popover-top > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before { + bottom: 0; + border-top-color: var(--bs-popover-arrow-border); +} +.bs-popover-top > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after { + bottom: var(--bs-popover-border-width); + border-top-color: var(--bs-popover-bg); +} + +/* rtl:begin:ignore */ +.bs-popover-end > .popover-arrow, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow { + left: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); + width: var(--bs-popover-arrow-height); + height: var(--bs-popover-arrow-width); +} +.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before, .bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after { + border-width: calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height) calc(var(--bs-popover-arrow-width) * 0.5) 0; +} +.bs-popover-end > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before { + left: 0; + border-right-color: var(--bs-popover-arrow-border); +} +.bs-popover-end > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after { + left: var(--bs-popover-border-width); + border-right-color: var(--bs-popover-bg); +} + +/* rtl:end:ignore */ +.bs-popover-bottom > .popover-arrow, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow { + top: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); +} +.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before, .bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after { + border-width: 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height); +} +.bs-popover-bottom > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::before { + top: 0; + border-bottom-color: var(--bs-popover-arrow-border); +} +.bs-popover-bottom > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after { + top: var(--bs-popover-border-width); + border-bottom-color: var(--bs-popover-bg); +} +.bs-popover-bottom .popover-header::before, .bs-popover-auto[data-popper-placement^=bottom] .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: var(--bs-popover-arrow-width); + margin-left: calc(-0.5 * var(--bs-popover-arrow-width)); + content: ""; + border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-header-bg); +} + +/* rtl:begin:ignore */ +.bs-popover-start > .popover-arrow, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow { + right: calc(-1 * (var(--bs-popover-arrow-height)) - var(--bs-popover-border-width)); + width: var(--bs-popover-arrow-height); + height: var(--bs-popover-arrow-width); +} +.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before, .bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after { + border-width: calc(var(--bs-popover-arrow-width) * 0.5) 0 calc(var(--bs-popover-arrow-width) * 0.5) var(--bs-popover-arrow-height); +} +.bs-popover-start > .popover-arrow::before, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before { + right: 0; + border-left-color: var(--bs-popover-arrow-border); +} +.bs-popover-start > .popover-arrow::after, .bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after { + right: var(--bs-popover-border-width); + border-left-color: var(--bs-popover-bg); +} + +/* rtl:end:ignore */ +.popover-header { + padding: var(--bs-popover-header-padding-y) var(--bs-popover-header-padding-x); + margin-bottom: 0; + font-size: var(--bs-popover-header-font-size); + color: var(--bs-popover-header-color); + background-color: var(--bs-popover-header-bg); + border-bottom: var(--bs-popover-border-width) solid var(--bs-popover-border-color); +} +.popover-header:empty { + display: none; +} + +.popover-body { + padding: var(--bs-popover-body-padding-y) var(--bs-popover-body-padding-x); + color: var(--bs-popover-body-color); +} + +.carousel { + position: relative; +} + +.carousel.pointer-event { + touch-action: pan-y; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner::after { + display: block; + clear: both; + content: ""; +} + +.carousel-item { + position: relative; + display: none; + float: left; + width: 100%; + margin-right: -100%; + backface-visibility: hidden; +} + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; +} + +.carousel-item-next:not(.carousel-item-start), +.active.carousel-item-end { + transform: translateX(100%); +} + +.carousel-item-prev:not(.carousel-item-end), +.active.carousel-item-start { + transform: translateX(-100%); +} + +.carousel-fade .carousel-item { + opacity: 0; + transition-property: opacity; + transform: none; +} +.carousel-fade .carousel-item.active, +.carousel-fade .carousel-item-next.carousel-item-start, +.carousel-fade .carousel-item-prev.carousel-item-end { + z-index: 1; + opacity: 1; +} +.carousel-fade .active.carousel-item-start, +.carousel-fade .active.carousel-item-end { + z-index: 0; + opacity: 0; +} + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + display: flex; + align-items: center; + justify-content: center; + width: 15%; + padding: 0; + color: #fff; + text-align: center; + background: none; + border: 0; + opacity: 0.5; +} +.carousel-control-prev:hover, .carousel-control-prev:focus, +.carousel-control-next:hover, +.carousel-control-next:focus { + color: #fff; + text-decoration: none; + outline: 0; + opacity: 0.9; +} + +.carousel-control-prev { + left: 0; +} + +.carousel-control-next { + right: 0; +} + +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: 2rem; + height: 2rem; + background-repeat: no-repeat; + background-position: 50%; + background-size: 100% 100%; +} + +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")*/; +} + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e") /*rtl:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")*/; +} + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 2; + display: flex; + justify-content: center; + padding: 0; + margin-right: 15%; + margin-bottom: 1rem; + margin-left: 15%; +} +.carousel-indicators [data-bs-target] { + box-sizing: content-box; + flex: 0 1 auto; + width: 30px; + height: 3px; + padding: 0; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + cursor: pointer; + background-color: #fff; + background-clip: padding-box; + border: 0; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + opacity: 0.5; +} +.carousel-indicators .active { + opacity: 1; +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 1.25rem; + left: 15%; + padding-top: 1.25rem; + padding-bottom: 1.25rem; + color: #fff; + text-align: center; +} + +.carousel-dark .carousel-control-prev-icon, +.carousel-dark .carousel-control-next-icon { + filter: invert(1) grayscale(100); +} +.carousel-dark .carousel-indicators [data-bs-target] { + background-color: #000; +} +.carousel-dark .carousel-caption { + color: #000; +} + +[data-bs-theme=dark] .carousel .carousel-control-prev-icon, +[data-bs-theme=dark] .carousel .carousel-control-next-icon, [data-bs-theme=dark].carousel .carousel-control-prev-icon, +[data-bs-theme=dark].carousel .carousel-control-next-icon { + filter: invert(1) grayscale(100); +} +[data-bs-theme=dark] .carousel .carousel-indicators [data-bs-target], [data-bs-theme=dark].carousel .carousel-indicators [data-bs-target] { + background-color: #000; +} +[data-bs-theme=dark] .carousel .carousel-caption, [data-bs-theme=dark].carousel .carousel-caption { + color: #000; +} + +.spinner-grow, +.spinner-border { + display: inline-block; + width: var(--bs-spinner-width); + height: var(--bs-spinner-height); + vertical-align: var(--bs-spinner-vertical-align); + border-radius: 50%; + animation: var(--bs-spinner-animation-speed) linear infinite var(--bs-spinner-animation-name); +} + +@keyframes spinner-border { + to { + transform: rotate(360deg) /* rtl:ignore */; + } +} +.spinner-border { + --bs-spinner-width: 2rem; + --bs-spinner-height: 2rem; + --bs-spinner-vertical-align: -0.125em; + --bs-spinner-border-width: 0.25em; + --bs-spinner-animation-speed: 0.75s; + --bs-spinner-animation-name: spinner-border; + border: var(--bs-spinner-border-width) solid currentcolor; + border-right-color: transparent; +} + +.spinner-border-sm { + --bs-spinner-width: 1rem; + --bs-spinner-height: 1rem; + --bs-spinner-border-width: 0.2em; +} + +@keyframes spinner-grow { + 0% { + transform: scale(0); + } + 50% { + opacity: 1; + transform: none; + } +} +.spinner-grow { + --bs-spinner-width: 2rem; + --bs-spinner-height: 2rem; + --bs-spinner-vertical-align: -0.125em; + --bs-spinner-animation-speed: 0.75s; + --bs-spinner-animation-name: spinner-grow; + background-color: currentcolor; + opacity: 0; +} + +.spinner-grow-sm { + --bs-spinner-width: 1rem; + --bs-spinner-height: 1rem; +} + +@media (prefers-reduced-motion: reduce) { + .spinner-border, + .spinner-grow { + --bs-spinner-animation-speed: 1.5s; + } +} +.offcanvas, .offcanvas-xxl, .offcanvas-xl, .offcanvas-lg, .offcanvas-md, .offcanvas-sm { + --bs-offcanvas-zindex: 1045; + --bs-offcanvas-width: 400px; + --bs-offcanvas-height: 30vh; + --bs-offcanvas-padding-x: 1rem; + --bs-offcanvas-padding-y: 1rem; + --bs-offcanvas-color: var(--bs-body-color); + --bs-offcanvas-bg: var(--bs-body-bg); + --bs-offcanvas-border-width: var(--bs-border-width); + --bs-offcanvas-border-color: var(--bs-border-color-translucent); + --bs-offcanvas-box-shadow: var(--bs-box-shadow-sm); + --bs-offcanvas-transition: transform 0.3s ease-in-out; + --bs-offcanvas-title-line-height: 1.5; +} + +@media (max-width: 575.98px) { + .offcanvas-sm { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + } + .offcanvas-sm.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-sm.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-sm.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-sm.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-sm.showing, .offcanvas-sm.show:not(.hiding) { + transform: none; + } + .offcanvas-sm.showing, .offcanvas-sm.hiding, .offcanvas-sm.show { + visibility: visible; + } +} +@media (min-width: 576px) { + .offcanvas-sm { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-sm .offcanvas-header { + display: none; + } + .offcanvas-sm .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +@media (max-width: 767.98px) { + .offcanvas-md { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + } + .offcanvas-md.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-md.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-md.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-md.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-md.showing, .offcanvas-md.show:not(.hiding) { + transform: none; + } + .offcanvas-md.showing, .offcanvas-md.hiding, .offcanvas-md.show { + visibility: visible; + } +} +@media (min-width: 768px) { + .offcanvas-md { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-md .offcanvas-header { + display: none; + } + .offcanvas-md .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +@media (max-width: 991.98px) { + .offcanvas-lg { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + } + .offcanvas-lg.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-lg.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-lg.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-lg.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-lg.showing, .offcanvas-lg.show:not(.hiding) { + transform: none; + } + .offcanvas-lg.showing, .offcanvas-lg.hiding, .offcanvas-lg.show { + visibility: visible; + } +} +@media (min-width: 992px) { + .offcanvas-lg { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-lg .offcanvas-header { + display: none; + } + .offcanvas-lg .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +@media (max-width: 1199.98px) { + .offcanvas-xl { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + } + .offcanvas-xl.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-xl.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-xl.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-xl.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-xl.showing, .offcanvas-xl.show:not(.hiding) { + transform: none; + } + .offcanvas-xl.showing, .offcanvas-xl.hiding, .offcanvas-xl.show { + visibility: visible; + } +} +@media (min-width: 1200px) { + .offcanvas-xl { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-xl .offcanvas-header { + display: none; + } + .offcanvas-xl .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +@media (max-width: 1399.98px) { + .offcanvas-xxl { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; + } + .offcanvas-xxl.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); + } + .offcanvas-xxl.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); + } + .offcanvas-xxl.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); + } + .offcanvas-xxl.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); + } + .offcanvas-xxl.showing, .offcanvas-xxl.show:not(.hiding) { + transform: none; + } + .offcanvas-xxl.showing, .offcanvas-xxl.hiding, .offcanvas-xxl.show { + visibility: visible; + } +} +@media (min-width: 1400px) { + .offcanvas-xxl { + --bs-offcanvas-height: auto; + --bs-offcanvas-border-width: 0; + background-color: transparent !important; + } + .offcanvas-xxl .offcanvas-header { + display: none; + } + .offcanvas-xxl .offcanvas-body { + display: flex; + flex-grow: 0; + padding: 0; + overflow-y: visible; + background-color: transparent !important; + } +} + +.offcanvas { + position: fixed; + bottom: 0; + z-index: var(--bs-offcanvas-zindex); + display: flex; + flex-direction: column; + max-width: 100%; + color: var(--bs-offcanvas-color); + visibility: hidden; + background-color: var(--bs-offcanvas-bg); + background-clip: padding-box; + outline: 0; +} +.offcanvas.offcanvas-start { + top: 0; + left: 0; + width: var(--bs-offcanvas-width); + border-right: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(-100%); +} +.offcanvas.offcanvas-end { + top: 0; + right: 0; + width: var(--bs-offcanvas-width); + border-left: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateX(100%); +} +.offcanvas.offcanvas-top { + top: 0; + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-bottom: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(-100%); +} +.offcanvas.offcanvas-bottom { + right: 0; + left: 0; + height: var(--bs-offcanvas-height); + max-height: 100%; + border-top: var(--bs-offcanvas-border-width) solid var(--bs-offcanvas-border-color); + transform: translateY(100%); +} +.offcanvas.showing, .offcanvas.show:not(.hiding) { + transform: none; +} +.offcanvas.showing, .offcanvas.hiding, .offcanvas.show { + visibility: visible; +} + +.offcanvas-backdrop { + position: fixed; + top: 0; + left: 0; + z-index: 1040; + width: 100vw; + height: 100vh; + background-color: #000; +} +.offcanvas-backdrop.fade { + opacity: 0; +} +.offcanvas-backdrop.show { + opacity: 0.5; +} + +.offcanvas-header { + display: flex; + align-items: center; + padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x); +} +.offcanvas-header .btn-close { + padding: calc(var(--bs-offcanvas-padding-y) * 0.5) calc(var(--bs-offcanvas-padding-x) * 0.5); + margin: calc(-0.5 * var(--bs-offcanvas-padding-y)) calc(-0.5 * var(--bs-offcanvas-padding-x)) calc(-0.5 * var(--bs-offcanvas-padding-y)) auto; +} + +.offcanvas-title { + margin-bottom: 0; + line-height: var(--bs-offcanvas-title-line-height); +} + +.offcanvas-body { + flex-grow: 1; + padding: var(--bs-offcanvas-padding-y) var(--bs-offcanvas-padding-x); + overflow-y: auto; +} + +.placeholder { + display: inline-block; + min-height: 1em; + vertical-align: middle; + cursor: wait; + background-color: currentcolor; + opacity: 0.5; +} +.placeholder.btn::before { + display: inline-block; + content: ""; +} + +.placeholder-xs { + min-height: 0.6em; +} + +.placeholder-sm { + min-height: 0.8em; +} + +.placeholder-lg { + min-height: 1.2em; +} + +.placeholder-glow .placeholder { + animation: placeholder-glow 2s ease-in-out infinite; +} + +@keyframes placeholder-glow { + 50% { + opacity: 0.2; + } +} +.placeholder-wave { + -webkit-mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%); + mask-image: linear-gradient(130deg, #000 55%, rgba(0, 0, 0, 0.8) 75%, #000 95%); + -webkit-mask-size: 200% 100%; + mask-size: 200% 100%; + animation: placeholder-wave 2s linear infinite; +} + +@keyframes placeholder-wave { + 100% { + -webkit-mask-position: -200% 0%; + mask-position: -200% 0%; + } +} +.clearfix::after { + display: block; + clear: both; + content: ""; +} + +.text-bg-primary { + color: #fff !important; + background-color: RGBA(var(--bs-primary-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-secondary { + color: #fff !important; + background-color: RGBA(var(--bs-secondary-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-success { + color: #000 !important; + background-color: RGBA(var(--bs-success-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-info { + color: #000 !important; + background-color: RGBA(var(--bs-info-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-warning { + color: #000 !important; + background-color: RGBA(var(--bs-warning-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-danger { + color: #000 !important; + background-color: RGBA(var(--bs-danger-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-light { + color: #000 !important; + background-color: RGBA(var(--bs-light-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.text-bg-dark { + color: #fff !important; + background-color: RGBA(var(--bs-dark-rgb), var(--bs-bg-opacity, 1)) !important; +} + +.link-primary { + color: RGBA(var(--bs-primary-rgb), var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-primary-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-primary:hover, .link-primary:focus { + color: RGBA(46, 58, 137, var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(46, 58, 137, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-secondary { + color: RGBA(var(--bs-secondary-rgb), var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-secondary-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-secondary:hover, .link-secondary:focus { + color: RGBA(86, 94, 100, var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(86, 94, 100, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-success { + color: RGBA(var(--bs-success-rgb), var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-success-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-success:hover, .link-success:focus { + color: RGBA(134, 178, 96, var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(134, 178, 96, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-info { + color: RGBA(var(--bs-info-rgb), var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-info-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-info:hover, .link-info:focus { + color: RGBA(51, 195, 255, var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(51, 195, 255, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-warning { + color: RGBA(var(--bs-warning-rgb), var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-warning-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-warning:hover, .link-warning:focus { + color: RGBA(253, 212, 93, var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(253, 212, 93, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-danger { + color: RGBA(var(--bs-danger-rgb), var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-danger-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-danger:hover, .link-danger:focus { + color: RGBA(255, 117, 133, var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(255, 117, 133, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-light { + color: RGBA(var(--bs-light-rgb), var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-light-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-light:hover, .link-light:focus { + color: RGBA(249, 250, 251, var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(249, 250, 251, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-dark { + color: RGBA(var(--bs-dark-rgb), var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-dark-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-dark:hover, .link-dark:focus { + color: RGBA(26, 30, 33, var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(26, 30, 33, var(--bs-link-underline-opacity, 1)) !important; +} + +.link-body-emphasis { + color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 1)) !important; + text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 1)) !important; +} +.link-body-emphasis:hover, .link-body-emphasis:focus { + color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-opacity, 0.75)) !important; + text-decoration-color: RGBA(var(--bs-emphasis-color-rgb), var(--bs-link-underline-opacity, 0.75)) !important; +} + +.focus-ring:focus { + outline: 0; + box-shadow: var(--bs-focus-ring-x, 0) var(--bs-focus-ring-y, 0) var(--bs-focus-ring-blur, 0) var(--bs-focus-ring-width) var(--bs-focus-ring-color); +} + +.icon-link { + display: inline-flex; + gap: 0.375rem; + align-items: center; + text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 0.5)); + text-underline-offset: 0.25em; + backface-visibility: hidden; +} +.icon-link > .bi { + flex-shrink: 0; + width: 1em; + height: 1em; + fill: currentcolor; +} + +.icon-link-hover:hover > .bi, .icon-link-hover:focus-visible > .bi { + transform: var(--bs-icon-link-transform, translate3d(0.25em, 0, 0)); +} + +.ratio { + position: relative; + width: 100%; +} +.ratio::before { + display: block; + padding-top: var(--bs-aspect-ratio); + content: ""; +} +.ratio > * { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.ratio-1x1 { + --bs-aspect-ratio: 100%; +} + +.ratio-4x3 { + --bs-aspect-ratio: 75%; +} + +.ratio-16x9 { + --bs-aspect-ratio: 56.25%; +} + +.ratio-21x9 { + --bs-aspect-ratio: 42.8571428571%; +} + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; +} + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; +} + +.sticky-top { + position: sticky; + top: 0; + z-index: 1020; +} + +.sticky-bottom { + position: sticky; + bottom: 0; + z-index: 1020; +} + +@media (min-width: 576px) { + .sticky-sm-top { + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-sm-bottom { + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 768px) { + .sticky-md-top { + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-md-bottom { + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 992px) { + .sticky-lg-top { + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-lg-bottom { + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 1200px) { + .sticky-xl-top { + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-xl-bottom { + position: sticky; + bottom: 0; + z-index: 1020; + } +} +@media (min-width: 1400px) { + .sticky-xxl-top { + position: sticky; + top: 0; + z-index: 1020; + } + .sticky-xxl-bottom { + position: sticky; + bottom: 0; + z-index: 1020; + } +} +.hstack { + display: flex; + flex-direction: row; + align-items: center; + align-self: stretch; +} + +.vstack { + display: flex; + flex: 1 1 auto; + flex-direction: column; + align-self: stretch; +} + +.visually-hidden, +.visually-hidden-focusable:not(:focus):not(:focus-within) { + width: 1px !important; + height: 1px !important; + padding: 0 !important; + margin: -1px !important; + overflow: hidden !important; + clip: rect(0, 0, 0, 0) !important; + white-space: nowrap !important; + border: 0 !important; +} +.visually-hidden:not(caption), +.visually-hidden-focusable:not(:focus):not(:focus-within):not(caption) { + position: absolute !important; +} + +.stretched-link::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + content: ""; +} + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.vr { + display: inline-block; + align-self: stretch; + width: var(--bs-border-width); + min-height: 1em; + background-color: currentcolor; + opacity: 0.25; +} + +.align-baseline { + vertical-align: baseline !important; +} + +.align-top { + vertical-align: top !important; +} + +.align-middle { + vertical-align: middle !important; +} + +.align-bottom { + vertical-align: bottom !important; +} + +.align-text-bottom { + vertical-align: text-bottom !important; +} + +.align-text-top { + vertical-align: text-top !important; +} + +.float-start { + float: left !important; +} + +.float-end { + float: right !important; +} + +.float-none { + float: none !important; +} + +.object-fit-contain { + -o-object-fit: contain !important; + object-fit: contain !important; +} + +.object-fit-cover { + -o-object-fit: cover !important; + object-fit: cover !important; +} + +.object-fit-fill { + -o-object-fit: fill !important; + object-fit: fill !important; +} + +.object-fit-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; +} + +.object-fit-none { + -o-object-fit: none !important; + object-fit: none !important; +} + +.opacity-0 { + opacity: 0 !important; +} + +.opacity-25 { + opacity: 0.25 !important; +} + +.opacity-50 { + opacity: 0.5 !important; +} + +.opacity-75 { + opacity: 0.75 !important; +} + +.opacity-100 { + opacity: 1 !important; +} + +.overflow-auto { + overflow: auto !important; +} + +.overflow-hidden { + overflow: hidden !important; +} + +.overflow-visible { + overflow: visible !important; +} + +.overflow-scroll { + overflow: scroll !important; +} + +.overflow-x-auto { + overflow-x: auto !important; +} + +.overflow-x-hidden { + overflow-x: hidden !important; +} + +.overflow-x-visible { + overflow-x: visible !important; +} + +.overflow-x-scroll { + overflow-x: scroll !important; +} + +.overflow-y-auto { + overflow-y: auto !important; +} + +.overflow-y-hidden { + overflow-y: hidden !important; +} + +.overflow-y-visible { + overflow-y: visible !important; +} + +.overflow-y-scroll { + overflow-y: scroll !important; +} + +.d-inline { + display: inline !important; +} + +.d-inline-block { + display: inline-block !important; +} + +.d-block { + display: block !important; +} + +.d-grid { + display: grid !important; +} + +.d-inline-grid { + display: inline-grid !important; +} + +.d-table { + display: table !important; +} + +.d-table-row { + display: table-row !important; +} + +.d-table-cell { + display: table-cell !important; +} + +.d-flex { + display: flex !important; +} + +.d-inline-flex { + display: inline-flex !important; +} + +.d-none { + display: none !important; +} + +.shadow { + box-shadow: var(--bs-box-shadow) !important; +} + +.shadow-sm { + box-shadow: var(--bs-box-shadow-sm) !important; +} + +.shadow-lg { + box-shadow: var(--bs-box-shadow-lg) !important; +} + +.shadow-none { + box-shadow: none !important; +} + +.focus-ring-primary { + --bs-focus-ring-color: rgba(var(--bs-primary-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-secondary { + --bs-focus-ring-color: rgba(var(--bs-secondary-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-success { + --bs-focus-ring-color: rgba(var(--bs-success-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-info { + --bs-focus-ring-color: rgba(var(--bs-info-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-warning { + --bs-focus-ring-color: rgba(var(--bs-warning-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-danger { + --bs-focus-ring-color: rgba(var(--bs-danger-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-light { + --bs-focus-ring-color: rgba(var(--bs-light-rgb), var(--bs-focus-ring-opacity)); +} + +.focus-ring-dark { + --bs-focus-ring-color: rgba(var(--bs-dark-rgb), var(--bs-focus-ring-opacity)); +} + +.position-static { + position: static !important; +} + +.position-relative { + position: relative !important; +} + +.position-absolute { + position: absolute !important; +} + +.position-fixed { + position: fixed !important; +} + +.position-sticky { + position: sticky !important; +} + +.top-0 { + top: 0 !important; +} + +.top-50 { + top: 50% !important; +} + +.top-100 { + top: 100% !important; +} + +.bottom-0 { + bottom: 0 !important; +} + +.bottom-50 { + bottom: 50% !important; +} + +.bottom-100 { + bottom: 100% !important; +} + +.start-0 { + left: 0 !important; +} + +.start-50 { + left: 50% !important; +} + +.start-100 { + left: 100% !important; +} + +.end-0 { + right: 0 !important; +} + +.end-50 { + right: 50% !important; +} + +.end-100 { + right: 100% !important; +} + +.translate-middle { + transform: translate(-50%, -50%) !important; +} + +.translate-middle-x { + transform: translateX(-50%) !important; +} + +.translate-middle-y { + transform: translateY(-50%) !important; +} + +.border { + border: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} + +.border-0 { + border: 0 !important; +} + +.border-top { + border-top: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} + +.border-top-0 { + border-top: 0 !important; +} + +.border-end { + border-right: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} + +.border-end-0 { + border-right: 0 !important; +} + +.border-bottom { + border-bottom: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} + +.border-bottom-0 { + border-bottom: 0 !important; +} + +.border-start { + border-left: var(--bs-border-width) var(--bs-border-style) var(--bs-border-color) !important; +} + +.border-start-0 { + border-left: 0 !important; +} + +.border-primary { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-primary-rgb), var(--bs-border-opacity)) !important; +} + +.border-secondary { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-secondary-rgb), var(--bs-border-opacity)) !important; +} + +.border-success { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-success-rgb), var(--bs-border-opacity)) !important; +} + +.border-info { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-info-rgb), var(--bs-border-opacity)) !important; +} + +.border-warning { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-warning-rgb), var(--bs-border-opacity)) !important; +} + +.border-danger { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-danger-rgb), var(--bs-border-opacity)) !important; +} + +.border-light { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-light-rgb), var(--bs-border-opacity)) !important; +} + +.border-dark { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-dark-rgb), var(--bs-border-opacity)) !important; +} + +.border-black { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-black-rgb), var(--bs-border-opacity)) !important; +} + +.border-white { + --bs-border-opacity: 1; + border-color: rgba(var(--bs-white-rgb), var(--bs-border-opacity)) !important; +} + +.border-primary-subtle { + border-color: var(--bs-primary-border-subtle) !important; +} + +.border-secondary-subtle { + border-color: var(--bs-secondary-border-subtle) !important; +} + +.border-success-subtle { + border-color: var(--bs-success-border-subtle) !important; +} + +.border-info-subtle { + border-color: var(--bs-info-border-subtle) !important; +} + +.border-warning-subtle { + border-color: var(--bs-warning-border-subtle) !important; +} + +.border-danger-subtle { + border-color: var(--bs-danger-border-subtle) !important; +} + +.border-light-subtle { + border-color: var(--bs-light-border-subtle) !important; +} + +.border-dark-subtle { + border-color: var(--bs-dark-border-subtle) !important; +} + +.border-1 { + border-width: 1px !important; +} + +.border-2 { + border-width: 2px !important; +} + +.border-3 { + border-width: 3px !important; +} + +.border-4 { + border-width: 4px !important; +} + +.border-5 { + border-width: 5px !important; +} + +.border-opacity-10 { + --bs-border-opacity: 0.1; +} + +.border-opacity-25 { + --bs-border-opacity: 0.25; +} + +.border-opacity-50 { + --bs-border-opacity: 0.5; +} + +.border-opacity-75 { + --bs-border-opacity: 0.75; +} + +.border-opacity-100 { + --bs-border-opacity: 1; +} + +.w-25 { + width: 25% !important; +} + +.w-50 { + width: 50% !important; +} + +.w-75 { + width: 75% !important; +} + +.w-100 { + width: 100% !important; +} + +.w-auto { + width: auto !important; +} + +.mw-100 { + max-width: 100% !important; +} + +.vw-100 { + width: 100vw !important; +} + +.min-vw-100 { + min-width: 100vw !important; +} + +.h-25 { + height: 25% !important; +} + +.h-50 { + height: 50% !important; +} + +.h-75 { + height: 75% !important; +} + +.h-100 { + height: 100% !important; +} + +.h-auto { + height: auto !important; +} + +.mh-100 { + max-height: 100% !important; +} + +.vh-100 { + height: 100vh !important; +} + +.min-vh-100 { + min-height: 100vh !important; +} + +.flex-fill { + flex: 1 1 auto !important; +} + +.flex-row { + flex-direction: row !important; +} + +.flex-column { + flex-direction: column !important; +} + +.flex-row-reverse { + flex-direction: row-reverse !important; +} + +.flex-column-reverse { + flex-direction: column-reverse !important; +} + +.flex-grow-0 { + flex-grow: 0 !important; +} + +.flex-grow-1 { + flex-grow: 1 !important; +} + +.flex-shrink-0 { + flex-shrink: 0 !important; +} + +.flex-shrink-1 { + flex-shrink: 1 !important; +} + +.flex-wrap { + flex-wrap: wrap !important; +} + +.flex-nowrap { + flex-wrap: nowrap !important; +} + +.flex-wrap-reverse { + flex-wrap: wrap-reverse !important; +} + +.justify-content-start { + justify-content: flex-start !important; +} + +.justify-content-end { + justify-content: flex-end !important; +} + +.justify-content-center { + justify-content: center !important; +} + +.justify-content-between { + justify-content: space-between !important; +} + +.justify-content-around { + justify-content: space-around !important; +} + +.justify-content-evenly { + justify-content: space-evenly !important; +} + +.align-items-start { + align-items: flex-start !important; +} + +.align-items-end { + align-items: flex-end !important; +} + +.align-items-center { + align-items: center !important; +} + +.align-items-baseline { + align-items: baseline !important; +} + +.align-items-stretch { + align-items: stretch !important; +} + +.align-content-start { + align-content: flex-start !important; +} + +.align-content-end { + align-content: flex-end !important; +} + +.align-content-center { + align-content: center !important; +} + +.align-content-between { + align-content: space-between !important; +} + +.align-content-around { + align-content: space-around !important; +} + +.align-content-stretch { + align-content: stretch !important; +} + +.align-self-auto { + align-self: auto !important; +} + +.align-self-start { + align-self: flex-start !important; +} + +.align-self-end { + align-self: flex-end !important; +} + +.align-self-center { + align-self: center !important; +} + +.align-self-baseline { + align-self: baseline !important; +} + +.align-self-stretch { + align-self: stretch !important; +} + +.order-first { + order: -1 !important; +} + +.order-0 { + order: 0 !important; +} + +.order-1 { + order: 1 !important; +} + +.order-2 { + order: 2 !important; +} + +.order-3 { + order: 3 !important; +} + +.order-4 { + order: 4 !important; +} + +.order-5 { + order: 5 !important; +} + +.order-last { + order: 6 !important; +} + +.m-0 { + margin: 0 !important; +} + +.m-1 { + margin: 0.25rem !important; +} + +.m-2 { + margin: 0.5rem !important; +} + +.m-3 { + margin: 1rem !important; +} + +.m-4 { + margin: 1.5rem !important; +} + +.m-5 { + margin: 3rem !important; +} + +.m-auto { + margin: auto !important; +} + +.mx-0 { + margin-right: 0 !important; + margin-left: 0 !important; +} + +.mx-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; +} + +.mx-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; +} + +.mx-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; +} + +.mx-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; +} + +.mx-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; +} + +.mx-auto { + margin-right: auto !important; + margin-left: auto !important; +} + +.my-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; +} + +.my-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; +} + +.my-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; +} + +.my-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; +} + +.my-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; +} + +.my-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; +} + +.my-auto { + margin-top: auto !important; + margin-bottom: auto !important; +} + +.mt-0 { + margin-top: 0 !important; +} + +.mt-1 { + margin-top: 0.25rem !important; +} + +.mt-2 { + margin-top: 0.5rem !important; +} + +.mt-3 { + margin-top: 1rem !important; +} + +.mt-4 { + margin-top: 1.5rem !important; +} + +.mt-5 { + margin-top: 3rem !important; +} + +.mt-auto { + margin-top: auto !important; +} + +.me-0 { + margin-right: 0 !important; +} + +.me-1 { + margin-right: 0.25rem !important; +} + +.me-2 { + margin-right: 0.5rem !important; +} + +.me-3 { + margin-right: 1rem !important; +} + +.me-4 { + margin-right: 1.5rem !important; +} + +.me-5 { + margin-right: 3rem !important; +} + +.me-auto { + margin-right: auto !important; +} + +.mb-0 { + margin-bottom: 0 !important; +} + +.mb-1 { + margin-bottom: 0.25rem !important; +} + +.mb-2 { + margin-bottom: 0.5rem !important; +} + +.mb-3 { + margin-bottom: 1rem !important; +} + +.mb-4 { + margin-bottom: 1.5rem !important; +} + +.mb-5 { + margin-bottom: 3rem !important; +} + +.mb-auto { + margin-bottom: auto !important; +} + +.ms-0 { + margin-left: 0 !important; +} + +.ms-1 { + margin-left: 0.25rem !important; +} + +.ms-2 { + margin-left: 0.5rem !important; +} + +.ms-3 { + margin-left: 1rem !important; +} + +.ms-4 { + margin-left: 1.5rem !important; +} + +.ms-5 { + margin-left: 3rem !important; +} + +.ms-auto { + margin-left: auto !important; +} + +.p-0 { + padding: 0 !important; +} + +.p-1 { + padding: 0.25rem !important; +} + +.p-2 { + padding: 0.5rem !important; +} + +.p-3 { + padding: 1rem !important; +} + +.p-4 { + padding: 1.5rem !important; +} + +.p-5 { + padding: 3rem !important; +} + +.px-0 { + padding-right: 0 !important; + padding-left: 0 !important; +} + +.px-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; +} + +.px-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; +} + +.px-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; +} + +.px-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; +} + +.px-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; +} + +.py-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.py-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; +} + +.py-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; +} + +.py-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; +} + +.py-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; +} + +.py-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; +} + +.pt-0 { + padding-top: 0 !important; +} + +.pt-1 { + padding-top: 0.25rem !important; +} + +.pt-2 { + padding-top: 0.5rem !important; +} + +.pt-3 { + padding-top: 1rem !important; +} + +.pt-4 { + padding-top: 1.5rem !important; +} + +.pt-5 { + padding-top: 3rem !important; +} + +.pe-0 { + padding-right: 0 !important; +} + +.pe-1 { + padding-right: 0.25rem !important; +} + +.pe-2 { + padding-right: 0.5rem !important; +} + +.pe-3 { + padding-right: 1rem !important; +} + +.pe-4 { + padding-right: 1.5rem !important; +} + +.pe-5 { + padding-right: 3rem !important; +} + +.pb-0 { + padding-bottom: 0 !important; +} + +.pb-1 { + padding-bottom: 0.25rem !important; +} + +.pb-2 { + padding-bottom: 0.5rem !important; +} + +.pb-3 { + padding-bottom: 1rem !important; +} + +.pb-4 { + padding-bottom: 1.5rem !important; +} + +.pb-5 { + padding-bottom: 3rem !important; +} + +.ps-0 { + padding-left: 0 !important; +} + +.ps-1 { + padding-left: 0.25rem !important; +} + +.ps-2 { + padding-left: 0.5rem !important; +} + +.ps-3 { + padding-left: 1rem !important; +} + +.ps-4 { + padding-left: 1.5rem !important; +} + +.ps-5 { + padding-left: 3rem !important; +} + +.gap-0 { + gap: 0 !important; +} + +.gap-1 { + gap: 0.25rem !important; +} + +.gap-2 { + gap: 0.5rem !important; +} + +.gap-3 { + gap: 1rem !important; +} + +.gap-4 { + gap: 1.5rem !important; +} + +.gap-5 { + gap: 3rem !important; +} + +.row-gap-0 { + row-gap: 0 !important; +} + +.row-gap-1 { + row-gap: 0.25rem !important; +} + +.row-gap-2 { + row-gap: 0.5rem !important; +} + +.row-gap-3 { + row-gap: 1rem !important; +} + +.row-gap-4 { + row-gap: 1.5rem !important; +} + +.row-gap-5 { + row-gap: 3rem !important; +} + +.column-gap-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; +} + +.column-gap-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; +} + +.column-gap-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; +} + +.column-gap-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; +} + +.column-gap-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; +} + +.column-gap-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; +} + +.font-monospace { + font-family: var(--bs-font-monospace) !important; +} + +.fs-1 { + font-size: calc(1.34375rem + 1.125vw) !important; +} + +.fs-2 { + font-size: calc(1.3rem + 0.6vw) !important; +} + +.fs-3 { + font-size: calc(1.278125rem + 0.3375vw) !important; +} + +.fs-4 { + font-size: calc(1.25625rem + 0.075vw) !important; +} + +.fs-5 { + font-size: 1.09375rem !important; +} + +.fs-6 { + font-size: 0.875rem !important; +} + +.fst-italic { + font-style: italic !important; +} + +.fst-normal { + font-style: normal !important; +} + +.fw-lighter { + font-weight: lighter !important; +} + +.fw-light { + font-weight: 300 !important; +} + +.fw-normal { + font-weight: 400 !important; +} + +.fw-medium { + font-weight: 500 !important; +} + +.fw-semibold { + font-weight: 600 !important; +} + +.fw-bold { + font-weight: 700 !important; +} + +.fw-bolder { + font-weight: bolder !important; +} + +.lh-1 { + line-height: 1 !important; +} + +.lh-sm { + line-height: 1.25 !important; +} + +.lh-base { + line-height: 1.5 !important; +} + +.lh-lg { + line-height: 2 !important; +} + +.text-start { + text-align: left !important; +} + +.text-end { + text-align: right !important; +} + +.text-center { + text-align: center !important; +} + +.text-decoration-none { + text-decoration: none !important; +} + +.text-decoration-underline { + text-decoration: underline !important; +} + +.text-decoration-line-through { + text-decoration: line-through !important; +} + +.text-lowercase { + text-transform: lowercase !important; +} + +.text-uppercase { + text-transform: uppercase !important; +} + +.text-capitalize { + text-transform: capitalize !important; +} + +.text-wrap { + white-space: normal !important; +} + +.text-nowrap { + white-space: nowrap !important; +} + +/* rtl:begin:remove */ +.text-break { + word-wrap: break-word !important; + word-break: break-word !important; +} + +/* rtl:end:remove */ +.text-primary { + --bs-text-opacity: 1; + color: rgba(var(--bs-primary-rgb), var(--bs-text-opacity)) !important; +} + +.text-secondary { + --bs-text-opacity: 1; + color: rgba(var(--bs-secondary-rgb), var(--bs-text-opacity)) !important; +} + +.text-success { + --bs-text-opacity: 1; + color: rgba(var(--bs-success-rgb), var(--bs-text-opacity)) !important; +} + +.text-info { + --bs-text-opacity: 1; + color: rgba(var(--bs-info-rgb), var(--bs-text-opacity)) !important; +} + +.text-warning { + --bs-text-opacity: 1; + color: rgba(var(--bs-warning-rgb), var(--bs-text-opacity)) !important; +} + +.text-danger { + --bs-text-opacity: 1; + color: rgba(var(--bs-danger-rgb), var(--bs-text-opacity)) !important; +} + +.text-light { + --bs-text-opacity: 1; + color: rgba(var(--bs-light-rgb), var(--bs-text-opacity)) !important; +} + +.text-dark { + --bs-text-opacity: 1; + color: rgba(var(--bs-dark-rgb), var(--bs-text-opacity)) !important; +} + +.text-black { + --bs-text-opacity: 1; + color: rgba(var(--bs-black-rgb), var(--bs-text-opacity)) !important; +} + +.text-white { + --bs-text-opacity: 1; + color: rgba(var(--bs-white-rgb), var(--bs-text-opacity)) !important; +} + +.text-body { + --bs-text-opacity: 1; + color: rgba(var(--bs-body-color-rgb), var(--bs-text-opacity)) !important; +} + +.text-muted { + --bs-text-opacity: 1; + color: var(--bs-secondary-color) !important; +} + +.text-black-50 { + --bs-text-opacity: 1; + color: rgba(0, 0, 0, 0.5) !important; +} + +.text-white-50 { + --bs-text-opacity: 1; + color: rgba(255, 255, 255, 0.5) !important; +} + +.text-body-secondary { + --bs-text-opacity: 1; + color: var(--bs-secondary-color) !important; +} + +.text-body-tertiary { + --bs-text-opacity: 1; + color: var(--bs-tertiary-color) !important; +} + +.text-body-emphasis { + --bs-text-opacity: 1; + color: var(--bs-emphasis-color) !important; +} + +.text-reset { + --bs-text-opacity: 1; + color: inherit !important; +} + +.text-opacity-25 { + --bs-text-opacity: 0.25; +} + +.text-opacity-50 { + --bs-text-opacity: 0.5; +} + +.text-opacity-75 { + --bs-text-opacity: 0.75; +} + +.text-opacity-100 { + --bs-text-opacity: 1; +} + +.text-primary-emphasis { + color: var(--bs-primary-text-emphasis) !important; +} + +.text-secondary-emphasis { + color: var(--bs-secondary-text-emphasis) !important; +} + +.text-success-emphasis { + color: var(--bs-success-text-emphasis) !important; +} + +.text-info-emphasis { + color: var(--bs-info-text-emphasis) !important; +} + +.text-warning-emphasis { + color: var(--bs-warning-text-emphasis) !important; +} + +.text-danger-emphasis { + color: var(--bs-danger-text-emphasis) !important; +} + +.text-light-emphasis { + color: var(--bs-light-text-emphasis) !important; +} + +.text-dark-emphasis { + color: var(--bs-dark-text-emphasis) !important; +} + +.link-opacity-10 { + --bs-link-opacity: 0.1; +} + +.link-opacity-10-hover:hover { + --bs-link-opacity: 0.1; +} + +.link-opacity-25 { + --bs-link-opacity: 0.25; +} + +.link-opacity-25-hover:hover { + --bs-link-opacity: 0.25; +} + +.link-opacity-50 { + --bs-link-opacity: 0.5; +} + +.link-opacity-50-hover:hover { + --bs-link-opacity: 0.5; +} + +.link-opacity-75 { + --bs-link-opacity: 0.75; +} + +.link-opacity-75-hover:hover { + --bs-link-opacity: 0.75; +} + +.link-opacity-100 { + --bs-link-opacity: 1; +} + +.link-opacity-100-hover:hover { + --bs-link-opacity: 1; +} + +.link-offset-1 { + text-underline-offset: 0.125em !important; +} + +.link-offset-1-hover:hover { + text-underline-offset: 0.125em !important; +} + +.link-offset-2 { + text-underline-offset: 0.25em !important; +} + +.link-offset-2-hover:hover { + text-underline-offset: 0.25em !important; +} + +.link-offset-3 { + text-underline-offset: 0.375em !important; +} + +.link-offset-3-hover:hover { + text-underline-offset: 0.375em !important; +} + +.link-underline-primary { + --bs-link-underline-opacity: 1; + text-decoration-color: rgba(var(--bs-primary-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-secondary { + --bs-link-underline-opacity: 1; + text-decoration-color: rgba(var(--bs-secondary-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-success { + --bs-link-underline-opacity: 1; + text-decoration-color: rgba(var(--bs-success-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-info { + --bs-link-underline-opacity: 1; + text-decoration-color: rgba(var(--bs-info-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-warning { + --bs-link-underline-opacity: 1; + text-decoration-color: rgba(var(--bs-warning-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-danger { + --bs-link-underline-opacity: 1; + text-decoration-color: rgba(var(--bs-danger-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-light { + --bs-link-underline-opacity: 1; + text-decoration-color: rgba(var(--bs-light-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline-dark { + --bs-link-underline-opacity: 1; + text-decoration-color: rgba(var(--bs-dark-rgb), var(--bs-link-underline-opacity)) !important; +} + +.link-underline { + --bs-link-underline-opacity: 1; + text-decoration-color: rgba(var(--bs-link-color-rgb), var(--bs-link-underline-opacity, 1)) !important; +} + +.link-underline-opacity-0 { + --bs-link-underline-opacity: 0; +} + +.link-underline-opacity-0-hover:hover { + --bs-link-underline-opacity: 0; +} + +.link-underline-opacity-10 { + --bs-link-underline-opacity: 0.1; +} + +.link-underline-opacity-10-hover:hover { + --bs-link-underline-opacity: 0.1; +} + +.link-underline-opacity-25 { + --bs-link-underline-opacity: 0.25; +} + +.link-underline-opacity-25-hover:hover { + --bs-link-underline-opacity: 0.25; +} + +.link-underline-opacity-50 { + --bs-link-underline-opacity: 0.5; +} + +.link-underline-opacity-50-hover:hover { + --bs-link-underline-opacity: 0.5; +} + +.link-underline-opacity-75 { + --bs-link-underline-opacity: 0.75; +} + +.link-underline-opacity-75-hover:hover { + --bs-link-underline-opacity: 0.75; +} + +.link-underline-opacity-100 { + --bs-link-underline-opacity: 1; +} + +.link-underline-opacity-100-hover:hover { + --bs-link-underline-opacity: 1; +} + +.bg-primary { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-primary-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-secondary { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-secondary-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-success { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-success-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-info { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-info-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-warning { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-warning-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-danger { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-danger-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-light { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-light-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-dark { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-dark-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-black { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-black-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-white { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-white-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-body { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-body-bg-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-transparent { + --bs-bg-opacity: 1; + background-color: transparent !important; +} + +.bg-body-secondary { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-secondary-bg-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-body-tertiary { + --bs-bg-opacity: 1; + background-color: rgba(var(--bs-tertiary-bg-rgb), var(--bs-bg-opacity)) !important; +} + +.bg-opacity-10 { + --bs-bg-opacity: 0.1; +} + +.bg-opacity-25 { + --bs-bg-opacity: 0.25; +} + +.bg-opacity-50 { + --bs-bg-opacity: 0.5; +} + +.bg-opacity-75 { + --bs-bg-opacity: 0.75; +} + +.bg-opacity-100 { + --bs-bg-opacity: 1; +} + +.bg-primary-subtle { + background-color: var(--bs-primary-bg-subtle) !important; +} + +.bg-secondary-subtle { + background-color: var(--bs-secondary-bg-subtle) !important; +} + +.bg-success-subtle { + background-color: var(--bs-success-bg-subtle) !important; +} + +.bg-info-subtle { + background-color: var(--bs-info-bg-subtle) !important; +} + +.bg-warning-subtle { + background-color: var(--bs-warning-bg-subtle) !important; +} + +.bg-danger-subtle { + background-color: var(--bs-danger-bg-subtle) !important; +} + +.bg-light-subtle { + background-color: var(--bs-light-bg-subtle) !important; +} + +.bg-dark-subtle { + background-color: var(--bs-dark-bg-subtle) !important; +} + +.bg-gradient { + background-image: var(--bs-gradient) !important; +} + +.user-select-all { + -webkit-user-select: all !important; + -moz-user-select: all !important; + user-select: all !important; +} + +.user-select-auto { + -webkit-user-select: auto !important; + -moz-user-select: auto !important; + user-select: auto !important; +} + +.user-select-none { + -webkit-user-select: none !important; + -moz-user-select: none !important; + user-select: none !important; +} + +.pe-none { + pointer-events: none !important; +} + +.pe-auto { + pointer-events: auto !important; +} + +.rounded { + border-radius: var(--bs-border-radius) !important; +} + +.rounded-0 { + border-radius: 0 !important; +} + +.rounded-1 { + border-radius: var(--bs-border-radius-sm) !important; +} + +.rounded-2 { + border-radius: var(--bs-border-radius) !important; +} + +.rounded-3 { + border-radius: var(--bs-border-radius-lg) !important; +} + +.rounded-4 { + border-radius: var(--bs-border-radius-xl) !important; +} + +.rounded-5 { + border-radius: var(--bs-border-radius-xxl) !important; +} + +.rounded-circle { + border-radius: 50% !important; +} + +.rounded-pill { + border-radius: var(--bs-border-radius-pill) !important; +} + +.rounded-top { + border-top-left-radius: var(--bs-border-radius) !important; + border-top-right-radius: var(--bs-border-radius) !important; +} + +.rounded-top-0 { + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; +} + +.rounded-top-1 { + border-top-left-radius: var(--bs-border-radius-sm) !important; + border-top-right-radius: var(--bs-border-radius-sm) !important; +} + +.rounded-top-2 { + border-top-left-radius: var(--bs-border-radius) !important; + border-top-right-radius: var(--bs-border-radius) !important; +} + +.rounded-top-3 { + border-top-left-radius: var(--bs-border-radius-lg) !important; + border-top-right-radius: var(--bs-border-radius-lg) !important; +} + +.rounded-top-4 { + border-top-left-radius: var(--bs-border-radius-xl) !important; + border-top-right-radius: var(--bs-border-radius-xl) !important; +} + +.rounded-top-5 { + border-top-left-radius: var(--bs-border-radius-xxl) !important; + border-top-right-radius: var(--bs-border-radius-xxl) !important; +} + +.rounded-top-circle { + border-top-left-radius: 50% !important; + border-top-right-radius: 50% !important; +} + +.rounded-top-pill { + border-top-left-radius: var(--bs-border-radius-pill) !important; + border-top-right-radius: var(--bs-border-radius-pill) !important; +} + +.rounded-end { + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important; +} + +.rounded-end-0 { + border-top-right-radius: 0 !important; + border-bottom-right-radius: 0 !important; +} + +.rounded-end-1 { + border-top-right-radius: var(--bs-border-radius-sm) !important; + border-bottom-right-radius: var(--bs-border-radius-sm) !important; +} + +.rounded-end-2 { + border-top-right-radius: var(--bs-border-radius) !important; + border-bottom-right-radius: var(--bs-border-radius) !important; +} + +.rounded-end-3 { + border-top-right-radius: var(--bs-border-radius-lg) !important; + border-bottom-right-radius: var(--bs-border-radius-lg) !important; +} + +.rounded-end-4 { + border-top-right-radius: var(--bs-border-radius-xl) !important; + border-bottom-right-radius: var(--bs-border-radius-xl) !important; +} + +.rounded-end-5 { + border-top-right-radius: var(--bs-border-radius-xxl) !important; + border-bottom-right-radius: var(--bs-border-radius-xxl) !important; +} + +.rounded-end-circle { + border-top-right-radius: 50% !important; + border-bottom-right-radius: 50% !important; +} + +.rounded-end-pill { + border-top-right-radius: var(--bs-border-radius-pill) !important; + border-bottom-right-radius: var(--bs-border-radius-pill) !important; +} + +.rounded-bottom { + border-bottom-right-radius: var(--bs-border-radius) !important; + border-bottom-left-radius: var(--bs-border-radius) !important; +} + +.rounded-bottom-0 { + border-bottom-right-radius: 0 !important; + border-bottom-left-radius: 0 !important; +} + +.rounded-bottom-1 { + border-bottom-right-radius: var(--bs-border-radius-sm) !important; + border-bottom-left-radius: var(--bs-border-radius-sm) !important; +} + +.rounded-bottom-2 { + border-bottom-right-radius: var(--bs-border-radius) !important; + border-bottom-left-radius: var(--bs-border-radius) !important; +} + +.rounded-bottom-3 { + border-bottom-right-radius: var(--bs-border-radius-lg) !important; + border-bottom-left-radius: var(--bs-border-radius-lg) !important; +} + +.rounded-bottom-4 { + border-bottom-right-radius: var(--bs-border-radius-xl) !important; + border-bottom-left-radius: var(--bs-border-radius-xl) !important; +} + +.rounded-bottom-5 { + border-bottom-right-radius: var(--bs-border-radius-xxl) !important; + border-bottom-left-radius: var(--bs-border-radius-xxl) !important; +} + +.rounded-bottom-circle { + border-bottom-right-radius: 50% !important; + border-bottom-left-radius: 50% !important; +} + +.rounded-bottom-pill { + border-bottom-right-radius: var(--bs-border-radius-pill) !important; + border-bottom-left-radius: var(--bs-border-radius-pill) !important; +} + +.rounded-start { + border-bottom-left-radius: var(--bs-border-radius) !important; + border-top-left-radius: var(--bs-border-radius) !important; +} + +.rounded-start-0 { + border-bottom-left-radius: 0 !important; + border-top-left-radius: 0 !important; +} + +.rounded-start-1 { + border-bottom-left-radius: var(--bs-border-radius-sm) !important; + border-top-left-radius: var(--bs-border-radius-sm) !important; +} + +.rounded-start-2 { + border-bottom-left-radius: var(--bs-border-radius) !important; + border-top-left-radius: var(--bs-border-radius) !important; +} + +.rounded-start-3 { + border-bottom-left-radius: var(--bs-border-radius-lg) !important; + border-top-left-radius: var(--bs-border-radius-lg) !important; +} + +.rounded-start-4 { + border-bottom-left-radius: var(--bs-border-radius-xl) !important; + border-top-left-radius: var(--bs-border-radius-xl) !important; +} + +.rounded-start-5 { + border-bottom-left-radius: var(--bs-border-radius-xxl) !important; + border-top-left-radius: var(--bs-border-radius-xxl) !important; +} + +.rounded-start-circle { + border-bottom-left-radius: 50% !important; + border-top-left-radius: 50% !important; +} + +.rounded-start-pill { + border-bottom-left-radius: var(--bs-border-radius-pill) !important; + border-top-left-radius: var(--bs-border-radius-pill) !important; +} + +.visible { + visibility: visible !important; +} + +.invisible { + visibility: hidden !important; +} + +.z-n1 { + z-index: -1 !important; +} + +.z-0 { + z-index: 0 !important; +} + +.z-1 { + z-index: 1 !important; +} + +.z-2 { + z-index: 2 !important; +} + +.z-3 { + z-index: 3 !important; +} + +@media (min-width: 576px) { + .float-sm-start { + float: left !important; + } + .float-sm-end { + float: right !important; + } + .float-sm-none { + float: none !important; + } + .object-fit-sm-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-sm-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-sm-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-sm-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-sm-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-sm-inline { + display: inline !important; + } + .d-sm-inline-block { + display: inline-block !important; + } + .d-sm-block { + display: block !important; + } + .d-sm-grid { + display: grid !important; + } + .d-sm-inline-grid { + display: inline-grid !important; + } + .d-sm-table { + display: table !important; + } + .d-sm-table-row { + display: table-row !important; + } + .d-sm-table-cell { + display: table-cell !important; + } + .d-sm-flex { + display: flex !important; + } + .d-sm-inline-flex { + display: inline-flex !important; + } + .d-sm-none { + display: none !important; + } + .flex-sm-fill { + flex: 1 1 auto !important; + } + .flex-sm-row { + flex-direction: row !important; + } + .flex-sm-column { + flex-direction: column !important; + } + .flex-sm-row-reverse { + flex-direction: row-reverse !important; + } + .flex-sm-column-reverse { + flex-direction: column-reverse !important; + } + .flex-sm-grow-0 { + flex-grow: 0 !important; + } + .flex-sm-grow-1 { + flex-grow: 1 !important; + } + .flex-sm-shrink-0 { + flex-shrink: 0 !important; + } + .flex-sm-shrink-1 { + flex-shrink: 1 !important; + } + .flex-sm-wrap { + flex-wrap: wrap !important; + } + .flex-sm-nowrap { + flex-wrap: nowrap !important; + } + .flex-sm-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-sm-start { + justify-content: flex-start !important; + } + .justify-content-sm-end { + justify-content: flex-end !important; + } + .justify-content-sm-center { + justify-content: center !important; + } + .justify-content-sm-between { + justify-content: space-between !important; + } + .justify-content-sm-around { + justify-content: space-around !important; + } + .justify-content-sm-evenly { + justify-content: space-evenly !important; + } + .align-items-sm-start { + align-items: flex-start !important; + } + .align-items-sm-end { + align-items: flex-end !important; + } + .align-items-sm-center { + align-items: center !important; + } + .align-items-sm-baseline { + align-items: baseline !important; + } + .align-items-sm-stretch { + align-items: stretch !important; + } + .align-content-sm-start { + align-content: flex-start !important; + } + .align-content-sm-end { + align-content: flex-end !important; + } + .align-content-sm-center { + align-content: center !important; + } + .align-content-sm-between { + align-content: space-between !important; + } + .align-content-sm-around { + align-content: space-around !important; + } + .align-content-sm-stretch { + align-content: stretch !important; + } + .align-self-sm-auto { + align-self: auto !important; + } + .align-self-sm-start { + align-self: flex-start !important; + } + .align-self-sm-end { + align-self: flex-end !important; + } + .align-self-sm-center { + align-self: center !important; + } + .align-self-sm-baseline { + align-self: baseline !important; + } + .align-self-sm-stretch { + align-self: stretch !important; + } + .order-sm-first { + order: -1 !important; + } + .order-sm-0 { + order: 0 !important; + } + .order-sm-1 { + order: 1 !important; + } + .order-sm-2 { + order: 2 !important; + } + .order-sm-3 { + order: 3 !important; + } + .order-sm-4 { + order: 4 !important; + } + .order-sm-5 { + order: 5 !important; + } + .order-sm-last { + order: 6 !important; + } + .m-sm-0 { + margin: 0 !important; + } + .m-sm-1 { + margin: 0.25rem !important; + } + .m-sm-2 { + margin: 0.5rem !important; + } + .m-sm-3 { + margin: 1rem !important; + } + .m-sm-4 { + margin: 1.5rem !important; + } + .m-sm-5 { + margin: 3rem !important; + } + .m-sm-auto { + margin: auto !important; + } + .mx-sm-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-sm-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-sm-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-sm-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-sm-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-sm-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-sm-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-sm-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-sm-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-sm-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-sm-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-sm-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-sm-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-sm-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-sm-0 { + margin-top: 0 !important; + } + .mt-sm-1 { + margin-top: 0.25rem !important; + } + .mt-sm-2 { + margin-top: 0.5rem !important; + } + .mt-sm-3 { + margin-top: 1rem !important; + } + .mt-sm-4 { + margin-top: 1.5rem !important; + } + .mt-sm-5 { + margin-top: 3rem !important; + } + .mt-sm-auto { + margin-top: auto !important; + } + .me-sm-0 { + margin-right: 0 !important; + } + .me-sm-1 { + margin-right: 0.25rem !important; + } + .me-sm-2 { + margin-right: 0.5rem !important; + } + .me-sm-3 { + margin-right: 1rem !important; + } + .me-sm-4 { + margin-right: 1.5rem !important; + } + .me-sm-5 { + margin-right: 3rem !important; + } + .me-sm-auto { + margin-right: auto !important; + } + .mb-sm-0 { + margin-bottom: 0 !important; + } + .mb-sm-1 { + margin-bottom: 0.25rem !important; + } + .mb-sm-2 { + margin-bottom: 0.5rem !important; + } + .mb-sm-3 { + margin-bottom: 1rem !important; + } + .mb-sm-4 { + margin-bottom: 1.5rem !important; + } + .mb-sm-5 { + margin-bottom: 3rem !important; + } + .mb-sm-auto { + margin-bottom: auto !important; + } + .ms-sm-0 { + margin-left: 0 !important; + } + .ms-sm-1 { + margin-left: 0.25rem !important; + } + .ms-sm-2 { + margin-left: 0.5rem !important; + } + .ms-sm-3 { + margin-left: 1rem !important; + } + .ms-sm-4 { + margin-left: 1.5rem !important; + } + .ms-sm-5 { + margin-left: 3rem !important; + } + .ms-sm-auto { + margin-left: auto !important; + } + .p-sm-0 { + padding: 0 !important; + } + .p-sm-1 { + padding: 0.25rem !important; + } + .p-sm-2 { + padding: 0.5rem !important; + } + .p-sm-3 { + padding: 1rem !important; + } + .p-sm-4 { + padding: 1.5rem !important; + } + .p-sm-5 { + padding: 3rem !important; + } + .px-sm-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-sm-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-sm-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-sm-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-sm-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-sm-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-sm-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-sm-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-sm-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-sm-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-sm-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-sm-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-sm-0 { + padding-top: 0 !important; + } + .pt-sm-1 { + padding-top: 0.25rem !important; + } + .pt-sm-2 { + padding-top: 0.5rem !important; + } + .pt-sm-3 { + padding-top: 1rem !important; + } + .pt-sm-4 { + padding-top: 1.5rem !important; + } + .pt-sm-5 { + padding-top: 3rem !important; + } + .pe-sm-0 { + padding-right: 0 !important; + } + .pe-sm-1 { + padding-right: 0.25rem !important; + } + .pe-sm-2 { + padding-right: 0.5rem !important; + } + .pe-sm-3 { + padding-right: 1rem !important; + } + .pe-sm-4 { + padding-right: 1.5rem !important; + } + .pe-sm-5 { + padding-right: 3rem !important; + } + .pb-sm-0 { + padding-bottom: 0 !important; + } + .pb-sm-1 { + padding-bottom: 0.25rem !important; + } + .pb-sm-2 { + padding-bottom: 0.5rem !important; + } + .pb-sm-3 { + padding-bottom: 1rem !important; + } + .pb-sm-4 { + padding-bottom: 1.5rem !important; + } + .pb-sm-5 { + padding-bottom: 3rem !important; + } + .ps-sm-0 { + padding-left: 0 !important; + } + .ps-sm-1 { + padding-left: 0.25rem !important; + } + .ps-sm-2 { + padding-left: 0.5rem !important; + } + .ps-sm-3 { + padding-left: 1rem !important; + } + .ps-sm-4 { + padding-left: 1.5rem !important; + } + .ps-sm-5 { + padding-left: 3rem !important; + } + .gap-sm-0 { + gap: 0 !important; + } + .gap-sm-1 { + gap: 0.25rem !important; + } + .gap-sm-2 { + gap: 0.5rem !important; + } + .gap-sm-3 { + gap: 1rem !important; + } + .gap-sm-4 { + gap: 1.5rem !important; + } + .gap-sm-5 { + gap: 3rem !important; + } + .row-gap-sm-0 { + row-gap: 0 !important; + } + .row-gap-sm-1 { + row-gap: 0.25rem !important; + } + .row-gap-sm-2 { + row-gap: 0.5rem !important; + } + .row-gap-sm-3 { + row-gap: 1rem !important; + } + .row-gap-sm-4 { + row-gap: 1.5rem !important; + } + .row-gap-sm-5 { + row-gap: 3rem !important; + } + .column-gap-sm-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-sm-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-sm-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-sm-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-sm-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-sm-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-sm-start { + text-align: left !important; + } + .text-sm-end { + text-align: right !important; + } + .text-sm-center { + text-align: center !important; + } +} +@media (min-width: 768px) { + .float-md-start { + float: left !important; + } + .float-md-end { + float: right !important; + } + .float-md-none { + float: none !important; + } + .object-fit-md-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-md-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-md-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-md-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-md-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-md-inline { + display: inline !important; + } + .d-md-inline-block { + display: inline-block !important; + } + .d-md-block { + display: block !important; + } + .d-md-grid { + display: grid !important; + } + .d-md-inline-grid { + display: inline-grid !important; + } + .d-md-table { + display: table !important; + } + .d-md-table-row { + display: table-row !important; + } + .d-md-table-cell { + display: table-cell !important; + } + .d-md-flex { + display: flex !important; + } + .d-md-inline-flex { + display: inline-flex !important; + } + .d-md-none { + display: none !important; + } + .flex-md-fill { + flex: 1 1 auto !important; + } + .flex-md-row { + flex-direction: row !important; + } + .flex-md-column { + flex-direction: column !important; + } + .flex-md-row-reverse { + flex-direction: row-reverse !important; + } + .flex-md-column-reverse { + flex-direction: column-reverse !important; + } + .flex-md-grow-0 { + flex-grow: 0 !important; + } + .flex-md-grow-1 { + flex-grow: 1 !important; + } + .flex-md-shrink-0 { + flex-shrink: 0 !important; + } + .flex-md-shrink-1 { + flex-shrink: 1 !important; + } + .flex-md-wrap { + flex-wrap: wrap !important; + } + .flex-md-nowrap { + flex-wrap: nowrap !important; + } + .flex-md-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-md-start { + justify-content: flex-start !important; + } + .justify-content-md-end { + justify-content: flex-end !important; + } + .justify-content-md-center { + justify-content: center !important; + } + .justify-content-md-between { + justify-content: space-between !important; + } + .justify-content-md-around { + justify-content: space-around !important; + } + .justify-content-md-evenly { + justify-content: space-evenly !important; + } + .align-items-md-start { + align-items: flex-start !important; + } + .align-items-md-end { + align-items: flex-end !important; + } + .align-items-md-center { + align-items: center !important; + } + .align-items-md-baseline { + align-items: baseline !important; + } + .align-items-md-stretch { + align-items: stretch !important; + } + .align-content-md-start { + align-content: flex-start !important; + } + .align-content-md-end { + align-content: flex-end !important; + } + .align-content-md-center { + align-content: center !important; + } + .align-content-md-between { + align-content: space-between !important; + } + .align-content-md-around { + align-content: space-around !important; + } + .align-content-md-stretch { + align-content: stretch !important; + } + .align-self-md-auto { + align-self: auto !important; + } + .align-self-md-start { + align-self: flex-start !important; + } + .align-self-md-end { + align-self: flex-end !important; + } + .align-self-md-center { + align-self: center !important; + } + .align-self-md-baseline { + align-self: baseline !important; + } + .align-self-md-stretch { + align-self: stretch !important; + } + .order-md-first { + order: -1 !important; + } + .order-md-0 { + order: 0 !important; + } + .order-md-1 { + order: 1 !important; + } + .order-md-2 { + order: 2 !important; + } + .order-md-3 { + order: 3 !important; + } + .order-md-4 { + order: 4 !important; + } + .order-md-5 { + order: 5 !important; + } + .order-md-last { + order: 6 !important; + } + .m-md-0 { + margin: 0 !important; + } + .m-md-1 { + margin: 0.25rem !important; + } + .m-md-2 { + margin: 0.5rem !important; + } + .m-md-3 { + margin: 1rem !important; + } + .m-md-4 { + margin: 1.5rem !important; + } + .m-md-5 { + margin: 3rem !important; + } + .m-md-auto { + margin: auto !important; + } + .mx-md-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-md-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-md-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-md-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-md-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-md-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-md-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-md-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-md-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-md-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-md-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-md-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-md-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-md-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-md-0 { + margin-top: 0 !important; + } + .mt-md-1 { + margin-top: 0.25rem !important; + } + .mt-md-2 { + margin-top: 0.5rem !important; + } + .mt-md-3 { + margin-top: 1rem !important; + } + .mt-md-4 { + margin-top: 1.5rem !important; + } + .mt-md-5 { + margin-top: 3rem !important; + } + .mt-md-auto { + margin-top: auto !important; + } + .me-md-0 { + margin-right: 0 !important; + } + .me-md-1 { + margin-right: 0.25rem !important; + } + .me-md-2 { + margin-right: 0.5rem !important; + } + .me-md-3 { + margin-right: 1rem !important; + } + .me-md-4 { + margin-right: 1.5rem !important; + } + .me-md-5 { + margin-right: 3rem !important; + } + .me-md-auto { + margin-right: auto !important; + } + .mb-md-0 { + margin-bottom: 0 !important; + } + .mb-md-1 { + margin-bottom: 0.25rem !important; + } + .mb-md-2 { + margin-bottom: 0.5rem !important; + } + .mb-md-3 { + margin-bottom: 1rem !important; + } + .mb-md-4 { + margin-bottom: 1.5rem !important; + } + .mb-md-5 { + margin-bottom: 3rem !important; + } + .mb-md-auto { + margin-bottom: auto !important; + } + .ms-md-0 { + margin-left: 0 !important; + } + .ms-md-1 { + margin-left: 0.25rem !important; + } + .ms-md-2 { + margin-left: 0.5rem !important; + } + .ms-md-3 { + margin-left: 1rem !important; + } + .ms-md-4 { + margin-left: 1.5rem !important; + } + .ms-md-5 { + margin-left: 3rem !important; + } + .ms-md-auto { + margin-left: auto !important; + } + .p-md-0 { + padding: 0 !important; + } + .p-md-1 { + padding: 0.25rem !important; + } + .p-md-2 { + padding: 0.5rem !important; + } + .p-md-3 { + padding: 1rem !important; + } + .p-md-4 { + padding: 1.5rem !important; + } + .p-md-5 { + padding: 3rem !important; + } + .px-md-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-md-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-md-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-md-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-md-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-md-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-md-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-md-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-md-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-md-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-md-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-md-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-md-0 { + padding-top: 0 !important; + } + .pt-md-1 { + padding-top: 0.25rem !important; + } + .pt-md-2 { + padding-top: 0.5rem !important; + } + .pt-md-3 { + padding-top: 1rem !important; + } + .pt-md-4 { + padding-top: 1.5rem !important; + } + .pt-md-5 { + padding-top: 3rem !important; + } + .pe-md-0 { + padding-right: 0 !important; + } + .pe-md-1 { + padding-right: 0.25rem !important; + } + .pe-md-2 { + padding-right: 0.5rem !important; + } + .pe-md-3 { + padding-right: 1rem !important; + } + .pe-md-4 { + padding-right: 1.5rem !important; + } + .pe-md-5 { + padding-right: 3rem !important; + } + .pb-md-0 { + padding-bottom: 0 !important; + } + .pb-md-1 { + padding-bottom: 0.25rem !important; + } + .pb-md-2 { + padding-bottom: 0.5rem !important; + } + .pb-md-3 { + padding-bottom: 1rem !important; + } + .pb-md-4 { + padding-bottom: 1.5rem !important; + } + .pb-md-5 { + padding-bottom: 3rem !important; + } + .ps-md-0 { + padding-left: 0 !important; + } + .ps-md-1 { + padding-left: 0.25rem !important; + } + .ps-md-2 { + padding-left: 0.5rem !important; + } + .ps-md-3 { + padding-left: 1rem !important; + } + .ps-md-4 { + padding-left: 1.5rem !important; + } + .ps-md-5 { + padding-left: 3rem !important; + } + .gap-md-0 { + gap: 0 !important; + } + .gap-md-1 { + gap: 0.25rem !important; + } + .gap-md-2 { + gap: 0.5rem !important; + } + .gap-md-3 { + gap: 1rem !important; + } + .gap-md-4 { + gap: 1.5rem !important; + } + .gap-md-5 { + gap: 3rem !important; + } + .row-gap-md-0 { + row-gap: 0 !important; + } + .row-gap-md-1 { + row-gap: 0.25rem !important; + } + .row-gap-md-2 { + row-gap: 0.5rem !important; + } + .row-gap-md-3 { + row-gap: 1rem !important; + } + .row-gap-md-4 { + row-gap: 1.5rem !important; + } + .row-gap-md-5 { + row-gap: 3rem !important; + } + .column-gap-md-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-md-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-md-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-md-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-md-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-md-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-md-start { + text-align: left !important; + } + .text-md-end { + text-align: right !important; + } + .text-md-center { + text-align: center !important; + } +} +@media (min-width: 992px) { + .float-lg-start { + float: left !important; + } + .float-lg-end { + float: right !important; + } + .float-lg-none { + float: none !important; + } + .object-fit-lg-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-lg-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-lg-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-lg-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-lg-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-lg-inline { + display: inline !important; + } + .d-lg-inline-block { + display: inline-block !important; + } + .d-lg-block { + display: block !important; + } + .d-lg-grid { + display: grid !important; + } + .d-lg-inline-grid { + display: inline-grid !important; + } + .d-lg-table { + display: table !important; + } + .d-lg-table-row { + display: table-row !important; + } + .d-lg-table-cell { + display: table-cell !important; + } + .d-lg-flex { + display: flex !important; + } + .d-lg-inline-flex { + display: inline-flex !important; + } + .d-lg-none { + display: none !important; + } + .flex-lg-fill { + flex: 1 1 auto !important; + } + .flex-lg-row { + flex-direction: row !important; + } + .flex-lg-column { + flex-direction: column !important; + } + .flex-lg-row-reverse { + flex-direction: row-reverse !important; + } + .flex-lg-column-reverse { + flex-direction: column-reverse !important; + } + .flex-lg-grow-0 { + flex-grow: 0 !important; + } + .flex-lg-grow-1 { + flex-grow: 1 !important; + } + .flex-lg-shrink-0 { + flex-shrink: 0 !important; + } + .flex-lg-shrink-1 { + flex-shrink: 1 !important; + } + .flex-lg-wrap { + flex-wrap: wrap !important; + } + .flex-lg-nowrap { + flex-wrap: nowrap !important; + } + .flex-lg-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-lg-start { + justify-content: flex-start !important; + } + .justify-content-lg-end { + justify-content: flex-end !important; + } + .justify-content-lg-center { + justify-content: center !important; + } + .justify-content-lg-between { + justify-content: space-between !important; + } + .justify-content-lg-around { + justify-content: space-around !important; + } + .justify-content-lg-evenly { + justify-content: space-evenly !important; + } + .align-items-lg-start { + align-items: flex-start !important; + } + .align-items-lg-end { + align-items: flex-end !important; + } + .align-items-lg-center { + align-items: center !important; + } + .align-items-lg-baseline { + align-items: baseline !important; + } + .align-items-lg-stretch { + align-items: stretch !important; + } + .align-content-lg-start { + align-content: flex-start !important; + } + .align-content-lg-end { + align-content: flex-end !important; + } + .align-content-lg-center { + align-content: center !important; + } + .align-content-lg-between { + align-content: space-between !important; + } + .align-content-lg-around { + align-content: space-around !important; + } + .align-content-lg-stretch { + align-content: stretch !important; + } + .align-self-lg-auto { + align-self: auto !important; + } + .align-self-lg-start { + align-self: flex-start !important; + } + .align-self-lg-end { + align-self: flex-end !important; + } + .align-self-lg-center { + align-self: center !important; + } + .align-self-lg-baseline { + align-self: baseline !important; + } + .align-self-lg-stretch { + align-self: stretch !important; + } + .order-lg-first { + order: -1 !important; + } + .order-lg-0 { + order: 0 !important; + } + .order-lg-1 { + order: 1 !important; + } + .order-lg-2 { + order: 2 !important; + } + .order-lg-3 { + order: 3 !important; + } + .order-lg-4 { + order: 4 !important; + } + .order-lg-5 { + order: 5 !important; + } + .order-lg-last { + order: 6 !important; + } + .m-lg-0 { + margin: 0 !important; + } + .m-lg-1 { + margin: 0.25rem !important; + } + .m-lg-2 { + margin: 0.5rem !important; + } + .m-lg-3 { + margin: 1rem !important; + } + .m-lg-4 { + margin: 1.5rem !important; + } + .m-lg-5 { + margin: 3rem !important; + } + .m-lg-auto { + margin: auto !important; + } + .mx-lg-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-lg-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-lg-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-lg-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-lg-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-lg-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-lg-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-lg-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-lg-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-lg-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-lg-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-lg-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-lg-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-lg-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-lg-0 { + margin-top: 0 !important; + } + .mt-lg-1 { + margin-top: 0.25rem !important; + } + .mt-lg-2 { + margin-top: 0.5rem !important; + } + .mt-lg-3 { + margin-top: 1rem !important; + } + .mt-lg-4 { + margin-top: 1.5rem !important; + } + .mt-lg-5 { + margin-top: 3rem !important; + } + .mt-lg-auto { + margin-top: auto !important; + } + .me-lg-0 { + margin-right: 0 !important; + } + .me-lg-1 { + margin-right: 0.25rem !important; + } + .me-lg-2 { + margin-right: 0.5rem !important; + } + .me-lg-3 { + margin-right: 1rem !important; + } + .me-lg-4 { + margin-right: 1.5rem !important; + } + .me-lg-5 { + margin-right: 3rem !important; + } + .me-lg-auto { + margin-right: auto !important; + } + .mb-lg-0 { + margin-bottom: 0 !important; + } + .mb-lg-1 { + margin-bottom: 0.25rem !important; + } + .mb-lg-2 { + margin-bottom: 0.5rem !important; + } + .mb-lg-3 { + margin-bottom: 1rem !important; + } + .mb-lg-4 { + margin-bottom: 1.5rem !important; + } + .mb-lg-5 { + margin-bottom: 3rem !important; + } + .mb-lg-auto { + margin-bottom: auto !important; + } + .ms-lg-0 { + margin-left: 0 !important; + } + .ms-lg-1 { + margin-left: 0.25rem !important; + } + .ms-lg-2 { + margin-left: 0.5rem !important; + } + .ms-lg-3 { + margin-left: 1rem !important; + } + .ms-lg-4 { + margin-left: 1.5rem !important; + } + .ms-lg-5 { + margin-left: 3rem !important; + } + .ms-lg-auto { + margin-left: auto !important; + } + .p-lg-0 { + padding: 0 !important; + } + .p-lg-1 { + padding: 0.25rem !important; + } + .p-lg-2 { + padding: 0.5rem !important; + } + .p-lg-3 { + padding: 1rem !important; + } + .p-lg-4 { + padding: 1.5rem !important; + } + .p-lg-5 { + padding: 3rem !important; + } + .px-lg-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-lg-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-lg-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-lg-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-lg-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-lg-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-lg-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-lg-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-lg-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-lg-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-lg-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-lg-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-lg-0 { + padding-top: 0 !important; + } + .pt-lg-1 { + padding-top: 0.25rem !important; + } + .pt-lg-2 { + padding-top: 0.5rem !important; + } + .pt-lg-3 { + padding-top: 1rem !important; + } + .pt-lg-4 { + padding-top: 1.5rem !important; + } + .pt-lg-5 { + padding-top: 3rem !important; + } + .pe-lg-0 { + padding-right: 0 !important; + } + .pe-lg-1 { + padding-right: 0.25rem !important; + } + .pe-lg-2 { + padding-right: 0.5rem !important; + } + .pe-lg-3 { + padding-right: 1rem !important; + } + .pe-lg-4 { + padding-right: 1.5rem !important; + } + .pe-lg-5 { + padding-right: 3rem !important; + } + .pb-lg-0 { + padding-bottom: 0 !important; + } + .pb-lg-1 { + padding-bottom: 0.25rem !important; + } + .pb-lg-2 { + padding-bottom: 0.5rem !important; + } + .pb-lg-3 { + padding-bottom: 1rem !important; + } + .pb-lg-4 { + padding-bottom: 1.5rem !important; + } + .pb-lg-5 { + padding-bottom: 3rem !important; + } + .ps-lg-0 { + padding-left: 0 !important; + } + .ps-lg-1 { + padding-left: 0.25rem !important; + } + .ps-lg-2 { + padding-left: 0.5rem !important; + } + .ps-lg-3 { + padding-left: 1rem !important; + } + .ps-lg-4 { + padding-left: 1.5rem !important; + } + .ps-lg-5 { + padding-left: 3rem !important; + } + .gap-lg-0 { + gap: 0 !important; + } + .gap-lg-1 { + gap: 0.25rem !important; + } + .gap-lg-2 { + gap: 0.5rem !important; + } + .gap-lg-3 { + gap: 1rem !important; + } + .gap-lg-4 { + gap: 1.5rem !important; + } + .gap-lg-5 { + gap: 3rem !important; + } + .row-gap-lg-0 { + row-gap: 0 !important; + } + .row-gap-lg-1 { + row-gap: 0.25rem !important; + } + .row-gap-lg-2 { + row-gap: 0.5rem !important; + } + .row-gap-lg-3 { + row-gap: 1rem !important; + } + .row-gap-lg-4 { + row-gap: 1.5rem !important; + } + .row-gap-lg-5 { + row-gap: 3rem !important; + } + .column-gap-lg-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-lg-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-lg-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-lg-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-lg-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-lg-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-lg-start { + text-align: left !important; + } + .text-lg-end { + text-align: right !important; + } + .text-lg-center { + text-align: center !important; + } +} +@media (min-width: 1200px) { + .float-xl-start { + float: left !important; + } + .float-xl-end { + float: right !important; + } + .float-xl-none { + float: none !important; + } + .object-fit-xl-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-xl-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-xl-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-xl-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-xl-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-xl-inline { + display: inline !important; + } + .d-xl-inline-block { + display: inline-block !important; + } + .d-xl-block { + display: block !important; + } + .d-xl-grid { + display: grid !important; + } + .d-xl-inline-grid { + display: inline-grid !important; + } + .d-xl-table { + display: table !important; + } + .d-xl-table-row { + display: table-row !important; + } + .d-xl-table-cell { + display: table-cell !important; + } + .d-xl-flex { + display: flex !important; + } + .d-xl-inline-flex { + display: inline-flex !important; + } + .d-xl-none { + display: none !important; + } + .flex-xl-fill { + flex: 1 1 auto !important; + } + .flex-xl-row { + flex-direction: row !important; + } + .flex-xl-column { + flex-direction: column !important; + } + .flex-xl-row-reverse { + flex-direction: row-reverse !important; + } + .flex-xl-column-reverse { + flex-direction: column-reverse !important; + } + .flex-xl-grow-0 { + flex-grow: 0 !important; + } + .flex-xl-grow-1 { + flex-grow: 1 !important; + } + .flex-xl-shrink-0 { + flex-shrink: 0 !important; + } + .flex-xl-shrink-1 { + flex-shrink: 1 !important; + } + .flex-xl-wrap { + flex-wrap: wrap !important; + } + .flex-xl-nowrap { + flex-wrap: nowrap !important; + } + .flex-xl-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-xl-start { + justify-content: flex-start !important; + } + .justify-content-xl-end { + justify-content: flex-end !important; + } + .justify-content-xl-center { + justify-content: center !important; + } + .justify-content-xl-between { + justify-content: space-between !important; + } + .justify-content-xl-around { + justify-content: space-around !important; + } + .justify-content-xl-evenly { + justify-content: space-evenly !important; + } + .align-items-xl-start { + align-items: flex-start !important; + } + .align-items-xl-end { + align-items: flex-end !important; + } + .align-items-xl-center { + align-items: center !important; + } + .align-items-xl-baseline { + align-items: baseline !important; + } + .align-items-xl-stretch { + align-items: stretch !important; + } + .align-content-xl-start { + align-content: flex-start !important; + } + .align-content-xl-end { + align-content: flex-end !important; + } + .align-content-xl-center { + align-content: center !important; + } + .align-content-xl-between { + align-content: space-between !important; + } + .align-content-xl-around { + align-content: space-around !important; + } + .align-content-xl-stretch { + align-content: stretch !important; + } + .align-self-xl-auto { + align-self: auto !important; + } + .align-self-xl-start { + align-self: flex-start !important; + } + .align-self-xl-end { + align-self: flex-end !important; + } + .align-self-xl-center { + align-self: center !important; + } + .align-self-xl-baseline { + align-self: baseline !important; + } + .align-self-xl-stretch { + align-self: stretch !important; + } + .order-xl-first { + order: -1 !important; + } + .order-xl-0 { + order: 0 !important; + } + .order-xl-1 { + order: 1 !important; + } + .order-xl-2 { + order: 2 !important; + } + .order-xl-3 { + order: 3 !important; + } + .order-xl-4 { + order: 4 !important; + } + .order-xl-5 { + order: 5 !important; + } + .order-xl-last { + order: 6 !important; + } + .m-xl-0 { + margin: 0 !important; + } + .m-xl-1 { + margin: 0.25rem !important; + } + .m-xl-2 { + margin: 0.5rem !important; + } + .m-xl-3 { + margin: 1rem !important; + } + .m-xl-4 { + margin: 1.5rem !important; + } + .m-xl-5 { + margin: 3rem !important; + } + .m-xl-auto { + margin: auto !important; + } + .mx-xl-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-xl-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-xl-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-xl-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-xl-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-xl-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-xl-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-xl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-xl-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-xl-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-xl-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-xl-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-xl-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-xl-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-xl-0 { + margin-top: 0 !important; + } + .mt-xl-1 { + margin-top: 0.25rem !important; + } + .mt-xl-2 { + margin-top: 0.5rem !important; + } + .mt-xl-3 { + margin-top: 1rem !important; + } + .mt-xl-4 { + margin-top: 1.5rem !important; + } + .mt-xl-5 { + margin-top: 3rem !important; + } + .mt-xl-auto { + margin-top: auto !important; + } + .me-xl-0 { + margin-right: 0 !important; + } + .me-xl-1 { + margin-right: 0.25rem !important; + } + .me-xl-2 { + margin-right: 0.5rem !important; + } + .me-xl-3 { + margin-right: 1rem !important; + } + .me-xl-4 { + margin-right: 1.5rem !important; + } + .me-xl-5 { + margin-right: 3rem !important; + } + .me-xl-auto { + margin-right: auto !important; + } + .mb-xl-0 { + margin-bottom: 0 !important; + } + .mb-xl-1 { + margin-bottom: 0.25rem !important; + } + .mb-xl-2 { + margin-bottom: 0.5rem !important; + } + .mb-xl-3 { + margin-bottom: 1rem !important; + } + .mb-xl-4 { + margin-bottom: 1.5rem !important; + } + .mb-xl-5 { + margin-bottom: 3rem !important; + } + .mb-xl-auto { + margin-bottom: auto !important; + } + .ms-xl-0 { + margin-left: 0 !important; + } + .ms-xl-1 { + margin-left: 0.25rem !important; + } + .ms-xl-2 { + margin-left: 0.5rem !important; + } + .ms-xl-3 { + margin-left: 1rem !important; + } + .ms-xl-4 { + margin-left: 1.5rem !important; + } + .ms-xl-5 { + margin-left: 3rem !important; + } + .ms-xl-auto { + margin-left: auto !important; + } + .p-xl-0 { + padding: 0 !important; + } + .p-xl-1 { + padding: 0.25rem !important; + } + .p-xl-2 { + padding: 0.5rem !important; + } + .p-xl-3 { + padding: 1rem !important; + } + .p-xl-4 { + padding: 1.5rem !important; + } + .p-xl-5 { + padding: 3rem !important; + } + .px-xl-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-xl-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-xl-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-xl-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-xl-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-xl-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-xl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-xl-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-xl-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-xl-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-xl-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-xl-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-xl-0 { + padding-top: 0 !important; + } + .pt-xl-1 { + padding-top: 0.25rem !important; + } + .pt-xl-2 { + padding-top: 0.5rem !important; + } + .pt-xl-3 { + padding-top: 1rem !important; + } + .pt-xl-4 { + padding-top: 1.5rem !important; + } + .pt-xl-5 { + padding-top: 3rem !important; + } + .pe-xl-0 { + padding-right: 0 !important; + } + .pe-xl-1 { + padding-right: 0.25rem !important; + } + .pe-xl-2 { + padding-right: 0.5rem !important; + } + .pe-xl-3 { + padding-right: 1rem !important; + } + .pe-xl-4 { + padding-right: 1.5rem !important; + } + .pe-xl-5 { + padding-right: 3rem !important; + } + .pb-xl-0 { + padding-bottom: 0 !important; + } + .pb-xl-1 { + padding-bottom: 0.25rem !important; + } + .pb-xl-2 { + padding-bottom: 0.5rem !important; + } + .pb-xl-3 { + padding-bottom: 1rem !important; + } + .pb-xl-4 { + padding-bottom: 1.5rem !important; + } + .pb-xl-5 { + padding-bottom: 3rem !important; + } + .ps-xl-0 { + padding-left: 0 !important; + } + .ps-xl-1 { + padding-left: 0.25rem !important; + } + .ps-xl-2 { + padding-left: 0.5rem !important; + } + .ps-xl-3 { + padding-left: 1rem !important; + } + .ps-xl-4 { + padding-left: 1.5rem !important; + } + .ps-xl-5 { + padding-left: 3rem !important; + } + .gap-xl-0 { + gap: 0 !important; + } + .gap-xl-1 { + gap: 0.25rem !important; + } + .gap-xl-2 { + gap: 0.5rem !important; + } + .gap-xl-3 { + gap: 1rem !important; + } + .gap-xl-4 { + gap: 1.5rem !important; + } + .gap-xl-5 { + gap: 3rem !important; + } + .row-gap-xl-0 { + row-gap: 0 !important; + } + .row-gap-xl-1 { + row-gap: 0.25rem !important; + } + .row-gap-xl-2 { + row-gap: 0.5rem !important; + } + .row-gap-xl-3 { + row-gap: 1rem !important; + } + .row-gap-xl-4 { + row-gap: 1.5rem !important; + } + .row-gap-xl-5 { + row-gap: 3rem !important; + } + .column-gap-xl-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-xl-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-xl-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-xl-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-xl-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-xl-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-xl-start { + text-align: left !important; + } + .text-xl-end { + text-align: right !important; + } + .text-xl-center { + text-align: center !important; + } +} +@media (min-width: 1400px) { + .float-xxl-start { + float: left !important; + } + .float-xxl-end { + float: right !important; + } + .float-xxl-none { + float: none !important; + } + .object-fit-xxl-contain { + -o-object-fit: contain !important; + object-fit: contain !important; + } + .object-fit-xxl-cover { + -o-object-fit: cover !important; + object-fit: cover !important; + } + .object-fit-xxl-fill { + -o-object-fit: fill !important; + object-fit: fill !important; + } + .object-fit-xxl-scale { + -o-object-fit: scale-down !important; + object-fit: scale-down !important; + } + .object-fit-xxl-none { + -o-object-fit: none !important; + object-fit: none !important; + } + .d-xxl-inline { + display: inline !important; + } + .d-xxl-inline-block { + display: inline-block !important; + } + .d-xxl-block { + display: block !important; + } + .d-xxl-grid { + display: grid !important; + } + .d-xxl-inline-grid { + display: inline-grid !important; + } + .d-xxl-table { + display: table !important; + } + .d-xxl-table-row { + display: table-row !important; + } + .d-xxl-table-cell { + display: table-cell !important; + } + .d-xxl-flex { + display: flex !important; + } + .d-xxl-inline-flex { + display: inline-flex !important; + } + .d-xxl-none { + display: none !important; + } + .flex-xxl-fill { + flex: 1 1 auto !important; + } + .flex-xxl-row { + flex-direction: row !important; + } + .flex-xxl-column { + flex-direction: column !important; + } + .flex-xxl-row-reverse { + flex-direction: row-reverse !important; + } + .flex-xxl-column-reverse { + flex-direction: column-reverse !important; + } + .flex-xxl-grow-0 { + flex-grow: 0 !important; + } + .flex-xxl-grow-1 { + flex-grow: 1 !important; + } + .flex-xxl-shrink-0 { + flex-shrink: 0 !important; + } + .flex-xxl-shrink-1 { + flex-shrink: 1 !important; + } + .flex-xxl-wrap { + flex-wrap: wrap !important; + } + .flex-xxl-nowrap { + flex-wrap: nowrap !important; + } + .flex-xxl-wrap-reverse { + flex-wrap: wrap-reverse !important; + } + .justify-content-xxl-start { + justify-content: flex-start !important; + } + .justify-content-xxl-end { + justify-content: flex-end !important; + } + .justify-content-xxl-center { + justify-content: center !important; + } + .justify-content-xxl-between { + justify-content: space-between !important; + } + .justify-content-xxl-around { + justify-content: space-around !important; + } + .justify-content-xxl-evenly { + justify-content: space-evenly !important; + } + .align-items-xxl-start { + align-items: flex-start !important; + } + .align-items-xxl-end { + align-items: flex-end !important; + } + .align-items-xxl-center { + align-items: center !important; + } + .align-items-xxl-baseline { + align-items: baseline !important; + } + .align-items-xxl-stretch { + align-items: stretch !important; + } + .align-content-xxl-start { + align-content: flex-start !important; + } + .align-content-xxl-end { + align-content: flex-end !important; + } + .align-content-xxl-center { + align-content: center !important; + } + .align-content-xxl-between { + align-content: space-between !important; + } + .align-content-xxl-around { + align-content: space-around !important; + } + .align-content-xxl-stretch { + align-content: stretch !important; + } + .align-self-xxl-auto { + align-self: auto !important; + } + .align-self-xxl-start { + align-self: flex-start !important; + } + .align-self-xxl-end { + align-self: flex-end !important; + } + .align-self-xxl-center { + align-self: center !important; + } + .align-self-xxl-baseline { + align-self: baseline !important; + } + .align-self-xxl-stretch { + align-self: stretch !important; + } + .order-xxl-first { + order: -1 !important; + } + .order-xxl-0 { + order: 0 !important; + } + .order-xxl-1 { + order: 1 !important; + } + .order-xxl-2 { + order: 2 !important; + } + .order-xxl-3 { + order: 3 !important; + } + .order-xxl-4 { + order: 4 !important; + } + .order-xxl-5 { + order: 5 !important; + } + .order-xxl-last { + order: 6 !important; + } + .m-xxl-0 { + margin: 0 !important; + } + .m-xxl-1 { + margin: 0.25rem !important; + } + .m-xxl-2 { + margin: 0.5rem !important; + } + .m-xxl-3 { + margin: 1rem !important; + } + .m-xxl-4 { + margin: 1.5rem !important; + } + .m-xxl-5 { + margin: 3rem !important; + } + .m-xxl-auto { + margin: auto !important; + } + .mx-xxl-0 { + margin-right: 0 !important; + margin-left: 0 !important; + } + .mx-xxl-1 { + margin-right: 0.25rem !important; + margin-left: 0.25rem !important; + } + .mx-xxl-2 { + margin-right: 0.5rem !important; + margin-left: 0.5rem !important; + } + .mx-xxl-3 { + margin-right: 1rem !important; + margin-left: 1rem !important; + } + .mx-xxl-4 { + margin-right: 1.5rem !important; + margin-left: 1.5rem !important; + } + .mx-xxl-5 { + margin-right: 3rem !important; + margin-left: 3rem !important; + } + .mx-xxl-auto { + margin-right: auto !important; + margin-left: auto !important; + } + .my-xxl-0 { + margin-top: 0 !important; + margin-bottom: 0 !important; + } + .my-xxl-1 { + margin-top: 0.25rem !important; + margin-bottom: 0.25rem !important; + } + .my-xxl-2 { + margin-top: 0.5rem !important; + margin-bottom: 0.5rem !important; + } + .my-xxl-3 { + margin-top: 1rem !important; + margin-bottom: 1rem !important; + } + .my-xxl-4 { + margin-top: 1.5rem !important; + margin-bottom: 1.5rem !important; + } + .my-xxl-5 { + margin-top: 3rem !important; + margin-bottom: 3rem !important; + } + .my-xxl-auto { + margin-top: auto !important; + margin-bottom: auto !important; + } + .mt-xxl-0 { + margin-top: 0 !important; + } + .mt-xxl-1 { + margin-top: 0.25rem !important; + } + .mt-xxl-2 { + margin-top: 0.5rem !important; + } + .mt-xxl-3 { + margin-top: 1rem !important; + } + .mt-xxl-4 { + margin-top: 1.5rem !important; + } + .mt-xxl-5 { + margin-top: 3rem !important; + } + .mt-xxl-auto { + margin-top: auto !important; + } + .me-xxl-0 { + margin-right: 0 !important; + } + .me-xxl-1 { + margin-right: 0.25rem !important; + } + .me-xxl-2 { + margin-right: 0.5rem !important; + } + .me-xxl-3 { + margin-right: 1rem !important; + } + .me-xxl-4 { + margin-right: 1.5rem !important; + } + .me-xxl-5 { + margin-right: 3rem !important; + } + .me-xxl-auto { + margin-right: auto !important; + } + .mb-xxl-0 { + margin-bottom: 0 !important; + } + .mb-xxl-1 { + margin-bottom: 0.25rem !important; + } + .mb-xxl-2 { + margin-bottom: 0.5rem !important; + } + .mb-xxl-3 { + margin-bottom: 1rem !important; + } + .mb-xxl-4 { + margin-bottom: 1.5rem !important; + } + .mb-xxl-5 { + margin-bottom: 3rem !important; + } + .mb-xxl-auto { + margin-bottom: auto !important; + } + .ms-xxl-0 { + margin-left: 0 !important; + } + .ms-xxl-1 { + margin-left: 0.25rem !important; + } + .ms-xxl-2 { + margin-left: 0.5rem !important; + } + .ms-xxl-3 { + margin-left: 1rem !important; + } + .ms-xxl-4 { + margin-left: 1.5rem !important; + } + .ms-xxl-5 { + margin-left: 3rem !important; + } + .ms-xxl-auto { + margin-left: auto !important; + } + .p-xxl-0 { + padding: 0 !important; + } + .p-xxl-1 { + padding: 0.25rem !important; + } + .p-xxl-2 { + padding: 0.5rem !important; + } + .p-xxl-3 { + padding: 1rem !important; + } + .p-xxl-4 { + padding: 1.5rem !important; + } + .p-xxl-5 { + padding: 3rem !important; + } + .px-xxl-0 { + padding-right: 0 !important; + padding-left: 0 !important; + } + .px-xxl-1 { + padding-right: 0.25rem !important; + padding-left: 0.25rem !important; + } + .px-xxl-2 { + padding-right: 0.5rem !important; + padding-left: 0.5rem !important; + } + .px-xxl-3 { + padding-right: 1rem !important; + padding-left: 1rem !important; + } + .px-xxl-4 { + padding-right: 1.5rem !important; + padding-left: 1.5rem !important; + } + .px-xxl-5 { + padding-right: 3rem !important; + padding-left: 3rem !important; + } + .py-xxl-0 { + padding-top: 0 !important; + padding-bottom: 0 !important; + } + .py-xxl-1 { + padding-top: 0.25rem !important; + padding-bottom: 0.25rem !important; + } + .py-xxl-2 { + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + .py-xxl-3 { + padding-top: 1rem !important; + padding-bottom: 1rem !important; + } + .py-xxl-4 { + padding-top: 1.5rem !important; + padding-bottom: 1.5rem !important; + } + .py-xxl-5 { + padding-top: 3rem !important; + padding-bottom: 3rem !important; + } + .pt-xxl-0 { + padding-top: 0 !important; + } + .pt-xxl-1 { + padding-top: 0.25rem !important; + } + .pt-xxl-2 { + padding-top: 0.5rem !important; + } + .pt-xxl-3 { + padding-top: 1rem !important; + } + .pt-xxl-4 { + padding-top: 1.5rem !important; + } + .pt-xxl-5 { + padding-top: 3rem !important; + } + .pe-xxl-0 { + padding-right: 0 !important; + } + .pe-xxl-1 { + padding-right: 0.25rem !important; + } + .pe-xxl-2 { + padding-right: 0.5rem !important; + } + .pe-xxl-3 { + padding-right: 1rem !important; + } + .pe-xxl-4 { + padding-right: 1.5rem !important; + } + .pe-xxl-5 { + padding-right: 3rem !important; + } + .pb-xxl-0 { + padding-bottom: 0 !important; + } + .pb-xxl-1 { + padding-bottom: 0.25rem !important; + } + .pb-xxl-2 { + padding-bottom: 0.5rem !important; + } + .pb-xxl-3 { + padding-bottom: 1rem !important; + } + .pb-xxl-4 { + padding-bottom: 1.5rem !important; + } + .pb-xxl-5 { + padding-bottom: 3rem !important; + } + .ps-xxl-0 { + padding-left: 0 !important; + } + .ps-xxl-1 { + padding-left: 0.25rem !important; + } + .ps-xxl-2 { + padding-left: 0.5rem !important; + } + .ps-xxl-3 { + padding-left: 1rem !important; + } + .ps-xxl-4 { + padding-left: 1.5rem !important; + } + .ps-xxl-5 { + padding-left: 3rem !important; + } + .gap-xxl-0 { + gap: 0 !important; + } + .gap-xxl-1 { + gap: 0.25rem !important; + } + .gap-xxl-2 { + gap: 0.5rem !important; + } + .gap-xxl-3 { + gap: 1rem !important; + } + .gap-xxl-4 { + gap: 1.5rem !important; + } + .gap-xxl-5 { + gap: 3rem !important; + } + .row-gap-xxl-0 { + row-gap: 0 !important; + } + .row-gap-xxl-1 { + row-gap: 0.25rem !important; + } + .row-gap-xxl-2 { + row-gap: 0.5rem !important; + } + .row-gap-xxl-3 { + row-gap: 1rem !important; + } + .row-gap-xxl-4 { + row-gap: 1.5rem !important; + } + .row-gap-xxl-5 { + row-gap: 3rem !important; + } + .column-gap-xxl-0 { + -moz-column-gap: 0 !important; + column-gap: 0 !important; + } + .column-gap-xxl-1 { + -moz-column-gap: 0.25rem !important; + column-gap: 0.25rem !important; + } + .column-gap-xxl-2 { + -moz-column-gap: 0.5rem !important; + column-gap: 0.5rem !important; + } + .column-gap-xxl-3 { + -moz-column-gap: 1rem !important; + column-gap: 1rem !important; + } + .column-gap-xxl-4 { + -moz-column-gap: 1.5rem !important; + column-gap: 1.5rem !important; + } + .column-gap-xxl-5 { + -moz-column-gap: 3rem !important; + column-gap: 3rem !important; + } + .text-xxl-start { + text-align: left !important; + } + .text-xxl-end { + text-align: right !important; + } + .text-xxl-center { + text-align: center !important; + } +} +@media (min-width: 1200px) { + .fs-1 { + font-size: 2.1875rem !important; + } + .fs-2 { + font-size: 1.75rem !important; + } + .fs-3 { + font-size: 1.53125rem !important; + } + .fs-4 { + font-size: 1.3125rem !important; + } +} +@media print { + .d-print-inline { + display: inline !important; + } + .d-print-inline-block { + display: inline-block !important; + } + .d-print-block { + display: block !important; + } + .d-print-grid { + display: grid !important; + } + .d-print-inline-grid { + display: inline-grid !important; + } + .d-print-table { + display: table !important; + } + .d-print-table-row { + display: table-row !important; + } + .d-print-table-cell { + display: table-cell !important; + } + .d-print-flex { + display: flex !important; + } + .d-print-inline-flex { + display: inline-flex !important; + } + .d-print-none { + display: none !important; + } +} +/** This is an auto-generated file, don't edit it. **/ +/** This is an auto-generated file, don't edit it. **/ +.vizro_dark, [data-bs-theme=dark] { + --bs-primary: #1b0734; + --bs-secondary: #808080; + --bs-success: #222222; + --bs-danger: #be3636; + --bs-info: #163960; + --bs-dark: #2a2a2a; + --bs-light: #333333; + --bs-primary-rgb: 27, 7, 52; + --bs-secondary-rgb: 128, 128, 128; + --bs-success-rgb: 34, 34, 34; + --bs-danger-rgb: 190, 54, 54; + --bs-info-rgb: 22, 57, 96; + --bs-dark-rgb: 42, 42, 42; + --bs-light-rgb: 51, 51, 51; +} + +.vizro_light, [data-bs-theme=light] { + --bs-primary: #1b0734; + --bs-secondary: #808080; + --bs-success: #222222; + --bs-danger: #be3636; + --bs-info: #163960; + --bs-dark: #2a2a2a; + --bs-light: #333333; + --bs-primary-rgb: 27, 7, 52; + --bs-secondary-rgb: 128, 128, 128; + --bs-success-rgb: 34, 34, 34; + --bs-danger-rgb: 190, 54, 54; + --bs-info-rgb: 22, 57, 96; + --bs-dark-rgb: 42, 42, 42; + --bs-light-rgb: 51, 51, 51; +} + +/* custom component files */ +/** This is an auto-generated file, don't edit it. **/ +h1, .h1 { + font-size: 32px; + text-decoration: none; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.128px; + line-height: 40px; + color: var(--text-primary); +} + +h2, .h2 { + font-size: 24px; + text-decoration: none; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.096px; + line-height: 32px; + color: var(--text-primary); +} + +h3, .h3, legend { + font-size: 20px; + text-decoration: none; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: 0px; + line-height: 28px; + color: var(--text-primary); +} + +h4, .h4 { + font-size: 16px; + text-decoration: none; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.016px; + line-height: 20px; + color: var(--text-primary); +} + +h5, .h5 { + font-size: 16px; + text-decoration: none; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.016px; + line-height: 20px; + font-size: 14px; + color: var(--text-primary); +} + +h6, .h6 { + font-size: 16px; + text-decoration: none; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.016px; + line-height: 20px; + font-size: 12px; + color: var(--text-primary); +} + +p, label, ul, li, blockquote { + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.112px; + line-height: 16px; + color: var(--text-secondary); +} + +a { + font-size: 16px; + text-decoration: underline; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.128px; + line-height: 20px; +} + +/* This is not taken over well. How do we deal with bootstrap variables that we want to change globally? */ +/* QQ: This should be changed globally via Bootstrap variables and without the need of important tags. */ +.text-muted { + color: var(--text-subtle); +} + +.text-primary { + color: var(--text-primary) !important; +} + +.text-secondary { + color: var(--text-secondary) !important; +} + +/* Types */ +blockquote { + border-left: 0.25rem var(--text-secondary) solid; + margin-bottom: 0.5rem; + padding-left: 0.25rem; +} + +hr { + border-bottom: 1px solid var(--border-subtleAlpha01); + width: 100%; +} + +li { + margin-bottom: 0.25rem; +} + +/** This is an auto-generated file, don't edit it. **/ +/* Thinking about moving all of these into a typography.scss file at some point */ +.btn { + width: -moz-fit-content; + width: fit-content; + height: 32px; +} +.btn-large { + height: 40px; +} +.btn-primary { + color: var(--text-primary-inverted); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: linear-gradient(var(--stateOverlays-enabled-inverted), var(--stateOverlays-enabled-inverted)), var(--fill-active); + border: 0 solid transparent; + box-shadow: var(--elevation-0); + transition: box-shadow 0.2s; +} +.btn-primary:enabled, .btn-primary.enabled { + border: 0 solid transparent; + box-shadow: var(--elevation-0); +} +.btn-primary:active, .btn-primary.active { + color: var(--text-primary-inverted); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + font-size: 14px; + text-decoration: underline; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: linear-gradient(var(--stateOverlays-active-inverted), var(--stateOverlays-active-inverted)), var(--fill-active); + border: 0 solid transparent; + box-shadow: var(--elevation-0); +} +.btn-primary:hover, .btn-primary.hover { + color: var(--text-primary-inverted); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + font-size: 14px; + text-decoration: underline; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: linear-gradient(var(--stateOverlays-hover-inverted), var(--stateOverlays-hover-inverted)), var(--fill-active); + border: 0 solid transparent; + box-shadow: var(--elevation-1); +} +.btn-primary:focus, .btn-primary.focus { + color: var(--text-primary-inverted); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + font-size: 14px; + text-decoration: underline; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: linear-gradient(var(--stateOverlays-enabled-inverted), var(--stateOverlays-enabled-inverted)), var(--fill-active); + border: 1px solid transparent; + box-shadow: var(--elevation-0); + outline: 2px solid var(--focus-color); +} +.btn-primary:disabled, .btn-primary.disabled { + color: var(--text-disabled-inverted); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: linear-gradient(var(--stateOverlays-disabled-inverted), var(--stateOverlays-disabled-inverted)), var(--fill-active); + border: 0 solid transparent; + box-shadow: var(--elevation-0); +} +.btn-primary:focus:not(:focus-visible, :disabled, .disabled) { + color: var(--text-primary-inverted); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + font-size: 14px; + text-decoration: underline; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: linear-gradient(var(--stateOverlays-enabled-inverted), var(--stateOverlays-enabled-inverted)), var(--fill-active); + border: 0 solid transparent; + box-shadow: var(--elevation-0); +} +.btn-secondary { + color: var(--text-primary); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: var(--stateOverlays-enabled); + border: 1px solid var(--border-enabled); + box-shadow: var(--elevation-0); + transition: box-shadow 0.2s; +} +.btn-secondary:enabled, .btn-secondary.enabled { + border: 1px solid var(--border-enabled); + box-shadow: var(--elevation-0); +} +.btn-secondary:active, .btn-secondary.active { + color: var(--text-primary); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + font-size: 14px; + text-decoration: underline; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: var(--stateOverlays-active); + border: 1px solid var(--border-selected); + box-shadow: var(--elevation-0); +} +.btn-secondary:hover, .btn-secondary.hover { + color: var(--text-primary); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + font-size: 14px; + text-decoration: underline; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: var(--stateOverlays-hover); + border: 1px solid var(--border-hover); + box-shadow: var(--elevation-1); +} +.btn-secondary:focus, .btn-secondary.focus { + color: var(--text-primary); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + font-size: 14px; + text-decoration: underline; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: var(--stateOverlays-enabled); + border: 1px solid var(--border-hover); + box-shadow: var(--elevation-0); + outline: 2px solid var(--focus-color); +} +.btn-secondary:disabled, .btn-secondary.disabled { + color: var(--text-disabled); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: var(--stateOverlays-enabled); + border: 1px solid var(--border-disabled); + box-shadow: var(--elevation-0); +} +.btn-secondary:focus:not(:focus-visible, :disabled, .disabled) { + color: var(--text-primary); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + font-size: 14px; + text-decoration: underline; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: var(--stateOverlays-enabled); + border: 1px solid var(--border-enabled); + box-shadow: var(--elevation-0); +} +.btn-tertiary, .btn-link { + color: var(--text-primary); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: var(--stateOverlays-enabled); + border: 0 solid transparent; + box-shadow: None; + transition: box-shadow 0.2s; +} +.btn-tertiary:enabled, .btn-tertiary.enabled, .btn-link:enabled, .btn-link.enabled { + border: 0 solid transparent; + box-shadow: None; +} +.btn-tertiary:active, .btn-tertiary.active, .btn-link:active, .btn-link.active { + color: var(--text-primary); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + font-size: 14px; + text-decoration: underline; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: var(--stateOverlays-active); + border: 0 solid transparent; + box-shadow: None; +} +.btn-tertiary:hover, .btn-tertiary.hover, .btn-link:hover, .btn-link.hover { + color: var(--text-primary); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + font-size: 14px; + text-decoration: underline; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: var(--stateOverlays-hover); + border: 0 solid transparent; + box-shadow: var(--elevation-1); +} +.btn-tertiary:focus, .btn-tertiary.focus, .btn-link:focus, .btn-link.focus { + color: var(--text-primary); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + font-size: 14px; + text-decoration: underline; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: var(--stateOverlays-enabled); + border: 1px solid transparent; + box-shadow: None; + outline: 2px solid var(--focus-color); +} +.btn-tertiary:disabled, .btn-tertiary.disabled, .btn-link:disabled, .btn-link.disabled { + color: var(--text-disabled); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: var(--stateOverlays-enabled); + border: 0 solid transparent; + box-shadow: None; +} +.btn-tertiary:focus:not(:focus-visible, :disabled, .disabled), .btn-link:focus:not(:focus-visible, :disabled, .disabled) { + color: var(--text-primary); + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + font-size: 14px; + text-decoration: underline; + font-family: Inter; + font-weight: 600; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.056px; + line-height: 16px; + background: var(--stateOverlays-enabled); + border: 0 solid transparent; + box-shadow: None; +} +.btn-tertiary, .btn-tertiary:disabled, .btn-tertiary.disabled, .btn-link, .btn-link:disabled, .btn-link.disabled { + text-decoration: underline; +} + +/** This is an auto-generated file, don't edit it. **/ +.accordion { + width: 100%; +} +.accordion-body { + display: flex; + flex-direction: column; + gap: 0.5rem; +} +.accordion-item { + color: var(--text-primary); + border-bottom: 1px solid var(--border-subtleAlpha01); +} +.accordion-item .nav-link { + padding: 0.5rem; + color: var(--text-secondary); +} +.accordion-item .nav-link:last-child { + margin-bottom: 0.75rem; +} +.accordion-item .nav-link:active, .accordion-item .nav-link.active { + background: var(--stateOverlays-active); + color: var(--text-primary); +} +.accordion-item .nav-link:hover, .accordion-item .nav-link.hover { + background: var(--stateOverlays-selectedHover); + color: var(--text-primary); +} +.accordion-item .nav-link:disabled, .accordion-item .nav-link.disabled { + color: var(--text-disabled); +} +.accordion-item .nav-link:focus:not(:focus-visible, :disabled, .disabled) { + background: var(--stateOverlays-active); + color: var(--text-primary); +} +.accordion-button { + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.112px; + line-height: 16px; + color: var(--text-secondary); + text-transform: uppercase; + border: none; + box-shadow: none; + /** CSS for the close/open icon **/ +} +.accordion-button::after { + filter: contrast(0); + opacity: 0.4; +} +.accordion-button:not(.collapsed) { + color: var(--text-primary); + background-color: transparent; + box-shadow: none; +} +.accordion-button:not(.collapsed)::after { + filter: contrast(0); + opacity: 1; +} +.accordion-button:hover, .accordion-button.hover { + color: var(--text-primary); +} +.accordion-button:hover::after, .accordion-button.hover::after { + opacity: 1; +} +.accordion-button:focus, .accordion-button.focus { + border: none; + box-shadow: none; +} + +/** This is an auto-generated file, don't edit it. **/ +.card { + background: var(--surfaces-bg-card); + border: none; + box-shadow: var(--elevation-1); + width: 100%; + height: 100%; + overflow: auto; + padding: 1rem; +} +.card-nav:hover, .card-nav.hover { + background: var(--field-enabled); + box-shadow: var(--elevation-2); + transform: translate3d(0, -0.5rem, 0); + will-change: transform; +} +.card-title, .card-header { + color: var(--text-primary); +} +.card-subtitle { + color: var(--text-secondary); +} +.card-text { + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.112px; + line-height: 16px; + color: var(--text-secondary); +} +.card-text:last-child { + margin-bottom: 0.5rem; +} + +/** This is an auto-generated file, don't edit it. **/ +.navbar { + background: var(--surfaces-bg02) !important; +} +.navbar .nav-link { + padding: 0; + align-items: center; + display: flex; + height: 4rem; + justify-content: center; + color: var(--text-secondary); +} +.navbar .nav-link:active, .navbar .nav-link.active { + color: var(--text-primary); +} + +/** This is an auto-generated file, don't edit it. **/ +/** This is an auto-generated file, don't edit it. **/ +/** This is an auto-generated file, don't edit it. **/ +/** space between radio icon/checkbox and the label is 8px **/ +.form-check { + transition: all 150ms ease 0s; + margin-bottom: 12px; + line-height: 16px; + min-height: auto; + padding-left: 28px; +} +.form-check-inline { + margin-right: 12px; +} +.form-check-input { + margin: 0; + position: relative; + border-color: var(--border-enabled); + border-radius: 0; + color: var(--fill-active); + width: 16px; + height: 16px; + outline: none; + background-color: transparent; + background-size: auto; +} +.form-check-input:checked { + background-color: transparent; + border-color: var(--border-enabled); + color: var(--fill-active); +} +.form-check-input[type=radio] { + background-image: none; +} +.form-check-input[type=radio]:checked::after { + position: absolute; + content: ""; + background-color: var(--fill-active); + border-radius: 50%; + width: 8px; + height: 8px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} +.form-check-input:hover, .form-check-input.hover { + border-color: var(--border-hover); +} +.form-check-input:active, .form-check-input.active { + border-color: var(--border-selected); +} +.form-check-input:disabled, .form-check-input.disabled { + border-color: var(--border-disabled); +} +.form-check-input:focus, .form-check-input.focus { + border-color: var(--border-selected); + box-shadow: none; +} +.form-check-input:hover { + border-color: var(--border-hover); +} +.form-check .form-check-input { + margin-left: -24px; +} +.form-check-label { + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.112px; + line-height: 16px; + color: var(--text-secondary); +} +.form-check-lg { + line-height: 20px; + min-height: auto; + padding-left: 32px; +} +.form-check-lg .form-check-input { + width: 20px; + height: 20px; + border-width: 2px; + margin-left: -28px; +} +.form-check-lg .form-check-label { + line-height: 20px; +} +.form-check.disabled .form-check-label { + color: var(--text-disabled); +} + +.vizro_light .form-check-input:checked[type=checkbox], [data-bs-theme=light] .form-check-input:checked[type=checkbox] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3Csvg width='8' height='7' viewBox='0 0 8 7' xmlns='http://www.w3.org/2000/svg' data-testid='checkmark-icon'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M7.5 1.25L6.25 0L2.5 3.75L1.25 2.5L0 3.75L2.5 6.25L7.5 1.25Z' fill='rgba(20, 23, 33, 0.8784313725)'/%3E%3C/svg%3E"); +} +.vizro_light .form-check-lg .form-check-input:checked[type=checkbox], [data-bs-theme=light] .form-check-lg .form-check-input:checked[type=checkbox] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3Csvg width='11' height='9' viewBox='0 0 11 9' xmlns='http://www.w3.org/2000/svg' data-testid='checkmark-icon'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.5 1.25L9.25 0L3.5 5.75L1.25 3.5L0 4.75L3.5 8.25L10.5 1.25Z' fill='rgba(20, 23, 33, 0.8784313725)' fill-opacity='0.88'/%3E%3C/svg%3E"); +} + +.vizro_dark .form-check-input:checked[type=checkbox], [data-bs-theme=dark] .form-check-input:checked[type=checkbox] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3Csvg width='8' height='7' viewBox='0 0 8 7' xmlns='http://www.w3.org/2000/svg' data-testid='checkmark-icon'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M7.5 1.25L6.25 0L2.5 3.75L1.25 2.5L0 3.75L2.5 6.25L7.5 1.25Z' fill='rgba(255, 255, 255, 0.8784313725)'/%3E%3C/svg%3E"); +} +.vizro_dark .form-check-lg .form-check-input:checked[type=checkbox], [data-bs-theme=dark] .form-check-lg .form-check-input:checked[type=checkbox] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3Csvg width='11' height='9' viewBox='0 0 11 9' xmlns='http://www.w3.org/2000/svg' data-testid='checkmark-icon'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.5 1.25L9.25 0L3.5 5.75L1.25 3.5L0 4.75L3.5 8.25L10.5 1.25Z' fill='rgba(255, 255, 255, 0.8784313725)' fill-opacity='0.88'/%3E%3C/svg%3E"); +} + +/*** +Since the above 2 css deinifitions have athe same specificity +the last one will be applied. So we need to add the below +css definitions to make sure the correct css is applied +if the parent has a dark mode but the child is rendering a light mode +**/ +.vizro_light .vizro_dark .form-check-input:checked[type=checkbox], .vizro_light [data-bs-theme=dark] .form-check-input:checked[type=checkbox], [data-bs-theme=light] .vizro_dark .form-check-input:checked[type=checkbox], [data-bs-theme=light] [data-bs-theme=dark] .form-check-input:checked[type=checkbox] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3Csvg width='8' height='7' viewBox='0 0 8 7' xmlns='http://www.w3.org/2000/svg' data-testid='checkmark-icon'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M7.5 1.25L6.25 0L2.5 3.75L1.25 2.5L0 3.75L2.5 6.25L7.5 1.25Z' fill='rgba(255, 255, 255, 0.8784313725)'/%3E%3C/svg%3E"); +} +.vizro_light .vizro_dark .form-check-lg .form-check-input:checked[type=checkbox], .vizro_light [data-bs-theme=dark] .form-check-lg .form-check-input:checked[type=checkbox], [data-bs-theme=light] .vizro_dark .form-check-lg .form-check-input:checked[type=checkbox], [data-bs-theme=light] [data-bs-theme=dark] .form-check-lg .form-check-input:checked[type=checkbox] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3Csvg width='11' height='9' viewBox='0 0 11 9' xmlns='http://www.w3.org/2000/svg' data-testid='checkmark-icon'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.5 1.25L9.25 0L3.5 5.75L1.25 3.5L0 4.75L3.5 8.25L10.5 1.25Z' fill='rgba(255, 255, 255, 0.8784313725)' fill-opacity='0.88'/%3E%3C/svg%3E"); +} + +.vizro_dark .vizro_light .form-check-input:checked[type=checkbox], .vizro_dark [data-bs-theme=light] .form-check-input:checked[type=checkbox], [data-bs-theme=dark] .vizro_light .form-check-input:checked[type=checkbox], [data-bs-theme=dark] [data-bs-theme=light] .form-check-input:checked[type=checkbox] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3Csvg width='8' height='7' viewBox='0 0 8 7' xmlns='http://www.w3.org/2000/svg' data-testid='checkmark-icon'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M7.5 1.25L6.25 0L2.5 3.75L1.25 2.5L0 3.75L2.5 6.25L7.5 1.25Z' fill='rgba(20, 23, 33, 0.8784313725)'/%3E%3C/svg%3E"); +} +.vizro_dark .vizro_light .form-check-lg .form-check-input:checked[type=checkbox], .vizro_dark [data-bs-theme=light] .form-check-lg .form-check-input:checked[type=checkbox], [data-bs-theme=dark] .vizro_light .form-check-lg .form-check-input:checked[type=checkbox], [data-bs-theme=dark] [data-bs-theme=light] .form-check-lg .form-check-input:checked[type=checkbox] { + --bs-form-check-bg-image: url("data:image/svg+xml,%3Csvg width='11' height='9' viewBox='0 0 11 9' xmlns='http://www.w3.org/2000/svg' data-testid='checkmark-icon'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M10.5 1.25L9.25 0L3.5 5.75L1.25 3.5L0 4.75L3.5 8.25L10.5 1.25Z' fill='rgba(20, 23, 33, 0.8784313725)' fill-opacity='0.88'/%3E%3C/svg%3E"); +} + +/** This is an auto-generated file, don't edit it. **/ +/** This is an auto-generated file, don't edit it. **/ +/** This is an auto-generated file, don't edit it. **/ +.form-control, +.input-group-text { + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.112px; + line-height: 16px; + background-color: var(--field-enabled); + color: var(--text-primary); + padding: 0.5rem; + box-shadow: var(--elevation-0); + border: none; +} +.form-control::-moz-placeholder, .input-group-text::-moz-placeholder { + color: var(--text-placeholder); +} +.form-control::placeholder, +.input-group-text::placeholder { + color: var(--text-placeholder); +} +.form-control:focus, .form-control.focus, +.input-group-text:focus, +.input-group-text.focus { + background-color: var(--field-hover); + color: var(--text-primary); + box-shadow: 0 0 0 2px var(--focus) inset; +} +.form-control:hover, .form-control.hover, +.input-group-text:hover, +.input-group-text.hover { + background-color: var(--field-hover); +} +.form-control:disabled, .form-control.disabled, +.input-group-text:disabled, +.input-group-text.disabled { + background-color: var(--field-disabled); + color: var(--text-disabled); +} +.form-control:focus-visible, .form-control.focus-visible, +.input-group-text:focus-visible, +.input-group-text.focus-visible { + outline: none; +} + +.form-control.is-invalid { + box-shadow: 0 0 0 2px var(--status-error) inset; + border: none; +} +.form-control.is-invalid:focus, .form-control.is-invalid.focus { + box-shadow: 0 0 0 2px var(--status-error) inset; +} +.form-control.is-valid { + box-shadow: 0 0 0 2px var(--status-success) inset; +} +.form-control.is-valid:focus, .form-control.is-valid.focus { + box-shadow: 0 0 0 2px var(--status-success) inset; +} + +.vizro_light .form-control.is-valid, [data-bs-theme=light] .form-control.is-valid { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%2326bf56' d='M421-311.463 690.537-581l-34.845-34.23L421-380.153 302.539-498.615l-33.846 34.23L421-311.463Zm59.067 211.462q-78.221 0-147.397-29.92-69.176-29.92-120.989-81.71-51.814-51.791-81.747-120.936-29.933-69.146-29.933-147.366 0-78.836 29.92-148.204 29.92-69.369 81.71-120.682 51.791-51.314 120.936-81.247 69.146-29.933 147.366-29.933 78.836 0 148.204 29.92 69.369 29.92 120.682 81.21 51.314 51.291 81.247 120.629 29.933 69.337 29.933 148.173 0 78.221-29.92 147.397-29.92 69.176-81.21 120.989-51.291 51.814-120.629 81.747-69.337 29.933-148.173 29.933ZM480-145.385q139.692 0 237.154-97.769Q814.615-340.923 814.615-480q0-139.692-97.461-237.154Q619.692-814.615 480-814.615q-139.077 0-236.846 97.461Q145.385-619.692 145.385-480q0 139.077 97.769 236.846T480-145.385ZM480-480Z'%3E%3C/path%3E%3C/svg%3E"), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%2326bf56' d='m421-298 283-283-46-45-237 237-120-120-45 45 165 166Zm59 218q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z'%3E%3C/path%3E%3C/svg%3E"); +} +.vizro_light .form-control.is-invalid, [data-bs-theme=light] .form-control.is-invalid { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%23f03b3a' d='M331.539-299.539 480-448.001l148.461 148.462 32-32L511.999-480l148.462-148.461-32-32L480-511.999 331.539-660.461l-32 32L448.001-480 299.539-331.539l32 32Zm148.528 199.538q-78.221 0-147.397-29.92-69.176-29.92-120.989-81.71-51.814-51.791-81.747-120.936-29.933-69.146-29.933-147.366 0-78.836 29.92-148.204 29.92-69.369 81.71-120.682 51.791-51.314 120.936-81.247 69.146-29.933 147.366-29.933 78.836 0 148.204 29.92 69.369 29.92 120.682 81.21 51.314 51.291 81.247 120.629 29.933 69.337 29.933 148.173 0 78.221-29.92 147.397-29.92 69.176-81.21 120.989-51.291 51.814-120.629 81.747-69.337 29.933-148.173 29.933ZM480-145.385q139.692 0 237.154-97.769Q814.615-340.923 814.615-480q0-139.692-97.461-237.154Q619.692-814.615 480-814.615q-139.077 0-236.846 97.461Q145.385-619.692 145.385-480q0 139.077 97.769 236.846T480-145.385ZM480-480Z'%3E%3C/path%3E%3C/svg%3E"), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%23f03b3a' d='m330-288 150-150 150 150 42-42-150-150 150-150-42-42-150 150-150-150-42 42 150 150-150 150 42 42ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z'%3E%3C/path%3E%3C/svg%3E"); +} + +.vizro_dark .form-control.is-valid, [data-bs-theme=dark] .form-control.is-valid { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%2340d86e' d='M421-311.463 690.537-581l-34.845-34.23L421-380.153 302.539-498.615l-33.846 34.23L421-311.463Zm59.067 211.462q-78.221 0-147.397-29.92-69.176-29.92-120.989-81.71-51.814-51.791-81.747-120.936-29.933-69.146-29.933-147.366 0-78.836 29.92-148.204 29.92-69.369 81.71-120.682 51.791-51.314 120.936-81.247 69.146-29.933 147.366-29.933 78.836 0 148.204 29.92 69.369 29.92 120.682 81.21 51.314 51.291 81.247 120.629 29.933 69.337 29.933 148.173 0 78.221-29.92 147.397-29.92 69.176-81.21 120.989-51.291 51.814-120.629 81.747-69.337 29.933-148.173 29.933ZM480-145.385q139.692 0 237.154-97.769Q814.615-340.923 814.615-480q0-139.692-97.461-237.154Q619.692-814.615 480-814.615q-139.077 0-236.846 97.461Q145.385-619.692 145.385-480q0 139.077 97.769 236.846T480-145.385ZM480-480Z'%3E%3C/path%3E%3C/svg%3E"), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%2340d86e' d='m421-298 283-283-46-45-237 237-120-120-45 45 165 166Zm59 218q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z'%3E%3C/path%3E%3C/svg%3E"); +} +.vizro_dark .form-control.is-invalid, [data-bs-theme=dark] .form-control.is-invalid { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%23f56565' d='M331.539-299.539 480-448.001l148.461 148.462 32-32L511.999-480l148.462-148.461-32-32L480-511.999 331.539-660.461l-32 32L448.001-480 299.539-331.539l32 32Zm148.528 199.538q-78.221 0-147.397-29.92-69.176-29.92-120.989-81.71-51.814-51.791-81.747-120.936-29.933-69.146-29.933-147.366 0-78.836 29.92-148.204 29.92-69.369 81.71-120.682 51.791-51.314 120.936-81.247 69.146-29.933 147.366-29.933 78.836 0 148.204 29.92 69.369 29.92 120.682 81.21 51.314 51.291 81.247 120.629 29.933 69.337 29.933 148.173 0 78.221-29.92 147.397-29.92 69.176-81.21 120.989-51.291 51.814-120.629 81.747-69.337 29.933-148.173 29.933ZM480-145.385q139.692 0 237.154-97.769Q814.615-340.923 814.615-480q0-139.692-97.461-237.154Q619.692-814.615 480-814.615q-139.077 0-236.846 97.461Q145.385-619.692 145.385-480q0 139.077 97.769 236.846T480-145.385ZM480-480Z'%3E%3C/path%3E%3C/svg%3E"), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%23f56565' d='m330-288 150-150 150 150 42-42-150-150 150-150-42-42-150 150-150-150-42 42 150 150-150 150 42 42ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z'%3E%3C/path%3E%3C/svg%3E"); +} + +.vizro_light .vizro_dark .form-control.is-valid, .vizro_light [data-bs-theme=dark] .form-control.is-valid, [data-bs-theme=light] .vizro_dark .form-control.is-valid, [data-bs-theme=light] [data-bs-theme=dark] .form-control.is-valid { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%2340d86e' d='M421-311.463 690.537-581l-34.845-34.23L421-380.153 302.539-498.615l-33.846 34.23L421-311.463Zm59.067 211.462q-78.221 0-147.397-29.92-69.176-29.92-120.989-81.71-51.814-51.791-81.747-120.936-29.933-69.146-29.933-147.366 0-78.836 29.92-148.204 29.92-69.369 81.71-120.682 51.791-51.314 120.936-81.247 69.146-29.933 147.366-29.933 78.836 0 148.204 29.92 69.369 29.92 120.682 81.21 51.314 51.291 81.247 120.629 29.933 69.337 29.933 148.173 0 78.221-29.92 147.397-29.92 69.176-81.21 120.989-51.291 51.814-120.629 81.747-69.337 29.933-148.173 29.933ZM480-145.385q139.692 0 237.154-97.769Q814.615-340.923 814.615-480q0-139.692-97.461-237.154Q619.692-814.615 480-814.615q-139.077 0-236.846 97.461Q145.385-619.692 145.385-480q0 139.077 97.769 236.846T480-145.385ZM480-480Z'%3E%3C/path%3E%3C/svg%3E"), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%2340d86e' d='m421-298 283-283-46-45-237 237-120-120-45 45 165 166Zm59 218q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z'%3E%3C/path%3E%3C/svg%3E"); +} +.vizro_light .vizro_dark .form-control.is-invalid, .vizro_light [data-bs-theme=dark] .form-control.is-invalid, [data-bs-theme=light] .vizro_dark .form-control.is-invalid, [data-bs-theme=light] [data-bs-theme=dark] .form-control.is-invalid { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%23f56565' d='M331.539-299.539 480-448.001l148.461 148.462 32-32L511.999-480l148.462-148.461-32-32L480-511.999 331.539-660.461l-32 32L448.001-480 299.539-331.539l32 32Zm148.528 199.538q-78.221 0-147.397-29.92-69.176-29.92-120.989-81.71-51.814-51.791-81.747-120.936-29.933-69.146-29.933-147.366 0-78.836 29.92-148.204 29.92-69.369 81.71-120.682 51.791-51.314 120.936-81.247 69.146-29.933 147.366-29.933 78.836 0 148.204 29.92 69.369 29.92 120.682 81.21 51.314 51.291 81.247 120.629 29.933 69.337 29.933 148.173 0 78.221-29.92 147.397-29.92 69.176-81.21 120.989-51.291 51.814-120.629 81.747-69.337 29.933-148.173 29.933ZM480-145.385q139.692 0 237.154-97.769Q814.615-340.923 814.615-480q0-139.692-97.461-237.154Q619.692-814.615 480-814.615q-139.077 0-236.846 97.461Q145.385-619.692 145.385-480q0 139.077 97.769 236.846T480-145.385ZM480-480Z'%3E%3C/path%3E%3C/svg%3E"), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%23f56565' d='m330-288 150-150 150 150 42-42-150-150 150-150-42-42-150 150-150-150-42 42 150 150-150 150 42 42ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z'%3E%3C/path%3E%3C/svg%3E"); +} + +.vizro_dark .vizro_light .form-control.is-valid, .vizro_dark [data-bs-theme=light] .form-control.is-valid, [data-bs-theme=dark] .vizro_light .form-control.is-valid, [data-bs-theme=dark] [data-bs-theme=light] .form-control.is-valid { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%2326bf56' d='M421-311.463 690.537-581l-34.845-34.23L421-380.153 302.539-498.615l-33.846 34.23L421-311.463Zm59.067 211.462q-78.221 0-147.397-29.92-69.176-29.92-120.989-81.71-51.814-51.791-81.747-120.936-29.933-69.146-29.933-147.366 0-78.836 29.92-148.204 29.92-69.369 81.71-120.682 51.791-51.314 120.936-81.247 69.146-29.933 147.366-29.933 78.836 0 148.204 29.92 69.369 29.92 120.682 81.21 51.314 51.291 81.247 120.629 29.933 69.337 29.933 148.173 0 78.221-29.92 147.397-29.92 69.176-81.21 120.989-51.291 51.814-120.629 81.747-69.337 29.933-148.173 29.933ZM480-145.385q139.692 0 237.154-97.769Q814.615-340.923 814.615-480q0-139.692-97.461-237.154Q619.692-814.615 480-814.615q-139.077 0-236.846 97.461Q145.385-619.692 145.385-480q0 139.077 97.769 236.846T480-145.385ZM480-480Z'%3E%3C/path%3E%3C/svg%3E"), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%2326bf56' d='m421-298 283-283-46-45-237 237-120-120-45 45 165 166Zm59 218q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z'%3E%3C/path%3E%3C/svg%3E"); +} +.vizro_dark .vizro_light .form-control.is-invalid, .vizro_dark [data-bs-theme=light] .form-control.is-invalid, [data-bs-theme=dark] .vizro_light .form-control.is-invalid, [data-bs-theme=dark] [data-bs-theme=light] .form-control.is-invalid { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%23f03b3a' d='M331.539-299.539 480-448.001l148.461 148.462 32-32L511.999-480l148.462-148.461-32-32L480-511.999 331.539-660.461l-32 32L448.001-480 299.539-331.539l32 32Zm148.528 199.538q-78.221 0-147.397-29.92-69.176-29.92-120.989-81.71-51.814-51.791-81.747-120.936-29.933-69.146-29.933-147.366 0-78.836 29.92-148.204 29.92-69.369 81.71-120.682 51.791-51.314 120.936-81.247 69.146-29.933 147.366-29.933 78.836 0 148.204 29.92 69.369 29.92 120.682 81.21 51.314 51.291 81.247 120.629 29.933 69.337 29.933 148.173 0 78.221-29.92 147.397-29.92 69.176-81.21 120.989-51.291 51.814-120.629 81.747-69.337 29.933-148.173 29.933ZM480-145.385q139.692 0 237.154-97.769Q814.615-340.923 814.615-480q0-139.692-97.461-237.154Q619.692-814.615 480-814.615q-139.077 0-236.846 97.461Q145.385-619.692 145.385-480q0 139.077 97.769 236.846T480-145.385ZM480-480Z'%3E%3C/path%3E%3C/svg%3E"), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='48' height='48' viewBox='0 -960 960 960'%3E%3Cpath fill='%23f03b3a' d='m330-288 150-150 150 150 42-42-150-150 150-150-42-42-150 150-150-150-42 42 150 150-150 150 42 42ZM480-80q-82 0-155-31.5t-127.5-86Q143-252 111.5-325T80-480q0-83 31.5-156t86-127Q252-817 325-848.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 82-31.5 155T763-197.5q-54 54.5-127 86T480-80Zm0-60q142 0 241-99.5T820-480q0-142-99-241t-241-99q-141 0-240.5 99T140-480q0 141 99.5 240.5T480-140Zm0-340Z'%3E%3C/path%3E%3C/svg%3E"); +} + +/** This is an auto-generated file, don't edit it. **/ +.form-label { + font-size: 14px; + text-decoration: none; + font-family: Inter; + font-weight: 400; + font-style: normal; + font-stretch: normal; + letter-spacing: -0.112px; + line-height: 16px; + color: var(--text-secondary); + margin-bottom: 12px; + width: 100%; +} + +.qb-container-bg-01 { + background-color: var(--surfaces-bg01); + padding: 40px; + width: 100%; +} + +.qb-container-bg-02 { + background-color: var(--surfaces-bg02); + padding: 40px; + width: 100%; +} + +.qb-container-bg-03 { + background-color: var(--surfaces-bg03); + padding: 40px; + width: 100%; +}/*# sourceMappingURL=vizro-bootstrap.min.css.map */ diff --git a/vizro-core/src/vizro/static/css/vizro-bootstrap.min.css.map b/vizro-core/src/vizro/static/css/vizro-bootstrap.min.css.map new file mode 100644 index 000000000..5037726f3 --- /dev/null +++ b/vizro-core/src/vizro/static/css/vizro-bootstrap.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["vizro-bootstrap.min.css","../sass/variables/_darkTokens.scss","../sass/variables/_lightTokens.scss","../sass/variables/_qb_variables.scss","../sass/variables/_bootstrap_variables.scss","../node_modules/bootstrap/scss/mixins/_banner.scss","../node_modules/bootstrap/scss/_root.scss","../node_modules/bootstrap/scss/vendor/_rfs.scss","../node_modules/bootstrap/scss/mixins/_color-mode.scss","../node_modules/bootstrap/scss/_reboot.scss","../node_modules/bootstrap/scss/_variables.scss","../node_modules/bootstrap/scss/_type.scss","../node_modules/bootstrap/scss/mixins/_lists.scss","../node_modules/bootstrap/scss/_images.scss","../node_modules/bootstrap/scss/mixins/_image.scss","../node_modules/bootstrap/scss/_containers.scss","../node_modules/bootstrap/scss/mixins/_container.scss","../node_modules/bootstrap/scss/mixins/_breakpoints.scss","../node_modules/bootstrap/scss/_grid.scss","../node_modules/bootstrap/scss/mixins/_grid.scss","../node_modules/bootstrap/scss/_tables.scss","../node_modules/bootstrap/scss/mixins/_table-variants.scss","../node_modules/bootstrap/scss/forms/_labels.scss","../node_modules/bootstrap/scss/forms/_form-text.scss","../node_modules/bootstrap/scss/forms/_form-control.scss","../node_modules/bootstrap/scss/mixins/_border-radius.scss","../node_modules/bootstrap/scss/mixins/_gradients.scss","../node_modules/bootstrap/scss/forms/_form-select.scss","../node_modules/bootstrap/scss/forms/_form-check.scss","../node_modules/bootstrap/scss/forms/_form-range.scss","../node_modules/bootstrap/scss/forms/_floating-labels.scss","../node_modules/bootstrap/scss/forms/_input-group.scss","../node_modules/bootstrap/scss/mixins/_forms.scss","../node_modules/bootstrap/scss/_buttons.scss","../node_modules/bootstrap/scss/mixins/_buttons.scss","../node_modules/bootstrap/scss/_transitions.scss","../node_modules/bootstrap/scss/_dropdown.scss","../node_modules/bootstrap/scss/mixins/_caret.scss","../node_modules/bootstrap/scss/_button-group.scss","../node_modules/bootstrap/scss/_nav.scss","../node_modules/bootstrap/scss/_navbar.scss","../node_modules/bootstrap/scss/_card.scss","../node_modules/bootstrap/scss/_accordion.scss","../node_modules/bootstrap/scss/_breadcrumb.scss","../node_modules/bootstrap/scss/_pagination.scss","../node_modules/bootstrap/scss/mixins/_pagination.scss","../node_modules/bootstrap/scss/_badge.scss","../node_modules/bootstrap/scss/_alert.scss","../node_modules/bootstrap/scss/_progress.scss","../node_modules/bootstrap/scss/_list-group.scss","../node_modules/bootstrap/scss/_close.scss","../node_modules/bootstrap/scss/_toasts.scss","../node_modules/bootstrap/scss/_modal.scss","../node_modules/bootstrap/scss/mixins/_backdrop.scss","../node_modules/bootstrap/scss/_tooltip.scss","../node_modules/bootstrap/scss/mixins/_reset-text.scss","../node_modules/bootstrap/scss/_popover.scss","../node_modules/bootstrap/scss/_carousel.scss","../node_modules/bootstrap/scss/mixins/_clearfix.scss","../node_modules/bootstrap/scss/_spinners.scss","../node_modules/bootstrap/scss/_offcanvas.scss","../node_modules/bootstrap/scss/_placeholders.scss","../node_modules/bootstrap/scss/helpers/_color-bg.scss","../node_modules/bootstrap/scss/helpers/_colored-links.scss","../node_modules/bootstrap/scss/helpers/_focus-ring.scss","../node_modules/bootstrap/scss/helpers/_icon-link.scss","../node_modules/bootstrap/scss/helpers/_ratio.scss","../node_modules/bootstrap/scss/helpers/_position.scss","../node_modules/bootstrap/scss/helpers/_stacks.scss","../node_modules/bootstrap/scss/helpers/_visually-hidden.scss","../node_modules/bootstrap/scss/mixins/_visually-hidden.scss","../node_modules/bootstrap/scss/helpers/_stretched-link.scss","../node_modules/bootstrap/scss/helpers/_text-truncation.scss","../node_modules/bootstrap/scss/mixins/_text-truncate.scss","../node_modules/bootstrap/scss/helpers/_vr.scss","../node_modules/bootstrap/scss/mixins/_utilities.scss","../node_modules/bootstrap/scss/utilities/_api.scss","../sass/variables/_bootstrap_theme_override.scss","../sass/vizro-bootstrap.scss","../sass/variables/_fontTokens.scss","../sass/components/_typography.scss","../sass/mixins/_buttons.scss","../sass/components/_buttons.scss","../sass/mixins/_util.scss","../sass/components/_accordion.scss","../sass/components/_card.scss","../sass/components/_vertical-nav-bar.scss","../sass/components/_form.scss","../sass/components/_form-control.scss","../sass/components/_labels.scss"],"names":[],"mappings":"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E,wBAAA;ATyhBF;;ASjhBA;;;;EAIE,0BAAA;ATohBF;ASjhBI;;;;EACE,eAAA;ATshBN;;AS/gBA;EACE,UAAA;EACA,kBAAA;ATkhBF;;AS7gBA;EACE,gBAAA;ATghBF;;AStgBA;EACE,YAAA;EACA,UAAA;EACA,SAAA;EACA,SAAA;ATygBF;;ASjgBA;EACE,WAAA;EACA,WAAA;EACA,UAAA;EACA,qBCmN4B;EHpatB,iCAAA;EEoNN,oBAAA;ATmgBF;AOn3BI;EEyWJ;IFtMQ,iBAAA;EPotBN;AACF;AStgBE;EACE,WAAA;ATwgBJ;;ASjgBA;;;;;;;EAOE,UAAA;ATogBF;;ASjgBA;EACE,YAAA;ATogBF;;AS3fA;EACE,6BAAA;EACA,oBAAA;AT8fF;;AStfA;;;;;;;CAAA;AAWA;EACE,wBAAA;ATsfF;;ASjfA;EACE,UAAA;ATofF;;AS7eA;EACE,aAAA;EACA,0BAAA;ATgfF;;AS3eA;EACE,qBAAA;AT8eF;;ASzeA;EACE,SAAA;AT4eF;;ASreA;EACE,kBAAA;EACA,eAAA;ATweF;;ASheA;EACE,wBAAA;ATmeF;;AS3dA;EACE,wBAAA;AT8dF;;AWniCA;EJmQM,qBALI;EI5PR,gBDwoB4B;AV8Z9B;;AWjiCE;EJgQM,iCAAA;EI5PJ,gBDynBkB;ECxnBlB,gBDwmB0B;AV0b9B;AOn8BI;EIpGF;IJuQM,eAAA;EPoyBN;AACF;;AW5iCE;EJgQM,iCAAA;EI5PJ,gBDynBkB;ECxnBlB,gBDwmB0B;AVqc9B;AO98BI;EIpGF;IJuQM,iBAAA;EP+yBN;AACF;;AWvjCE;EJgQM,iCAAA;EI5PJ,gBDynBkB;ECxnBlB,gBDwmB0B;AVgd9B;AOz9BI;EIpGF;IJuQM,eAAA;EP0zBN;AACF;;AWlkCE;EJgQM,iCAAA;EI5PJ,gBDynBkB;ECxnBlB,gBDwmB0B;AV2d9B;AOp+BI;EIpGF;IJuQM,iBAAA;EPq0BN;AACF;;AW7kCE;EJgQM,iCAAA;EI5PJ,gBDynBkB;ECxnBlB,gBDwmB0B;AVse9B;AO/+BI;EIpGF;IJuQM,eAAA;EPg1BN;AACF;;AWxlCE;EJgQM,iCAAA;EI5PJ,gBDynB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ALI;APu2EV;AwBv+EE;EACE,uBAAA;EACA,wBAAA;EACA,yBdooB0B;AVq2D9B;;AwBr+EA;EACE,iEdgwBsC;Ec/vBtC,oBAAA;EjB4HI,mBALI;APk3EV;AwBr+EE;EACE,oBAAA;EACA,qBAAA;EACA,uBd2nB0B;AV42D9B;;AwB/9EE;EACE,mEd6uBoC;AVqvDxC;AwB/9EE;EACE,mEd0uBoC;AVuvDxC;AwB99EE;EACE,iEduuBoC;AVyvDxC;;AwB39EA;EACE,WdquBsC;EcpuBtC,+Dd8tBsC;Ec7tBtC,gBpBnKoB;AJioFtB;AwB59EE;EACE,eAAA;AxB89EJ;AwB39EE;EACE,oBAAA;AxB69EJ;AwBz9EE;EACE,oBAAA;AxB29EJ;AwBv9EE;EAAoB,+Dd8sBkB;AV4wDxC;AwBz9EE;EAAoB,6Dd8sBkB;AV8wDxC;;A2B3qFA;EACE,wPAAA;EAEA,cAAA;EACA,WAAA;EACA,sCAAA;EpBqRI,mBALI;EoB7QR,gBjB+lB4B;EiB9lB5B,gBvBoBsB;EuBnBtB,2BjBy3BsC;EiBx3BtC,wBAAA;KAAA,qBAAA;UAAA,gBAAA;EACA,mCjBk3BsC;EiBj3BtC,mFAAA;EACA,4BAAA;EACA,wCjB+9BkC;EiB99BlC,0BjB+9BkC;EiB99BlC,2DAAA;EFAE,gBECiD;A3B4qFrD;A2BxqFE;EACE,qBjBs3BoC;EiBr3BpC,UAAA;EAKE,iDjBi+B4B;AVqsDlC;A2BlqFE;EAEE,qBvBPkB;EuBQlB,sBAAA;A3BmqFJ;A2BhqFE;EAEE,wCjBu1BoC;AV00DxC;A2B5pFE;EACE,kBAAA;EACA,uCAAA;A3B8pFJ;;A2B1pFA;EACE,oBjBsuB4B;EiBruB5B,uBjBquB4B;EiBpuB5B,oBjBquB4B;EHlgBxB,qBALI;APg8EV;;A2BzpFA;EACE,mBjBkuB4B;EiBjuB5B,sBjBiuB4B;EiBhuB5B,kBjBiuB4B;EHtgBxB,mBALI;APu8EV;;A2BtpFI;EACE,wPAAA;A3BypFN;;A4BjuFA;EACE,cAAA;EACA,qBlBq6BwC;EkBp6BxC,mBlBq6BwC;EkBp6BxC,uBlBq6BwC;AV+zD1C;A4BluFE;EACE,WAAA;EACA,mBAAA;A5BouFJ;;A4BhuFA;EACE,oBlB25BwC;EkB15BxC,eAAA;EACA,iBAAA;A5BmuFF;A4BjuFE;EACE,YAAA;EACA,oBAAA;EACA,cAAA;A5BmuFJ;;A4B/tFA;EACE,qCAAA;EAEA,cAAA;EACA,UlB04BwC;EkBz4BxC,WlBy4BwC;EkBx4BxC,kBAAA;EACA,mBAAA;EACA,wBAAA;KAAA,qBAAA;UAAA,gBAAA;EACA,yCAAA;EACA,+CAAA;EACA,4BAAA;EACA,2BAAA;EACA,wBAAA;EACA,2DlB24BwC;EkB14BxC,iCAAA;UAAA,yBAAA;A5BiuFF;A4B1tFE;EAEE,kBlBm4BsC;AVw1D1C;A4BxtFE;EACE,uBlB03BsC;AVg2D1C;A4BvtFE;EACE,qBlBs1BoC;EkBr1BpC,UAAA;EACA,iDlB8foB;AV2tExB;A4BttFE;EACE,yBxB9DG;EwB+DH,qBxB/DG;AJuxFP;A4BttFI;EAII,uPAAA;A5BqtFR;A4BjtFI;EAII,+JAAA;A5BgtFR;A4B3sFE;EACE,yBxBnFG;EwBoFH,qBxBpFG;EwByFD,iPAAA;A5BysFN;A4BrsFE;EACE,oBAAA;EACA,YAAA;EACA,YlBk2BuC;AVq2D3C;A4BhsFI;EACE,eAAA;EACA,YlBy1BqC;AVy2D3C;;A4BprFA;EACE,mBlBo1BgC;AVm2DlC;A4BrrFE;EACE,2KAAA;EAEA,UlB80B8B;EkB70B9B,mBAAA;EACA,0CAAA;EACA,gCAAA;EH9GA,gBG+GmD;A5BsrFvD;A4BnrFI;EACE,6JAAA;A5BqrFN;A4BlrFI;EACE,iClB60B4B;EkBx0B1B,0JAAA;A5BgrFR;A4B3qFE;EACE,oBlBwzB8B;EkBvzB9B,eAAA;A5B6qFJ;A4B3qFI;EACE,oBAAA;EACA,cAAA;A5B6qFN;;A4BxqFA;EACE,qBAAA;EACA,kBlBsyBgC;AVq4DlC;;A4BxqFA;EACE,kBAAA;EACA,sBAAA;EACA,oBAAA;A5B2qFF;A4BvqFI;EACE,oBAAA;EACA,YAAA;EACA,UxB9JiB;AJu0FvB;;A4BlqFI;EACE,iLAAA;A5BqqFN;;A6Bx1FA;EACE,WAAA;EACA,cAAA;EACA,UAAA;EACA,wBAAA;KAAA,qBAAA;UAAA,gBAAA;EACA,6BAAA;A7B21FF;A6Bz1FE;EACE,UAAA;A7B21FJ;A6Bv1FI;EAA0B,iEnB8gCa;AV40D3C;A6Bz1FI;EAA0B,iEnB6gCa;AV+0D3C;A6Bz1FE;EACE,SAAA;A7B21FJ;A6Bx1FE;EACE,WnB+/BuC;EmB9/BvC,YnB8/BuC;EmB7/BvC,oBAAA;EACA,wBAAA;UAAA,gBAAA;EH1BF,yBAAA;EG4BE,SnB6/BuC;AV61D3C;A6Br1FI;EHjCF,yBhB8hCyC;AV21D3C;A6Bn1FE;EACE,WnBw+B8B;EmBv+B9B,cnBw+B8B;EmBv+B9B,kBAAA;EACA,enBu+B8B;EmBt+B9B,wCnBu+B8B;EmBt+B9B,yBAAA;A7Bq1FJ;A6Bh1FE;EACE,WnBo+BuC;EmBn+BvC,YnBm+BuC;EmBl+BvC,qBAAA;OAAA,gBAAA;EHpDF,yBAAA;EGsDE,SnBm+BuC;AV+2D3C;A6B70FI;EH3DF,yBhB8hCyC;AV62D3C;A6B30FE;EACE,WnB88B8B;EmB78B9B,cnB88B8B;EmB78B9B,kBAAA;EACA,enB68B8B;EmB58B9B,wCnB68B8B;EmB58B9B,yBAAA;A7B60FJ;A6Bx0FE;EACE,oBAAA;A7B00FJ;A6Bx0FI;EACE,2CnBg9BqC;AV03D3C;A6Bv0FI;EACE,2CnB48BqC;AV63D3C;;A8Bh6FA;EACE,kBAAA;A9Bm6FF;A8Bj6FE;;;EAGE,uDpBwiCoC;EoBviCpC,2DpBuiCoC;EoBtiCpC,iBpBuiCoC;AV43DxC;A8Bh6FE;EACE,kBAAA;EACA,MAAA;EACA,OAAA;EACA,UAAA;EACA,YAAA;EACA,oBAAA;EACA,gBAAA;EACA,iBAAA;EACA,uBAAA;EACA,mBAAA;EACA,oBAAA;EACA,gDAAA;EACA,qBAAA;A9Bk6FJ;A8B95FE;;EAEE,oBAAA;A9Bg6FJ;A8B95FI;EACE,kBAAA;A9Bi6FN;A8Bl6FI;;EACE,kBAAA;A9Bi6FN;A8B95FI;EAEE,qBpB4gCkC;EoB3gClC,wBpB4gCkC;AVq5DxC;A8Bp6FI;;;EAEE,qBpB4gCkC;EoB3gClC,wBpB4gCkC;AVq5DxC;A8B95FI;;EACE,qBpBugCkC;EoBtgClC,wBpBugCkC;AV05DxC;A8B75FE;EACE,qBpBigCoC;EoBhgCpC,wBpBigCoC;AV85DxC;A8Bx5FI;EACE,2CAAA;EACA,8DpB2/BkC;AVk6DxC;A8B/5FI;;;;EACE,2CAAA;EACA,8DpB2/BkC;AVk6DxC;A8B35FM;EACE,kBAAA;EACA,mBAAA;EACA,WAAA;EACA,apBm/BgC;EoBl/BhC,WAAA;EACA,mCpBg0BgC;AVgmExC;A8Bt6FM;;;;EACE,kBAAA;EACA,mBAAA;EACA,WAAA;EACA,apBm/BgC;EoBl/BhC,WAAA;EACA,mCpBg0BgC;AVgmExC;A8Bz5FI;EACE,2CAAA;EACA,8DpB0+BkC;AVi7DxC;A8Bt5FI;EACE,sCAAA;A9Bw5FN;A8Bp5FE;;EAEE,cpB1EO;AVg+FX;A8Bp5FI;;EACE,wCpB0yBkC;AV6mExC;;A+B9+FA;EACE,kBAAA;EACA,aAAA;EACA,eAAA;EACA,oBAAA;EACA,WAAA;A/Bi/FF;A+B/+FE;;;EAGE,kBAAA;EACA,cAAA;EACA,SAAA;EACA,YAAA;A/Bi/FJ;A+B7+FE;;;EAGE,UAAA;A/B++FJ;A+Bz+FE;EACE,kBAAA;EACA,UAAA;A/B2+FJ;A+Bz+FI;EACE,UAAA;A/B2+FN;;A+Bh+FA;EACE,aAAA;EACA,mBAAA;EACA,uBAAA;ExB8OI,mBALI;EwBvOR,gBrByjB4B;EqBxjB5B,gB3BlBsB;E2BmBtB,2BrBm1BsC;EqBl1BtC,kBAAA;EACA,mBAAA;EACA,uCrB06BsC;EqBz6BtC,2DAAA;A/Bm+FF;;A+Bz9FA;;;;EAIE,oBAAA;ExBwNI,mBALI;AP0wFV;;A+Bx9FA;;;;EAIE,uBAAA;ExB+MI,qBALI;APkxFV;;A+Bv9FA;;EAEE,mBAAA;A/B09FF;;A+Bv7FE;EACE,8CAAA;A/B07FJ;AgC3hGE;EACE,aAAA;EACA,WAAA;EACA,mBtBu0BoC;EHrkBlC,kBALI;EyB1PN,iCtBkjCqB;AV0+DzB;;AgCzhGE;EACE,kBAAA;EACA,SAAA;EACA,UAAA;EACA,aAAA;EACA,eAAA;EACA,uBAAA;EACA,kBAAA;EzBqPE,qBALI;EyB7ON,WtBqiCqB;EsBpiCrB,mCtBoiCqB;AVu/DzB;;AgCthGI;;;;EAEE,cAAA;AhC2hGN;;AgC1kGI;EAqDE,+CtBuhCmB;EsBphCjB,mCtB81BgC;EsB71BhC,2PAAA;EACA,4BAAA;EACA,wDAAA;EACA,4DAAA;AhCuhGR;AgCphGM;EACE,+CtB4gCiB;EsBvgCf,2DtBugCe;AV2gEzB;;AgCvlGI;EA+EI,mCtBu0BgC;EsBt0BhC,4EAAA;AhC4gGR;;AgC5lGI;EAuFE,+CtBq/BmB;AVohEzB;AgCtgGQ;EAEE,mQAAA;EACA,sBtBq5B8B;EsBp5B9B,6DAAA;EACA,uEAAA;AhCugGV;AgCngGM;EACE,+CtBw+BiB;EsBn+Bf,2DtBm+Be;AV8hEzB;;AgC1mGI;EAkHI,wCAAA;AhC4/FR;;AgC9mGI;EAyHE,+CtBm9BmB;AVsiEzB;AgCv/FM;EACE,4CtBg9BiB;AVyiEzB;AgCt/FM;EACE,2DtB48BiB;AV4iEzB;AgCr/FM;EACE,iCtBw8BiB;AV+iEzB;;AgCl/FI;EACE,kBAAA;AhCq/FN;;AgC/nGI;;;;;EAoJM,UAAA;AhCm/FV;;AgCnnGE;EACE,aAAA;EACA,WAAA;EACA,mBtBu0BoC;EHrkBlC,kBALI;EyB1PN,mCtBkjCqB;AVmkEzB;;AgClnGE;EACE,kBAAA;EACA,SAAA;EACA,UAAA;EACA,aAAA;EACA,eAAA;EACA,uBAAA;EACA,kBAAA;EzBqPE,qBALI;EyB7ON,WtBqiCqB;EsBpiCrB,kCtBoiCqB;AVglEzB;;AgC/mGI;;;;EAEE,cAAA;AhConGN;;AgCnqGI;EAqDE,iDtBuhCmB;EsBphCjB,mCtB81BgC;EsB71BhC,4UAAA;EACA,4BAAA;EACA,wDAAA;EACA,4DAAA;AhCgnGR;AgC7mGM;EACE,iDtB4gCiB;EsBvgCf,0DtBugCe;AVomEzB;;AgChrGI;EA+EI,mCtBu0BgC;EsBt0BhC,4EAAA;AhCqmGR;;AgCrrGI;EAuFE,iDtBq/BmB;AV6mEzB;AgC/lGQ;EAEE,oVAAA;EACA,sBtBq5B8B;EsBp5B9B,6DAAA;EACA,uEAAA;AhCgmGV;AgC5lGM;EACE,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2E97NQ;EAOI,2BAAA;A3E27NZ;;A2El8NQ;EAOI,4BAAA;A3E+7NZ;;A2Et8NQ;EAOI,2BAAA;A3Em8NZ;;A2E18NQ;EAOI,2BAAA;A3Eu8NZ;;A2E98NQ;EAOI,6BAAA;A3E28NZ;;A2El9NQ;EAOI,8BAAA;A3E+8NZ;;A2Et9NQ;EAOI,6BAAA;A3Em9NZ;;A2E19NQ;EAOI,2BAAA;A3Eu9NZ;;A2E99NQ;EAOI,6BAAA;A3E29NZ;;A2El+NQ;EAOI,8BAAA;A3E+9NZ;;A2Et+NQ;EAOI,6BAAA;A3Em+NZ;;A2E1+NQ;EAOI,0BAAA;A3Eu+NZ;;A2E9+NQ;EAOI,gCAAA;A3E2+NZ;;A2El/NQ;EAOI,yBAAA;A3E++NZ;;A2Et/NQ;EAOI,wBAAA;A3Em/NZ;;A2E1/NQ;EAOI,+BAAA;A3Eu/NZ;;A2E9/NQ;EAOI,yBAAA;A3E2/NZ;;A2ElgOQ;EAOI,6BAAA;A3E+/NZ;;A2EtgOQ;EAOI,8BAAA;A3EmgOZ;;A2E1gOQ;EAOI,wBAAA;A3EugOZ;;A2E9gOQ;EAOI,+BAAA;A3E2gOZ;;A2ElhOQ;EAOI,wBAAA;A3E+gOZ;;A2EthOQ;EAOI,2CAAA;A3EmhOZ;;A2E1hOQ;EAOI,8CAAA;A3EuhOZ;;A2E9hOQ;EAOI,8CAAA;A3E2hOZ;;A2EliOQ;EAOI,2BAAA;A3E+hOZ;;A2EhjOQ;EACE,gFAAA;A3EmjOV;;A2EpjOQ;EACE,kFAAA;A3EujOV;;A2ExjOQ;EACE,gFAAA;A3E2jOV;;A2E5jOQ;EACE,6EAAA;A3E+jOV;;A2EhkOQ;EACE,gFAAA;A3EmkOV;;A2EpkOQ;EACE,+EAAA;A3EukOV;;A2ExkOQ;EACE,8EAAA;A3E2kOV;;A2E5kOQ;EACE,6EAAA;A3E+kOV;;A2EtkOQ;EAOI,2BAAA;A3EmkOZ;;A2E1kOQ;EAOI,6BAAA;A3EukOZ;;A2E9kOQ;EAOI,6BAAA;A3E2kOZ;;A2EllOQ;EAOI,0BAAA;A3E+kOZ;;A2EtlOQ;EAOI,2BAAA;A3EmlOZ;;A2E1lOQ;EAOI,iBAAA;A3EulOZ;;A2E9lOQ;EAOI,mBAAA;A3E2lOZ;;A2ElmOQ;EAOI,oBAAA;A3E+lOZ;;A2EtmOQ;EAOI,oBAAA;A3EmmOZ;;A2E1mOQ;EAOI,sBAAA;A3EumOZ;;A2E9mOQ;EAOI,uBAAA;A3E2mOZ;;A2ElnOQ;EAOI,kBAAA;A3E+mOZ;;A2EtnOQ;EAOI,oBAAA;A3EmnOZ;;A2E1nOQ;EAOI,qBAAA;A3EunOZ;;A2E9nOQ;EAOI,mBAAA;A3E2nOZ;;A2EloOQ;EAOI,qBAAA;A3E+nOZ;;A2EtoOQ;EAOI,sBAAA;A3EmoOZ;;A2E1oOQ;EAOI,2CAAA;A3EuoOZ;;A2E9oOQ;EAOI,sCAAA;A3E2oOZ;;A2ElpOQ;EAOI,sCAAA;A3E+oOZ;;A2EtpOQ;EAOI,uFAAA;A3EmpOZ;;A2E1pOQ;EAOI,oBAAA;A3EupOZ;;A2E9pOQ;EAOI,2FAAA;A3E2pOZ;;A2ElqOQ;EAOI,wBAAA;A3E+pOZ;;A2EtqOQ;EAOI,6FAAA;A3EmqOZ;;A2E1qOQ;EAOI,0BAAA;A3EuqOZ;;A2E9qOQ;EAOI,8FAAA;A3E2qOZ;;A2ElrOQ;EAOI,2BAAA;A3E+qOZ;;A2EtrOQ;EAOI,4FAAA;A3EmrOZ;;A2E1rOQ;EAOI,yBAAA;A3EurOZ;;A2E9rOQ;EAIQ,sBAAA;EAGJ,8EAAA;A3E4rOZ;;A2EnsOQ;EAIQ,sBAAA;EAGJ,gFAAA;A3EisOZ;;A2ExsOQ;EAIQ,sBAAA;EAGJ,8EAAA;A3EssOZ;;A2E7sOQ;EAIQ,sBAAA;EAGJ,2EAAA;A3E2sOZ;;A2EltOQ;EAIQ,sBAAA;EAGJ,8EAAA;A3EgtOZ;;A2EvtOQ;EAIQ,sBAAA;EAGJ,6EAAA;A3EqtOZ;;A2E5tOQ;EAIQ,sBAAA;EAGJ,4EAAA;A3E0tOZ;;A2EjuOQ;EAIQ,sBAAA;EAGJ,2EAAA;A3E+tOZ;;A2EtuOQ;EAIQ,sBAAA;EAGJ,4EAAA;A3EouOZ;;A2E3uOQ;EAIQ,sBAAA;EAGJ,4EAAA;A3EyuOZ;;A2EhvOQ;EAOI,wDAAA;A3E6uOZ;;A2EpvOQ;EAOI,0DAAA;A3EivOZ;;A2ExvOQ;EAOI,wDAAA;A3EqvOZ;;A2E5vOQ;EAOI,qDAAA;A3EyvOZ;;A2EhwOQ;EAOI,wDAAA;A3E6vOZ;;A2EpwOQ;EAOI,uDAAA;A3EiwOZ;;A2ExwOQ;EAOI,sDAAA;A3EqwOZ;;A2E5wOQ;EAOI,qDAAA;A3EywOZ;;A2EhxOQ;EAOI,4BAAA;A3E6wOZ;;A2EpxOQ;EAOI,4BAAA;A3EixOZ;;A2ExxOQ;EAOI,4BAAA;A3EqxOZ;;A2E5xOQ;EAOI,4BAAA;A3EyxOZ;;A2EhyOQ;EAOI,4BAAA;A3E6xOZ;;A2E9yOQ;EACE,wBAAA;A3EizOV;;A2ElzOQ;EACE,yBAAA;A3EqzOV;;A2EtzOQ;EACE,wBAAA;A3EyzOV;;A2E1zOQ;EACE,yBAAA;A3E6zOV;;A2E9zOQ;EACE,sBAAA;A3Ei0OV;;A2ExzOQ;EAOI,qBAAA;A3EqzOZ;;A2E5zOQ;EAOI,qBAAA;A3EyzOZ;;A2Eh0OQ;EAOI,qBAAA;A3E6zOZ;;A2Ep0OQ;EAOI,sBAAA;A3Ei0OZ;;A2Ex0OQ;EAOI,sBAAA;A3Eq0OZ;;A2E50OQ;EAOI,0BAAA;A3Ey0OZ;;A2Eh1OQ;EAOI,uBAAA;A3E60OZ;;A2Ep1OQ;EAOI,2BAAA;A3Ei1OZ;;A2Ex1OQ;EAOI,sBAAA;A3Eq1OZ;;A2E51OQ;EAOI,sBAAA;A3Ey1OZ;;A2Eh2OQ;EAOI,sBAAA;A3E61OZ;;A2Ep2OQ;EAOI,uBAAA;A3Ei2OZ;;A2Ex2OQ;EAOI,uBAAA;A3Eq2OZ;;A2E52OQ;EAOI,2BAAA;A3Ey2OZ;;A2Eh3OQ;EAOI,wBAAA;A3E62OZ;;A2Ep3OQ;EAOI,4BAAA;A3Ei3OZ;;A2Ex3OQ;EAOI,yBAAA;A3Eq3OZ;;A2E53OQ;EAOI,8BAAA;A3Ey3OZ;;A2Eh4OQ;EAOI,iCAAA;A3E63OZ;;A2Ep4OQ;EAOI,sCAAA;A3Ei4OZ;;A2Ex4OQ;EAOI,yCAAA;A3Eq4OZ;;A2E54OQ;EAOI,uBAAA;A3Ey4OZ;;A2Eh5OQ;EAOI,uBAAA;A3E64OZ;;A2Ep5OQ;EAOI,yBAAA;A3Ei5OZ;;A2Ex5OQ;EAOI,yBAAA;A3Eq5OZ;;A2E55OQ;EAOI,0BAAA;A3Ey5OZ;;A2Eh6OQ;EAOI,4BAAA;A3E65OZ;;A2Ep6OQ;EAOI,kCAAA;A3Ei6OZ;;A2Ex6OQ;EAOI,sCAAA;A3Eq6OZ;;A2E56OQ;EAOI,oCAAA;A3Ey6OZ;;A2Eh7OQ;EAOI,kCAAA;A3E66OZ;;A2Ep7OQ;EAOI,yCAAA;A3Ei7OZ;;A2Ex7OQ;EAOI,wCAAA;A3Eq7OZ;;A2E57OQ;EAOI,wCAAA;A3Ey7OZ;;A2Eh8OQ;EAOI,kCAAA;A3E67OZ;;A2Ep8OQ;EAOI,gCAAA;A3Ei8OZ;;A2Ex8OQ;EAOI,8BAAA;A3Eq8OZ;;A2E58OQ;EAOI,gCAAA;A3Ey8OZ;;A2Eh9OQ;EAOI,+BAAA;A3E68OZ;;A2Ep9OQ;EAOI,oCAAA;A3Ei9OZ;;A2Ex9OQ;EAOI,kCAAA;A3Eq9OZ;;A2E59OQ;EAOI,gCAAA;A3Ey9OZ;;A2Eh+OQ;EAOI,uCAAA;A3E69OZ;;A2Ep+OQ;EAOI,sCAAA;A3Ei+OZ;;A2Ex+OQ;EAOI,iCAAA;A3Eq+OZ;;A2E5+OQ;EAOI,2BAAA;A3Ey+OZ;;A2Eh/OQ;EAOI,iCAAA;A3E6+OZ;;A2Ep/OQ;EAOI,+BAAA;A3Ei/OZ;;A2Ex/OQ;EAOI,6BAAA;A3Eq/OZ;;A2E5/OQ;EAOI,+BAAA;A3Ey/OZ;;A2EhgPQ;EAOI,8BAAA;A3E6/OZ;;A2EpgPQ;EAOI,oBAAA;A3EigPZ;;A2ExgPQ;EAOI,mBAAA;A3EqgPZ;;A2E5gPQ;EAOI,mBAAA;A3EygPZ;;A2EhhPQ;EAOI,mBAAA;A3E6gPZ;;A2EphPQ;EAOI,mBAAA;A3EihPZ;;A2ExhPQ;EAOI,mBAAA;A3EqhPZ;;A2E5hPQ;EAOI,mBAAA;A3EyhPZ;;A2EhiPQ;EAOI,mBAAA;A3E6hPZ;;A2EpiPQ;EAOI,oBAAA;A3EiiPZ;;A2ExiPQ;EAOI,0BAAA;A3EqiPZ;;A2E5iPQ;EAOI,yBAAA;A3EyiPZ;;A2EhjPQ;EAOI,uBAAA;A3E6iPZ;;A2EpjPQ;EAOI,yBAAA;A3EijPZ;;A2ExjPQ;EAOI,uBAAA;A3EqjPZ;;A2E5jPQ;EAOI,uBAAA;A3EyjPZ;;A2EhkPQ;EAOI,0BAAA;EAAA,yBAAA;A3E8jPZ;;A2ErkPQ;EAOI,gCAAA;EAAA,+BAAA;A3EmkPZ;;A2E1kPQ;EAOI,+BAAA;EAAA,8BAAA;A3EwkPZ;;A2E/kPQ;EAOI,6BAAA;EAAA,4BAAA;A3E6kPZ;;A2EplPQ;EAOI,+BAAA;EAAA,8BAAA;A3EklPZ;;A2EzlPQ;EAOI,6BAAA;EAAA,4BAAA;A3EulPZ;;A2E9lPQ;EAOI,6BAAA;EAAA,4BAAA;A3E4lPZ;;A2EnmPQ;EAOI,wBAAA;EAAA,2BAAA;A3EimPZ;;A2ExmPQ;EAOI,8BAAA;EAAA,iCAAA;A3EsmPZ;;A2E7mPQ;EAOI,6BAAA;EAAA,gCAAA;A3E2mPZ;;A2ElnPQ;EAOI,2BAAA;EAAA,8BAAA;A3EgnPZ;;A2EvnPQ;EAOI,6BAAA;EAAA,gCAAA;A3EqnPZ;;A2E5nPQ;EAOI,2BAAA;EAAA,8BAAA;A3E0nPZ;;A2EjoPQ;EAOI,2BAAA;EAAA,8BAAA;A3E+nPZ;;A2EtoPQ;EAOI,wBAAA;A3EmoPZ;;A2E1oPQ;EAOI,8BAAA;A3EuoPZ;;A2E9oPQ;EAOI,6BAAA;A3E2oPZ;;A2ElpPQ;EAOI,2BAAA;A3E+oPZ;;A2EtpPQ;EAOI,6BAAA;A3EmpPZ;;A2E1pPQ;EAOI,2BAAA;A3EupPZ;;A2E9pPQ;EAOI,2BAAA;A3E2pPZ;;A2ElqPQ;EAOI,0BAAA;A3E+pPZ;;A2EtqPQ;EAOI,gCAAA;A3EmqPZ;;A2E1qPQ;EAOI,+BAAA;A3EuqPZ;;A2E9qPQ;EAOI,6BAAA;A3E2qPZ;;A2ElrPQ;EAOI,+BAAA;A3E+qPZ;;A2EtrPQ;EAOI,6BAAA;A3EmrPZ;;A2E1rPQ;EAOI,6BAAA;A3EurPZ;;A2E9rPQ;EAOI,2BAAA;A3E2rPZ;;A2ElsPQ;EAOI,iCAAA;A3E+rPZ;;A2EtsPQ;EAOI,gCAAA;A3EmsPZ;;A2E1sPQ;EAOI,8BAAA;A3EusPZ;;A2E9sPQ;EAOI,gCAAA;A3E2sPZ;;A2EltPQ;EAOI,8BAAA;A3E+sPZ;;A2EttPQ;EAOI,8BAAA;A3EmtPZ;;A2E1tPQ;EAOI,yBAAA;A3EutPZ;;A2E9tPQ;EAOI,+BAAA;A3E2tPZ;;A2EluPQ;EAOI,8BAAA;A3E+tPZ;;A2EtuPQ;EAOI,4BAAA;A3EmuPZ;;A2E1uPQ;EAOI,8BAAA;A3EuuPZ;;A2E9uPQ;EAOI,4BAAA;A3E2uPZ;;A2ElvPQ;EAOI,4BAAA;A3E+uPZ;;A2EtvPQ;EAOI,qBAAA;A3EmvPZ;;A2E1vPQ;EAOI,2BAAA;A3EuvPZ;;A2E9vPQ;EAOI,0BAAA;A3E2vPZ;;A2ElwPQ;EAOI,wBAAA;A3E+vPZ;;A2EtwPQ;EAOI,0BAAA;A3EmwPZ;;A2E1wPQ;EAOI,wBAAA;A3EuwPZ;;A2E9wPQ;EAOI,2BAAA;EAAA,0BAAA;A3E4wPZ;;A2EnxPQ;EAOI,iCAAA;EAAA,gCAAA;A3EixPZ;;A2ExxPQ;EAOI,gCAAA;EAAA,+BAAA;A3EsxPZ;;A2E7xPQ;EAOI,8BAAA;EAAA,6BAAA;A3E2xPZ;;A2ElyPQ;EAOI,gCAAA;EAAA,+BAAA;A3EgyPZ;;A2EvyPQ;EAOI,8BAAA;EAAA,6BAAA;A3EqyPZ;;A2E5yPQ;EAOI,yBAAA;EAAA,4BAAA;A3E0yPZ;;A2EjzPQ;EAOI,+BAAA;EAAA,kCAAA;A3E+yPZ;;A2EtzPQ;EAOI,8BAAA;EAAA,iCAAA;A3EozPZ;;A2E3zPQ;EAOI,4BAAA;EAAA,+BAAA;A3EyzPZ;;A2Eh0PQ;EAOI,8BAAA;EAAA,iCAAA;A3E8zPZ;;A2Er0PQ;EAOI,4BAAA;EAAA,+BAAA;A3Em0PZ;;A2E10PQ;EAOI,yBAAA;A3Eu0PZ;;A2E90PQ;EAOI,+BAAA;A3E20PZ;;A2El1PQ;EAOI,8BAAA;A3E+0PZ;;A2Et1PQ;EAOI,4BAAA;A3Em1PZ;;A2E11PQ;EAOI,8BAAA;A3Eu1PZ;;A2E91PQ;EAOI,4BAAA;A3E21PZ;;A2El2PQ;EAOI,2BAAA;A3E+1PZ;;A2Et2PQ;EAOI,iCAAA;A3Em2PZ;;A2E12PQ;EAOI,gCAAA;A3Eu2PZ;;A2E92PQ;EAOI,8BAAA;A3E22PZ;;A2El3PQ;EAOI,gCAAA;A3E+2PZ;;A2Et3PQ;EAOI,8BAAA;A3Em3PZ;;A2E13PQ;EAOI,4BAAA;A3Eu3PZ;;A2E93PQ;EAOI,kCAAA;A3E23PZ;;A2El4PQ;EAOI,iCAAA;A3E+3PZ;;A2Et4PQ;EAOI,+BAAA;A3Em4PZ;;A2E14PQ;EAOI,iCAAA;A3Eu4PZ;;A2E94PQ;EAOI,+BAAA;A3E24PZ;;A2El5PQ;EAOI,0BAAA;A3E+4PZ;;A2Et5PQ;EAOI,gCAAA;A3Em5PZ;;A2E15PQ;EAOI,+BAAA;A3Eu5PZ;;A2E95PQ;EAOI,6BAAA;A3E25PZ;;A2El6PQ;EAOI,+BAAA;A3E+5PZ;;A2Et6PQ;EAOI,6BAAA;A3Em6PZ;;A2E16PQ;EAOI,iBAAA;A3Eu6PZ;;A2E96PQ;EAOI,uBAAA;A3E26PZ;;A2El7PQ;EAOI,sBAAA;A3E+6PZ;;A2Et7PQ;EAOI,oBAAA;A3Em7PZ;;A2E17PQ;EAOI,sBAAA;A3Eu7PZ;;A2E97PQ;EAOI,oBAAA;A3E27PZ;;A2El8PQ;EAOI,qBAAA;A3E+7PZ;;A2Et8PQ;EAOI,2BAAA;A3Em8PZ;;A2E18PQ;EAOI,0BAAA;A3Eu8PZ;;A2E98PQ;EAOI,wBAAA;A3E28PZ;;A2El9PQ;EAOI,0BAAA;A3E+8PZ;;A2Et9PQ;EAOI,wBAAA;A3Em9PZ;;A2E19PQ;EAOI,6BAAA;OAAA,wBAAA;A3Eu9PZ;;A2E99PQ;EAOI,mCAAA;OAAA,8BAAA;A3E29PZ;;A2El+PQ;EAOI,kCAAA;OAAA,6BAAA;A3E+9PZ;;A2Et+PQ;EAOI,gCAAA;OAAA,2BAAA;A3Em+PZ;;A2E1+PQ;EAOI,kCAAA;OAAA,6BAAA;A3Eu+PZ;;A2E9+PQ;EAOI,gCAAA;OAAA,2BAAA;A3E2+PZ;;A2El/PQ;EAOI,gDAAA;A3E++PZ;;A2Et/PQ;EAOI,gDAAA;A3Em/PZ;;A2E1/PQ;EAOI,0CAAA;A3Eu/PZ;;A2E9/PQ;EAOI,kDAAA;A3E2/PZ;;A2ElgQQ;EAOI,gDAAA;A3E+/PZ;;A2EtgQQ;EAOI,gCAAA;A3EmgQZ;;A2E1gQQ;EAOI,8BAAA;A3EugQZ;;A2E9gQQ;EAOI,6BAAA;A3E2gQZ;;A2ElhQQ;EAOI,6BAAA;A3E+gQZ;;A2EthQQ;EAOI,+BAAA;A3EmhQZ;;A2E1hQQ;EAOI,2BAAA;A3EuhQZ;;A2E9hQQ;EAOI,2BAAA;A3E2hQZ;;A2EliQQ;EAOI,2BAAA;A3E+hQZ;;A2EtiQQ;EAOI,2BAAA;A3EmiQZ;;A2E1iQQ;EAOI,2BAAA;A3EuiQZ;;A2E9iQQ;EAOI,8BAAA;A3E2iQZ;;A2EljQQ;EAOI,yBAAA;A3E+iQZ;;A2EtjQQ;EAOI,4BAAA;A3EmjQZ;;A2E1jQQ;EAOI,2BAAA;A3EujQZ;;A2E9jQQ;EAOI,yBAAA;A3E2jQZ;;A2ElkQQ;EAOI,2BAAA;A3E+jQZ;;A2EtkQQ;EAOI,4BAAA;A3EmkQZ;;A2E1kQQ;EAOI,6BAAA;A3EukQZ;;A2E9kQQ;EAOI,gCAAA;A3E2kQZ;;A2EllQQ;EAOI,qCAAA;A3E+kQZ;;A2EtlQQ;EAOI,wCAAA;A3EmlQZ;;A2E1lQQ;EAOI,oCAAA;A3EulQZ;;A2E9lQQ;EAOI,oCAAA;A3E2lQZ;;A2ElmQQ;EAOI,qCAAA;A3E+lQZ;;A2EtmQQ;EAOI,8BAAA;A3EmmQZ;;A2E1mQQ;EAOI,8BAAA;A3EumQZ;;A2E5nQQ,qBAAA;AAcA;EAOI,gCAAA;EAAA,iCAAA;A3E6mQZ;;A2E1lQQ,mBAAA;AA1BA;EAIQ,oBAAA;EAGJ,qEAAA;A3EmnQZ;;A2E1nQQ;EAIQ,oBAAA;EAGJ,uEAAA;A3EwnQZ;;A2E/nQQ;EAIQ,oBAAA;EAGJ,qEAAA;A3E6nQZ;;A2EpoQQ;EAIQ,oBAAA;EAGJ,kEAAA;A3EkoQZ;;A2EzoQQ;EAIQ,oBAAA;EAGJ,qEAAA;A3EuoQZ;;A2E9oQQ;EAIQ,oBAAA;EAGJ,oEAAA;A3E4oQZ;;A2EnpQQ;EAIQ,oBAAA;EAGJ,mEAAA;A3EipQZ;;A2ExpQQ;EAIQ,oBAAA;EAGJ,kEAAA;A3EspQZ;;A2E7pQQ;EAIQ,oBAAA;EAGJ,mEAAA;A3E2pQZ;;A2ElqQQ;EAIQ,oBAAA;EAGJ,mEAAA;A3EgqQZ;;A2EvqQQ;EAIQ,oBAAA;EAGJ,wEAAA;A3EqqQZ;;A2E5qQQ;EAIQ,oBAAA;EAGJ,2CAAA;A3E0qQZ;;A2EjrQQ;EAIQ,oBAAA;EAGJ,oCAAA;A3E+qQZ;;A2EtrQQ;EAIQ,oBAAA;EAGJ,0CAAA;A3EorQZ;;A2E3rQQ;EAIQ,oBAAA;EAGJ,2CAAA;A3EyrQZ;;A2EhsQQ;EAIQ,oBAAA;EAGJ,0CAAA;A3E8rQZ;;A2ErsQQ;EAIQ,oBAAA;EAGJ,0CAAA;A3EmsQZ;;A2E1sQQ;EAIQ,oBAAA;EAGJ,yBAAA;A3EwsQZ;;A2EztQQ;EACE,uBAAA;A3E4tQV;;A2E7tQQ;EACE,sBAAA;A3EguQV;;A2EjuQQ;EACE,uBAAA;A3EouQV;;A2EruQQ;EACE,oBAAA;A3EwuQV;;A2E/tQQ;EAOI,iDAAA;A3E4tQZ;;A2EnuQQ;EAOI,mDAAA;A3EguQZ;;A2EvuQQ;EAOI,iDAAA;A3EouQZ;;A2E3uQQ;EAOI,8CAAA;A3EwuQZ;;A2E/uQQ;EAOI,iDAAA;A3E4uQZ;;A2EnvQQ;EAOI,gDAAA;A3EgvQZ;;A2EvvQQ;EAOI,+CAAA;A3EovQZ;;A2E3vQQ;EAOI,8CAAA;A3EwvQZ;;A2EzwQQ;EACE,sBAAA;A3E4wQV;;A2ExwQU;EACE,sBAAA;A3E2wQZ;;A2EjxQQ;EACE,uBAAA;A3EoxQV;;A2EhxQU;EACE,uBAAA;A3EmxQZ;;A2EzxQQ;EACE,sBAAA;A3E4xQV;;A2ExxQU;EACE,sBAAA;A3E2xQZ;;A2EjyQQ;EACE,uBAAA;A3EoyQV;;A2EhyQU;EACE,uBAAA;A3EmyQZ;;A2EzyQQ;EACE,oBAAA;A3E4yQV;;A2ExyQU;EACE,oBAAA;A3E2yQZ;;A2EvyQQ;EAOI,yCAAA;A3EoyQZ;;A2E/xQU;EAOI,yCAAA;A3E4xQd;;A2E/yQQ;EAOI,wCAAA;A3E4yQZ;;A2EvyQU;EAOI,wCAAA;A3EoyQd;;A2EvzQQ;EAOI,yCAAA;A3EozQZ;;A2E/yQU;EAOI,yCAAA;A3E4yQd;;A2E/zQQ;EAIQ,8BAAA;EAGJ,+FAAA;A3E6zQZ;;A2Ep0QQ;EAIQ,8BAAA;EAGJ,iGAAA;A3Ek0QZ;;A2Ez0QQ;EAIQ,8BAAA;EAGJ,+FAAA;A3Eu0QZ;;A2E90QQ;EAIQ,8BAAA;EAGJ,4FAAA;A3E40QZ;;A2En1QQ;EAIQ,8BAAA;EAGJ,+FAAA;A3Ei1QZ;;A2Ex1QQ;EAIQ,8BAAA;EAGJ,8FAAA;A3Es1QZ;;A2E71QQ;EAIQ,8BAAA;EAGJ,6FAAA;A3E21QZ;;A2El2QQ;EAIQ,8BAAA;EAGJ,4FAAA;A3Eg2QZ;;A2Ev2QQ;EAIQ,8BAAA;EAGJ,qGAAA;A3Eq2QZ;;A2Et3QQ;EACE,8BAAA;A3Ey3QV;;A2Er3QU;EACE,8BAAA;A3Ew3QZ;;A2E93QQ;EACE,gCAAA;A3Ei4QV;;A2E73QU;EACE,gCAAA;A3Eg4QZ;;A2Et4QQ;EACE,iCAAA;A3Ey4QV;;A2Er4QU;EACE,iCAAA;A3Ew4QZ;;A2E94QQ;EACE,gCAAA;A3Ei5QV;;A2E74QU;EACE,gCAAA;A3Eg5QZ;;A2Et5QQ;EACE,iCAAA;A3Ey5QV;;A2Er5QU;EACE,iCAAA;A3Ew5QZ;;A2E95QQ;EACE,8BAAA;A3Ei6QV;;A2E75QU;EACE,8BAAA;A3Eg6QZ;;A2E55QQ;EAIQ,kBAAA;EAGJ,8EAAA;A3E05QZ;;A2Ej6QQ;EAIQ,kBAAA;EAGJ,gFAAA;A3E+5QZ;;A2Et6QQ;EAIQ,kBAAA;EAGJ,8EAAA;A3Eo6QZ;;A2E36QQ;EAIQ,kBAAA;EAGJ,2EAAA;A3Ey6QZ;;A2Eh7QQ;EAIQ,kBAAA;EAGJ,8EAAA;A3E86QZ;;A2Er7QQ;EAIQ,kBAAA;EAGJ,6EAAA;A3Em7QZ;;A2E17QQ;EAIQ,kBAAA;EAGJ,4EAAA;A3Ew7QZ;;A2E/7QQ;EAIQ,kBAAA;EAGJ,2EAAA;A3E67QZ;;A2Ep8QQ;EAIQ,kBAAA;EAGJ,4EAAA;A3Ek8QZ;;A2Ez8QQ;EAIQ,kBAAA;EAGJ,4EAAA;A3Eu8QZ;;A2E98QQ;EAIQ,kBAAA;EAGJ,8EAAA;A3E48QZ;;A2En9QQ;EAIQ,kBAAA;EAGJ,wCAAA;A3Ei9QZ;;A2Ex9QQ;EAIQ,kBAAA;EAGJ,mFAAA;A3Es9QZ;;A2E79QQ;EAIQ,kBAAA;EAGJ,kFAAA;A3E29QZ;;A2E5+QQ;EACE,oBAAA;A3E++QV;;A2Eh/QQ;EACE,qBAAA;A3Em/QV;;A2Ep/QQ;EACE,oBAAA;A3Eu/QV;;A2Ex/QQ;EACE,qBAAA;A3E2/QV;;A2E5/QQ;EACE,kBAAA;A3E+/QV;;A2Et/QQ;EAOI,wDAAA;A3Em/QZ;;A2E1/QQ;EAOI,0DAAA;A3Eu/QZ;;A2E9/QQ;EAOI,wDAAA;A3E2/QZ;;A2ElgRQ;EAOI,qDAAA;A3E+/QZ;;A2EtgRQ;EAOI,wDAAA;A3EmgRZ;;A2E1gRQ;EAOI,uDAAA;A3EugRZ;;A2E9gRQ;EAOI,sDAAA;A3E2gRZ;;A2ElhRQ;EAOI,qDAAA;A3E+gRZ;;A2EthRQ;EAOI,+CAAA;A3EmhRZ;;A2E1hRQ;EAOI,mCAAA;KAAA,gCAAA;UAAA,2BAAA;A3EuhRZ;;A2E9hRQ;EAOI,oCAAA;KAAA,iCAAA;UAAA,4BAAA;A3E2hRZ;;A2EliRQ;EAOI,oCAAA;KAAA,iCAAA;UAAA,4BAAA;A3E+hRZ;;A2EtiRQ;EAOI,+BAAA;A3EmiRZ;;A2E1iRQ;EAOI,+BAAA;A3EuiRZ;;A2E9iRQ;EAOI,iDAAA;A3E2iRZ;;A2EljRQ;EAOI,2BAAA;A3E+iRZ;;A2EtjRQ;EAOI,oDAAA;A3EmjRZ;;A2E1jRQ;EAOI,iDAAA;A3EujRZ;;A2E9jRQ;EAOI,oDAAA;A3E2jRZ;;A2ElkRQ;EAOI,oDAAA;A3E+jRZ;;A2EtkRQ;EAOI,qDAAA;A3EmkRZ;;A2E1kRQ;EAOI,6BAAA;A3EukRZ;;A2E9kRQ;EAOI,sDAAA;A3E2kRZ;;A2EllRQ;EAOI,0DAAA;EAAA,2DAAA;A3EglRZ;;A2EvlRQ;EAOI,oCAAA;EAAA,qCAAA;A3EqlRZ;;A2E5lRQ;EAOI,6DAAA;EAAA,8DAAA;A3E0lRZ;;A2EjmRQ;EAOI,0DAAA;EAAA,2DAAA;A3E+lRZ;;A2EtmRQ;EAOI,6DAAA;EAAA,8DAAA;A3EomRZ;;A2E3mRQ;EAOI,6DAAA;EAAA,8DAAA;A3EymRZ;;A2EhnRQ;EAOI,8DAAA;EAAA,+DAAA;A3E8mRZ;;A2ErnRQ;EAOI,sCAAA;EAAA,uCAAA;A3EmnRZ;;A2E1nRQ;EAOI,+DAAA;EAAA,gEAAA;A3EwnRZ;;A2E/nRQ;EAOI,2DAAA;EAAA,8DAAA;A3E6nRZ;;A2EpoRQ;EAOI,qCAAA;EAAA,wCAAA;A3EkoRZ;;A2EzoRQ;EAOI,8DAAA;EAAA,iEAAA;A3EuoRZ;;A2E9oRQ;EAOI,2DAAA;EAAA,8DAAA;A3E4oRZ;;A2EnpRQ;EAOI,8DAAA;EAAA,iEAAA;A3EipRZ;;A2ExpRQ;EAOI,8DAAA;EAAA,iEAAA;A3EspRZ;;A2E7pRQ;EAOI,+DAAA;EAAA,kEAAA;A3E2pRZ;;A2ElqRQ;EAOI,uCAAA;EAAA,0CAAA;A3EgqRZ;;A2EvqRQ;EAOI,gEAAA;EAAA,mEAAA;A3EqqRZ;;A2E5qRQ;EAOI,8DAAA;EAAA,6DAAA;A3E0qRZ;;A2EjrRQ;EAOI,wCAAA;EAAA,uCAAA;A3E+qRZ;;A2EtrRQ;EAOI,iEAAA;EAAA,gEAAA;A3EorRZ;;A2E3rRQ;EAOI,8DAAA;EAAA,6DAAA;A3EyrRZ;;A2EhsRQ;EAOI,iEAAA;EAAA,gEAAA;A3E8rRZ;;A2ErsRQ;EAOI,iEAAA;EAAA,gEAAA;A3EmsRZ;;A2E1sRQ;EAOI,kEAAA;EAAA,iEAAA;A3EwsRZ;;A2E/sRQ;EAOI,0CAAA;EAAA,yCAAA;A3E6sRZ;;A2EptRQ;EAOI,mEAAA;EAAA,kEAAA;A3EktRZ;;A2EztRQ;EAOI,6DAAA;EAAA,0DAAA;A3EutRZ;;A2E9tRQ;EAOI,uCAAA;EAAA,oCAAA;A3E4tRZ;;A2EnuRQ;EAOI,gEAAA;EAAA,6DAAA;A3EiuRZ;;A2ExuRQ;EAOI,6DAAA;EAAA,0DAAA;A3EsuRZ;;A2E7uRQ;EAOI,gEAAA;EAAA,6DAAA;A3E2uRZ;;A2ElvRQ;EAOI,gEAAA;EAAA,6DAAA;A3EgvRZ;;A2EvvRQ;EAOI,iEAAA;EAAA,8DAAA;A3EqvRZ;;A2E5vRQ;EAOI,yCAAA;EAAA,sCAAA;A3E0vRZ;;A2EjwRQ;EAOI,kEAAA;EAAA,+DAAA;A3E+vRZ;;A2EtwRQ;EAOI,8BAAA;A3EmwRZ;;A2E1wRQ;EAOI,6BAAA;A3EuwRZ;;A2E9wRQ;EAOI,sBAAA;A3E2wRZ;;A2ElxRQ;EAOI,qBAAA;A3E+wRZ;;A2EtxRQ;EAOI,qBAAA;A3EmxRZ;;A2E1xRQ;EAOI,qBAAA;A3EuxRZ;;A2E9xRQ;EAOI,qBAAA;A3E2xRZ;;AiBryRI;E0DGI;IAOI,sBAAA;E3EgyRV;E2EvyRM;IAOI,uBAAA;E3EmyRV;E2E1yRM;IAOI,sBAAA;E3EsyRV;E2E7yRM;IAOI,iCAAA;OAAA,8BAAA;E3EyyRV;E2EhzRM;IAOI,+BAAA;OAAA,4BAAA;E3E4yRV;E2EnzRM;IAOI,8BAAA;OAAA,2BAAA;E3E+yRV;E2EtzRM;IAOI,oCAAA;OAAA,iCAAA;E3EkzRV;E2EzzRM;IAOI,8BAAA;OAAA,2BAAA;E3EqzRV;E2E5zRM;IAOI,0BAAA;E3EwzRV;E2E/zRM;IAOI,gCAAA;E3E2zRV;E2El0RM;IAOI,yBAAA;E3E8zRV;E2Er0RM;IAOI,wBAAA;E3Ei0RV;E2Ex0RM;IAOI,+BAAA;E3Eo0RV;E2E30RM;IAOI,yBAAA;E3Eu0RV;E2E90RM;IAOI,6BAAA;E3E00RV;E2Ej1RM;IAOI,8BAAA;E3E60RV;E2Ep1RM;IAOI,wBAAA;E3Eg1RV;E2Ev1RM;IAOI,+BAAA;E3Em1RV;E2E11RM;IAOI,wBAAA;E3Es1RV;E2E71RM;IAOI,yBAAA;E3Ey1RV;E2Eh2RM;IAOI,8BAAA;E3E41RV;E2En2RM;IAOI,iCAAA;E3E+1RV;E2Et2RM;IAOI,sCAAA;E3Ek2RV;E2Ez2RM;IAOI,yCAAA;E3Eq2RV;E2E52RM;IAOI,uBAAA;E3Ew2RV;E2E/2RM;IAOI,uBAAA;E3E22RV;E2El3RM;IAOI,yBAAA;E3E82RV;E2Er3RM;IAOI,yBAAA;E3Ei3RV;E2Ex3RM;IAOI,0BAAA;E3Eo3RV;E2E33RM;IAOI,4BAAA;E3Eu3RV;E2E93RM;IAOI,kCAAA;E3E03RV;E2Ej4RM;IAOI,sCAAA;E3E63RV;E2Ep4RM;IAOI,oCAAA;E3Eg4RV;E2Ev4RM;IAOI,kCAAA;E3Em4RV;E2E14RM;IAOI,yCAAA;E3Es4RV;E2E74RM;IAOI,wCAAA;E3Ey4RV;E2Eh5RM;IAOI,wCAAA;E3E44RV;E2En5RM;IAOI,kCAAA;E3E+4RV;E2Et5RM;IAOI,gCAAA;E3Ek5RV;E2Ez5RM;IAOI,8BAAA;E3Eq5RV;E2E55RM;IAOI,gCAAA;E3Ew5RV;E2E/5RM;IAOI,+BAAA;E3E25RV;E2El6RM;IAOI,oCAAA;E3E85RV;E2Er6RM;IAOI,kCAAA;E3Ei6RV;E2Ex6RM;IAOI,gCAAA;E3Eo6RV;E2E36RM;IAOI,uCAAA;E3Eu6RV;E2E96RM;IAOI,sCAAA;E3E06RV;E2Ej7RM;IAOI,iCAAA;E3E66RV;E2Ep7RM;IAOI,2BAAA;E3Eg7RV;E2Ev7RM;IAOI,iCAAA;E3Em7RV;E2E17RM;IAOI,+BAAA;E3Es7RV;E2E77RM;IAOI,6BAAA;E3Ey7RV;E2Eh8RM;IAOI,+BAAA;E3E47RV;E2En8RM;IAOI,8BAAA;E3E+7RV;E2Et8RM;IAOI,oBAAA;E3Ek8RV;E2Ez8RM;IAOI,mBAAA;E3Eq8RV;E2E58RM;IAOI,mBAAA;E3Ew8RV;E2E/8RM;IAOI,mBAAA;E3E28RV;E2El9RM;IAOI,mBAAA;E3E88RV;E2Er9RM;IAOI,mBAAA;E3Ei9RV;E2Ex9RM;IAOI,mBAAA;E3Eo9RV;E2E39RM;IAOI,mBAAA;E3Eu9RV;E2E99RM;IAOI,oBAAA;E3E09R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5+VM;IAOI,wBAAA;E3Ew+VV;E2E/+VM;IAOI,+BAAA;E3E2+VV;E2El/VM;IAOI,wBAAA;E3E8+VV;E2Er/VM;IAOI,yBAAA;E3Ei/VV;E2Ex/VM;IAOI,8BAAA;E3Eo/VV;E2E3/VM;IAOI,iCAAA;E3Eu/VV;E2E9/VM;IAOI,sCAAA;E3E0/VV;E2EjgWM;IAOI,yCAAA;E3E6/VV;E2EpgWM;IAOI,uBAAA;E3EggWV;E2EvgWM;IAOI,uBAAA;E3EmgWV;E2E1gWM;IAOI,yBAAA;E3EsgWV;E2E7gWM;IAOI,yBAAA;E3EygWV;E2EhhWM;IAOI,0BAAA;E3E4gWV;E2EnhWM;IAOI,4BAAA;E3E+gWV;E2EthWM;IAOI,kCAAA;E3EkhWV;E2EzhWM;IAOI,sCAAA;E3EqhWV;E2E5hWM;IAOI,oCAAA;E3EwhWV;E2E/hWM;IAOI,kCAAA;E3E2hWV;E2EliWM;IAOI,yCAAA;E3E8hWV;E2EriWM;IAOI,wCAAA;E3EiiWV;E2ExiWM;IAOI,wCAAA;E3EoiWV;E2E3iWM;IAOI,kCAAA;E3EuiWV;E2E9iWM;IAOI,gCAAA;E3E0iWV;E2EjjWM;IAOI,8BAAA;E3E6iWV;E2EpjWM;IAOI,gCAAA;E3EgjWV;E2EvjWM;IAOI,+BAAA;E3EmjWV;E2E1jWM;IAOI,oCAAA;E3EsjWV;E2E7jWM;IAOI,kCAAA;E3EyjWV;E2EhkWM;IAOI,gCAAA;E3E4jWV;E2EnkWM;IAOI,uCAAA;E3E+jWV;E2EtkWM;IAOI,sCAAA;E3EkkWV;E2EzkWM;IAOI,iCAAA;E3EqkWV;E2E5kWM;IAOI,2BAAA;E3EwkWV;E2E/kWM;IAOI,iCAAA;E3E2kWV;E2EllWM;IAOI,+BAAA;E3E8kWV;E2ErlWM;IAOI,6BAAA;E3EilWV;E2ExlWM;IAOI,+BAAA;E3EolWV;E2E3lWM;IAOI,8BAAA;E3EulWV;E2E9lWM;IAOI,oBAAA;E3E0lWV;E2EjmWM;IAOI,mBAAA;E3E6lWV;E2EpmWM;IAOI,mBAAA;E3EgmWV;E2EvmWM;IAOI,mBAAA;E3EmmWV;E2E1mWM;IAOI,mBAAA;E3EsmWV;E2E7mWM;IAOI,mBAAA;E3EymWV;E2EhnWM;IAOI,mB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","file":"vizro-bootstrap.min.css"} diff --git a/vizro-core/src/vizro/static/js/models/dashboard.js b/vizro-core/src/vizro/static/js/models/dashboard.js index 4c6639b10..76a27d994 100644 --- a/vizro-core/src/vizro/static/js/models/dashboard.js +++ b/vizro-core/src/vizro/static/js/models/dashboard.js @@ -20,7 +20,6 @@ export function _collapse_nav_panel(n_clicks, is_open) { false, { transform: "rotate(180deg)", - left: "4px", transition: "transform 0.35s ease-in-out", }, "Show Menu", @@ -33,7 +32,6 @@ export function _collapse_nav_panel(n_clicks, is_open) { false, { transform: "rotate(180deg)", - left: "4px", transition: "transform 0.35s ease-in-out", }, "Show Menu", diff --git a/vizro-core/src/vizro/tables/_dash_ag_grid.py b/vizro-core/src/vizro/tables/_dash_ag_grid.py index b81de5de9..fe06cb9b0 100644 --- a/vizro-core/src/vizro/tables/_dash_ag_grid.py +++ b/vizro-core/src/vizro/tables/_dash_ag_grid.py @@ -67,8 +67,6 @@ def dash_ag_grid(data_frame: pd.DataFrame, **kwargs) -> dag.AgGrid: "buttons": ["apply", "reset"], "closeOnApply": True, }, - "flex": 1, - "minWidth": 70, }, "dashGridOptions": { "dataTypeDefinitions": _DATA_TYPE_DEFINITIONS, diff --git a/vizro-core/src/vizro/tables/_dash_table.py b/vizro-core/src/vizro/tables/_dash_table.py index 0581d96d8..c4e07fab5 100644 --- a/vizro-core/src/vizro/tables/_dash_table.py +++ b/vizro-core/src/vizro/tables/_dash_table.py @@ -25,6 +25,7 @@ def dash_data_table(data_frame: pd.DataFrame, **kwargs): defaults = { "columns": [{"name": i, "id": i} for i in data_frame.columns], "style_as_list_view": True, + "style_cell": {"position": "static"}, "style_data": {"border_bottom": "1px solid var(--border-subtle-alpha-01)", "height": "40px"}, "style_header": { "border_bottom": "1px solid var(--state-overlays-selected-hover)", diff --git a/vizro-core/tests/integration/test_examples.py b/vizro-core/tests/integration/test_examples.py index d34a44c77..e3bc32ac8 100644 --- a/vizro-core/tests/integration/test_examples.py +++ b/vizro-core/tests/integration/test_examples.py @@ -3,7 +3,7 @@ import runpy from pathlib import Path -import chromedriver_autoinstaller_fix +import chromedriver_autoinstaller import pytest from vizro import Vizro @@ -21,7 +21,7 @@ def setup_integration_test_environment(monkeypatch_session): monkeypatch_session.setenv("DASH_DEBUG", "false") # We only need to install chromedriver outside CI. if not os.getenv("CI"): - chromedriver_autoinstaller_fix.install() + chromedriver_autoinstaller.install() @pytest.fixture @@ -35,15 +35,21 @@ def dashboard(request, monkeypatch): # Ignore deprecation warning until this is solved: https://github.com/plotly/dash/issues/2590 +# The `features` examples do add_type, which ideally we would clean up afterwards to restore vizro.models to its +# previous state. Since we don't currently do this, `hatch run test` fails. +# This is difficult to fix fully by un-importing vizro.models though, since we use `import vizro.models as vm` - see +# https://stackoverflow.com/questions/437589/how-do-i-unload-reload-a-python-module. @pytest.mark.filterwarnings("ignore:HTTPResponse.getheader()") +# Ignore as it doesn't affect the test run +@pytest.mark.filterwarnings("ignore::pytest.PytestUnhandledThreadExceptionWarning") @pytest.mark.parametrize( "example_path, version", [ (examples_path / "_dev", ""), (examples_path / "features", ""), - (examples_path / "demo", ""), - (examples_path / "_dev", "yaml_version"), - (examples_path / "features", "yaml_version"), + # (examples_path / "demo", ""), + # (examples_path / "_dev", "yaml_version"), + # (examples_path / "features", "yaml_version"), ], ) def test_dashboard(dash_duo, example_path, dashboard, version): diff --git a/vizro-core/tests/js/models/dashboard.test.js b/vizro-core/tests/js/models/dashboard.test.js index a4829e9b5..f11c82d48 100644 --- a/vizro-core/tests/js/models/dashboard.test.js +++ b/vizro-core/tests/js/models/dashboard.test.js @@ -15,7 +15,6 @@ describe("_collapse_nav_panel function", () => { false, { transform: "rotate(180deg)", - left: "12px", transition: "transform 0.35s ease-in-out", }, "Show Menu", diff --git a/vizro-core/tests/tests_utils/asserts.py b/vizro-core/tests/tests_utils/asserts.py index f477563c9..923d32e21 100644 --- a/vizro-core/tests/tests_utils/asserts.py +++ b/vizro-core/tests/tests_utils/asserts.py @@ -2,6 +2,7 @@ import dash.development import plotly +from pandas.testing import assert_frame_equal STRIP_ALL = object() @@ -42,3 +43,15 @@ def assert_component_equal(left, right, *, keys_to_strip=None): left = _strip_keys(_component_to_dict(left), keys_to_strip) right = _strip_keys(_component_to_dict(right), keys_to_strip) assert left == right + + +# Taken from https://stackoverflow.com/questions/38778266/assert-two-frames-are-not-equal. +def assert_frame_not_equal(*args, **kwargs): + try: + assert_frame_equal(*args, **kwargs) + except AssertionError: + # frames are not equal + pass + else: + # frames are equal + raise AssertionError diff --git a/vizro-core/tests/unit/vizro/actions/_callback_mapping/test_get_action_callback_mapping.py b/vizro-core/tests/unit/vizro/actions/_callback_mapping/test_get_action_callback_mapping.py index d22caefb2..9a728a6d3 100644 --- a/vizro-core/tests/unit/vizro/actions/_callback_mapping/test_get_action_callback_mapping.py +++ b/vizro-core/tests/unit/vizro/actions/_callback_mapping/test_get_action_callback_mapping.py @@ -152,26 +152,6 @@ def action_callback_outputs_expected(request): } -@pytest.fixture -def export_data_inputs_expected(): - return { - "filters": [ - dash.State("filter_continent_selector", "value"), - dash.State("filter_country_selector", "value"), - ], - "parameters": [], - "filter_interaction": [ - {"clickData": dash.State("scatter_chart", "clickData"), "modelID": dash.State("scatter_chart", "id")}, - { - "active_cell": dash.State("underlying_table_id", "active_cell"), - "derived_viewport_data": dash.State("underlying_table_id", "derived_viewport_data"), - "modelID": dash.State("vizro_table", "id"), - }, - ], - "theme_selector": [], - } - - @pytest.fixture def export_data_outputs_expected(request): return { @@ -195,20 +175,18 @@ class TestCallbackMapping: """Tests action callback mapping for predefined and custom actions.""" @pytest.mark.parametrize( - "action_id, callback_mapping_inputs_expected", + "action_id", [ - ("filter_action_filter_continent", "action_callback_inputs_expected"), - ("filter_interaction_action", "action_callback_inputs_expected"), - ("parameter_action_parameter_x", "action_callback_inputs_expected"), - ("on_page_load_action_action_test_page", "action_callback_inputs_expected"), - ("export_data_action", "export_data_inputs_expected"), + "filter_action_filter_continent", + "filter_interaction_action", + "parameter_action_parameter_x", + "on_page_load_action_action_test_page", + "export_data_action", ], ) - def test_action_callback_mapping_inputs(self, action_id, callback_mapping_inputs_expected, request): + def test_action_callback_mapping_inputs(self, action_id, action_callback_inputs_expected): result = _get_action_callback_mapping(action_id=action_id, argument="inputs") - - callback_mapping_inputs_expected = request.getfixturevalue(callback_mapping_inputs_expected) - assert result == callback_mapping_inputs_expected + assert result == action_callback_inputs_expected @pytest.mark.parametrize( "action_id, action_callback_outputs_expected", diff --git a/vizro-core/tests/unit/vizro/actions/conftest.py b/vizro-core/tests/unit/vizro/actions/conftest.py index 413fb36eb..2adf94ae8 100644 --- a/vizro-core/tests/unit/vizro/actions/conftest.py +++ b/vizro-core/tests/unit/vizro/actions/conftest.py @@ -1,3 +1,4 @@ +import pandas as pd import pytest import vizro.models as vm import vizro.plotly.express as px @@ -9,6 +10,15 @@ def gapminder_2007(gapminder): return gapminder.query("year == 2007") +@pytest.fixture +def gapminder_dynamic_first_n_last_n_function(gapminder): + return lambda first_n=None, last_n=None: ( + pd.concat([gapminder[:first_n], gapminder[-last_n:]]) + if last_n + else gapminder[:first_n] if first_n else gapminder + ) + + @pytest.fixture def box_params(): return {"x": "continent", "y": "lifeExp", "custom_data": ["continent"]} @@ -19,6 +29,11 @@ def box_chart(gapminder_2007, box_params): return px.box(gapminder_2007, **box_params).update_layout(margin_t=24) +@pytest.fixture +def box_chart_dynamic_data_frame(box_params): + return px.box("gapminder_dynamic_first_n_last_n", **box_params).update_layout(margin_t=24) + + @pytest.fixture def scatter_params(): return {"x": "gdpPercap", "y": "lifeExp"} @@ -29,6 +44,11 @@ def scatter_chart(gapminder_2007, scatter_params): return px.scatter(gapminder_2007, **scatter_params).update_layout(margin_t=24) +@pytest.fixture +def scatter_chart_dynamic_data_frame(scatter_params): + return px.scatter("gapminder_dynamic_first_n_last_n", **scatter_params).update_layout(margin_t=24) + + @pytest.fixture def target_scatter_filtered_continent(request, gapminder_2007, scatter_params): continent = request.param @@ -58,6 +78,21 @@ def managers_one_page_two_graphs_one_button(box_chart, scatter_chart): Vizro._pre_build() +@pytest.fixture +def managers_one_page_two_graphs_with_dynamic_data(box_chart_dynamic_data_frame, scatter_chart_dynamic_data_frame): + """Instantiates a simple model_manager and data_manager with a page, two graph models and the button component.""" + vm.Page( + id="test_page", + title="My first dashboard", + components=[ + vm.Graph(id="box_chart", figure=box_chart_dynamic_data_frame), + vm.Graph(id="scatter_chart", figure=scatter_chart_dynamic_data_frame), + vm.Button(id="button"), + ], + ) + Vizro._pre_build() + + @pytest.fixture def managers_one_page_two_graphs_one_table_one_aggrid_one_button( box_chart, scatter_chart, dash_data_table_with_id, ag_grid_with_id diff --git a/vizro-core/tests/unit/vizro/actions/test_export_data_action.py b/vizro-core/tests/unit/vizro/actions/test_export_data_action.py index f3a1f5585..5f4fbee8e 100644 --- a/vizro-core/tests/unit/vizro/actions/test_export_data_action.py +++ b/vizro-core/tests/unit/vizro/actions/test_export_data_action.py @@ -5,11 +5,11 @@ from vizro import Vizro from vizro.actions import export_data, filter_interaction from vizro.actions._actions_utils import CallbackTriggerDict -from vizro.managers import model_manager +from vizro.managers import data_manager, model_manager @pytest.fixture -def target_scatter_filter_and_filter_interaction(request, gapminder_2007): +def target_data_filter_and_filter_interaction(request, gapminder_2007): pop_filter, continent_filter_interaction, country_table_filter_interaction = request.param data = gapminder_2007 if pop_filter: @@ -22,7 +22,18 @@ def target_scatter_filter_and_filter_interaction(request, gapminder_2007): @pytest.fixture -def target_box_filtered_pop(request, gapminder_2007): +def target_data_filter_and_parameter(request, gapminder): + pop_filter, first_n_parameter = request.param + data = gapminder + if first_n_parameter: + data = data.head(first_n_parameter) + if pop_filter: + data = data[data["pop"].between(pop_filter[0], pop_filter[1], inclusive="both")] + return data + + +@pytest.fixture +def target_data_filtered_pop(request, gapminder_2007): pop_filter = request.param data = gapminder_2007 if pop_filter: @@ -94,6 +105,7 @@ def ctx_export_data(request): if pop_filter else [] ), + "parameters": [], "filter_interaction": args_grouping_filter_interaction, } }, @@ -107,6 +119,48 @@ def ctx_export_data(request): return context_value +@pytest.fixture +def ctx_export_data_filter_and_parameter(request): + """Mock dash.ctx that represents filters and parameter applied.""" + targets, pop_filter, first_n_parameter = request.param + mock_ctx = { + "args_grouping": { + "external": { + "filters": ( + [ + CallbackTriggerDict( + id="pop_filter", property="value", value=pop_filter, str_id="pop_filter", triggered=False + ) + ] + if pop_filter + else [] + ), + "parameters": ( + [ + CallbackTriggerDict( + id="first_n_parameter", + property="value", + value=first_n_parameter, + str_id="first_n_parameter", + triggered=False, + ) + ] + if first_n_parameter + else [] + ), + "filter_interaction": [], + } + }, + "outputs_list": [ + {"id": {"action_id": "test_action", "target_id": target, "type": "download_dataframe"}, "property": "data"} + for target in targets + ], + } + + context_value.set(AttributeDict(**mock_ctx)) + return context_value + + class TestExportData: @pytest.mark.usefixtures("managers_one_page_without_graphs_one_button") @pytest.mark.parametrize("ctx_export_data", [([[], None, None, None])], indirect=True) @@ -237,7 +291,7 @@ def test_invalid_target(self, ctx_export_data): @pytest.mark.usefixtures("managers_one_page_two_graphs_one_button") @pytest.mark.parametrize( - "ctx_export_data, target_scatter_filter_and_filter_interaction, target_box_filtered_pop", + "ctx_export_data, target_data_filter_and_filter_interaction, target_data_filtered_pop", [ ( [["scatter_chart", "box_chart"], [10**6, 10**7], None, None], @@ -254,7 +308,7 @@ def test_invalid_target(self, ctx_export_data): indirect=True, ) def test_multiple_targets_with_filter_and_filter_interaction( - self, ctx_export_data, target_scatter_filter_and_filter_interaction, target_box_filtered_pop + self, ctx_export_data, target_data_filter_and_filter_interaction, target_data_filtered_pop ): # Creating and adding a Filter object to the existing Page pop_filter = vm.Filter(column="pop", selector=vm.RangeSlider(id="pop_filter")) @@ -277,13 +331,13 @@ def test_multiple_targets_with_filter_and_filter_interaction( expected = { "download_dataframe_scatter_chart": { "filename": "scatter_chart.csv", - "content": target_scatter_filter_and_filter_interaction.to_csv(index=False), + "content": target_data_filter_and_filter_interaction.to_csv(index=False), "type": None, "base64": False, }, "download_dataframe_box_chart": { "filename": "box_chart.csv", - "content": target_box_filtered_pop.to_csv(index=False), + "content": target_data_filtered_pop.to_csv(index=False), "type": None, "base64": False, }, @@ -293,7 +347,7 @@ def test_multiple_targets_with_filter_and_filter_interaction( @pytest.mark.usefixtures("managers_one_page_two_graphs_one_table_one_aggrid_one_button") @pytest.mark.parametrize( - "ctx_export_data, target_scatter_filter_and_filter_interaction, target_box_filtered_pop", + "ctx_export_data, target_data_filter_and_filter_interaction, target_data_filtered_pop", [ ( [["scatter_chart", "box_chart"], [10**6, 10**7], None, "Algeria"], @@ -310,7 +364,7 @@ def test_multiple_targets_with_filter_and_filter_interaction( indirect=True, ) def test_multiple_targets_with_filter_and_filter_interaction_and_table( - self, ctx_export_data, target_scatter_filter_and_filter_interaction, target_box_filtered_pop + self, ctx_export_data, target_data_filter_and_filter_interaction, target_data_filtered_pop ): # Creating and adding a Filter object to the existing Page pop_filter = vm.Filter(column="pop", selector=vm.RangeSlider(id="pop_filter")) @@ -337,13 +391,79 @@ def test_multiple_targets_with_filter_and_filter_interaction_and_table( expected = { "download_dataframe_scatter_chart": { "filename": "scatter_chart.csv", - "content": target_scatter_filter_and_filter_interaction.to_csv(index=False), + "content": target_data_filter_and_filter_interaction.to_csv(index=False), + "type": None, + "base64": False, + }, + "download_dataframe_box_chart": { + "filename": "box_chart.csv", + "content": target_data_filtered_pop.to_csv(index=False), + "type": None, + "base64": False, + }, + } + + assert result == expected + + @pytest.mark.usefixtures("managers_one_page_two_graphs_with_dynamic_data") + @pytest.mark.parametrize( + "ctx_export_data_filter_and_parameter, target_data_filter_and_parameter", + [ + ( + [["scatter_chart", "box_chart"], [10**6, 10**7], None], + [[10**6, 10**7], None], + ), + ( + [["scatter_chart", "box_chart"], None, 50], + [None, 50], + ), + ( + [["scatter_chart", "box_chart"], [10**6, 10**7], 50], + [[10**6, 10**7], 50], + ), + ], + indirect=True, + ) + def test_multiple_targets_with_filter_and_data_frame_parameter( + self, + ctx_export_data_filter_and_parameter, + target_data_filter_and_parameter, + gapminder_dynamic_first_n_last_n_function, + ): + # Adding dynamic data_frame to data_manager + data_manager["gapminder_dynamic_first_n_last_n"] = gapminder_dynamic_first_n_last_n_function + + # Creating and adding a Filter object to the existing Page + pop_filter = vm.Filter(column="pop", selector=vm.RangeSlider(id="pop_filter")) + model_manager["test_page"].controls = [pop_filter] + # Adds a default _filter Action to the filter selector objects + pop_filter.pre_build() + + # Creating and adding a Parameter object (data_frame function argument parametrizing) to the existing Page + first_n_parameter = vm.Parameter( + targets=["scatter_chart.data_frame.first_n", "box_chart.data_frame.first_n"], + selector=vm.Slider(id="first_n_parameter", min=1, max=10, step=1, value=1), + ) + model_manager["test_page"].controls.append(first_n_parameter) + first_n_parameter.pre_build() + + # Add export_data action to relevant component + model_manager["button"].actions = [ + vm.Action(id="test_action", function=export_data(targets=["scatter_chart", "box_chart"])) + ] + + # Run action by picking the above added export_data action function and executing it with () + result = model_manager["test_action"].function() + expected = { + "download_dataframe_scatter_chart": { + "filename": "scatter_chart.csv", + "content": target_data_filter_and_parameter.to_csv(index=False), "type": None, "base64": False, }, "download_dataframe_box_chart": { "filename": "box_chart.csv", - "content": target_box_filtered_pop.to_csv(index=False), + "content": target_data_filter_and_parameter.to_csv(index=False), "type": None, "base64": False, }, diff --git a/vizro-core/tests/unit/vizro/actions/test_parameter_action.py b/vizro-core/tests/unit/vizro/actions/test_parameter_action.py index 9015a382a..d4677e865 100644 --- a/vizro-core/tests/unit/vizro/actions/test_parameter_action.py +++ b/vizro-core/tests/unit/vizro/actions/test_parameter_action.py @@ -5,7 +5,7 @@ from dash._utils import AttributeDict from vizro._constants import PARAMETER_ACTION_PREFIX from vizro.actions._actions_utils import CallbackTriggerDict -from vizro.managers import model_manager +from vizro.managers import data_manager, model_manager @pytest.fixture @@ -37,6 +37,24 @@ def target_scatter_parameter_y_and_x(request, gapminder_2007, scatter_params): return px.scatter(gapminder_2007, **scatter_params).update_layout(margin_t=24) +@pytest.fixture +def target_scatter_parameter_data_frame_first_n_last_n( + request, gapminder_dynamic_first_n_last_n_function, scatter_params +): + first_n_last_n_args = request.param + return px.scatter(gapminder_dynamic_first_n_last_n_function(**first_n_last_n_args), **scatter_params).update_layout( + margin_t=24 + ) + + +@pytest.fixture +def target_box_parameter_data_frame_first_n_last_n(request, gapminder_dynamic_first_n_last_n_function, box_params): + first_n_last_n_args = request.param + return px.box(gapminder_dynamic_first_n_last_n_function(**first_n_last_n_args), **box_params).update_layout( + margin_t=24 + ) + + @pytest.fixture def target_box_parameter_y_and_x(request, gapminder_2007, box_params): y, x = request.param @@ -148,8 +166,46 @@ def ctx_parameter_y_and_x(request): return context_value -@pytest.mark.usefixtures("managers_one_page_two_graphs_one_button") +@pytest.fixture +def ctx_parameter_data_frame_argument(request): + """Mock dash.ctx that represents parameter applied.""" + targets, first_n_last_n_args = request.param + + parameters = [ + CallbackTriggerDict( + id="first_n_parameter", + property="value", + value=first_n_last_n_args["first_n"], + str_id="first_n_parameter", + triggered=False, + ) + ] + + if last_n := first_n_last_n_args.get("last_n"): + parameters.append( + CallbackTriggerDict( + id="last_n_parameter", + property="value", + value=last_n, + str_id="last_n_parameter", + triggered=False, + ) + ) + + mock_ctx = { + "args_grouping": {"external": {"filters": [], "filter_interaction": [], "parameters": parameters}}, + "outputs_list": [ + {"id": {"action_id": "test_action", "target_id": target, "type": "download_dataframe"}, "property": "data"} + for target in targets + ], + } + + context_value.set(AttributeDict(**mock_ctx)) + return context_value + + class TestParameter: + @pytest.mark.usefixtures("managers_one_page_two_graphs_one_button") @pytest.mark.parametrize( "ctx_parameter_y, target_scatter_parameter_y", [("pop", "pop"), ("gdpPercap", "gdpPercap"), ("NONE", None)], @@ -173,6 +229,7 @@ def test_one_parameter_one_target(self, ctx_parameter_y, target_scatter_paramete assert result == expected + @pytest.mark.usefixtures("managers_one_page_two_graphs_one_button") @pytest.mark.parametrize( "ctx_parameter_hover_data, target_scatter_parameter_hover_data", [ @@ -205,6 +262,7 @@ def test_one_parameter_one_target_NONE_list(self, ctx_parameter_hover_data, targ assert result == expected + @pytest.mark.usefixtures("managers_one_page_two_graphs_one_button") @pytest.mark.parametrize( "ctx_parameter_y, target_scatter_parameter_y, target_box_parameter_y", [("pop", "pop", "pop"), ("gdpPercap", "gdpPercap", "gdpPercap")], @@ -228,6 +286,7 @@ def test_one_parameter_multiple_targets(self, ctx_parameter_y, target_scatter_pa assert result == expected + @pytest.mark.usefixtures("managers_one_page_two_graphs_one_button") @pytest.mark.parametrize( "ctx_parameter_y_and_x, target_scatter_parameter_y_and_x", [(["pop", "continent"], ["pop", "continent"]), (["gdpPercap", "country"], ["gdpPercap", "country"])], @@ -257,6 +316,7 @@ def test_multiple_parameters_one_target(self, ctx_parameter_y_and_x, target_scat assert result == expected + @pytest.mark.usefixtures("managers_one_page_two_graphs_one_button") @pytest.mark.parametrize( "ctx_parameter_y_and_x, target_scatter_parameter_y_and_x, target_box_parameter_y_and_x", [ @@ -291,6 +351,7 @@ def test_multiple_parameters_multiple_targets( assert result == expected + @pytest.mark.usefixtures("managers_one_page_two_graphs_one_button") @pytest.mark.parametrize( "ctx_parameter_y_and_x, target_scatter_parameter_y_and_x, target_box_parameter_y_and_x", [ @@ -329,3 +390,110 @@ def test_one_parameter_per_target_multiple_attributes( expected = {"box_chart": target_box_parameter_y_and_x} assert result == expected + + @pytest.mark.usefixtures("managers_one_page_two_graphs_with_dynamic_data") + @pytest.mark.parametrize( + "ctx_parameter_data_frame_argument, " "target_scatter_parameter_data_frame_first_n_last_n", + [ + ((["scatter_chart"], {"first_n": 50}), {"first_n": 50}), + ((["scatter_chart"], {"first_n": 50, "last_n": 50}), {"first_n": 50, "last_n": 50}), + ], + indirect=[ + "ctx_parameter_data_frame_argument", + "target_scatter_parameter_data_frame_first_n_last_n", + ], + ) + def test_data_frame_parameters_one_target( + self, + ctx_parameter_data_frame_argument, + target_scatter_parameter_data_frame_first_n_last_n, + gapminder_dynamic_first_n_last_n_function, + ): + data_manager["gapminder_dynamic_first_n_last_n"] = gapminder_dynamic_first_n_last_n_function + + # Creating and adding a Parameter object (data_frame function argument parametrizing) to the existing Page + first_n_parameter = vm.Parameter( + id="test_data_frame_parameter", + targets=["scatter_chart.data_frame.first_n"], + selector=vm.Slider(id="first_n_parameter", min=1, max=10, step=1), + ) + model_manager["test_page"].controls.append(first_n_parameter) + first_n_parameter.pre_build() + + # This parameter will affect results only if included in the ctx_parameter_data_frame_argument: + last_n_parameter = vm.Parameter( + id="test_data_frame_parameter_last_n", + targets=["scatter_chart.data_frame.last_n"], + selector=vm.Slider(id="last_n_parameter", min=1, max=10, step=1), + ) + model_manager["test_page"].controls.append(last_n_parameter) + last_n_parameter.pre_build() + + # Run action by picking the above added action function and executing it with () + result = model_manager[f"{PARAMETER_ACTION_PREFIX}_test_data_frame_parameter"].function() + + expected = { + "scatter_chart": target_scatter_parameter_data_frame_first_n_last_n, + } + + assert result == expected + + @pytest.mark.usefixtures("managers_one_page_two_graphs_with_dynamic_data") + @pytest.mark.parametrize( + "ctx_parameter_data_frame_argument, " + "target_scatter_parameter_data_frame_first_n_last_n, " + "target_box_parameter_data_frame_first_n_last_n", + [ + ( + (["scatter_chart", "box_chart"], {"first_n": 50}), + {"first_n": 50}, + {"first_n": 50}, + ), + ( + (["scatter_chart", "box_chart"], {"first_n": 50, "last_n": 50}), + {"first_n": 50, "last_n": 50}, + {"first_n": 50, "last_n": 50}, + ), + ], + indirect=[ + "ctx_parameter_data_frame_argument", + "target_scatter_parameter_data_frame_first_n_last_n", + "target_box_parameter_data_frame_first_n_last_n", + ], + ) + def test_data_frame_parameters_multiple_targets( + self, + ctx_parameter_data_frame_argument, + target_scatter_parameter_data_frame_first_n_last_n, + target_box_parameter_data_frame_first_n_last_n, + gapminder_dynamic_first_n_last_n_function, + ): + data_manager["gapminder_dynamic_first_n_last_n"] = gapminder_dynamic_first_n_last_n_function + + # Creating and adding a Parameter object (data_frame function argument parametrizing) to the existing Page + first_n_parameter = vm.Parameter( + id="test_data_frame_parameter", + targets=["scatter_chart.data_frame.first_n", "box_chart.data_frame.first_n"], + selector=vm.Slider(id="first_n_parameter", min=1, max=10, step=1), + ) + model_manager["test_page"].controls.append(first_n_parameter) + first_n_parameter.pre_build() + + # This parameter will affect results only if included in the ctx_parameter_data_frame_argument: + last_n_parameter = vm.Parameter( + id="test_data_frame_parameter_last_n", + targets=["scatter_chart.data_frame.last_n", "box_chart.data_frame.last_n"], + selector=vm.Slider(id="last_n_parameter", min=1, max=10, step=1), + ) + model_manager["test_page"].controls.append(last_n_parameter) + last_n_parameter.pre_build() + + # Run action by picking the above added action function and executing it with () + result = model_manager[f"{PARAMETER_ACTION_PREFIX}_test_data_frame_parameter"].function() + + expected = { + "scatter_chart": target_scatter_parameter_data_frame_first_n_last_n, + "box_chart": target_box_parameter_data_frame_first_n_last_n, + } + + assert result == expected diff --git a/vizro-core/tests/unit/vizro/managers/test_data_manager.py b/vizro-core/tests/unit/vizro/managers/test_data_manager.py index 7bffb9f13..967bb9db3 100644 --- a/vizro-core/tests/unit/vizro/managers/test_data_manager.py +++ b/vizro-core/tests/unit/vizro/managers/test_data_manager.py @@ -1,62 +1,451 @@ """Unit tests for vizro.managers.data_manager.""" +from contextlib import suppress +from functools import partial + +import numpy as np import pandas as pd import pytest -from vizro.managers._data_manager import DataManager - - -class TestDataManager: - def setup_method(self): - self.data_manager = DataManager() - self.data = pd.DataFrame({"col1": [1, 2, 3], "col2": [4, 5, 6]}) - - def test_add_dataframe(self): - dataset_name = "test_dataset" - component_id = "component_id_a" - self.data_manager[dataset_name] = self.data - self.data_manager._add_component(component_id, dataset_name) - assert self.data_manager._get_component_data(component_id).equals(self.data) - - def test_add_lazy_dataframe(self): - dataset_name = "test_lazy_dataset" - component_id = "component_id_b" - - def lazy_data(): - return self.data - - self.data_manager[dataset_name] = lazy_data - self.data_manager._add_component(component_id, dataset_name) - assert self.data_manager._get_component_data(component_id).equals(lazy_data()) - - def test_add_existing_dataset(self): - dataset_name = "existing_dataset" - self.data_manager[dataset_name] = self.data - with pytest.raises(ValueError): - self.data_manager[dataset_name] = self.data - - def test_add_invalid_dataset(self): - dataset_name = "invalid_dataset" - invalid_data = "not_a_dataframe" - with pytest.raises(TypeError): - self.data_manager[dataset_name] = invalid_data - - def test_add_component_to_nonexistent_dataset(self): - component_id = "test_component" - dataset_name = "nonexistent_dataset" - with pytest.raises(KeyError): - self.data_manager._add_component(component_id, dataset_name) - - def test_add_existing_component(self): - component_id = "existing_component" - dataset_name = "test_dataset" - self.data_manager[dataset_name] = self.data - self.data_manager._add_component(component_id, dataset_name) - with pytest.raises(ValueError): - self.data_manager._add_component(component_id, dataset_name) - - def test_get_component_data_nonexistent(self): - dataset_name = "test_dataset" - nonexistent_component = "nonexistent_component" - self.data_manager[dataset_name] = self.data - with pytest.raises(KeyError): - self.data_manager._get_component_data(nonexistent_component) +from asserts import assert_frame_not_equal +from flask_caching import Cache +from freezegun import freeze_time +from pandas.testing import assert_frame_equal +from vizro import Vizro +from vizro.managers import data_manager + + +# Fixture that freezes the time so that tests involving time.sleep can run quickly. Instead of time.sleep, +# tests should use freezer.tick() to advance time. This is very similar to the fixture in the pytest-freezegun package. +# This makes it possible to test with realistic timeouts that flask-caching can handle well. Otherwise we +# test with very low timeouts and time.sleep(1), and this is flaky since flask-caching is not designed to +# handle such small intervals (e.g. it rounds times to the nearest second). +# We use tick=True so that time continues to pass between directly consecutive calls to a load function. This makes the +# behavior in tests as close as possible to real world. +@pytest.fixture +def freezer(): + with freeze_time(tick=True) as frozen_time: + yield frozen_time + + +@pytest.fixture(autouse=True) +def clear_cache(): + yield + # Vizro._reset doesn't empty the cache, so any tests which have something other than NullCache must clear it + # after running. Suppress AttributeError: 'Cache' object has no attribute 'app' that occurs when + # data_manager._cache_has_app is False. + with suppress(AttributeError): + data_manager.cache.clear() + + +def make_fixed_data(): + return pd.DataFrame([1, 2, 3]) + + +class TestLoad: + def test_static(self): + data = make_fixed_data() + data_manager["data"] = data + loaded_data = data_manager["data"].load() + assert_frame_equal(loaded_data, data) + # Make sure loaded_data is a copy rather than the same object. + assert loaded_data is not data + + def test_dynamic(self): + data = make_fixed_data + data_manager["data"] = data + loaded_data = data_manager["data"].load() + assert_frame_equal(loaded_data, data()) + # Make sure loaded_data is a copy rather than the same object. + assert loaded_data is not data() + + def test_dynamic_lambda(self): + data = lambda: make_fixed_data() # noqa: E731 + data_manager["data"] = data + loaded_data = data_manager["data"].load() + assert_frame_equal(loaded_data, data()) + # Make sure loaded_data is a copy rather than the same object. + assert loaded_data is not data() + + +class TestInvalid: + def test_static_data_does_not_support_timeout(self): + data = make_fixed_data() + data_manager["data"] = data + with pytest.raises( + AttributeError, match="Static data that is a pandas.DataFrame itself does not support timeout" + ): + data_manager["data"].timeout = 10 + + def test_setitem_invalid_type(self): + with pytest.raises( + TypeError, match="Data source data must be a pandas DataFrame or function that returns a pandas DataFrame." + ): + data_manager["data"] = pd.Series([1, 2, 3]) + + def test_does_not_exist(self): + with pytest.raises(KeyError, match="Data source data does not exist."): + data_manager["data"] + + +def make_random_data(): + return pd.DataFrame(np.random.default_rng().random(3)) + + +def make_random_data_with_args(label="x"): + return make_random_data().assign(label=label) + + +# This is important to test since it's like how kedro datasets work. +class RandomData: + # This cannot be @staticmethod since we want to test it as a bound method. + def load(self): + return make_random_data() + + +class RandomDataWithArgs: + # This cannot be @staticmethod since we want to test it as a bound method. + def load(self, label="x"): + return make_random_data_with_args(label) + + +make_random_data_lambda = lambda: make_random_data() # noqa: E731 + +make_random_data_partial = partial(make_random_data) + +make_random_data_with_args_lambda = lambda label="x": make_random_data_with_args(label) # noqa: E731 + +make_random_data_with_args_partial = partial(make_random_data_with_args) + + +# We test the function and bound method cases but not the unbound methods RandomData.load and RandomDataWithArgs.load +# which are not important. +@pytest.mark.parametrize( + "data_callable", + [ + make_random_data, + make_random_data_with_args, + RandomData().load, + RandomDataWithArgs().load, + make_random_data_lambda, + make_random_data_with_args_lambda, + make_random_data_partial, + make_random_data_with_args_partial, + ], +) +class TestCacheNotOperational: + def test_null_cache_no_app(self, data_callable): + # No app at all, so data_manager._cache_has_app is False. + data_manager["data"] = data_callable + loaded_data_1 = data_manager["data"].load() + loaded_data_2 = data_manager["data"].load() + assert_frame_not_equal(loaded_data_1, loaded_data_2) + + def test_null_cache_with_app(self, data_callable): + # App exists but cache is NullCache so does not do anything. + data_manager["data"] = data_callable + Vizro() + loaded_data_1 = data_manager["data"].load() + loaded_data_2 = data_manager["data"].load() + assert_frame_not_equal(loaded_data_1, loaded_data_2) + + def test_cache_no_app(self, data_callable): + # App exists and has a real cache but data_manager.cache is set too late so app is not attached to cache. + data_manager["data"] = data_callable + Vizro() + data_manager.cache = Cache(config={"CACHE_TYPE": "SimpleCache"}) + + with pytest.warns(UserWarning, match="Cache does not have Vizro app attached and so is not operational."): + loaded_data_1 = data_manager["data"].load() + loaded_data_2 = data_manager["data"].load() + assert_frame_not_equal(loaded_data_1, loaded_data_2) + + +@pytest.fixture +def simple_cache(): + # We don't need the Flask request context to run tests. (flask-caching tests for memoize use + # app.test_request_context() but look like they don't actually need to, since only flask_caching.Cache.cached + # requires the request context.) + # We do need a Flask app to be attached for the cache to be operational though, hence the call Vizro(). + data_manager.cache = Cache(config={"CACHE_TYPE": "SimpleCache"}) + Vizro() + yield + + +# We test the function and bound method cases but not the unbound method RandomData.load which is not important. +@pytest.mark.parametrize( + "data_callable", + [ + make_random_data, + RandomData().load, + make_random_data_lambda, + make_random_data_partial, + ], +) +class TestCache: + def test_default_timeout(self, data_callable, simple_cache, freezer): + data_manager["data"] = data_callable + + loaded_data_1 = data_manager["data"].load() + loaded_data_2 = data_manager["data"].load() + # Default timeout is 300, so wait for longer than that. + freezer.tick(300 + 50) + loaded_data_3 = data_manager["data"].load() + loaded_data_4 = data_manager["data"].load() + + # Cache has expired between loaded_data_2 and loaded_data_3 only. + assert_frame_equal(loaded_data_1, loaded_data_2) + assert_frame_equal(loaded_data_3, loaded_data_4) + assert_frame_not_equal(loaded_data_2, loaded_data_3) + + def test_change_non_default_timeout(self, data_callable, freezer): + data_manager.cache = Cache(config={"CACHE_TYPE": "SimpleCache", "CACHE_DEFAULT_TIMEOUT": 100}) + Vizro() + data_manager["data"] = data_callable + + loaded_data_1 = data_manager["data"].load() + loaded_data_2 = data_manager["data"].load() + freezer.tick(100 + 50) + loaded_data_3 = data_manager["data"].load() + loaded_data_4 = data_manager["data"].load() + + # Cache has expired between loaded_data_2 and loaded_data_3 only. + assert_frame_equal(loaded_data_1, loaded_data_2) + assert_frame_equal(loaded_data_3, loaded_data_4) + assert_frame_not_equal(loaded_data_2, loaded_data_3) + + def test_change_individual_timeout(self, data_callable, simple_cache, freezer): + data_manager["data"] = data_callable + data_manager["data"].timeout = 100 + + loaded_data_1 = data_manager["data"].load() + loaded_data_2 = data_manager["data"].load() + freezer.tick(100 + 50) + loaded_data_3 = data_manager["data"].load() + loaded_data_4 = data_manager["data"].load() + + # Cache has expired between loaded_data_2 and loaded_data_3 only. + assert_frame_equal(loaded_data_1, loaded_data_2) + assert_frame_equal(loaded_data_3, loaded_data_4) + assert_frame_not_equal(loaded_data_2, loaded_data_3) + + +# We test the function and bound method cases but not the unbound method RandomDataWithArgs.load which is not important. +@pytest.mark.usefixtures("simple_cache") +@pytest.mark.parametrize( + "data_callable", + [ + make_random_data_with_args, + RandomDataWithArgs().load, + make_random_data_with_args_lambda, + make_random_data_with_args_partial, + ], +) +class TestCacheWithArguments: + def test_default_timeout(self, data_callable, freezer): + # Analogous to TestCache.test_default_timeout + data_manager["data"] = data_callable + + loaded_data_x_1 = data_manager["data"].load("x") + loaded_data_x_2 = data_manager["data"].load("x") + loaded_data_y_1 = data_manager["data"].load("y") + loaded_data_y_2 = data_manager["data"].load("y") + # Default timeout is 300, so wait for longer than that. + freezer.tick(300 + 50) + loaded_data_x_3 = data_manager["data"].load("x") + loaded_data_x_4 = data_manager["data"].load("x") + loaded_data_y_3 = data_manager["data"].load("y") + loaded_data_y_4 = data_manager["data"].load("y") + + # For both x and y, cache has expired between loaded_data_2 and loaded_data_3 only. + assert_frame_equal(loaded_data_x_1, loaded_data_x_2) + assert_frame_equal(loaded_data_x_3, loaded_data_x_4) + assert_frame_not_equal(loaded_data_x_2, loaded_data_x_3) + + assert_frame_equal(loaded_data_y_1, loaded_data_y_2) + assert_frame_equal(loaded_data_y_3, loaded_data_y_4) + assert_frame_not_equal(loaded_data_y_2, loaded_data_y_3) + + assert_frame_not_equal(loaded_data_x_1, loaded_data_y_1) + assert_frame_not_equal(loaded_data_x_3, loaded_data_y_3) + + def test_change_individual_timeout(self, data_callable, freezer): + # Analogous to TestCache.test_change_individual_timeout. + data_manager["data"] = data_callable + data_manager["data"].timeout = 100 + + loaded_data_x_1 = data_manager["data"].load("x") + loaded_data_x_2 = data_manager["data"].load("x") + loaded_data_y_1 = data_manager["data"].load("y") + loaded_data_y_2 = data_manager["data"].load("y") + freezer.tick(100 + 50) + loaded_data_x_3 = data_manager["data"].load("x") + loaded_data_x_4 = data_manager["data"].load("x") + loaded_data_y_3 = data_manager["data"].load("y") + loaded_data_y_4 = data_manager["data"].load("y") + + # For both x and y, cache has expired between loaded_data_2 and loaded_data_3 only. + assert_frame_equal(loaded_data_x_1, loaded_data_x_2) + assert_frame_equal(loaded_data_x_3, loaded_data_x_4) + assert_frame_not_equal(loaded_data_x_2, loaded_data_x_3) + + assert_frame_equal(loaded_data_y_1, loaded_data_y_2) + assert_frame_equal(loaded_data_y_3, loaded_data_y_4) + assert_frame_not_equal(loaded_data_y_2, loaded_data_y_3) + + assert_frame_not_equal(loaded_data_x_1, loaded_data_y_1) + assert_frame_not_equal(loaded_data_x_3, loaded_data_y_3) + + def test_timeout_expires_all(self, data_callable, freezer): + # When the cache for one set of memoized arguments expires, the cache for the whole data source expires, even + # for other values of memoized arguments. + # This behavior is not particularly desirable (in fact it's maybe a bit annoying); the test is here just + # to document the current behavior. It's not easy to change this behavior within flask-caching. + # Remember the default timeout is 300s. + # Loading sequence of data sources is as follows: + # t=0: load x_1 + # t=200: load x_2 y_1 -> x cache has not expired + # t=400: load x_3 y_2 -> x cache has expired. y cache might be expected to not expire but also has. + # t=600: load y_3 + data_manager["data"] = data_callable + + loaded_data_x_1 = data_manager["data"].load("x") + freezer.tick(150 + 50) + loaded_data_x_2 = data_manager["data"].load("x") + loaded_data_y_1 = data_manager["data"].load("y") + freezer.tick(150 + 50) + loaded_data_x_3 = data_manager["data"].load("x") + loaded_data_y_2 = data_manager["data"].load("y") + freezer.tick(150 + 50) + loaded_data_y_3 = data_manager["data"].load("y") + + # These are as you would expect. + assert_frame_equal(loaded_data_x_1, loaded_data_x_2) + assert_frame_not_equal(loaded_data_x_2, loaded_data_x_3) + + # These you might expect to be the other way round: + # assert_frame_equal(loaded_data_y_1, loaded_data_y_2) + # assert_frame_not_equal(loaded_data_y_2, loaded_data_y_3) + assert_frame_not_equal(loaded_data_y_1, loaded_data_y_2) + assert_frame_equal(loaded_data_y_2, loaded_data_y_3) + + def test_named_and_default_args(self, data_callable): + data_manager["data"] = data_callable + + loaded_data_x_1 = data_manager["data"].load(label="x") + loaded_data_x_2 = data_manager["data"].load() + loaded_data_x_3 = data_manager["data"].load("x") + + assert_frame_equal(loaded_data_x_1, loaded_data_x_2) + assert_frame_equal(loaded_data_x_2, loaded_data_x_3) + + +class TestCacheIndependence: + # Test both data callable with and without args in one test. The ones which don't take args are just passed an + # empty dictionary so it's like doing data_manager["data_x"].load() with no arguments. + @pytest.mark.parametrize( + "data_callable, kwargs", + [ + (make_random_data, {}), + (RandomData().load, {}), + (make_random_data_lambda, {}), + (make_random_data_partial, {}), + (make_random_data_with_args, {"label": "z"}), + (RandomDataWithArgs().load, {"label": "z"}), + (make_random_data_with_args_lambda, {"label": "z"}), + (make_random_data_with_args_partial, {"label": "z"}), + ], + ) + def test_shared_dynamic_data_callable_no_timeout(self, data_callable, kwargs, simple_cache): + # Two data sources that share the same function or bound method are independent when neither times out. + # It doesn't really matter if this test passes; it's mainly here just to document the current behavior. The use + # cases for actually wanting to do this seem limited. + data_manager["data_x"] = data_callable + data_manager["data_y"] = data_callable + + loaded_data_x_1 = data_manager["data_x"].load(**kwargs) + loaded_data_y_1 = data_manager["data_y"].load(**kwargs) + loaded_data_x_2 = data_manager["data_x"].load(**kwargs) + loaded_data_y_2 = data_manager["data_y"].load(**kwargs) + + assert_frame_equal(loaded_data_x_1, loaded_data_x_2) + assert_frame_equal(loaded_data_y_1, loaded_data_y_2) + assert_frame_not_equal(loaded_data_x_1, loaded_data_y_1) + + # Test both data callable with and without args in one test. The ones which don't take args are just passed an + # empty dictionary so it's like doing data_manager["data_x"].load() with no arguments. + @pytest.mark.parametrize( + "data_callable, kwargs", + [ + (make_random_data, {}), + (RandomData().load, {}), + (make_random_data_lambda, {}), + (make_random_data_partial, {}), + (make_random_data_with_args, {"label": "z"}), + (RandomDataWithArgs().load, {"label": "z"}), + (make_random_data_with_args_lambda, {"label": "z"}), + (make_random_data_with_args_partial, {"label": "z"}), + ], + ) + def test_shared_dynamic_data_callable_with_timeout(self, data_callable, kwargs, simple_cache, freezer): + # Two data sources that share the same function or bound method are independent when only one times out. + # It doesn't really matter if this test passes; it's mainly here just to document the current behavior. The use + # cases for actually wanting to do this seem limited. + data_manager["data_x"] = data_callable + data_manager["data_y"] = data_callable + data_manager["data_y"].timeout = 100 + + loaded_data_x_1 = data_manager["data_x"].load(**kwargs) + loaded_data_y_1 = data_manager["data_y"].load(**kwargs) + freezer.tick(100 + 50) + loaded_data_x_2 = data_manager["data_x"].load(**kwargs) + loaded_data_y_2 = data_manager["data_y"].load(**kwargs) + + # Cache has expired for data_y but not data_x. + assert_frame_equal(loaded_data_x_1, loaded_data_x_2) + assert_frame_not_equal(loaded_data_y_1, loaded_data_y_2) + + @pytest.mark.parametrize( + "random_data_cls, kwargs", + [ + (RandomData, {}), + (RandomDataWithArgs, {"label": "z"}), + ], + ) + def test_independent_dynamic_data_callable_no_timeout(self, simple_cache, random_data_cls, kwargs): + # Two data sources use same method but have *different* bound instances are independent when neither times out. + # This is the same as test_shared_dynamic_data_callable_no_timeout but it *does* matter that this test passes. + data_manager["data_x"] = random_data_cls().load + data_manager["data_y"] = random_data_cls().load + + loaded_data_x_1 = data_manager["data_x"].load(**kwargs) + loaded_data_y_1 = data_manager["data_y"].load(**kwargs) + loaded_data_x_2 = data_manager["data_x"].load(**kwargs) + loaded_data_y_2 = data_manager["data_y"].load(**kwargs) + + assert_frame_equal(loaded_data_x_1, loaded_data_x_2) + assert_frame_equal(loaded_data_y_1, loaded_data_y_2) + assert_frame_not_equal(loaded_data_x_1, loaded_data_y_1) + + @pytest.mark.parametrize( + "random_data_cls, kwargs", + [ + (RandomData, {}), + (RandomDataWithArgs, {"label": "z"}), + ], + ) + def test_independent_dynamic_data_callable_with_timeout(self, simple_cache, random_data_cls, kwargs, freezer): + # Two data sources use same method but have *different* bound instances are independent when only one times out. + # This is the same as test_shared_dynamic_data_callable_with_timeout but it *does* matter that this test passes. + data_manager["data_x"] = random_data_cls().load + data_manager["data_y"] = random_data_cls().load + data_manager["data_y"].timeout = 100 + + loaded_data_x_1 = data_manager["data_x"].load(**kwargs) + loaded_data_y_1 = data_manager["data_y"].load(**kwargs) + freezer.tick(100 + 50) + loaded_data_x_2 = data_manager["data_x"].load(**kwargs) + loaded_data_y_2 = data_manager["data_y"].load(**kwargs) + + # Cache has expired for data_y but not data_x. + assert_frame_equal(loaded_data_x_1, loaded_data_x_2) + assert_frame_not_equal(loaded_data_y_1, loaded_data_y_2) diff --git a/vizro-core/tests/unit/vizro/models/_action/test_action.py b/vizro-core/tests/unit/vizro/models/_action/test_action.py index 10f7b1420..c145424f3 100644 --- a/vizro-core/tests/unit/vizro/models/_action/test_action.py +++ b/vizro-core/tests/unit/vizro/models/_action/test_action.py @@ -32,12 +32,12 @@ def _custom_action_function_mock_return(): @pytest.fixture def custom_action_build_expected(): - return html.Div(children=[], id="action_test_action_model_components_div", hidden=True) + return html.Div(id="action_test_action_model_components_div", children=[], hidden=True) @pytest.fixture def predefined_action_build_expected(): - return html.Div(children=[], id="filter_action_test_filter_action_model_components_div", hidden=True) + return html.Div(id="filter_action_test_filter_action_model_components_div", children=[], hidden=True) class TestActionInstantiation: diff --git a/vizro-core/tests/unit/vizro/models/_action/test_actions_chain.py b/vizro-core/tests/unit/vizro/models/_action/test_actions_chain.py index 53ad80a47..a29d5672a 100644 --- a/vizro-core/tests/unit/vizro/models/_action/test_actions_chain.py +++ b/vizro-core/tests/unit/vizro/models/_action/test_actions_chain.py @@ -28,7 +28,7 @@ def test_create_actions_chain_mandatory_only(self, test_trigger): assert actions_chain.actions == [] def test_create_action_chains_mandatory_and_optional(self, test_trigger, test_action): - actions_chain = ActionsChain(trigger=test_trigger, actions=[test_action], id="actions_chain_id") + actions_chain = ActionsChain(id="actions_chain_id", trigger=test_trigger, actions=[test_action]) assert actions_chain.id == "actions_chain_id" assert actions_chain.trigger == test_trigger diff --git a/vizro-core/tests/unit/vizro/models/_components/form/test_checklist.py b/vizro-core/tests/unit/vizro/models/_components/form/test_checklist.py index 995bb4fbf..a78c8b7b8 100755 --- a/vizro-core/tests/unit/vizro/models/_components/form/test_checklist.py +++ b/vizro-core/tests/unit/vizro/models/_components/form/test_checklist.py @@ -1,8 +1,9 @@ """Unit tests for vizro.models.Checklist.""" +import dash_bootstrap_components as dbc import pytest from asserts import assert_component_equal -from dash import dcc, html +from dash import html try: from pydantic.v1 import ValidationError @@ -27,7 +28,7 @@ def test_create_checklist_mandatory_only(self): assert checklist.actions == [] def test_create_checklist_mandatory_and_optional(self): - checklist = Checklist(options=["A", "B", "C"], value=["A"], title="Title", id="checklist-id") + checklist = Checklist(id="checklist-id", options=["A", "B", "C"], value=["A"], title="Title") assert checklist.id == "checklist-id" assert checklist.type == "checklist" @@ -129,19 +130,16 @@ class TestChecklistBuild: def test_checklist_build(self): checklist = Checklist(id="checklist_id", options=["A", "B", "C"], title="Title").build() - expected_checklist = html.Div( + expected_checklist = html.Fieldset( [ - html.Label("Title", htmlFor="checklist_id"), - dcc.Checklist( + html.Legend("Title", className="form-label"), + dbc.Checklist( id="checklist_id", options=["ALL", "A", "B", "C"], value=["ALL"], - className="checkboxes-list", persistence=True, persistence_type="session", ), ], - className="input-container", - id="checklist_id_outer", ) assert_component_equal(checklist, expected_checklist) diff --git a/vizro-core/tests/unit/vizro/models/_components/form/test_date_picker.py b/vizro-core/tests/unit/vizro/models/_components/form/test_date_picker.py index eeae756e9..887e5bc53 100644 --- a/vizro-core/tests/unit/vizro/models/_components/form/test_date_picker.py +++ b/vizro-core/tests/unit/vizro/models/_components/form/test_date_picker.py @@ -2,6 +2,7 @@ from datetime import date, datetime +import dash_bootstrap_components as dbc import dash_mantine_components as dmc import pytest from asserts import assert_component_equal @@ -120,7 +121,7 @@ def test_datepicker_build(self, range, value): additional_kwargs = {"allowSingleDateInRange": True} if range else {} expected_datepicker = html.Div( [ - html.Label("Test title", htmlFor="datepicker_id"), + dbc.Label("Test title", html_for="datepicker_id"), date_picker_class( id="datepicker_id", minDate="2023-01-01", @@ -137,6 +138,5 @@ def test_datepicker_build(self, range, value): dcc.Store(id="datepicker_id_input_store", storage_type="session", data=value), ], className="selector_container", - id="datepicker_id_outer", ) assert_component_equal(date_picker, expected_datepicker) diff --git a/vizro-core/tests/unit/vizro/models/_components/form/test_dropdown.py b/vizro-core/tests/unit/vizro/models/_components/form/test_dropdown.py index 783706f3d..66145424a 100755 --- a/vizro-core/tests/unit/vizro/models/_components/form/test_dropdown.py +++ b/vizro-core/tests/unit/vizro/models/_components/form/test_dropdown.py @@ -1,5 +1,6 @@ """Unit tests for vizro.models.Dropdown.""" +import dash_bootstrap_components as dbc import pytest from asserts import assert_component_equal from dash import dcc, html @@ -28,7 +29,7 @@ def test_create_dropdown_mandatory_only(self): assert dropdown.actions == [] def test_create_dropdown_mandatory_and_optional(self): - dropdown = Dropdown(options=["A", "B", "C"], value="A", multi=False, title="Title", id="dropdown-id") + dropdown = Dropdown(id="dropdown-id", options=["A", "B", "C"], value="A", multi=False, title="Title") assert dropdown.id == "dropdown-id" assert dropdown.type == "dropdown" @@ -150,7 +151,7 @@ def test_dropdown_with_all_option(self): dropdown = Dropdown(options=["A", "B", "C"], title="Title", id="dropdown_id").build() expected_dropdown = html.Div( [ - html.Label("Title", htmlFor="dropdown_id"), + dbc.Label("Title", html_for="dropdown_id"), dcc.Dropdown( id="dropdown_id", options=["ALL", "A", "B", "C"], @@ -158,11 +159,8 @@ def test_dropdown_with_all_option(self): multi=True, persistence=True, persistence_type="session", - className="selector_body_dropdown", ), - ], - className="input-container", - id="dropdown_id_outer", + ] ) assert_component_equal(dropdown, expected_dropdown) @@ -171,7 +169,7 @@ def test_dropdown_without_all_option(self): dropdown = Dropdown(id="dropdown_id", options=["A", "B", "C"], multi=False, title="Title").build() expected_dropdown = html.Div( [ - html.Label("Title", htmlFor="dropdown_id"), + dbc.Label("Title", html_for="dropdown_id"), dcc.Dropdown( id="dropdown_id", options=["A", "B", "C"], @@ -179,11 +177,8 @@ def test_dropdown_without_all_option(self): multi=False, persistence=True, persistence_type="session", - className="selector_body_dropdown", ), - ], - className="input-container", - id="dropdown_id_outer", + ] ) assert_component_equal(dropdown, expected_dropdown) diff --git a/vizro-core/tests/unit/vizro/models/_components/form/test_radio_items.py b/vizro-core/tests/unit/vizro/models/_components/form/test_radio_items.py index 1cd41affb..c48d7c7a9 100755 --- a/vizro-core/tests/unit/vizro/models/_components/form/test_radio_items.py +++ b/vizro-core/tests/unit/vizro/models/_components/form/test_radio_items.py @@ -1,8 +1,9 @@ """Unit tests for vizro.models.RadioItems.""" +import dash_bootstrap_components as dbc import pytest from asserts import assert_component_equal -from dash import dcc, html +from dash import html try: from pydantic.v1 import ValidationError @@ -27,7 +28,7 @@ def test_create_radio_items_mandatory_only(self): assert radio_items.actions == [] def test_create_radio_items_mandatory_and_optional(self): - radio_items = RadioItems(options=["A", "B", "C"], value="A", title="Title", id="radio_items_id") + radio_items = RadioItems(id="radio_items_id", options=["A", "B", "C"], value="A", title="Title") assert radio_items.id == "radio_items_id" assert radio_items.type == "radio_items" @@ -129,20 +130,17 @@ class TestRadioItemsBuild: def test_radio_items_build(self): radio_items = RadioItems(id="radio_items_id", options=["A", "B", "C"], title="Title").build() - expected_radio_items = html.Div( + expected_radio_items = html.Fieldset( [ - html.Label("Title", htmlFor="radio_items_id"), - dcc.RadioItems( + html.Legend("Title", className="form-label"), + dbc.RadioItems( id="radio_items_id", options=["A", "B", "C"], value="A", - className="radio-items-list", persistence=True, persistence_type="session", ), - ], - className="input-container", - id="radio_items_id_outer", + ] ) assert_component_equal(radio_items, expected_radio_items) diff --git a/vizro-core/tests/unit/vizro/models/_components/form/test_range_slider.py b/vizro-core/tests/unit/vizro/models/_components/form/test_range_slider.py index 61304e2eb..80a23c9cc 100644 --- a/vizro-core/tests/unit/vizro/models/_components/form/test_range_slider.py +++ b/vizro-core/tests/unit/vizro/models/_components/form/test_range_slider.py @@ -1,5 +1,6 @@ """Unit tests for hyphen.models.slider.""" +import dash_bootstrap_components as dbc import pytest from asserts import assert_component_equal from dash import dcc, html @@ -16,7 +17,7 @@ def expected_range_slider_default(): return html.Div( [ - dcc.Store("range_slider_callback_data", data={"id": "range_slider", "min": None, "max": None}), + dcc.Store(id="range_slider_callback_data", data={"id": "range_slider", "min": None, "max": None}), html.Div( [ None, @@ -65,9 +66,7 @@ def expected_range_slider_default(): persistence_type="session", className="slider-track-without-marks", ), - ], - className="input-container", - id="range_slider_outer", + ] ) @@ -75,10 +74,10 @@ def expected_range_slider_default(): def expected_range_slider_with_optional(): return html.Div( [ - dcc.Store("range_slider_callback_data", data={"id": "range_slider", "min": 0.0, "max": 10.0}), + dcc.Store(id="range_slider_callback_data", data={"id": "range_slider", "min": 0.0, "max": 10.0}), html.Div( [ - html.Label("Title", htmlFor="range_slider"), + dbc.Label("Title", html_for="range_slider"), html.Div( [ dcc.Input( @@ -124,9 +123,7 @@ def expected_range_slider_with_optional(): persistence_type="session", className="slider-track-with-marks", ), - ], - className="input-container", - id="range_slider_outer", + ] ) @@ -148,24 +145,24 @@ def test_create_range_slider_mandatory_only(self): def test_create_range_slider_mandatory_and_optional(self): range_slider = vm.RangeSlider( + id="range_slider_id", min=0, max=10, step=1, marks={1: "1", 5: "5", 10: "10"}, value=[1, 9], title="Test title", - id="range_slider_id", ) + assert range_slider.id == "range_slider_id" + assert range_slider.type == "range_slider" assert range_slider.min == 0 assert range_slider.max == 10 assert range_slider.step == 1 assert range_slider.marks == {1: "1", 5: "5", 10: "10"} assert range_slider.value == [1, 9] assert range_slider.title == "Test title" - assert range_slider.id == "range_slider_id" assert range_slider.actions == [] - assert range_slider.type == "range_slider" @pytest.mark.parametrize( "min, max, expected_min, expected_max", @@ -292,13 +289,13 @@ def test_range_slider_build_default(self, expected_range_slider_default): def test_range_slider_build_with_optional(self, expected_range_slider_with_optional): range_slider = vm.RangeSlider( + id="range_slider", min=0, max=10, step=2, + marks={1: "1", 5: "5", 10: "10"}, value=[0, 10], - id="range_slider", title="Title", - marks={1: "1", 5: "5", 10: "10"}, ).build() assert_component_equal(range_slider, expected_range_slider_with_optional) diff --git a/vizro-core/tests/unit/vizro/models/_components/form/test_slider.py b/vizro-core/tests/unit/vizro/models/_components/form/test_slider.py index 07cbbe3cc..7ce15011d 100755 --- a/vizro-core/tests/unit/vizro/models/_components/form/test_slider.py +++ b/vizro-core/tests/unit/vizro/models/_components/form/test_slider.py @@ -1,5 +1,6 @@ """Unit tests for hyphen.models.slider.""" +import dash_bootstrap_components as dbc import pytest from asserts import assert_component_equal from dash import dcc, html @@ -16,10 +17,10 @@ def expected_slider(): return html.Div( [ - dcc.Store("slider_id_callback_data", data={"id": "slider_id", "min": 0.0, "max": 10.0}), + dcc.Store(id="slider_id_callback_data", data={"id": "slider_id", "min": 0.0, "max": 10.0}), html.Div( [ - html.Label("Test title", htmlFor="slider_id"), + dbc.Label("Test title", html_for="slider_id"), html.Div( [ dcc.Input( @@ -53,9 +54,7 @@ def expected_slider(): persistence_type="session", className="slider-track-with-marks", ), - ], - className="input-container", - id="slider_id_outer", + ] ) @@ -173,6 +172,6 @@ def test_set_action_via_validator(self, identity_action_function): class TestBuildMethod: def test_slider_build(self, expected_slider): - slider = vm.Slider(min=0, max=10, step=1, value=5, id="slider_id", title="Test title").build() + slider = vm.Slider(id="slider_id", min=0, max=10, step=1, value=5, title="Test title").build() assert_component_equal(slider, expected_slider) diff --git a/vizro-core/tests/unit/vizro/models/_components/form/test_text_area.py b/vizro-core/tests/unit/vizro/models/_components/form/test_text_area.py index ac81b09ce..9d0b09949 100755 --- a/vizro-core/tests/unit/vizro/models/_components/form/test_text_area.py +++ b/vizro-core/tests/unit/vizro/models/_components/form/test_text_area.py @@ -19,7 +19,7 @@ def test_create_text_area_mandatory_only(self): assert text_area.actions == [] def test_create_text_area_mandatory_and_optional(self): - text_area = TextArea(title="Title", placeholder="Placeholder", id="text-area-id") + text_area = TextArea(id="text-area-id", title="Title", placeholder="Placeholder") assert text_area.id == "text-area-id" assert text_area.type == "text_area" @@ -32,20 +32,17 @@ class TestUserInputBuild: """Tests model build method.""" def test_text_area_build(self): - text_area = TextArea(title="Title", placeholder="Placeholder", id="text-area-id").build() + text_area = TextArea(id="text-area-id", title="Title", placeholder="Placeholder").build() expected_text_area = html.Div( [ - html.Label("Title", htmlFor="text-area-id"), + dbc.Label("Title", html_for="text-area-id"), dbc.Textarea( id="text-area-id", placeholder="Placeholder", persistence=True, persistence_type="session", debounce=True, - className="text-area", ), - ], - className="input-container", - id="text-area-id_outer", + ] ) assert_component_equal(text_area, expected_text_area) diff --git a/vizro-core/tests/unit/vizro/models/_components/form/test_user_input.py b/vizro-core/tests/unit/vizro/models/_components/form/test_user_input.py index efd959dd0..ac54d8190 100755 --- a/vizro-core/tests/unit/vizro/models/_components/form/test_user_input.py +++ b/vizro-core/tests/unit/vizro/models/_components/form/test_user_input.py @@ -19,7 +19,7 @@ def test_create_user_input_mandatory_only(self): assert user_input.actions == [] def test_create_user_input_mandatory_and_optional(self): - user_input = UserInput(title="Title", placeholder="Placeholder", id="user-input-id") + user_input = UserInput(id="user-input-id", title="Title", placeholder="Placeholder") assert user_input.id == "user-input-id" assert user_input.type == "user_input" @@ -32,10 +32,10 @@ class TestUserInputBuild: """Tests model build method.""" def test_user_input_build(self): - user_input = UserInput(title="Title", placeholder="Placeholder", id="user-input-id").build() + user_input = UserInput(id="user-input-id", title="Title", placeholder="Placeholder").build() expected_user_input = html.Div( [ - html.Label("Title", htmlFor="user-input-id"), + dbc.Label("Title", html_for="user-input-id"), dbc.Input( id="user-input-id", placeholder="Placeholder", @@ -43,10 +43,7 @@ def test_user_input_build(self): persistence=True, persistence_type="session", debounce=True, - className="user_input", ), - ], - className="input-container", - id="user-input-id_outer", + ] ) assert_component_equal(user_input, expected_user_input) diff --git a/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py index fb751caa0..a1200b9cb 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py @@ -1,6 +1,5 @@ """Unit tests for vizro.models.AgGrid.""" -import pandas as pd import pytest from asserts import assert_component_equal from dash import dcc, html @@ -91,15 +90,12 @@ def test_ag_grid_filter_interaction_attributes(self, ag_grid_with_id): class TestProcessAgGridDataFrame: def test_process_figure_data_frame_str_df(self, dash_ag_grid_with_str_dataframe, gapminder): data_manager["gapminder"] = gapminder - ag_grid_with_str_df = vm.AgGrid(id="ag_grid", figure=dash_ag_grid_with_str_dataframe) - assert data_manager._get_component_data("ag_grid").equals(gapminder) - assert ag_grid_with_str_df["data_frame"] == "gapminder" + ag_grid = vm.AgGrid(id="ag_grid", figure=dash_ag_grid_with_str_dataframe) + assert data_manager[ag_grid["data_frame"]].load().equals(gapminder) def test_process_figure_data_frame_df(self, standard_ag_grid, gapminder): - ag_grid_with_str_df = vm.AgGrid(id="ag_grid", figure=standard_ag_grid) - assert data_manager._get_component_data("ag_grid").equals(gapminder) - with pytest.raises(KeyError, match="'data_frame'"): - ag_grid_with_str_df.figure["data_frame"] + ag_grid = vm.AgGrid(id="ag_grid", figure=standard_ag_grid) + assert data_manager[ag_grid["data_frame"]].load().equals(gapminder) class TestPreBuildAgGrid: @@ -116,7 +112,7 @@ def test_pre_build_actions_underlying_ag_grid_id(self, ag_grid_with_id, filter_i class TestBuildAgGrid: - def test_ag_grid_build_mandatory_only(self, standard_ag_grid): + def test_ag_grid_build_mandatory_only(self, standard_ag_grid, gapminder): ag_grid = vm.AgGrid(id="text_ag_grid", figure=standard_ag_grid) ag_grid.pre_build() ag_grid = ag_grid.build() @@ -124,19 +120,19 @@ def test_ag_grid_build_mandatory_only(self, standard_ag_grid): [ None, html.Div( - dash_ag_grid(data_frame=pd.DataFrame(), id="__input_text_ag_grid")(), id="text_ag_grid", + children=dash_ag_grid(data_frame=gapminder, id="__input_text_ag_grid")(), className="table-container", ), ], - id="text_ag_grid_outer", color="grey", parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, ) assert_component_equal(ag_grid, expected_ag_grid) - def test_ag_grid_build_with_underlying_id(self, ag_grid_with_id_and_conf, filter_interaction_action): + def test_ag_grid_build_with_underlying_id(self, ag_grid_with_id_and_conf, filter_interaction_action, gapminder): ag_grid = vm.AgGrid(id="text_ag_grid", figure=ag_grid_with_id_and_conf, actions=[filter_interaction_action]) ag_grid.pre_build() ag_grid = ag_grid.build() @@ -145,16 +141,16 @@ def test_ag_grid_build_with_underlying_id(self, ag_grid_with_id_and_conf, filter [ None, html.Div( - dash_ag_grid( - data_frame=pd.DataFrame(), id="underlying_ag_grid_id", dashGridOptions={"pagination": True} - )(), id="text_ag_grid", + children=dash_ag_grid( + data_frame=gapminder, id="underlying_ag_grid_id", dashGridOptions={"pagination": True} + )(), className="table-container", ), ], - id="text_ag_grid_outer", color="grey", parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, ) assert_component_equal(ag_grid, expected_ag_grid) diff --git a/vizro-core/tests/unit/vizro/models/_components/test_button.py b/vizro-core/tests/unit/vizro/models/_components/test_button.py index b0425e741..69c740929 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_button.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_button.py @@ -4,7 +4,6 @@ import pytest import vizro.models as vm from asserts import assert_component_equal -from dash import html from vizro.actions import export_data @@ -35,9 +34,5 @@ def test_set_action_via_validator(self): class TestBuildMethod: def test_button_build(self): button = vm.Button(id="button_id", text="My text").build() - expected = html.Div( - dbc.Button(id="button_id", children="My text", className="button_primary"), - className="button_container", - id="button_id_outer", - ) + expected = dbc.Button(id="button_id", children="My text") assert_component_equal(button, expected) diff --git a/vizro-core/tests/unit/vizro/models/_components/test_card.py b/vizro-core/tests/unit/vizro/models/_components/test_card.py index 275528153..c2fe1fa09 100755 --- a/vizro-core/tests/unit/vizro/models/_components/test_card.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_card.py @@ -26,11 +26,11 @@ def test_create_card_mandatory_only(self): @pytest.mark.parametrize("id, href", [("id_1", "/page_1_reference"), ("id_2", "https://www.google.de/")]) def test_create_card_mandatory_and_optional(self, id, href): - card = vm.Card(text="Text to test card", id=id, href=href) + card = vm.Card(id=id, text="Text to test card", href=href) + assert card.id == id assert card.type == "card" assert card.text == "Text to test card" - assert card.id == id assert card.href == href def test_mandatory_text_missing(self): @@ -45,22 +45,27 @@ def test_none_as_text(self): class TestBuildMethod: """Tests build method.""" - def test_card_build(self): + def test_card_build_with_href(self): card = vm.Card(id="card_id", text="Hello", href="https://www.google.com") card = card.build() expected_card = dbc.Card( dbc.NavLink( - dcc.Markdown("Hello", className="card_text", dangerously_allow_html=False, id="card_id"), + dcc.Markdown(id="card_id", children="Hello", dangerously_allow_html=False), href="https://www.google.com", - className="card-link", ), - className="nav-card", - id="card_id_outer", + className="card-nav", ) assert_component_equal(card, expected_card) + def test_card_build_wo_href(self): + card = vm.Card(id="card_id", text="Hello") + card = card.build() + assert_component_equal( + card, dbc.Card(dcc.Markdown(id="card_id", children="Hello", dangerously_allow_html=False), className="") + ) + @pytest.mark.parametrize( "test_text, expected", [ @@ -79,7 +84,7 @@ def test_card_build(self): ], ) def test_markdown_setting(self, test_text, expected): - card = vm.Card(text=test_text, id="id_valid") + card = vm.Card(id="id_valid", text=test_text) card = card.build() card_markdown = card["id_valid"] @@ -96,7 +101,7 @@ def test_markdown_setting(self, test_text, expected): ], ) def test_markdown_build_invalid(self, test_text, expected): - card = vm.Card(text=test_text, id="test_id") + card = vm.Card(id="test_id", text=test_text) card = card.build() card_markdown = card["test_id"] diff --git a/vizro-core/tests/unit/vizro/models/_components/test_container.py b/vizro-core/tests/unit/vizro/models/_components/test_container.py index 145824008..e5308a899 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_container.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_container.py @@ -24,12 +24,12 @@ def test_create_container_mandatory_only(self): def test_create_container_mandatory_and_optional(self): container = vm.Container( - title="Title", components=[vm.Button(), vm.Button()], id="my-id", layout=vm.Layout(grid=[[0, 1]]) + id="my-id", title="Title", components=[vm.Button(), vm.Button()], layout=vm.Layout(grid=[[0, 1]]) ) + assert container.id == "my-id" assert isinstance(container.components[0], vm.Button) and isinstance(container.components[1], vm.Button) assert container.layout.grid == [[0, 1]] assert container.title == "Title" - assert container.id == "my-id" def test_mandatory_title_missing(self): with pytest.raises(ValidationError, match="field required"): @@ -46,10 +46,10 @@ def test_container_build(self): id="container", title="Title", components=[vm.Button()], layout=vm.Layout(id="layout_id", grid=[[0]]) ).build() assert_component_equal( - result, html.Div(className="page-component-container", id="container"), keys_to_strip={"children"} + result, html.Div(id="container", className="page-component-container"), keys_to_strip={"children"} ) assert_component_equal(result.children, [html.H3(), html.Div()], keys_to_strip=STRIP_ALL) # We still want to test the exact H3 produced in Container.build: assert_component_equal(result.children[0], html.H3("Title", className="container__title")) # And also that a button has been inserted in the right place: - assert_component_equal(result["layout_id_0"].children.children, dbc.Button(), keys_to_strip=STRIP_ALL) + assert_component_equal(result["layout_id_0"].children, dbc.Button(), keys_to_strip=STRIP_ALL) diff --git a/vizro-core/tests/unit/vizro/models/_components/test_graph.py b/vizro-core/tests/unit/vizro/models/_components/test_graph.py index 20556f2f1..406e41fe5 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_graph.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_graph.py @@ -43,7 +43,7 @@ def test_create_graph_mandatory_only(self, standard_px_chart): @pytest.mark.parametrize("id", ["id_1", "id_2"]) def test_create_graph_mandatory_and_optional(self, standard_px_chart, id): - graph = vm.Graph(figure=standard_px_chart, id=id, actions=[]) + graph = vm.Graph(id=id, figure=standard_px_chart, actions=[]) assert graph.id == id assert graph.type == "graph" @@ -78,9 +78,9 @@ def test_title_margin_adjustment(self, gapminder, title, expected): assert graph.layout.margin.t == expected assert graph.layout.template.layout.margin.t == 64 - assert graph.layout.template.layout.margin.l == 80 + assert graph.layout.template.layout.margin.l == 24 assert graph.layout.template.layout.margin.b == 64 - assert graph.layout.template.layout.margin.r == 12 + assert graph.layout.template.layout.margin.r == 24 def test_update_theme_outside_callback(self, standard_px_chart): graph = vm.Graph(figure=standard_px_chart).__call__() @@ -119,18 +119,15 @@ def test_graph_filter_interaction_attributes(self, standard_px_chart): assert "modelID" in graph._filter_interaction_input -class TestProcessFigureDataFrame: +class TestProcessGraphDataFrame: def test_process_figure_data_frame_str_df(self, standard_px_chart_with_str_dataframe, gapminder): data_manager["gapminder"] = gapminder - graph_with_str_df = vm.Graph(id="text_graph", figure=standard_px_chart_with_str_dataframe) - assert data_manager._get_component_data("text_graph").equals(gapminder) - assert graph_with_str_df["data_frame"] == "gapminder" + graph = vm.Graph(id="graph", figure=standard_px_chart_with_str_dataframe) + assert data_manager[graph["data_frame"]].load().equals(gapminder) def test_process_figure_data_frame_df(self, standard_px_chart, gapminder): - graph_with_df = vm.Graph(id="text_graph", figure=standard_px_chart) - assert data_manager._get_component_data("text_graph").equals(gapminder) - with pytest.raises(KeyError, match="'data_frame'"): - graph_with_df.figure["data_frame"] + graph = vm.Graph(id="graph", figure=standard_px_chart) + assert data_manager[graph["data_frame"]].load().equals(gapminder) class TestBuild: @@ -157,5 +154,6 @@ def test_graph_build(self, standard_px_chart): ), color="grey", parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, ) assert_component_equal(graph, expected_graph) diff --git a/vizro-core/tests/unit/vizro/models/_components/test_table.py b/vizro-core/tests/unit/vizro/models/_components/test_table.py index b6ac5ad3a..0310b7922 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_table.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_table.py @@ -1,6 +1,5 @@ """Unit tests for vizro.models.Table.""" -import pandas as pd import pytest from asserts import assert_component_equal from dash import dcc, html @@ -38,7 +37,7 @@ def test_create_graph_mandatory_only(self, standard_dash_table): @pytest.mark.parametrize("id", ["id_1", "id_2"]) def test_create_table_mandatory_and_optional(self, standard_dash_table, id): - table = vm.Table(figure=standard_dash_table, id=id, actions=[]) + table = vm.Table(id=id, figure=standard_dash_table, actions=[]) assert table.id == id assert table.type == "table" @@ -91,15 +90,12 @@ class TestProcessTableDataFrame: # Testing at this low implementation level as mocking callback contexts skips checking for creation of these objects def test_process_figure_data_frame_str_df(self, dash_table_with_str_dataframe, gapminder): data_manager["gapminder"] = gapminder - table_with_str_df = vm.Table(id="table", figure=dash_table_with_str_dataframe) - assert data_manager._get_component_data("table").equals(gapminder) - assert table_with_str_df["data_frame"] == "gapminder" + table = vm.Table(id="table", figure=dash_table_with_str_dataframe) + assert data_manager[table["data_frame"]].load().equals(gapminder) def test_process_figure_data_frame_df(self, standard_dash_table, gapminder): - table_with_str_df = vm.Table(id="table", figure=standard_dash_table) - assert data_manager._get_component_data("table").equals(gapminder) - with pytest.raises(KeyError, match="'data_frame'"): - table_with_str_df.figure["data_frame"] + table = vm.Table(id="table", figure=standard_dash_table) + assert data_manager[table["data_frame"]].load().equals(gapminder) class TestPreBuildTable: @@ -117,7 +113,7 @@ def test_pre_build_actions_underlying_table_id(self, dash_data_table_with_id, fi class TestBuildTable: - def test_table_build_mandatory_only(self, standard_dash_table): + def test_table_build_mandatory_only(self, standard_dash_table, gapminder): table = vm.Table(id="text_table", figure=standard_dash_table) table.pre_build() table = table.build() @@ -125,18 +121,18 @@ def test_table_build_mandatory_only(self, standard_dash_table): html.Div( [ None, - html.Div(dash_data_table(id="__input_text_table", data_frame=pd.DataFrame())(), id="text_table"), + html.Div(dash_data_table(id="__input_text_table", data_frame=gapminder)(), id="text_table"), ], className="table-container", - id="text_table_outer", ), color="grey", parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, ) assert_component_equal(table, expected_table) - def test_table_build_with_underlying_id(self, dash_data_table_with_id, filter_interaction_action): + def test_table_build_with_underlying_id(self, dash_data_table_with_id, filter_interaction_action, gapminder): table = vm.Table(id="text_table", figure=dash_data_table_with_id, actions=[filter_interaction_action]) table.pre_build() table = table.build() @@ -145,13 +141,13 @@ def test_table_build_with_underlying_id(self, dash_data_table_with_id, filter_in html.Div( [ None, - html.Div(dash_data_table(id="underlying_table_id", data_frame=pd.DataFrame())(), id="text_table"), + html.Div(dash_data_table(id="underlying_table_id", data_frame=gapminder)(), id="text_table"), ], className="table-container", - id="text_table_outer", ), color="grey", parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, ) assert_component_equal(table, expected_table) diff --git a/vizro-core/tests/unit/vizro/models/_controls/test_filter.py b/vizro-core/tests/unit/vizro/models/_controls/test_filter.py index 0c85d873b..62f99c0c3 100644 --- a/vizro-core/tests/unit/vizro/models/_controls/test_filter.py +++ b/vizro-core/tests/unit/vizro/models/_controls/test_filter.py @@ -377,6 +377,9 @@ def test_set_actions(self, filtered_column, selector, filter_function, managers_ # TODO: Add tests for custom temporal and categorical selectors too. Probably inside the conftest file and reused in # all other tests. Also add tests for the custom selector that is an entirely new component and adjust docs. + # This test does add_type so ideally we would clean up after this to restore vizro.models to its previous state. + # This is difficult to fix fully by un-importing vizro.models though, since we use `import vizro.models as vm` - see + # https://stackoverflow.com/questions/437589/how-do-i-unload-reload-a-python-module. def test_numerical_custom_selector(self, gapminder, managers_one_page_two_graphs): class RangeSliderNonCross(vm.RangeSlider): """Custom numerical multi-selector `RangeSliderNonCross` to be provided to `Filter`.""" diff --git a/vizro-core/tests/unit/vizro/models/_controls/test_parameter.py b/vizro-core/tests/unit/vizro/models/_controls/test_parameter.py index a2a1fabc3..1d1095627 100644 --- a/vizro-core/tests/unit/vizro/models/_controls/test_parameter.py +++ b/vizro-core/tests/unit/vizro/models/_controls/test_parameter.py @@ -23,8 +23,8 @@ def test_instantiation(self): def test_check_dot_notation_failed(self): with pytest.raises( ValueError, - match="Invalid target scatter_chart. Targets must be supplied in the from of " - ".", + match="Invalid target scatter_chart. " + "Targets must be supplied in the form .", ): Parameter(targets=["scatter_chart"], selector=vm.Dropdown(options=["lifeExp", "pop"])) @@ -32,6 +32,15 @@ def test_check_target_present_failed(self): with pytest.raises(ValueError, match="Target scatter_chart_invalid not found in model_manager."): Parameter(targets=["scatter_chart_invalid.x"], selector=vm.Dropdown(options=["lifeExp", "pop"])) + @pytest.mark.parametrize("target", ["scatter_chart.data_frame", "scatter_chart.data_frame.argument.nested_arg"]) + def test_check_data_frame_as_target_argument_failed(self, target): + with pytest.raises( + ValueError, + match=f"Invalid target {target}. 'data_frame' target must be supplied in the form " + f".data_frame.", + ): + Parameter(targets=[target], selector=vm.Dropdown(options=["lifeExp", "pop"])) + def test_duplicate_parameter_target_failed(self): with pytest.raises(ValueError, match="Duplicate parameter targets {'scatter_chart.x'} found."): Parameter(targets=["scatter_chart.x", "scatter_chart.x"], selector=vm.Dropdown(options=["lifeExp", "pop"])) diff --git a/vizro-core/tests/unit/vizro/models/_navigation/test_nav_link.py b/vizro-core/tests/unit/vizro/models/_navigation/test_nav_link.py index 0b93baea3..262c102ee 100644 --- a/vizro-core/tests/unit/vizro/models/_navigation/test_nav_link.py +++ b/vizro-core/tests/unit/vizro/models/_navigation/test_nav_link.py @@ -94,7 +94,6 @@ def test_nav_link_active(self, pages, request): ], active=True, href="/", - className="nav-bar-icon-link", id="nav-link", ) assert_component_equal(built_nav_link["nav-link"], expected_nav_link) @@ -116,7 +115,6 @@ def test_nav_link_not_active(self, pages, request): ], active=False, href="/", - className="nav-bar-icon-link", id="nav-link", ) assert_component_equal(built_nav_link["nav-link"], expected_button) diff --git a/vizro-core/tests/unit/vizro/models/_navigation/test_navigation.py b/vizro-core/tests/unit/vizro/models/_navigation/test_navigation.py index 44a89b91d..a774bbe5c 100644 --- a/vizro-core/tests/unit/vizro/models/_navigation/test_navigation.py +++ b/vizro-core/tests/unit/vizro/models/_navigation/test_navigation.py @@ -92,7 +92,9 @@ def test_non_default_nav_selector_pags_as_dict(self, pages_as_dict, built_nav_li navigation = vm.Navigation(pages=pages_as_dict, nav_selector=vm.NavBar()) navigation.pre_build() built_navigation = navigation.build(active_page_id="Page 1") - assert_component_equal(built_navigation["nav-bar"], dbc.Navbar(id="nav-bar"), keys_to_strip={"children"}) + assert_component_equal( + built_navigation["nav-bar"], dbc.Navbar(id="nav-bar", className="flex-column"), keys_to_strip={"children"} + ) assert_component_equal(built_navigation["nav-panel"], dbc.Nav(id="nav-panel"), keys_to_strip={"children"}) assert_component_equal(built_navigation["nav-panel"].children, [dbc.Accordion()], keys_to_strip=STRIP_ALL) @@ -100,7 +102,9 @@ def test_non_default_nav_selector_pages_as_list(self, pages_as_list): navigation = vm.Navigation(pages=pages_as_list, nav_selector=vm.NavBar()) navigation.pre_build() built_navigation = navigation.build(active_page_id="Page 1") - assert_component_equal(built_navigation["nav-bar"], dbc.Navbar(id="nav-bar"), keys_to_strip={"children"}) + assert_component_equal( + built_navigation["nav-bar"], dbc.Navbar(id="nav-bar", className="flex-column"), keys_to_strip={"children"} + ) assert_component_equal( built_navigation["nav-panel"], dbc.Nav(id="nav-panel", className="d-none invisible"), diff --git a/vizro-core/tests/unit/vizro/models/test_dashboard.py b/vizro-core/tests/unit/vizro/models/test_dashboard.py index 6f18db592..a3956ed33 100644 --- a/vizro-core/tests/unit/vizro/models/test_dashboard.py +++ b/vizro-core/tests/unit/vizro/models/test_dashboard.py @@ -208,7 +208,7 @@ def test_make_page_404_layout(self, vizro_app): ], className="error_text_container", ), - dbc.Button("Take me home", href="/", className="button_primary"), + dbc.Button("Take me home", href="/"), ], className="error_content_container", ), @@ -229,7 +229,7 @@ def test_dashboard_build(self, vizro_app, page_1, page_2): expected_dashboard_container = html.Div( id="dashboard-container", children=[ - html.Div(vizro.__version__, id="vizro_version", hidden=True), + html.Div(id="vizro_version", children=vizro.__version__, hidden=True), ActionLoop._create_app_callbacks(), dash.page_container, ], diff --git a/vizro-core/tests/unit/vizro/models/test_layout.py b/vizro-core/tests/unit/vizro/models/test_layout.py index ebc059edc..32c807b5a 100755 --- a/vizro-core/tests/unit/vizro/models/test_layout.py +++ b/vizro-core/tests/unit/vizro/models/test_layout.py @@ -174,20 +174,26 @@ def test_set_layout_invalid(self, model_with_layout): class TestLayoutBuild: def test_layout_build(self): - result = vm.Layout(grid=[[0, 1], [0, 2]], id="layout_id").build() + result = vm.Layout(id="layout_id", grid=[[0, 1], [0, 2]]).build() expected = html.Div( - [ - html.Div(id="layout_id_0", style={"gridColumn": "1/2", "gridRow": "1/3"}), - html.Div(id="layout_id_1", style={"gridColumn": "2/3", "gridRow": "1/2"}), - html.Div(id="layout_id_2", style={"gridColumn": "2/3", "gridRow": "2/3"}), + id="layout_id", + children=[ + html.Div( + id="layout_id_0", style={"gridColumn": "1/2", "gridRow": "1/3", "height": "100%", "width": "100%"} + ), + html.Div( + id="layout_id_1", style={"gridColumn": "2/3", "gridRow": "1/2", "height": "100%", "width": "100%"} + ), + html.Div( + id="layout_id_2", style={"gridColumn": "2/3", "gridRow": "2/3", "height": "100%", "width": "100%"} + ), ], style={ - "gridRowGap": "12px", - "gridColumnGap": "12px", + "gridRowGap": "24px", + "gridColumnGap": "24px", "gridTemplateColumns": f"repeat(2," f"minmax({'0px'}, 1fr))", "gridTemplateRows": f"repeat(2," f"minmax({'0px'}, 1fr))", }, className="grid-layout", - id="layout_id", ) assert_component_equal(result, expected) diff --git a/vizro-core/tests/unit/vizro/models/test_page.py b/vizro-core/tests/unit/vizro/models/test_page.py index 9f7835526..62bce4966 100644 --- a/vizro-core/tests/unit/vizro/models/test_page.py +++ b/vizro-core/tests/unit/vizro/models/test_page.py @@ -27,17 +27,17 @@ def test_create_page_mandatory_only(self): def test_create_page_mandatory_and_optional(self): page = vm.Page( + id="my-id", title="Page 1", components=[vm.Button(), vm.Button()], - id="my-id", layout=vm.Layout(grid=[[0, 1]]), path="my-path", ) assert isinstance(page.components[0], vm.Button) and isinstance(page.components[1], vm.Button) + assert page.id == "my-id" assert page.layout.grid == [[0, 1]] assert page.controls == [] assert page.title == "Page 1" - assert page.id == "my-id" assert page.path == "/my-path" assert page.actions == [] @@ -50,8 +50,8 @@ def test_mandatory_components_missing(self): vm.Page(title="Page 1") def test_set_id_duplicate_title_valid(self): - vm.Page(title="Page 1", components=[vm.Button()], id="my-id-1") - vm.Page(title="Page 1", components=[vm.Button()], id="my-id-2") + vm.Page(id="my-id-1", title="Page 1", components=[vm.Button()]) + vm.Page(id="my-id-2", title="Page 1", components=[vm.Button()]) def test_set_id_duplicate_title_invalid(self): with pytest.raises( @@ -65,6 +65,7 @@ def test_set_id_duplicate_title_invalid(self): @pytest.mark.parametrize( "test_path, expected", [ + ("Title", "/title"), ("this-path-works", "/this-path-works"), ("2147abc", "/2147abc"), ("this_path_works", "/this_path_works"), @@ -76,7 +77,9 @@ def test_set_path_valid(self, test_path, expected): page = vm.Page(title="Page 1/2", components=[vm.Button()], path=test_path) assert page.path == expected - @pytest.mark.parametrize("test_path", ["this needs? fixing*", " this needs fixing", "THIS NEEDS FIXING"]) + @pytest.mark.parametrize( + "test_path", ["this needs? fixing*", " this needs fixing", "THIS NEEDS FIXING", "this-needs!@#$%^&*()+=-fixing"] + ) def test_set_path_invalid(self, test_path): page = vm.Page(title="Page 1", components=[vm.Button()], path=test_path) assert page.path == "/this-needs-fixing" diff --git a/vizro-core/tests/unit/vizro/models/test_types.py b/vizro-core/tests/unit/vizro/models/test_types.py index 619379b64..54d3e7bbc 100644 --- a/vizro-core/tests/unit/vizro/models/test_types.py +++ b/vizro-core/tests/unit/vizro/models/test_types.py @@ -76,11 +76,9 @@ def test_getitem_unknown_args(self, captured_callable): with pytest.raises(KeyError): captured_callable["c"] - def test_delitem(self, captured_callable): - del captured_callable["a"] - - with pytest.raises(KeyError): - captured_callable["a"] + def test_setitem(self, captured_callable): + captured_callable["a"] = 2 + assert captured_callable["a"] == 2 @pytest.mark.parametrize( diff --git a/vizro-core/tests/unit/vizro/plotly/test_express.py b/vizro-core/tests/unit/vizro/plotly/test_express.py index b82e3f5d1..cd4c749a5 100644 --- a/vizro-core/tests/unit/vizro/plotly/test_express.py +++ b/vizro-core/tests/unit/vizro/plotly/test_express.py @@ -1,12 +1,12 @@ import plotly.express as px -import vizro.plotly.express as hpx +import vizro.plotly.express as vpx def test_non_chart_unchanged(): - assert hpx.data is px.data + assert vpx.data is px.data def test_chart_wrapped(): - graph = hpx.scatter(px.data.iris(), x="petal_width", y="petal_length") + graph = vpx.scatter(px.data.iris(), x="petal_width", y="petal_length") assert graph._captured_callable._function is px.scatter - assert hpx.scatter is not px.scatter + assert vpx.scatter is not px.scatter diff --git a/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py b/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py index 3f0eb7971..7dce3eb2f 100644 --- a/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py +++ b/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py @@ -3,6 +3,7 @@ import vizro.models as vm from asserts import assert_component_equal from dash import dcc, html +from pandas import Timestamp from vizro.models.types import capture from vizro.tables import dash_ag_grid @@ -14,6 +15,24 @@ "date": pd.to_datetime(["2021/01/01", "2021/01/02", "2021/01/03"]), } ) +column_defs = [{"field": "cat"}, {"field": "int"}, {"field": "float"}, {"field": "date"}] +row_data_date_converted = [ + {"cat": "a", "int": 4, "float": 7.3, "date": "2021-01-01"}, + {"cat": "b", "int": 5, "float": 8.2, "date": "2021-01-02"}, + {"cat": "c", "int": 6, "float": 9.1, "date": "2021-01-03"}, +] +row_data_date_raw = [ + {"cat": "a", "date": Timestamp("2021-01-01 00:00:00"), "float": 7.3, "int": 4}, + {"cat": "b", "date": Timestamp("2021-01-02 00:00:00"), "float": 8.2, "int": 5}, + {"cat": "c", "date": Timestamp("2021-01-03 00:00:00"), "float": 9.1, "int": 6}, +] +default_col_defs = { + "filter": True, + "filterParams": {"buttons": ["apply", "reset"], "closeOnApply": True}, + "resizable": True, + "sortable": True, +} +style = {"height": "100%"} class TestDashAgGrid: @@ -22,17 +41,14 @@ def test_dash_ag_grid(self): assert_component_equal( grid, dag.AgGrid( - columnDefs=[{"field": "cat"}, {"field": "int"}, {"field": "float"}, {"field": "date"}], - rowData=[ - {"cat": "a", "int": 4, "float": 7.3, "date": "2021-01-01"}, - {"cat": "b", "int": 5, "float": 8.2, "date": "2021-01-02"}, - {"cat": "c", "int": 6, "float": 9.1, "date": "2021-01-03"}, - ], + columnDefs=column_defs, + rowData=row_data_date_converted, + defaultColDef=default_col_defs, + style=style, ), - keys_to_strip={"className", "defaultColDef", "dashGridOptions", "style"}, + keys_to_strip={"className", "dashGridOptions"}, ) - # we could test other properties such as defaultColDef, - # but this would just test our chosen defaults, and no functionality really + # skipping only dashGridOptions as this is mostly our defaults for data formats, and would crowd the tests class TestCustomDashAgGrid: @@ -58,14 +74,52 @@ def custom_ag_grid(data_frame): [ None, html.Div( - dag.AgGrid(id="__input_custom_ag_grid", columnDefs=[], rowData=[]), + dag.AgGrid(id="__input_custom_ag_grid", columnDefs=column_defs, rowData=row_data_date_raw), id=id, className="table-container", ), ], - id=f"{id}_outer", color="grey", parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, + ) + + assert_component_equal(custom_grid, expected_grid) + + def test_custom_dash_ag_grid_column_referral(self): + """Tests whether a custom created grid can be correctly built in vm.AgGrid. + + This test focuses on the case that the custom grid includes column referrals on presumed data knowledge. + """ + id = "custom_ag_grid" + + @capture("ag_grid") + def custom_ag_grid(data_frame): + data_frame["cat"] # access "existing" column + return dag.AgGrid( + columnDefs=[{"field": col} for col in data_frame.columns], + rowData=data_frame.to_dict("records"), + ) + + grid_model = vm.AgGrid( + id=id, + figure=custom_ag_grid(data_frame=data), + ) + grid_model.pre_build() + custom_grid = grid_model.build() + + expected_grid = dcc.Loading( + [ + None, + html.Div( + dag.AgGrid(id="__input_custom_ag_grid", columnDefs=column_defs, rowData=row_data_date_raw), + id=id, + className="table-container", + ), + ], + color="grey", + parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, ) assert_component_equal(custom_grid, expected_grid) diff --git a/vizro-core/tests/unit/vizro/tables/test_dash_table.py b/vizro-core/tests/unit/vizro/tables/test_dash_table.py index 534816c45..12119a10f 100644 --- a/vizro-core/tests/unit/vizro/tables/test_dash_table.py +++ b/vizro-core/tests/unit/vizro/tables/test_dash_table.py @@ -13,6 +13,17 @@ "date": pd.to_datetime(["2021/01/01", "2021/01/02", "2021/01/03"]), } ) +columns = [ + {"id": "cat", "name": "cat"}, + {"id": "int", "name": "int"}, + {"id": "float", "name": "float"}, + {"id": "date", "name": "date"}, +] +data_in_table = [ + {"cat": "a", "int": 4, "float": 7.3, "date": pd.Timestamp("2021-01-01 00:00:00")}, + {"cat": "b", "int": 5, "float": 8.2, "date": pd.Timestamp("2021-01-02 00:00:00")}, + {"cat": "c", "int": 6, "float": 9.1, "date": pd.Timestamp("2021-01-03 00:00:00")}, +] class TestDashDataTable: @@ -21,18 +32,10 @@ def test_dash_data_table(self): assert_component_equal( table, dash_table.DataTable( - columns=[ - {"id": "cat", "name": "cat"}, - {"id": "int", "name": "int"}, - {"id": "float", "name": "float"}, - {"id": "date", "name": "date"}, - ], - data=[ - {"cat": "a", "int": 4, "float": 7.3, "date": pd.Timestamp("2021-01-01 00:00:00")}, - {"cat": "b", "int": 5, "float": 8.2, "date": pd.Timestamp("2021-01-02 00:00:00")}, - {"cat": "c", "int": 6, "float": 9.1, "date": pd.Timestamp("2021-01-03 00:00:00")}, - ], + columns=columns, + data=data_in_table, style_as_list_view=True, + style_cell={"position": "static"}, style_data={"border_bottom": "1px solid var(--border-subtle-alpha-01)", "height": "40px"}, style_header={ "border_bottom": "1px solid var(--state-overlays-selected-hover)", @@ -70,7 +73,54 @@ def custom_dash_data_table(data_frame): custom_table = table.build() - expected_table_object = custom_dash_data_table(data_frame=pd.DataFrame())() + expected_table_object = dash_table.DataTable( + columns=columns, + data=data_in_table, + ) + expected_table_object.id = "__input_" + id + + expected_table = dcc.Loading( + html.Div( + [ + None, + html.Div(expected_table_object, id=id), + ], + className="table-container", + ), + color="grey", + parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, + ) + + assert_component_equal(custom_table, expected_table) + + def test_custom_dash_data_table_column_referral(self): + """Tests whether a custom created table callable can be correctly built in vm.Table. + + This test focuses on the case that the custom grid include column referrals on presumed data knowledge. + """ + id = "custom_dash_data_table" + + @capture("table") + def custom_dash_data_table(data_frame): + data_frame["cat"] # access "existing" column + return dash_table.DataTable( + columns=[{"name": col, "id": col} for col in data_frame.columns], + data=data_frame.to_dict("records"), + ) + + table = vm.Table( + id=id, + figure=custom_dash_data_table(data_frame=data), + ) + table.pre_build() + + custom_table = table.build() + + expected_table_object = dash_table.DataTable( + columns=columns, + data=data_in_table, + ) expected_table_object.id = "__input_" + id expected_table = dcc.Loading( @@ -80,10 +130,10 @@ def custom_dash_data_table(data_frame): html.Div(expected_table_object, id=id), ], className="table-container", - id=f"{id}_outer", ), color="grey", parent_className="loading-container", + overlay_style={"visibility": "visible", "opacity": 0.3}, ) assert_component_equal(custom_table, expected_table)