├── .gitignore ├── README.md ├── multi_page_basics ├── app.py ├── app_dbc.py ├── app_ddk.py ├── assets │ ├── app.jpeg │ ├── birds.jpeg │ ├── home.jpeg │ └── logo.jpeg └── pages │ ├── __init__.py │ ├── historical_archive.py │ ├── not_found_404.py │ ├── outlook.py │ ├── path_variables.py │ ├── query_string.py │ ├── redirect.py │ └── snapshot.py ├── multi_page_basics_pathname_prefix ├── app.py ├── assets │ ├── app.jpeg │ ├── birds.jpeg │ ├── home.jpeg │ └── logo.jpeg └── pages │ ├── __init__.py │ ├── historical_archive.py │ ├── not_found_404.py │ ├── outlook.py │ ├── path_variables.py │ ├── query_string.py │ └── redirect.py ├── multi_page_cache_background_callback ├── app.py └── pages │ ├── example_1.py │ ├── example_2.py │ └── example_3.py ├── multi_page_dash_auth ├── app.py └── pages │ ├── __init__.py │ ├── bar_charts.py │ ├── heatmaps.py │ ├── histograms.py │ └── not_found_404.py ├── multi_page_example1 ├── app.py └── pages │ ├── __init__.py │ ├── bar_charts.py │ ├── heatmaps.py │ ├── histograms.py │ └── not_found_404.py ├── multi_page_flask_login ├── .env ├── app.py └── pages │ ├── home.py │ ├── login.py │ ├── logout.py │ ├── page-1.py │ └── page-2.py ├── multi_page_flask_login2 ├── .env ├── app.py ├── pages │ ├── home.py │ ├── login.py │ ├── logout.py │ ├── page-1.py │ └── page-2.py └── utils │ └── login_handler.py ├── multi_page_layout_functions ├── app.py └── pages │ ├── about.py │ ├── home.py │ ├── side_bar.py │ ├── topic_1.py │ ├── topic_2.py │ └── topic_3.py ├── multi_page_meta_tags ├── app.py ├── app_dbc.py ├── assets │ ├── app.jpeg │ ├── birdhouse.jpeg │ ├── birds.jpeg │ └── logo.jpeg └── pages │ ├── __init__.py │ ├── a_page.py │ ├── birds.py │ └── home.py ├── multi_page_nested_folders ├── app.py └── pages │ ├── __init__.py │ ├── chapter1 │ ├── bar-charts.py │ └── pie-chart.py │ ├── chapter2 │ ├── heatmaps.py │ └── histograms.py │ └── home.py ├── multi_page_no_pages_folder └── app.py ├── multi_page_path_variables ├── app.py └── pages │ ├── about.py │ ├── home.py │ ├── side_bar.py │ ├── topic_1.py │ ├── topic_2.py │ └── topic_3.py ├── multi_page_query_strings ├── app.py └── pages │ ├── bar_chart.py │ ├── home.py │ └── not_found_404.py ├── multi_page_store ├── app.py └── pages │ ├── __init__.py │ ├── graph.py │ └── grid.py ├── multi_page_sync_components ├── app.py └── pages │ ├── page1.py │ ├── page2.py │ └── page3.py ├── multi_page_sync_components2 ├── app.py └── pages │ ├── page1.py │ ├── page2.py │ └── page3.py ├── multi_page_table_links ├── app.py ├── assets │ └── dashAgGridComponentFunctions.js └── pages │ ├── not_found_404.py │ ├── portfolio.py │ └── stocks.py ├── multi_page_theme_switch ├── app.py └── pages │ ├── bar_charts.py │ ├── default_fig.py │ ├── heatmaps.py │ ├── histograms.py │ └── not_found_404.py ├── multi_page_update_url_from_figure ├── app.py └── pages │ ├── airports.py │ ├── flight_status.py │ └── not_found_404.py ├── multi_page_update_url_from_figure_V292 ├── app.py └── pages │ ├── flight_status.py │ └── home.py ├── multi_page_update_url_in_callback ├── app.py └── pages │ ├── home.py │ ├── not_found_404.py │ └── stocks.py ├── multi_page_update_url_in_callback_V292 ├── app.py └── pages │ ├── home.py │ ├── not_found_404.py │ └── stocks.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .idea 3 | *.egg-info 4 | build 5 | dist 6 | cache 7 | .pytest_cache 8 | __pycache__ 9 | venv 10 | .venv 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dash `pages` multi-page app demos 2 | 3 | This repo contains minimal examples of multi-page apps using the Pages feature available in dash>=2.5.1 4 | 5 | __See the Dash Documentation [Multi-Page Apps and URL Support](https://dash.plotly.com/urls)__ 6 | 7 | __:movie_camera: Don't miss the video tutorials:__ 8 | - [Introducing Dash `pages` -- A better way to make multi-page apps`](https://youtu.be/pJMZ0r84Rqs) by Adam Schroeder and Chris Parmer. 9 | - Charming Data Videos by Adam Schroeder: 10 | - [Creating Multi Page Apps - Part I ](https://youtu.be/Hc9_-ncr4nU) Getting Started 11 | - [Creating Multi Page Apps - Part II](https://www.youtube.com/watch?v=MtSgh6FOL7I) Sidebar and Layout Enhancements 12 | 13 | I hope these examples help you get started exploring all the cool features in Pages. If you find this project helpful, please consider giving it a :star: 14 | 15 | --------------------------- 16 | 17 | __Example Apps__ 18 | 19 | The easiest way to begin is by cloning this repository and running the examples on your local machine. Below is a brief description of each app. 20 | 21 | __If you are using a Jupyter Notebook:__ 22 | - Place the example your Jupyter folder 23 | - Make a notebook, and use %run as an entry in a cell. For example `run multi_page_basics/app` 24 | - Alternately, you can execute the code as cells if the app does not use the `pages` folder. (See example #17) 25 | 26 | __Other tutorials or examples using `pages`:__ 27 | 28 | 29 | 1. Adding a Blog to your Dash app. See this [Dash Community Forum post](https://community.plotly.com/t/adding-a-blog-to-your-dash-app/65955). It describes how to do this and includes [this repo](https://github.com/bradley-erickson/dash-blog-page) from @bradley-erickson. 30 | 31 | 2. See the [Dash Webb Compare](https://dash-webb-compare.herokuapp.com/ ) app live. This app shows the first images from the James Webb Space Telescope. Compare before and after images of Hubble vs Webb. The Github repo has 2 versions of the app using `pages`. 32 | - [app_pages.py](https://github.com/AnnMarieW/webb-compare/blob/master/app_pages.py) - Creates an multi-page app without using the `pages` folder. 33 | - [app_pages_no_assets.py](https://github.com/AnnMarieW/webb-compare/blob/master/app_pages_no_assets.py) - This multi-page app uses images that are hosted on GitHub so it doesn't use either the `pages` or the `assets` folder. 34 | 35 | __Tips and Tricks__ 36 | 1. [Pretty print dash.page_registry](#1-print_registry-from-dash-labs-110) - with the `print_registry()` function from dash-labs 37 | 2. [How to use dcc.Link in Markdown](#2-tada-use-dcclink-in-dccmarkdown) - for high performance page navigation from a link in a dcc.Markdown component. 38 | 3. [Avoiding duplicate ids](#3-avoiding-duplicate-ids) - Strategies for handling ids in a large multi-page app. 39 | 4. [Display loading screen when page_container is loading](https://community.plotly.com/t/displaying-loading-screen-when-pages-container-is-loading/72109/1) - Shows how to make the overall loading screen only display when there is a change to the `_pages_content` that involves a layout being changed and not changes within the layout. 40 | 5. [Preventing Query String Errors](#5-preventing-query-string-errors) 41 | --- 42 | 43 | 44 |
45 |
46 | 47 | # Example Apps 48 | ## 1. [multi_page_basics/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_basics) 49 | 50 | This folder has a minimal overview of the basic `pages features`, including: 51 | - setting the default home page 52 | - handling variables in the pathname 53 | - updating the app title and description with a function 54 | - handling variable in query strings 55 | - setting redirects 56 | - adding extra data to the `dash.page_registry` 57 | - customizing the `dash.page_registry` defaults 58 | - how images are added to meta tags 59 | - adding pages without using the pages folder 60 | 61 | The image below :point_down: is from the `path_variables` page. Note that asset "inventory" and department "branch-1001" are passed from the pathname to the layout function and are displayed on the page. 62 | 63 | ![basics](https://user-images.githubusercontent.com/72614349/174487978-ceaac40c-4421-4d86-b9a4-077a7cf85d3d.png) 64 | 65 | ---- 66 | 67 | 68 |
69 |
70 | 71 | ## 2. [multi_page_pathname_prefix/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_basics_pathname_prefix) 72 | 73 | This example shows how to use the `relative_path` attribute in `dash.page_registry` in deployment environments that use a pathname prefix. 74 | It also shows use of `dash.get_asset_url()` to get the correct path to the `assets` folder from a file in the `pages` folder. 75 | 76 | - `relative_path`: 77 | The path with `requests_pathname_prefix` prefixed before it. 78 | Use this path when specifying local URL paths that will work 79 | in environments regardless of what `requests_pathname_prefix` is. 80 | In some deployment environments, like Dash Enterprise, 81 | `requests_pathname_prefix` is set to the application name, 82 | e.g. `my-dash-app`. 83 | When working locally, `requests_pathname_prefix` might be unset and 84 | so a relative URL like `/page-2` can just be `/page-2`. 85 | However, when the app is deployed to a URL like `/my-dash-app`, then 86 | `relative_path` will be `/my-dash-app/page-2`. 87 | 88 | 89 | __Note the `/app1/` pathname prefix in the url__ :point_down: 90 | 91 | ![pathname_prefix](https://user-images.githubusercontent.com/72614349/174487979-4e9a4d6f-bad4-45b3-bef7-db59ae04a84d.png) 92 | 93 | ------- 94 | 95 | 96 |
97 |
98 | 99 | ## 3. [multi_page_cache/]() 100 | 101 | Example removed - please see #11 multi_page_store and #4 multi_page_cach_background_callbacks. 102 | 103 | 104 | --- 105 | 106 |
107 | 108 | 109 | 110 | 111 | ## 4. [multi_page_cache_background_callbacks](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_cache_background_callback) 112 | This example shows how to use caching and background callbacks in a multi-page app. The examples in the dash docs needed to be modified to make it possible to switch pages 113 | while background callbacks are running. 114 | 115 | 116 | 117 |
118 |
119 | 120 | ## 5. [multi_page_example1/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_example1) 121 | 122 | This example shows a small app with three pages with callbacks. Each page displays a figure. It uses dash-bootstrap-components with `dbc.DropdownMenu` to display the links in a navbar. 123 | 124 | ![Example1](https://user-images.githubusercontent.com/72614349/174487976-57f797b7-c2e5-4ab6-8f05-0cc62e176898.png) 125 | 126 | --- 127 |
128 |
129 | 130 | ## Authentication - Basic 131 | ## 5a. [multi_page_dash_auth](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_dash_auth) 132 | This example is the `multi_page_example1` app with `HTTP Basic Auth from the `dash-auth` package. 133 | [Basic Auth](https://dash.plotly.com/authentication#basic-auth) section of the dash docs. 134 | 135 | 136 | ## Authentication - using Flask 137 | ## 6. [multi_page_flask_login/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_flask_login) 138 | 139 | You will find two similar examples. 140 | 141 | 1. [multi_page_flask_login/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_flask_login) - original example 142 | 2. [multi_page_flask_login2/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_flask_login2) - the new and improved version contributed by @jinnyzor. See this [Dash Community Forum](https://community.plotly.com/t/dash-app-pages-with-flask-login-flow-using-flask/69507) post for more information 143 | 144 | 145 | ![flask__login](https://user-images.githubusercontent.com/72614349/174487970-74351830-b971-4874-bb3f-d33d2fdec74c.gif) 146 | 147 | ## Authentication with Flask and OAuth 148 | Here is a great example by @leberber 149 | > I have created an example Dash application that integrates with Flask for user authentication, including support for OAuth with Google. The application uses the Mantine library for UI components and supports user registration, login, and logout functionality. Additionally, it includes a theme switcher and sidebar toggle implemented via client-side callbacks 150 | - See this Dash community [forum post](https://community.plotly.com/t/dash-app-with-user-registration-login-and-logout-functionality/85291) for more information 151 | - See the code in [Github](https://github.com/leberber/google-auth-flask-session) 152 | - See the [live example](https://google-auth-flask-session.onrender.com/) Please allow for up to a minute for the app to comp up. 153 | 154 | __For other authentication options see:__ 155 | - [Dash Enterprise Auth](https://dash.plotly.com/authentication#dash-enterprise-auth) 156 | - [Dash Basic Auth](https://dash.plotly.com/authentication#basic-auth) 157 | 158 | 159 | ----- 160 | 161 | 162 |
163 |
164 | 165 | ## 7. [multi_page_layout_functions/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_layout_functions) 166 | This app demonstrates how to create a sub-topics sidebar that is only used in certain pages. It shows how to use 167 | functions to access the `dash.page_registry` from within the `pages` folder after it's finished building. 168 | For more details see the dash docs: https://dash.plotly.com/urls#dash-page-registry 169 | 170 | It also shows how arbitrary data added to the `dash_page_registry` can be used. In this app, we add `top_nav=True` on the 171 | three pages we want to include in the top nav bar. Then we create the nav links like this: 172 | 173 | ```python 174 | dbc.Nav( 175 | [ 176 | dbc.NavLink(page["name"], href=page["path"]) 177 | for page in dash.page_registry.values() 178 | if page.get("top_nav") 179 | ], 180 | ), 181 | ``` 182 | 183 | ![pages-side-nav-funct](https://github.com/AnnMarieW/dash-multi-page-app-demos/assets/72614349/ca6bd011-57e3-4c5f-b72a-c64a97437f70) 184 | 185 | --- 186 | 187 |
188 |
189 | 190 | ## 7b [multi_page_path_variables](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_path_variables) 191 | 192 | Here's how to make an example like the one above, but using path variables. 193 | This example also shows how to add a different title and meta tag descriptions for each of the pages specified in the path variable. 194 | 195 | For more information, see this forum post on how to [Contain a Dash Page Under a Parent Page.](https://community.plotly.com/t/contain-a-dash-page-under-a-parent-page/82695/) 196 | 197 | ![multi-page-demo-path-variables](https://github.com/AnnMarieW/dash-multi-page-app-demos/assets/72614349/d382f896-30ae-42f8-bb86-67427873f75e) 198 | 199 | --- 200 | 201 |
202 |
203 | 204 | ## 8. [multi_page_meta_tags/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_meta_tags) 205 | 206 | This app shows more details on how the images are added to the meta tags. 207 | See also the Dash Documentation: https://dash.plotly.com/urls#meta-tags 208 | 209 | --- 210 | 211 |
212 |
213 | 214 | ## 9. [multi_page_nested_folder/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_nested_folders) 215 | 216 | For more info, please see the Dash Documentation: https://dash.plotly.com/urls#nested-pages 217 | This app demonstrates the case where you have nested folders with pages folder, like in the following: 218 | ``` 219 | - app.py 220 | - pages 221 | - chapter1 222 | |-- page1.py 223 | |-- page2.py 224 | - chapter2 225 | |-- page1.py 226 | |-- page2.py 227 | - home.py 228 | ``` 229 | 230 | 231 | It also demos how to add arbitrary data to the `page_registry`. It adds icons to the `page_registry` which are used when creating the links. 232 | 233 | This app uses [dash-mantine-components](https://dash-mantine-components.herokuapp.com/dash-iconify) and [dash-iconify libraries.](https://dash-mantine-components.herokuapp.com/dash-iconify) 234 | ![nested_folders](https://user-images.githubusercontent.com/72614349/175791749-4c6aafc2-b49e-403f-b651-9b24bdce565a.png) 235 | 236 | ------ 237 | 238 | 239 |
240 |
241 | 242 | ## 10. [multi_page_query_strings/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_query_strings) 243 | 244 | This app demonstrates passing variables to a page using query strings. 245 | For more information see the Dash Documentation: https://dash.plotly.com/urls#query-strings. 246 | You will also see how to use a `dcc.Link` within a `dcc.Markdown` 247 | 248 | ![query_strings](https://user-images.githubusercontent.com/72614349/175389777-dbf10ccf-d4cb-4f86-9e09-12a7ad048fd5.gif) 249 | 250 | ---- 251 | 252 | 253 |
254 |
255 | 256 | ## 11. [multi_page_store/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_store) 257 | This app shows how to share data between callbacks on different pages using a `dcc.Store` component. 258 | 259 | ![pages-share-data-between-pages](https://github.com/AnnMarieW/dash-multi-page-app-demos/assets/72614349/80473cef-3dc8-4d66-821f-095cfe09a25b) 260 | 261 | ---- 262 | 263 | 264 |
265 |
266 | 267 | ## 12. [multi_page_table_links/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_table_links) 268 | 269 | This app uses `dcc.Link`in the cells of a Dash AG Grid to navigate to a new page without refreshing the page. 270 | 271 | ![grid links](https://github.com/AnnMarieW/dash-multi-page-app-demos/assets/72614349/123d0abf-d0f3-4a5c-ae8f-2b6dbc7c9d1c) 272 | 273 | 274 | 275 | ---- 276 | 277 | 278 |
279 |
280 | 281 | ## 13. [multi_page_sync_components/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_sync_components) 282 | 283 | These examples show how to synchronize component values between pages. 284 | 285 | You will find two example: 286 | 287 | 1. [multi_page_sync_components/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_sync_components) is a simple example which uses the same component on each page and sets `persistence=True` Thanks @nopria for the example! 288 | 2. [multi_page_sync_components2/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_sync_components2)In some cases, the simple example won't work (ie component values updated in callbacks). Version 2 uses a `dcc.Store` component to sync the component values. It required dash>=2.9.2 to allow updating the dcc.Store from multiple callbacks on different pages. 289 | 290 | ![sync](https://user-images.githubusercontent.com/72614349/175389756-bf064f6d-edd1-4107-9764-1373c260451f.gif) 291 | 292 | --- 293 | 294 | 295 |
296 |
297 | 298 | ## 14. [multi_page_theme_switch/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_theme_switch) 299 | This example demonstrate a light and dark theme switch component from the [dash-bootstrap-templates](https://github.com/AnnMarieW/dash-bootstrap-templates) library. 300 | See a live demo at [Dash Bootstrap Theme Explorer](https://hellodash.pythonanywhere.com/) The Theme Explorer app is also made with `pages` :tada: 301 | 302 | 303 | __For Dash Enterprise Customers, see: [Dash Design Kit](https://plotly.com/dash/design-kit/)__ 304 | 305 | 306 | 307 | ![theme_switch](https://user-images.githubusercontent.com/72614349/174487972-078fec10-a54f-418d-b0c4-8de0e8e4b438.gif) 308 | 309 | 310 | 311 | ------ 312 | 313 | 314 |
315 |
316 | 317 | ## Navigation in a callback 318 | 319 | With Dash Pages, the routing callback is under-the-hood, which reduces the amount of boilderplate code you need to write. 320 | The best way to navigate is to use components such as the `dcc.Link` or `dbc.Button`. When the user clicks on these 321 | links, it will navigate to the new page without refreshing the page, making the navigation very 322 | fast. And the best part? No callback required! :tada: 323 | 324 | This works well when you have static links. However, at times, you may want to navigate based on an input field, 325 | dropdown, or clicking on a figure etc. There are two options: 326 | 327 | 1) Update href of `dcc.Location` in a callback. Not recommended in Dash<2.9.2 because it refreshes the page. 328 | 2) Update the link in a callback. Best practice! 329 | 330 | :tada: New in dash 2.9.2 `dcc.Location(refresh="callback-nav")` - navigate without refreshing the page. See examples below 331 | 332 | --- 333 | 334 |
335 |
336 | 337 | ### 15. [multi_page_update_url_in_callback/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_update_url_in_callback) 338 | ### 15b.[multi_page_update_url_in_callback_V292/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_update_url_in_callback_V292) 339 | 340 | See two versions of the same app. It shows both ways to navigate in a callback - by updating dcc.Location and by updating links. 341 | The V2.9.2 version uses the "callback-nav" option in `dcc.Location` so that the page does not refresh. 342 | 343 | 344 | ![callback-nav](https://user-images.githubusercontent.com/72614349/230732368-f9d48477-92ef-4d73-a099-227bcaa7871f.png) 345 | 346 | 347 | For more information see this [community forum post.]() 348 | 349 | Here are more examples. This one (best practice) is to update a link when a user clicks on a figure: 350 | 351 | --- 352 |
353 |
354 | 355 | ## 16. [multi_page_update_url_from_figure/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_update_url_from_figure) 356 | 357 | ![fight-status](https://user-images.githubusercontent.com/72614349/187049002-6ae8fc65-c9f7-4f4b-b823-538301391792.gif) 358 | 359 | 360 | --- 361 |
362 |
363 | 364 | ## 16b. [multi_page_update_url_from_figure_V292/](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_update_url_from_figure_V292) 365 | 366 | This option is available with dash>=2.9.2. It uses `dcc.Location(refresh="callback-nav")` to navigate without refreshing the page. 367 | img 368 | 369 | ![callback-nav-fig-292](https://user-images.githubusercontent.com/72614349/230731674-81ff311a-fbd4-4770-aae3-bc587c0ad2c9.gif) 370 | 371 | 372 | There are some known issues with `dcc.Location`. Here are some workarounds to avoid things like the browser crashing or the back button not 373 | working: 374 | - Only include a `dcc.Location` component if you need to update it in a callback. 375 | - Be sure to use only one `dcc.Location` component - do not use multiple. 376 | - Place the `dcc.Location` in `app.py` - do not put it in a file in the `pages` folder. 377 | - Place the `dcc.Location` component as the first component in the `app.py` layout. It must be in the layout before `page_container` which has the Pages `dcc.Location` component. 378 | 379 | ```python 380 | 381 | app.layout = html.Div( 382 | [ 383 | dcc.Location(id="url", refresh="callback-nav"), 384 | navbar, page_container, 385 | ], 386 | ) 387 | 388 | ``` 389 | 390 | 391 | --- 392 |
393 |
394 | 395 | ## 17. [multi_page_no_pages_folder](https://github.com/AnnMarieW/dash-multi-page-app-demos/blob/main/multi_page_no_pages_folder/app.py) 396 | 397 | This is an example of how to create a multi-page app without using the `pages` folder. 398 | 399 | Use cases: 400 | 1. Jupyter Notebooks: Use this method when using a Jupyter Notebook and you would like to run the code as a cell. To run this example in a Jupyter Notebook, copy the content of the app.py file and paste it into a notebook cell. 401 | 2. Accessing Pages features in a single page app. For more information and examples, see this [forum post](https://community.plotly.com/t/use-url-query-strings-with-single-page-app/83448) 402 | --- 403 | 404 | 405 |
406 |
407 | 408 | # Tips and Tricks 409 | 410 | ## 1. print_registry() from dash-labs>-1.1.0 411 | 412 | 413 | 414 | When debugging a `pages` app, it's very helpful to inspect the content of the `dash.page_registry`. 415 | 416 | `print_registry()` is a handy utility that pretty-prints all or part of the `dash.page_registry` dict. 417 | 418 | 419 | Examples for `print_registry()` 420 | 421 | 422 | ``` 423 | 424 | 425 | from dash import Dash, html, register_page 426 | 427 | # must use dash-labs>=1.1.0 428 | from dash_labs import print_registry 429 | 430 | app = Dash(__name__, use_pages=True) 431 | 432 | register_page("another_home", layout=html.Div("We're home!"), path="/") 433 | 434 | print_registry() 435 | 436 | .... rest of your app 437 | 438 | ``` 439 | Will print to the console: 440 | 441 | ``` 442 | {'another_home': {'module': 'another_home', 443 | 'supplied_path': '/', 444 | 'path_template': None, 445 | 'path': '/', 446 | 'supplied_name': None, 447 | 'name': 'Another home', 448 | 'supplied_title': None, 449 | 'title': 'Another home', 450 | 'description': '', 451 | 'order': 0, 452 | 'supplied_order': None, 453 | 'supplied_layout': Div("We're home!"), 454 | 'image': None, 455 | 'supplied_image': None, 456 | 'image_url': None, 457 | 'redirect_from': None, 458 | 'layout': Div("We're home!")}} 459 | ``` 460 | 461 | __Reference__ 462 | 463 | `print_registry(modules='ALL', exclude=None, include='ALL')` 464 | 465 | Params: 466 | - `module`: (string or list) Default "ALL". Specifies which modules to print. 467 | - `exclude`: (string or list) Default None. Specifies which of the page's parameter(s) to exclude. 468 | - `include`: (string or list) Default "ALL". Prints only the parameters that are specified. 469 | 470 | Examples: 471 | 472 | - `print_registry()` Will print the entire content of dash.page_registry. If called from a file in the pages folder `dash.page_registry` may not be complete. 473 | - `print_registry("pages.home")` will print only one module, in this case, the `pages.home` module 474 | - `print_registry(__name__)` will print the current module. When called from app.py it will print all modules. 475 | - `print_registry(["pages.home", "pages.archive"])` Will print the modules in the list. 476 | - `print_registry(exclude="layout")` will print info for all the modules, but will exclude the "layout" attribute 477 | - `print_registry(include=["path", "name"]` will print only the "path" and "name" attributes for all modules 478 | - `print_registry(include=None)` prints the keys (module names) only 479 | 480 | ------------------ 481 | 482 | 483 |
484 |
485 | 486 | ## 2. :tada: Use dcc.Link in dcc.Markdown 487 | 488 | Did you know it's possible to use dcc.Link in `dcc.Markdown`? 489 | The advantage of using `dcc.Link` to navigate between pages of a multi-page app is that when you click on the link it updates the pathname without refreshing the page -- which makes browsing really fast. :rocket: 490 | 491 | Here's how: 492 | ```python 493 | dcc.Markdown( "This is text more text", dangerously_allow_html=True) 494 | ``` 495 | 496 | For comparison, here is a regular Markdown link syntax: 497 | ```python 498 | dcc.Markdown( "This is text [Page 1](/page1/news) more text") 499 | ``` 500 | See [multi_page_query_strings/](#9-multi_page_query_strings) for an example. 501 | For more examples including how to format the link title with Markdown syntax or use an image [get the gist.](https://gist.github.com/AnnMarieW/b5269c177cc3dfed06766aded802f664) 502 | 503 | 504 |
505 |
506 | 507 | ## 3. Avoiding duplicate ids 508 | 509 | All ids in the entire app must be unique, otherwise callbacks may not fire. Here are some tips to ensure that all ids in the app are unique: 510 | 511 | __3a. From this [forum post](https://community.plotly.com/t/examples-of-multi-page-apps-with-dash-pages/66489/8?u=annmariew) as recommended by @chriddyp:__ 512 | 513 | >What I’ve done in big projects is to create an id function that creates the prefix automatically. This is easier with pages as each component tree is in its own layout so you can use `__name__` as the prefix. 514 | > 515 | > So you’d write something like: 516 | 517 | `utils.py` 518 | ``` 519 | def id(name, localid): 520 | return f"{name.replace('_', '-').replace('.py', '').replace('/', '')}-{localid}" 521 | ``` 522 | 523 | `pages/historical_analysis.py` 524 | ``` 525 | from utils import id 526 | 527 | layout = html.Div(id=id(__name__, 'parent-div')) 528 | ``` 529 | 530 | __3b. From this [video by arjancodes ](https://youtu.be/XOFrvzWFM7Y)__ 531 | 532 | Define ids in module. It makes them easier to access, maintain, and reduces typos. 533 | See this used in [multi_page_long_callback](https://github.com/AnnMarieW/dash-multi-page-app-demos/tree/main/multi_page_long_callback) 534 | 535 | for example: 536 | 537 | `ids.py` 538 | ``` 539 | PAGE1_BUTTON = "page1-button" 540 | PAGE1_GRAPH = "page1-graph" 541 | ``` 542 | `page1.py` 543 | ```python 544 | import ids 545 | 546 | html.Button("button", id=ids.PAGE1_BUTTON) 547 | dcc.Graph(ids.PAGE1.GRAPH) 548 | 549 | @callback( 550 | Output(ids.PAGE1_GRAPH, "figure"), 551 | Input(ids.PAGE1_BUTTON, "n_clicks") 552 | ) 553 | 554 | 555 | ``` 556 | 557 | 558 |
559 |
560 | 561 | ## 4. Display loading screen when page_container is loading 562 | Shows how to make the overall loading screen only display when there is a change to the `_pages_content` that involves a layout being changed and not changes within the layout. See the post on the [Dash Community Forum](https://community.plotly.com/t/displaying-loading-screen-when-pages-container-is-loading/72109/1). Thanks @BSd3v for this example! 563 | 564 | ## 5. Preventing Query String Errors 565 | 566 | > TypeError: layout() got an unexpected keyword argument.... 567 | > 568 | 569 | Dash Pages captures query strings and path variables from the URL, passing them to the layout function as keyword 570 | arguments. It's recommended to include `**kwargs` to handle unexpected query strings. This prevents errors if a user enters 571 | a query string in the URL, even if you don't plan to use them. 572 | 573 | 574 | ```python 575 | def layout (**kwargs): 576 | return html.Div("my layout") 577 | ``` -------------------------------------------------------------------------------- /multi_page_basics/app.py: -------------------------------------------------------------------------------- 1 | from dash import Dash, html, dcc 2 | import dash 3 | 4 | 5 | app = Dash(__name__, use_pages=True) 6 | 7 | # Example of creating a page without using a pages folder 8 | dash.register_page("another_home", layout=html.Div("We're home!"), path="/") 9 | 10 | # Example of setting an order. The page registry is sorted by the `order` parameter, then by module name 11 | dash.register_page( 12 | "very_important", layout=html.Div("Don't miss it!"), path="/important", order=0 13 | ) 14 | 15 | 16 | app.layout = html.Div( 17 | [ 18 | html.H1("App Frame"), 19 | html.Div( 20 | [ 21 | html.Div( 22 | dcc.Link(f"{page['name']} - {page['path']}", href=page["path"]) 23 | ) 24 | for page in dash.page_registry.values() 25 | if page["module"] != "pages.not_found_404" 26 | ] 27 | ), 28 | html.Hr(), 29 | dash.page_container, 30 | ] 31 | ) 32 | 33 | 34 | if __name__ == "__main__": 35 | app.run(debug=True) -------------------------------------------------------------------------------- /multi_page_basics/app_dbc.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dash_bootstrap_components as dbc 3 | 4 | app = dash.Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP]) 5 | 6 | dash.register_page("home", layout="We're home!", path="/") 7 | 8 | navbar = dbc.NavbarSimple( 9 | dbc.DropdownMenu( 10 | [ 11 | dbc.DropdownMenuItem(page["name"], href=page["path"]) 12 | for page in dash.page_registry.values() 13 | if page["module"] != "pages.not_found_404" 14 | ], 15 | nav=True, 16 | label="More Pages", 17 | ), 18 | brand="Multi Page App Demo", 19 | color="primary", 20 | dark=True, 21 | className="mb-2", 22 | ) 23 | 24 | app.layout = dbc.Container( 25 | [ 26 | navbar, 27 | dash.page_container, 28 | ], 29 | fluid=True, 30 | ) 31 | 32 | if __name__ == "__main__": 33 | app.run(debug=True) 34 | -------------------------------------------------------------------------------- /multi_page_basics/app_ddk.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import dcc 3 | 4 | import dash_design_kit as ddk 5 | 6 | app = dash.Dash(__name__) 7 | 8 | app.layout = ddk.App( 9 | [ 10 | ddk.Header( 11 | [ 12 | ddk.Menu(dcc.Link(page["name"], href=page["path"])) 13 | for page in dash.page_registry 14 | ] 15 | ), 16 | dash.page_container, 17 | ] 18 | ) 19 | 20 | 21 | if __name__ == "__main__": 22 | app.run(debug=True) 23 | -------------------------------------------------------------------------------- /multi_page_basics/assets/app.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_basics/assets/app.jpeg -------------------------------------------------------------------------------- /multi_page_basics/assets/birds.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_basics/assets/birds.jpeg -------------------------------------------------------------------------------- /multi_page_basics/assets/home.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_basics/assets/home.jpeg -------------------------------------------------------------------------------- /multi_page_basics/assets/logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_basics/assets/logo.jpeg -------------------------------------------------------------------------------- /multi_page_basics/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_basics/pages/__init__.py -------------------------------------------------------------------------------- /multi_page_basics/pages/historical_archive.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of the register_page defaults 3 | """ 4 | 5 | 6 | from dash import html 7 | 8 | import dash 9 | 10 | dash.register_page(__name__) 11 | 12 | 13 | layout = html.H1("Historical Archive") 14 | -------------------------------------------------------------------------------- /multi_page_basics/pages/not_found_404.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of creating a custom 404 page to display when the URL isn't found 3 | """ 4 | 5 | 6 | from dash import html 7 | import dash 8 | 9 | dash.register_page(__name__, path="/404") 10 | 11 | 12 | layout = html.H1("Custom 404") 13 | -------------------------------------------------------------------------------- /multi_page_basics/pages/outlook.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of 3 | - adding a custom title, description, and image which will be used to create the meta-tags. 4 | - setting a pathname for the page. 5 | """ 6 | 7 | import dash 8 | from dash import html 9 | 10 | dash.register_page( 11 | __name__, 12 | title="Forward Outlook", 13 | description="This is the forward outlook", 14 | image="birds.jpeg", 15 | path="/forward-outlook", 16 | ) 17 | 18 | 19 | layout = html.Div("Forward outlook") 20 | -------------------------------------------------------------------------------- /multi_page_basics/pages/path_variables.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of: 3 | - title and description updated dynamically with a function 4 | - passing variables in the url pathname to the layout function. 5 | Using the path_template parameter in dash.register_page, you can define which segments of the path 6 | are variables by marking them like this: . The layout function then receives 7 | the as a keyword argument. 8 | """ 9 | 10 | import dash 11 | 12 | 13 | def title(asset_id=None, dept_id=None): 14 | return f"Asset Analysis: {asset_id} {dept_id}" 15 | 16 | 17 | def description(asset_id=None, dept_id=None): 18 | return f"This is the AVN Industries Asset Analysis: {asset_id} in {dept_id}" 19 | 20 | 21 | dash.register_page( 22 | __name__, 23 | path_template="/asset//department/", 24 | title=title, 25 | description=description, 26 | path="/asset/inventory/department/branch-1001", 27 | ) 28 | 29 | 30 | def layout(asset_id=None, dept_id=None, **other_unknown_query_strings): 31 | return dash.html.Div( 32 | f"variables from pathname: asset_id: {asset_id} dept_id: {dept_id}" 33 | ) 34 | -------------------------------------------------------------------------------- /multi_page_basics/pages/query_string.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of passing query strings from the url to a layout function 3 | """ 4 | 5 | 6 | import dash 7 | 8 | dash.register_page(__name__, path="/dashboard") 9 | 10 | 11 | def layout(velocity=None, **other_unknown_query_strings): 12 | return dash.html.Div(id="velocity", children=f"velocity={velocity}") 13 | -------------------------------------------------------------------------------- /multi_page_basics/pages/redirect.py: -------------------------------------------------------------------------------- 1 | """ 2 | example of 3 | - setting redirects with the `redirect_from=[...]` parameter in register_page: 4 | - adding arbitrary 5 | """ 6 | 7 | import dash 8 | from dash import html 9 | 10 | 11 | dash.register_page( 12 | __name__, 13 | description="Welcome to my app", 14 | redirect_from=["/old-home-page", "/v2"], 15 | extra_template_stuff="yup", 16 | ) 17 | 18 | layout = html.Div(["Home Page", html.Img(src="/assets/birds.jpeg", height="50px")]) 19 | -------------------------------------------------------------------------------- /multi_page_basics/pages/snapshot.py: -------------------------------------------------------------------------------- 1 | # example of matching a path that starts with "shapshot-" 2 | 3 | import dash 4 | 5 | dash.register_page( 6 | __name__, 7 | path_template="/snapshot-", 8 | ) 9 | 10 | 11 | def layout(snapshot_id=None, **other_unknown_query_strings): 12 | return dash.html.Div(f"snapshot page: snapshot-{snapshot_id}") 13 | -------------------------------------------------------------------------------- /multi_page_basics_pathname_prefix/app.py: -------------------------------------------------------------------------------- 1 | from dash import Dash, html, dcc 2 | import dash 3 | 4 | 5 | app = Dash(__name__, use_pages=True, url_base_pathname="/app1/") 6 | 7 | dash.register_page("another_home", layout=html.Div("We're home!"), path="/") 8 | dash.register_page( 9 | "very_important", layout=html.Div("Don't miss it!"), path="/important", order=0 10 | ) 11 | 12 | app.layout = html.Div( 13 | [ 14 | html.H1("App Frame"), 15 | html.Div( 16 | [ 17 | html.Div( 18 | dcc.Link( 19 | f"{page['name']} - {page['path']}", href=(page["relative_path"]) 20 | ) 21 | ) 22 | for page in dash.page_registry.values() 23 | if page["module"] != "pages.not_found_404" 24 | ] 25 | ), 26 | html.Hr(), 27 | dash.page_container, 28 | ] 29 | ) 30 | 31 | 32 | if __name__ == "__main__": 33 | app.run(debug=True) 34 | -------------------------------------------------------------------------------- /multi_page_basics_pathname_prefix/assets/app.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_basics_pathname_prefix/assets/app.jpeg -------------------------------------------------------------------------------- /multi_page_basics_pathname_prefix/assets/birds.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_basics_pathname_prefix/assets/birds.jpeg -------------------------------------------------------------------------------- /multi_page_basics_pathname_prefix/assets/home.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_basics_pathname_prefix/assets/home.jpeg -------------------------------------------------------------------------------- /multi_page_basics_pathname_prefix/assets/logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_basics_pathname_prefix/assets/logo.jpeg -------------------------------------------------------------------------------- /multi_page_basics_pathname_prefix/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_basics_pathname_prefix/pages/__init__.py -------------------------------------------------------------------------------- /multi_page_basics_pathname_prefix/pages/historical_archive.py: -------------------------------------------------------------------------------- 1 | from dash import html, dcc, callback, Input, Output 2 | 3 | import dash 4 | 5 | dash.register_page(__name__) 6 | 7 | 8 | def layout(**kwargs): 9 | return html.H1("Historical Archive") 10 | -------------------------------------------------------------------------------- /multi_page_basics_pathname_prefix/pages/not_found_404.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | import dash 3 | 4 | dash.register_page(__name__, path="/404") 5 | 6 | 7 | layout = html.H1("Custom 404") 8 | -------------------------------------------------------------------------------- /multi_page_basics_pathname_prefix/pages/outlook.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | dash.register_page( 4 | __name__, 5 | title="Forward Outlook", 6 | description="This is the forward outlook", # should accept callable too 7 | path="/forward-outlook", 8 | image="birds.jpeg", 9 | ) 10 | 11 | 12 | def layout(**kwargs): 13 | return "Forward outlook" 14 | -------------------------------------------------------------------------------- /multi_page_basics_pathname_prefix/pages/path_variables.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | 4 | def title(asset_id=None, dept_id=None): 5 | return f"Asset Analysis: {asset_id} {dept_id}" 6 | 7 | 8 | def description(asset_id=None, dept_id=None): 9 | return f"This is the AVN Industries Asset Analysis: {asset_id} in {dept_id}" 10 | 11 | 12 | dash.register_page( 13 | __name__, 14 | path_template="/asset//department/", 15 | title=title, 16 | description=description, 17 | path="/asset/inventory/department/branch-1001", 18 | ) 19 | 20 | 21 | def layout(asset_id=None, dept_id=None, **other_unknown_query_strings): 22 | return dash.html.Div( 23 | f"variables from pathname: asset_id: {asset_id} dept_id: {dept_id}" 24 | ) 25 | -------------------------------------------------------------------------------- /multi_page_basics_pathname_prefix/pages/query_string.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | dash.register_page(__name__, path="/dashboard") 4 | 5 | 6 | def layout(velocity=None, **other_unknown_query_strings): 7 | return dash.html.Div([dash.dcc.Input(id="velocity", value=velocity)]) 8 | -------------------------------------------------------------------------------- /multi_page_basics_pathname_prefix/pages/redirect.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html, get_asset_url 3 | 4 | 5 | dash.register_page( 6 | __name__, 7 | description="Welcome to my app", 8 | redirect_from=["/old-home-page", "/v2"], 9 | extra_template_stuff="yup", 10 | ) 11 | 12 | layout = html.Div( 13 | ["Home Page", html.Img(src=get_asset_url("birds.jpeg"), height="50px")] 14 | ) 15 | -------------------------------------------------------------------------------- /multi_page_cache_background_callback/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | THis is an example of caching with background callbacks in dash>=2.6.1. 3 | 4 | Note that with multi-page apps, if the user switches pages before the background callback 5 | is complete that it may cause errors if the callback tries to update components (such as progress bars etc) 6 | that do not exist on the new page. 7 | 8 | This may be fixed in a future version, but in the meantime, here is a workaround: 9 | - add a `dcc.Store` in app.py to make the status of the callback available to all pages 10 | - in the background callback, the `running` prop updates ONLY this `dcc.Store` DO NOT include 11 | other components such as disable run/cancel buttons, progress bar etc. 12 | - use the dcc.Store in a separate callback to trigger the update of the other components. 13 | 14 | """ 15 | 16 | 17 | import os 18 | from uuid import uuid4 19 | 20 | import dash 21 | from dash import DiskcacheManager, CeleryManager, html, dcc 22 | import dash_bootstrap_components as dbc 23 | 24 | launch_uid = uuid4() 25 | 26 | if "REDIS_URL" in os.environ: 27 | # Use Redis & Celery if REDIS_URL set as an env variable 28 | from celery import Celery 29 | 30 | celery_app = Celery( 31 | __name__, broker=os.environ["REDIS_URL"], backend=os.environ["REDIS_URL"] 32 | ) 33 | background_callback_manager = CeleryManager( 34 | celery_app, cache_by=[lambda: launch_uid], expire=60 35 | ) 36 | 37 | else: 38 | # Diskcache for non-production apps when developing locally 39 | import diskcache 40 | 41 | cache = diskcache.Cache("./cache") 42 | background_callback_manager = DiskcacheManager( 43 | cache, cache_by=[lambda: launch_uid], expire=60 44 | ) 45 | 46 | app = dash.Dash( 47 | __name__, 48 | background_callback_manager=background_callback_manager, 49 | use_pages=True, 50 | suppress_callback_exceptions=True, 51 | external_stylesheets=[dbc.themes.BOOTSTRAP], 52 | ) 53 | 54 | app.layout = dbc.Container( 55 | [ 56 | dcc.Markdown( 57 | """ 58 | # Background Callback with Caching Examples 59 | These examples are from the [Background Callback Caching](https://dash.plotly.com/background-callback-caching) section of the dash docs. 60 | Each example is a page of a multi-page app. Requires dash>=2.6.1 61 | """ 62 | ), 63 | html.Div( 64 | [ 65 | html.Div(dcc.Link(f"{page['title']}", href=page["path"])) 66 | for page in dash.page_registry.values() 67 | if page["module"] != "pages.not_found_404" 68 | ] 69 | ), 70 | html.Hr(), 71 | dash.page_container, 72 | dcc.Store(id="example1_running"), 73 | dcc.Store(id="example3_running"), 74 | ] 75 | ) 76 | 77 | if __name__ == "__main__": 78 | app.run(debug=True) 79 | -------------------------------------------------------------------------------- /multi_page_cache_background_callback/pages/example_1.py: -------------------------------------------------------------------------------- 1 | import time 2 | import dash 3 | from dash import Input, Output, html 4 | import dash_bootstrap_components as dbc 5 | 6 | title = "Enabling Caching - example from the dash docs" 7 | dash.register_page(__name__, path="/", title=title) 8 | 9 | layout = html.Div( 10 | [ 11 | html.H4(title), 12 | html.P( 13 | "The first 4 times the button is clicked, it's slow. After that, cached values are used" 14 | ), 15 | html.Div([html.P(id="paragraph1_id", children=["Button not clicked"])]), 16 | dbc.Button(id="button1_id", children="Run Job!"), 17 | dbc.Button(id="cancel_button1_id", children="Cancel Running Job!"), 18 | ] 19 | ) 20 | 21 | 22 | @dash.callback( 23 | output=(Output("paragraph1_id", "children"), Output("button1_id", "n_clicks")), 24 | inputs=Input("button1_id", "n_clicks"), 25 | background=True, 26 | running=[(Output("example1_running", "data"), True, False)], 27 | cancel=Input("cancel_button1_id", "n_clicks"), 28 | config_prevent_initial_callbacks=True, 29 | ) 30 | def update_clicks(n_clicks): 31 | time.sleep(10.0) 32 | return [f"Clicked {n_clicks} times"], (n_clicks or 0) % 4 33 | 34 | 35 | @dash.callback( 36 | Output("button1_id", "disabled"), 37 | Output("cancel_button1_id", "disabled"), 38 | Input("example1_running", "data"), 39 | ) 40 | def disable(running): 41 | # disable the run and cancel button based on whether the callback is running 42 | return running, not running 43 | -------------------------------------------------------------------------------- /multi_page_cache_background_callback/pages/example_2.py: -------------------------------------------------------------------------------- 1 | import time 2 | import dash 3 | from dash import dcc, html, Input, Output, callback 4 | import plotly.express as px 5 | 6 | title = "Enable caching - with dropdown and figure" 7 | dash.register_page(__name__, title=title) 8 | 9 | df = px.data.tips() 10 | 11 | dropdown = dcc.Dropdown( 12 | ["Fri", "Sat", "Sun"], 13 | "Fri", 14 | clearable=False, 15 | persistence=True, 16 | id="dropdown2", 17 | ) 18 | graph = dcc.Graph() 19 | 20 | layout = html.Div([html.H4(title), dropdown, dcc.Loading(graph)]) 21 | 22 | 23 | @callback( 24 | Output(graph, "figure"), 25 | Output("dropdown2", "value"), 26 | Input("dropdown2", "value"), 27 | background=True, 28 | ) 29 | def update_bar_chart(day): 30 | # simulate long calculation 31 | time.sleep(5) 32 | mask = df["day"] == day 33 | fig = px.bar(df[mask], x="sex", y="total_bill", color="smoker", barmode="group") 34 | return fig, day 35 | -------------------------------------------------------------------------------- /multi_page_cache_background_callback/pages/example_3.py: -------------------------------------------------------------------------------- 1 | import time 2 | import dash 3 | from dash import dcc, html, Input, Output, callback 4 | import plotly.express as px 5 | import dash_bootstrap_components as dbc 6 | 7 | title = "Caching with background callback with progress bar" 8 | dash.register_page(__name__, title=title) 9 | 10 | df = px.data.tips() 11 | 12 | layout = dbc.Container( 13 | [ 14 | html.H4(title), 15 | dcc.Dropdown( 16 | ["total_bill", "tip", "size"], 17 | "total_bill", 18 | clearable=False, 19 | id="dropdown3", 20 | persistence=True, 21 | style={"minWidth": 300}, 22 | ), 23 | dbc.Button("Cancel Running Job", id="cancel3", className="m-3"), 24 | dbc.Progress(id="progress3", max=10), 25 | dcc.Graph(id="graph3"), 26 | ] 27 | ) 28 | 29 | 30 | @callback( 31 | Output("graph3", "figure"), 32 | Output("dropdown3", "value"), 33 | Input("dropdown3", "value"), 34 | background=True, 35 | running=[(Output("example3_running", "data"), True, False)], 36 | cancel=[Input("cancel3", "n_clicks")], 37 | progress=Output("progress3", "value"), 38 | # config_prevent_initial_callbacks=True, 39 | ) 40 | def update_progress(set_progress, value): 41 | for i in range(11): 42 | set_progress(i) 43 | time.sleep(1) 44 | fig = px.pie(df, values=value, names="day", hole=0.3) 45 | return fig, value 46 | 47 | 48 | @callback( 49 | Output("dropdown3", "disabled"), 50 | Output("cancel3", "disabled"), 51 | Output("graph3", "style"), 52 | Output("progress3", "style"), 53 | Input("example3_running", "data"), 54 | ) 55 | def disable(running): 56 | disable_dropdown = False 57 | disable_cancel = True 58 | display_graph = {"visibility": "visible"} 59 | display_progress = {"visibility": "hidden"} 60 | if running: 61 | disable_dropdown = True 62 | disable_cancel = False 63 | display_graph = {"visibility": "hidden"} 64 | display_progress = {"visibility": "visible"} 65 | return disable_dropdown, disable_cancel, display_graph, display_progress 66 | -------------------------------------------------------------------------------- /multi_page_dash_auth/app.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dash_bootstrap_components as dbc 3 | import dash_auth 4 | 5 | 6 | # Keep this out of source code repository - save in a file or a database 7 | VALID_USERNAME_PASSWORD_PAIRS = {"hello": "world"} 8 | 9 | app = dash.Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP]) 10 | 11 | auth = dash_auth.BasicAuth(app, VALID_USERNAME_PASSWORD_PAIRS) 12 | 13 | navbar = dbc.NavbarSimple( 14 | dbc.DropdownMenu( 15 | [ 16 | dbc.DropdownMenuItem(page["name"], href=page["path"]) 17 | for page in dash.page_registry.values() 18 | if page["module"] != "pages.not_found_404" 19 | ], 20 | nav=True, 21 | label="More Pages", 22 | ), 23 | brand="Multi Page App Demo", 24 | color="primary", 25 | dark=True, 26 | className="mb-2", 27 | ) 28 | 29 | app.layout = dbc.Container( 30 | [navbar, dash.page_container], 31 | fluid=True, 32 | ) 33 | 34 | if __name__ == "__main__": 35 | app.run(debug=True) 36 | -------------------------------------------------------------------------------- /multi_page_dash_auth/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_dash_auth/pages/__init__.py -------------------------------------------------------------------------------- /multi_page_dash_auth/pages/bar_charts.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | dash.register_page(__name__) 4 | 5 | from dash import Dash, dcc, html, Input, Output, callback 6 | import plotly.express as px 7 | 8 | df = px.data.tips() 9 | days = df.day.unique() 10 | 11 | layout = html.Div( 12 | [ 13 | dcc.Dropdown( 14 | id="dropdown", 15 | options=[{"label": x, "value": x} for x in days], 16 | value=days[0], 17 | clearable=False, 18 | ), 19 | dcc.Graph(id="bar-chart"), 20 | ] 21 | ) 22 | 23 | 24 | @callback(Output("bar-chart", "figure"), Input("dropdown", "value")) 25 | def update_bar_chart(day): 26 | mask = df["day"] == day 27 | fig = px.bar(df[mask], x="sex", y="total_bill", color="smoker", barmode="group") 28 | return fig 29 | -------------------------------------------------------------------------------- /multi_page_dash_auth/pages/heatmaps.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | dash.register_page(__name__, path="/") 4 | 5 | from dash import Dash, dcc, html, Input, Output, callback 6 | import plotly.express as px 7 | 8 | df = px.data.medals_wide(indexed=True) 9 | 10 | layout = html.Div( 11 | [ 12 | html.P("Medals included:"), 13 | dcc.Checklist( 14 | id="heatmaps-medals", 15 | options=[{"label": x, "value": x} for x in df.columns], 16 | value=df.columns.tolist(), 17 | ), 18 | dcc.Graph(id="heatmaps-graph"), 19 | ] 20 | ) 21 | 22 | 23 | @callback(Output("heatmaps-graph", "figure"), Input("heatmaps-medals", "value")) 24 | def filter_heatmap(cols): 25 | fig = px.imshow(df[cols]) 26 | return fig 27 | -------------------------------------------------------------------------------- /multi_page_dash_auth/pages/histograms.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | dash.register_page(__name__) 4 | 5 | from dash import Dash, dcc, html, Input, Output, callback 6 | import plotly.express as px 7 | import numpy as np 8 | 9 | np.random.seed(2020) 10 | 11 | layout = html.Div( 12 | [ 13 | dcc.Graph(id="histograms-graph"), 14 | html.P("Mean:"), 15 | dcc.Slider( 16 | id="histograms-mean", min=-3, max=3, value=0, marks={-3: "-3", 3: "3"} 17 | ), 18 | html.P("Standard Deviation:"), 19 | dcc.Slider(id="histograms-std", min=1, max=3, value=1, marks={1: "1", 3: "3"}), 20 | ] 21 | ) 22 | 23 | 24 | @callback( 25 | Output("histograms-graph", "figure"), 26 | Input("histograms-mean", "value"), 27 | Input("histograms-std", "value"), 28 | ) 29 | def display_color(mean, std): 30 | data = np.random.normal(mean, std, size=500) 31 | fig = px.histogram(data, nbins=30, range_x=[-10, 10]) 32 | return fig 33 | -------------------------------------------------------------------------------- /multi_page_dash_auth/pages/not_found_404.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | import dash 3 | 4 | dash.register_page(__name__, path="/404") 5 | 6 | 7 | layout = html.H1("Custom 404") 8 | -------------------------------------------------------------------------------- /multi_page_example1/app.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dash_bootstrap_components as dbc 3 | 4 | 5 | app = dash.Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP]) 6 | 7 | navbar = dbc.NavbarSimple( 8 | dbc.DropdownMenu( 9 | [ 10 | dbc.DropdownMenuItem(page["name"], href=page["path"]) 11 | for page in dash.page_registry.values() 12 | if page["module"] != "pages.not_found_404" 13 | ], 14 | nav=True, 15 | label="More Pages", 16 | ), 17 | brand="Multi Page App Demo", 18 | color="primary", 19 | dark=True, 20 | className="mb-2", 21 | ) 22 | 23 | app.layout = dbc.Container( 24 | [navbar, dash.page_container], 25 | fluid=True, 26 | ) 27 | 28 | if __name__ == "__main__": 29 | app.run(debug=True) 30 | -------------------------------------------------------------------------------- /multi_page_example1/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_example1/pages/__init__.py -------------------------------------------------------------------------------- /multi_page_example1/pages/bar_charts.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | dash.register_page(__name__) 4 | 5 | from dash import Dash, dcc, html, Input, Output, callback 6 | import plotly.express as px 7 | 8 | df = px.data.tips() 9 | days = df.day.unique() 10 | 11 | layout = html.Div( 12 | [ 13 | dcc.Dropdown( 14 | id="dropdown", 15 | options=[{"label": x, "value": x} for x in days], 16 | value=days[0], 17 | clearable=False, 18 | ), 19 | dcc.Graph(id="bar-chart"), 20 | ] 21 | ) 22 | 23 | 24 | @callback(Output("bar-chart", "figure"), Input("dropdown", "value")) 25 | def update_bar_chart(day): 26 | mask = df["day"] == day 27 | fig = px.bar(df[mask], x="sex", y="total_bill", color="smoker", barmode="group") 28 | return fig 29 | -------------------------------------------------------------------------------- /multi_page_example1/pages/heatmaps.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | dash.register_page(__name__, path="/") 4 | 5 | from dash import Dash, dcc, html, Input, Output, callback 6 | import plotly.express as px 7 | 8 | df = px.data.medals_wide(indexed=True) 9 | 10 | layout = html.Div( 11 | [ 12 | html.P("Medals included:"), 13 | dcc.Checklist( 14 | id="heatmaps-medals", 15 | options=[{"label": x, "value": x} for x in df.columns], 16 | value=df.columns.tolist(), 17 | ), 18 | dcc.Graph(id="heatmaps-graph"), 19 | ] 20 | ) 21 | 22 | 23 | @callback(Output("heatmaps-graph", "figure"), Input("heatmaps-medals", "value")) 24 | def filter_heatmap(cols): 25 | fig = px.imshow(df[cols]) 26 | return fig 27 | -------------------------------------------------------------------------------- /multi_page_example1/pages/histograms.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | dash.register_page(__name__) 4 | 5 | from dash import Dash, dcc, html, Input, Output, callback 6 | import plotly.express as px 7 | import numpy as np 8 | 9 | np.random.seed(2020) 10 | 11 | layout = html.Div( 12 | [ 13 | dcc.Graph(id="histograms-graph"), 14 | html.P("Mean:"), 15 | dcc.Slider( 16 | id="histograms-mean", min=-3, max=3, value=0, marks={-3: "-3", 3: "3"} 17 | ), 18 | html.P("Standard Deviation:"), 19 | dcc.Slider(id="histograms-std", min=1, max=3, value=1, marks={1: "1", 3: "3"}), 20 | ] 21 | ) 22 | 23 | 24 | @callback( 25 | Output("histograms-graph", "figure"), 26 | Input("histograms-mean", "value"), 27 | Input("histograms-std", "value"), 28 | ) 29 | def display_color(mean, std): 30 | data = np.random.normal(mean, std, size=500) 31 | fig = px.histogram(data, nbins=30, range_x=[-10, 10]) 32 | return fig 33 | -------------------------------------------------------------------------------- /multi_page_example1/pages/not_found_404.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | import dash 3 | 4 | dash.register_page(__name__, path="/404") 5 | 6 | 7 | layout = html.H1("Custom 404") 8 | -------------------------------------------------------------------------------- /multi_page_flask_login/.env: -------------------------------------------------------------------------------- 1 | SECRET_KEY=291a47103f3cd8fc26d05ffc7b31e33f73ca3d459d6259bd -------------------------------------------------------------------------------- /multi_page_flask_login/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | CREDIT: This code is adapted for `pages` based on Nader Elshehabi's article: 3 | https://dev.to/naderelshehabi/securing-plotly-dash-using-flask-login-4ia2 4 | https://github.com/naderelshehabi/dash-flask-login 5 | 6 | For other Authentication options see: 7 | Dash Enterprise: https://dash.plotly.com/authentication#dash-enterprise-auth 8 | Dash Basic Auth: https://dash.plotly.com/authentication#basic-auth 9 | 10 | """ 11 | 12 | 13 | import os 14 | from flask import Flask 15 | from flask_login import login_user, LoginManager, UserMixin, logout_user, current_user 16 | 17 | import dash 18 | from dash import dcc, html, Input, Output, State 19 | 20 | 21 | # Exposing the Flask Server to enable configuring it for logging in 22 | server = Flask(__name__) 23 | app = dash.Dash( 24 | __name__, server=server, use_pages=True, suppress_callback_exceptions=True 25 | ) 26 | 27 | # Keep this out of source code repository - save in a file or a database 28 | # passwords should be encrypted 29 | VALID_USERNAME_PASSWORD = {"test": "test", "hello": "world"} 30 | 31 | 32 | # Updating the Flask Server configuration with Secret Key to encrypt the user session cookie 33 | server.config.update(SECRET_KEY=os.getenv("SECRET_KEY")) 34 | 35 | # Login manager object will be used to login / logout users 36 | login_manager = LoginManager() 37 | login_manager.init_app(server) 38 | login_manager.login_view = "/login" 39 | 40 | 41 | class User(UserMixin): 42 | # User data model. It has to have at least self.id as a minimum 43 | def __init__(self, username): 44 | self.id = username 45 | 46 | 47 | @login_manager.user_loader 48 | def load_user(username): 49 | """This function loads the user by user id. Typically this looks up the user from a user database. 50 | We won't be registering or looking up users in this example, since we'll just login using LDAP server. 51 | So we'll simply return a User object with the passed in username. 52 | """ 53 | return User(username) 54 | 55 | 56 | app.layout = html.Div( 57 | [ 58 | dcc.Location(id="url"), 59 | html.Div(id="user-status-header"), 60 | html.Hr(), 61 | dash.page_container, 62 | ] 63 | ) 64 | 65 | 66 | @app.callback( 67 | Output("user-status-header", "children"), 68 | Input("url", "pathname"), 69 | ) 70 | def update_authentication_status(_): 71 | if current_user.is_authenticated: 72 | return dcc.Link("logout", href="/logout") 73 | return dcc.Link("login", href="/login") 74 | 75 | 76 | @app.callback( 77 | Output("output-state", "children"), 78 | Input("login-button", "n_clicks"), 79 | State("uname-box", "value"), 80 | State("pwd-box", "value"), 81 | prevent_initial_call=True, 82 | ) 83 | def login_button_click(n_clicks, username, password): 84 | if n_clicks > 0: 85 | if VALID_USERNAME_PASSWORD.get(username) is None: 86 | return "Invalid username" 87 | if VALID_USERNAME_PASSWORD.get(username) == password: 88 | login_user(User(username)) 89 | return "Login Successful" 90 | return "Incorrect password" 91 | 92 | 93 | if __name__ == "__main__": 94 | app.run(debug=True) 95 | -------------------------------------------------------------------------------- /multi_page_flask_login/pages/home.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html, dcc 3 | 4 | dash.register_page(__name__, path="/") 5 | 6 | 7 | layout = html.Div( 8 | [ 9 | dcc.Link("Go to Page 1", href="/page-1"), 10 | html.Br(), 11 | dcc.Link("Go to Page 2", href="/page-2"), 12 | ] 13 | ) 14 | -------------------------------------------------------------------------------- /multi_page_flask_login/pages/login.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html, dcc 3 | 4 | 5 | dash.register_page(__name__) 6 | 7 | # Login screen 8 | layout = html.Div( 9 | [ 10 | html.H2("Please log in to continue:", id="h1"), 11 | dcc.Input(placeholder="Enter your username", type="text", id="uname-box"), 12 | dcc.Input(placeholder="Enter your password", type="password", id="pwd-box"), 13 | html.Button(children="Login", n_clicks=0, type="submit", id="login-button"), 14 | html.Div(children="", id="output-state"), 15 | html.Br(), 16 | dcc.Link("Home", href="/"), 17 | ] 18 | ) 19 | -------------------------------------------------------------------------------- /multi_page_flask_login/pages/logout.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html, dcc 3 | from flask_login import logout_user, current_user 4 | 5 | dash.register_page(__name__) 6 | 7 | 8 | def layout(**kwargs): 9 | if current_user.is_authenticated: 10 | logout_user() 11 | return html.Div( 12 | [ 13 | html.Div(html.H2("You have been logged out - Please login")), 14 | html.Br(), 15 | dcc.Link("Home", href="/"), 16 | ] 17 | ) 18 | -------------------------------------------------------------------------------- /multi_page_flask_login/pages/page-1.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html, dcc, Output, Input, callback 3 | 4 | dash.register_page(__name__) 5 | 6 | 7 | layout = html.Div( 8 | [ 9 | html.H1("Page 1"), 10 | dcc.Dropdown( 11 | id="page-1-dropdown", 12 | options=[{"label": i, "value": i} for i in ["LA", "NYC", "MTL"]], 13 | value="LA", 14 | ), 15 | html.Div(id="page-1-content"), 16 | html.Br(), 17 | dcc.Link("Go to Page 2", href="/page-2"), 18 | html.Br(), 19 | dcc.Link("Go back to home", href="/"), 20 | ] 21 | ) 22 | 23 | 24 | @callback(Output("page-1-content", "children"), Input("page-1-dropdown", "value")) 25 | def page_1_dropdown(value): 26 | return f'You have selected "{value}"' 27 | -------------------------------------------------------------------------------- /multi_page_flask_login/pages/page-2.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html, dcc, Output, Input, callback 3 | from flask_login import current_user 4 | 5 | dash.register_page(__name__) 6 | 7 | 8 | def layout(**kwargs): 9 | if not current_user.is_authenticated: 10 | return html.Div(["Please ", dcc.Link("login", href="/login"), " to continue"]) 11 | 12 | return html.Div( 13 | [ 14 | html.H1("Page 2"), 15 | dcc.RadioItems( 16 | id="page-2-radios", 17 | options=[{"label": i, "value": i} for i in ["Orange", "Blue", "Red"]], 18 | value="Orange", 19 | ), 20 | html.Div(id="page-2-content"), 21 | html.Br(), 22 | dcc.Link("Go to Page 1", href="/page-1"), 23 | html.Br(), 24 | dcc.Link("Go back to home", href="/"), 25 | ] 26 | ) 27 | 28 | 29 | @callback(Output("page-2-content", "children"), Input("page-2-radios", "value")) 30 | def page_2_radios(value): 31 | return f'You have selected "{value}"' 32 | -------------------------------------------------------------------------------- /multi_page_flask_login2/.env: -------------------------------------------------------------------------------- 1 | SECRET_KEY=291a47103f3cd8fc26d05ffc7b31e33f73ca3d459d6259bd -------------------------------------------------------------------------------- /multi_page_flask_login2/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | CREDIT: This code was originally adapted for Pages based on Nader Elshehabi's article: 3 | https://dev.to/naderelshehabi/securing-plotly-dash-using-flask-login-4ia2 4 | https://github.com/naderelshehabi/dash-flask-login 5 | 6 | This version is updated by Dash community member @jinnyzor For more info see: 7 | https://community.plotly.com/t/dash-app-pages-with-flask-login-flow-using-flask/69507 8 | 9 | For other Authentication options see: 10 | Dash Enterprise: https://dash.plotly.com/authentication#dash-enterprise-auth 11 | Dash Basic Auth: https://dash.plotly.com/authentication#basic-auth 12 | 13 | """ 14 | 15 | 16 | import os 17 | from flask import Flask, request, redirect, session 18 | from flask_login import login_user, LoginManager, UserMixin, logout_user, current_user 19 | 20 | import dash 21 | from dash import dcc, html, Input, Output, State, ALL 22 | from dash.exceptions import PreventUpdate 23 | from utils.login_handler import restricted_page 24 | 25 | 26 | 27 | # Exposing the Flask Server to enable configuring it for logging in 28 | server = Flask(__name__) 29 | 30 | 31 | @server.route('/login', methods=['POST']) 32 | def login_button_click(): 33 | if request.form: 34 | username = request.form['username'] 35 | password = request.form['password'] 36 | if VALID_USERNAME_PASSWORD.get(username) is None: 37 | return """invalid username and/or password login here""" 38 | if VALID_USERNAME_PASSWORD.get(username) == password: 39 | login_user(User(username)) 40 | if 'url' in session: 41 | if session['url']: 42 | url = session['url'] 43 | session['url'] = None 44 | return redirect(url) ## redirect to target url 45 | return redirect('/') ## redirect to home 46 | return """invalid username and/or password login here""" 47 | 48 | 49 | app = dash.Dash( 50 | __name__, server=server, use_pages=True, suppress_callback_exceptions=True 51 | ) 52 | 53 | # Keep this out of source code repository - save in a file or a database 54 | # passwords should be encrypted 55 | VALID_USERNAME_PASSWORD = {"test": "test", "hello": "world"} 56 | 57 | 58 | # Updating the Flask Server configuration with Secret Key to encrypt the user session cookie 59 | server.config.update(SECRET_KEY=os.getenv("SECRET_KEY")) 60 | 61 | # Login manager object will be used to login / logout users 62 | login_manager = LoginManager() 63 | login_manager.init_app(server) 64 | login_manager.login_view = "/login" 65 | 66 | 67 | class User(UserMixin): 68 | # User data model. It has to have at least self.id as a minimum 69 | def __init__(self, username): 70 | self.id = username 71 | 72 | 73 | @login_manager.user_loader 74 | def load_user(username): 75 | """This function loads the user by user id. Typically this looks up the user from a user database. 76 | We won't be registering or looking up users in this example, since we'll just login using LDAP server. 77 | So we'll simply return a User object with the passed in username. 78 | """ 79 | return User(username) 80 | 81 | 82 | app.layout = html.Div( 83 | [ 84 | dcc.Location(id="url"), 85 | html.Div(id="user-status-header"), 86 | html.Hr(), 87 | dash.page_container, 88 | ] 89 | ) 90 | 91 | 92 | @app.callback( 93 | Output("user-status-header", "children"), 94 | Output('url','pathname'), 95 | Input("url", "pathname"), 96 | Input({'index': ALL, 'type':'redirect'}, 'n_intervals') 97 | ) 98 | def update_authentication_status(path, n): 99 | ### logout redirect 100 | if n: 101 | if not n[0]: 102 | return '', dash.no_update 103 | else: 104 | return '', '/login' 105 | 106 | ### test if user is logged in 107 | if current_user.is_authenticated: 108 | if path == '/login': 109 | return dcc.Link("logout", href="/logout"), '/' 110 | return dcc.Link("logout", href="/logout"), dash.no_update 111 | else: 112 | ### if page is restricted, redirect to login and save path 113 | if path in restricted_page: 114 | session['url'] = path 115 | return dcc.Link("login", href="/login"), '/login' 116 | 117 | ### if path not login and logout display login link 118 | if current_user and path not in ['/login', '/logout']: 119 | return dcc.Link("login", href="/login"), dash.no_update 120 | 121 | ### if path login and logout hide links 122 | if path in ['/login', '/logout']: 123 | return '', dash.no_update 124 | 125 | 126 | 127 | if __name__ == "__main__": 128 | app.run(debug=True) -------------------------------------------------------------------------------- /multi_page_flask_login2/pages/home.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html, dcc 3 | 4 | dash.register_page(__name__, path="/") 5 | 6 | 7 | layout = html.Div( 8 | [ 9 | dcc.Link("Go to Page 1", href="/page-1"), 10 | html.Br(), 11 | dcc.Link("Go to Page 2", href="/page-2"), 12 | ] 13 | ) 14 | -------------------------------------------------------------------------------- /multi_page_flask_login2/pages/login.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html, dcc 3 | 4 | 5 | dash.register_page(__name__) 6 | 7 | # Login screen 8 | layout = html.Form( 9 | [ 10 | html.H2("Please log in to continue:", id="h1"), 11 | dcc.Input(placeholder="Enter your username", type="text", id="uname-box", name='username'), 12 | dcc.Input(placeholder="Enter your password", type="password", id="pwd-box", name='password'), 13 | html.Button(children="Login", n_clicks=0, type="submit", id="login-button"), 14 | html.Div(children="", id="output-state") 15 | ], method='POST' 16 | ) -------------------------------------------------------------------------------- /multi_page_flask_login2/pages/logout.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html, dcc 3 | from flask_login import logout_user, current_user 4 | 5 | dash.register_page(__name__) 6 | 7 | 8 | def layout(**kwargs): 9 | if current_user.is_authenticated: 10 | logout_user() 11 | return html.Div( 12 | [ 13 | html.Div(html.H2("You have been logged out - You will be redirected to login")), 14 | dcc.Interval(id={'index':'redirectLogin', 'type':'redirect'}, n_intervals=0, interval=1*3000) 15 | ] 16 | ) -------------------------------------------------------------------------------- /multi_page_flask_login2/pages/page-1.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html, dcc, Output, Input, callback 3 | 4 | dash.register_page(__name__) 5 | 6 | 7 | layout = html.Div( 8 | [ 9 | html.H1("Page 1"), 10 | dcc.Dropdown( 11 | id="page-1-dropdown", 12 | options=[{"label": i, "value": i} for i in ["LA", "NYC", "MTL"]], 13 | value="LA", 14 | ), 15 | html.Div(id="page-1-content"), 16 | html.Br(), 17 | dcc.Link("Go to Page 2", href="/page-2"), 18 | html.Br(), 19 | dcc.Link("Go back to home", href="/"), 20 | ] 21 | ) 22 | 23 | 24 | @callback(Output("page-1-content", "children"), Input("page-1-dropdown", "value")) 25 | def page_1_dropdown(value): 26 | return f'You have selected "{value}"' 27 | -------------------------------------------------------------------------------- /multi_page_flask_login2/pages/page-2.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html, dcc, Output, Input, callback 3 | from flask_login import current_user 4 | from utils.login_handler import require_login 5 | 6 | dash.register_page(__name__) 7 | require_login(__name__) 8 | 9 | 10 | def layout(**kwargs): 11 | if not current_user.is_authenticated: 12 | return html.Div(["Please ", dcc.Link("login", href="/login"), " to continue"]) 13 | 14 | return html.Div( 15 | [ 16 | html.H1("Page 2"), 17 | dcc.RadioItems( 18 | id="page-2-radios", 19 | options=[{"label": i, "value": i} for i in ["Orange", "Blue", "Red"]], 20 | value="Orange", 21 | ), 22 | html.Div(id="page-2-content"), 23 | html.Br(), 24 | dcc.Link("Go to Page 1", href="/page-1"), 25 | html.Br(), 26 | dcc.Link("Go back to home", href="/"), 27 | ] 28 | ) 29 | 30 | 31 | @callback(Output("page-2-content", "children"), Input("page-2-radios", "value")) 32 | def page_2_radios(value): 33 | return f'You have selected "{value}"' -------------------------------------------------------------------------------- /multi_page_flask_login2/utils/login_handler.py: -------------------------------------------------------------------------------- 1 | import dash 2 | restricted_page = {} 3 | 4 | def require_login(page): 5 | for pg in dash.page_registry: 6 | if page == pg: 7 | restricted_page[dash.page_registry[pg]['path']] = True -------------------------------------------------------------------------------- /multi_page_layout_functions/app.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | import dash_bootstrap_components as dbc 4 | 5 | 6 | app = dash.Dash( 7 | __name__, 8 | use_pages=True, 9 | external_stylesheets=[dbc.themes.BOOTSTRAP], 10 | ) 11 | 12 | 13 | navbar = dbc.NavbarSimple( 14 | dbc.Nav( 15 | [ 16 | dbc.NavLink(page["name"], href=page["path"]) 17 | for page in dash.page_registry.values() 18 | if page.get("top_nav") 19 | ], 20 | ), 21 | brand="Multi Page App Demo", 22 | color="primary", 23 | dark=True, 24 | className="mb-2", 25 | ) 26 | 27 | 28 | app.layout = dbc.Container( 29 | [navbar, dash.page_container], 30 | fluid=True, 31 | ) 32 | 33 | 34 | if __name__ == "__main__": 35 | app.run(debug=True) 36 | -------------------------------------------------------------------------------- /multi_page_layout_functions/pages/about.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | import dash 3 | 4 | dash.register_page(__name__, top_nav=True) 5 | 6 | 7 | layout = html.Div("About page content") 8 | -------------------------------------------------------------------------------- /multi_page_layout_functions/pages/home.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | import dash 3 | 4 | dash.register_page(__name__, path="/", top_nav=True) 5 | 6 | 7 | layout = html.Div("Home page content") 8 | -------------------------------------------------------------------------------- /multi_page_layout_functions/pages/side_bar.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html 3 | import dash_bootstrap_components as dbc 4 | 5 | 6 | def sidebar(): 7 | return html.Div( 8 | dbc.Nav( 9 | [ 10 | dbc.NavLink( 11 | [ 12 | html.Div(page["name"], className="ms-2"), 13 | ], 14 | href=page["path"], 15 | active="exact", 16 | ) 17 | for page in dash.page_registry.values() 18 | if page["path"].startswith("/topic") 19 | ], 20 | vertical=True, 21 | pills=True, 22 | className="bg-light", 23 | ) 24 | ) 25 | -------------------------------------------------------------------------------- /multi_page_layout_functions/pages/topic_1.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | 3 | import dash 4 | import dash_bootstrap_components as dbc 5 | 6 | from .side_bar import sidebar 7 | 8 | dash.register_page( 9 | __name__, 10 | name="Topics", 11 | top_nav=True, 12 | ) 13 | 14 | 15 | def layout(**kwargs): 16 | return dbc.Row( 17 | [dbc.Col(sidebar(), width=2), dbc.Col(html.Div("Topics Home Page"), width=10)] 18 | ) 19 | -------------------------------------------------------------------------------- /multi_page_layout_functions/pages/topic_2.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | 3 | import dash 4 | import dash_bootstrap_components as dbc 5 | 6 | from .side_bar import sidebar 7 | 8 | dash.register_page(__name__) 9 | 10 | 11 | def layout(**kwargs): 12 | return dbc.Row( 13 | [dbc.Col(sidebar(), width=2), dbc.Col(html.Div("Topic 2 content"), width=10)] 14 | ) 15 | -------------------------------------------------------------------------------- /multi_page_layout_functions/pages/topic_3.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | 3 | import dash 4 | import dash_bootstrap_components as dbc 5 | 6 | from .side_bar import sidebar 7 | 8 | dash.register_page(__name__) 9 | 10 | 11 | def layout(**kwargs): 12 | return dbc.Row( 13 | [dbc.Col(sidebar(), width=2), dbc.Col(html.Div("Topic 3 content"), width=10)] 14 | ) 15 | -------------------------------------------------------------------------------- /multi_page_meta_tags/app.py: -------------------------------------------------------------------------------- 1 | from dash import Dash, html, dcc 2 | import dash 3 | 4 | 5 | app = Dash(__name__, use_pages=True) 6 | 7 | 8 | app.layout = html.Div( 9 | [ 10 | html.H1("App Frame"), 11 | html.Div( 12 | [ 13 | html.Div( 14 | [ 15 | html.Img( 16 | src=app.get_asset_url(page["image"]), 17 | height="40px", 18 | width="60px", 19 | ), 20 | dcc.Link(f"{page['name']} - {page['path']}", href=page["path"]), 21 | ], 22 | style={"margin": 20}, 23 | ) 24 | for page in dash.page_registry.values() 25 | ] 26 | ), 27 | dash.page_container, 28 | ] 29 | ) 30 | 31 | 32 | if __name__ == "__main__": 33 | app.run(debug=True) 34 | -------------------------------------------------------------------------------- /multi_page_meta_tags/app_dbc.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html 3 | import dash_bootstrap_components as dbc 4 | 5 | 6 | app = dash.Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP]) 7 | 8 | 9 | pages_dropdown = dbc.DropdownMenu( 10 | [ 11 | dbc.DropdownMenuItem( 12 | children=html.Div( 13 | [ 14 | html.Img( 15 | src=app.get_asset_url(page["image"]), 16 | height="60px", 17 | width="80px", 18 | ), 19 | page["name"], 20 | ] 21 | ), 22 | href=page["path"], 23 | ) 24 | for page in dash.page_registry.values() 25 | ], 26 | nav=True, 27 | label="More Pages", 28 | toggle_class_name="text-white", 29 | ) 30 | # 31 | navbar = dbc.Navbar( 32 | dbc.Container( 33 | [ 34 | html.A( 35 | # Use row and col to control vertical alignment of logo / brand 36 | dbc.Row( 37 | [ 38 | dbc.Col( 39 | html.Img(src=app.get_asset_url("logo.jpeg"), height="50px") 40 | ), 41 | dbc.Col(dbc.NavbarBrand("Navbar", className="ms-2")), 42 | ], 43 | align="center", 44 | className="g-0", 45 | ), 46 | ), 47 | pages_dropdown, 48 | ], 49 | fluid=True, 50 | ), 51 | color="dark", 52 | dark=True, 53 | ) 54 | # 55 | 56 | app.layout = dbc.Container( 57 | [ 58 | navbar, 59 | dash.page_container, 60 | ], 61 | fluid=True, 62 | ) 63 | 64 | 65 | if __name__ == "__main__": 66 | app.run(debug=True) 67 | -------------------------------------------------------------------------------- /multi_page_meta_tags/assets/app.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_meta_tags/assets/app.jpeg -------------------------------------------------------------------------------- /multi_page_meta_tags/assets/birdhouse.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_meta_tags/assets/birdhouse.jpeg -------------------------------------------------------------------------------- /multi_page_meta_tags/assets/birds.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_meta_tags/assets/birds.jpeg -------------------------------------------------------------------------------- /multi_page_meta_tags/assets/logo.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_meta_tags/assets/logo.jpeg -------------------------------------------------------------------------------- /multi_page_meta_tags/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_meta_tags/pages/__init__.py -------------------------------------------------------------------------------- /multi_page_meta_tags/pages/a_page.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | dash.register_page(__name__) 4 | 5 | 6 | def layout(**kwargs): 7 | return """ 8 | This page uses a generic image. No image is specified and there is no image that matches 9 | the module name in the assets folder, so it uses `app.jpeg` or `logo.jpeg` if no `app.jpeg` exists. 10 | 11 | The title and description are not supplied, so they will be inferred from the module name. In this 12 | case it will be "A page". 13 | """ 14 | -------------------------------------------------------------------------------- /multi_page_meta_tags/pages/birds.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | dash.register_page( 4 | __name__, 5 | title="(birds) The title, headline or name of the page", 6 | description="(birds) A short description or summary 2-3 sentences", 7 | ) 8 | 9 | 10 | def layout(**kwargs): 11 | return """ 12 | No image is specified but it's inferred from the module name. 13 | The module name is`birds.py` so it uses the `birds.jpeg` file in the assets folder. 14 | """ 15 | -------------------------------------------------------------------------------- /multi_page_meta_tags/pages/home.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import html 3 | 4 | 5 | dash.register_page( 6 | __name__, 7 | path="/", 8 | image="birdhouse.jpeg", 9 | title="(home) The title, headline or name of the page", 10 | description="(home) A short description or summary 2-3 sentences", 11 | ) 12 | 13 | 14 | layout = html.Div("The image for the home page is specified as `birdhouse.jpeg'") 15 | -------------------------------------------------------------------------------- /multi_page_nested_folders/app.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import callback, Output, Input, State 3 | import dash_mantine_components as dmc 4 | from dash_iconify import DashIconify 5 | 6 | # Set "mantine_light" as the default plotly figure template 7 | # styles Plotly figures with Mantine theme https://www.dash-mantine-components.com/components/figure-templates 8 | dmc.add_figure_templates(default="mantine_light") 9 | 10 | app = dash.Dash(__name__, use_pages=True, external_stylesheets=dmc.styles.ALL) 11 | 12 | 13 | def create_nav_link(icon, label, href): 14 | return dmc.NavLink( 15 | label=label, 16 | href=href, 17 | active="exact", 18 | leftSection=DashIconify(icon=icon) 19 | 20 | ) 21 | 22 | navbar_links =dmc.Box( 23 | [ 24 | create_nav_link( 25 | icon="radix-icons:rocket", 26 | label="Home", 27 | href="/", 28 | ), 29 | dmc.Divider( 30 | label="Chapter 1", style={"marginBottom": 20, "marginTop": 20} 31 | ), 32 | dmc.Stack( 33 | [ 34 | create_nav_link( 35 | icon=page["icon"], label=page["name"], href=page["path"] 36 | ) 37 | for page in dash.page_registry.values() 38 | if page["path"].startswith("/chapter1") 39 | ], 40 | ), 41 | dmc.Divider( 42 | label="Chapter 2", style={"marginBottom": 20, "marginTop": 20} 43 | ), 44 | dmc.Stack( 45 | [ 46 | create_nav_link( 47 | icon=page["icon"], label=page["name"], href=page["path"] 48 | ) 49 | for page in dash.page_registry.values() 50 | if page["path"].startswith("/chapter2") 51 | ], 52 | ), 53 | ], 54 | ) 55 | 56 | layout = dmc.AppShell( 57 | [ 58 | dmc.AppShellHeader( 59 | dmc.Group( 60 | [ 61 | dmc.Burger(id="burger", size="sm", hiddenFrom="sm", opened=False), 62 | dmc.Title("Demo App", c="blue"), 63 | ], 64 | h="100%", 65 | px="md", 66 | ) 67 | ), 68 | dmc.AppShellNavbar( 69 | id="navbar", 70 | children=navbar_links, 71 | p="md", 72 | ), 73 | dmc.AppShellMain(dash.page_container), 74 | ], 75 | header={"height": 60}, 76 | padding="md", 77 | navbar={ 78 | "width": 300, 79 | "breakpoint": "sm", 80 | "collapsed": {"mobile": True}, 81 | }, 82 | id="appshell", 83 | ) 84 | 85 | 86 | app.layout = dmc.MantineProvider(layout) 87 | 88 | 89 | @callback( 90 | Output("appshell", "navbar"), 91 | Input("burger", "opened"), 92 | State("appshell", "navbar"), 93 | ) 94 | def navbar_is_open(opened, navbar): 95 | navbar["collapsed"] = {"mobile": not opened} 96 | return navbar 97 | 98 | 99 | if __name__ == "__main__": 100 | app.run(debug=True) 101 | -------------------------------------------------------------------------------- /multi_page_nested_folders/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_nested_folders/pages/__init__.py -------------------------------------------------------------------------------- /multi_page_nested_folders/pages/chapter1/bar-charts.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, Input, Output, callback, register_page 2 | import dash_mantine_components as dmc 3 | import plotly.express as px 4 | 5 | register_page(__name__, icon="fa:bar-chart") 6 | df = px.data.tips() 7 | days = df.day.unique() 8 | 9 | layout = dmc.Box( 10 | [ 11 | dmc.Select( 12 | id="dropdown", 13 | data=[{"label": x, "value": x} for x in days], 14 | value=days[0], 15 | clearable=False, 16 | ), 17 | dcc.Graph(id="bar-chart"), 18 | ] 19 | ) 20 | 21 | 22 | @callback(Output("bar-chart", "figure"), Input("dropdown", "value")) 23 | def update_bar_chart(day): 24 | mask = df["day"] == day 25 | fig = px.bar(df[mask], x="sex", y="total_bill", color="smoker", barmode="group") 26 | return fig 27 | -------------------------------------------------------------------------------- /multi_page_nested_folders/pages/chapter1/pie-chart.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, Input, Output, callback, register_page 2 | import dash_mantine_components as dmc 3 | import plotly.express as px 4 | 5 | register_page(__name__, icon="fa:pie-chart") 6 | 7 | # This dataframe has 244 lines, but 4 distinct values for `day` 8 | df = px.data.tips() 9 | 10 | 11 | layout = dmc.Box( 12 | [ 13 | dmc.Text("Names:"), 14 | dmc.Select( 15 | id="names", 16 | value="day", 17 | data=[{"value": x, "label": x} for x in ["smoker", "day", "time", "sex"]], 18 | clearable=False, 19 | ), 20 | dmc.Space(h=20), 21 | dmc.Text("Values:"), 22 | dmc.Select( 23 | id="values", 24 | value="total_bill", 25 | data=[{"value": x, "label": x} for x in ["total_bill", "tip", "size"]], 26 | clearable=False, 27 | ), 28 | dcc.Graph(id="pie-chart"), 29 | ] 30 | ) 31 | 32 | 33 | @callback( 34 | Output("pie-chart", "figure"), [Input("names", "value"), Input("values", "value")] 35 | ) 36 | def generate_chart(names, values): 37 | fig = px.pie(df, values=values, names=names, template="mantine_light") 38 | return fig 39 | -------------------------------------------------------------------------------- /multi_page_nested_folders/pages/chapter2/heatmaps.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, html, Input, Output, callback, register_page 2 | import dash_mantine_components as dmc 3 | import plotly.express as px 4 | 5 | register_page(__name__, icon="ph:squares-four-duotone") 6 | 7 | df = px.data.medals_wide(indexed=True) 8 | 9 | layout = html.Div( 10 | [ 11 | dmc.Text("Medals included:"), 12 | dmc.MultiSelect( 13 | id="heatmaps-medals", 14 | data=[{"label": x, "value": x} for x in df.columns], 15 | value=df.columns.tolist(), 16 | ), 17 | dcc.Graph(id="heatmaps-graph"), 18 | ] 19 | ) 20 | 21 | 22 | @callback(Output("heatmaps-graph", "figure"), Input("heatmaps-medals", "value")) 23 | def filter_heatmap(cols): 24 | fig = px.imshow(df[cols]) 25 | return fig 26 | -------------------------------------------------------------------------------- /multi_page_nested_folders/pages/chapter2/histograms.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, Input, Output, callback, register_page 2 | import dash_mantine_components as dmc 3 | import plotly.express as px 4 | import numpy as np 5 | 6 | 7 | register_page(__name__, icon="mdi:chart-histogram") 8 | 9 | np.random.seed(2020) 10 | 11 | layout = dmc.Box( 12 | [ 13 | dcc.Graph(id="histograms-graph"), 14 | dmc.Text("Mean:"), 15 | dmc.Slider( 16 | id="histograms-mean", 17 | min=-3, 18 | max=3, 19 | value=0, 20 | marks=[ 21 | {"value": -3, "label": "-3"}, 22 | {"value": 3, "label": "3"}, 23 | ], 24 | ), 25 | dmc.Text("Standard Deviation:"), 26 | dmc.Slider( 27 | id="histograms-std", 28 | min=1, 29 | max=3, 30 | value=1, 31 | marks=[ 32 | {"value": 1, "label": "1"}, 33 | {"value": 3, "label": "3"}, 34 | ], 35 | ), 36 | ] 37 | ) 38 | 39 | 40 | @callback( 41 | Output("histograms-graph", "figure"), 42 | Input("histograms-mean", "value"), 43 | Input("histograms-std", "value"), 44 | ) 45 | def display_color(mean, std): 46 | data = np.random.normal(mean, std, size=500) 47 | fig = px.histogram(data, nbins=30, range_x=[-10, 10]) 48 | return fig 49 | -------------------------------------------------------------------------------- /multi_page_nested_folders/pages/home.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, register_page 2 | import dash_mantine_components as dmc 3 | 4 | register_page(__name__, path="/", icon="fa-solid:home") 5 | 6 | layout = dmc.Container( 7 | [ 8 | dmc.Title("Welcome to the home page"), 9 | dcc.Markdown( 10 | """ 11 | This is a demo of a multi-page app with nested folders in the `pages` folder. 12 | 13 | For example: 14 | ``` 15 | - app.py 16 | - pages 17 | - chapter1 18 | |-- page1.py 19 | |-- page2.py 20 | - chapter2 21 | |-- page1.py 22 | |-- page2.py 23 | - home.py 24 | ``` 25 | 26 | This app also demos how to add arbitrary data to the `page_registry`. This example adds icons to the `page_registry` 27 | 28 | ``` 29 | dash.register_page(__name__, icon="fa:bar-chart") 30 | 31 | ``` 32 | 33 | In `app.py` we loop through `dash.page_registry` to create the links: 34 | 35 | ``` 36 | children=[ 37 | create_nav_link( 38 | icon=page["icon"], label=page["name"], href=page["path"] 39 | ) 40 | for page in dash.page_registry.values() 41 | if page["path"].startswith("/chapter2") 42 | ], 43 | ``` 44 | 45 | """ 46 | ), 47 | ] 48 | ) 49 | -------------------------------------------------------------------------------- /multi_page_no_pages_folder/app.py: -------------------------------------------------------------------------------- 1 | from dash import Dash, html, dcc 2 | import dash 3 | 4 | app = Dash(__name__, use_pages=True, pages_folder="") 5 | 6 | dash.register_page("home", layout="We're home!", path="/") 7 | dash.register_page( 8 | "very_important", layout="Don't miss it!", path="/important", order=0 9 | ) 10 | 11 | 12 | app.layout = html.Div( 13 | [ 14 | html.H1("App Frame"), 15 | html.Div( 16 | [ 17 | html.Div( 18 | dcc.Link(f"{page['name']} - {page['path']}", href=page["path"]) 19 | ) 20 | for page in dash.page_registry.values() 21 | if page["module"] != "pages.not_found_404" 22 | ] 23 | ), 24 | dash.page_container, 25 | ] 26 | ) 27 | 28 | 29 | if __name__ == "__main__": 30 | app.run(debug=True) 31 | -------------------------------------------------------------------------------- /multi_page_path_variables/app.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dash_bootstrap_components as dbc 3 | 4 | 5 | app = dash.Dash( 6 | __name__, 7 | use_pages=True, 8 | external_stylesheets=[dbc.themes.BOOTSTRAP], 9 | ) 10 | 11 | 12 | navbar = dbc.NavbarSimple( 13 | dbc.Nav( 14 | [ 15 | dbc.NavLink(page["name"], href=page["path"]) 16 | for page in dash.page_registry.values() 17 | ], 18 | ), 19 | brand="Multi Page App Demo", 20 | color="primary", 21 | dark=True, 22 | className="mb-2", 23 | ) 24 | 25 | 26 | app.layout = dbc.Container( 27 | [navbar, dash.page_container], 28 | fluid=True, 29 | ) 30 | 31 | 32 | if __name__ == "__main__": 33 | app.run(debug=True) 34 | -------------------------------------------------------------------------------- /multi_page_path_variables/pages/about.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | import dash_bootstrap_components as dbc 3 | import dash 4 | 5 | from .side_bar import sidebar 6 | from .topic_1 import layout_1 7 | from .topic_2 import layout_2 8 | from .topic_3 import layout_3 9 | 10 | def title(topic=None): 11 | # This will show in browser tab and the meta tags 12 | return f"About page: {topic}" 13 | 14 | 15 | def description(topic=None): 16 | # This is the description for the meta tags. It will show when you share a link to this page. 17 | if topic == "topic1": 18 | return "Here is more information on topic 1" 19 | return "Here is general info about the topics on this page" 20 | 21 | 22 | dash.register_page( 23 | __name__, 24 | path_template="/about/", 25 | title=title, 26 | description=description, 27 | # sets a default for the path variable 28 | path="/about/topic-1", 29 | # prevents showing a Page Not Found if someone enters /about in the browser 30 | redirect_from=["/about", "/about/"], 31 | ) 32 | 33 | 34 | def layout(topic=None, **other_unknown_query_strings): 35 | parent_card = dbc.Card(" Here is the main About Page content", body=True) 36 | 37 | if topic == "topic-1": 38 | topic_card = layout_1() 39 | elif topic == "topic-2": 40 | topic_card = layout_2 41 | elif topic == "topic-3": 42 | topic_card = layout_3 43 | else: 44 | topic_card= "" 45 | 46 | return dbc.Row( 47 | [dbc.Col(sidebar(), width=2), dbc.Col([parent_card, topic_card], width=10)] 48 | ) 49 | 50 | 51 | -------------------------------------------------------------------------------- /multi_page_path_variables/pages/home.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | import dash 3 | 4 | dash.register_page(__name__, path="/") 5 | 6 | 7 | layout = html.Div("Home page content") 8 | -------------------------------------------------------------------------------- /multi_page_path_variables/pages/side_bar.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | import dash_bootstrap_components as dbc 3 | 4 | path_pagename_map = { 5 | "/about/topic-1": "Topic 1", 6 | "/about/topic-2": "Topic 2", 7 | "/about/topic-3": "Topic 3" 8 | } 9 | 10 | def sidebar(): 11 | return html.Div( 12 | dbc.Nav( 13 | [ 14 | dbc.NavLink( 15 | [ 16 | html.Div(name, className="ms-2"), 17 | ], 18 | href=href, 19 | active="exact", 20 | ) 21 | for href, name in path_pagename_map.items() 22 | ], 23 | vertical=True, 24 | pills=True, 25 | className="bg-light", 26 | ) 27 | ) 28 | -------------------------------------------------------------------------------- /multi_page_path_variables/pages/topic_1.py: -------------------------------------------------------------------------------- 1 | import dash_bootstrap_components as dbc 2 | 3 | 4 | def layout_1(): 5 | return dbc.Card("Topic 1 content", body=True, className="mt-2") 6 | -------------------------------------------------------------------------------- /multi_page_path_variables/pages/topic_2.py: -------------------------------------------------------------------------------- 1 | import dash_bootstrap_components as dbc 2 | 3 | layout_2 = dbc.Card("Topic 2 content", body=True, className="mt-2") 4 | -------------------------------------------------------------------------------- /multi_page_path_variables/pages/topic_3.py: -------------------------------------------------------------------------------- 1 | import dash_bootstrap_components as dbc 2 | 3 | layout_3 = dbc.Card("Topic 3 content", body=True, className="mt-2") 4 | -------------------------------------------------------------------------------- /multi_page_query_strings/app.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dash_bootstrap_components as dbc 3 | 4 | 5 | app = dash.Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP]) 6 | 7 | 8 | navbar = dbc.NavbarSimple( 9 | dbc.Nav( 10 | [ 11 | dbc.NavLink(page["name"], href=page["path"]) 12 | for page in dash.page_registry.values() 13 | if page["module"] != "pages.not_found_404" 14 | ], 15 | ), 16 | brand="Multi Page App Demo: Query Strings", 17 | color="primary", 18 | dark=True, 19 | className="mb-2", 20 | ) 21 | 22 | app.layout = dbc.Container( 23 | [navbar, dash.page_container], 24 | fluid=True, 25 | ) 26 | 27 | if __name__ == "__main__": 28 | app.run(debug=True) 29 | -------------------------------------------------------------------------------- /multi_page_query_strings/pages/bar_chart.py: -------------------------------------------------------------------------------- 1 | import dash 2 | 3 | dash.register_page(__name__) 4 | 5 | from dash import Dash, dcc, html, Input, Output, callback 6 | import plotly.express as px 7 | 8 | df = px.data.tips() 9 | days = df.day.unique() 10 | 11 | 12 | def layout(day=days[0], **other_unknown_query_strings): 13 | return html.Div( 14 | [ 15 | dcc.Dropdown( 16 | id="dropdown", 17 | options=[{"label": x, "value": x} for x in days], 18 | value=day, 19 | clearable=False, 20 | ), 21 | dcc.Graph(id="bar-chart"), 22 | ] 23 | ) 24 | 25 | 26 | @callback(Output("bar-chart", "figure"), Input("dropdown", "value")) 27 | def update_bar_chart(day): 28 | mask = df["day"] == day 29 | fig = px.bar(df[mask], x="sex", y="total_bill", color="smoker", barmode="group") 30 | return fig 31 | -------------------------------------------------------------------------------- /multi_page_query_strings/pages/home.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, html 2 | import dash 3 | 4 | dash.register_page(__name__, path="/") 5 | 6 | text = """ 7 | __This is a demo of how to use query strings to pass variables to other pages of a multi-page app.__ 8 | 9 | See the bar chart page with tips on: 10 | - 11 | - 12 | 13 | -------------- 14 | 15 | __This app also demos how to use high performance `dcc.Link` within `dcc.Markdown`__ 16 | 17 | One of the great things about multi-page apps with Dash is that the when you navigate with `dcc.Location` 18 | and `dcc.Link`, it will update the layout without refreshing the page, making the navigation super fast. 19 | 20 | The links above are high performance dcc.Link components. They are defined in the Markup text like this: 21 | ``` 22 | 23 | ``` 24 | 25 | Here's the same link with the standard Markdown format `[Saturday](/bar-chart?day=Sat)` 26 | Give it a try! This simple page updates pretty fast, but you will notice a brief flash while the page updates: 27 | 28 | - [Saturday](/bar-chart?day=Sat) 29 | 30 | For more information, see this [Dash Community Forum post](https://community.plotly.com/t/how-to-use-dcc-link-in-dcc-markdown-for-high-performance-links/66781). 31 | 32 | 33 | ---------- 34 | 35 | Of course, you can also use `dcc.Link` with query strings in your layout in the standard way: 36 | ```python 37 | html.Div([ 38 | dcc.Link("Saturday", href="/bar-chart?day=Sat") 39 | ... 40 | ]) 41 | ``` 42 | 43 | """ 44 | 45 | layout = dcc.Markdown(text, dangerously_allow_html= True, style={"margin": 50}) 46 | -------------------------------------------------------------------------------- /multi_page_query_strings/pages/not_found_404.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | import dash 3 | 4 | dash.register_page(__name__, path="/404") 5 | 6 | 7 | layout = html.H1("Custom 404") 8 | -------------------------------------------------------------------------------- /multi_page_store/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example demonstrates sharing data between pages of a multi-page app. 3 | Note that dcc.Store is in the app.py file so that it's accessible to all pages. 4 | """ 5 | 6 | 7 | from dash import html, dcc 8 | import dash 9 | 10 | app = dash.Dash(__name__, use_pages=True) 11 | 12 | app.layout = html.Div( 13 | [ 14 | dcc.Store(id="store", data={}), 15 | html.H1("Multi Page App Demo: Sharing data between pages"), 16 | html.Div( 17 | [ 18 | html.Div( 19 | dcc.Link(f"{page['name']}", href=page["path"]), 20 | ) 21 | for page in dash.page_registry.values() 22 | ] 23 | ), 24 | html.Hr(), 25 | dash.page_container, 26 | ] 27 | ) 28 | 29 | 30 | if __name__ == "__main__": 31 | app.run(debug=True) 32 | -------------------------------------------------------------------------------- /multi_page_store/pages/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AnnMarieW/dash-multi-page-app-demos/1b21f0b7c4edb43a194840eab0d5c45d7fe94afd/multi_page_store/pages/__init__.py -------------------------------------------------------------------------------- /multi_page_store/pages/graph.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, html, Input, Output, callback, register_page 2 | import plotly.express as px 3 | import pandas as pd 4 | 5 | register_page(__name__, path="/") 6 | 7 | df = px.data.gapminder() 8 | years = df.year.unique() 9 | 10 | layout = html.Div( 11 | [ 12 | html.Label("Select Year"), 13 | dcc.Dropdown(years, years[-1], id="year", clearable=False, persistence="session"), 14 | dcc.Graph(id="graph"), 15 | ] 16 | ) 17 | 18 | 19 | @callback( 20 | Output("store", "data"), 21 | Input("year", "value"), 22 | ) 23 | def get_data(year): 24 | # simulate some expensive data processing step 25 | # store results in a dcc.Store in app.py 26 | dff = df.query(f"year=={year}") 27 | return dff.to_dict("records") 28 | 29 | 30 | @callback( 31 | Output("graph", "figure"), 32 | Input("store", "data"), 33 | ) 34 | def update(store): 35 | dff = pd.DataFrame(store) 36 | return px.scatter( 37 | dff, 38 | x="gdpPercap", 39 | y="lifeExp", 40 | size="pop", 41 | color="continent", 42 | log_x=True, 43 | size_max=60, 44 | ) 45 | -------------------------------------------------------------------------------- /multi_page_store/pages/grid.py: -------------------------------------------------------------------------------- 1 | from dash import html, Input, Output, callback, register_page 2 | import dash_ag_grid as dag 3 | import pandas as pd 4 | 5 | register_page(__name__) 6 | 7 | columnDefs = [ 8 | {"field": "country"}, 9 | {"field": "continent"}, 10 | {"field": "year"}, 11 | { 12 | "headerName": "Life Expectancy", 13 | "field": "lifeExp", 14 | "type": "rightAligned", 15 | "valueFormatter": {"function": "d3.format('.1f')(params.value)"}, 16 | }, 17 | { 18 | "headerName": "Population", 19 | "field": "pop", 20 | "type": "rightAligned", 21 | "valueFormatter": {"function": "d3.format(',.0f')(params.value)"}, 22 | }, 23 | { 24 | "headerName": "GDP per Capita", 25 | "field": "gdpPercap", 26 | "type": "rightAligned", 27 | "valueFormatter": {"function": "d3.format('$,.1f')(params.value)"}, 28 | }, 29 | ] 30 | 31 | layout = html.Div(id="grid-output") 32 | 33 | 34 | @callback( 35 | Output("grid-output", "children"), 36 | Input("store", "data"), 37 | ) 38 | def update(store): 39 | if store == {}: 40 | return "Select year on the graph page" 41 | dff = pd.DataFrame(store) 42 | 43 | return dag.AgGrid( 44 | columnDefs=columnDefs, 45 | rowData=dff.to_dict("records"), 46 | columnSize="sizeToFit", 47 | ) -------------------------------------------------------------------------------- /multi_page_sync_components/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example demonstrates an easy way of syncing components between pages of a multi-page app. 3 | It uses the same dropdown component on each page and sets `persistence=True` 4 | 5 | If persistence doesn't work (such as when you update the component in a callback), 6 | try using muti_page_sync_components2 which uses a dcc.Store to track the values 7 | """ 8 | 9 | 10 | from dash import Dash, html, dcc, page_registry, page_container 11 | 12 | 13 | app = Dash(use_pages=True) 14 | 15 | app.layout = html.Div( 16 | [ 17 | html.H1("Multi Page App Demo: Sync components between pages"), 18 | html.Div( 19 | [ 20 | html.Div( 21 | dcc.Link(f"{page['name']}", href=page["path"]), 22 | ) 23 | for page in page_registry.values() 24 | ] 25 | ), 26 | html.Hr(), 27 | page_container, 28 | ] 29 | ) 30 | 31 | 32 | if __name__ == "__main__": 33 | app.run(debug=True) 34 | -------------------------------------------------------------------------------- /multi_page_sync_components/pages/page1.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, html, Output, Input, callback, register_page 2 | 3 | register_page(__name__, path="/") 4 | 5 | layout = html.Div( 6 | [ 7 | html.Label("Page 1 Select Year"), 8 | dcc.Dropdown( 9 | options=tuple(range(2010, 2023)), 10 | id="all-pages-year", 11 | persistence=True, 12 | ), 13 | html.Div(id="page1-out"), 14 | ] 15 | ) 16 | 17 | @callback( 18 | Output("page1-out", "children"), 19 | Input("all-pages-year", "value") 20 | ) 21 | def update(year): 22 | return f"The dropdown is {year}" -------------------------------------------------------------------------------- /multi_page_sync_components/pages/page2.py: -------------------------------------------------------------------------------- 1 | from dash import html, dcc, Output, Input, callback, register_page 2 | 3 | register_page(__name__, path="/page2") 4 | 5 | layout = html.Div( 6 | [ 7 | html.Label("Page 2 Select Year"), 8 | dcc.Dropdown( 9 | options=tuple(range(2010, 2023)), 10 | id="all-pages-year", 11 | persistence=True, 12 | ), 13 | html.Div(id="page2-out"), 14 | ] 15 | ) 16 | 17 | @callback( 18 | Output("page2-out", "children"), 19 | Input("all-pages-year", "value") 20 | ) 21 | def update(year): 22 | return f"The dropdown is {year}" -------------------------------------------------------------------------------- /multi_page_sync_components/pages/page3.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, html, Output, Input, callback, register_page 2 | 3 | register_page(__name__, path="/page3") 4 | 5 | layout = html.Div( 6 | [ 7 | html.Label("Page 3 Select Year"), 8 | dcc.Dropdown( 9 | options=tuple(range(2010, 2023)), 10 | id="all-pages-year", 11 | persistence=True, 12 | ), 13 | html.Div(id="page3-out"), 14 | ] 15 | ) 16 | 17 | @callback( 18 | Output("page3-out", "children"), 19 | Input("all-pages-year", "value") 20 | ) 21 | def update(year): 22 | return f"The dropdown is {year}" -------------------------------------------------------------------------------- /multi_page_sync_components2/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | This example demonstrates syncing components between pages of a multi-page app. 3 | 4 | For syncing component, please start with simpler version: multi_page_sync_components. 5 | If that doesn't work (such as when the component is updated in the callback or the 6 | persistence prop cannot be set), then try this version. (v2) 7 | 8 | Note that dcc.Store is in the app.py file so that it's accessible in all pages. 9 | 10 | required dash>=2.9.2 to allow duplicate callback outputs 11 | 12 | """ 13 | 14 | 15 | from dash import Dash, html, dcc, page_registry, page_container 16 | 17 | app = Dash( 18 | __name__, 19 | use_pages=True, 20 | suppress_callback_exceptions=True, 21 | ) 22 | 23 | app.layout = html.Div( 24 | [ 25 | dcc.Store(id="store", data=2022), 26 | html.H1("Multi Page App Demo: Sync components between pages"), 27 | html.Div( 28 | [ 29 | html.Div( 30 | dcc.Link(f"{page['name']}", href=page["path"]), 31 | ) 32 | for page in page_registry.values() 33 | ] 34 | ), 35 | html.Hr(), 36 | page_container, 37 | ] 38 | ) 39 | 40 | 41 | if __name__ == "__main__": 42 | app.run(debug=True) 43 | -------------------------------------------------------------------------------- /multi_page_sync_components2/pages/page1.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, html, register_page, no_update, Output, Input, State, callback 2 | 3 | register_page(__name__, path="/") 4 | 5 | years = [year for year in (range(2010, 2023))] 6 | 7 | layout = html.Div( 8 | [ 9 | html.Label("Page 1 Select Year"), 10 | dcc.Dropdown(years, id="page1-year"), 11 | html.Div(id="page1-out"), 12 | ] 13 | ) 14 | 15 | 16 | @callback( 17 | Output("page1-year", "value"), 18 | Output("store", "data", allow_duplicate=True), 19 | Input("page1-year", "value"), 20 | State("store", "data"), 21 | prevent_initial_call=True 22 | ) 23 | def sync_dropdowns(dd_year, store_year): 24 | if dd_year is None: 25 | return store_year, no_update 26 | return dd_year, dd_year 27 | 28 | 29 | @callback(Output("page1-out", "children"), Input("page1-year", "value")) 30 | def update(year): 31 | return f"The dropdown is {year}" 32 | -------------------------------------------------------------------------------- /multi_page_sync_components2/pages/page2.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, html, register_page, no_update, Output, Input, State, callback 2 | 3 | 4 | register_page(__name__) 5 | 6 | years = [year for year in (range(2010, 2023))] 7 | 8 | layout = html.Div( 9 | [ 10 | html.Label("Page 2 Select Year"), 11 | dcc.Dropdown(years, id="page2-year"), 12 | ] 13 | ) 14 | 15 | 16 | @callback( 17 | Output("page2-year", "value"), 18 | Output("store", "data", allow_duplicate=True), 19 | Input("page2-year", "value"), 20 | State("store", "data"), 21 | prevent_initial_call=True 22 | ) 23 | def sync_dropdowns(dd_year, store_year): 24 | if dd_year is None: 25 | return store_year, no_update 26 | return dd_year, dd_year 27 | -------------------------------------------------------------------------------- /multi_page_sync_components2/pages/page3.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, html, register_page, no_update, Output, Input, State, callback 2 | 3 | 4 | register_page(__name__) 5 | 6 | years = [year for year in (range(2010, 2023))] 7 | 8 | layout = html.Div( 9 | [ 10 | html.Label("Page 3 Select Year"), 11 | dcc.Dropdown(years, id="page3-year"), 12 | ] 13 | ) 14 | 15 | 16 | @callback( 17 | Output("page3-year", "value"), 18 | Output("store", "data", allow_duplicate=True), 19 | Input("page3-year", "value"), 20 | State("store", "data"), 21 | prevent_initial_call=True 22 | ) 23 | def sync_dropdowns(dd_year, store_year): 24 | if dd_year is None: 25 | return store_year, no_update 26 | return dd_year, dd_year 27 | -------------------------------------------------------------------------------- /multi_page_table_links/app.py: -------------------------------------------------------------------------------- 1 | import dash 2 | import dash_bootstrap_components as dbc 3 | 4 | 5 | app = dash.Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP]) 6 | 7 | navbar = dbc.NavbarSimple( 8 | [ 9 | dbc.Button("Portfolio", href="/", color="secondary", className="me-1"), 10 | dbc.Button("Stock Analysis", href="/stocks/AAPL", color="secondary"), 11 | ], 12 | brand="Multi Page App Demo", 13 | color="primary", 14 | dark=True, 15 | className="mb-2", 16 | ) 17 | 18 | app.layout = dbc.Container( 19 | [navbar, dash.page_container], 20 | fluid=True, 21 | ) 22 | 23 | if __name__ == "__main__": 24 | app.run(debug=True) 25 | -------------------------------------------------------------------------------- /multi_page_table_links/assets/dashAgGridComponentFunctions.js: -------------------------------------------------------------------------------- 1 | 2 | var dagcomponentfuncs = window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {}; 3 | 4 | dagcomponentfuncs.StockLink = function (props) { 5 | return React.createElement(window.dash_core_components.Link, { 6 | children: props.value, 7 | href: '/stocks/' + props.data.ticker, 8 | }); 9 | } -------------------------------------------------------------------------------- /multi_page_table_links/pages/not_found_404.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of creating a custom 404 page to display when the URL isn't found 3 | """ 4 | 5 | 6 | from dash import html 7 | import dash 8 | 9 | dash.register_page(__name__, path="/404") 10 | 11 | 12 | layout = html.H1("Custom 404") 13 | -------------------------------------------------------------------------------- /multi_page_table_links/pages/portfolio.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of using dcc.Link in AG Grid to navigate to another page. 3 | 4 | """ 5 | 6 | import dash_ag_grid as dag 7 | from dash import html, dcc, register_page 8 | import pandas as pd 9 | 10 | register_page(__name__, path="/") 11 | 12 | data = { 13 | "ticker": ["AAPL", "MSFT", "AMZN", "GOOGL"], 14 | "company": ["Apple", "Microsoft", "Amazon", "Alphabet"], 15 | "shares": [75, 40, 100, 50], 16 | } 17 | df = pd.DataFrame(data) 18 | 19 | columnDefs = [ 20 | { 21 | "headerName": "Stock Ticker", 22 | "field": "ticker", 23 | # stockLink function is defined in the dashAgGridComponentFunctions.js in assets folder 24 | "cellRenderer": "StockLink", 25 | }, 26 | {"field": "company"}, 27 | {"field": "shares" }, 28 | ] 29 | 30 | 31 | grid = dag.AgGrid( 32 | columnDefs=columnDefs, 33 | rowData=df.to_dict("records"), 34 | columnSize="sizeToFit", 35 | ) 36 | 37 | 38 | layout = html.Div( 39 | [dcc.Markdown("Adding dcc.Link in cells with cellRenderer"), grid], 40 | style={"margin": 20}, 41 | ) 42 | -------------------------------------------------------------------------------- /multi_page_table_links/pages/stocks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of: 3 | - title and description updated dynamically with a function 4 | - passing variables in the url pathname to the layout function. 5 | Using the path_template parameter in dash.register_page, you can define which segments of the path 6 | are variables by marking them like this: . The layout function then receives 7 | the as a keyword argument. 8 | """ 9 | 10 | import dash 11 | 12 | 13 | def title(ticker=None): 14 | return f"{ticker} Analysis" 15 | 16 | 17 | def description(ticker=None): 18 | return f"News, financials and technical analysis for {ticker}" 19 | 20 | 21 | dash.register_page( 22 | __name__, 23 | path_template="/stocks/", 24 | title=title, 25 | description=description, 26 | path="/stocks/aapl", 27 | ) 28 | 29 | 30 | def layout(ticker=None, **other_unknown_query_strings): 31 | return dash.html.H3(f"Financial and Technical Analysis for: {ticker}") 32 | -------------------------------------------------------------------------------- /multi_page_theme_switch/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a minimal example of changing themes with Bootstrap color modes 3 | See more information at https://hellodash.pythonanywhere.com/adding-themes/color-modes 4 | 5 | """ 6 | 7 | from dash import Dash, html, page_registry, page_container, clientside_callback, Input, Output 8 | import dash_bootstrap_components as dbc 9 | from dash_bootstrap_templates import load_figure_template 10 | 11 | # adds templates to plotly.io 12 | load_figure_template(["flatly", "flatly_dark"]) 13 | 14 | 15 | color_mode_switch = html.Span( 16 | [ 17 | dbc.Label(className="fa fa-moon", html_for="switch"), 18 | dbc.Switch( id="switch", value=True, className="d-inline-block ms-1", persistence=True), 19 | dbc.Label(className="fa fa-sun", html_for="switch"), 20 | ] 21 | ) 22 | 23 | # This stylesheet defines the "dbc" class. Use it to style dash-core-components 24 | # and the dash DataTable with the bootstrap theme. 25 | dbc_css = "https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates/dbc.min.css" 26 | 27 | app = Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.FLATLY, dbc_css]) 28 | 29 | 30 | navbar = dbc.NavbarSimple( 31 | [ 32 | dbc.DropdownMenu( 33 | [ 34 | dbc.DropdownMenuItem(page["name"], href=page["path"]) 35 | for page in page_registry.values() 36 | if page["module"] != "pages.not_found_404" 37 | ], 38 | nav=True, 39 | label="More Pages", 40 | ), 41 | ], 42 | brand="Multi Page App Demo", 43 | color="primary", 44 | dark=True, 45 | className="mb-2", 46 | ) 47 | 48 | app.layout = dbc.Container( 49 | [navbar, color_mode_switch, page_container], fluid=True, className="dbc" 50 | ) 51 | 52 | clientside_callback( 53 | """ 54 | (switchOn) => { 55 | document.documentElement.setAttribute('data-bs-theme', switchOn ? 'light' : 'dark'); 56 | return window.dash_clientside.no_update 57 | } 58 | """, 59 | Output("switch", "id"), 60 | Input("switch", "value"), 61 | ) 62 | 63 | if __name__ == "__main__": 64 | app.run(debug=True) 65 | -------------------------------------------------------------------------------- /multi_page_theme_switch/pages/bar_charts.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, html, Input, Output, callback, register_page 2 | import plotly.express as px 3 | from .default_fig import default_fig 4 | 5 | register_page(__name__) 6 | 7 | df = px.data.tips() 8 | days = df.day.unique() 9 | 10 | layout = html.Div( 11 | [ 12 | dcc.Dropdown( 13 | id="dropdown", 14 | options=[{"label": x, "value": x} for x in days], 15 | value=days[0], 16 | clearable=False, 17 | ), 18 | dcc.Graph(id="bar-chart", figure=default_fig), 19 | ] 20 | ) 21 | 22 | 23 | @callback( 24 | Output("bar-chart", "figure"), 25 | Input("dropdown", "value"), 26 | Input("switch", "value"), 27 | ) 28 | def update_bar_chart(day, toggle): 29 | template = "flatly" if toggle else "flatly_dark" 30 | mask = df["day"] == day 31 | fig = px.bar( 32 | df[mask], 33 | x="sex", 34 | y="total_bill", 35 | color="smoker", 36 | barmode="group", 37 | template=template, 38 | ) 39 | return fig 40 | -------------------------------------------------------------------------------- /multi_page_theme_switch/pages/default_fig.py: -------------------------------------------------------------------------------- 1 | """ 2 | Use this default figure in the layout. It has a transparent background and no axis, which 3 | reduces the flash when navigating to a different pages or switching themes 4 | e.g. 5 | layout = html.Div( 6 | [ 7 | dcc.Graph(id="heatmaps-graph", figure=default_fig) 8 | ] 9 | 10 | ) 11 | """ 12 | 13 | import plotly.express as px 14 | 15 | default_fig = px.scatter() 16 | default_fig.update_layout( 17 | paper_bgcolor="rgba(0, 0, 0, 0)", 18 | plot_bgcolor="rgba(0, 0, 0, 0)", 19 | xaxis=dict(visible=False), 20 | yaxis=dict(visible=False), 21 | ) 22 | -------------------------------------------------------------------------------- /multi_page_theme_switch/pages/heatmaps.py: -------------------------------------------------------------------------------- 1 | 2 | import dash_bootstrap_components as dbc 3 | from .default_fig import default_fig 4 | from dash import dcc, html, Input, Output, callback, register_page 5 | import plotly.express as px 6 | 7 | 8 | register_page(__name__, path="/") 9 | df = px.data.medals_wide(indexed=True) 10 | 11 | 12 | layout = html.Div( 13 | [ 14 | html.P("Medals included:"), 15 | dbc.Checklist( 16 | id="heatmaps-medals", 17 | options=[{"label": x, "value": x} for x in df.columns], 18 | value=df.columns.tolist(), 19 | ), 20 | dcc.Graph(id="heatmaps-graph", figure=default_fig), 21 | ] 22 | ) 23 | 24 | 25 | @callback( 26 | Output("heatmaps-graph", "figure"), 27 | Input("heatmaps-medals", "value"), 28 | Input("switch", "value"), 29 | ) 30 | def filter_heatmap(cols, toggle): 31 | fig = px.imshow(df[cols], template="flatly" if toggle else "flatly_dark") 32 | return fig 33 | -------------------------------------------------------------------------------- /multi_page_theme_switch/pages/histograms.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, html, Input, Output, callback, register_page 2 | import plotly.express as px 3 | import numpy as np 4 | 5 | from .default_fig import default_fig 6 | 7 | register_page(__name__) 8 | 9 | 10 | np.random.seed(2020) 11 | 12 | layout = html.Div( 13 | [ 14 | dcc.Graph(id="histograms-graph", figure=default_fig), 15 | html.P("Mean:"), 16 | dcc.Slider( 17 | id="histograms-mean", min=-3, max=3, value=0, marks={-3: "-3", 3: "3"} 18 | ), 19 | html.P("Standard Deviation:"), 20 | dcc.Slider(id="histograms-std", min=1, max=3, value=1, marks={1: "1", 3: "3"}), 21 | ] 22 | ) 23 | 24 | 25 | @callback( 26 | Output("histograms-graph", "figure"), 27 | Input("histograms-mean", "value"), 28 | Input("histograms-std", "value"), 29 | Input("switch", "value"), 30 | ) 31 | def display_color(mean, std, switch_on): 32 | data = np.random.normal(mean, std, size=500) 33 | template = "flatly" if switch_on else "flatly_dark" 34 | fig = px.histogram(data, nbins=30, range_x=[-10, 10], template=template) 35 | return fig 36 | -------------------------------------------------------------------------------- /multi_page_theme_switch/pages/not_found_404.py: -------------------------------------------------------------------------------- 1 | from dash import html 2 | import dash 3 | 4 | dash.register_page(__name__, path="/404") 5 | 6 | 7 | layout = html.H1("Custom 404") 8 | -------------------------------------------------------------------------------- /multi_page_update_url_from_figure/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Navigation in a callback. 3 | 4 | With Dash Pages, the routing callback is under the hood, which reduces the amount of boilerplate code you need to write. 5 | The best way to navigate is for the user to click on a link made with components such as the `dcc.Link` or `dbc.Button`. 6 | It navigates to the new page without refreshing the page making the navigation very fast -- and the best part: No callback required!. 7 | 8 | This works well when you have predefined links. However, at times, you may want to navigate based on an input field, 9 | dropdown, or clicking on a figure etc. Rather than updating the `href` prop of a `dcc.Location` in a callback, 10 | it's a best practice to update the `dcc.Link` in a callback. This makes it so that the user can click on the new 11 | link to navigate to a different page. 12 | 13 | In this example, when the user clicks on an airport in the figure, we display a card with links to get more 14 | information on that airport. 15 | 16 | 17 | """ 18 | 19 | import dash 20 | import dash_bootstrap_components as dbc 21 | 22 | 23 | app = dash.Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP]) 24 | 25 | navbar = dbc.NavbarSimple( 26 | [ 27 | dbc.Button("Airports", href="/", color="secondary", className="me-1"), 28 | ], 29 | brand="Multi Page App Demo", 30 | color="primary", 31 | dark=True, 32 | className="mb-2", 33 | ) 34 | 35 | app.layout = dbc.Container( 36 | [navbar, dash.page_container], 37 | fluid=True, 38 | ) 39 | 40 | if __name__ == "__main__": 41 | app.run(debug=True) 42 | -------------------------------------------------------------------------------- /multi_page_update_url_from_figure/pages/airports.py: -------------------------------------------------------------------------------- 1 | from dash import dcc, html, Input, Output, callback, register_page 2 | import plotly.express as px 3 | import pandas as pd 4 | import dash_bootstrap_components as dbc 5 | 6 | register_page(__name__, path="/") 7 | 8 | df_airports = pd.read_csv( 9 | "https://raw.githubusercontent.com/plotly/datasets/master/2011_february_us_airport_traffic.csv" 10 | ) 11 | fig = px.scatter_mapbox( 12 | df_airports, 13 | lat="lat", 14 | lon="long", 15 | hover_data=["iata", "airport", "city", "state", "cnt"], 16 | size="cnt", 17 | color="cnt", 18 | zoom=3, 19 | ) 20 | fig.update_layout(mapbox_style="open-street-map") 21 | fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) 22 | 23 | layout = html.Div( 24 | [html.H4("Airports"), dcc.Graph(id="graph", figure=fig), html.Div(id="link")] 25 | ) 26 | 27 | 28 | @callback( 29 | Output("link", "children"), 30 | Input("graph", "clickData"), 31 | ) 32 | def generate_chart(clickdata): 33 | if not clickdata: 34 | return "" 35 | airport_code = clickdata["points"][0]["customdata"][0] 36 | airport_name = clickdata["points"][0]["customdata"][1] 37 | 38 | return dbc.Card( 39 | dbc.CardBody( 40 | [ 41 | html.H4("Flight Status", className="card-title"), 42 | html.H6(f"For {airport_name}", className="card-subtitle"), 43 | # Note: dbc.Cardlink works like a dcc.Link -- navigation without refreshing the page. 44 | dbc.CardLink( 45 | "Arrivals", href=f"/flight-status/{airport_code}/arrivals" 46 | ), 47 | dbc.CardLink( 48 | "Departures", href=f"/flight-status/{airport_code}/departures" 49 | ), 50 | ] 51 | ), 52 | style={"width": "18rem"}, 53 | ) 54 | -------------------------------------------------------------------------------- /multi_page_update_url_from_figure/pages/flight_status.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of: 3 | - title and description updated dynamically with a function 4 | - passing variables in the url pathname to the layout function. 5 | Using the path_template parameter in dash.register_page, you can define which segments of the path 6 | are variables by marking them like this: . The layout function then receives 7 | the as a keyword argument. 8 | """ 9 | 10 | 11 | from dash import html, register_page 12 | 13 | 14 | def title(airport=None, type=None): 15 | return f"Flight Status for {airport}" 16 | 17 | 18 | def description(airport=None, type=None): 19 | return f"Arrival and Departure status for {airport}" 20 | 21 | 22 | register_page( 23 | __name__, 24 | path_template="/flight-status//", 25 | title=title, 26 | description=description, 27 | path="/flight-status/ord/arrivals", 28 | ) 29 | 30 | 31 | def layout(airport=None, type=None, **other_unknown_query_strings): 32 | return html.H3(f"Flight {type} data for: {airport}") 33 | -------------------------------------------------------------------------------- /multi_page_update_url_from_figure/pages/not_found_404.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of creating a custom 404 page to display when the URL isn't found 3 | """ 4 | 5 | 6 | from dash import html, register_page 7 | import dash 8 | 9 | register_page(__name__, path="/404") 10 | 11 | 12 | layout = html.H1("Custom 404") 13 | -------------------------------------------------------------------------------- /multi_page_update_url_from_figure_V292/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Navigation in a callback. 3 | 4 | With Dash Pages, the routing callback is under the hood, which reduces the amount of boilerplate code you need to write. 5 | The best way to navigate is to use components such as the `dcc.Link` or `dbc.Button`. When the 6 | user clicks on these links, it will navigate to the new page without refreshing the page making the navigation 7 | very fast -- and the best part: No callback required!. 8 | 9 | This works well when you have predefined links. However, at times, you may want to navigate based on an input field, 10 | dropdown, or clicking on a figure etc. In these cases, you can update `href` prop of a `dcc.Location` in a callback. 11 | Prior to dash 2.9.2, this was not recommended because it will refresh the page. 12 | 13 | ** NEW in dash>=2.9.2 ** 14 | 15 | It is now possible to navigate to the new page without refreshing the page. Set `refresh="callback-nav"` 16 | 17 | dcc.Location(id="url", refresh="callback-nav") 18 | 19 | 20 | This example updates the URL in a callback when the user clicks on a figure. 21 | """ 22 | 23 | from dash import Dash, dcc, page_container 24 | import dash_bootstrap_components as dbc 25 | 26 | app = Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP]) 27 | 28 | navbar = dbc.NavbarSimple( 29 | dbc.Button("Home", href="/", color="secondary", className="me-1"), 30 | brand="Multi Page App Demo", 31 | color="primary", 32 | dark=True, 33 | className="mb-2", 34 | ) 35 | 36 | app.layout = dbc.Container( 37 | [ 38 | dcc.Location(id="url", refresh="callback-nav"), 39 | navbar, page_container, 40 | ], fluid=True 41 | ) 42 | 43 | if __name__ == "__main__": 44 | app.run(debug=True) 45 | -------------------------------------------------------------------------------- /multi_page_update_url_from_figure_V292/pages/flight_status.py: -------------------------------------------------------------------------------- 1 | 2 | from dash import html, register_page 3 | 4 | register_page( __name__, path_template="/flight-status/") 5 | 6 | def layout(airport=None, **other_unknown_query_strings): 7 | return html.H3(f"Arrivals and Departures for: {airport}") 8 | 9 | -------------------------------------------------------------------------------- /multi_page_update_url_from_figure_V292/pages/home.py: -------------------------------------------------------------------------------- 1 | import dash 2 | from dash import dcc, html, Input, Output, callback, register_page 3 | import plotly.express as px 4 | import pandas as pd 5 | 6 | register_page(__name__, path="/") 7 | 8 | df_airports = pd.read_csv( 9 | "https://raw.githubusercontent.com/plotly/datasets/master/2011_february_us_airport_traffic.csv" 10 | ) 11 | fig = px.scatter_mapbox( 12 | df_airports, 13 | lat="lat", 14 | lon="long", 15 | hover_data=["iata", "airport", "city", "state", "cnt"], 16 | size="cnt", 17 | color="cnt", 18 | zoom=3, 19 | title="Airport Traffic Data" 20 | ) 21 | fig.update_layout(mapbox_style="open-street-map") 22 | 23 | layout = html.Div(dcc.Graph(id="graph", figure=fig)) 24 | 25 | 26 | @callback( 27 | Output("url", "href"), 28 | Input("graph", "clickData"), 29 | prevent_initial_callback=True 30 | ) 31 | def generate_chart(clickdata): 32 | if not clickdata: 33 | return dash.no_update 34 | airport_code = clickdata["points"][0]["customdata"][0] 35 | return f"/flight-status/{airport_code}" 36 | -------------------------------------------------------------------------------- /multi_page_update_url_in_callback/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of updating url in a callback. Note that the dcc.Location must be in app.py 3 | """ 4 | 5 | 6 | import dash 7 | from dash import dcc 8 | import dash_bootstrap_components as dbc 9 | 10 | 11 | app = dash.Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP]) 12 | 13 | navbar = dbc.NavbarSimple( 14 | [ 15 | dbc.Button("Home", href="/", color="secondary", className="me-1"), 16 | dbc.Button("Stock Analysis", href="/stocks/AAPL", color="secondary"), 17 | ], 18 | brand="Multi Page App Demo", 19 | color="primary", 20 | dark=True, 21 | className="mb-2", 22 | ) 23 | 24 | app.layout = dbc.Container( 25 | [ 26 | dcc.Location(id="url", refresh=True), 27 | navbar, dash.page_container 28 | ], 29 | fluid=True, 30 | ) 31 | 32 | if __name__ == "__main__": 33 | app.run(debug=True) 34 | -------------------------------------------------------------------------------- /multi_page_update_url_in_callback/pages/home.py: -------------------------------------------------------------------------------- 1 | """ 2 | Note that the callback will trigger even if prevent_initial_call=True. This is because dcc.Location must 3 | be in app.py. Since the dcc.Location component is not in the layout when navigating to this page, it triggers the callback. 4 | The workaround is to check if the input value is None. 5 | 6 | """ 7 | 8 | 9 | import dash 10 | from dash import dcc, html, Input, Output, State, callback, register_page 11 | import dash_bootstrap_components as dbc 12 | 13 | register_page(__name__, path="/") 14 | 15 | intro = """ 16 | ## Navigation in a callback. 17 | 18 | With Dash Pages, the routing callback is under the hood, which reduces the amount of boilderplate code you need to write. 19 | The best way to navigate is to use components such as the `dcc.Link` or `dbc.Button`. When the 20 | user clicks on these links, it will navigate to the new page without refreshing the page making the navigation 21 | very fast -- and the best part: No callback required!. 22 | 23 | This works well when you have predefined links. However, at times, you may want to navigate based on an input field, 24 | dropdown, or clicking on a figure etc. In these cases, you can update the link dynamically in a callback. 25 | 26 | While it's possible to update the `href` prop of a `dcc.Location` in a callback, this is not recommended because it 27 | refreshes the page. 28 | 29 | 30 | ### Example 1 dash<2.9.0 31 | Using `dcc.Location` to update the url. **This is not recommended ** since it refreshes the page. Note the 32 | `dcc.Location` must be in `app.py` 33 | 34 | If you are using dash>=2.9.0 use `dcc.Location(id="url", refresh="callback-nav")` 35 | This will navigate without refreshing the page. 36 | """ 37 | 38 | example2 = """ 39 | ### Example 2 40 | "Using `dcc.Link` and `html.Button` to navigate without refreshing the page" 41 | """ 42 | 43 | example3 = """ 44 | ### Example 3 45 | Using `dbc.Button` to navigate without refreshing the page 46 | """ 47 | 48 | 49 | example4 = """ 50 | ### Example 4 51 | Using `html.A` and target="_blank" to open in a new browser window 52 | """ 53 | 54 | 55 | layout = html.Div( 56 | [ 57 | dcc.Markdown(intro), 58 | dbc.Input( 59 | id="ticker-search1", 60 | placeholder="Search ticker or company name...", 61 | className="mb-5", 62 | debounce=True, 63 | ), 64 | dcc.Markdown(example2), 65 | dcc.Input( 66 | id="ticker-search2", 67 | placeholder="Search ticker or company name...", 68 | className="mb-5", 69 | ), 70 | dcc.Link( 71 | html.Button("submit", n_clicks=0, id="ticker-search2-btn"), 72 | id="ticker-search2-link", 73 | href="/", 74 | ), 75 | dcc.Markdown(example3), 76 | dbc.InputGroup( 77 | [ 78 | dbc.Input( 79 | id="ticker-search3", 80 | placeholder="Search ticker or company name...", 81 | style={"maxWidth": 400}, 82 | ), 83 | dbc.Button("submit", n_clicks=0, id="ticker-search3-btn", href="/"), 84 | ] 85 | ), 86 | dcc.Markdown(example4, className="mt-5"), 87 | dcc.Input( 88 | id="ticker-search4", 89 | placeholder="Search ticker or company name...", 90 | className="mb-5" 91 | ), 92 | html.A( 93 | html.Button("submit", n_clicks=0, id="ticker-search2-btn"), 94 | id="ticker-search4-link", 95 | href="/", 96 | target="_blank" 97 | ), 98 | ] 99 | ) 100 | 101 | 102 | @callback( 103 | Output("url", "href"), Input("ticker-search1", "value"), prevent_initial_call=True 104 | ) 105 | def search(ticker): 106 | if ticker is None or ticker == "": 107 | return dash.no_update 108 | return f"/stocks/{ticker}" 109 | 110 | 111 | @callback( 112 | Output("ticker-search2-link", "href"), 113 | Input("ticker-search2", "value"), 114 | prevent_initial_call=True, 115 | ) 116 | def search(ticker): 117 | if ticker is None or ticker == "": 118 | return dash.no_update 119 | return f"/stocks/{ticker}" 120 | 121 | 122 | @callback( 123 | Output("ticker-search3-btn", "href"), 124 | Input("ticker-search3", "value"), 125 | prevent_initial_call=True, 126 | ) 127 | def search(ticker): 128 | if ticker is None or ticker == "": 129 | return dash.no_update 130 | return f"/stocks/{ticker}" 131 | 132 | 133 | @callback( 134 | Output("ticker-search4-link", "href"), 135 | Input("ticker-search4", "value"), 136 | prevent_initial_call=True, 137 | ) 138 | def search(ticker): 139 | if ticker is None or ticker == "": 140 | return dash.no_update 141 | return f"/stocks/{ticker}" 142 | -------------------------------------------------------------------------------- /multi_page_update_url_in_callback/pages/not_found_404.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of creating a custom 404 page to display when the URL isn't found 3 | """ 4 | 5 | 6 | from dash import html, register_page 7 | import dash 8 | 9 | register_page(__name__, path="/404") 10 | 11 | 12 | layout = html.H1("Custom 404") 13 | -------------------------------------------------------------------------------- /multi_page_update_url_in_callback/pages/stocks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of: 3 | - title and description updated dynamically with a function 4 | - passing variables in the url pathname to the layout function. 5 | Using the path_template parameter in dash.register_page, you can define which segments of the path 6 | are variables by marking them like this: . The layout function then receives 7 | the as a keyword argument. 8 | - using urllib.parse.unquote to get decoded strings from the url 9 | """ 10 | 11 | from urllib.parse import unquote 12 | from dash import html, register_page 13 | 14 | 15 | def title(ticker=None): 16 | return f"{unquote(ticker)} Analysis" 17 | 18 | 19 | def description(ticker=None): 20 | return f"News, financials and technical analysis for {unquote(ticker)}" 21 | 22 | 23 | register_page( 24 | __name__, 25 | path_template="/stocks/", 26 | title=title, 27 | description=description, 28 | path="/stocks/aapl", 29 | ) 30 | 31 | 32 | def layout(ticker=None, **other_unknown_query_strings): 33 | if ticker: 34 | ticker = unquote(ticker) 35 | return html.H3(f"Financial and Technical Analysis for: {ticker}") 36 | -------------------------------------------------------------------------------- /multi_page_update_url_in_callback_V292/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of updating url in a callback in dash>=2.9.0. 3 | 4 | """ 5 | 6 | 7 | import dash 8 | from dash import dcc 9 | import dash_bootstrap_components as dbc 10 | 11 | 12 | app = dash.Dash(__name__, use_pages=True, external_stylesheets=[dbc.themes.BOOTSTRAP]) 13 | 14 | navbar = dbc.NavbarSimple( 15 | [ 16 | dbc.Button("Home", href="/", color="secondary", className="me-1"), 17 | dbc.Button("Stock Analysis", href="/stocks/AAPL", color="secondary"), 18 | ], 19 | brand="Multi Page App Demo", 20 | color="primary", 21 | dark=True, 22 | className="mb-2", 23 | ) 24 | 25 | app.layout = dbc.Container( 26 | [ 27 | dcc.Location(id="url", refresh="callback-nav"), 28 | navbar, dash.page_container 29 | ], 30 | ) 31 | 32 | if __name__ == "__main__": 33 | app.run(debug=True) 34 | -------------------------------------------------------------------------------- /multi_page_update_url_in_callback_V292/pages/home.py: -------------------------------------------------------------------------------- 1 | """ 2 | Note that the callback will trigger even if prevent_initial_call=True. This is because dcc.Location must 3 | be in app.py. Since the dcc.Location component is not in the layout when navigating to this page, it triggers the callback. 4 | The workaround is to check if the input value is None. 5 | 6 | """ 7 | 8 | 9 | import dash 10 | from dash import dcc, html, Input, Output, State, callback, register_page 11 | import dash_bootstrap_components as dbc 12 | 13 | register_page(__name__, path="/") 14 | 15 | intro = """ 16 | ## Navigation in a callback without refreshing the page in dash>=2.9.2 17 | 18 | With Dash Pages, the routing callback is under the hood, which reduces the amount of boilderplate code you need to write. 19 | The best way to navigate is for the user to click on a link made with components such as the `dcc.Link` or `dbc.Button`. 20 | It navigates to the new page without refreshing the page making the navigation very fast -- and the best part: No callback required!. 21 | 22 | This works well when you have static links. However, at times, you may want to navigate based on an input field, 23 | dropdown, or clicking on a figure etc. 24 | 25 | It's possible to navigate to a new page by updating the `href` prop of dcc.Location in a callback. Prior to dash 26 | 2.9.2 this was not recommended because it was necessary to refresh the page, which is slow and you see an annoying flash. 27 | 28 | Eliminate the flash simply by including `refresh="callback-nav"` prop in the `dcc.Location` component. 29 | 30 | 31 | 32 | ### Example 1 33 | Updating `href` in `dcc.Location(refresh="callback-nav")` 34 | 35 | """ 36 | 37 | example2 = """ 38 | ### Example 2 39 | Updating `href` in `dcc.Link` 40 | 41 | """ 42 | 43 | example3 = """ 44 | ### Example 3 45 | Updating `href` in `dbc.Button` 46 | 47 | """ 48 | 49 | 50 | layout = html.Div( 51 | [ 52 | dcc.Markdown(intro), 53 | dbc.Input( 54 | id="ticker-search1", 55 | placeholder="Search ticker or company name...", 56 | className="mb-5", 57 | debounce=True, 58 | ), 59 | dcc.Markdown(example2), 60 | dcc.Input( 61 | id="ticker-search2", 62 | placeholder="Search ticker or company name...", 63 | className="mb-5", 64 | ), 65 | dcc.Link( 66 | html.Button("submit", n_clicks=0, id="ticker-search2-btn"), 67 | id="ticker-search2-link", 68 | href="/", 69 | ), 70 | dcc.Markdown(example3), 71 | dbc.InputGroup( 72 | [ 73 | dbc.Input( 74 | id="ticker-search3", 75 | placeholder="Search ticker or company name...", 76 | style={"maxWidth": 400}, 77 | ), 78 | dbc.Button("submit", n_clicks=0, id="ticker-search3-btn", href="/"), 79 | ] 80 | ), 81 | ], className="pb-4" 82 | ) 83 | 84 | 85 | @callback( 86 | Output("url", "href"), Input("ticker-search1", "value"), prevent_initial_call=True 87 | ) 88 | def update_dcc_location(ticker): 89 | if ticker is None or ticker == "": 90 | return dash.no_update 91 | return f"/stocks/{ticker}" 92 | 93 | 94 | @callback( 95 | Output("ticker-search2-link", "href"), 96 | Input("ticker-search2", "value"), 97 | prevent_initial_call=True, 98 | ) 99 | def search(ticker): 100 | if ticker is None or ticker == "": 101 | return dash.no_update 102 | return f"/stocks/{ticker}" 103 | 104 | 105 | @callback( 106 | Output("ticker-search3-btn", "href"), 107 | Input("ticker-search3", "value"), 108 | prevent_initial_call=True, 109 | ) 110 | def search(ticker): 111 | if ticker is None or ticker == "": 112 | return dash.no_update 113 | return f"/stocks/{ticker}" 114 | -------------------------------------------------------------------------------- /multi_page_update_url_in_callback_V292/pages/not_found_404.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of creating a custom 404 page to display when the URL isn't found 3 | """ 4 | 5 | 6 | from dash import html, register_page 7 | import dash 8 | 9 | register_page(__name__, path="/404") 10 | 11 | 12 | layout = html.H1("Custom 404") 13 | -------------------------------------------------------------------------------- /multi_page_update_url_in_callback_V292/pages/stocks.py: -------------------------------------------------------------------------------- 1 | """ 2 | Example of: 3 | - title and description updated dynamically with a function 4 | - passing variables in the url pathname to the layout function. 5 | Using the path_template parameter in dash.register_page, you can define which segments of the path 6 | are variables by marking them like this: . The layout function then receives 7 | the as a keyword argument. 8 | - using urllib.parse.unquote to get decoded strings from the url 9 | """ 10 | 11 | from urllib.parse import unquote 12 | from dash import html, register_page 13 | import flask 14 | 15 | 16 | def title(ticker=None): 17 | return f"{unquote(ticker)} Analysis" 18 | 19 | 20 | def description(ticker=None): 21 | args = flask.request.args 22 | name = flask.request.args.get('name') 23 | 24 | return f"{name}-{args} : News, financials and technical analysis for {unquote(ticker)}" 25 | 26 | 27 | 28 | register_page( 29 | __name__, 30 | path_template="/stocks/", 31 | title=title, 32 | description=description, 33 | path="/stocks/aapl", 34 | ) 35 | 36 | 37 | def layout(ticker=None, **other_unknown_query_strings): 38 | 39 | query_string = flask.request.query_string 40 | print(query_string) 41 | if ticker: 42 | ticker = unquote(ticker) 43 | return html.H3(f"Financial and Technical Analysis for: {ticker}") 44 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | dash>=3.00 2 | dash-ag-grid>=31.3.1 3 | dash-bootstrap-components>=2.0.0 4 | dash-mantine-components>=1.1.0 5 | dash-iconify>=0.1.2 6 | numpy 7 | pandas 8 | 9 | # for background callbacks 10 | dash[diskcache] 11 | 12 | # for basic dash auth example 13 | dash-auth 14 | 15 | # for caching demo only 16 | Flask-Caching>= 2.0.1 17 | diskcache>=5.2.1 18 | numpy 19 | pandas 20 | redis 21 | dill>=0.3.5.1 22 | 23 | # for flash-login demo only 24 | ldap3 25 | flask-login 26 | python-dotenv 27 | 28 | # for theme switch demo only 29 | dash-bootstrap-templates>=2.1.0 30 | 31 | # for debugging demo using dash_labs.print_registry() only 32 | dash-labs>=1.1.0 33 | 34 | 35 | --------------------------------------------------------------------------------