├── environment.yml ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── README.md └── content └── demo.ipynb /environment.yml: -------------------------------------------------------------------------------- 1 | name: voici 2 | channels: 3 | - https://repo.mamba.pm/emscripten-forge 4 | - conda-forge 5 | dependencies: 6 | - xeus-python 7 | - pandas 8 | - bqplot 9 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - '*' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup Python 19 | uses: actions/setup-python@v5 20 | with: 21 | python-version: '3.11' 22 | 23 | - name: Install mamba 24 | uses: mamba-org/setup-micromamba@v1 25 | with: 26 | micromamba-version: '1.5.8-0' 27 | environment-file: 'build-environment.yml' 28 | 29 | - name: Build the JupyterLite site 30 | shell: bash -l {0} 31 | run: voici build --contents content --output-dir dist 32 | 33 | - name: Upload artifact 34 | uses: actions/upload-pages-artifact@v3 35 | with: 36 | path: ./dist 37 | 38 | deploy: 39 | needs: build 40 | if: github.ref == 'refs/heads/main' 41 | permissions: 42 | pages: write 43 | id-token: write 44 | 45 | environment: 46 | name: github-pages 47 | url: ${{ steps.deployment.outputs.page_url }} 48 | 49 | runs-on: ubuntu-latest 50 | steps: 51 | - name: Deploy to GitHub Pages 52 | id: deployment 53 | uses: actions/deploy-pages@v4 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle.* 2 | lib/ 3 | node_modules/ 4 | .yarn-packages/ 5 | *.egg-info/ 6 | .ipynb_checkpoints 7 | *.tsbuildinfo 8 | 9 | # Created by https://www.gitignore.io/api/python 10 | # Edit at https://www.gitignore.io/?templates=python 11 | 12 | ### Python ### 13 | # Byte-compiled / optimized / DLL files 14 | __pycache__/ 15 | *.py[cod] 16 | *$py.class 17 | 18 | # C extensions 19 | *.so 20 | 21 | # Distribution / packaging 22 | .Python 23 | build/ 24 | develop-eggs/ 25 | dist/ 26 | downloads/ 27 | eggs/ 28 | .eggs/ 29 | lib/ 30 | lib64/ 31 | parts/ 32 | sdist/ 33 | var/ 34 | wheels/ 35 | pip-wheel-metadata/ 36 | share/python-wheels/ 37 | .installed.cfg 38 | *.egg 39 | MANIFEST 40 | 41 | # PyInstaller 42 | # Usually these files are written by a python script from a template 43 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 44 | *.manifest 45 | *.spec 46 | 47 | # Installer logs 48 | pip-log.txt 49 | pip-delete-this-directory.txt 50 | 51 | # Unit test / coverage reports 52 | htmlcov/ 53 | .tox/ 54 | .nox/ 55 | .coverage 56 | .coverage.* 57 | .cache 58 | nosetests.xml 59 | coverage.xml 60 | *.cover 61 | .hypothesis/ 62 | .pytest_cache/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | .spyproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | 93 | # Mr Developer 94 | .mr.developer.cfg 95 | .project 96 | .pydevproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | .dmypy.json 104 | dmypy.json 105 | 106 | # Pyre type checker 107 | .pyre/ 108 | 109 | # OS X stuff 110 | *.DS_Store 111 | 112 | # End of https://www.gitignore.io/api/python 113 | 114 | # jupyterlite 115 | *.doit.db 116 | _output 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Voici demo 2 | 3 | [![lite-badge](https://jupyterlite.rtfd.io/en/latest/_static/badge.svg)](https://voila-dashboards.github.io/voici-demo) 4 | 5 | [Voici](https://github.com/voila-dashboards/voici) deployed as a static site to GitHub Pages, for demo purposes. 6 | 7 | It uses [jupyterlite-xeus](https://github.com/jupyterlite/xeus) to build the Emscripten environment, including the **xeus-python** kernel and run dependencies. 8 | 9 | ## ✨ Try it in your browser ✨ 10 | 11 | https://voila-dashboards.github.io/voici-demo 12 | 13 | ## 💡 How to make your own deployment 14 | 15 | https://user-images.githubusercontent.com/21197331/223079815-0ea78df4-5173-4adc-a2e4-e10b9593a9f4.webm 16 | 17 | Then your site will be published under https://{USERNAME}.github.io/{DEMO_REPO_NAME} 18 | 19 | ## 📦 How to install extra packages 20 | 21 | You can pre-install extra packages by adding them to the ``environment.yml`` file. 22 | 23 | For example, if you want to create a Voici deployment with NumPy and Matplotlib pre-installed, you would need to edit the ``environment.yml`` file as following: 24 | 25 | ```yml 26 | name: voici 27 | channels: 28 | - https://repo.mamba.pm/emscripten-forge 29 | - conda-forge 30 | dependencies: 31 | - xeus-python 32 | - numpy 33 | - matplotlib 34 | ``` 35 | 36 | Only ``no-arch`` packages from ``conda-forge`` and packages from ``emscripten-forge`` can be installed. 37 | - **How do I know if a package is ``no-arch`` on ``conda-forge``?** ``no-arch`` means that the package is OS-independent, usually pure-python packages are ``no-arch``. To check if your package is ``no-arch`` on ``conda-forge``, check if the "Platform" entry is "no-arch" in the https://beta.mamba.pm/channels/conda-forge?tab=packages page. If your package is not ``no-arch`` but is a pure Python package, then you should probably update the feedstock to turn your package into a ``no-arch`` one. 38 | ![](https://raw.githubusercontent.com/jupyterlite/xeus-python-demo/main/noarch.png) 39 | - **How do I know if my package is on ``emscripten-forge``?** You can see the list of packages pubished on ``emscripten-forge`` [here](https://beta.mamba.pm/channels/emscripten-forge?tab=packages). In case your package is missing, or it's not up-to-date, feel free to open an issue or a PR on https://github.com/emscripten-forge/recipes. 40 | -------------------------------------------------------------------------------- /content/demo.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": { 7 | "tags": [] 8 | }, 9 | "outputs": [], 10 | "source": [ 11 | "import pandas as pd\n", 12 | "import numpy as np\n", 13 | "import ipywidgets as widgets\n", 14 | "from IPython.display import HTML" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": null, 20 | "metadata": { 21 | "tags": [] 22 | }, 23 | "outputs": [], 24 | "source": [ 25 | "from bqplot import Figure, Scatter, Axis, LinearScale" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "tags": [] 33 | }, 34 | "outputs": [], 35 | "source": [ 36 | "EXPLANATION = \"\"\"\\\n", 37 | "
\n", 38 | "

Compare different development indicators.

\n", 39 | "\n", 40 | "

Select what indicators to plot in the dropdowns, and use the slider\n", 41 | "to sub-select a fraction of years to include in the plot.

\n", 42 | "\n", 43 | "

Data and idea copied from the \n", 44 | "Plotly Dash documentation.

\n", 45 | "\n", 46 | "

This example demonstrates combining \n", 47 | "bqplot with Jupyter widgets.

\n", 48 | "
\n", 49 | "\"\"\"" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": { 56 | "tags": [] 57 | }, 58 | "outputs": [], 59 | "source": [ 60 | "HTML(\"\"\"\\\n", 61 | "\n", 87 | "\"\"\")" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": null, 93 | "metadata": { 94 | "tags": [] 95 | }, 96 | "outputs": [], 97 | "source": [ 98 | "class App:\n", 99 | "\n", 100 | " def __init__(self, df):\n", 101 | " self._df = df\n", 102 | " available_indicators = self._df['Indicator Name'].unique()\n", 103 | " self._x_dropdown = self._create_indicator_dropdown(available_indicators, 0)\n", 104 | " self._y_dropdown = self._create_indicator_dropdown(available_indicators, 1)\n", 105 | "\n", 106 | " x_scale = LinearScale()\n", 107 | " y_scale = LinearScale()\n", 108 | "\n", 109 | " self._x_axis = Axis(scale=x_scale, label=\"X\")\n", 110 | " self._y_axis = Axis(scale=y_scale, orientation=\"vertical\", label=\"Y\")\n", 111 | "\n", 112 | " self._scatter = Scatter(\n", 113 | " x=[], y=[], scales={\"x\": x_scale, \"y\": y_scale}\n", 114 | " )\n", 115 | "\n", 116 | " self._figure = Figure(marks=[self._scatter], axes=[self._x_axis, self._y_axis], layout=dict(width=\"99%\"), animation_duration=1000)\n", 117 | "\n", 118 | " self._year_slider, year_slider_box = self._create_year_slider(\n", 119 | " min(df['Year']), max(df['Year'])\n", 120 | " )\n", 121 | " _app_container = widgets.VBox([\n", 122 | " widgets.HBox([self._x_dropdown, self._y_dropdown]),\n", 123 | " self._figure,\n", 124 | " year_slider_box\n", 125 | " ], layout=widgets.Layout(align_items='center', flex='3 0 auto'))\n", 126 | " self.container = widgets.VBox([\n", 127 | " widgets.HTML(\n", 128 | " (\n", 129 | " '

Development indicators. A Voici dashboard, running entirely in your browser!

'\n", 130 | " '

Link to code

'\n", 131 | " ),\n", 132 | " layout=widgets.Layout(margin='0 0 5em 0')\n", 133 | " ),\n", 134 | " widgets.HBox([\n", 135 | " _app_container,\n", 136 | " widgets.HTML(EXPLANATION, layout=widgets.Layout(margin='0 0 0 2em'))\n", 137 | " ])\n", 138 | " ], layout=widgets.Layout(flex='1 1 auto', margin='0 auto 0 auto', max_width='1024px'))\n", 139 | " self._update_app()\n", 140 | "\n", 141 | " @classmethod\n", 142 | " def from_csv(cls, path):\n", 143 | " df = pd.read_csv(path)\n", 144 | " return cls(df)\n", 145 | "\n", 146 | " def _create_indicator_dropdown(self, indicators, initial_index):\n", 147 | " dropdown = widgets.Dropdown(options=indicators, value=indicators[initial_index])\n", 148 | " dropdown.observe(self._on_change, names=['value'])\n", 149 | " return dropdown\n", 150 | "\n", 151 | " def _create_year_slider(self, min_year, max_year):\n", 152 | " year_slider_label = widgets.Label('Year range: ')\n", 153 | " year_slider = widgets.IntRangeSlider(\n", 154 | " min=min_year, max=max_year,\n", 155 | " layout=widgets.Layout(width='500px'),\n", 156 | " continuous_update=False\n", 157 | " )\n", 158 | " year_slider.observe(self._on_change, names=['value'])\n", 159 | " year_slider_box = widgets.HBox([year_slider_label, year_slider])\n", 160 | " return year_slider, year_slider_box\n", 161 | "\n", 162 | " def _on_change(self, _):\n", 163 | " self._update_app()\n", 164 | "\n", 165 | " def _update_app(self):\n", 166 | " x_indicator = self._x_dropdown.value\n", 167 | " y_indicator = self._y_dropdown.value\n", 168 | " year_range = self._year_slider.value\n", 169 | "\n", 170 | " with self._scatter.hold_sync():\n", 171 | " df = self._df[self._df['Year'].between(*year_range)].dropna()\n", 172 | " x = df[df['Indicator Name'] == x_indicator]['Value']\n", 173 | " y = df[df['Indicator Name'] == y_indicator]['Value']\n", 174 | "\n", 175 | " self._x_axis.label = x_indicator\n", 176 | " self._y_axis.label = y_indicator\n", 177 | "\n", 178 | " self._scatter.default_opacities = [0.2]\n", 179 | "\n", 180 | " self._scatter.x = x\n", 181 | " self._scatter.y = y" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "metadata": { 188 | "tags": [] 189 | }, 190 | "outputs": [], 191 | "source": [ 192 | "app = App.from_csv(\"indicators.csv\")\n", 193 | "\n", 194 | "app.container" 195 | ] 196 | } 197 | ], 198 | "metadata": { 199 | "kernelspec": { 200 | "display_name": "Python 3 (ipykernel)", 201 | "language": "python", 202 | "name": "python3" 203 | }, 204 | "language_info": { 205 | "codemirror_mode": { 206 | "name": "ipython", 207 | "version": 3 208 | }, 209 | "file_extension": ".py", 210 | "mimetype": "text/x-python", 211 | "name": "python", 212 | "nbconvert_exporter": "python", 213 | "pygments_lexer": "ipython3", 214 | "version": "3.11.3" 215 | }, 216 | "vscode": { 217 | "interpreter": { 218 | "hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a" 219 | } 220 | } 221 | }, 222 | "nbformat": 4, 223 | "nbformat_minor": 4 224 | } 225 | --------------------------------------------------------------------------------