├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app.py ├── assets ├── .DS_Store ├── example_pic.png ├── favicon.ico ├── logo-dash.jpg ├── logo-small.png └── logo.png ├── custom_utilities ├── custom_functions.py └── custom_functions0.py ├── data └── analytics_columns ├── poetry.lock ├── presentation.py ├── pyproject.toml ├── requirements.txt ├── server.py └── slides ├── content.py ├── graph.py ├── intro.py ├── last.py ├── picture.py └── template.py /.dockerignore: -------------------------------------------------------------------------------- 1 | README.md 2 | LICENSE 3 | pyproject.toml 4 | __pycache__ 5 | .gitignore -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | 107 | fly.toml -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-slim-bullseye 2 | 3 | ENV PYTHONUNBUFFERED True 4 | ENV APP_HOME /app 5 | WORKDIR $APP_HOME 6 | COPY . ./ 7 | 8 | RUN pip install --no-cache-dir -r requirements.txt 9 | 10 | CMD ["gunicorn", "--worker-class","gevent","--workers","2","--worker-connections=500","app:server", "-b", "0.0.0.0:8080"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2023 Russell Romney 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dash-slides 2 | 3 | Make easy interactive presentation slides in Python with Dash. 4 | 5 | Example app at https://dash-slides.fly.dev/ 6 | 7 | This is a Dash app with built in slide navigation, logo, web title, etc. Just run `python3 app.py` in a terminal to see it work! 8 | 9 | ## To use: 10 | 11 | ```shell 12 | git clone https://github.com/russellromney/dash-slides 13 | cd dash-slides 14 | ``` 15 | 16 | Using pip virtualenv: 17 | 18 | ```shell 19 | virtualenv env 20 | source env/bin/activate 21 | pip install -r requirements.txt 22 | python3 app.py 23 | ``` 24 | 25 | Or, my preferred way, using `poetry`: 26 | 27 | ```shell 28 | poetry shell 29 | poetry install 30 | python3 app.py 31 | ``` 32 | 33 | ![example gif](https://raw.githubusercontent.com/russellromney/dash-slides/master/example/assets/example_gif.gif) 34 | 35 | 36 | **Making your own presentation** 37 | 38 | 1. Download, clone, or fork `dash-slides/`. 39 | 1. Delete the example slides and add your own slides (individual Dash apps) in `slides/` as files with content and associated callbacks, with a filename like `.py`. 40 | - Slide names must be valid Python variable names e.g. `example.py` or `_intro5.py` but not `my great slide.py` or `5 people.py`. 41 | - Each slide's layout is stored in a variable named `content`, e.g. `content = html.Div...` (similar to multipage Dash app where each page has a `layout = html.Div...` 42 | - Every slide needs `from app import app` at the beginning 43 | 3. Configure the presentation in `presentation.py`: 44 | - List your slides' names in order (without the `.py`) in `slide_order` 45 | - e.g. `slide_order = ['intro','template','last_slide','end']` 46 | 2. Store custom functions, utilities, objects, etc. in `custom_utilities/` or however else you'd like. 47 | 4. `pip install dash dash-bootstrap-components` 48 | - the navigation depends on it 49 | 50 | Then run it like a normal Dash app with `python app.py` or using Gunicorn or whatever else you'd normally use with Dash or Flask. 51 | 52 | For the curious, there's a Dockerfile included to run this with `gunicorn` workers. 53 | 54 | How this would look: 55 | ``` 56 | # files structure 57 | /dash-slides 58 | /assets 59 | /slides 60 | intro.py 61 | body1.py 62 | body2.py 63 | conclusion.py 64 | index.py 65 | app.py 66 | presentation.py 67 | 68 | # in /slides/presentation.py 69 | slide_order = [ 70 | 'intro', 71 | 'body1', 72 | 'body2', 73 | 'conclusion' 74 | ] 75 | ... 76 | ``` 77 | 78 | --- 79 | 80 | ## More custom stuff 81 | 82 | **Logo:** Replace `assets/logo.png` with your own file named `logo.png` for your logo to appear on all slides. 83 | 84 | **Favicon:** Like a normal Dash app, replace `assets/favicon.ico` with your own favicon for an even more custom experience. 85 | 86 | **Title:** Give your presentation a title by changing `presentation_title`. 87 | 88 | **Custom navigation text:** Replace `prev_text` and `next_text` values with new words to have any navigation text you want. 89 | 90 | 91 | 92 | --- 93 | 94 | ## Free deployment on [fly.io](https://fly.io) 95 | 96 | You can deploy to free to https://fly.io with automatic HTTPS and a URL! 97 | 98 | I've included a `Dockerfile` for you - just install flyctl (https://fly.io/docs/hands-on/install-flyctl/) and create a free account, then do: 99 | 100 | ```shell 101 | fly launch 102 | ``` 103 | 104 | It will prompt you for an app name and a region and create a `fly.toml` file; your app will then be available at https://app-name.fly.dev. 105 | 106 | If you don't deploy immediately, or to redeploy after changes do: 107 | 108 | ```shell 109 | fly deploy 110 | ``` 111 | 112 | again. 113 | --- 114 | 115 | Made with :heart: by Russell Romney in Madison, WI and NYC. 116 | 117 | > Shoutout to Tom Begley's excellent `dash-bootstrap-components` package, used here to make prettify navigation. 118 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | from dash import html, dcc, Input, Output, State 2 | import dash_bootstrap_components as dbc 3 | import os 4 | import importlib 5 | 6 | from server import app, server 7 | from presentation import slide_order, prev_text, next_text 8 | 9 | # add the slides to the object space if they are in the slide order 10 | for x in os.listdir(os.getcwd() + "/slides"): 11 | slide_name = x.split(".")[0] 12 | if slide_name in slide_order: 13 | globals()["slide_" + slide_name] = importlib.import_module( 14 | "slides." + slide_name 15 | ) 16 | 17 | # helper function that returns dict of enumerated slide names 18 | def slide_dict(): 19 | d = {v: k for k, v in dict(enumerate(slide_order)).items()} 20 | d["/"] = 0 21 | return d 22 | 23 | 24 | nav_style = dict( 25 | textAlign="center", 26 | ) 27 | 28 | 29 | def nav_button_div(text): 30 | """helper function to return the navigation buttons easily""" 31 | return html.Div( 32 | dbc.Button( 33 | html.H4(text), style=dict(width="100%"), color="primary", outline=True 34 | ) 35 | ) 36 | 37 | 38 | # logo if there is one 39 | def get_logo(): 40 | assets = os.listdir(os.getcwd() + "/assets/") 41 | split_assets = [x.split(".")[0] for x in assets] 42 | for i, x in enumerate(split_assets): 43 | if x == "logo": 44 | return html.Img(src="assets/" + assets[i], style=dict(height="50px")) 45 | return html.Img( 46 | height="40px", 47 | src="https://github.com/russellromney/dash-slides/assets/raw/logo.png", 48 | ) 49 | 50 | 51 | app.layout = html.Div( 52 | [ 53 | # URL control 54 | dcc.Location(id="url", refresh=False), 55 | # navigation header 56 | dbc.Container( 57 | fluid=True, 58 | children=[ 59 | html.Div(id="current-slide", style=dict(display="none", children="")), 60 | # nav div 61 | dbc.Row( 62 | style=dict(height="auto", position="sticky", margin="10px"), 63 | children=[ 64 | # logo 65 | dbc.Col(width=2, style=nav_style, children=[get_logo()]), 66 | # previous 67 | dbc.Col( 68 | width=4, 69 | style=nav_style, 70 | children=[ 71 | dcc.Link( 72 | id="previous-link", 73 | href="", 74 | children=nav_button_div("<< Previous"), 75 | ), 76 | ], 77 | ), # end previous 78 | # slide count 79 | dbc.Col( 80 | width=2, 81 | style=nav_style, 82 | children=[ 83 | dbc.DropdownMenu( 84 | id="slide-count", 85 | size="lg", 86 | children=[ 87 | dbc.DropdownMenuItem( 88 | s, 89 | href="/" + s, 90 | ) 91 | for s in slide_order 92 | ], 93 | ) 94 | ], 95 | ), # end slide count 96 | # next 97 | dbc.Col( 98 | width=4, 99 | style=nav_style, 100 | children=[ 101 | dcc.Link( 102 | id="next-link", 103 | href="", 104 | children=nav_button_div("Next >>"), 105 | ), 106 | ], 107 | ), # end next 108 | ], 109 | ), 110 | ], 111 | ), 112 | # slide content 113 | html.Div(id="page-content"), 114 | ] 115 | ) 116 | 117 | ### 118 | # url function 119 | @app.callback( 120 | Output("page-content", "children"), 121 | [Input("url", "pathname")], 122 | ) 123 | def change_slide(pathname): 124 | """gets current slide goes either back a slide or forward a slide""" 125 | if pathname == "/" or pathname == "/" + slide_order[0] or pathname == None: 126 | return globals()["slide_" + slide_order[0]].content 127 | else: 128 | try: 129 | pathname = pathname.split("/")[1].strip() 130 | return globals()["slide_" + pathname].content 131 | except: 132 | return "404" 133 | 134 | 135 | ### 136 | 137 | ### 138 | # navigation functions 139 | @app.callback( 140 | [Output("next-link", "href"), Output("previous-link", "href")], 141 | [Input("current-slide", "children")], 142 | [State("url", "pathname")], 143 | ) 144 | def navigate(current_slide, pathname): 145 | """ 146 | - listens to 147 | - next/previous buttons 148 | - determines the current slide name 149 | - changes 'next' and 'previous' to the names of the slides on each side of the current slide 150 | - if this is the last or first slide, 'next' or 'previous' will just refresh the current slide 151 | """ 152 | next_slide = current_slide 153 | previous_slide = current_slide 154 | current_order = slide_dict()[current_slide] 155 | num_slides = max(slide_dict().values()) 156 | 157 | # if we're on the first slide, clicking 'previous' just refreshes the page 158 | if current_order != 0: 159 | previous_slide = slide_order[current_order - 1] 160 | # if we're on the last slide, clicking 'next' just refreshes the page 161 | if current_order != num_slides: 162 | next_slide = slide_order[current_order + 1] 163 | 164 | return next_slide, previous_slide 165 | 166 | 167 | @app.callback(Output("current-slide", "children"), [Input("url", "pathname")]) 168 | def set_slide_state(pathname): 169 | """ 170 | returns the name of the current slide based on the pathname 171 | this runs first and triggers navigate (changes the relative hrefs of 'next' and 'previous') 172 | """ 173 | if pathname == None: 174 | return "/" 175 | if "/" in pathname: 176 | if pathname == "/": 177 | return pathname 178 | return pathname.split("/")[1].strip() 179 | 180 | 181 | @app.callback(Output("slide-count", "label"), [Input("current-slide", "children")]) 182 | def update_slide_count(current_slide): 183 | """shows the current slide number out of the total""" 184 | total = len(slide_order) 185 | current = slide_dict()[current_slide] + 1 186 | return "{}/{}".format(current, total) 187 | 188 | 189 | if __name__ == "__main__": 190 | app.run_server( 191 | port=8050, 192 | host="localhost", 193 | debug=False, 194 | ) 195 | -------------------------------------------------------------------------------- /assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/russellromney/dash-slides/846ed149cc8e9dd9213617051531bc9983bbc735/assets/.DS_Store -------------------------------------------------------------------------------- /assets/example_pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/russellromney/dash-slides/846ed149cc8e9dd9213617051531bc9983bbc735/assets/example_pic.png -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/russellromney/dash-slides/846ed149cc8e9dd9213617051531bc9983bbc735/assets/favicon.ico -------------------------------------------------------------------------------- /assets/logo-dash.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/russellromney/dash-slides/846ed149cc8e9dd9213617051531bc9983bbc735/assets/logo-dash.jpg -------------------------------------------------------------------------------- /assets/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/russellromney/dash-slides/846ed149cc8e9dd9213617051531bc9983bbc735/assets/logo-small.png -------------------------------------------------------------------------------- /assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/russellromney/dash-slides/846ed149cc8e9dd9213617051531bc9983bbc735/assets/logo.png -------------------------------------------------------------------------------- /custom_utilities/custom_functions.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import random 3 | 4 | # file for custom functions 5 | 6 | 7 | def my_function(): 8 | return 0 9 | 10 | 11 | def print_lorem_ipsum(): 12 | r = requests.get( 13 | "https://baconipsum.com/api/?type=meat-and-filler¶s=5&start-with-lorem=1" 14 | ) 15 | # r = requests.get('https://loripsum.net/api/20/medium/plaintext') 16 | return r.json() 17 | 18 | 19 | def new_random_colors(): 20 | return dict( 21 | background="rgba({},{},{},.9)".format( 22 | *[random.randint(100, 255) for x in range(3)] 23 | ), 24 | maxWidth="700px", 25 | textAlign="center", 26 | margin="auto", 27 | ) 28 | -------------------------------------------------------------------------------- /custom_utilities/custom_functions0.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import random 3 | 4 | # file for custom functions 5 | 6 | 7 | def my_function(): 8 | return 0 9 | 10 | 11 | def print_lorem_ipsum(): 12 | r = requests.get("https://loripsum.net/api/20/medium/plaintext") 13 | return r.text 14 | 15 | 16 | def new_random_colors(): 17 | return dict( 18 | background="rgba({},{},{},.9)".format( 19 | *[random.randint(100, 255) for x in range(3)] 20 | ), 21 | maxWidth="700px", 22 | textAlign="center", 23 | margin="auto", 24 | ) 25 | -------------------------------------------------------------------------------- /data/analytics_columns: -------------------------------------------------------------------------------- 1 | ['id', 2 | 'ip_address', 3 | 'dt', 4 | 'pathname', 5 | 'location', 6 | 'session_id', 7 | 'referrer', 8 | '_referrer', 9 | 'platform', 10 | 'browser', 11 | 'version', 12 | 'screen_res', 13 | 'continent', 14 | 'country', 15 | 'city', 16 | 'lat', 17 | 'long', 18 | 'time_zone', 19 | 'lang', 20 | '_lang'] 21 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "certifi" 3 | version = "2022.12.7" 4 | description = "Python package for providing Mozilla's CA Bundle." 5 | category = "main" 6 | optional = false 7 | python-versions = ">=3.6" 8 | 9 | [[package]] 10 | name = "cffi" 11 | version = "1.15.1" 12 | description = "Foreign Function Interface for Python calling C code." 13 | category = "main" 14 | optional = false 15 | python-versions = "*" 16 | 17 | [package.dependencies] 18 | pycparser = "*" 19 | 20 | [[package]] 21 | name = "charset-normalizer" 22 | version = "3.0.1" 23 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 24 | category = "main" 25 | optional = false 26 | python-versions = "*" 27 | 28 | [[package]] 29 | name = "click" 30 | version = "8.1.3" 31 | description = "Composable command line interface toolkit" 32 | category = "main" 33 | optional = false 34 | python-versions = ">=3.7" 35 | 36 | [package.dependencies] 37 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 38 | 39 | [[package]] 40 | name = "colorama" 41 | version = "0.4.6" 42 | description = "Cross-platform colored terminal text." 43 | category = "main" 44 | optional = false 45 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 46 | 47 | [[package]] 48 | name = "dash" 49 | version = "2.8.1" 50 | description = "A Python framework for building reactive web-apps. Developed by Plotly." 51 | category = "main" 52 | optional = false 53 | python-versions = ">=3.6" 54 | 55 | [package.dependencies] 56 | dash-core-components = "2.0.0" 57 | dash-html-components = "2.0.0" 58 | dash-table = "5.0.0" 59 | Flask = ">=1.0.4" 60 | plotly = ">=5.0.0" 61 | 62 | [package.extras] 63 | celery = ["redis (>=3.5.3)", "celery[redis] (>=5.1.2)", "importlib-metadata (<5)"] 64 | ci = ["dash-flow-example (==0.0.5)", "dash-dangerously-set-inner-html", "flake8 (==3.9.2)", "flaky (==3.7.0)", "flask-talisman (==1.0.0)", "mimesis", "mock (==4.0.3)", "numpy", "preconditions", "pylint (==2.13.5)", "pytest-mock", "pytest-sugar (==0.9.6)", "pytest-rerunfailures", "black (==21.6b0)", "isort (==4.3.21)", "orjson (==3.5.4)", "pyarrow (<3)", "pandas (==1.1.5)", "xlrd (<2)", "black (==22.3.0)", "orjson (==3.6.7)", "pyarrow", "openpyxl", "pandas (>=1.4.0)", "xlrd (>=2.0.1)"] 65 | compress = ["flask-compress"] 66 | dev = ["coloredlogs (>=15.0.1)", "fire (>=0.4.0)", "PyYAML (>=5.4.1)"] 67 | diskcache = ["diskcache (>=5.2.1)", "multiprocess (>=0.70.12)", "psutil (>=5.8.0)"] 68 | testing = ["beautifulsoup4 (>=4.8.2)", "lxml (>=4.6.2)", "percy (>=2.0.2)", "pytest (>=6.0.2)", "requests[security] (>=2.21.0)", "selenium (>=3.141.0,<=4.2.0)", "waitress (>=1.4.4)", "multiprocess (>=0.70.12)", "psutil (>=5.8.0)", "cryptography (<3.4)"] 69 | 70 | [[package]] 71 | name = "dash-bootstrap-components" 72 | version = "1.3.1" 73 | description = "Bootstrap themed components for use in Plotly Dash" 74 | category = "main" 75 | optional = false 76 | python-versions = ">=3.7, <4" 77 | 78 | [package.dependencies] 79 | dash = ">=2.0.0" 80 | 81 | [package.extras] 82 | pandas = ["numpy", "pandas"] 83 | 84 | [[package]] 85 | name = "dash-core-components" 86 | version = "2.0.0" 87 | description = "Core component suite for Dash" 88 | category = "main" 89 | optional = false 90 | python-versions = "*" 91 | 92 | [[package]] 93 | name = "dash-html-components" 94 | version = "2.0.0" 95 | description = "Vanilla HTML components for Dash" 96 | category = "main" 97 | optional = false 98 | python-versions = "*" 99 | 100 | [[package]] 101 | name = "dash-table" 102 | version = "5.0.0" 103 | description = "Dash table" 104 | category = "main" 105 | optional = false 106 | python-versions = "*" 107 | 108 | [[package]] 109 | name = "flask" 110 | version = "2.2.3" 111 | description = "A simple framework for building complex web applications." 112 | category = "main" 113 | optional = false 114 | python-versions = ">=3.7" 115 | 116 | [package.dependencies] 117 | click = ">=8.0" 118 | importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} 119 | itsdangerous = ">=2.0" 120 | Jinja2 = ">=3.0" 121 | Werkzeug = ">=2.2.2" 122 | 123 | [package.extras] 124 | async = ["asgiref (>=3.2)"] 125 | dotenv = ["python-dotenv"] 126 | 127 | [[package]] 128 | name = "gevent" 129 | version = "22.10.2" 130 | description = "Coroutine-based network library" 131 | category = "main" 132 | optional = false 133 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5" 134 | 135 | [package.dependencies] 136 | cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} 137 | greenlet = {version = ">=2.0.0", markers = "platform_python_implementation == \"CPython\""} 138 | "zope.event" = "*" 139 | "zope.interface" = "*" 140 | 141 | [package.extras] 142 | dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] 143 | docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput", "zope.schema"] 144 | monitor = ["psutil (>=5.7.0)"] 145 | recommended = ["cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "selectors2", "backports.socketpair", "psutil (>=5.7.0)"] 146 | test = ["requests", "objgraph", "cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "selectors2", "futures", "mock", "backports.socketpair", "contextvars (==2.4)", "coverage (>=5.0)", "coveralls (>=1.7.0)", "psutil (>=5.7.0)"] 147 | 148 | [[package]] 149 | name = "greenlet" 150 | version = "2.0.2" 151 | description = "Lightweight in-process concurrent programming" 152 | category = "main" 153 | optional = false 154 | python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" 155 | 156 | [package.extras] 157 | docs = ["sphinx", "docutils (<0.18)"] 158 | test = ["objgraph", "psutil"] 159 | 160 | [[package]] 161 | name = "gunicorn" 162 | version = "20.1.0" 163 | description = "WSGI HTTP Server for UNIX" 164 | category = "main" 165 | optional = false 166 | python-versions = ">=3.5" 167 | 168 | [package.extras] 169 | eventlet = ["eventlet (>=0.24.1)"] 170 | gevent = ["gevent (>=1.4.0)"] 171 | setproctitle = ["setproctitle"] 172 | tornado = ["tornado (>=0.2)"] 173 | 174 | [[package]] 175 | name = "idna" 176 | version = "3.4" 177 | description = "Internationalized Domain Names in Applications (IDNA)" 178 | category = "main" 179 | optional = false 180 | python-versions = ">=3.5" 181 | 182 | [[package]] 183 | name = "importlib-metadata" 184 | version = "6.0.0" 185 | description = "Read metadata from Python packages" 186 | category = "main" 187 | optional = false 188 | python-versions = ">=3.7" 189 | 190 | [package.dependencies] 191 | zipp = ">=0.5" 192 | 193 | [package.extras] 194 | docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"] 195 | perf = ["ipython"] 196 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8", "importlib-resources (>=1.3)"] 197 | 198 | [[package]] 199 | name = "itsdangerous" 200 | version = "2.1.2" 201 | description = "Safely pass data to untrusted environments and back." 202 | category = "main" 203 | optional = false 204 | python-versions = ">=3.7" 205 | 206 | [[package]] 207 | name = "jinja2" 208 | version = "3.1.2" 209 | description = "A very fast and expressive template engine." 210 | category = "main" 211 | optional = false 212 | python-versions = ">=3.7" 213 | 214 | [package.dependencies] 215 | MarkupSafe = ">=2.0" 216 | 217 | [package.extras] 218 | i18n = ["Babel (>=2.7)"] 219 | 220 | [[package]] 221 | name = "markupsafe" 222 | version = "2.1.2" 223 | description = "Safely add untrusted strings to HTML/XML markup." 224 | category = "main" 225 | optional = false 226 | python-versions = ">=3.7" 227 | 228 | [[package]] 229 | name = "plotly" 230 | version = "5.13.1" 231 | description = "An open-source, interactive data visualization library for Python" 232 | category = "main" 233 | optional = false 234 | python-versions = ">=3.6" 235 | 236 | [package.dependencies] 237 | tenacity = ">=6.2.0" 238 | 239 | [[package]] 240 | name = "pycparser" 241 | version = "2.21" 242 | description = "C parser in Python" 243 | category = "main" 244 | optional = false 245 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 246 | 247 | [[package]] 248 | name = "requests" 249 | version = "2.28.2" 250 | description = "Python HTTP for Humans." 251 | category = "main" 252 | optional = false 253 | python-versions = ">=3.7, <4" 254 | 255 | [package.dependencies] 256 | certifi = ">=2017.4.17" 257 | charset-normalizer = ">=2,<4" 258 | idna = ">=2.5,<4" 259 | urllib3 = ">=1.21.1,<1.27" 260 | 261 | [package.extras] 262 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 263 | use_chardet_on_py3 = ["chardet (>=3.0.2,<6)"] 264 | 265 | [[package]] 266 | name = "tenacity" 267 | version = "8.2.1" 268 | description = "Retry code until it succeeds" 269 | category = "main" 270 | optional = false 271 | python-versions = ">=3.6" 272 | 273 | [package.extras] 274 | doc = ["reno", "sphinx", "tornado (>=4.5)"] 275 | 276 | [[package]] 277 | name = "urllib3" 278 | version = "1.26.14" 279 | description = "HTTP library with thread-safe connection pooling, file post, and more." 280 | category = "main" 281 | optional = false 282 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 283 | 284 | [package.extras] 285 | brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] 286 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "urllib3-secure-extra", "ipaddress"] 287 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 288 | 289 | [[package]] 290 | name = "werkzeug" 291 | version = "2.2.3" 292 | description = "The comprehensive WSGI web application library." 293 | category = "main" 294 | optional = false 295 | python-versions = ">=3.7" 296 | 297 | [package.dependencies] 298 | MarkupSafe = ">=2.1.1" 299 | 300 | [package.extras] 301 | watchdog = ["watchdog"] 302 | 303 | [[package]] 304 | name = "zipp" 305 | version = "3.15.0" 306 | description = "Backport of pathlib-compatible object wrapper for zip files" 307 | category = "main" 308 | optional = false 309 | python-versions = ">=3.7" 310 | 311 | [package.extras] 312 | docs = ["sphinx (>=3.5)", "jaraco.packaging (>=9)", "rst.linker (>=1.9)", "furo", "sphinx-lint", "jaraco.tidelift (>=1.4)"] 313 | testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "flake8 (<5)", "pytest-cov", "pytest-enabler (>=1.3)", "jaraco.itertools", "jaraco.functools", "more-itertools", "big-o", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "pytest-flake8"] 314 | 315 | [[package]] 316 | name = "zope.event" 317 | version = "4.6" 318 | description = "Very basic event publishing system" 319 | category = "main" 320 | optional = false 321 | python-versions = "*" 322 | 323 | [package.extras] 324 | docs = ["sphinx"] 325 | test = ["zope.testrunner"] 326 | 327 | [[package]] 328 | name = "zope.interface" 329 | version = "5.5.2" 330 | description = "Interfaces for Python" 331 | category = "main" 332 | optional = false 333 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 334 | 335 | [package.extras] 336 | docs = ["sphinx", "repoze.sphinx.autointerface"] 337 | test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] 338 | testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] 339 | 340 | [metadata] 341 | lock-version = "1.1" 342 | python-versions = "^3.8" 343 | content-hash = "657a5255e25cf1ff8d6477195c31249078edf5d1bf47caf0039e3d6694c2cedb" 344 | 345 | [metadata.files] 346 | certifi = [] 347 | cffi = [] 348 | charset-normalizer = [] 349 | click = [] 350 | colorama = [] 351 | dash = [] 352 | dash-bootstrap-components = [] 353 | dash-core-components = [] 354 | dash-html-components = [] 355 | dash-table = [] 356 | flask = [] 357 | gevent = [] 358 | greenlet = [] 359 | gunicorn = [] 360 | idna = [] 361 | importlib-metadata = [] 362 | itsdangerous = [] 363 | jinja2 = [] 364 | markupsafe = [] 365 | plotly = [] 366 | pycparser = [] 367 | requests = [] 368 | tenacity = [] 369 | urllib3 = [] 370 | werkzeug = [] 371 | zipp = [] 372 | "zope.event" = [] 373 | "zope.interface" = [] 374 | -------------------------------------------------------------------------------- /presentation.py: -------------------------------------------------------------------------------- 1 | ### 2 | # EDIT THIS PAGE BUT DO NOT REMOVE THE OBJECTS 3 | ### 4 | 5 | # the order of the slides, using the names of the files 6 | # NOTE - slide filenames MUST BE VALID PYTHON VARIABLE NAMES 7 | slide_order = [ 8 | "intro", 9 | "graph", 10 | # 'template', 11 | "content", 12 | "picture", 13 | "last", 14 | ] 15 | 16 | # the text that appears in the tab 17 | presentation_title = "Dash Slides" 18 | 19 | # the text that appears in the previous and next buttons 20 | prev_text = "<< Previous" 21 | next_text = "Next >>" 22 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "dash-slides" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | license = "MIT" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.8" 10 | dash = "^2.8.1" 11 | dash-bootstrap-components = "^1.3.1" 12 | gunicorn = "^20.1.0" 13 | gevent = "^22.10.2" 14 | requests = "^2.28.2" 15 | 16 | [tool.poetry.dev-dependencies] 17 | 18 | [build-system] 19 | requires = ["poetry-core>=1.0.0"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2022.12.7; python_version >= "3.7" and python_version < "4" 2 | cffi==1.15.1; platform_python_implementation == "CPython" and sys_platform == "win32" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_version > "3.5") 3 | charset-normalizer==3.0.1; python_version >= "3.7" and python_version < "4" 4 | click==8.1.3; python_version >= "3.7" 5 | colorama==0.4.6; python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" or platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.7.0" 6 | dash-bootstrap-components==1.3.1; python_version >= "3.7" and python_version < "4" 7 | dash-core-components==2.0.0; python_version >= "3.7" and python_version < "4" 8 | dash-html-components==2.0.0; python_version >= "3.7" and python_version < "4" 9 | dash-table==5.0.0; python_version >= "3.7" and python_version < "4" 10 | dash==2.8.1; python_version >= "3.6" 11 | flask==2.2.3; python_version >= "3.7" and python_version < "4" 12 | gevent==22.10.2; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_version > "3.5") 13 | greenlet==2.0.2; python_version >= "2.7" and python_full_version < "3.0.0" and platform_python_implementation == "CPython" or python_version > "3.5" and python_full_version < "3.0.0" and platform_python_implementation == "CPython" or python_version > "3.5" and platform_python_implementation == "CPython" and python_full_version >= "3.5.0" 14 | gunicorn==20.1.0; python_version >= "3.5" 15 | idna==3.4; python_version >= "3.7" and python_version < "4" 16 | importlib-metadata==6.0.0; python_version < "3.10" and python_version >= "3.7" 17 | itsdangerous==2.1.2; python_version >= "3.7" 18 | jinja2==3.1.2; python_version >= "3.7" 19 | markupsafe==2.1.2; python_version >= "3.7" 20 | plotly==5.13.1; python_version >= "3.7" and python_version < "4" 21 | pycparser==2.21; python_version >= "2.7" and python_full_version < "3.0.0" and platform_python_implementation == "CPython" and sys_platform == "win32" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_version > "3.5") or platform_python_implementation == "CPython" and sys_platform == "win32" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_version > "3.5") and python_full_version >= "3.4.0" 22 | requests==2.28.2; python_version >= "3.7" and python_version < "4" 23 | tenacity==8.2.1; python_version >= "3.6" 24 | urllib3==1.26.14; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "3.7" and python_version < "4" and python_full_version >= "3.6.0" 25 | werkzeug==2.2.3; python_version >= "3.7" 26 | zipp==3.15.0; python_version < "3.10" and python_version >= "3.7" 27 | zope.event==4.6; python_version >= "2.7" and python_full_version < "3.0.0" or python_version > "3.5" 28 | zope.interface==5.5.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_version > "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version > "3.5" 29 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | from presentation import presentation_title 2 | import dash 3 | import dash_bootstrap_components as dbc 4 | 5 | external_stylesheets = [dbc.themes.BOOTSTRAP] 6 | 7 | app = dash.Dash(__name__, external_stylesheets=external_stylesheets) 8 | app.config.suppress_callback_exceptions = True 9 | app.title = presentation_title 10 | server = app.server 11 | -------------------------------------------------------------------------------- /slides/content.py: -------------------------------------------------------------------------------- 1 | # necessary imports - do not change 2 | from dash import html, dcc, Input, Output, State 3 | from server import app 4 | 5 | # 6 | 7 | # custom imports 8 | from custom_utilities.custom_functions import print_lorem_ipsum, new_random_colors 9 | from dash import no_update 10 | import dash_bootstrap_components as dbc 11 | 12 | content_page_style = dict(textAlign="center", margin="20px") 13 | 14 | content = html.Div( 15 | [ 16 | html.H1("A slide for a random lorem ipsum", style=content_page_style), 17 | html.Div( 18 | dbc.Button( 19 | children="Generate a new random lorem ipsum!", 20 | id="template-content-button", 21 | n_clicks=0, 22 | color="primary", 23 | ), 24 | style=content_page_style, 25 | ), 26 | dcc.Loading( 27 | dbc.Container( 28 | id="template-content", 29 | children=[html.P(x) for x in print_lorem_ipsum()], 30 | style=new_random_colors(), 31 | ), 32 | ), 33 | html.Div( 34 | [ 35 | "Thanks to the folks at baconipsum.net for the cool API! ", 36 | html.A("baconipsum.net", href="https://baconipsum.com/json-api/"), 37 | ], 38 | style=content_page_style, 39 | ), 40 | ] 41 | ) 42 | 43 | 44 | @app.callback( 45 | [Output("template-content", "children"), Output("template-content", "style")], 46 | [Input("template-content-button", "n_clicks")], 47 | ) 48 | def generate_template_content(n): 49 | """returns a random lorem ipsum with a random transparent background color""" 50 | if n == 0: 51 | return no_update, no_update 52 | return [html.P(x) for x in print_lorem_ipsum()], new_random_colors() 53 | -------------------------------------------------------------------------------- /slides/graph.py: -------------------------------------------------------------------------------- 1 | # necessary imports - do not change 2 | from dash import html, dcc, Input, Output, State 3 | from server import app 4 | 5 | # custom imports 6 | import plotly.graph_objs as go 7 | import dash_bootstrap_components as dbc 8 | from dash import no_update 9 | from random import randrange 10 | 11 | content = html.Div( 12 | style=dict(textAlign="center"), 13 | children=[ 14 | html.H1("Template Slide Title"), 15 | dcc.Markdown( 16 | """ 17 | This is template content! 18 | """ 19 | ), 20 | dbc.Button( 21 | "Click this to change the graph!", 22 | id="template-button", 23 | n_clicks=0, 24 | color="primary", 25 | ), 26 | dcc.Graph(id="template-graph"), 27 | dcc.Markdown( 28 | """ 29 | This is more template content 30 | """ 31 | ), 32 | ], 33 | ) 34 | 35 | 36 | @app.callback( 37 | Output("template-graph", "figure"), [Input("template-button", "n_clicks")] 38 | ) 39 | def create_template_graph(n): 40 | x_data = list(range(50)) 41 | y_data = [i + randrange(-10, 10) for i in range(50)] 42 | data = [go.Scatter(x=x_data, y=y_data)] 43 | layout = go.Layout(title="Template Graph Title") 44 | return go.Figure(data, layout) 45 | -------------------------------------------------------------------------------- /slides/intro.py: -------------------------------------------------------------------------------- 1 | # no need to delete this - it won't show up in the presentation unless you add it to presentation.py 2 | 3 | # necessary imports - do not change 4 | from dash import html, dcc, Input, Output, State 5 | from server import app 6 | 7 | # custom imports 8 | # ... 9 | 10 | 11 | content = html.Div( 12 | style=dict(textAlign="center"), 13 | children=[ 14 | html.H1("Intro Slide Title"), 15 | html.Button("Click this!", id="intro-button", n_clicks=0), 16 | html.H2(id="intro-div"), 17 | html.Br(), 18 | html.Div( 19 | html.Img( 20 | src="https://cdn-images-1.medium.com/max/2600/1*tJGJzsEJJM21-3LT1XZbyw.jpeg", 21 | style=dict(height="300px"), 22 | ) 23 | ), 24 | ], 25 | ) 26 | 27 | 28 | @app.callback(Output("intro-div", "children"), [Input("intro-button", "n_clicks")]) 29 | def create_template_graph(n): 30 | return "Button has been clicked {} times.".format(n) 31 | -------------------------------------------------------------------------------- /slides/last.py: -------------------------------------------------------------------------------- 1 | # necessary imports - do not change, and include on every slide 2 | from dash import html, dcc, Input, Output, State 3 | from server import app 4 | 5 | ### 6 | 7 | content = html.H1("End", style=dict(textAlign="center")) 8 | -------------------------------------------------------------------------------- /slides/picture.py: -------------------------------------------------------------------------------- 1 | # necessary imports - do not change 2 | from dash import html, dcc, Input, Output, State 3 | from server import app 4 | 5 | ### 6 | 7 | content = html.Div( 8 | style=dict(textAlign="center"), 9 | children=[ 10 | html.H1("A Slide for a Picture"), 11 | html.Img(src="https://picsum.photos/id/222/800/600"), 12 | ], 13 | ) 14 | -------------------------------------------------------------------------------- /slides/template.py: -------------------------------------------------------------------------------- 1 | ### 2 | # no need to delete this - it won't show up in the presentation unless you add it to presentation.py 3 | ### 4 | 5 | # necessary imports - do not change 6 | from dash import html, dcc, Input, Output, State 7 | from server import app 8 | 9 | # custom imports - delete these if you don't need them 10 | from custom_utilities.custom_functions import my_function 11 | 12 | content = html.Div( 13 | style=dict(textAlign="center"), 14 | children=[ 15 | html.H1("Template Slide Title"), 16 | html.Button("Click this!", id="template-button", n_clicks=0), 17 | html.H2(id="template-div"), 18 | ], 19 | ) 20 | 21 | 22 | @app.callback( 23 | Output("template-div", "children"), [Input("template-button", "n_clicks")] 24 | ) 25 | def create_template_graph(n): 26 | return "Button has been clicked {} times.".format(n) 27 | --------------------------------------------------------------------------------