diff --git a/doc/_static/css/custom.css b/doc/_static/css/custom.css index fcf39de82b..34aa30b6c1 100644 --- a/doc/_static/css/custom.css +++ b/doc/_static/css/custom.css @@ -77,3 +77,7 @@ button.copybtn:hover { .o-tooltip--left:after { background: none; } + +video { + max-width: 100%; +} diff --git a/doc/_static/images/align-cards-solution.png b/doc/_static/images/align-cards-solution.png new file mode 100644 index 0000000000..9ce048fc96 Binary files /dev/null and b/doc/_static/images/align-cards-solution.png differ diff --git a/doc/_static/images/awesome-panel.png b/doc/_static/images/awesome-panel.png new file mode 100644 index 0000000000..fb041febc6 Binary files /dev/null and b/doc/_static/images/awesome-panel.png differ diff --git a/doc/_static/images/develop_editor_breakpoint.png b/doc/_static/images/develop_editor_breakpoint.png new file mode 100644 index 0000000000..7aff5abc39 Binary files /dev/null and b/doc/_static/images/develop_editor_breakpoint.png differ diff --git a/doc/_static/images/develop_editor_click_me.png b/doc/_static/images/develop_editor_click_me.png new file mode 100644 index 0000000000..a14a832a30 Binary files /dev/null and b/doc/_static/images/develop_editor_click_me.png differ diff --git a/doc/_static/images/develop_editor_hover.png b/doc/_static/images/develop_editor_hover.png new file mode 100644 index 0000000000..bf7a3ab8c4 Binary files /dev/null and b/doc/_static/images/develop_editor_hover.png differ diff --git a/doc/_static/images/develop_editor_layout0.png b/doc/_static/images/develop_editor_layout0.png new file mode 100644 index 0000000000..2b211de9dd Binary files /dev/null and b/doc/_static/images/develop_editor_layout0.png differ diff --git a/doc/_static/images/develop_editor_layout1.png b/doc/_static/images/develop_editor_layout1.png new file mode 100644 index 0000000000..1e371b5af5 Binary files /dev/null and b/doc/_static/images/develop_editor_layout1.png differ diff --git a/doc/_static/images/develop_editor_panel_serve_after.png b/doc/_static/images/develop_editor_panel_serve_after.png new file mode 100644 index 0000000000..c4d01c9740 Binary files /dev/null and b/doc/_static/images/develop_editor_panel_serve_after.png differ diff --git a/doc/_static/images/develop_editor_panel_serve_before.png b/doc/_static/images/develop_editor_panel_serve_before.png new file mode 100644 index 0000000000..054908cb54 Binary files /dev/null and b/doc/_static/images/develop_editor_panel_serve_before.png differ diff --git a/doc/_static/images/develop_editor_param.png b/doc/_static/images/develop_editor_param.png new file mode 100644 index 0000000000..258daed613 Binary files /dev/null and b/doc/_static/images/develop_editor_param.png differ diff --git a/doc/_static/images/develop_editor_reference_guide.png b/doc/_static/images/develop_editor_reference_guide.png new file mode 100644 index 0000000000..1122ed13fb Binary files /dev/null and b/doc/_static/images/develop_editor_reference_guide.png differ diff --git a/doc/_static/images/develop_editor_serve_app.png b/doc/_static/images/develop_editor_serve_app.png new file mode 100644 index 0000000000..2bf8d3a3e1 Binary files /dev/null and b/doc/_static/images/develop_editor_serve_app.png differ diff --git a/doc/_static/images/develop_notebook_panel_serve_after.png b/doc/_static/images/develop_notebook_panel_serve_after.png new file mode 100644 index 0000000000..695f35dac8 Binary files /dev/null and b/doc/_static/images/develop_notebook_panel_serve_after.png differ diff --git a/doc/_static/images/develop_notebook_panel_serve_before.png b/doc/_static/images/develop_notebook_panel_serve_before.png new file mode 100644 index 0000000000..3b487bb1d4 Binary files /dev/null and b/doc/_static/images/develop_notebook_panel_serve_before.png differ diff --git a/doc/_static/images/develop_notebook_reload_preview.png b/doc/_static/images/develop_notebook_reload_preview.png new file mode 100644 index 0000000000..e18ef27a8d Binary files /dev/null and b/doc/_static/images/develop_notebook_reload_preview.png differ diff --git a/doc/_static/images/develop_notebook_simple_example.png b/doc/_static/images/develop_notebook_simple_example.png new file mode 100644 index 0000000000..63f086b76d Binary files /dev/null and b/doc/_static/images/develop_notebook_simple_example.png differ diff --git a/doc/_static/images/develop_notebook_simple_example_add_hello_again.png b/doc/_static/images/develop_notebook_simple_example_add_hello_again.png new file mode 100644 index 0000000000..80e6a1fafa Binary files /dev/null and b/doc/_static/images/develop_notebook_simple_example_add_hello_again.png differ diff --git a/doc/_static/images/develop_notebook_simple_example_add_hello_again_and_again.png b/doc/_static/images/develop_notebook_simple_example_add_hello_again_and_again.png new file mode 100644 index 0000000000..ba1e9d3601 Binary files /dev/null and b/doc/_static/images/develop_notebook_simple_example_add_hello_again_and_again.png differ diff --git a/doc/_static/images/develop_notebook_simple_example_open_preview.png b/doc/_static/images/develop_notebook_simple_example_open_preview.png new file mode 100644 index 0000000000..edc0a9f312 Binary files /dev/null and b/doc/_static/images/develop_notebook_simple_example_open_preview.png differ diff --git a/doc/_static/images/editor_server_app.png b/doc/_static/images/editor_server_app.png deleted file mode 100644 index 8372771162..0000000000 Binary files a/doc/_static/images/editor_server_app.png and /dev/null differ diff --git a/doc/_static/images/explore_data_perspective_chart.png b/doc/_static/images/explore_data_perspective_chart.png new file mode 100644 index 0000000000..cb87a30049 Binary files /dev/null and b/doc/_static/images/explore_data_perspective_chart.png differ diff --git a/doc/_static/images/explore_data_perspective_table.png b/doc/_static/images/explore_data_perspective_table.png new file mode 100644 index 0000000000..bad20def1f Binary files /dev/null and b/doc/_static/images/explore_data_perspective_table.png differ diff --git a/doc/_static/images/getting_started_app.png b/doc/_static/images/getting_started_app.png new file mode 100644 index 0000000000..df083277d7 Binary files /dev/null and b/doc/_static/images/getting_started_app.png differ diff --git a/doc/_static/images/hugging_face_spaces_delete.png b/doc/_static/images/hugging_face_spaces_delete.png new file mode 100644 index 0000000000..c52711a2b0 Binary files /dev/null and b/doc/_static/images/hugging_face_spaces_delete.png differ diff --git a/doc/_static/images/hugging_face_spaces_duplicate.png b/doc/_static/images/hugging_face_spaces_duplicate.png new file mode 100644 index 0000000000..ecc8a0318f Binary files /dev/null and b/doc/_static/images/hugging_face_spaces_duplicate.png differ diff --git a/doc/_static/images/hugging_face_spaces_duplicate_form.png b/doc/_static/images/hugging_face_spaces_duplicate_form.png new file mode 100644 index 0000000000..9d7d5a9950 Binary files /dev/null and b/doc/_static/images/hugging_face_spaces_duplicate_form.png differ diff --git a/doc/_static/images/hugging_face_spaces_files.png b/doc/_static/images/hugging_face_spaces_files.png new file mode 100644 index 0000000000..432a71bd56 Binary files /dev/null and b/doc/_static/images/hugging_face_spaces_files.png differ diff --git a/doc/_static/images/join_community_discord.png b/doc/_static/images/join_community_discord.png new file mode 100644 index 0000000000..fedcd6ad95 Binary files /dev/null and b/doc/_static/images/join_community_discord.png differ diff --git a/doc/_static/images/join_community_discourse.png b/doc/_static/images/join_community_discourse.png new file mode 100644 index 0000000000..5d7974fd0d Binary files /dev/null and b/doc/_static/images/join_community_discourse.png differ diff --git a/doc/_static/images/join_community_github.png b/doc/_static/images/join_community_github.png new file mode 100644 index 0000000000..59b70db0fd Binary files /dev/null and b/doc/_static/images/join_community_github.png differ diff --git a/doc/_static/images/join_community_linkedin.png b/doc/_static/images/join_community_linkedin.png new file mode 100644 index 0000000000..50ffb2bcf6 Binary files /dev/null and b/doc/_static/images/join_community_linkedin.png differ diff --git a/doc/_static/images/join_community_open_source.png b/doc/_static/images/join_community_open_source.png new file mode 100644 index 0000000000..d99a372926 Binary files /dev/null and b/doc/_static/images/join_community_open_source.png differ diff --git a/doc/_static/images/join_community_twitter.png b/doc/_static/images/join_community_twitter.png new file mode 100644 index 0000000000..be4e40db3d Binary files /dev/null and b/doc/_static/images/join_community_twitter.png differ diff --git a/doc/_static/images/jupyter_panel_preview_in_action.png b/doc/_static/images/jupyter_panel_preview_in_action.png new file mode 100644 index 0000000000..fe7451a2eb Binary files /dev/null and b/doc/_static/images/jupyter_panel_preview_in_action.png differ diff --git a/doc/_static/images/monitoring_dashboard.png b/doc/_static/images/monitoring_dashboard.png new file mode 100644 index 0000000000..d746afb57b Binary files /dev/null and b/doc/_static/images/monitoring_dashboard.png differ diff --git a/doc/_static/images/notebook_inspect_param_class.png b/doc/_static/images/notebook_inspect_param_class.png new file mode 100644 index 0000000000..7c45be9ed2 Binary files /dev/null and b/doc/_static/images/notebook_inspect_param_class.png differ diff --git a/doc/_static/images/notebook_inspect_param_instance.png b/doc/_static/images/notebook_inspect_param_instance.png new file mode 100644 index 0000000000..75422569fc Binary files /dev/null and b/doc/_static/images/notebook_inspect_param_instance.png differ diff --git a/doc/_static/images/notebook_inspect_print.png b/doc/_static/images/notebook_inspect_print.png new file mode 100644 index 0000000000..3b7e2b746b Binary files /dev/null and b/doc/_static/images/notebook_inspect_print.png differ diff --git a/doc/_static/images/notebook_inspect_print_1.png b/doc/_static/images/notebook_inspect_print_1.png new file mode 100644 index 0000000000..2f08bdb9cc Binary files /dev/null and b/doc/_static/images/notebook_inspect_print_1.png differ diff --git a/doc/_static/images/notebook_inspect_question_mark.png b/doc/_static/images/notebook_inspect_question_mark.png new file mode 100644 index 0000000000..b51241fe27 Binary files /dev/null and b/doc/_static/images/notebook_inspect_question_mark.png differ diff --git a/doc/_static/images/notebook_inspect_shift_tab.png b/doc/_static/images/notebook_inspect_shift_tab.png new file mode 100644 index 0000000000..e50c11d6ce Binary files /dev/null and b/doc/_static/images/notebook_inspect_shift_tab.png differ diff --git a/doc/_static/images/notebook_inspect_shift_tab_link.png b/doc/_static/images/notebook_inspect_shift_tab_link.png new file mode 100644 index 0000000000..647475094c Binary files /dev/null and b/doc/_static/images/notebook_inspect_shift_tab_link.png differ diff --git a/doc/_static/images/notebook_intslider_reference_doc.png b/doc/_static/images/notebook_intslider_reference_doc.png new file mode 100644 index 0000000000..b851018748 Binary files /dev/null and b/doc/_static/images/notebook_intslider_reference_doc.png differ diff --git a/doc/_static/images/panel-chat-examples.png b/doc/_static/images/panel-chat-examples.png new file mode 100644 index 0000000000..2e56dddf48 Binary files /dev/null and b/doc/_static/images/panel-chat-examples.png differ diff --git a/doc/_static/images/panel-serve-ipynb-app.png b/doc/_static/images/panel-serve-ipynb-app.png new file mode 100644 index 0000000000..f3c2e9b49c Binary files /dev/null and b/doc/_static/images/panel-serve-ipynb-app.png differ diff --git a/doc/_static/images/panel-serve-ipynb-notebook.png b/doc/_static/images/panel-serve-ipynb-notebook.png new file mode 100644 index 0000000000..f162e248f7 Binary files /dev/null and b/doc/_static/images/panel-serve-ipynb-notebook.png differ diff --git a/doc/_static/images/panel-serve-multipage-app.png b/doc/_static/images/panel-serve-multipage-app.png new file mode 100644 index 0000000000..5d0ee60866 Binary files /dev/null and b/doc/_static/images/panel-serve-multipage-app.png differ diff --git a/doc/_static/images/panel-serve-py-app.png b/doc/_static/images/panel-serve-py-app.png new file mode 100644 index 0000000000..e9e3968b46 Binary files /dev/null and b/doc/_static/images/panel-serve-py-app.png differ diff --git a/doc/_static/images/panes.png b/doc/_static/images/panes.png new file mode 100644 index 0000000000..62b77b2227 Binary files /dev/null and b/doc/_static/images/panes.png differ diff --git a/doc/_static/images/structure_data_store_app.png b/doc/_static/images/structure_data_store_app.png new file mode 100644 index 0000000000..b1d3bb04c8 Binary files /dev/null and b/doc/_static/images/structure_data_store_app.png differ diff --git a/doc/_static/images/templates_customized_dark.png b/doc/_static/images/templates_customized_dark.png new file mode 100644 index 0000000000..04f787891f Binary files /dev/null and b/doc/_static/images/templates_customized_dark.png differ diff --git a/doc/_static/images/templates_customized_default.png b/doc/_static/images/templates_customized_default.png new file mode 100644 index 0000000000..a9dbd3bd15 Binary files /dev/null and b/doc/_static/images/templates_customized_default.png differ diff --git a/doc/_static/images/templates_hello_world.png b/doc/_static/images/templates_hello_world.png new file mode 100644 index 0000000000..9ef1a55d56 Binary files /dev/null and b/doc/_static/images/templates_hello_world.png differ diff --git a/doc/_static/images/templates_hello_world_notebook.png b/doc/_static/images/templates_hello_world_notebook.png new file mode 100644 index 0000000000..e7044d6caf Binary files /dev/null and b/doc/_static/images/templates_hello_world_notebook.png differ diff --git a/doc/_static/images/understand_load.png b/doc/_static/images/understand_load.png new file mode 100644 index 0000000000..84fe3c4ed2 Binary files /dev/null and b/doc/_static/images/understand_load.png differ diff --git a/doc/_static/images/understand_load_refresh.png b/doc/_static/images/understand_load_refresh.png new file mode 100644 index 0000000000..686e763f60 Binary files /dev/null and b/doc/_static/images/understand_load_refresh.png differ diff --git a/doc/_static/images/vscode-script.png b/doc/_static/images/vscode-script.png deleted file mode 100644 index 74044280d6..0000000000 Binary files a/doc/_static/images/vscode-script.png and /dev/null differ diff --git a/doc/community.md b/doc/community.md new file mode 100644 index 0000000000..49c8a1f924 --- /dev/null +++ b/doc/community.md @@ -0,0 +1,75 @@ +# Community + +Find inspiration in the community forums, ask for help, help others, share your work, report issues, add feature requests, and contribute improvements. + +## Detailed discussions on Discourse + +:::{note} +[Discourse](https://discourse.holoviz.org/) is the place for detailed discussions around code. Here we can ask for help, find answers to other users' questions, and showcase our work. + +When asking questions, please include a [Minimal, Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) if at all possible. It will make it much, much easier to help you. +::: + +Go to the [HoloViz Discourse](https://discourse.holoviz.org/) and sign up. + +Click [this link](https://discourse.holoviz.org/t/welcome-please-introduce-yourself/3310/) to *introduce yourself* to the community. + +[![HoloViz Discourse](_static/images/join_community_discourse.png)](https://discourse.holoviz.org/) + +## Quick chats on Discord + +:::{note} +[Discord](https://discord.gg/rb6gPXbdAr) is the place for quick and live discussions. +::: + +Click [this link](https://discord.gg/rb6gPXbdAr) to sign up. + +Then *introduce yourself* in the `introduce-yourself` channel. + +[![HoloViz Discourse](_static/images/join_community_discord.png)](https://discord.gg/rb6gPXbdAr) + +## Bugs, Features, and Contributions on Github + +:::{note} +We can report bugs, ask for new features, and contribute improvements at [github.com/holoviz/panel](https://github.com/holoviz/panel). + +Before reporting bugs or asking for new features, please make sure they are not already in the [*Issues tracker*](https://github.com/holoviz/panel/issues). +::: + +[![Github Issues](_static/images/join_community_github.png)](https://github.com/holoviz/panel/issues). + +Create an account by following [this guide](https://docs.github.com/en/get-started/quickstart/creating-an-account-on-github). + +## Announcements and Showcases on Social Media + +Follow Panel on [X](https://x.com/Panel_org). + +[![Panel on X](_static/images/join_community_twitter.png)](https://x.com/Panel_org) + +Follow Panel on [LinkedIn](https://www.linkedin.com/company/panel-org) + +[![Panel on LinkedIn](_static/images/join_community_linkedin.png)](https://www.linkedin.com/company/panel-org) + +## Community Resources at Awesome Panel + +Bookmark [Awesome Panel](https://awesome-panel.org). + +[![Awesome Panel](_static/images/awesome-panel.png)](https://awesome-panel.org) + +## Contribute + +Panel is an open source, community-driven project. We are nothing without the community. + +If you want to start contributing, check out the [Contributing Guide](https://github.com/holoviz/panel/blob/main/CONTRIBUTING.MD). Thanks. + +[![Panel is an Open Source, Community-driven project](_static/images/join_community_open_source.png)](https://github.com/holoviz/panel/blob/main/CONTRIBUTING.MD) + +## Resources + +- [Awesome Panel](https://awesome-panel.org). +- [Contributing Guide](https://github.com/holoviz/panel/blob/main/CONTRIBUTING.MD) +- [Discord](https://discord.gg/rb6gPXbdAr) +- [Discourse](https://discourse.holoviz.org/) +- [Github](https://github.com/holoviz/panel) +- [LinkedIn](https://www.linkedin.com/company/panel-org) +- [X](https://x.com/Panel_org) diff --git a/doc/developer_guide/index.md b/doc/developer_guide/index.md index f97a2823df..1245114e26 100644 --- a/doc/developer_guide/index.md +++ b/doc/developer_guide/index.md @@ -121,7 +121,7 @@ Once pyctdev is available and you are in the cloned panel repository you can set doit env_create -c pyviz/label/dev -c conda-forge --name=panel_dev --python=3.9 ``` -Specify the desired Python version, currently Panel officially supports Python 3.8 or later. Once the environment has been created you can activate it with: +Specify the desired Python version, currently Panel officially supports Python 3.9 or later. Once the environment has been created you can activate it with: ```bash conda activate panel_dev diff --git a/doc/explanation/api/examples/outliers_declarative.md b/doc/explanation/api/examples/outliers_declarative.md index 4f490f94d8..de58693c2e 100644 --- a/doc/explanation/api/examples/outliers_declarative.md +++ b/doc/explanation/api/examples/outliers_declarative.md @@ -1,62 +1,80 @@ -# Simple Outlier App - Declarative API +# Declarative API with Class-Based Approach -This app is a complement to the simple app demonstrated in the [Getting Started > Build an app](../../../getting_started/build_app.md) tutorial which utilized the reactive API. +This section introduces a more advanced and powerful method of creating Panel apps using a declarative, class-based approach. It builds upon the simple app demonstrated in the [Getting Started > Build an app](../../../getting_started/build_app.md) tutorial, which utilized the reactive, function-based API. -The reactive API approach is very flexible, but it ties your domain-specific code (the parts about sine waves) with your widget display code. That's fine for small, quick projects or projects dominated by visualization code, but what about large-scale, long-lived projects, where the code is used in many different contexts over time, such as in large batch runs, one-off command-line usage, notebooks, and deployed dashboards? For larger projects like that, it's important to be able to separate the parts of the code that are about the underlying domain (i.e. application or research area) from those that are tied to specific display technologies (such as Jupyter notebooks or web servers). +While the reactive API approach is flexible, it intertwines domain-specific code with widget display code. This works well for small projects or those heavily focused on visualization. However, for larger, long-term projects used across various contexts like batch runs, command-line usage, notebooks, and deployed dashboards, it becomes crucial to separate domain logic from display technologies. -For such usages, Panel supports objects declared with the separate [Param](http://param.pyviz.org) library, which provides a GUI-independent way of capturing and declaring the parameters of your objects (and dependencies between your code and those parameters), in a way that's independent of any particular application or dashboard technology. For instance, the app in [Getting Started > Build an app](../../../getting_started/build_app.md) can be captured in an object that declares the ranges and values of all parameters, as well as how to generate the plot, independently of the Panel library or any other way of interacting with the object. First, we'll copy the initial steps : +For such scenarios, Panel supports the use of objects declared with the separate [Param](http://param.holoviz.org) library. Param provides a GUI-independent way to capture and declare object parameters and dependencies, irrespective of any specific application or dashboard technology. This allows for modularization, making it easier to manage and reuse code across different environments. +In this approach, the app's logic is encapsulated within a class, separating concerns and promoting code organization. Let's walk through the steps: ```{pyodide} -import panel as pn import hvplot.pandas import numpy as np -import param import pandas as pd +import panel as pn +import param -pn.extension() +PRIMARY_COLOR = "#0072B5" +SECONDARY_COLOR = "#B54300" +CSV_FILE = ( + "https://raw.githubusercontent.com/holoviz/panel/main/examples/assets/occupancy.csv" +) +pn.extension(design="material") +``` + +```{pyodide} +@pn.cache +def get_data(): + return pd.read_csv(CSV_FILE, parse_dates=["date"], index_col="date") -csv_file = 'https://raw.githubusercontent.com/holoviz/panel/main/examples/assets/occupancy.csv' -data = pd.read_csv(csv_file, parse_dates=['date'], index_col='date') +data = get_data() data.tail() ``` ```{pyodide} -def view_hvplot(avg, highlight): - return avg.hvplot(height=300, width=400, legend=False) * highlight.hvplot.scatter( - color="orange", padding=0.1, legend=False - ) - -def find_outliers(variable="Temperature", window=30, sigma=10, view_fn=view_hvplot): +def transform_data(variable, window, sigma): + """Calculates the rolling average and identifies outliers""" avg = data[variable].rolling(window=window).mean() residual = data[variable] - avg std = residual.rolling(window=window).std() outliers = np.abs(residual) > std * sigma - return view_fn(avg, avg[outliers]) + return avg, avg[outliers] + + +def get_plot(variable="Temperature", window=30, sigma=10): + """Plots the rolling average and the outliers""" + avg, highlight = transform_data(variable, window, sigma) + return avg.hvplot( + height=300, width=800, legend=False, color=PRIMARY_COLOR + ) * highlight.hvplot.scatter(color=SECONDARY_COLOR, padding=0.1, legend=False) ``` -Now, let's implement the declarative API approach: +```{pyodide} +get_plot(variable='Temperature', window=20, sigma=10) +``` + +Now, let's implement the declarative API approach using a `Parameterized` class: ```{pyodide} class RoomOccupancy(param.Parameterized): - variable = param.Selector(default="Temperature", objects=list(data.columns)) - window = param.Integer(default=30, bounds=(1, 60)) - sigma = param.Number(default=10, bounds=(0, 20)) + variable = param.Selector(default="Temperature", objects=list(data.columns)) + window = param.Integer(default=30, bounds=(1, 60)) + sigma = param.Number(default=10, bounds=(0, 20)) def view(self): - return find_outliers(self.variable, self.window, self.sigma, view_fn=view_hvplot) + return get_plot(self.variable, self.window, self.sigma) obj = RoomOccupancy() obj ``` -The `RoomOccupancy` class and the `obj` instance have no dependency on Panel, Jupyter, or any other GUI or web toolkit; they simply declare facts about a certain domain (such as that smoothing requires window and sigma parameters, and that window is an integer greater than 0 and sigma is a positive real number). This information is then enough for Panel to create an editable and viewable representation for this object without having to specify anything that depends on the domain-specific details encapsulated in `obj`: - +The `RoomOccupancy` class and the `obj` instance have no direct dependency on Panel, Jupyter, or any other GUI toolkit. They solely declare facts about a specific domain, such as the parameters required for smoothing. This information is sufficient for Panel to create an interactive representation without needing domain-specific details encapsulated in `obj`: ```{pyodide} pn.Column(obj.param, obj.view) ``` -To support a particular domain, you can create hierarchies of such classes encapsulating all the parameters and functionality you need across different families of objects, with both parameters and code inheriting across the classes as appropriate, all without any dependency on a particular GUI library or even the presence of a GUI at all. This approach makes it practical to maintain a large codebase, all fully displayable and editable with Panel, in a way that can be maintained and adapted over time. See the [Attractors Panel app](https://examples.pyviz.org/attractors/attractors_panel.html) ([source](https://github.com/holoviz-topics/examples/tree/main/attractors)) for a more complex illustration of this approach, and the Panel codebase itself for the ultimate demonstration of using Param throughout a codebase! +To support various domains, you can create hierarchies of classes encapsulating parameters and functionality across different object families. Parameters and code can inherit across classes as needed, without depending on any specific GUI library. This approach facilitates the maintenance of large codebases, all displayable and editable with Panel, adaptable over time. For a more complex illustration, refer to the [Attractors Panel app](https://examples.holoviz.org/gallery/attractors/attractors_panel.html) ([source](https://github.com/holoviz-topics/examples/tree/main/attractors)), and explore the Panel codebase itself for extensive usage of Param throughout the codebase. diff --git a/doc/explanation/components/components_overview.md b/doc/explanation/components/components_overview.md index 89502db52a..cc31bc9053 100644 --- a/doc/explanation/components/components_overview.md +++ b/doc/explanation/components/components_overview.md @@ -1,215 +1,298 @@ -# Component overview +# Components Overview -Panel provides a wide range of components for easily composing panels, apps, and dashboards both in the notebook and as standalone apps. The components can be broken down into three broad classes of objects: +Panel is a library that provides a lot of object types and while building an app, even a simple one, you will create and interact with many of them. Compare this for instance to the Pandas library, with which you'll normally interact with just a few object types (e.g. `Series`, `DataFrame`). It is important for you to understand the differences between the objects Panel offers and how you can use them. -* ``Pane`` objects allow wrapping external viewable items like Bokeh, Plotly, Vega, or HoloViews plots, so they can be embedded in a panel. -* ``Widget`` objects provide controls that can trigger Python or JavaScript events. -* ``Panel`` layout objects allow combining plots into a ``Row``, ``Column``, ``Tabs`` or a ``Grid``. +```{pyodide} +import panel as pn + +pn.extension(notifications=True) +``` + +The main objects that Panel provides, and that we are going to call *components* hereafter, short for *visual components*, include: -All objects share an API that makes it easy to [customize behavior and appearance](../../how_to/styling/index.md), [link parameters](../../how_to/links/index.md), and to [display them in notebooks](../../how_to/notebook/index.md), [serve them as applications](../../how_to/server/index.md) or [export them](../../how_to/export/index.md). To display any panel objects in a notebook environment ensure you load the extension first: +- *Widgets*: widgets are components, usually quite small even if there are exceptions, that allow your users to interact with your app. Most importantly, they allow you to get user input! Examples include a text input, a checkbox, a slider, etc. +- *Panes*: panes are wrappers around some data that allow you to render that data, possibly customizing the rendering. Panel is known to support many date types, especially from the PyData ecosystem. You can indeed display a Pandas DataFrame, a Plotly plot, a Matplotlib plot, an Altair plot, all together on the same app! You can of course display HTML text or just raw text. Panes aren't limited to rendering data statically, they can allow for some user interactions and state syncing, like for instance the `Audio` or `Vega` panes. +- *Indicators*: indicators are useful to display some static state, they are indeed implemented as widgets that you can only control programmatically. You'll find for instance a progress indicator or a tooltip. +- *Layouts*: after having built various widgets, panes and indicators, it's time to display them together. Panel provides a dozen of layout components, including of course the most common `Row` and `Column` layouts. +- *Templates*: templates are components that render multiple Panel objects in an HTML document. The basic template, which you get when you serve an app without setting any template, is basically a blank canvas. Instead when you use one of the built-in templates you can easily improve the design and branding of your app, which will get for free a header, a sidebar, etc. +- *Notifications*: notifications are components that display so called "toasts", designed to mimic the push notifications that have been popularized by mobile and desktop operating systems. +All the Panel components can be visualized on the [Component Gallery](../../reference/index.md). + +:::{tip} +Components usually have in their docstring a link to their documentation page, use `?` in a notebook or your IDE inspection capabilities to access the link. +::: + +## Parameterized components + +All components in Panel are built on the [Param](https://param.holoviz.org/) library. Each component declares a set of *Parameters* that control the behavior and output of the component. The basic idea however is that the *Parameter* values can be controlled both at the class-level: ```{pyodide} -import panel as pn -pn.extension() +pn.widgets.IntRangeSlider.width = 350 ``` -Note that to use certain components such as Vega, LaTeX, and Plotly plots in a notebook, the models must be loaded using the extension. E.g.: +or on each instance: -``` python -pn.extension('vega', 'katex') +```{pyodide} +pn.widgets.IntRangeSlider(width=100) ``` -will ensure that the Vega and LaTeX JS dependencies are loaded. Once the extension is loaded, Panel objects will display themselves in the notebook. Outside the notebook, objects can be displayed in a server using the `show` method or run from the commandline by appending ``.servable()`` to the objects to be displayed. +## State syncing -## Parameterized Components +Be it in a notebook, in a served app, or in a Pyodide/PyScript app, Panel components sync their state between all views of the object. To better understand what that means, we create a `TextInput` widget and display it two times. Run the two following cells to render two views of the same widget. -All components in Panel are built on the [Param](https://param.holoviz.org/) library. Each component declares a set of parameters that control the behavior and output of the component. See the [Param documentation](https://param.holoviz.org/) for more details on how to use Param. The basic idea, however, is that the parameter values can be controlled both at the class level: +```{pyodide} +w_text = pn.widgets.TextInput() +w_text +``` ```{pyodide} -pn.widgets.Select.sizing_mode = 'stretch_both' +w_text ``` -or on each instance: +This widget has many *Parameters* than can be set and will be synced, i.e. a programmatic update of their value will be reflected in the user interface, affecting its views. Run the two cells below and observe the two views above being updated. + +```{pyodide} +w_text.width = 100 +``` + +```{pyodide} +w_text.value = 'new text' +``` + +What you just experimented is one-way syncing from your code to the user interface, i.e. from your running Python interpreter to your browser tab. + +This can also work the other way around, i.e. when you modify a component from the user interface directly its Python state gets updated accordingly. Try this out by typing some text in one of the widgets above and then execute the cell below. You will see that what you typed is now reflected in the widget `value`. In this case we say that the `value` *Parameter* is bi-directionally synced (or linked). ```{pyodide} -pn.widgets.Select(sizing_mode='stretch_both'); +w_text.value ``` -In the first case, all subsequent Select objects created will have sizing mode `stretch_both`; in the second only this particular instance will. +## Core components + +### Widgets + +More than 50 different widgets are available in the `pn.widgets` subpackage, varying from a simple text input to a more complex chat bot. The widget classes use a consistent API that allows treating broad categories of widgets as interchangeable. For instance, to select a value from a list of options, you can interchangeably use a `Select` widget, a `RadioButtonGroup`, or a range of other equivalent widgets. + +#### `value` + +Widgets **all** have a `value` *Parameter* that holds the widget state and that is bi-directionally synced. What is accepted for `value` is often a constrained *Parameter*. + +:::{note} -## Panes +One gotcha that doesn't only apply to the `value` *Parameter* but that you are more likely to encounter with this *Parameter* than others is when it is referencing a mutable data structure that you mutate inplace. Take for example a `MultiSelect` widget whose `value` is a `list`. If you programmatically update that list directly, with for example `append` or `extend`, Panel will not be able to detect that change. In which case you need to explicitly trigger updates with `w_multiselect.param.trigger('value')` that will run all the same underlying machinery as if you were setting the *Parameter* to a new value. A notable widget that holds a multable datastructure is the `Tabulator` widget whose `value` is a Pandas DataFrame that can be updated inplace with e.g. `df.loc[0, 'A'] = new_value`, its `patch` method allows to both update the data and the user interface. +::: -``Pane`` objects makes it possible to display and arrange a wide range of plots and other media on a page, including plots (e.g. Matplotlib, Bokeh, Vega/Altair, HoloViews, Plotly), images (e.g. PNGs, SVGs, GIFs, JPEGs), and various markup languages (e.g. Markdown, HTML, LaTeX). +```{pyodide} +w_multi = pn.widgets.MultiSelect(options=list('abc')) +w_multi +``` -There are two main ways to construct panes. The first is to explicitly construct a pane: +```{pyodide} +# Setting `value` updates the user interface as expected. +w_multi.value = ['a', 'b'] +``` +```{pyodide} +# However, updating `value` in place doesn't. +w_multi.value.append('c') +``` ```{pyodide} -pn.pane.Markdown(''' -# H1 -## H2 -### H3 -''') +# Trigger updates to also select 'c' in the user interface +w_multi.param.trigger('value') ``` -Panel also provides a convenient helper function that will convert objects into a Pane or Panel. This utility is also used internally when passing external objects to a Row, Column, or other Panel. The utility resolves the appropriate representation for an object by checking all Pane object types available and then ranking them by priority. When passing a string (for instance) there are many representations, but the PNG pane takes precedence if the string is a valid URL or local file path ending in ".png". +#### Throttling + +There are two types of widgets that can be updated very frequently in the user interface: sliders (e.g. `IntSlider`, `FloatSlider`, `RangerSlider`) and text inputs (e.g. `TextInput`, `AutocompleteInput`). In some cases you may want to react on every state update of these widgets, in some others you only want to react when the user has finished interacting with the widget. For instance, you certainly do not want to run a long simulation on every step of a slider being dragged by your user, but you may want a plot to be updated live when your user moves another slider. Panel provides additional *Parameters* to cover those cases: +- sliders: the `value` *Parameter* is updated continuously when the slider is being dragged and the `value_throttled` *Parameter* is updated only when the handle on the slider is released (less updates) +- text inputs: the `value` *Parameter* is only updated when the *Enter* key is pressed or the widget loses focus and the `value_input` *Parameter* is updated on every key press (more updates). ```{pyodide} -png = pn.panel('https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png', width=500) +pn.config.throttled = True +``` -png +```{pyodide} +slider = pn.widgets.IntSlider(start=0, end=10) ``` -To see the type of the pane, use the `print` function, which works with any Widget, Pane, or (perhaps most usefully) Panel: +```{pyodide} +text = pn.widgets.TextInput() +``` +```{pyodide} +text.value_input +``` ```{pyodide} -print(png) +slider.param.value_throttled +``` + +#### `name` + +Most widgets can have a caption that is set with the `name` *Parameter*. + +```{pyodide} +pn.widgets.Button(name='Click me!') ``` -The `print` representation typically includes *Parameter* values for that object, such as `width` in this case. +```{pyodide} +pn.widgets.TextInput(name='Age:') +``` -All Panel objects store the object they are wrapping on their ``object`` parameter. By setting that parameter, existing views of this object (whether in this or other notebook cells or on a server instance) will update: +#### `description` +The `description` *Parameter* was added in Panel 1.0 to some widgets. It adds a tooltip icon next to the widget label, it supports rendering HTML content. ```{pyodide} -png.object = 'https://upload.wikimedia.org/wikipedia/commons/3/39/PNG_demo_heatmap_Banana.png' +pn.widgets.Select( + name='Mode', description='', + options=[1, 2] +) ``` -In addition to the ``object`` parameter, each pane type may have additional parameters that modify how the ``object`` is rendered. +### Panes -## Widgets +Pane objects makes it possible to display a wide range of plots and other media on a page, including plots (e.g. Matplotlib, Bokeh, Vega/Altair, HoloViews, Plotly, Vizzu), images (e.g. PNGs, SVGs, GIFs, JPEGs), various markup languages (e.g. Markdown, HTML, LaTeX) and DataFrames. Panes are available under the `pn.pane` subpackage. -``Widget`` components, like all objects in Panel, sync their parameter state between all views of the object. Widget objects have a ``value`` parameter, layout parameters, and other parameters specific to each widget. In the notebook we can display widgets just like other Panel objects: +#### `pn.panel()` +Panel provides `pn.panel` as a convenient helper function that will convert objects into a Pane. The utility resolves the appropriate representation for an object by checking all Pane object types available and then ranking them by priority. When passing a string (for instance) there are many representations, but the PNG pane takes precedence if the string is a valid URL or local file path ending in ".png". ```{pyodide} -widget = pn.widgets.TextInput(name='A widget', value='A string') -widget +png = pn.panel('https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png', width=200) +png ``` -In this way the widget values can easily be accessed and set: +```{pyodide} +row = pn.Row('# Title') +``` +We can check that `PNG` is indeed the Pane type inferred by the utility. ```{pyodide} -widget.value = '3' -widget.width = 100 +print(png) ``` -As well as linked to other objects: +#### `object` + +Panes **all** store the object they are wrapping on the `object` *Parameter*. Existing views of a pane will update when `object` is set to a new value. Let's verify that with the `Markdown` pane, run the next cells to display two views of the same pane and update both of them by setting `object` with a new string. +```{pyodide} +p_md = pn.pane.Markdown('# Title') +p_md +``` ```{pyodide} -string = pn.pane.Str() +p_md.object +``` -widget.jslink(string, value='object') +```{pyodide} +p_md.object = '# New title' +``` -pn.Row(widget, string) +### Indicators + +Indicators are useful to show transient state (e.g. progress bar), a numerical indicator or a text indicator (e.g. tooltip). They sit in between widgets and panes, they have a `value` *Parameter* that holds their state but cannot be modified from the user interface. + +```{pyodide} +i_number = pn.indicators.Number( + name='Total Amount', value=3.5, format='{value}K', + colors=[(5, 'green'), (10, 'red')] +) +i_number ``` -See the [How-To > Link section](../../how_to/links/index.md) for recipes on how to link widgets to other objects, using either Python or JavaScript. +```{pyodide} +i_number.value = 6 +``` -## Panels +Indicators work well with asynchronous generators to quickly update their state without blocking. -Panels allow arranging widget and pane objects into fixed-size or responsively resizing layouts, building simple apps or complex dashboards. The whole Panel library is designed to make it easy to create such objects, which is why it takes its name from them. +### Layouts -There are four main types of ``Panel``s: +Layouts, aka *Panels*, allow arranging other components objects into fixed-size or responsively resizing layouts, building simple apps or complex dashboards. The whole Panel library is designed to make it easy to create such objects, which is why it takes its name from them. -* **``Row``**: A ``Row`` arranges a list of components horizontally. -* **``Column``**: A ``Column`` arranges a list of components vertically. -* **``Tabs``**: A ``Tabs`` object lays out a list of components as selectable tabs. -* **``GridSpec``**: A ``GridSpec`` lays out components on a grid. +There are four main types of Panels: -``Spacer`` components are also provided, to control spacing between other components. +- A `Row` arranges a list of components horizontally. +- A `Column` arranges a list of components vertically. +- A `Tabs` object lays out a list of components as selectable tabs. +- A `GridSpec` lays out components on a grid. -### Row & Column +We aren't going to explore all these types of layouts, instead we are going to focus on a key difference between some of them that affects how you interact with them: -The ``Row``, ``Column``, and ``Tabs`` Panels all behave very similarly. All of them are list-like, which means they have many of the same methods as a simple Python list, making it easy to add, replace, and remove components interactively using ``append``, ``extend``, ``clear``, ``insert``, ``pop``, ``remove`` and ``__setitem__``. These methods make it possible to interactively configure and modify an arrangement of plots, making them an extremely powerful tool for building apps or dashboards. +- The `Row`, `Column`, `Tabs`, `GridBox`, `FlexBox`, `Accordion` and `FloatPanel` layouts have list-like semantics, which means they have many of the same methods as a simple Python `list`, making it easy to add, replace, and remove components interactively using `append`, `extend`, `clear`, `insert`, `pop`, `remove` and `__setitem__`. These methods make it possible to interactively configure and modify an arrangement of plots, making them an extremely powerful tool for building apps or dashboards. +- The `GridSpec` and `GridStack` layout is quite different from the other layout types in that it isn’t list-like. Instead it behaves more like a 2D array that automatically expands when assigned to. This property makes it a very powerful means of declaring a dashboard layout with either a fixed size or with responsive sizing, i.e. one that will rescale with the browser window. -``Row`` and ``Column`` can be initialized as empty or with the objects to be displayed as arguments. If each of the object(s) provided is not already a ``Widget``, ``Pane``, or ``Panel``, the panel will internally call the ``pn.panel`` function to convert it to a displayable representation (typically a Pane). +All the layout objects can be initialized as empty. When initialized with some objects, or when some new objects are set, if these objects aren't Panel components the `pn.panel` helper will be called internally to convert it into a displayable representation (typically a Pane). -To start with, we will declare a ``Column`` and populate it with a title and a widget: +#### List-like API +List-like layouts can be initialized with a variable number of objects. To start with, we will declare a `Column` and populate it with a title and a widget. ```{pyodide} column = pn.Column('# A title', pn.widgets.FloatSlider()) +column ``` -Next we add another bit of markdown: - +Next we add another bit of markdown. ```{pyodide} column.append('* Item 1\n* Item 2') ``` -Then we add a few more widgets: - +Then we add a few more widgets. ```{pyodide} column.extend([pn.widgets.TextInput(), pn.widgets.Checkbox(name='Tick this!')]) ``` -and finally we change our mind and replace the ``Checkbox`` with a button: - +And finally we change our mind and replace the `Checkbox` with a button. ```{pyodide} column[4] = pn.widgets.Button(name='Click here') - -column ``` -The ability to add, remove, and replace items using list operations opens up the possibility of building rich and responsive GUIs with the ease of manipulating a Python list. - -### Tabs - -The ``Tabs`` layout allows displaying multiple objects as individually toggleable tabs. Just like ``Column`` and ``Row``, the tabs object can be used like a list. However, when adding or replacing items, it is also possible to pass a tuple providing a custom title for the tab: - +The ability to add, remove, and replace items using list operations opens up the possibility of building rich and responsive GUIs with the ease of manipulating a Python list! You can inspect the structure of a layout calling `print`. ```{pyodide} -from bokeh.plotting import figure - -p1 = figure(width=300, height=300) -p1.line([1, 2, 3], [1, 2, 3]) - -tabs = pn.Tabs(p1) - -# Add a tab -tabs.append(('Slider', pn.widgets.FloatSlider())) +print(column) +``` -# Add multiple tabs -tabs.extend([ - ('Text', pn.widgets.TextInput()), - ('Color', pn.widgets.ColorPicker()) -]) +The `Tabs` and `Accordion` layouts behave similarly, however, when adding or replacing items, it is also possible to pass a tuple providing a custom title for the tab +```{pyodide} +tabs = pn.Tabs(('Text', 'Some text')) tabs ``` -### GridSpec - -A ``GridSpec`` is quite different from the other ``Panel`` layout types in that it isn't list-like. Instead it behaves more like a 2D array that automatically expands when assigned to. This property makes it a very powerful means of declaring a dashboard layout with either a fixed size or with responsive sizing, i.e. one that will rescale with the browser window. **Note**: A GridSpec modifies the layout parameters of the objects that are assigned to the grid, so that it can support both fixed and responsive sizing modes. +```{pyodide} +# Insert a tab with a title +tabs.insert(0, ('Slider', pn.widgets.FloatSlider())) +``` -To create a ``GridSpec`` we first declare it, either with a responsive ``sizing_mode`` or a fixed width and height. Once declared we can use 2D assignment to specify the index or span on indices the object in the grid should occupy. Just like a Python array, the indexing is zero-based and specifies the rows first and the columns second, i.e. ``gspec[0, 1]`` would assign an object to the first row and second column. +#### Grid-like API -Like the other Panel types, any object can be assigned to a grid location. However, responsive sizing modes will only work well if each object being assigned supports interactive rescaling. To demonstrate the abilities, let us declare a grid with a wide range of different objects, including Spacers, Bokeh figures, HoloViews objects, images, and widgets: +Grid-like layouts are initialized empty and populated setting 2D assignments to specify the index or span on indices the object in the grid should occupy. Just like a Python array, the indexing is zero-based and specifies the rows first and the columns second, i.e. `gridlike[0, 1]` would assign an object to the first row and second column. +To demonstrate the abilities, let us declare a grid with a wide range of different objects, including `Spacers`, HoloViews objects, images, and widgets. ```{pyodide} import holoviews as hv -import holoviews.plotting.bokeh -from bokeh.plotting import figure +hv.extension('bokeh') -fig = figure() -fig.scatter([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], [0, 1, 2, 3, 2, 1, 0, -1, -2, -3]) - -gspec = pn.GridSpec(sizing_mode='stretch_both', max_height=800) +pn.extension('gridstack') +``` +```{pyodide} +gspec = pn.GridStack(sizing_mode='stretch_width', height=500) gspec[0, :3] = pn.Spacer(styles=dict(background='#FF0000')) gspec[1:3, 0] = pn.Spacer(styles=dict(background='#0000FF')) -gspec[1:3, 1:3] = fig +gspec[1:3, 1:3] = hv.Scatter([0, 1, 0]).opts(shared_axes=False) gspec[3:5, 0] = hv.Curve([1, 2, 3]) gspec[3:5, 1] = 'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png' gspec[4:5, 2] = pn.Column( @@ -220,17 +303,141 @@ gspec[4:5, 2] = pn.Column( gspec ``` -When assigning to a grid cell that is already occupied, the ``GridSpec`` will generate a helpful warning that highlights which objects are overlapping and where. Even so, it will still replace any overlapping items by default. To control this behavior to either error or replace the objects silently, set the ``GridSpec`` mode to 'error' or 'override'. +### Shared *Parameters* + +Widgets, indicators, panes and layouts all share a set of *Parameters* which are going to be briefly introduced in the following sections. + +#### `loading` + +Loading spinners are everywhere on modern apps and for good reasons, they let your users know that your app is doing some work for them! Fortunately they are very easy to set up in Panel, just set the `loading` *Parameter* to `True` or `False` on a component to display and hide its loading spinner. + +```{pyodide} +p_md = pn.pane.Markdown('# Title', loading=True) +p_md +``` + +```{pyodide} +pn.config.loading_spinner = 'petal' +pn.config.loading_color = 'red' +``` + +```{pyodide} +p_md.loading = False +``` + +#### `visible` + +Sometimes it's useful to just completely hide a component, for instance to hide some advanced options. Again that's very easy to do, just set the `visible` *Parameter* to `False` on a component you want to hide. + +```{pyodide} +w_text = pn.widgets.TextInput(name='Advanced') +w_text +``` + +```{pyodide} +w_text.visible = True +``` + +#### Style + +A few *Parameters* allow to control the style of components, including `styles`, `stylesheets`, `css_classes` and `design`. These will be explored in more details in one of the next guides. As a teaser, the next cell is a simple example leveraging the `styles` *Parameter* only, that accepts a dictionary of CSS styles. + +```{pyodide} +custom_style = { + 'background': '#f9f9f9', + 'border': '1px solid black', + 'padding': '10px', + 'box-shadow': '5px 5px 5px #bcbcbc' +} + +pn.widgets.FloatSlider(name='Number', styles=custom_style) +``` + +#### Size and responsivity + +A few *Parameters* allow to control the size and responsivity of components, including `height`, `width`, `min_height`, `min_width` and `sizing_mode`. These will be explored in more details in one of the next guides. + +#### `margin` + +The `margin` *Parameter* can be used to create space around an element defined as the number of pixels at the (top, right, bottom, and left). When you set it with a single value the margin is going to be applied to each side of the element, `margin` allows for more fine-grained distributio of the margin. + +```{pyodide} +pn.widgets.Button(name='Click', margin=(25, 0, 0, 0)) +``` + +#### `align` + +The `align` *Parameter* controls how components align vertically and horizontally. It supports `‘start’`, `‘center’`, and `‘end’` values and can be set for both horizontal and vertical directions at once or for each separately by passing in a tuple of the form `(horizontal, vertical)`. ```{pyodide} -gspec[0:3, :2] = 'Some text' +pn.Row( + pn.widgets.IntSlider(), + pn.widgets.IntSlider(align=('center', 'start')), + height=100, + styles={'background': 'lightgrey'}, +) +``` + +## Templates + +A template is the HTML document that ends up being served by your app, it defines what resources (Javascript, CSS) need to be loaded, the page title, where the Panel objects are supposed to be rendered on the page, etc. + +When you serve an app without defining a particular template Panel serves it with its default template, that is pretty much a blank canvas where the served objects, if there are a few of them, will be rendered vertically one after the other. + +Try saving the following snippet in a `app.py` file and serving it with `panel serve app.py --show` + +```python +import panel as pn + +pn.panel('# Title').servable() +pn.panel('Some text').servable() +pn.panel('More text').servable() ``` -In addition to assignment, we can also slice and index the ``GridSpec`` to access an individual object or a subregion of the grid, returning another GridSpec. Here we will access the last row and everything except the first column: +When developing an app, someone (possibly you!) will require at some point to make it prettier! A quick way to achieve that is to wrap your app in one of the templates that Panel provides, that are defined by declaring four main content areas on the page, which can be populated as desired: +- `header`: The header area of the HTML page +- `sidebar`: A collapsible sidebar +- `main`: The main area of the application +- `modal`: A modal, i.e. a dialog box/popup window + +These four areas behave very similarly to layouts that have list-like semantics. This means we can easily append new components into these areas. Unlike other layout components however, the contents of the areas is fixed once rendered. If you need a dynamic layout you should therefore insert a regular layout (e.g. a `Column` or `Row`) and modify it in place once added to one of the content areas. + + + +:::{warning} +The templates provided by Panel should not be rendered in a notebook, as their CSS styling usually doesn't play well with the CSS styling of the notebook itself. +::: + +Since an app can only have one template, Panel allows to declare the app template via `pn.extension(template='..')`. Of course you can also explicitly instantiate a template and manipulate it as you would do with other Panel objects. Try serving the content of this snippet. + +```python +import panel as pn + + +template = pn.template.BootstrapTemplate(title='Loving Panel!') + + +def compute(i): + return '❤️' * i + + +w_number = pn.widgets.IntSlider(value=5, start=1, end=5) +p_hearts = pn.panel(pn.bind(compute, w_number)) + +template.sidebar.append(w_number) +template.main.append(p_hearts) +template.show() +``` + +## Notifications + +The web apps you end up building with Panel are often quite interactive. Therefore you will be interested in finding a way to let your users know what's going on, when their operations succeed or abort, etc. This is exactly what notifications are for! Contrary to the components we have just covered, notifications are objects you don't manipulate directly, instead you just call `pn.state.notifications` with one the following methods: `success`, `info`, `warning` and `error`. ```{pyodide} -gspec[-1, 1:] +pn.state.notifications.success('Your first notification!') ``` -For more details on using `GridSpec` Panels, see the [component gallery](../../reference/layouts/GridSpec.ipynb). +```{pyodide} +pn.state.notifications.error('Oops!', duration=5000) +``` diff --git a/doc/tutorials/development.md b/doc/explanation/develop_seamlessly.md similarity index 73% rename from doc/tutorials/development.md rename to doc/explanation/develop_seamlessly.md index 38a891f50b..c9191e12b3 100644 --- a/doc/tutorials/development.md +++ b/doc/explanation/develop_seamlessly.md @@ -1,19 +1,17 @@ -# Effective Development +# Develop Seamlessly Across Environments -:::{note} Tutorial 4. **Efficient Development** -:icon: false +Our objective is to empower teams to seamlessly utilize Panel regardless of the development environment, e.g. Jupyter Notebook, Jupyter Lab, Binder, Google Colab, Spyder, VS Code, and PyCharm. - It's also important for you to be able to use a coding environment you are comfortable with. To this end, Panel offers very good support for Jupyter Notebooks (Classic and JupyterLab) and editors like Visual Studio Code, which are the two environments we will cover in this guide. - -::: ## Notebook +We love notebooks. + ### Notebook friendly -Jupyter notebooks have been supported in Panel since day 1 and more generally since the inception of the HoloViz project. Notebooks are environments that allow to iterate quickly, running some code and observing instantaneously its output. Notebooks also happen to be a valid deployment option: +Jupyter notebooks have been supported in Panel since day 1. Notebooks are environments that allow one to iterate quickly, running some code and observing instantaneously its output. Notebooks also happen to be a valid deployment option: -- you can actually develop apps that are meant to live only in a notebook, that you will share as a file with your colleagues and friends. Panel allows you to make your notebooks more user friendly, like IPywidgets does. +- you can actually develop apps that are meant to live only in a notebook, that you will share as a file with your colleagues and friends. Panel allows you to make your notebooks more user friendly, like [IPywidgets](https://ipywidgets.readthedocs.io/en/stable/) does. - you can serve a notebook with `panel serve file.ipynb`, Panel will parse your notebook to collect all of its code and run it as if it were a Python script. Jupyter notebooks aren't the only flavor of notebooks available, Panel also supports Visual Studio Code and Google Colab notebooks, and probably works fine on other platforms. For Visual Studio Code notebooks, you will need to install the `jupyter_bokeh` package in your environment. @@ -28,7 +26,7 @@ import panel as pn pn.extension() ``` -The extension ensures that all required Javascript and CSS resources are added to your notebook environment. If you are going to be using any custom extensions, such as [Vega](../reference/panes/Vega.md) or [Tabulator](../reference/widgets/Tabulator.md) you must ensure that you initialize these as well. +The extension ensures that the [`pyviz_comms`](https://github.com/holoviz/pyviz_comms) and all required Javascript and CSS resources are added to your notebook environment. If you are going to be using any custom extensions, such as [Vega](../reference/panes/Vega.md) or [Tabulator](../reference/widgets/Tabulator.md) you must ensure that you initialize these as well. ```{pyodide} pn.extension('vega', 'tabulator') @@ -69,7 +67,7 @@ pn.Row(w, pn.bind(compute, w)) ### Render templates -You have been told in the previous guide to avoid rendering Panel templates in a notebook. That's however not a dead end! There are two approaches you can leverage to display an app that is wrapped in a template while working from a notebook, in addition to just being able to serve the notebook with `panel serve notebook.ipynb`. +You have been told in the Panel tutorial to avoid rendering Panel templates in a notebook. That's however not a dead end! There are two approaches you can leverage to display an app that is wrapped in a template while working from a notebook, in addition to just being able to serve the notebook with `panel serve notebook.ipynb`. Templates, as any other Panel visual components, are equipped with the `.show()` method that when called starts a Bokeh server, serves your app and opens it in a new tab. While this approach doesn't allow you to iterate super quickly, it's a simple way to check what your app looks like when served. @@ -116,11 +114,13 @@ This is what you should be able to see after a little while. Now tick `Render on ![alt text](../_static/images/jlabpreview.png) -## Visual Studio Code +## Editor + +Editors like VS Code and PyCharm have quickly become some of the most popular Python coding environment. -Visual Studio Code (VS Code) has quickly become one of the most popular Python coding environment. In this section you will see how you can to set yourself a nice developer experience with VS Code when developing a Panel app. +### Serve apps with autoreload -### Setup and autoreload +To enable a quick feedback loop while developing, we support serving your apps with autoreload. Save the content of the next cell in a file named `app.py`. @@ -153,35 +153,15 @@ The `--show` flag will open a browser tab with the live app and the `--autoreloa ![VSCode Preview](../_static/images/vscode_preview.png) -### Debugging - -While you can debug your app using the often blamed but never outperformed `print`, or by manually adding breakpoints with `breakpoint()`, the VS Code integrated debugger offers a more user friendly experience. - -To configure the integrated debugger for Panel, you will need to add a debugging configuration like the following. - -```json -// .vscode/launch.json -{ - "version": "0.2.0", - "configurations": [ - { - "name": "panel serve", - "type": "python", - "request": "launch", - "program": "-m", - "args": [ - "panel", - "serve", - "${relativeFile}", - "--show" - ], - "console": "integratedTerminal", - "justMyCode": true - } - ] -} -``` +## Resources + +### Tutorials + +- [Develop in an Editor (Basic)](../tutorials/basic/develop_editor.md) +- [Develop in an Editor (Intermediate)](../tutorials/intermediate/develop_editor.md)¨ +- [Develop in a Notebook (Basic)](../tutorials/basic/develop_notebook.md) -With this configuration in place, launching the debugger will serve the file you are working on with Panel, open a new browser tab pointing at the served app. Interact then with your app to hit one of the breakpoints you set up in VS Code. +### How-to -![VSCode Debugging](../static/images/vscode_debug.png) +- [Configure VS Code](../how_to/editor/vscode_configure.md) +- [Write apps in Markdown](../how_to/editor/markdown.md) diff --git a/doc/explanation/index.md b/doc/explanation/index.md index f449dcf02e..ed354b3368 100644 --- a/doc/explanation/index.md +++ b/doc/explanation/index.md @@ -4,6 +4,20 @@ The explanation section aims to clarify, deepen, and broaden the understanding o Beyond the [Getting Started > Core Concepts](../getting_started/core_concepts.md), which new users must complete before working with Panel, this explanation section is intended to help practitioners form and strengthen a conceptual web that facilitates new and advanced usage directions. +## Develop + +::::{grid} 1 2 2 3 +:gutter: 1 1 1 2 + +:::{grid-item-card} {octicon}`code;2.5em;sd-mr-1 sd-animate-grow50` Develop Seamlessly +:link: api/index +:link-type: doc + +Learn how we enable you and your team to work seamlessly with Panel across a wide range of development environments +::: + +:::: + ## APIs ::::{grid} 1 2 2 3 diff --git a/doc/explanation/styling/templates_overview.md b/doc/explanation/styling/templates_overview.md index 3d78183cf6..2d7dd3d122 100644 --- a/doc/explanation/styling/templates_overview.md +++ b/doc/explanation/styling/templates_overview.md @@ -1,4 +1,4 @@ -# Templates +# Templates Overview When deploying a Panel app or dashboard as a Bokeh application, it is rendered into a default template that serves the JS and CSS resources as well as the actual Panel object being shown. However, it is often desirable to customize the layout of the deployed app, or even to embed multiple separate panels into an app. The ``Template`` component in Panel allows customizing this default template, including the ability to rendering multiple components in a single document easily. diff --git a/doc/getting_started/build_app.md b/doc/getting_started/build_app.md index f5b74fb538..26f5bce132 100644 --- a/doc/getting_started/build_app.md +++ b/doc/getting_started/build_app.md @@ -1,63 +1,89 @@ -# {octicon}`tools;2em;sd-mr-1` Build an app +# {octicon}`tools;2em;sd-mr-1` Build an App -At this point you should have [set up your environment and installed Panel](installation.md) so you should be ready to get going. +By now, you should have [set up your environment and installed Panel](installation.md), so you're all set to dive in! -On this page, we're going to be building a basic interactive application based on Numpy, Pandas and [hvplot](https://hvplot.holoviz.org/). If you want to implement this app yourself as you follow along, we recommend starting with a Jupyter notebook. You can also launch the Notebook with JupyterLite on the right. +In this section, we'll walk through creating a basic interactive application using [NumPy](https://numpy.org/), [Pandas](https://pandas.pydata.org/), and [hvplot](https://hvplot.holoviz.org/). If you haven't installed `hvPlot` yet, you can do so with `pip install hvplot` or `conda install -c conda-forge hvplot`. -## Fetch the data +Let's envision what our app will look like: -First let's load the [UCI ML dataset](http://archive.ics.uci.edu/ml/datasets/Occupancy+Detection+) that measured the environment in a meeting room: +![Getting Started App](../_static/images/getting_started_app.png) + +If you're eager to roll up your sleeves and build this app alongside us, we recommend starting with a Jupyter notebook. You can also run the code directly in the docs or launch the Notebook with JupyterLite on the right. :::{important} -At first, this website renders code block outputs with limited interactivity, indicated by the golden border to the left of the output below. By clicking the play button ( ) you can activate full interactivity, indicated by a green left-border. +Initially, the code block outputs on this website offer limited interactivity, indicated by the golden border to the left of the output below. By clicking the play button ( ), you can activate full interactivity, marked by a green left-border. ::: +## Fetching the Data + +First, let's import the necessary dependencies and define some variables: + ```{pyodide} -import panel as pn import hvplot.pandas -import pandas as pd import numpy as np +import pandas as pd +import panel as pn + +PRIMARY_COLOR = "#0072B5" +SECONDARY_COLOR = "#B54300" +CSV_FILE = ( + "https://raw.githubusercontent.com/holoviz/panel/main/examples/assets/occupancy.csv" +) +``` + +Next, we'll import the Panel JavaScript dependencies using `pn.extension(...)`. For a visually appealing and responsive user experience, we'll set the `design` to `"material"` and the `sizing_mode` to `stretch_width`: + +```{pyodide} +pn.extension(design="material", sizing_mode="stretch_width") +``` + +Now, let's load the [UCI ML dataset](http://archive.ics.uci.edu/ml/datasets/Occupancy+Detection+) that measured the environment in a meeting room: -pn.extension(design='material') +```{pyodide} +@pn.cache +def get_data(): + return pd.read_csv(CSV_FILE, parse_dates=["date"], index_col="date") -csv_file = ("https://raw.githubusercontent.com/holoviz/panel/main/examples/assets/occupancy.csv") -data = pd.read_csv(csv_file, parse_dates=["date"], index_col="date") +data = get_data() data.tail() ``` -## Visualize a subset of the data +We'll speed up our application by caching (`@pn.cache`) the data across users. + +## Visualizing a Subset of the Data -Before we utilize Panel, let's write a function that smooths one of our time series and finds the outliers. We will then plot the result using hvplot. +Before diving into Panel, let's create a function that smooths one of our time series and identifies outliers. Then, we'll plot the result using hvplot: ```{pyodide} def transform_data(variable, window, sigma): - ''' Calculates the rolling average and the outliers ''' + """Calculates the rolling average and identifies outliers""" avg = data[variable].rolling(window=window).mean() residual = data[variable] - avg std = residual.rolling(window=window).std() outliers = np.abs(residual) > std * sigma return avg, avg[outliers] -def create_plot(variable="Temperature", window=30, sigma=10): - ''' Plots the rolling average and the outliers ''' + +def get_plot(variable="Temperature", window=30, sigma=10): + """Plots the rolling average and the outliers""" avg, highlight = transform_data(variable, window, sigma) - return avg.hvplot(height=300, width=400, legend=False) * highlight.hvplot.scatter( - color="orange", padding=0.1, legend=False - ) + return avg.hvplot( + height=300, legend=False, color=PRIMARY_COLOR + ) * highlight.hvplot.scatter(color=SECONDARY_COLOR, padding=0.1, legend=False) ``` -We can now call our `create_plot` function with specific parameters to get a plot with a single set of parameters. +Now, we can call our `get_plot` function with specific parameters to obtain a plot with a single set of parameters: ```{pyodide} -create_plot(variable='Temperature', window=20, sigma=10) +get_plot(variable='Temperature', window=20, sigma=10) ``` -It works! But now we want explore how values for `window` and `sigma` affect the plot. We could reevaluate the above cell a lot of times, but that would be a slow and painful process. Instead, let's use Panel to quickly add some interactive controls and quickly determine how different parameter values impact the output. +Great! Now, let's explore how different values for `window` and `sigma` affect the plot. Instead of reevaluating the above cell multiple times, let's use Panel to add interactive controls and quickly visualize the impact of different parameter values. -## Explore parameter space +## Exploring the Parameter Space -Let's create some Panel slider widgets for the range of parameter values that we want to explore. +Let's create some Panel slider widgets to explore the range of parameter values: ```{pyodide} variable_widget = pn.widgets.Select(name="variable", value="Temperature", options=list(data.columns)) @@ -65,22 +91,58 @@ window_widget = pn.widgets.IntSlider(name="window", value=30, start=1, end=60) sigma_widget = pn.widgets.IntSlider(name="sigma", value=10, start=0, end=20) ``` -Now that we have a function and some widgets, let's link them together so that updates to the widgets rerun the function. One easy way to create this link in Panel is with `pn.bind`: +Now, let's link these widgets to our plotting function so that updates to the widgets rerun the function. We can achieve this easily in Panel using `pn.bind`: + +```{pyodide} +bound_plot = pn.bind( + get_plot, variable=variable_widget, window=window_widget, sigma=sigma_widget +) +``` + +Once we've bound the widgets to the function's arguments, we can layout the resulting `bound_plot` component along with the `widgets` using a Panel layout such as `Column`: ```{pyodide} -bound_plot = pn.bind(create_plot, variable=variable_widget, window=window_widget, sigma=sigma_widget) +widgets = pn.Column(variable_widget, window_widget, sigma_widget, sizing_mode="fixed", width=300) +pn.Column(widgets, bound_plot) ``` -Once you have bound the widgets to the function's arguments you can lay out the resulting `bound_plot` component along with the `widget` components using a Panel layout such as `Column`: +As long as you have a live Python process running, dragging these widgets will trigger a call to the `get_plot` callback function, evaluating it for whatever combination of parameter values you select and displaying the results. + +## Serving the Notebook + +We'll organize our components in a nicely styled template (`MaterialTemplate`) and mark it `.servable()` to add it to our served app: ```{pyodide} -first_app = pn.Column(variable_widget, window_widget, sigma_widget, bound_plot) +pn.template.MaterialTemplate( + site="Panel", + title="Getting Started App", + sidebar=[variable_widget, window_widget, sigma_widget], + main=[bound_plot], +).servable(); # The ; is needed in the notebook to not display the template. Its not needed in a script +``` -first_app.servable() +Save the notebook with the name `app.ipynb`. + +Finally, we'll serve the app with: + +```bash +panel serve app.ipynb --autoreload ``` -As long as you have a live Python process running, dragging these widgets will trigger a call to the `create_plot` callback function, evaluating it for whatever combination of parameter values you select and displaying the results. +Now, open the app in your browser at `http://localhost:5006/app`. + +It should look like this: + +![Getting Started App](../_static/images/getting_started_app.png) + +:::{note} + +If you prefer developing in a Python Script using an editor, you can copy the code into a file `app.py` and serve it. + +```bash +panel serve app.py --autoreload +``` -## Next Steps +## What's Next? -Now that we have given you a taste of how easy it is to build a little application in Panel, it's time to introduce you to some of the [core concepts](core_concepts.md) behind Panel. +Now that you've experienced how easy it is to build a simple application in Panel, it's time to delve into some of the [core concepts](core_concepts.md) behind Panel. diff --git a/doc/getting_started/core_concepts.md b/doc/getting_started/core_concepts.md index 7d38f1faa1..27a844b8d8 100644 --- a/doc/getting_started/core_concepts.md +++ b/doc/getting_started/core_concepts.md @@ -1,78 +1,89 @@ -# {octicon}`mortar-board;2em;sd-mr-1` Core Concepts +# Core Concepts -In the last section we learned how to [build a simple app](build_app.md). A Panel app like this makes it very easy to explore any function that produces a visual result of a [supported type](https://github.com/pyviz/panel/issues/2), such as Matplotlib, Bokeh, Plotly, Altair, or various text and image types. +In the previous section, we delved into [building a simple app](build_app.md). A Panel app like this makes exploration of various visualizations very easy, supporting types such as Matplotlib, Bokeh, Plotly, Altair, DataFrame, and various text and image types. -## Development flow +Now, let's explain the core concepts of Panel. -As you prepare to develop your Panel application, there are a couple initial development decisions to make about **API** and **environment**: +## Development Flow -1. Will you be programming with a Python class-based approach, suitable for more complex apps? If not, or if you are unsure, we recommend starting with the reactive API (`pn.bind`) that you already tasted while [building a simple app](build_app.md). +As you gear up to develop your Panel application, you'll encounter a couple of initial decisions to make regarding **API** and **environment**: -:::{dropdown} Interested in a class-based approach instead? -:margin: 4 -:color: muted -Checkout the same ['outlier' app built using a class-based declarative API](../explanation/api/examples/outliers_declarative.md). And to dive deeper, read the [Explanation > APIs](../explanation/apis.md) section for a discussion on each of the API options. -::: +1. **Programming Approach:** Will you opt for a Python *function-based* approach or a *class-based* approach? + - The function-based approach is recommended for new and basic users. It utilizes `pn.bind`, as you've already experienced while [building a simple app](build_app.md). + - The class-based approach is recommended for intermediate Panel users who aim to construct larger and more maintainable apps, as well as reusable components. + + :::{dropdown} Interested in a class-based approach instead? + :margin: 4 + :color: muted + Explore the same ['outlier' app built using a class-based declarative API](../explanation/api/examples/outliers_declarative.md). Study the [Explanation > APIs](../explanation/apis/index.md) section for a detailed discussion on each of the API options. + ::: -2. Will you be developing in a [notebook](https://jupyter.org/) or in an editor environment? If you are unsure, we recommend starting in a notebook as you get familiar with Panel, but you can switch between them at any time. Let's talk a bit more about these development environment options: +2. **Development Environment:** Will you develop in a [notebook](https://jupyter.org/) or in an editor environment? + - If you're unsure, starting in a notebook is recommended as you familiarize yourself with Panel. However, you can switch between them at any time. ### Notebook -In a notebook you can quickly iterate on individual components of your application because Panel components render inline. To get started in a notebook simply `import panel` and initialize the extension with `pn.extension()`. +In a notebook, you can swiftly iterate on individual components of your application because Panel components render inline. To get started in a notebook, simply `import panel` and initialize the extension with `pn.extension()`. ```python import panel as pn pn.extension() ``` -Now, when you put a Panel component at the end of a notebook cell it will render as part of the output of that cell. By changing the code in your cell and re-running it you can quickly iterate and build the individual units that will make up your final application. This way of working has many benefits because you can work on each component in your application individually without having to re-run your entire application each time. +By placing a Panel component at the end of a notebook cell, it renders as part of the output. You can then modify the code and re-run the cell to iterate and build the individual units of your final application. This iterative approach offers many benefits, enabling you to work on each component independently without re-running your entire application each time. +To add Panel components to your app, mark them as `.servable()` and serve the app with: + +```bash +panel serve app.ipynb --autoreload +``` + +You've already experimented with this while [building a simple app](build_app.md). + ### Editor -If you are working in an editor, declare the Panel components that you want to display as `servable` (we will discover what that means soon), then launch the script or notebook file. For example, if you were to save the app you built in the previous page into `app.py` and declared `first_app.servable()` within the file, then on the command line you can just run: +If you're working in an editor, declare the Panel components you want to display as `.servable()`, then serve the script with: ```bash panel serve app.py --autoreload --show ``` -Once you run that command Panel will launch a server that will serve your app, open a tab in your default browser (`--show`) and update the application whenever you update the code (`--autoreload`). +Upon running that command, Panel launches a server that serves your app, opens a tab in your default browser (`--show`), and updates the application whenever you modify the code (`--autoreload`). ```{note} -We recommend installing `watchfiles`, which will provide a significantly better user experience when using `--autoreload`. +We recommend installing `watchfiles` to get the best user experience when using `--autoreload`. ``` -> Checkout [How-to > Prepare to develop](../how_to/prepare_to_develop.md) for more guidance on each of the development environment options. -## Control flow +> Explore [How-to > Prepare to Develop](../how_to/prepare_to_develop.md) for more guidance on each development environment option. -Now let's get into how Panel actually works. Panel is built on a library called [Param](https://param.holoviz.org/) that controls how information flows through your application. When a *Parameter* changes, e.g. the value of a slider updates or when you update the value manually in code, events are triggered that your app can respond to. Panel provides a number of high-level and lower-level approaches for setting up interactivity in response to updates in *Parameters*. Understanding some of the basic concepts behind Param is essential to getting the hang of Panel. +## Control Flow -Let's start simple and answer the question "what is Param?" +Panel operates on a powerful framework called [Param](https://param.holoviz.org/), which governs how information flows within your app. When a change occurs, such as the value of a slider or a manual update in your code, events are triggered for your app to respond to. Panel provides various methods for setting up this interactivity. Understanding the basics of Param is crucial to mastering Panel. However, it's not necessary to get started as a new, basic user. -- Param is a framework that lets Python classes have attributes with defaults, type/value validation and callbacks when a value changes. -- Param is similar to other frameworks like [Python dataclasses](https://docs.python.org/3/library/dataclasses.html), [pydantic](https://pydantic-docs.helpmanual.io/), and [traitlets](https://traitlets.readthedocs.io/en/stable/) +So, what exactly is Param? It's a framework that enables Python classes to have attributes with defaults, type/value validation, and callbacks when values change. You can liken it to other frameworks like Python dataclasses, pydantic, and traitlets. -One of the most important concepts to understand in both Param and Panel is the ability to use *Parameters* as references to drive interactivity. This is often called reactivity and the most well known instance of this approach are spreadsheet applications like Excel. When you reference a particular cell in the formula of another cell, changing the original cell will automatically trigger an update in all cells that reference. The same concept applies to *Parameter* objects. +Reactivity is a key concept in both Param and Panel. This means changes in one part of your app can automatically update other parts. Think of it like Excel, where altering one cell can prompt updates in cells that reference it. Param objects operate similarly. -One of the main things to understand about Param in the context of its use in Panel is the distinction between the *Parameter* **value** and the *Parameter* **object**. The **value** represents the current value of a *Parameter* at a particular point in time, while the **object** holds metadata about the *Parameter* but also acts as a **reference** to the *Parameter's* value across time. In many cases you can pass a *Parameter* **object** and Panel will automatically resolve the current value **and** reactively update when that *Parameter* changes. Let's take a widget as an example: +In Panel, understanding the distinction between a Parameter's value and the Parameter object itself is important. The value represents the current value at a specific moment, while the object contains information about the Parameter and serves as a reference to its value over time. In many cases, you can utilize a Parameter object, and Panel will handle updating the value reactively as it changes. For instance, consider a widget: ```python text = pn.widgets.TextInput() -text.value # 👈 the "value" Parameter of this widget reflects the current value -text.param.value # 👈 can be used as a reference to the live value +text.value # 👈 The current value of the widget +text.param.value # 👈 A reference to the "value" Parameter, used in Panel to *bind* to the "value" ``` -We will dive into this more deeply later, for now just remember that parameter objects (whether associated with widgets or not) allow you to pass around a reference to a value that automatically updates if the original value changes. +We'll delve deeper into this later. For now, remember that parameter objects (whether associated with widgets or not) enable you to pass around a reference to a value that automatically updates if the original value changes. -## Display and rendering +## Display and Rendering -Panel aims to let you work with all your favorite Python libraries and has a system for automatically inferring how to render a particular object, whether that is a pandas `DataFrame`, matplotlib `Figure` or any other plotting object. This means that you can easily place any object you want to render into a layout (such as a `Row` or `Column`) and Panel will automatically figure out the appropriate `Pane` type to wrap it. Different `Pane` types know how to render different objects but also provide ways to update the object or even listen to events such as selection of Vega/Altair charts or Plotly plots. +Panel aims to seamlessly integrate with all your favorite Python libraries and automatically infer how to render a particular object, whether it's a `DataFrame`, a plotting `Figure`, or any other Python object. This means you can effortlessly place any object you want to render into a layout (such as a `Row` or `Column`), and Panel will automatically determine the appropriate `Pane` type to wrap it. Different `Pane` types know how to render different objects but also provide ways to update the object or even listen to events such as selection of Vega/Altair charts or Plotly plots. -For this reason, it often makes sense to get a handle on the Pane type. So if you want to wrap your `DataFrame` into a pane you can call the `panel` function and it will automatically convert it (this is exactly what a layout does internally when you give it an object to render): +Hence, it's often advantageous to understand the Pane type. For instance, to wrap your `DataFrame` into a pane, you can call the `panel` function, which will automatically convert it (this is precisely what a layout does internally when given an object to render): ```python import pandas as pd @@ -85,23 +96,25 @@ df = pd.DataFrame({ df_pane = pn.panel(df) ``` -:::{admonition} Tip +:::{tip} :class: success -To inspect the type of an object simply `print` it: +To inspect the type of an object, simply `print` it: ```python >>> print(pn.Row(df)) Row [0] DataFrame(DataFrame) ``` + ::: -Sometimes an object has multiple possible representations to pick from. In these cases you can explicitly construct the desired `Pane` type, e.g. here are a few representations of a `DataFrame`: +At times, an object may have multiple possible representations to choose from. In such cases, you can explicitly construct the desired `Pane` type. For example, here are a few representations of a `DataFrame`: ::::{tab-set} :::{tab-item} DataFrame Pane + ```python pn.pane.DataFrame(df) ``` @@ -141,6 +154,7 @@ pn.pane.DataFrame(df) ::: :::{tab-item} HTML Pane + ```python pn.pane.HTML(df) ``` @@ -194,8 +208,8 @@ pn.pane.HTML(df) ::: - :::{tab-item} Str Pane + ```python pn.pane.Str(df) ``` @@ -214,24 +228,23 @@ pn.pane.Str(df) :::{admonition} Learn More :class: info -Learn more about Panes in the [Explanation for Components](../explanation/components/components_overview.md#panes) +Discover more about Panes in the [Component Overview - Panes](https://panel.holoviz.org/explanation/components/components_overview.html#panes) section. ::: -So far we have only learned how to display data, but to actually add it to your deployed application you also need to mark it as `servable`. To mark an object as servable adds it to the current template, something we will get into later. You can either mark multiple objects as servable which will add it to the page sequentially, or you can use layouts to arrange objects explicitly. +So far, we've learned how to display data. However, to actually incorporate it into your served application, you also need to mark it as `.servable()`. Marking an object as servable adds it to the current template, something we'll delve into later. You can either mark multiple objects as servable, which adds them to the page sequentially, or you can use layouts to arrange objects explicitly. ```python df_pane.servable() ``` -:::{admonition} Note -:class: info +:::{info} -In the notebook the `.servable()` method is effectively a no-op. This means you can add it the components you want to add to the rendered app but also see it rendered inline. This makes it possible to build components sequentially in a notebook while simultaneously building an application to be served. If you want to mark something servable but do _not_ want it rendered inline, just put a semicolon (';') after it to tell Jupyter not to render it even if it is the last item in the cell. +In the notebook, the `.servable()` method effectively does nothing. This allows you to add the components you want to include in the rendered app while simultaneously viewing them inline. Thus, you can sequentially build components in a notebook while simultaneously constructing an application to be served. If you want to mark something as servable but do _not_ want it rendered inline, simply append a semicolon (';') after it to instruct Jupyter not to render it, even if it's the last item in the cell. ::: ## Widgets -To build an interactive application you will typically want to add widget components (such as `TextInput`, `FloatSlider` or `Checkbox`) to your application and then bind them to an interactive function. As an example let's create a slider: +To craft an interactive application, you'll typically add widget components (such as `TextInput`, `FloatSlider`, or `Checkbox`) to your application and then bind them to an interactive function. For example, let's create a slider: ```{pyodide} import panel as pn @@ -244,15 +257,15 @@ def square(x): pn.Row(x, pn.bind(square, x)) ``` -The `pn.bind` function lets us bind a widget or a *Parameter* **object** to a function that returns an item to be displayed. Once bound, the function can be added to a layout or rendered directly using `pn.panel` and `.servable()`. In this way you can express reactivity between widgets and output very easily. Even better, if you use a Panel-aware library like hvPlot, you often don't even need to write and bind a function explicitly, as hvPlot's [.interactive](https://hvplot.holoviz.org/user_guide/Interactive.html) DataFrames already create reactive pipelines by accepting widgets and parameters for most arguments and options. +The `pn.bind` function allows us to bind a widget or a *Parameter* **object** to a function that returns an item to be displayed. Once bound, the function can be added to a layout or rendered directly using `pn.panel` and `.servable()`. This enables you to express reactivity between widgets and output very easily. :::{admonition} Reminder :class: info -Remember how we talked about the difference between a *Parameter* **value** and a *Parameter* **object**. In the previous example the widget itself is effectively an alias for the *Parameter* object, i.e. the binding operation is exactly equivalent to `pn.bind(square, x.param.value)`. This is true for all widgets: the widget object is treated as an alias for the widget's `value` *Parameter* object. So you can generally either pass in the widget (as a convenient shorthand) or the underlying *Parameter* object. +Recall our discussion on the difference between a *Parameter* **value** and a *Parameter* **object**. In the previous example, the widget itself effectively serves as an alias for the *Parameter* object, i.e., the binding operation is exactly equivalent to `pn.bind(square, x.param.value)`. This holds true for all widgets: the widget object is treated as an alias for the widget's `value` *Parameter* object. Thus, you can generally pass either the widget (as a convenient shorthand) or the underlying *Parameter* object. ::: -The binding approach above works, but it is quite heavy handed. Whenever the slider value changes, Panel will re-create a whole new Pane and re-render the output. If we want more fine-grained control we can instead explicitly instantiate a `Markdown` pane and pass it bound functions and *Parameters* by reference: +While the binding approach above works, it can be somewhat heavy-handed. Whenever the slider value changes, Panel will recreate a whole new Pane and re-render the output. For finer control, we can instead explicitly instantiate a `Markdown` pane and pass it bound functions and *Parameters* by reference: ```{pyodide} import panel as pn @@ -273,48 +286,21 @@ pn.Column( ) ``` -To achieve the same thing using more classic callbacks we have to dig a bit further into Param functionality to learn about watching *Parameters*. To `watch` a Parameter means to declare a callback that fires when the *Parameter's* value changes. As an example let's rewrite the example above using a watcher: - -```{pyodide} -import panel as pn - -x = pn.widgets.IntSlider(name='x', start=0, end=100) -background = pn.widgets.ColorPicker(name='Background', value='lightgray') - -square = pn.pane.Markdown( - f'{x.value} squared is {x.value**2}', - styles={'background-color': background.value, 'padding': '0 10px'} -) - -def update_square(event): - square.object = f'{event.new} squared is {event.new**2}' - -def update_styles(event): - square.styles = {'background-color': event.new, 'padding': '0 10px'} - -x.param.watch(update_square, 'value') -background.param.watch(update_styles, 'value') - -pn.Row(x, background, square) -``` - -The first thing you will note is how much more verbose this is, which should make you appreciate the power of expressing reactivity using *Parameter* binding instead. But if you do need this power, it's there for you! - ## Templates -Once you have started to build an application, you will probably want to make it look nicer, which is where templates come in. Whenever you mark an object as `.servable` you are inserting it into a template. By default Panel uses a completely blank template, but it is very simple to select another template by setting `pn.config.template`. Here you will have a few options based on different frameworks, including `'bootstrap'`, `'material'` and `'fast'`. +Once you've begun building an application, you'll likely want to enhance its appearance, which is where templates come into play. When you mark an object as `.servable()`, you're inserting it into a template. By default, Panel employs a completely blank template, but selecting another template is straightforward by setting `pn.config.template`. You'll have several options based on different frameworks, including `'bootstrap'`, `'material'`, and `'fast'`. ```python pn.config.template = 'fast' ``` -:::{admonition} Note +:::{note} :class: info -The `pn.config` object provides a range of options that will allow you to configure your application. As a shortcut, you may instead provide options for the `config` object as keywords to the `pn.extension` call. In other words `pn.extension(template='fast')` is equivalent to `pn.config.template = 'fast'`, providing a clean way to set multiple config options at once. +The `pn.config` object offers a range of options to configure your application. As a shortcut, you may provide options for the `config` object as keywords to the `pn.extension` call. In other words, `pn.extension(template='fast')` is equivalent to `pn.config.template = 'fast'`, providing a clean way to set multiple config options at once. ::: -Once you have configured a template you can control where to render your components using the `target` argument of the `.servable()` method. Most templates have multiple target areas including 'main', 'sidebar', 'header' and 'modal'. As an example you might want to render your widgets into the sidebar and your plots into the main area: +Once you've configured a template, you can control where to render your components using the `target` argument of the `.servable()` method. Most templates feature multiple target areas including 'main', 'sidebar', 'header', and 'modal'. For example, you might want to render your widgets into the sidebar and your plots into the main area: ```python import numpy as np @@ -352,12 +338,12 @@ pn.Column( ## Next Steps -While `Getting Started`, you have installed Panel, built a simple app, and reviewed core concepts - the basic foundations for you to start using Panel for your own work. +In this **Getting Started** guide, you've installed Panel, built a simple app, and explored core concepts, laying the basic groundwork to start using Panel for your projects. -While working, you can find solutions to specific problems in the [How-to](../how_to/index.md) section and you can consult the [API Reference](../api/index.md) or [Component Gallery](../reference/index.rst) sections for technical specifications, descriptions, or examples. +For a hands-on approach to **learning Panel**, dive into our [Tutorials](../tutorials/index.md). If you seek clarity or wish to deepen your understanding of specific topics, refer to the [Explanation](../explanation/index.md) section of the docs. -If you want to gain clarity or deepen your understanding on particular topics, refer to the [Explanation](../explanation/index.md) section of the docs. For example, the [Explanation > APIs](../explanation/apis.md) subsection covers the benefits and drawbacks of each supported Panel API. +During **Panel usage**, consult the [Component Gallery](../reference/index.rst) reference guides, find solutions to specific problems in the [How-to](../how_to/index.md) guides, or explore the [API Reference](../api/index.md) sections for technical specifications and descriptions. ## Getting Help -Check out the [HoloViz Community](https://holoviz.org/community.html) page for all of the options to connect with developers and other users. +Explore the [Panel Community](../community.md) page for all options to connect with other users and the core developers. diff --git a/doc/getting_started/index.md b/doc/getting_started/index.md index 986237628c..dced8ca979 100644 --- a/doc/getting_started/index.md +++ b/doc/getting_started/index.md @@ -2,7 +2,7 @@ Welcome to Panel! -The getting started guide will get you set up with Panel and provide a basic overview of the features and strengths of Panel. +The getting started guides are for those who would like to **quickly try out Panel** and **explore the features and strengths of Panel**. --- @@ -19,7 +19,7 @@ Walks you through setting up your Python environment, installing Panel into it a [Learn more »](installation) ::: -:::{grid-item-card} {octicon}`tools;2.5em;sd-mr-1` Build an app +:::{grid-item-card} {octicon}`mortar-board;2.5em;sd-mr-1` Build an app :link: build_app :link-type: doc @@ -29,11 +29,11 @@ Before we dig into some of the core concepts behind Panel this guide gives you a [Learn more »](build_app) ::: -:::{grid-item-card} {octicon}`rocket;2.5em;sd-mr-1` Core Concepts +:::{grid-item-card} {octicon}`telescope;2.5em;sd-mr-1` Core Concepts :link: core_concepts :link-type: doc -Introduces you to some of the core concepts behind Panel, how to develop Panel applications effectively both in your IDE and in the notebook and some of the core features that make Panel such a powerful library. +Introduces you to some of the core concepts behind Panel, how to develop Panel applications effectively both in your Editor and in the notebook and some of the core features that make Panel such a powerful library. +++ [Learn more »](core_concepts) @@ -41,6 +41,8 @@ Introduces you to some of the core concepts behind Panel, how to develop Panel a :::: +As the next step after this Getting Started guide, we recommend learning the basics of Panel systematically via the *in depth* [Basic Tutorials](../tutorials/basic/index.md). + ## Additional Resources - The [original announcement of Panel from 2019](https://blog.holoviz.org/panel_announcement.html). diff --git a/doc/getting_started/installation.md b/doc/getting_started/installation.md index 5af34fbbe8..c6fd62ff1e 100644 --- a/doc/getting_started/installation.md +++ b/doc/getting_started/installation.md @@ -4,53 +4,73 @@ [![conda defaults badge](https://img.shields.io/conda/v/anaconda/panel.svg?label=conda%7Cdefaults)](https://anaconda.org/anaconda/panel) [![PyPI badge](https://img.shields.io/pypi/v/panel.svg)](https://pypi.python.org/pypi/panel) [![License badge](https://img.shields.io/pypi/l/panel.svg)](https://github.com/holoviz/panel/blob/main/LICENSE.txt) + +Panel is compatible with Python 3.9 or later and works seamlessly on Linux, Windows, and Mac. + ## Setup -Panel works with Python 3.8 or later on Linux, Windows, and Mac. +:::::{tab-set} -The recommended way to install Panel is using the [conda](https://docs.conda.io/projects/conda/en/latest/index.html) command that is included in the installation of [Anaconda or Miniconda](https://conda.io/projects/conda/en/latest/user-guide/install/index.html). Completing the installation for either Anaconda or Miniconda will also install Python. +::::{tab-item} pip +:sync: pip -:::{admonition} Note +:::{note} +Before proceeding, ensure you have Python installed on your system. If not, you can download and install Python from [Python.org](https://www.python.org/downloads/). -To help you choose between Anaconda and Miniconda, review [this page](https://docs.conda.io/projects/conda/en/latest/user-guide/install/download.html#anaconda-or-miniconda). +When using `pip`, it's important to keep your default Python environment clean. Utilize [virtual environments](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/) to isolate your projects effectively. ::: -:::{admonition} Note -When you begin using conda, you already have a default environment named `base`. You don't want to install programs into your base environment, though. [Create separate environments](https://conda.io/projects/conda/en/latest/user-guide/getting-started.html) to keep your programs isolated from each other. +:::: + +::::{tab-item} conda +:sync: conda + +:::{note} +Before proceeding, make sure you have either [Anaconda or Miniconda](https://conda.io/projects/conda/en/latest/user-guide/install/index.html) installed on your system. If not, you can find installation instructions [here](https://conda.io/projects/conda/en/latest/user-guide/install/index.html). + +When using `conda`, it's crucial to maintain a clean base environment. Consider [creating separate environments](https://conda.io/projects/conda/en/latest/user-guide/getting-started.html) to manage your projects efficiently. ::: -If you choose not to install Anaconda or Miniconda, you can download Python directly from [Python.org](https://www.python.org/downloads/). In this case, you can install Panel using [pip](https://pip.pypa.io/en/stable/), which comes with Python. +:::: + +::::: ## Installing Panel -Open up a terminal and run the following command, which will install Panel with all its dependencies. +Now, let's get Panel installed on your system. -::::{tab-set} +:::::{tab-set} -:::{tab-item} conda -:sync: conda +::::{tab-item} pip +:sync: pip -``` bash -conda install panel +```bash +pip install panel watchfiles ``` -::: -:::{tab-item} pip -:sync: pip +:::: + +::::{tab-item} conda +:sync: conda -``` bash -pip install panel +```bash +conda install panel watchfiles ``` -::: :::: +::::: + +```{tip} +We recommend also installing `watchfiles` while developing. This will provide a significantly better experience when using Panel's `--autoreload` feature. It's not needed for production. +``` + :::{important} -Is Panel installed together with JupyterLab/Jupyter Notebook in your working environment? If not, you need to make sure that `pyviz_comms` is explicitly installed in the same environment as JupyterLab/Jupyter Notebook (`conda install pyviz_comms` or `pip install pyviz-comms`) for bi-directional communication to be fully working. +Make sure Panel is installed in the same environment as JupyterLab/Jupyter Notebook (`conda install panel` or `pip install panel`) to ensure all features work correctly. ::: :::{seealso} -If you intend to work with Panel in a non-Jupyter notebook environment such as VSCode have a quick at the [relevant how-to section](../how_to/notebook/other_nb.md). +If you plan to use Panel in a non-Jupyter notebook environment, such as Google Colab or VSCode, refer to the [relevant how-to section](../how_to/notebook/other_nb.md). ::: ## Next Steps diff --git a/doc/how_to/authentication/basic.md b/doc/how_to/authentication/basic.md index 48904de42a..2c10092548 100644 --- a/doc/how_to/authentication/basic.md +++ b/doc/how_to/authentication/basic.md @@ -59,7 +59,7 @@ panel serve app.py --basic-auth credentials.json --cookie-secret my_super_safe_c The basic auth provider will now check the provided credentials against the credentials declared in this file. -:::note +:::{admonition} Note When serving an application dynamically using `pn.serve` you can also provide a dictionary of usernames and passwords via the `basic_auth` keyword argument. ::: diff --git a/doc/how_to/components/construct_panes.md b/doc/how_to/components/construct_panes.md index bdae238a90..8fe92d120c 100644 --- a/doc/how_to/components/construct_panes.md +++ b/doc/how_to/components/construct_panes.md @@ -6,7 +6,7 @@ This guide addresses how to construct Pane objects for displaying visible compon There are two main ways to construct a pane - explicitly or automatically. -To explicitly construct a pane, use one of the pane types listed in the [component gallery](../../reference/index.md#panes). For example, you can create a Markdown pane as follows: +To explicitly construct a pane, use one of the pane types listed in the [component gallery](https://panel.holoviz.org/reference/index.md#panes). For example, you can create a Markdown pane as follows: ```{pyodide} import panel as pn diff --git a/doc/how_to/editor/editor.md b/doc/how_to/editor/editor.md deleted file mode 100644 index cdf447eaf7..0000000000 --- a/doc/how_to/editor/editor.md +++ /dev/null @@ -1,46 +0,0 @@ -# Develop Apps in an Editor - -This guide addresses how to rapidly develop a Panel application in your favorite IDE or editor. - ---- - -You can edit your Panel code as a ``.py`` file in any text editor, marking the objects you want to render as ``.servable()``. For example: - -:::{card} app.py -```{code-block} python -:emphasize-lines: 14 - -import panel as pn -import hvplot.pandas -from bokeh.sampledata.autompg import autompg - -columns = list(autompg.columns[:-2]) - -x = pn.widgets.Select(value='mpg', options=columns, name='x') -y = pn.widgets.Select(value='hp', options=columns, name='y') -color = pn.widgets.ColorPicker(name='Color', value='#AA0505') - -pn.Row( - pn.Column('## MPG Explorer', x, y, color), - pn.bind(autompg.hvplot.scatter, x, y, c=color) -).servable() -``` -::: - -Then, from the command line, launch a server with: - -```bash -panel serve app.py --show --autoreload -``` - -:::{admonition} Note -The `--show` flag will open a browser tab with the live app and the ``--autoreload`` flag ensures that the app reloads whenever you make a change to the Python source. -::: - -In VS Code this looks like - -![VS Code Script](../../_static/images/vscode-script.png) - -The app looks like - -![App Developed in an Editor](../../_static/images/editor_server_app.png) diff --git a/doc/how_to/editor/editor_debug.md b/doc/how_to/editor/editor_debug.md deleted file mode 100644 index f9d8a0b699..0000000000 --- a/doc/how_to/editor/editor_debug.md +++ /dev/null @@ -1,16 +0,0 @@ -# Debug Apps in an Editor - -This guide addresses how to debug apps in your favorite IDE or editor. - ---- - -The simplest way to debug is to insert a `breakpoint()` in your code and then serve your app from a terminal. Type `help` in the debugger to see the available *commands*. - - - -If your editor or IDE provides *integrated debugging* you can also use that in one of two ways. - -- Use `.servable()` and configure your editor to start debugging using the command `python -m panel serve `. This is our recommended method. -- Use `.show()` on a single Panel component. This is an alternative method. - -For more details check out the [VS Code Debug Configuration Guide](vscode_configure#debugging). diff --git a/doc/how_to/editor/index.md b/doc/how_to/editor/index.md index 88d235aefa..c3fbd3a594 100644 --- a/doc/how_to/editor/index.md +++ b/doc/how_to/editor/index.md @@ -5,20 +5,6 @@ One of the main design goals for Panel was to make it seamless to work across de ::::{grid} 1 2 2 3 :gutter: 1 1 1 2 -:::{grid-item-card} {octicon}`code-square;2.5em;sd-mr-1 sd-animate-grow50` Develop apps in an editor -:link: editor -:link-type: doc - -How to rapidly develop a Panel application in your favorite IDE or editor. -::: - -:::{grid-item-card} {octicon}`bug;2.5em;sd-mr-1 sd-animate-grow50` Debug apps in an editor -:link: editor_debug -:link-type: doc - -How to debug a Panel application in your favorite IDE or editor. -::: - :::{grid-item-card} {octicon}`gear;2.5em;sd-mr-1 sd-animate-grow50` Configure VS Code :link: vscode_configure :link-type: doc @@ -40,6 +26,6 @@ How to write Panel applications inside Markdown files. :hidden: :maxdepth: 2 -editor +vscode_configure markdown ``` diff --git a/doc/how_to/editor/vscode_configure.md b/doc/how_to/editor/vscode_configure.md index 37cd2ec157..07cacf3a19 100644 --- a/doc/how_to/editor/vscode_configure.md +++ b/doc/how_to/editor/vscode_configure.md @@ -4,7 +4,7 @@ This guide addresses how to configure VS Code for an efficient Panel development We assume you have -- a basic understanding of [developing Panel apps in an editor](editor.md) and [working with Python in VS Code](https://code.visualstudio.com/docs/python/python-tutorial). +- a basic understanding of [working with Python in VS Code](https://code.visualstudio.com/docs/python/python-tutorial). - installed the VS Code [Python extension](https://github.com/Microsoft/vscode-python) --- diff --git a/doc/how_to/index.md b/doc/how_to/index.md index 170cca518c..05d6fd3649 100644 --- a/doc/how_to/index.md +++ b/doc/how_to/index.md @@ -3,7 +3,7 @@ The Panel How-to guides provide step by step recipes for solving essential problems and tasks that arise during your work. They assume that you've completed the Getting Started material and therefore already have some knowledge of how Panel works. There is no order to the guides, other than any potential prerequisites listed at the top of a page. Jump to the topic that is relevant to you now. -## Prepare to develop +## Develop Efficiently ::::{grid} 1 2 2 3 :gutter: 1 1 1 2 diff --git a/doc/how_to/layout/align.md b/doc/how_to/layout/align.md index 8e950e6136..551398642f 100644 --- a/doc/how_to/layout/align.md +++ b/doc/how_to/layout/align.md @@ -8,7 +8,6 @@ The `align` parameter controls how components align vertically and horizontally. One common use-case where alignment is important is when placing multiple items with different heights in a `Row`. Let's create a big button and align a slider to the center of the button using `align=center`: - ```{pyodide} import panel as pn pn.extension() # for notebook diff --git a/doc/how_to/notebook/jupyterlabpreview.md b/doc/how_to/notebook/jupyterlabpreview.md index 03566adbce..489ec068db 100644 --- a/doc/how_to/notebook/jupyterlabpreview.md +++ b/doc/how_to/notebook/jupyterlabpreview.md @@ -20,7 +20,7 @@ The *Preview* offers two update modes that are configurable in the preview tab: - automatic: toggle the *Render on save* checkbox ({far}`square-check`) for the app to be automatically re-rendered when you save your notebook. :::{tip} -Panel [built-in templates](../../reference/index.md#templates) don't render necessarily well in a notebook as their styling can badly interact with the notebook built-in styling. Using the *Preview* is a good way to circumvent this issue, while still being able to work within JupyterLab . +Panel [built-in templates](https://panel.holoviz.org/reference/index.md#templates) don't render necessarily well in a notebook as their styling can badly interact with the notebook built-in styling. Using the *Preview* is a good way to circumvent this issue, while still being able to work within JupyterLab . ::: ## Related Resources diff --git a/doc/how_to/server/commandline.md b/doc/how_to/server/commandline.md index 1b73cab843..10b2baa7bc 100644 --- a/doc/how_to/server/commandline.md +++ b/doc/how_to/server/commandline.md @@ -1,4 +1,4 @@ -# Launching a server on the commandline +# Launch a server on the command line Once the app is ready for deployment it can be served using the Bokeh server. For a detailed breakdown of the design and functionality of Bokeh server, see the [Bokeh documentation](https://bokeh.pydata.org/en/latest/docs/user_guide/server.html). The most important thing to know is that Panel (and Bokeh) provide a CLI command to serve a Python script, app directory, or Jupyter notebook containing a Bokeh or Panel app. To launch a server using the CLI, simply run: @@ -20,7 +20,7 @@ We recommend installing `watchfiles`, which will provide a significantly better The ``panel serve`` command has the following options: -``` console +```bash positional arguments: DIRECTORY-OR-SCRIPT The app directories or scripts to serve (serve empty document if not specified) diff --git a/doc/how_to/streamlit_migration/caching.md b/doc/how_to/streamlit_migration/caching.md index 24590559ce..29c2a6e6f4 100644 --- a/doc/how_to/streamlit_migration/caching.md +++ b/doc/how_to/streamlit_migration/caching.md @@ -14,7 +14,7 @@ In Panel - your script is run once when a user visits the page. - only *specific, bound functions* are rerun on user interactions. -Thus with Panel you *may use* caching to to make the user experience nice and fast. +Thus with Panel you *may use* caching to make the user experience nice and fast. In Panel you use `pn.cache` to speed up your apps. Check out the [Cache How-To Guides](../caching/index.md) for more details. diff --git a/doc/how_to/streamlit_migration/get_started.md b/doc/how_to/streamlit_migration/get_started.md index 767f65ded7..8ba76650a3 100644 --- a/doc/how_to/streamlit_migration/get_started.md +++ b/doc/how_to/streamlit_migration/get_started.md @@ -1,6 +1,6 @@ -# Get Started migrating from Streamlit to Panel +# Serve Apps -This guide addresses the basics of migrating from Streamlit to Panel. +This guide will show you how-to migrate and serve a *Hello World* application. --- diff --git a/doc/how_to/streamlit_migration/index.md b/doc/how_to/streamlit_migration/index.md index 2c7cc93127..ec7c8faa4a 100644 --- a/doc/how_to/streamlit_migration/index.md +++ b/doc/how_to/streamlit_migration/index.md @@ -14,14 +14,14 @@ These guides can also be used as: ::::{grid} 1 2 2 3 :gutter: 1 1 1 2 -:::{grid-item-card} {octicon}`rocket;2.5em;sd-mr-1 sd-animate-grow50` Get Started +:::{grid-item-card} {octicon}`rocket;2.5em;sd-mr-1 sd-animate-grow50` Serve Apps :link: get_started :link-type: doc -How to convert a Hello World application from Streamlit to Panel +How to convert a *Hello World* application from Streamlit to Panel and serve it ::: -:::{grid-item-card} {octicon}`device-desktop;2.5em;sd-mr-1 sd-animate-grow50` Display Content +:::{grid-item-card} {octicon}`device-desktop;2.5em;sd-mr-1 sd-animate-grow50` Display Objects :link: panes :link-type: doc diff --git a/doc/how_to/streamlit_migration/interactivity.md b/doc/how_to/streamlit_migration/interactivity.md index 7c05ad61fd..5d306c0756 100644 --- a/doc/how_to/streamlit_migration/interactivity.md +++ b/doc/how_to/streamlit_migration/interactivity.md @@ -93,7 +93,7 @@ data = np.random.normal(1, 1, size=100) bins_input = pn.widgets.IntSlider(value=20, start=10, end=30, step=1, name="Bins") bplot = pn.bind(plot, data=data, bins=bins_input) -pn.Column(bins, bplot).servable() +pn.Column(bins_input, bplot).servable() ``` ![Panel Basic Interactivity Example](https://assets.holoviz.org/panel/gifs/panel_interactivity_example.gif) diff --git a/doc/how_to/streamlit_migration/layouts.md b/doc/how_to/streamlit_migration/layouts.md index a4b832b4bc..2188387653 100644 --- a/doc/how_to/streamlit_migration/layouts.md +++ b/doc/how_to/streamlit_migration/layouts.md @@ -1,6 +1,6 @@ -# Organize Components with Layouts +# Layout Objects -*Layouts* helps you organize your Panel *components*, i.e. *panes*, *widgets* and *layouts*. +*Layouts* helps you organize your *objects* including Panel *components*. Panel provides layouts similar to the ones you know from Streamlit and many unique ones too. diff --git a/doc/how_to/streamlit_migration/panes.md b/doc/how_to/streamlit_migration/panes.md index f83a6c108c..4bbd81d95a 100644 --- a/doc/how_to/streamlit_migration/panes.md +++ b/doc/how_to/streamlit_migration/panes.md @@ -1,4 +1,4 @@ -# Displaying Content with Panes +# Display Objects with Panes In Panel the objects that can display your Python objects are called *panes*. With Panels *panes* you will be able to: diff --git a/doc/how_to/styling/design.md b/doc/how_to/styling/design.md index 9ee6c4b104..240f1fbc22 100644 --- a/doc/how_to/styling/design.md +++ b/doc/how_to/styling/design.md @@ -34,7 +34,7 @@ def create_components(design): pn.widgets.FloatSlider(name='Slider', design=design), pn.widgets.TextInput(name='TextInput', design=design), pn.widgets.Select( - name='Select', options=['Biology', 'Chemistry', 'Physics'], design=design + name='Select', options=['Biology', 'Chemistry', 'Physics'], design=design ), pn.widgets.Button( name='Click me!', icon='hand-click', button_type='primary', design=design diff --git a/doc/how_to/wasm/sphinx.md b/doc/how_to/wasm/sphinx.md index 93c4227466..734019adec 100644 --- a/doc/how_to/wasm/sphinx.md +++ b/doc/how_to/wasm/sphinx.md @@ -7,20 +7,21 @@ One more option is to include live Panel examples in your Sphinx documentation u In the near future we hope to make this a separate Sphinx extension, until then simply install latest nbsite with `pip` or `conda`: ::::{tab-set} -:::{tab-item} Conda -:sync: conda +:::{tab-item} Pip +:sync: pip ``` bash -conda install -c pyviz nbsite +pip install nbsite ``` ::: -:::{tab-item} Pip -:sync: pip +:::{tab-item} Conda +:sync: conda ``` bash -pip install nbsite +conda install -c pyviz nbsite ``` + ::: :::: diff --git a/doc/index.md b/doc/index.md index 961b4a9119..7647baf6cd 100644 --- a/doc/index.md +++ b/doc/index.md @@ -86,7 +86,7 @@ Panel makes it simple to: Enjoying Panel? Show your support with a [Github star](https://github.com/holoviz/panel) — it’s a simple click that means the world to us and helps others discover it too! ⭐️ -## Usage +## Learn Panel ::::{grid} 1 2 2 4 :gutter: 1 1 1 2 @@ -98,25 +98,46 @@ Enjoying Panel? Show your support with a [Github star](https://github.com/holovi The getting started guide will get you set up with Panel and provide a basic overview of the features and strengths of Panel. ::: -:::{grid-item-card} {octicon}`beaker;2.5em;sd-mr-1` How-to -:link: how_to/index +:::{grid-item-card} {octicon}`mortar-board;2.5em;sd-mr-1` Tutorials +:link: tutorials/index :link-type: doc -How-to guides provide step by step recipes for solving essential problems and tasks that arise during your work. +Through guided steps and activities the tutorials will help you acquire the skills and knowledge to use Panel. ::: -:::{grid-item-card} {octicon}`mortar-board;2.5em;sd-mr-1` Explanation +:::{grid-item-card} {octicon}`telescope;2.5em;sd-mr-1` Explanation :link: explanation/index :link-type: doc Introduces you to some of the core concepts behind Panel and some of the advanced features that make Panel such a powerful library. ::: +:::: + +## Use Panel + +::::{grid} 1 2 2 4 +:gutter: 1 1 1 2 + +:::{grid-item-card} {octicon}`package;2.5em;sd-mr-1` Component Gallery +:link: reference/index +:link-type: doc + +The Component Gallery showcases Panel's components and their essential reference guides, offering users vital usage information. +::: + +:::{grid-item-card} {octicon}`beaker;2.5em;sd-mr-1` How-to +:link: how_to/index +:link-type: doc + +How-to guides provide step by step recipes for solving essential problems and tasks that arise during your work. +::: + :::{grid-item-card} {octicon}`book;2.5em;sd-mr-1` API Reference :link: api/index :link-type: doc -The Panel API Reference Manual provides a comprehensive reference for all methods and parameters on Panel components. +The Panel API Reference Manual furnishes an extensive guide covering Panels methods and parameters. ::: :::: @@ -193,11 +214,12 @@ alt: Quansight Logo getting_started/index tutorials/index -how_to/index explanation/index reference/index +how_to/index gallery/index api/index +community upgrade FAQ about/index diff --git a/doc/tutorials/basic/align.md b/doc/tutorials/basic/align.md new file mode 100644 index 0000000000..894afc1592 --- /dev/null +++ b/doc/tutorials/basic/align.md @@ -0,0 +1,198 @@ +# Aligning Content + +Welcome to our tutorial on aligning content in Panel! Let's dive into exploring various alignment, margin, and spacing options to make your content look polished and visually appealing. + +## Understand Alignment Options + +In this tutorial, we'll delve into three key aspects: + +- **Alignment**: You can `align` content horizontally and vertically within a container, choosing from options like `'start'`, `'center'`, or `'end'`. +- **Spacer Component**: Utilize `Spacer` components to adjust alignment or add space between content. +- **Margin**: Fine-tune alignment by adding `margin`s to content elements. + +Let's jump in and explore these concepts in action! + +## Align a Button + +Let's start by aligning a button horizontally within a container. Run the code below: + +```{pyodide} +import panel as pn + +pn.extension() + +pn.Column( + pn.pane.PNG("https://assets.holoviz.org/panel/tutorials/wind_turbine.png", height=150, sizing_mode="scale_width"), + pn.widgets.Button(name="Stop the Turbine", icon="hand-stop",), + sizing_mode="fixed", width=400, height=400, styles={"border": "1px solid black"} +).servable() +``` + +Now, let's align the button to the center horizontally using three different techniques: + +- Using the `align` parameter +- Leveraging `Spacer` components +- Adjusting `margin` values + +Feel free to run the code snippets for each technique below and observe the changes. + +:::::{tab-set} + +::::{tab-item} Align + +```{pyodide} +import panel as pn + +pn.extension() + +pn.Column( + pn.pane.PNG("https://assets.holoviz.org/panel/tutorials/wind_turbine.png", height=150, sizing_mode="scale_width"), + pn.widgets.Button(name="Stop the Turbine", icon="hand-stop", align="center"), + sizing_mode="fixed", width=400, height=400, styles={"border": "1px solid black"} +).servable() +``` + +You can use a single value or a 2-tuple (horizontal, vertical) with the `align` parameter. + +:::: + +::::{tab-item} Spacer + +```{pyodide} +import panel as pn + +pn.extension() + +pn.Column( + pn.pane.PNG("https://assets.holoviz.org/panel/tutorials/wind_turbine.png", height=150, sizing_mode="scale_width"), + pn.Row( + pn.Spacer(sizing_mode="stretch_width"), + pn.widgets.Button(name="Stop the Turbine", icon="hand-stop"), + pn.Spacer(sizing_mode="stretch_width") + ), + sizing_mode="fixed", width=400, height=400, styles={"border": "1px solid black", "border-radius": "5px"} +).servable() +``` + +:::: + +::::{tab-item} Margin + +```{pyodide} +import panel as pn + +pn.extension() + +margin = int((400-147)/2) # The Button is 147 pixels wide + +pn.Column( + pn.pane.PNG("https://assets.holoviz.org/panel/tutorials/wind_turbine.png", height=150, sizing_mode="scale_width"), + pn.widgets.Button(name="Stop the Turbine", icon="hand-stop", margin=(5,margin)), + sizing_mode="fixed", width=400, height=400, styles={"border": "1px solid black"} +).servable() +``` + +:::{tip} +The `margin` parameter can take a single value, a 2-tuple (top/bottom, left/right), or a 4-tuple (top, right, bottom, left). +::: + +:::: + +::::: + +:::{tip} +As a rule of thumb: + +- Use `align` to adjust the overall alignment. +- Use `Spacer` for broader alignment or spacing adjustments when `align` isn't sufficient. +- Use `margin` for fine-tuning alignment or adding fixed-size spacing. +::: + +## Exercise: Align Cards + +Let's practice aligning cards within a container. Run the code snippet below: + +```{pyodide} +import panel as pn + +pn.extension() + +image = pn.pane.PNG("https://assets.holoviz.org/panel/tutorials/wind_turbine.png", height=150, sizing_mode="scale_width") + +card1 = pn.Card(image, title='Turbine 1', width=200) +card2 = pn.Card(image, title='Turbine 2', width=200) + +pn.Column( + card1, card2, + sizing_mode="fixed", width=400, height=400, styles={"border": "1px solid black"} +).servable() +``` + +Your task is to align each card in the center of its respective vertical half. There are many possible solutions, but your solution should resemble the image below: + +![Align Cards Solution](../../_static/images/align-cards-solution.png) + +:::::{dropdown} Solutions + +::::{tab-set} + +:::{tab-item} Align + Spacer + +```{pyodide} +import panel as pn + +pn.extension() + +image = pn.pane.PNG("https://assets.holoviz.org/panel/tutorials/wind_turbine.png", sizing_mode="scale_both") + +card1 = pn.Card(image, title='Turbine 1', height=150, width=200, align="center") +card2 = pn.Card(image, title='Turbine 2', height=150, width=200, align="center") +spacer = pn.Spacer(height=33) + +pn.Column( + spacer, card1, spacer, card2, + sizing_mode="fixed", width=400, height=400, styles={"border": "1px solid black"} +).servable() +``` + +::: + +:::{tab-item} Align + Margin + +```{pyodide} +import panel as pn + +pn.extension() + +image = pn.pane.PNG("https://assets.holoviz.org/panel/tutorials/wind_turbine.png", sizing_mode="scale_both") + +card1 = pn.Card(image, title='Turbine 1', height=150, width=200, align="center", margin=(33,0,17,0)) +card2 = pn.Card(image, title='Turbine 2', height=150, width=200, align="center", margin=(16,0,33,0)) + +pn.Column( + card1, card2, + sizing_mode="fixed", width=400, height=400, styles={"border": "1px solid black"} +).servable() +``` + +::: + +:::: + +::::: + +## Recap + +In this tutorial, we've explored various alignment and spacing options for content in Panel: + +- **Alignment**: Adjust content `align`meant horizontally and vertically within a container. +- **Spacer Component**: Utilize `Spacer` components to align or add space between content elements. +- **Margin**: Fine-tune alignment or add space between content using `margin`. + +Now, you're equipped with the knowledge to beautifully align and space your content in Panel! + +## References + +### How-to Guides + +- [Align Components](../../how_to/layout/align.md) diff --git a/doc/tutorials/basic/build_animation.md b/doc/tutorials/basic/build_animation.md new file mode 100644 index 0000000000..aa48fa2df9 --- /dev/null +++ b/doc/tutorials/basic/build_animation.md @@ -0,0 +1,289 @@ +# Build Animation + +In this tutorial, we will create a *bar chart race* using the [Altair](https://altair-viz.github.io) plotting library and the [`Player`](../../reference/widgets/Player.ipynb) widget. + + + +:::{dropdown} Dependencies + +```bash +altair panel +``` + +::: + +:::{dropdown} Code + +```python +import altair as alt +import pandas as pd +import panel as pn + +pn.extension("vega") # load the vega/ Altair JavaScript dependencies + +BY = "t_manu" # t_state, t_county, or t_manu +VALUE = "p_cap" + +## Extract the data + +@pn.cache() # use caching to only download data once +def get_data(): + return pd.read_csv("https://assets.holoviz.org/panel/tutorials/turbines.csv.gz") + +## Transform the data + +data = get_data() +min_year = int(data.p_year.min()) +max_year = int(data.p_year.max()) +max_capacity_by_manufacturer = data.groupby(BY)[VALUE].sum().max() + + +def get_group_sum(year): + return ( + data[data.p_year <= year][[BY, VALUE]] + .groupby(BY) + .sum() + .sort_values(by=VALUE, ascending=False) + .reset_index() + .head(10) + ) + +# Plot the data + +def get_plot(year): + data = get_group_sum(year) + base = ( + alt.Chart(data) + .encode( + x=alt.X(VALUE, scale=alt.Scale(domain=[0, max_capacity_by_manufacturer]), title="Capacity"), + y=alt.Y(f"{BY}:O", sort=[BY], title="Manufacturer"), + text=alt.Text(VALUE, format=",.0f"), + ) + .properties(title=str(year), height=500, width="container") + ) + return base.mark_bar() + base.mark_text(align="left", dx=2) + +# Add widgets + +year = pn.widgets.Player( + value=max_year, + start=min_year, + end=max_year, + name="Year", + loop_policy="loop", + interval=300, + align="center", +) + +def pause_player_at_max_year(value): + if year.value==max_year: + year.pause() + +pn.bind(pause_player_at_max_year, year, watch=True) # Stops the player when max_year is reached. + +# Bind the plot to the Player widget + +plot = pn.pane.Vega(pn.bind(get_plot, year)) + +# Layout the components +pn.Column("# Wind Turbine Capacity 1982-2022", plot, year, sizing_mode="stretch_width").servable() +``` + +::: + +## Install the dependencies + +Please ensure that [Altair](https://altair-viz.github.io/) and [Panel](https://panel.holoviz.org) are installed. + +::::{tab-set} + +:::{tab-item} pip +:sync: pip + +``` bash +pip install altair panel +``` + +::: + +:::{tab-item} conda +:sync: conda + +``` bash +conda install -y -c conda-forge altair panel +``` + +::: + +:::: + +## Explanation + +🚀 **Welcome to the Altair & Panel Bar Chart Race Tutorial!** + +Let's embark on an adventure to visualize the dynamic evolution of wind turbine capacities over the years. 🌬️💨 + +```{pyodide} +import altair as alt +import pandas as pd +import panel as pn + +pn.extension("vega") +``` + +First things first, we need to import our trusty companions: [Altair](https://altair-viz.github.io/) for stunning visualizations, [Pandas](https://pandas.pydata.org/) for data manipulation, and [Panel](https://panel.holoviz.org) for creating interactive web apps. + +--- + +📊 **Step 1: Data Exploration and Extraction** + +```{pyodide} +BY = "t_manu" +VALUE = "p_cap" + +@pn.cache() +def get_data(): + return pd.read_csv("https://assets.holoviz.org/panel/tutorials/turbines.csv.gz") + +data = get_data() +min_year = int(data.p_year.min()) +max_year = int(data.p_year.max()) +max_capacity_by_manufacturer = data.groupby(BY)[VALUE].sum().max() +``` + +We begin by defining our data source and understanding its bounds. We extract turbine data and identify the minimum and maximum years present in the dataset, along with the maximum capacity value aggregated by manufacturer. + +--- + +🔄 **Step 2: Data Transformation and Grouping** + +```{pyodide} +def get_group_sum(year): + return ( + data[data.p_year <= year][[BY, VALUE]] + .groupby(BY) + .sum() + .sort_values(by=VALUE, ascending=False) + .reset_index() + .head(10) + ) +``` + +We create a function to aggregate the turbine data up to a specified year, grouping by manufacturer and summing the capacity values. We then select the top 10 manufacturers based on capacity. + +--- + +📈 **Step 3: Plotting Function** + +```{pyodide} +def get_plot(year): + data = get_group_sum(year) + base = ( + alt.Chart(data) + .encode( + x=alt.X(VALUE, scale=alt.Scale(domain=[0, max_capacity_by_manufacturer]), title="Capacity"), + y=alt.Y(f"{BY}:O", sort=[BY], title="Manufacturer"), + text=alt.Text(VALUE, format=",.0f"), + ) + .properties(title=str(year), height=500, width="container") + ) + return base.mark_bar() + base.mark_text(align="left", dx=2) +``` + +Time to bring our visualization to life! This function generates an Altair chart based on the grouped data for a given year, encoding capacity on the x-axis, manufacturer on the y-axis, and displaying capacity as text within the bars. + +--- + +⏩ **Step 4: Adding Interactive Controls** + +```{pyodide} +year = pn.widgets.Player( + value=max_year, + start=min_year, + end=max_year, + name="Year", + loop_policy="loop", + interval=300, + align="center", +) + +def pause_player_at_max_year(value): + if year.value==max_year: + year.pause() + +pn.bind(pause_player_at_max_year, year, watch=True) +``` + +Let's make it interactive! We introduce a [`Player`](../../reference/widgets/Player.ipynb) widget to loop continuously through the years. + +--- + +🎨 **Step 5: Binding Plot to Widget** + +```{pyodide} +plot = pn.pane.Vega(pn.bind(get_plot, year)) +``` + +Now, let's bind our plot function to the selected year. Whenever the user changes the year, the plot dynamically updates to reflect the selected timeframe. + +--- + +🖼️ **Step 6: Layout and Presentation** + +```{pyodide} +pn.Column("# Wind Turbine Capacity 1982-2022", plot, year, sizing_mode="stretch_width").servable() +``` + +Lastly, we organize our components into a neat layout. A title sets the stage, followed by our interactive plot and the year selection widget. The layout adjusts to fill the available width. + +--- + +Voilà! 🎉 You're now ready to run the code and witness the mesmerizing bar chart race showcasing the evolution of wind turbine capacities over the years. Happy exploring! 🌟 + +:::{hint} +You can create many types of animations using the object types that Panel can display. Not just bar chart races using Altair. +::: + +## Serve the App + +Now serve the app with: + +::::{tab-set} + +:::{tab-item} Script +:sync: script + +```bash +panel serve app.py --autoreload +``` + +::: + +:::{tab-item} Notebook +:sync: notebook + +```bash +panel serve app.ipynb --autoreload +``` + +::: + +:::: + +Open [http://localhost:5006/app](http://localhost:5006/app) + +Press the *play* button. + +It should resemble + + + +## Recap + +In this tutorial, we built a *bar chart race* using the [Altair](https://altair-viz.github.io) plotting library and the [`Player`](../../reference/widgets/Player.ipynb) widget. diff --git a/doc/tutorials/basic/build_chatbot.md b/doc/tutorials/basic/build_chatbot.md new file mode 100644 index 0000000000..6010296274 --- /dev/null +++ b/doc/tutorials/basic/build_chatbot.md @@ -0,0 +1,94 @@ +# Build a Chat Bot + +In this tutorial, we will build a streaming *chat bot*. We will first use the *high-level* [`ChatInterface`](../../reference/chat/ChatInterface.ipynb) to build a basic chat bot. Then we will add streaming. + +:::{note} +When we ask to *run the code* in the sections below, we may either execute the code directly in the Panel docs via the green *run* button, in a cell in a notebook, or in a file `app.py` that is served with `panel serve app.py --autoreload`. +::: + +## Build a Basic Chat Bot + +Run the code below: + +```{pyodide} +import panel as pn +from time import sleep + +pn.extension() + +def get_response(contents, user, instance): + if "turbine" in contents.lower(): + response = "A wind turbine converts wind energy into electricity." + else: + response = "Sorry, I don't know." + sleep(1) + return response + +chat_bot = pn.chat.ChatInterface(callback=get_response, max_height=500) +chat_bot.send("Ask me what a wind turbine is", user="Assistant", respond=False) +chat_bot.servable() +``` + +Try entering `What is a wind turbine?` in the *text input* and click *Send*. + +:::{note} +The `callback` function `get_response` will receive `"What is a wind turbine?"` in the `contents` argument. Since `contents` contains the word *turbine*, the chat bot will return `"A wind turbine converts wind energy into electricity."` as the `response`. +::: + +## Add Streaming + +We will now make the chat bot *stream* its response just like ChatGPT does. + +Run the code below: + +```{pyodide} +import panel as pn +from time import sleep + +pn.extension() + +def get_response(contents, user, instance): + if "turbine" in contents.lower(): + response = "A wind turbine converts wind energy into electricity." + else: + response = "Sorry, I don't know." + for index in range(len(response)): + yield response[0:index+1] + sleep(0.03) # to simulate slowish response + +chat_bot = pn.chat.ChatInterface(callback=get_response, max_height=500) +chat_bot.send("Ask me anything!", user="Assistant", respond=False) +chat_bot.servable() +``` + +Try entering `What is a wind turbine?` in the *text input* and click *Send*. + +:::{note} +The chat app is now *streaming* because we `yield` the partial `response` instead of `return`ing the full `response`. +::: + +:::{note} +To make the Streaming Chat Bot scale to many users you should be using `async`. You will learn more about scaling applications and `async` in the [Intermediate Tutorials](../intermediate/index.md). +::: + +## Learn More + +We can learn more about the `ChatInterface` via its [*reference guide*](../../reference/chat/ChatInterface.ipynb). We find the *reference guide* in the [Chat Section](https://panel.holoviz.org/reference/index.html#chat) of the [Component Gallery](../../reference/index.md). + +## Find Inspiration + +We can find more inspiration and starter templates at [Panel-Chat-Examples](https://holoviz-topics.github.io/panel-chat-examples/). + +:::{image} ../../_static/images/panel-chat-examples.png +:height: 300px +:target: https://holoviz-topics.github.io/panel-chat-examples/ +::: + +## Recap + +In this section, we have used the *easy to use*, *high-level* [`ChatInterface`](../../reference/chat/ChatInterface.ipynb) to build a streaming chat bot. + +## Resources + +- [Chat Component Gallery](https://panel.holoviz.org/reference/index.html#chat) +- [Panel-Chat-Examples](https://holoviz-topics.github.io/panel-chat-examples/) diff --git a/doc/tutorials/basic/build_dashboard.md b/doc/tutorials/basic/build_dashboard.md new file mode 100644 index 0000000000..97c7e9053d --- /dev/null +++ b/doc/tutorials/basic/build_dashboard.md @@ -0,0 +1,339 @@ +# Build a Dashboard + +Welcome to our tutorial on building an interactive dashboard showcasing essential metrics from wind turbine manufacturers. + +We'll demonstrate how to leverage a variety of components such as sliders, dropdowns, plots, indicators, tables and layouts to craft a visually stunning and functional application. + +By following along, you'll gain insights into how these components can be seamlessly combined to present data in a meaningful way. + +Our objective is to empower you to replicate this process for your own datasets and use cases, enabling you to create impactful dashboards tailored to your specific needs. + + + +Click the dropdowns below to see the requirements or the full code. + +:::::{dropdown} Requirements + +::::{tab-set} + +:::{tab-item} pip +:sync: pip + +```bash +pip install hvplot pandas panel +``` + +::: + +:::{tab-item} conda +:sync: conda + +```bash +conda install -y -c conda-forge hvplot pandas panel +``` + +::: + +:::: + +::::: + +:::{dropdown} Code + +```python +import hvplot.pandas +import pandas as pd +import panel as pn + +pn.extension("tabulator") + +ACCENT = "teal" + +styles = { + "box-shadow": "rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px", + "border-radius": "4px", + "padding": "10px", +} + +# Extract Data + +@pn.cache() # only download data once +def get_data(): + return pd.read_csv("https://assets.holoviz.org/panel/tutorials/turbines.csv.gz") + +source_data = get_data() + +# Transform Data + +min_year = int(source_data["p_year"].min()) +max_year = int(source_data["p_year"].max()) +top_manufacturers = ( + source_data.groupby("t_manu").p_cap.sum().sort_values().iloc[-10:].index.to_list() +) + +def filter_data(t_manu, year): + data = source_data[(source_data.t_manu == t_manu) & (source_data.p_year <= year)] + return data + +# Filters + +t_manu = pn.widgets.Select( + name="Manufacturer", + value="Vestas", + options=sorted(top_manufacturers), + description="The name of the manufacturer", +) +p_year = pn.widgets.IntSlider(name="Year", value=max_year, start=min_year, end=max_year) + +# Transform Data 2 + +df = pn.rx(filter_data)(t_manu=t_manu, year=p_year) +count = df.rx.len() +total_capacity = df.t_cap.sum() +avg_capacity = df.t_cap.mean() +avg_rotor_diameter = df.t_rd.mean() + +# Plot Data + +fig = ( + df[["p_year", "t_cap"]].groupby("p_year").sum() / 10**6 +).hvplot.bar( + title="Capacity Change", + rot=90, + ylabel="Capacity (MW)", + xlabel="Year", + xlim=(min_year, max_year), + color=ACCENT, +) + +# Display Data + +image = pn.pane.JPG("https://assets.holoviz.org/panel/tutorials/wind_turbines_sunset.png") + +indicators = pn.FlexBox( + pn.indicators.Number( + value=count, name="Count", format="{value:,.0f}", styles=styles + ), + pn.indicators.Number( + value=total_capacity / 1e6, + name="Total Capacity (TW)", + format="{value:,.1f}", + styles=styles, + ), + pn.indicators.Number( + value=avg_capacity/1e3, + name="Avg. Capacity (MW)", + format="{value:,.1f}", + styles=styles, + ), + pn.indicators.Number( + value=avg_rotor_diameter, + name="Avg. Rotor Diameter (m)", + format="{value:,.1f}", + styles=styles, + ), +) + +plot = pn.pane.HoloViews(fig, sizing_mode="stretch_both", name="Plot") +table = pn.widgets.Tabulator(df, sizing_mode="stretch_both", name="Table") + +# Layout Data + +tabs = pn.Tabs( + plot, table, styles=styles, sizing_mode="stretch_width", height=500, margin=10 +) + +pn.template.FastListTemplate( + title="Wind Turbine Dashboard", + sidebar=[image, t_manu, p_year], + main=[pn.Column(indicators, tabs, sizing_mode="stretch_both")], + main_layout=None, + accent=ACCENT, +).servable() +``` + +::: + +## Explanation + +### Importing Libraries + +```{pyodide} +import hvplot.pandas +import pandas as pd +import panel as pn +``` + +- `hvplot.pandas`: This library provides a high-level plotting interface for Pandas DataFrames using [HoloViews](https://holoviews.holoviz.org/). +- `pandas`: This library is used for data manipulation and analysis. +- `panel`: This is the [Panel](https://panel.holoviz.org/) library used for creating interactive dashboards and apps. + +### Extension + +```{pyodide} +pn.extension("tabulator") +``` + +- `pn.extension("tabulator")`: This line enables the Tabulator widget, which provides a high-performance interactive table widget in Panel. + +### Styles + +```{pyodide} +ACCENT = "teal" + +styles = { + "box-shadow": "rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px", + "border-radius": "4px", + "padding": "10px", +} +``` + +- `ACCENT`: This variable stores the color teal, which will be used as the accent color throughout the dashboard. +- `styles`: This dictionary contains CSS styles to be applied to various components in the dashboard for consistent styling, including box shadow, border radius, and padding. + +### Data Extraction + +```{pyodide} +@pn.cache() +def get_data(): + return pd.read_csv("https://assets.holoviz.org/panel/tutorials/turbines.csv.gz") + +source_data = get_data() +``` + +- `get_data()`: This function is decorated with `@pn.cache()`, which ensures that the data is only downloaded once, improving performance by caching the result. +- `source_data`: This variable stores the downloaded CSV data as a Pandas DataFrame. + +### Data Transformation + +```{pyodide} +min_year = int(source_data["p_year"].min()) +max_year = int(source_data["p_year"].max()) +top_manufacturers = ( + source_data.groupby("t_manu").p_cap.sum().sort_values().iloc[-10:].index.to_list() +) + +def filter_data(t_manu, year): + data = source_data[(source_data.t_manu == t_manu) & (source_data.p_year <= year)] + return data +``` + +- `min_year`, `max_year`: These variables store the minimum and maximum values of the "p_year" column in the DataFrame, representing the earliest and latest years. +- `top_manufacturers`: This variable stores the top 10 manufacturers based on the sum of their turbine capacities. +- `filter_data()`: This function filters the source data based on the selected manufacturer and year. + +### Widgets + +```{pyodide} +t_manu = pn.widgets.Select( + name="Manufacturer", + value="Vestas", + options=sorted(top_manufacturers), + description="The name of the manufacturer", +) +p_year = pn.widgets.IntSlider(name="Year", value=max_year, start=min_year, end=max_year) +``` + +- `t_manu`: This widget is a select dropdown for choosing the manufacturer. +- `p_year`: This widget is an integer slider for selecting the year. + +### Data Transformation 2 + +```{pyodide} +df = pn.rx(filter_data)(t_manu=t_manu, year=p_year) +count = df.rx.len() +total_capacity = df.t_cap.sum() +avg_capacity = df.t_cap.mean() +avg_rotor_diameter = df.t_rd.mean() +``` + +- `df`: This variable stores the filtered DataFrame based on the selected manufacturer and year. +- `count`, `total_capacity`, `avg_capacity`, `avg_rotor_diameter`: These variables calculate various statistics from the filtered DataFrame, such as count, total capacity, average capacity, and average rotor diameter. + +### Plotting Data + +```{pyodide} +fig = ( + df[["p_year", "t_cap"]].groupby("p_year").sum() / 10**6 +).hvplot.bar( + title="Capacity Change", + rot=90, + ylabel="Capacity (MW)", + xlabel="Year", + xlim=(min_year, max_year), + color=ACCENT, +) +``` + +- `fig`: This variable stores a bar plot of the total turbine capacity over the years. + +### Displaying Data + +```{pyodide} +image = pn.pane.JPG("https://assets.holoviz.org/panel/tutorials/wind_turbines_sunset.png") + +indicators = pn.FlexBox( + pn.indicators.Number( + value=count, name="Count", format="{value:,.0f}", styles=styles + ), + pn.indicators.Number( + value=total_capacity / 1e6, + name="Total Capacity (TW)", + format="{value:,.1f}", + styles=styles, + ), + pn.indicators.Number( + value=avg_capacity/1e3, + name="Avg. Capacity (MW)", + format="{value:,.1f}", + styles=styles, + ), + pn.indicators.Number( + value=avg_rotor_diameter, + name="Avg. Rotor Diameter (m)", + format="{value:,.1f}", + styles=styles, + ), +) +``` + +- `image`: This variable stores an image pane displaying a background image for the dashboard. +- `indicators`: This variable stores a collection of indicator widgets displaying various statistics. + +### Creating Components + +```{pyodide} +plot = pn.pane.HoloViews(fig, sizing_mode="stretch_both", name="Plot") +table = pn.widgets.Tabulator(df, sizing_mode="stretch_both", name="Table") +``` + +- `plot`: This variable stores a HoloViews plot pane displaying the bar plot. +- `table`: This variable stores a Tabulator widget displaying the filtered DataFrame as a table. + +### Layout + +```{pyodide} +tabs = pn.Tabs( + plot, table, styles=styles, sizing_mode="stretch_width", height=500, margin=10 +) +``` + +- `tabs`: This variable stores a Tabs widget containing the plot and table, allowing users to switch between them. + +### Creating the Dashboard + +```python +pn.template.FastListTemplate( + title="Wind Turbine Dashboard", + sidebar=[image, t_manu, p_year], + main=[pn.Column(indicators, tabs, sizing_mode="stretch_both")], + main_layout=None, + accent=ACCENT, +).servable() +``` + +- This code creates a `FastListTemplate` dashboard with a sidebar containing the image, manufacturer select dropdown, and year slider, and a main section containing the indicators and tabs. + +### Conclusion + +This code sets up a dynamic dashboard for exploring wind turbine data, including filtering, data transformation, visualization, and statistics display. It leverages various Panel widgets, HoloViews plots, and CSS styling to create an interactive and informative dashboard. diff --git a/doc/tutorials/basic/build_image_classifier.md b/doc/tutorials/basic/build_image_classifier.md new file mode 100644 index 0000000000..6ea769176a --- /dev/null +++ b/doc/tutorials/basic/build_image_classifier.md @@ -0,0 +1,201 @@ +# Build an Image Classifier + +In this tutorial, we will collaboratively build an *Image Classifier* that can detect wind turbines in images. As a team, we will support the following functionalities: + +- Uploading an image file +- Using an example image file +- Viewing the image file +- Viewing the predicted result + +We will be using a *random* classifier, but you can later replace it with your own custom classifier if you want. + +:::{note} +When we ask you to *run the code* in the sections below, you may either execute the code directly in the Panel docs via the green *run* button, in a cell in a notebook, or in a file `app.py` that is served with `panel serve app.py --autoreload`. +::: + +## Install the Dependencies + +Please ensure that [hvPlot](https://hvplot.holoviz.org) is installed. + +::::{tab-set} + +:::{tab-item} pip +:sync: pip + +``` bash +pip install hvplot panel +``` + +::: + +:::{tab-item} conda +:sync: conda + +``` bash +conda install -y -c conda-forge hvplot panel +``` + +::: + +:::: + +## Create the App + +Run the code below. + +```{pyodide} +import random +from io import BytesIO +from time import sleep + +import hvplot.pandas +import pandas as pd +import requests +from PIL import Image + +import panel as pn + +IMAGE_DIM = 350 + +pn.extension(design="material", sizing_mode="stretch_width") + +## Transformations + + +@pn.cache +def get_pil_image(url): + response = requests.get(url) + return Image.open(BytesIO(response.content)) + + +@pn.cache +def get_plot(label): + data = pd.Series(label).sort_values() + return data.hvplot.barh(title="Prediction", ylim=(0, 100)).opts(default_tools=[]) + + +## Custom Components + + +def get_label_view(fn, image: Image): + return get_plot(fn(image)) + + +def get_image_button(url, image_pane): + button = pn.widgets.Button( + width=100, + height=100, + stylesheets=[ + f"button {{background-image: url({url});background-size: cover;}}" + ], + ) + pn.bind(handle_example_click, button, url, image_pane, watch=True) + return button + + +## Event Handlers + + +def handle_example_click(event, url, image_pane): + image_pane.object = get_pil_image(url) + + +def handle_file_upload(value, image_pane): + file = BytesIO(value) + image_pane.object = Image.open(file) + + +def image_classification_interface(fn, examples): + + ## State + + image_view = pn.pane.Image( + get_pil_image(examples[0]), + height=IMAGE_DIM, + width=IMAGE_DIM, + fixed_aspect=False, + margin=0, + ) + + label_view = pn.pane.JSON() + + ## Inputs + + file_input = pn.widgets.FileInput( + accept=".png,.jpeg", + ) + pn.bind(handle_file_upload, file_input, image_view, watch=True) + file_input_component = pn.Column("### Upload Image", file_input) + + examples_input_component = pn.Column( + "### Examples", pn.Row(*(get_image_button(url, image_view) for url in examples)) + ) + + ## Views + + label_view = pn.Row( + pn.panel( + pn.bind(get_label_view, fn=fn, image=image_view.param.object), + defer_load=True, + loading_indicator=True, + height=IMAGE_DIM, + width=IMAGE_DIM, + ) + ) + + ## Layouts + + input_component = pn.Column( + "# Input", + image_view, + file_input_component, + examples_input_component, + width=IMAGE_DIM, + margin=10, + ) + + output_component = pn.Column( + "# Output", + label_view, + width=IMAGE_DIM, + margin=10, + ) + return pn.FlexBox(input_component, output_component) + + +def predict(image: Image): + # Replace with your own image classification model + sleep(1.5) + a = random.uniform(0, 100) + b = random.uniform(0, 100 - a) + c = 100 - a - b + return { + "Wind Turbine": a, + "Solar Panel": b, + "Battery Storage": c, + } + + +EXAMPLES = [ + # Replace with your own examples + "https://assets.holoviz.org/panel/tutorials/wind_turbine.png", + "https://assets.holoviz.org/panel/tutorials/solar_panel.png", + "https://assets.holoviz.org/panel/tutorials/battery_storage.png", +] + +image_classification_interface(fn=predict, examples=EXAMPLES).servable() +``` + +Try to + +- Click an example image +- Upload an `.png` or `.jpg` image file + +## Recap + +In this tutorial we have built an *Image Classifier* that can detect wind turbines. We will support + +- Uploading an image file +- Using an example image file +- Viewing the image file +- Viewing the predicted result diff --git a/doc/tutorials/basic/build_monitoring_dashboard.md b/doc/tutorials/basic/build_monitoring_dashboard.md new file mode 100644 index 0000000000..df7515410a --- /dev/null +++ b/doc/tutorials/basic/build_monitoring_dashboard.md @@ -0,0 +1,266 @@ +# Build a Monitoring Dashboard + +In this tutorial, we will construct a *display-only* dashboard that refreshes the entire page periodically. + +These types of dashboards are suitable for displaying on a large screen running continuously, such as in a call center, control room, or trading area. + +![Monitoring Dashboard](https://assets.holoviz.org/panel/tutorials/build-monitoring-dashboard.gif) + +:::::{dropdown} Dependencies + +```bash +panel scipy +``` + +::::: + +:::::{dropdown} Code + +```python +import numpy as np +import panel as pn +from scipy.interpolate import interp1d + +pn.extension() + +ACCENT = "teal" + +WIND_SPEED_STD_DEV = 2.0 +WIND_SPEED_MEAN = 8.0 + +WIND_SPEEDS = np.array([0, 3, 6, 9, 12, 15, 18, 21]) # Wind speed (m/s) +POWER_OUTPUTS = np.array([0, 39, 260, 780, 1300, 1300, 0, 0]) # Power output (MW) + +# Extract Data + +def get_wind_speed(): + # Replace with your wind speed source + return round(np.random.normal(WIND_SPEED_MEAN, WIND_SPEED_STD_DEV), 1) + +wind_speed = get_wind_speed() + +# Transform Data + +def get_power_output(wind_speed): + # Replace with your power output calculation + power_interpolation = interp1d( + WIND_SPEEDS, POWER_OUTPUTS, kind="linear", fill_value="extrapolate" + ) + return np.round(power_interpolation(wind_speed), 1) + +power_output = get_power_output(wind_speed) + +# View Data + +wind_speed_view = pn.indicators.Number( + name="Wind Speed", + value=wind_speed, + format="{value} m/s", + colors=[(10, ACCENT), (100, "red")], +) +power_output_view = pn.indicators.Number( + name="Power Output", + value=power_output, + format="{value} MW", + colors=[ + (power_output, ACCENT), + (max(POWER_OUTPUTS), "red"), + ], +) +indicators = pn.FlexBox(wind_speed_view, power_output_view) + +# Layout and style with template + +pn.template.FastListTemplate( + title="WTG Monitoring Dashboard", + main=[indicators], + accent=ACCENT, + main_layout=None, + theme="dark", + theme_toggle=False, + meta_refresh="2", # Automatically refresh every 2 seconds +).servable(); # The ; is only needed if used in a notebook +``` + +::::: + +## Install the dependencies + +Please ensure that [Panel](https://panel.holoviz.org) and [SciPy](https://scipy.org/) are installed. + +::::{tab-set} + +:::{tab-item} pip +:sync: pip + +```bash +pip install panel scipy +``` + +::: + +:::{tab-item} conda +:sync: conda + +```bash +conda install -y -c conda-forge panel scipy +``` + +::: + +:::: + +## Explanation + +Let's dissect the code that brings our dashboard to life: + +```{pyodide} +import numpy as np +import panel as pn +from scipy.interpolate import interp1d +``` + +We import the necessary libraries: [NumPy](https://numpy.org/) for numerical computations, [Panel](https://panel.holoviz.org) for building interactive web apps, and [SciPy](https://scipy.org/) for interpolation. + +```{pyodide} +pn.extension() +``` + +We load the JavaScript dependencies required by our app. + +```{pyodide} +ACCENT = "teal" + +WIND_SPEED_STD_DEV = 2.0 +WIND_SPEED_MEAN = 8.0 + +WIND_SPEEDS = np.array([0, 3, 6, 9, 12, 15, 18, 21]) # Wind speed (m/s) +POWER_OUTPUTS = np.array([0, 39, 260, 780, 1300, 1300, 0, 0]) # Power output (MW) +``` + +We set some constants related to wind speed and power output data. + +```{pyodide} +def get_wind_speed(): + return round(np.random.normal(WIND_SPEED_MEAN, WIND_SPEED_STD_DEV), 1) + +wind_speed = get_wind_speed() +``` + +A function `get_wind_speed()` generates a random wind speed value based on a normal distribution. + +```{pyodide} +def get_power_output(wind_speed): + power_interpolation = interp1d( + WIND_SPEEDS, POWER_OUTPUTS, kind="linear", fill_value="extrapolate" + ) + return np.round(power_interpolation(wind_speed), 1) + +power_output = get_power_output(wind_speed) +``` + +Another function `get_power_output(wind_speed)` calculates the corresponding power output based on the provided wind speed using linear interpolation. + +```{pyodide} +wind_speed_view = pn.indicators.Number( + name="Wind Speed", + value=wind_speed, + format="{value} m/s", + colors=[(10, ACCENT), (100, "red")], +) +power_output_view = pn.indicators.Number( + name="Power Output", + value=power_output, + format="{value} MW", + colors=[ + (power_output, ACCENT), + (max(POWER_OUTPUTS), "red"), + ], +) +indicators = pn.FlexBox(wind_speed_view, power_output_view) +indicators +``` + +We create [`Number`](../../reference/indicators/Number.ipynb) indicators to display the wind speed and power output. The [`FlexBox`](../../reference/layouts/FlexBox.ipynb) layout will arrange the indicators horizontally or vertically depending on the screen size. + +::::{tab-set} + +:::{tab-item} Script +:sync: script + +```python +pn.template.FastListTemplate( + title="WTG Monitoring Dashboard", + main=[indicators], + accent=ACCENT, + main_layout=None, + theme="dark", + theme_toggle=False, + meta_refresh="2", # Automatically refresh every 2 seconds +).servable() +``` + +::: + +:::{tab-item} Notebook +:sync: notebook + +```python +pn.template.FastListTemplate( + title="WTG Monitoring Dashboard", + main=[indicators], + accent=ACCENT, + main_layout=None, + theme="dark", + theme_toggle=False, + meta_refresh="2", # Automatically refresh every 2 seconds +).servable(); # The ; is only needed if used in a notebook +``` + +::: + +:::: + +Finally, we construct the dashboard using the [`FastListTemplate`](../../reference/templates/FastListTemplate.ipynb). We set the accent color, theme, and enable automatic refreshing every 2 seconds. + +:::{note} +In the example, we use a `meta_refresh` rate of 2 for illustration purposes. For real use cases, we recommend `meta_refresh` rates of 15 or above. For lower refresh rates we would be using a [*Periodic Callback*](../../how_to/callbacks/periodic.md) or [*generators*](../../how_to/interactivity/bind_generators.md) in combination with a `meta_refresh` rate of 900 or above. +::: + +## Serve the App + +Now serve the app with: + +::::{tab-set} + +:::{tab-item} Script +:sync: script + +```bash +panel serve app.py --autoreload +``` + +::: + +:::{tab-item} Notebook +:sync: notebook + +```bash +panel serve app.ipynb --autoreload +``` + +::: + +:::: + +Open [http://localhost:5006/app](http://localhost:5006/app) + +It should resemble + +![Monitoring Dashboard](https://assets.holoviz.org/panel/tutorials/build-monitoring-dashboard.gif) + +## Recap + +In this tutorial, we have built a basic monitoring dashboard that *refreshes* the entire page periodically. We have used `pn.panel`, the [`Number`](../../reference/indicators/Number.ipynb) *indicator* and the [`FastListTemplate`](../../reference/templates/FastListTemplate.ipynb). + +We used the `meta_refresh` argument of the [FastListTemplate](../../reference/templates/FastListTemplate.ipynb) to automatically *refresh* the dashboard periodically. diff --git a/doc/tutorials/basic/build_report.md b/doc/tutorials/basic/build_report.md new file mode 100644 index 0000000000..20aef95472 --- /dev/null +++ b/doc/tutorials/basic/build_report.md @@ -0,0 +1,409 @@ +# Build a Report + +In this section, we will collaboratively create a *Wind Turbine Report*. Together, we will: + +- Layout and style the report nicely. +- Export the report to a static `.html` file using the `.save` method. +- Distribute the report via email. + + + +:::::{dropdown} Dependencies + +```bash +altair panel +``` + +::::: + +:::::{dropdown} Code + +```python +import altair as alt +import pandas as pd +import panel as pn + +pn.extension("vega", sizing_mode="stretch_width") + +# Extract Data + +TEXT ="""# Wind Turbine + +A wind turbine is a device that converts the kinetic energy of wind into \ +[electrical energy](https://en.wikipedia.org/wiki/Electrical_energy). + +The most visible part of a wind turbine is its *rotor*, which typically consists of two or three long *blades* attached to a central hub. These blades are meticulously designed to efficiently capture the energy of the wind as it passes through them. Through careful aerodynamic engineering, the shape, length, and angle of the blades are optimized to maximize the amount of kinetic energy they can extract from the wind. + +As the wind blows, it causes the rotor blades to rotate. This rotational motion is transferred to a generator housed within the turbine's nacelle, a large enclosure situated atop a tall tower. The generator converts the mechanical energy of the rotating blades into electrical energy through the principles of electromagnetic induction. This electricity is then transmitted via cables down the tower and into the electrical grid for distribution to homes, businesses, and industries. + +The height of the tower plays a crucial role in the efficiency of a wind turbine. By elevating the rotor assembly high above the ground, turbines can access stronger and more consistent wind speeds, which translates to higher energy production. Taller towers also help minimize the impact of surface friction and turbulence near the ground, allowing the rotor blades to operate more smoothly and efficiently. + +Read more [here](https://en.wikipedia.org/wiki/Wind_turbine). +""" + +@pn.cache +def get_data(): + return pd.read_csv("https://assets.holoviz.org/panel/tutorials/turbines.csv.gz") + +df = get_data() + +# Transform Data + +count = len(df) +total_capacity = df.t_cap.sum() +avg_capacity = df.t_cap.mean() / 10**3 +avg_rotor_diameter = df.t_rd.mean() +top_manufacturers = ( + df.groupby("t_manu").p_cap.sum().sort_values().iloc[-10:].index.to_list() +) +example_df = df.dropna().sample(5).iloc[:5,:13].reset_index(drop=True) + +# Plot Data + +df = df[df.t_manu.isin(top_manufacturers)] +fig = ( + alt.Chart( + df.sample(5000), + title="Capacity by Manufacturer", + ) + .mark_circle(size=8) + .encode( + y="t_manu:N", + x="p_cap:Q", + yOffset="jitter:Q", + color=alt.Color("t_manu:N").legend(None), + tooltip=["t_manu", "p_cap"], + ) + .transform_calculate(jitter="sqrt(-2*log(random()))*cos(2*PI*random())") + .properties( + height=400, + width="container", + ) +) + +## Build components: display, layout, and style the objects + +BRAND_COLOR = "teal" +BRAND_TEXT_ON_COLOR = "white" + +CARD_STYLE = { + "box-shadow": "rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px", + "padding": "10px", + "border-radius": "5px" +} + +header = pn.Row( + pn.pane.Markdown( + "# Wind Turbine Report", styles={"color": BRAND_TEXT_ON_COLOR}, margin=(5, 20) + ), + styles={"background": BRAND_COLOR}, +) +indicators = pn.FlexBox( + pn.indicators.Number( + value=total_capacity / 1e6, + name="Total Capacity (TW)", + format="{value:,.0f}", + styles=CARD_STYLE, + ), + pn.indicators.Number( + value=avg_capacity, + name="Avg. Capacity (MW)", + format="{value:,.1f}", + styles=CARD_STYLE, + ), + pn.indicators.Number( + value=avg_rotor_diameter, + name="Avg. Rotor Diameter (m)", + format="{value:,.0f}", + styles=CARD_STYLE, + ), + pn.indicators.Number( + value=avg_rotor_diameter, + name="Avg. Rotor Diameter (m)", + format="{value:,.0f}", + styles=CARD_STYLE, + ), + pn.indicators.Number( + value=count, + name="Count", + format="{value:,.0f}", + styles=CARD_STYLE, + ), + margin=(20, 5), +) + +plot = pn.pane.Vega( + fig, + styles=CARD_STYLE, + margin=10, +) + +table = pn.pane.DataFrame(example_df, styles=CARD_STYLE) + +## Put the components together + +main = pn.Column( + "# Summary", + indicators, + TEXT, + "## Manufacturer Capacities", + plot, + "## Turbine Examples", + table, +) + +main_container = pn.Row( + main, + max_width=1024, + styles={"margin-right": "auto", "margin-left": "auto", "margin-top": "10px", "margin-bottom": "20px"}, +) +report = pn.Column(header, main_container) + +## Export and save it + +report.save("report.html") + +report.servable() # Added such that the report can be served for development +``` + +::::: + +## Install the dependencies + +Please ensure that [Altair](https://altair-viz.github.io/) and [Panel](https://panel.holoviz.org) are installed. + +::::{tab-set} + +:::{tab-item} pip +:sync: pip + +```bash +pip install altair panel +``` + +::: + +:::{tab-item} conda +:sync: conda + +```bash +conda install -y -c conda-forge altair panel +``` + +::: + +:::: + +## Explanation + +Let's break down and explain the code: + +```{pyodide} +import altair as alt +import pandas as pd +import panel as pn +``` + +Here, we import the necessary libraries: [Altair](https://altair-viz.github.io/) for data visualization, [Pandas](https://pandas.pydata.org/) for data manipulation, and [Panel](https://panel.holoviz.org) for creating interactive web apps. + +```{pyodide} +pn.extension("vega", sizing_mode="stretch_width") +``` + +Additionally we load the `"vega"` renderer, which enables rendering Altair plots, and set the sizing mode to `"stretch_width"` to make sure the content fills the available horizontal space. + +```{pyodide} +TEXT ="""# Wind Turbine + +A wind turbine is a device that converts the kinetic energy of wind into \ +[electrical energy](https://en.wikipedia.org/wiki/Electrical_energy). + +The most visible part of a wind turbine is its *rotor*, which typically consists of two or three long *blades* attached to a central hub. These blades are meticulously designed to efficiently capture the energy of the wind as it passes through them. Through careful aerodynamic engineering, the shape, length, and angle of the blades are optimized to maximize the amount of kinetic energy they can extract from the wind. + +As the wind blows, it causes the rotor blades to rotate. This rotational motion is transferred to a generator housed within the turbine's nacelle, a large enclosure situated atop a tall tower. The generator converts the mechanical energy of the rotating blades into electrical energy through the principles of electromagnetic induction. This electricity is then transmitted via cables down the tower and into the electrical grid for distribution to homes, businesses, and industries. + +The height of the tower plays a crucial role in the efficiency of a wind turbine. By elevating the rotor assembly high above the ground, turbines can access stronger and more consistent wind speeds, which translates to higher energy production. Taller towers also help minimize the impact of surface friction and turbulence near the ground, allowing the rotor blades to operate more smoothly and efficiently. + +Read more [here](https://en.wikipedia.org/wiki/Wind_turbine). +""" + +@pn.cache +def get_data(): + return pd.read_csv("https://assets.holoviz.org/panel/tutorials/turbines.csv.gz") + +df = get_data() +``` + +We define a multiline string `TEXT` containing some markdown text describing wind turbines. Then, we define a function `get_data()` decorated with `@pn.cache` to cache the data retrieval operation. This function reads a CSV file containing turbine data from a URL and returns a Pandas DataFrame, which we store in the variable `df`. + +```{pyodide} +count = len(df) +total_capacity = df.t_cap.sum() +avg_capacity = df.t_cap.mean() / 10**3 +avg_rotor_diameter = df.t_rd.mean() +top_manufacturers = ( + df.groupby("t_manu").p_cap.sum().sort_values().iloc[-10:].index.to_list() +) +example_df = df.dropna().sample(5).iloc[:5,:13].reset_index(drop=True) +``` + +We calculate various summary statistics from the dataset, such as the total count of turbines, the total capacity, the average capacity (converted to MW), the average rotor diameter, and a list of top manufacturers by total capacity. Additionally, we create an example DataFrame (`example_df`) containing a random sample of turbine data to display in a table later. + +```{pyodide} +df = df[df.t_manu.isin(top_manufacturers)] +fig = ( + alt.Chart( + df.sample(5000), + title="Capacity by Manufacturer", + ) + .mark_circle(size=8) + .encode( + y="t_manu:N", + x="p_cap:Q", + yOffset="jitter:Q", + color=alt.Color("t_manu:N").legend(None), + tooltip=["t_manu", "p_cap"], + ) + .transform_calculate(jitter="sqrt(-2*log(random()))*cos(2*PI*random())") + .properties( + height=400, + width="container", + ) +) +``` + +We filter the dataset to include only the turbines from the top manufacturers. Then, we create an Altair scatter plot (`fig`) to visualize the capacity of turbines by manufacturer. We encode the manufacturer on the y-axis, the capacity on the x-axis, and use jitter to prevent overplotting. The tooltip shows the manufacturer and capacity when hovering over data points. + +```{pyodide} +BRAND_COLOR = "teal" +BRAND_TEXT_ON_COLOR = "white" + +CARD_STYLE = { + "box-shadow": "rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px", + "padding": "10px", + "border-radius": "5px" +} + +header = pn.Row( + pn.pane.Markdown( + "# Wind Turbine Report", styles={"color": BRAND_TEXT_ON_COLOR}, margin=(5, 20) + ), + styles={"background": BRAND_COLOR}, +) +``` + +Here, we define some styling constants and create a header for the report, which consists of a Markdown title styled with the brand color and a teal background. + +```{pyodide} +indicators = pn.FlexBox( + pn.indicators.Number( + value=total_capacity / 1e6, + name="Total Capacity (TW)", + format="{value:,.0f}", + styles=CARD_STYLE, + ), + pn.indicators.Number( + value=avg_capacity, + name="Avg. Capacity (MW)", + format="{value:,.1f}", + styles=CARD_STYLE, + ), + pn.indicators.Number( + value=avg_rotor_diameter, + name="Avg. Rotor Diameter (m)", + format="{value:,.0f}", + styles=CARD_STYLE, + ), + pn.indicators.Number( + value=avg_rotor_diameter, + name="Avg. Rotor Diameter (m)", + format="{value:,.0f}", + styles=CARD_STYLE, + ), + pn.indicators.Number( + value=count, + name="Count", + format="{value:,.0f}", + styles=CARD_STYLE, + ), + margin=(20, 5), +) + +plot = pn.pane.Vega( + fig, + styles=CARD_STYLE, + margin=10, +) +table = pn.pane.DataFrame(example_df, styles=CARD_STYLE) +``` + +We create various components for the report, including a set of indicators (total capacity, average capacity, etc.), a Vega plot (`plot`) displaying the capacity by manufacturer, and a DataFrame (`table`) showing example turbine data. + +```{pyodide} +main = pn.Column( + "# Summary", + indicators, + TEXT, + "## Manufacturer Capacities", + plot, + "## Turbine Examples", + table, +) +``` + +We assemble the main content of the report (`main`) as a Column layout, including the summary section, the indicators, the Markdown text, the plot, and the table. + +```{pyodide} +main_container = pn.Row( + main, + max_width=1024, + styles={"margin-right": "auto", "margin-left": "auto", "margin-top": "10px", "margin-bottom": "20px"}, +) +report = pn.Column(header, main_container) +report +``` + +We create a container (`main_container`) to hold the main content with a maximum width and some margin styling. Then, we compose the entire report (`report`) by combining the header and the main container. + +```python +report.save("report.html") +``` + +Finally, we save the report as an HTML file named "report.html". This HTML file will contain the rendered content of the report, including the header, indicators, text, plot, and table. + +:::{note} +With `.save` you can produce *static* reports that display content and have very limited interactivity. It is possible to add more interactivity. Check out the [*Embed State*](../../how_to/export/embedding.md) or [Convert Panel Applications](../../how_to/wasm/convert.md) guides for more detail. +::: + +## Run the Code + +Run the code with `python app.py`. + +Please verify that the file `report.html` has been created. + +Please open the `report.html` file in your browser. It should look like: + + + +## Distribute the report + +Attach the `report.html` file generated in the previous section to an email and send it to yourself. When you receive the email, then open the `report.html` attachment. + +🥳 Congrats! Together, we have successfully created and distributed an interactive report based on DataFrames and one of our favorite plotting libraries. This is an amazing achievement! + +## Recap + +In this section we have built a *Wind Turbine Report* with a nice user experience, exported it to a `.html` file using the `.save` method and distributed it via email. + +## Resources + +### How-to + +- [Embed State](../../how_to/export/embedding.md) +- [Save app to file](../../how_to/export/saving.md) diff --git a/doc/tutorials/basic/build_streaming_dashboard.md b/doc/tutorials/basic/build_streaming_dashboard.md new file mode 100644 index 0000000000..c34e9d5fe5 --- /dev/null +++ b/doc/tutorials/basic/build_streaming_dashboard.md @@ -0,0 +1,121 @@ +# Build Streaming Dashboard + +In this tutorial, we come together to create a simple streaming dashboard to monitor the wind speed and power output of one of our wind turbines: + +- We will use `pn.state.add_periodic_callback` to trigger a task to run on a schedule. + +:::{note} +When we ask to *run the code* in the sections below, you may either execute the code directly in the Panel docs via the green *run* button, in a cell in a notebook, or in a file `app.py` that is served with `panel serve app.py --autoreload`. +::: + +## Install the Dependencies + +Please ensure that [SciPy](https://scipy.org/) is installed. + +::::{tab-set} + +:::{tab-item} conda +:sync: conda + +``` bash +conda install -y -c conda-forge panel scipy +``` + +::: + +:::{tab-item} pip +:sync: pip + +``` bash +pip install panel scipy +``` + +::: + +:::: + +## Build the App + +Run the code below: + +```{pyodide} +import numpy as np +import panel as pn +from scipy.interpolate import interp1d + +pn.extension() + +WIND_SPEEDS = np.array([0, 3, 6, 9, 12, 15, 18, 21]) # Wind speed (m/s) +WIND_SPEED_MEAN = 8 +WIND_SPEED_STD_DEV = 0.5 +POWER_OUTPUTS = np.array([0, 0.03, 0.20, 0.6, 1.0, 1.0, 0, 0]) # Power output (MW) + +# Define State + +wind_speed = pn.rx(8.0) + +## Extract Data + +def get_wind_speed(): + # Replace with your own wind speed source + return round( + np.random.normal(WIND_SPEED_MEAN, WIND_SPEED_STD_DEV), 1 + ) + +## Transform Data + +power_interpolation = interp1d( + WIND_SPEEDS, POWER_OUTPUTS, kind="linear", fill_value="extrapolate" +) + +def get_power_output(wind_speed): + return np.round(power_interpolation(wind_speed), 2) + +## View Data + +wind_speed_view = pn.indicators.Number( + name="Wind Speed", + value=wind_speed, + format="{value} m/s", + colors=[(10, "green"), (100, "red")], +) +power_output_view = pn.indicators.Number( + name="Power Output", + value=wind_speed.rx.pipe(get_power_output), + format="{value} MW", + colors=[(10, "green"), (100, "red")], +) + +# Update data periodically + +def update_wind_speed(): + wind_speed.rx.value = get_wind_speed() + +pn.state.add_periodic_callback(update_wind_speed, period=1000) + +# Layout the app + +pn.Column( + "# WTG Monitoring Dashboard", + pn.FlexBox(wind_speed_view, power_output_view), +).servable() +``` + +Try changing the `period` from `1000` to `100`. + +:::{note} +The code refers to + +- `wind_speed = pn.rx(8.0)`: This is a *reactive expression* with an initial value of 8.0. The UI updates whenever the value `wind_speed.rx.value` is changed. +- `pn.state.add_periodic_callback(update_wind_speed, period=1000)`: This updates the `wind_speed_rx.value` every 1000 milliseconds. +::: + +## Resources + +### How-to + +- [Periodically Run Callbacks](../../how_to/callbacks/periodic.md) + +### App Gallery + +- [Streaming VideoStream](../../gallery/index.md) diff --git a/doc/tutorials/basic/build_todo.md b/doc/tutorials/basic/build_todo.md new file mode 100644 index 0000000000..0e06088806 --- /dev/null +++ b/doc/tutorials/basic/build_todo.md @@ -0,0 +1,167 @@ +# Build a Todo App + +In this section, we will work on building a *Todo App* together so that our wind turbine technicians can keep track of their tasks. As a team, we will collaborate to create an app that provides the following functionality: + +- Adding, removing, and clearing all tasks +- Marking a task as solved +- Keeping track of the number of completed tasks +- Disabling or hiding buttons when necessary + +:::{note} +When we ask everyone to *run the code* in the sections below, you may either execute the code directly in the Panel docs via the green *run* button, in a cell in a notebook, or in a file `app.py` that is served with `panel serve app.py --autoreload`. +::: + + + +:::{dropdown} Requirements + +```bash +panel +``` + +::: + +:::{dropdown} Code + +```python +import panel as pn + +pn.extension(sizing_mode="stretch_width", design="material") + +BUTTON_WIDTH = 125 + +# We use intslider to avoid teaching users pn.rx. Is that a good thing? +state_changed_count = pn.widgets.IntInput() +tasks = pn.Column() + +def update_state_changed_count(*args): + state_changed_count.value += 1 + +def remove_task(task, *args): + index = tasks.index(task) + tasks.pop(index) + +def remove_all_tasks(*args): + tasks.clear() + +def create_task(text): + state = pn.widgets.Checkbox(align="center", sizing_mode="fixed") + content = pn.pane.Markdown(text) + remove = pn.widgets.Button(width=BUTTON_WIDTH, icon="trash", sizing_mode="fixed") + task = pn.Row(state, content, remove, sizing_mode="stretch_width") + + pn.bind(remove_task, task, remove, watch=True) + # We have to bind the below after the above! + pn.bind(update_state_changed_count, state, remove, watch=True) + + return task + +def add_task(text, *args): + if not text: + return + + new_task = create_task(text) + tasks.append(new_task) + + return tasks + +def get_state(*args): + total_tasks = len(tasks) + completed_tasks = sum(check[0].value for check in tasks) + return f"{completed_tasks} of {total_tasks} tasks completed" + +def can_add(value_input): + return not bool(value_input) + +def has_tasks(*args): + return len(tasks) > 0 + + +add_task("Inspect the blades") +add_task("Inspect the nacelle") +add_task("Tighten the bolts") + +text_input = pn.widgets.TextInput(name="Task", placeholder="Enter a task") + +submit_task = pn.widgets.Button( + name="Add", + align="center", + button_type="primary", + width=BUTTON_WIDTH, + sizing_mode="fixed", + disabled=pn.bind(can_add, text_input.param.value_input) +) +clear = pn.widgets.Button( + name="Remove All", + button_type="primary", + button_style="outline", + width=BUTTON_WIDTH, + sizing_mode="fixed", + visible=pn.bind(has_tasks, state_changed_count) +) + +def reset_text_input(*args): + text_input.value = text_input.value_input = "" + +pn.bind(add_task, text_input, submit_task, watch=True) +pn.bind(reset_text_input, text_input, submit_task, watch=True) +pn.bind(remove_all_tasks, clear, watch=True) +# We have to bind the below after the above! +pn.bind(update_state_changed_count, text_input, submit_task, clear, watch=True) + +status_report = pn.bind(get_state, state_changed_count, tasks.param.objects) + +pn.Column( + "## WTG Task List", + status_report, + pn.Row(text_input, submit_task), + tasks, + pn.Row(pn.Spacer(), clear), + max_width=500, +).servable() +``` + +::: + +## Install the Requirements + +::::{tab-set} + +:::{tab-item} pip +:sync: pip + +```bash +pip install panel +``` + +::: + +:::{tab-item} conda +:sync: conda + +```bash +conda install -y -c conda-forge panel +``` + +::: + +:::: + +## Explanation + +COMING UP + +Let's perform the following actions: + +- Remove one task +- Remove all tasks +- Add 3 Tasks +- Check 1 of 3 tasks and see the `status_message` update accordingly. + +:::{note} +A todo app can be built in many other ways. The main purpose of this example is for all of us to acquire the basic skills needed to develop *stateful*, *dynamically updating* apps like this one. +::: + +## Recap + +In this section, we have built a *Todo App* with many features. We needed to combine many of the things we have learned so far. diff --git a/doc/tutorials/basic/caching.md b/doc/tutorials/basic/caching.md new file mode 100644 index 0000000000..f1b4251a34 --- /dev/null +++ b/doc/tutorials/basic/caching.md @@ -0,0 +1,217 @@ +# Optimize Performance with Caching + +Caching is a powerful technique that not only accelerates your applications but also conserves computational resources by storing and reusing valuable computations. Let's dive into how you can leverage caching in your Panel apps to enhance their speed and efficiency. + +## Harness the Power of Caching + +With `pn.cache`, you can effortlessly cache function results, unlocking a plethora of performance benefits for your Panel apps. + +:::{note} +As you proceed through the sections below, feel free to execute the code directly in the Panel docs using the handy green *run* button, in a notebook cell, or within a Python file `app.py` served with `panel serve app.py --autoreload`. +::: + +## Grasp Panel Application Execution + +Before delving into caching, it's crucial to comprehend how Panel applications execute code. Let's gain some insights into this process. + +Create a file named `external_module.py` with the following content: + +```python +print("running external_module.py") + +# This object is defined once and shared across all sessions (i.e., all users) +external_data = {"__name__": __name__} +``` + +Next, create a file named `app.py` with the following code: + +```python +print("running app.py") +import panel as pn + +from external_module import external_data + +pn.extension() + +# This object is defined each time the app loads and is shared only within that session +data = {"__name__": __name__} + +pn.Column( + "## External Module", external_data, f"Object id: {id(external_data)}", + "## App", data, f"Object id: {id(data)}", +).servable() +``` + +Now, execute the following command in your terminal: + +```bash +panel serve app.py +``` + +Open the app in your browser and refresh it a few times. + +It should look like + + + +:::{note} + +- Imported modules are executed once when they are first imported. Objects defined in these modules are shared across all user sessions. +- The `app.py` script is executed each time the app is loaded. Objects defined here are shared within the single user session only (unless cached). +- Only specific, bound functions are re-executed upon user interactions, not the entire `app.py` script. + +::: + +### Exercise: Enable `--autoreload` + +Try repeating the aforementioned steps with `--autoreload`. + +```bash +panel serve app.py --autoreload +``` + +Observe the changes. + +:::{dropdown} Solution + +With `--autoreload`, both files are executed when the server starts and before the page is loaded for the first time. + + + +::: + +## Enhance Loading Speed with Caching + +Now, let's explore how caching can dramatically improve the loading speed of your Panel apps. + +Execute the code snippet below: + +```{pyodide} +from datetime import datetime +from time import sleep + +import panel as pn + +pn.extension() + + +def get_data(): + print("loading data ...") + sleep(2) + return {"last_update": str(datetime.now())} + + +data = get_data() + +pn.pane.JSON(data).servable() +``` + +Refresh the browser a few times and observe the loading time. + +### Exercise: Implement Caching + +Let's apply caching to the `get_data` function by decorating it with the `@pn.cache` decorator. + +Observe in the terminal that the `data` is loaded only once when the server starts. + +Try loading and refreshing the app several times to experience the improved performance. + +:::::{dropdown} Solution + +```{pyodide} +from datetime import datetime +from time import sleep + +import panel as pn + +pn.extension() + +@pn.cache +def get_data(): + print("loading data ...") + sleep(2) + return {"last_update": str(datetime.now())} + + +data = get_data() + +pn.pane.JSON(data).servable() +``` + +::::: + +If the `get_data` function is provided externally, you can utilize `pn.cache` as a function instead. + +Try using `pn.cache` as a function instead of a decorator. + +:::::{dropdown} Solution + +```{pyodide} +from datetime import datetime +from time import sleep + +import panel as pn + +pn.extension() + +def get_data(): + print("loading data ...") + sleep(2) + return {"last_update": str(datetime.now())} + +get_data_cached = pn.cache(get_data) + +data = get_data_cached() + +pn.pane.JSON(data).servable() +``` + +::::: + +## Maximize Efficiency by Reusing Function Results + +Execute the following code snippet: + +```{pyodide} +from time import sleep + +import panel as pn + +pn.extension() + +@pn.cache +def algo(value): + print(f"calculating {value}") + sleep(1) + return value + +slider = pn.widgets.IntSlider(name="Value", value=2, start=0, end=10) +pn.Column( + slider, pn.bind(algo, slider) +).servable() +``` + +Drag the slider and notice the initial slow response of the app, which progressively becomes instantaneous as the cache is populated. + +:::{tip} +For further optimization, explore fine-tuning options for `pn.cache` in the [Automatically Cache](../../how_to/caching/memoization.md) guide. +::: + +## Recap + +In this section, we've learned how to significantly enhance the performance of Panel apps through caching: + +- Leveraged `pn.cache` to cache function results efficiently. + +## Resources + +### How-to Guides + +- [Automatically Cache](../../how_to/caching/memoization.md) +- [Manually Cache](../../how_to/caching/manual.md) +- [Migration from Streamlit: Adding Caching](../../how_to/streamlit_migration/caching.md) + +Keep exploring and optimizing your Panel apps for maximum efficiency! 🚀 diff --git a/doc/tutorials/basic/deploy.md b/doc/tutorials/basic/deploy.md new file mode 100644 index 0000000000..e7beda37dc --- /dev/null +++ b/doc/tutorials/basic/deploy.md @@ -0,0 +1,114 @@ +# Deploy a Dashboard + +Let's take your dashboard to the next level and get it into the hands of your users. We'll deploy it to [Hugging Face Spaces](https://huggingface.co/spaces?sort=trending&search=panel) to make it easily accessible. + +## Prerequisites + +Before we begin, ensure that you've signed up for [Hugging Face](https://huggingface.co/) and logged into your account. If not, simply click the "Sign Up" button on the [Hugging Face](https://huggingface.co/spaces?sort=trending&search=panel) page and follow the instructions to create your account. + +## Clone the Project + +First things first, let's clone the project: + +1. Open [panel-Org/build_dashboard](https://huggingface.co/spaces/panel-Org/build_dashboard). +2. Click the three vertical dots, then select "Duplicate this Space". + +[![Duplicate Space](../../_static/images/hugging_face_spaces_duplicate.png)](https://huggingface.co/spaces/panel-Org/build_dashboard) + +A new form will pop up. Change the "Visibility" to "Public" if you want to share it with the world. + +![Duplicate Space Form](../../_static/images/hugging_face_spaces_duplicate_form.png) + +Now, click "Duplicate Space". + +Hugging Face will now start building your image, which should take less than a minute. + +Once done, you'll have your own copy of the dashboard running. + +## Check the Files + +Let's take a quick look at the files tab: + +[![Files Tab](../../_static/images/hugging_face_spaces_files.png)](https://huggingface.co/spaces/Panel-Org/build_dashboard/tree/main) + +The "Files Tab" makes it incredibly easy to update your project: + +- **+ Add File**: Use this option to add or update files via drag and drop. +- **File**: Click on any file and then hit "Edit" to make changes. Remember to "Commit changes" when you're done. + +### Check the Dockerfile + +Take a look at the `Dockerfile`. It should look something like this: + +```bash +FROM python:3.11 + +WORKDIR /code + +COPY ./requirements.txt /code/requirements.txt +RUN python3 -m pip install --no-cache-dir --upgrade pip +RUN python3 -m pip install --no-cache-dir --upgrade -r /code/requirements.txt + +COPY . . + +CMD ["panel", "serve", "/code/app.py", "--address", "0.0.0.0", "--port", "7860", "--allow-websocket-origin", "*", "--num-procs", "2", "--num-threads", "0", "--index", "app"] + +RUN mkdir /.cache +RUN chmod 777 /.cache +``` + +:::{note} +Here's a breakdown of the `Dockerfile`: + +- `FROM python:3.11`: Starts from a Python 3.11 image. +- `WORKDIR /code`: Executes commands from the `/code` folder. +- `pip install ...`: Installs the requirements. +- `COPY . .`: Copies the repository files to the current `WORKDIR`. +- `CMD [...]`: Runs the specified command when the Docker container starts. +- `"--address", "0.0.0.0", "--port", "7860"`: Serves the app at `http://0.0.0.0:7860/app`. +- `"--allow-websocket-origin", "*"`: Allows requests from any domain, including `huggingface.co`. +- `"--num-procs", "2"`: Starts 2 server processes to handle users in parallel. +- `"--num-threads", "0"`: Uses threading to execute callbacks and bound functions concurrently. +- `"--index", "app"`: Also serves the app at `http://0.0.0.0:7860`. +::: + +## Delete Your Space + +If your dashboard isn't intended for real usage, please consider deleting it to conserve resources. + +Navigate to the "Settings" tab, scroll to the bottom of the page, and follow the instructions to "Delete this space". + +![Delete Space](../../_static/images/hugging_face_spaces_delete.png) + +## 🥳 Congrats: You're a Hero! + +You've now acquired the basic skills required to build a wide range of Panel apps. You are now a "Panel Hero"! + +Feel free to use the badge below on your blog, repository, or apps to showcase your achievement. + +![Static Badge](https://img.shields.io/badge/Panel-Hero-blue) + +```markdown +![Panel Hero](https://img.shields.io/badge/Panel-Hero-blue) +``` + +## Next Steps + +The next recommended steps are to explore the basic apps on the [Basic Tutorials](index.md) page and start utilizing Panel for real projects. + +When you're ready to tackle larger and more complex apps, dive into the [Intermediate Tutorials](../intermediate/index.md). + +## References + +### How-to + +- [Configure the Server](../../how_to/server/index.md) +- [Deploy Panel Applications](../../how_to/deployment/index.md) +- [Enable Automatic Threading](../../how_to/concurrency/threading.md) + +### Hugging Face Spaces + +- [Andrew Huang](https://huggingface.co/ahuang11) +- [Awesome Panel](https://huggingface.co/awesome-panel) +- [Panel-Org](https://huggingface.co/Panel-Org) +- [Sophia Yang](https://huggingface.co/sophiamyang) diff --git a/doc/tutorials/basic/design.md b/doc/tutorials/basic/design.md new file mode 100644 index 0000000000..4128d67a0f --- /dev/null +++ b/doc/tutorials/basic/design.md @@ -0,0 +1,116 @@ +# Apply a Design + +Panel empowers you to effortlessly style your apps using pre-built *designs*, regardless of your prior experience in frontend development. These *designs* offer ready-made visual themes for your applications: + +- **"bootstrap"**: Embraces the elegance and responsiveness of the [Bootstrap](https://getbootstrap.com/) library. +- **"fast"**: Harnesses the speed and modern aesthetics of the [Microsoft Fast Design](https://www.fast.design/) library. +- **"material"**: Draws inspiration from Google's [Material Design](https://m3.material.io/), providing a clean and intuitive user experience. +- **"native"**: Ensures compatibility and consistency with the default styling inherited from [Bokeh](https://bokeh.org/). + +Additionally, Panel supports both `"default"` and `"dark"` themes to further tailor the appearance of your application. + +:::{note} +In the sections below, you can run the provided code directly in the Panel documentation by utilizing the green *run* button, executing it in a notebook cell, or saving it in a file named `app.py` and serving it with `panel serve app.py --autoreload`. +::: + +## Change the Design + +Let's elevate our apps with a *clean and intuitive user experience* by applying the `"material"` *design*. + +Run the following code: + +```{pyodide} +import panel as pn + +pn.extension(design="material") + +pn.Column( + pn.widgets.FloatSlider(name="Slider"), + pn.widgets.TextInput(name="TextInput"), + pn.widgets.Select(name="Select", options=["Wind Turbine", "Solar Panel", "Battery Storage"]), + pn.widgets.Button(name="Click me!", icon="hand-click", button_type="primary"), +).servable() +``` + +Feel free to experiment by changing the `design` to `"bootstrap"`, `"fast"`, or `"native"`. + +## Change the Theme + +Choose a *tab* to proceed: + +::::{tab-set} + +:::{tab-item} Python Script +:sync: script + +Run the code below: + +```{pyodide} +import panel as pn + +pn.extension(design="fast", theme="dark") + +pn.Column( + pn.widgets.FloatSlider(name="Slider"), + pn.widgets.TextInput(name="TextInput"), + pn.widgets.Select(name="Select", options=["Wind Turbine", "Solar Panel", "Battery Storage"]), + pn.widgets.Button(name="Click me!", icon="hand-click", button_type="primary"), + styles={"background": "#181818"} # styles only necessary in the Panel docs +).servable() +``` + +::: + +:::{tab-item} Notebook +:sync: script + +In the notebook, the `theme` automatically adapts to the current JupyterLab theme. + +Run the code below: + +```{pyodide} +import panel as pn + +pn.extension(design="material") + +pn.Column( + pn.widgets.FloatSlider(name="Slider"), + pn.widgets.TextInput(name="TextInput"), + pn.widgets.Select(name="Select", options=["Wind Turbine", "Solar Panel", "Battery Storage"]), + pn.widgets.Button(name="Click me!", icon="hand-click", button_type="primary"), +).servable() +``` + +Experiment by switching the JupyterLab Theme from Dark to Light or vice versa. + +The experience should look something like this: + +![Jupyterlab Theme Switching](https://assets.holoviz.org/panel/tutorials/jupyterlab_theme_support.gif) + +::: + +:::: + +## Recap + +You don't need to be a frontend developer to style your Panel apps. With high-level `design`s, you can effortlessly tailor your applications: + +- `"bootstrap"`: Based on the [Bootstrap](https://getbootstrap.com/) library. +- `"fast"`: Based on the [Microsoft Fast Design](https://www.fast.design/) library. +- `"material"`: Based on [Material Design](https://m3.material.io/). +- `"native"`: The default styling inherited from [Bokeh](https://bokeh.org/). + +Panel also supports the `"default"` and `"dark"` `theme`s. + +## References + +### How-to + +- [Apply a Design](../../how_to/styling/design.md) +- [Customize a Design](../../how_to/styling/design_variables.md) +- [Customize Loading Icon](../../how_to/styling/load_icon.md) +- [Toggle themes](../../how_to/styling/themes.md) + +### Explanation + +- [Designs and Theming](../../explanation/styling/design.md) diff --git a/doc/tutorials/basic/develop_editor.md b/doc/tutorials/basic/develop_editor.md new file mode 100644 index 0000000000..0f81a3a505 --- /dev/null +++ b/doc/tutorials/basic/develop_editor.md @@ -0,0 +1,165 @@ +# Develop in an Editor + +Welcome to the "Develop in an Editor" section! Here, we'll explore how to develop Panel apps efficiently right within your favorite editor. + +Let's dive in! + +## Serve Your App with Autoreload + +Let's serve our app with autoreload. Autoreload ensures that your app updates in real-time as you make changes, providing a smooth development experience. + +### Serving Your App + +Imagine you have a simple Panel app like the following: + +```python +import panel as pn +import numpy as np + +from matplotlib.figure import Figure + +ACCENT = "goldenrod" +LOGO = "https://assets.holoviz.org/panel/tutorials/matplotlib-logo.png" + +pn.extension(sizing_mode="stretch_width") + +data = np.random.normal(1, 1, size=100) +fig = Figure(figsize=(8, 4)) +ax = fig.subplots() +ax.hist(data, bins=20, color=ACCENT) + +component = pn.pane.Matplotlib(fig, format='svg', sizing_mode='scale_both') + +pn.template.FastListTemplate( + title="My App", sidebar=[LOGO], main=[component], accent=ACCENT +).servable() +``` + +Copy this code into a file named `app.py` and save it. + +Now, open a terminal and run the following command: + +```bash +panel serve app.py --autoreload +``` + +This will start the server and provide you with a URL where your app is being served. Open this URL in your browser to see your app in action. + +It should look like + +![Panel served app](../../_static/images/develop_editor_panel_serve_before.png) + +Now change the + +- `ACCENT` value to `teal` and save the `app.py` file. +- `bins` value to `15` and save. +- `title` value to `"My Matplotlib App"` and save. + +It should look like + + + +## Inspect via Hover + +One of the benefits of developing in an editor is the ability to inspect Panel objects conveniently via hover. Simply hover over the object you want to inspect, and you'll see tooltips providing useful information and links to reference guides. + +Let's say you want to inspect the `FastListTemplate` object in your code. Hover over it, and you'll see a tooltip with an example code snippet and a reference link. Clicking the reference link takes you directly to the reference guide for further information. + +![Tooltip of FastListTemplate](../../_static/images/develop_editor_hover.png) + +## Inspect via `print` + +Another handy way to inspect objects is by printing them. This allows you to see detailed information about the object's structure and contents. + +Consider the following code snippet: + +```python +import panel as pn + +pn.extension(design="material") + +component = pn.panel("Hello World") +layout = pn.Column( + component, pn.widgets.IntSlider(value=2, start=0, end=10, name="Value") +) +print(layout) +layout.servable() +``` + +Serving this code will print information about the `component` object, revealing its structure and attributes. + +Serve the app by running the below command in a terminal. + +```bash +panel serve app.py --autoreload +``` + +Open [http://localhost:5006/app](http://localhost:5006/app) in a browser. + +This will look something like the below in the terminal. + +```bash +2024-02-22 20:39:50,405 Starting Bokeh server version 3.3.3 (running on Tornado 6.4) +2024-02-22 20:39:50,406 User authentication hooks NOT provided (default user enabled) +2024-02-22 20:39:50,413 Bokeh app running at: http://localhost:5006/app +2024-02-22 20:39:50,413 Starting Bokeh server with process id: 38480 +Column(design= + + Your browser does not support the video tag. + + +## Serve Your App with Autoreload + +Alternatively, you can serve your notebook externally with autoreload using the following command: + +```bash +panel serve app.ipynb --autoreload +``` + +This method provides a faster alternative to the *Jupyter Panel Preview*. Check out the video for inspiration. + + + +:::{dropdown} Code + +```python +import panel as pn +import numpy as np + +from matplotlib.figure import Figure + +ACCENT = "goldenrod" +LOGO = "https://assets.holoviz.org/panel/tutorials/matplotlib-logo.png" + +pn.extension(sizing_mode="stretch_width") +``` + +```python +data = np.random.normal(1, 1, size=100) +fig = Figure(figsize=(8,4)) +ax = fig.subplots() +ax.hist(data, bins=20, color=ACCENT) + +component = pn.pane.Matplotlib(fig, format='svg', sizing_mode='scale_both') + +pn.template.FastListTemplate( + title="My App", sidebar=[LOGO], main=[component], accent=ACCENT +).servable(); +``` + +::: + +## Inspect a Component using `SHIFT+Tab` + +Start from an empty notebook named `app.ipynb`. + +Copy-paste the code below into the first cell + +```python +import panel as pn + +pn.extension() +``` + +Run the cell. + +Write `pn.widgets.IntSlider` in a cell and press `SHIFT+Tab`. + +It should look like + +![Inspect a Panel component using SHIFT+Tab](../../_static/images/notebook_inspect_shift_tab.png) + +Use the mouse to scroll down until we find the *Example* code snippet and *Reference* link. + +![Inspect a Panel component using SHIFT+Tab](../../_static/images/notebook_inspect_shift_tab_link.png) + +Click the *Reference* link https://panel.holoviz.org/reference/widgets/IntSlider.html. + +It should now look like + +[![IntSlider Reference Documentation](../../_static/images/notebook_intslider_reference_doc.png)](https://panel.holoviz.org/reference/widgets/IntSlider.html) + +:::{tip} +It is a great idea to use the *Example* code snippets and *Reference* links to speed up our workflow. +::: + +## Inspect a Component using `print` + +Start from an empty notebook named `app.ipynb`. + +Copy-paste the code below into the notebook. + +```python +import panel as pn + +pn.extension() +``` + +```python +print(pn.panel("Hello World")) +``` + +```python +component = pn.Column( + "Hello World", pn.widgets.IntSlider(value=2, end=10, name="Value") +) +print(component) +``` + +Run the cells if they have not already been run. + +It should look like + +![Inspect a Panel component](../../_static/images/notebook_inspect_print.png) + +:::{note} +By printing *layout* components like `Column` we can understand how they are composed. This enables us to *access* the subcomponents of the layout. +::: + +Add the two code cells below + +```python +component[0] +``` + +```python +component[1] +``` + +Run the cells if they have not already been run. + +It should look like + +![Inspect a Panel component](../../_static/images/notebook_inspect_print_1.png) + +## Inspect a Component's Parameters using `.param` + +Start from an empty notebook named `app.ipynb`. + +Copy-paste the two code cells below into the notebook. + +```python +import panel as pn + +pn.extension() +``` + +```python +pn.widgets.IntSlider.param +``` + +Run the cells if they have not already been run. + +It should look like + +![Inspect a Panel component class with .param](../../_static/images/notebook_inspect_param_class.png) + +:::{note} +- The `.param` table shows us the **default** parameter values of the `IntSlider` **class**. For example, the *default* value of `align` is `'start'`. +- The `.param` table shows us additional information like the `Type` and `Range` of the Parameter. +::: + +Add the new cell + +```python +pn.widgets.IntSlider(align="end").param +``` + +Run the code cell. + +It should look like + +![Inspect a Panel component instance with .param](../../_static/images/notebook_inspect_param_instance.png) + +:::{note} +- In the picture above we see the **actual** parameter values of the `IntSlider` **instance**. For example, the *actual* value of `align` is `'end'`. +::: + +## Recap + +In this section, we covered: + +- Previewing a notebook app with the *Jupyter Panel Preview*. +- Serving a notebook app with autoreload. +- Inspecting components using `SHIFT+Tab`, `print`, and `.param`. + +Now you're equipped with the tools to efficiently develop Panel apps directly within a notebook environment! + +## Resources + +For more detailed instructions and explanations, check out the resources below: + +- [Develop in Other Notebook Environments](../../how_to/notebook/notebook.md) +- [Display Output in Notebooks](../../how_to/notebook/notebook.md) +- [Preview Apps in JupyterLab](../../how_to/notebook/jupyterlabpreview.md) +- [Serve an App from a Notebook File](serve.md) +- [Use VS Code Notebook and Interactive Environment](https://panel.holoviz.org//how_to/editor/vscode_configure.html#notebook-and-interactive-environment) + +Happy developing! 🚀 diff --git a/doc/tutorials/basic/index.md b/doc/tutorials/basic/index.md new file mode 100644 index 0000000000..456e5075f3 --- /dev/null +++ b/doc/tutorials/basic/index.md @@ -0,0 +1,112 @@ +# Basic Tutorials + +Welcome to the Basic Tutorials! + +Are you ready to dive into the exciting world of Panel? Our Basic Tutorials are designed to guide you step by step through building awesome apps with wind turbine data. Whether you're a beginner or an enthusiast, we've got you covered! And don't hesitate to reach out on [Discord](https://discord.gg/rb6gPXbdAr) if you need help along the way. + +## Prerequisites + +Before we dive in, make sure you've followed along with our [Getting Started Guide](../../getting_started/index.md). + +Please execute the following command to install the dependencies required by the basic tutorials: + +::::{tab-set} + +:::{tab-item} pip +:sync: pip + +```bash +pip install altair hvplot matplotlib numpy pandas panel plotly scipy +``` + +::: + +:::{tab-item} conda +:sync: conda + +```bash +conda install -y -c conda-forge altair hvplot matplotlib numpy pandas panel plotly scipy +``` + +::: + +:::: + +:::{important} +Is Panel installed together with JupyterLab/Jupyter Notebook in your working environment? If not, you need to make sure that `panel` is also installed in the same environment as JupyterLab/Jupyter Notebook (`conda install -c conda-forge panel` or `pip install panel`). +::: + +## Let's Get Started + +Start your journey with these foundational tutorials: + +- **[Build Hello World App](serve.md):** Kick things off with a simple app. +- **[Develop in Notebooks](develop_notebook.md):** Learn how to build apps right in your notebooks. +- **[Develop in Editors](develop_editor.md):** Explore tips for developing in your favorite code editor. + +## Master Panel Basics + +Once you're comfortable, it's time to dive deeper: + +| Part | Section A | Section B| Section C | +|--------------------------|---------------------------|-------------------|------------------------------------------------------| +| **1. Display Content** | [`pn.panel`](pn_panel.md) | [Panes](panes.md) | [Performance Indicators](indicators_performance.md) | +| **2. Organize Content** | [Layouts](layouts.md) | [Control the Size](size.md) | [Align Content](align.md) | +| **3. Handle User Input** | [Widgets](widgets.md) | [React to User Input](pn_bind.md) | [Handle State](state.md) | +| **4. Improve the look** | [Templates](templates.md)| [Designs](design.md) | [Styles](style.md) | +| **5. Improve the Feel** | [Caching](caching.md) | [Activity Indicators](indicators_activity.md) | [Progressive Updates](progressive_layouts.md) | + +## Share Your Creations + +Share your awesome apps with the world! + +- **[Build a Dashboard](build_dashboard.md)** +- **[Deploy a Dashboard](deploy.md)** + +## Ready for Projects? + +Now that you've got the basics down, it's time to put your skills to the test: + +- **[Build a Report](build_report.md)** +- **[Build a Monitoring Dashboard](build_monitoring_dashboard.md)** +- **[Build an Animation](build_animation.md)** +- **[Build a Todo App](build_todo.md)** +- **[Build an Image Classifier](build_image_classifier.md)** +- **[Build a Streaming Dashboard](build_streaming_dashboard.md)** +- **[Build a Chat Bot](build_chatbot.md)** + +Let's start building some amazing wind turbine apps! 🌬️🌀 + +```{toctree} +:titlesonly: +:hidden: +:maxdepth: 2 + +serve +develop_notebook +develop_editor +pn_panel +panes +indicators_performance +layouts +size +align +widgets +pn_bind +state +indicators_activity +progressive_layouts +caching +templates +design +style +build_dashboard +deploy +build_monitoring_dashboard +build_report +build_animation +build_todo +build_image_classifier +build_streaming_dashboard +build_chatbot +``` diff --git a/doc/tutorials/basic/indicators_activity.md b/doc/tutorials/basic/indicators_activity.md new file mode 100644 index 0000000000..fe029a3306 --- /dev/null +++ b/doc/tutorials/basic/indicators_activity.md @@ -0,0 +1,420 @@ +# Display Activity + +Let's bring our Panel apps to life with dynamic indicators and notifications, much like the rotating blades of wind turbines indicate activity. + +- Overlay loading indicators using the `loading` parameter. +- Automatically overlay loading indicators on outputs from *bound functions* by enabling `loading_indicator=True`. +- Enhance user experience with slowly loading applications by deferring load with `defer_load=True`. +- Customize loading indicators with various components. +- Provide notifications using `pn.state.notifications`. + +:::{note} +In the sections below, you can execute the code directly in the Panel documentation using the green *run* button, in a notebook cell, or in a file named `app.py` served with `panel serve app.py --autoreload`. +::: + +```{pyodide} +import panel as pn + +pn.extension(notifications=True) +``` + +## Overlay a Loading Indicator + +We can overlay a loading indicator on a Panel component by setting `loading=True`. + +Run the code below: + +```{pyodide} +import panel as pn + +pn.extension() + +pn.widgets.Button(name="Loading", loading=True, button_type="primary").servable() +``` + +It also works for composed components. + +Run the code below: + +```{pyodide} +import panel as pn + +pn.extension() + +pn.WidgetBox( + pn.widgets.Checkbox(name="Checked", value=True), + pn.widgets.Button(name="Submit", button_type="primary"), + loading=True, margin=(10,10), +).servable() +``` + +## Automatically Overlay a Loading Indicator + +We can automatically overlay a loading indicator on *bound functions* by setting `loading_indicator=True` in `pn.panel`. + +Run the code below: + +```{pyodide} +from time import sleep +import hvplot.pandas +import pandas as pd +import panel as pn + +pn.extension() + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +button = pn.widgets.Button(name="Submit", button_type="primary") + +def get_figure(running): + if not running: + return "Click Submit" + + sleep(2) + return data.hvplot(x="Day", y="Wind Speed (m/s)", line_width=10, ylim=(0,10), title="Wind Speed") + +bound_function = pn.bind(get_figure, button) +plot = pn.panel(bound_function, height=400, sizing_mode="stretch_width", loading_indicator=True) + +pn.Column(button, plot).servable() +``` + +### Exercise: Configure Global Loading Indicator + +Add two more plots to the previous example. Make sure a loading indicator is applied to all three plots when loading. + +:::{hint} +You can configure a global `loading_indicator` via one of: + +- `pn.extension(..., loading_indicator=True)` +- `pn.config.loading_indicator=True`. + +::: + +:::{dropdown} Solution + +```{pyodide} +from time import sleep +import hvplot.pandas +import pandas as pd +import panel as pn + +pn.extension(sizing_mode="stretch_width", loading_indicator=True) + +button = pn.widgets.Button(name="Submit", button_type="primary") + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +def get_figure(running): + if not running: + return None + + sleep(2) + return data.hvplot(x="Day", y="Wind Speed (m/s)", line_width=10, ylim=(0,10), title="Wind Speed") + +bound_function = pn.bind(get_figure, button) +plot = pn.panel(bound_function, height=400) +bound_function2 = pn.bind(get_figure, button) +plot2 = pn.panel(bound_function2, height=400) +bound_function3 = pn.bind(get_figure, button) +plot3 = pn.panel(bound_function3, height=400) + +pn.Column(button, plot, pn.Row(plot2, plot3), sizing_mode="stretch_width").servable() +``` + +::: + +### Exercise: Defer the Load + +:::{hint} +If the bound functions in the application are slow, it will take a while for the application to load. + +We can improve the user experience by using `defer_load=True`. This can be used locally in `pn.panel` or globally via `pn.extension(..., defer_load=True)` or `pn.config.defer_load=True`. +::: + +Run the code below + +```{pyodide} +from time import sleep +import hvplot.pandas +import pandas as pd +import panel as pn + +pn.extension(sizing_mode="stretch_width") + +button = pn.widgets.Button(name="Submit", button_type="primary") + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +def get_figure(running): + sleep(2) + return data.hvplot(x="Day", y="Wind Speed (m/s)", line_width=10, ylim=(0,10), title="Wind Speed") + +bound_function = pn.bind(get_figure, button) +plot = pn.panel(bound_function, height=400) +bound_function2 = pn.bind(get_figure, button) +plot2 = pn.panel(bound_function2, height=400) +bound_function3 = pn.bind(get_figure, button) +plot3 = pn.panel(bound_function3, height=400) + +pn.Column(button, plot, pn.Row(plot2, plot3), sizing_mode="stretch_width").servable() +``` + +Notice that it takes +6 seconds before the application loads. + +Click the `Submit` button. Notice the missing loading indicator. + +Improve the user experience by deferring the load and adding a loading indicator to each plot. + +:::{dropdown} Solution + +```{pyodide} +from time import sleep +import hvplot.pandas +import pandas as pd +import panel as pn + +pn.extension(sizing_mode="stretch_width", defer_load=True, loading_indicator=True) + +button = pn.widgets.Button(name="Submit", button_type="primary") + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +def get_figure(running): + sleep(2) + return data.hvplot(x="Day", y="Wind Speed (m/s)", line_width=10, ylim=(0,10), title="Wind Speed") + +bound_function = pn.bind(get_figure, button) +plot = pn.panel(bound_function, height=400) +bound_function2 = pn.bind(get_figure, button) +plot2 = pn.panel(bound_function2, height=400) +bound_function3 = pn.bind(get_figure, button) +plot3 = pn.panel(bound_function3, height=400) + +pn.Column(button, plot, pn.Row(plot2, plot3), sizing_mode="stretch_width").servable() +``` + +::: + +## Loading Indicators + +Panel provides a selection of loading indicators, such as the [`LoadingSpinner`](../../reference/indicators/LoadingSpinner.ipynb) and [`Progress`](../../reference/indicators/Progress.ipynb) indicator. + +Run the code below + +```{pyodide} +import panel as pn + +pn.extension() + +pn.Row( + pn.Column( + "## Loading Spinner", + pn.Column( + pn.indicators.LoadingSpinner(value=False, height=25, width=25), + pn.indicators.LoadingSpinner( + value=True, height=25, width=25, color="secondary" + ), + ), + ), + pn.Column( + "## Progress", + pn.Column( + pn.indicators.Progress( + name="Progress", value=20, width=150, bar_color="dark" + ), + pn.indicators.Progress( + name="Progress", active=True, width=150, bar_color="dark" + ), + ), + ) +).servable() +``` + +### Exercise: Loading Spinner + +Run the code below + +```{pyodide} +from time import sleep + +import panel as pn + +pn.extension() + +button = pn.widgets.Button(name="Submit", button_type="primary") + +running_indicator = pn.indicators.LoadingSpinner( + value=False, height=25, width=25, color="secondary", visible=True +) + + +def predict(running): + if not running: + return "Click Submit" + + sleep(2) + return "It's a Wind Turbine!" + + +prediction = pn.bind(predict, button) + +pn.Column( + button, + running_indicator, + prediction, +).servable() +``` + +Notice the poor user experience. + +Now fix the issues: + +- Show `LoadingSpinner` only when the prediction is running. +- Make the `LoadingSpinner` spin when the prediction is running. + +:::::{dropdown} Solutions + +::::{tab-set} + +:::{tab-item} Basic +:sync: basic + +```{pyodide} +from time import sleep + +import panel as pn + +pn.extension() + +button = pn.widgets.Button(name="Submit", button_type="primary") + +running_indicator = pn.indicators.LoadingSpinner( + value=False, height=25, width=25, color="secondary", visible=False +) + + +def predict(running): + if not running: + return "Click Submit" + + running_indicator.value = running_indicator.visible = True + + sleep(2) + running_indicator.value = running_indicator.visible = False + return "It's a Wind Turbine!" + + +prediction = pn.bind(predict, button) + +pn.Column(button, running_indicator, prediction).servable() +``` + +::: + +:::{tab-item} Reactive Expressions +:sync: reactive-expressions + +```{pyodide} +from time import sleep + +import panel as pn + +pn.extension() + +is_running = pn.rx(False) + +button = pn.widgets.Button( + name="Submit", button_type="primary", disabled=is_running, loading=is_running +) + +running_indicator = pn.indicators.LoadingSpinner( + value=is_running, height=25, width=25, color="secondary", visible=is_running +) + + +def predict(running): + + if not running: + return "Click Submit" + + is_running.rx.value=True + sleep(2) + is_running.rx.value=False + return "It's a Wind Turbine!" + + +prediction = pn.bind(predict, button) + +pn.Column(button, running_indicator, prediction).servable() +``` + +::: + +:::: + +::::: + +## Notifications + +Let's display an `info` notification. + +```{pyodide} +import panel as pn + +pn.extension(notifications=True) + +def send_notification(event): + pn.state.notifications.info("This is a notification", duration=3000) + +pn.widgets.Button(name="Send", on_click=send_notification).servable() +``` + +Try clicking the `Button`. You should see a notification pop up in the lower left corner of the app. + +:::{note} +The code refers to: + +- `pn.extension(notifications=True)`: We need to set `notifications=True` to support notifications in Panel. +- `pn.state.notifications.info(..., duration=3000)`: We send an `info` notification. The notification is shown for `3000` milliseconds. +::: + +Try changing `pn.state.notifications.info` to `pn.state.notifications.warning`. Then click the `Button`. What happens? + +Try changing `duration=3000` to `duration=0`. Then click the `Button`. What happens? + +See the [Notifications](../../reference/global/Notifications.ipynb) reference guide for more detail. + +## Recap + +We've explored how to show activity with indicators, much like the rotating blades of wind turbines: + +- Overlay a loading indicator with the `loading` parameter. +- Automatically overlay a loading indicator on output from *bound functions* by setting `loading_indicator=True`. +- Improve the user experience of slowly loading applications by setting `defer_load=True`. +- Customize the loading indication with various components. +- Provide notifications using `pn.state.notifications`. + +## Resources + +### How-to + +- [Customize Loading Icon](../../how_to/styling/load_icon.md) +- [Migrate from Streamlit | Show Activity](../../how_to/streamlit_migration/activity.md) +- [Defer Load](../../how_to/callbacks/defer_load.md) + +### Reference Guides + +- [Notifications](../../reference/global/Notifications.ipynb) diff --git a/doc/tutorials/basic/indicators_performance.md b/doc/tutorials/basic/indicators_performance.md new file mode 100644 index 0000000000..9b9fd81f2b --- /dev/null +++ b/doc/tutorials/basic/indicators_performance.md @@ -0,0 +1,80 @@ +# Display Performance with Indicators + +Welcome to our tutorial on displaying the performance of wind turbines using Panel's [indicators](https://panel.holoviz.org/reference/index.html#indicators)! Let's delve into visualizing the key metrics of our wind turbines in an engaging and insightful manner. + +## Explore Indicators + +In this tutorial, we'll explore various indicators offered by Panel to showcase the performance metrics: + +- **Current Performance**: Utilize the [`Number`](../../reference/indicators/Number.ipynb) indicator to display the current performance. +- **Trending Performance**: Employ the [`Trend`](../../reference/indicators/Trend.ipynb) indicator to showcase the trending performance over time. + +For a comprehensive list of indicators and their detailed reference guides, you can always refer to the [Indicators Section](https://panel.holoviz.org/reference/index.html#indicators) in the [Component Gallery](../../reference/index.md). + +:::{note} +Throughout this tutorial, whenever we refer to "run the code," you can execute it directly in the Panel docs using the green *run* button, in a notebook cell, or within a file named `app.py` served with `panel serve app.py --autoreload`. +::: + +## Display a Number + +Let's start by displaying the wind speed using the `Number` indicator: + +```{pyodide} +import panel as pn + +pn.extension() + +pn.indicators.Number( + name="Wind Speed", + value=8.6, + format="{value} m/s", + colors=[(10, "green"), (100, "red")], +).servable() +``` + +:::{note} +Adding `.servable()` to the `Number` indicator incorporates it into the app served by `panel serve app.py --autoreload`. Note that it's not necessary for displaying the indicator in a notebook. +::: + +Feel free to tweak the `value` from `8.6` to `11.4` and observe the color change to *red*. + +## Display a Trend + +Next, let's visualize the hourly average wind speed trend: + +```{pyodide} +import panel as pn +import numpy as np + +pn.extension() + +def get_wind_speeds(n): + # Replace this with your own wind speed data source + return {"x": np.arange(n), "y": 8 + np.random.randn(n)} + +pn.indicators.Trend( + name="Wind Speed (m/s, hourly avg.)", + data=get_wind_speeds(24), + width=500, + height=300, +).servable() +``` + +Experiment by adjusting the `height` parameter from `300` to `500`. + +For more detailed insights into the `Trend` indicator, take a moment to explore its [reference guide](../../reference/indicators/Trend.ipynb). Trust us, it's worth it! + +## Explore the Indicators + +Panel provides a vast collection of indicators to suit your needs. + +Click [this link](https://panel.holoviz.org/reference/index.html#indicators) to explore the available indicators and their detailed reference guides. + +## Recap + +In this tutorial, we've embarked on visualizing the performance metrics of wind turbines using Panel's versatile indicators: + +- Leveraged the [`Number`](../../reference/indicators/Number.ipynb) indicator to display current performance. +- Utilized the [`Trend`](../../reference/indicators/Trend.ipynb) indicator to showcase trending performance over time. + +Remember, there's a plethora of indicators waiting for you to explore in the [Indicators Section](https://panel.holoviz.org/reference/index.html#indicators) of the [Component Gallery](../../reference/index.md). Keep experimenting and uncovering new insights! 🚀 diff --git a/doc/tutorials/basic/layouts.md b/doc/tutorials/basic/layouts.md new file mode 100644 index 0000000000..dc58178386 --- /dev/null +++ b/doc/tutorials/basic/layouts.md @@ -0,0 +1,274 @@ +# Layouts Content + +Welcome to our guide on layouting Python objects, including Panel components! Let's dive into arranging your content in a visually appealing and organized manner. + +## Explore Layouts + +In this guide, we'll explore the following aspects of layouts: + +- **Layouts**: Accessible in the `pn` namespace. +- **Layout Techniques**: Utilize `pn.Column` and `pn.Row` to structure your content. +- **Reference Guides**: Explore detailed documentation for each layout in the [Layouts Section](https://panel.holoviz.org/reference/index.html#layouts) of the [Component Gallery](../../reference/index.md). + +:::{note} +As you follow along with the code examples below, feel free to execute them directly in the Panel documentation, a notebook cell, or within a file named `app.py` served with `panel serve app.py --autoreload`. +::: + +## Layout in a Column + +Let's start by arranging objects vertically using the [Column](../../reference/layouts/Column.ipynb) layout. + +Run the code below: + +```{pyodide} +import pandas as pd +import panel as pn + +pn.extension() + +button = pn.widgets.Button(name="Refresh", icon="refresh", button_type="primary") + +data = pd.DataFrame( + [ + ("Monday", 7), + ("Tuesday", 4), + ("Wednesday", 9), + ("Thursday", 4), + ("Friday", 4), + ("Saturday", 4), + ("Sunday", 4), + ], + columns=["Day", "Wind Speed (m/s)"], +) + +pn.Column("# Wind Speed", data, button).servable() +``` + +:::{note} +The `Column` layout organizes the elements `"# Wind Speed"`, `data`, and `button` vertically. + +We add `.servable()` to display the `Column` component in a server app. It's not necessary for displaying it in a notebook. +::: + +Click [this link](../../reference/layouts/Column.ipynb) to access the `Column` *reference guide* and familiarize yourself with its functionality. + +## Layout in a Row + +Next, let's arrange objects horizontally using the [`Row`](../../reference/layouts/Row.ipynb) layout. + +Run the code below: + +```{pyodide} +import pandas as pd +import panel as pn +import hvplot.pandas + +pn.extension() + +data = pd.DataFrame( + [ + ("Monday", 7), + ("Tuesday", 4), + ("Wednesday", 9), + ("Thursday", 4), + ("Friday", 4), + ("Saturday", 4), + ("Sunday", 4), + ], + columns=["Day", "Wind Speed (m/s)"], +) +plot = data.hvplot(x="Day", y="Wind Speed (m/s)", kind="bar", color="goldenrod", title="Wind Speed (m/s)") + +pn.Row(plot, data).servable() +``` + +## Displays using `pn.panel` + +Layouts automatically display objects using `pn.panel`. + +Run the code below: + +```{pyodide} +import pandas as pd +import panel as pn + +pn.extension() + +data = pd.DataFrame( + [ + ("Monday", 7), + ("Tuesday", 4), + ("Wednesday", 9), + ("Thursday", 4), + ("Friday", 4), + ("Saturday", 4), + ("Sunday", 4), + ], + columns=["Day", "Wind Speed (m/s)"], +) +button = pn.widgets.Button(name="Refresh", icon="refresh", button_type="primary") +component = pn.Column("# Wind Speed", data, button) +print(component) +component.servable() +``` + +The `print` statement will output something like: + +```bash +Column + [0] Markdown(str) + [1] DataFrame(DataFrame) + [2] Button(button_type='primary', icon='refresh', name='Refresh') +``` + +Under the hood, the `Column` layout uses `pn.panel` to determine how to best display Python objects. + +:::{tip} +You can customize how objects are displayed using various arguments of `pn.panel`, specific *Panes*, or specific *Widgets*. +::: + +Run the code below to see custom display: + +```{pyodide} +import pandas as pd +import panel as pn + +pn.extension() + +button = pn.widgets.Button(name="Refresh", icon="refresh", button_type="primary") + +data = pd.DataFrame( + [ + ("Monday", 7), + ("Tuesday", 4), + ("Wednesday", 9), + ("Thursday", 4), + ("Friday", 4), + ("Saturday", 4), + ("Sunday", 4), + ], + columns=["Day", "Wind Speed (m/s)"], +) + +pn.Column( + pn.pane.Str("# Wind Speed"), pn.panel(data, sizing_mode="stretch_width"), button +).servable() +``` + +## Works like a list + +`Column`, `Row`, and many other layouts behave like lists. + +Run the code below: + +```{pyodide} +import pandas as pd +import panel as pn + +pn.extension() + +button = pn.widgets.Button(name="Refresh", icon="refresh", button_type="primary") + +data = pd.DataFrame( + [ + ("Monday", 7), + ("Tuesday", 4), + ("Wednesday", 9), + ("Thursday", 4), + ("Friday", 4), + ("Saturday", 4), + ("Sunday", 4), + ], + columns=["Day", "Wind Speed (m/s)"], +) + +component = pn.Column("# Wind Speed", data, button) +pn.Column(component[0], component[2], component[1]).servable() +``` + +:::{note} +We utilize the *list-like* properties of the `Column` layout to rearrange its elements using *list-indexing* as in `component[0], component[2], component[1]`. + +The `Column` layout implements all the methods you would expect from a *list-like* object, including `.append` and `.remove`. +::: + +## Combine Layouts + +To create more complex layouts, we can combine and nest layouts. + +Let's run the code below: + +```{pyodide} +import pandas as pd +import panel as pn +import hvplot.pandas + +pn.extension() + +button = pn.widgets.Button(name="Refresh", icon="refresh", button_type="primary") +data = pd.DataFrame( + [ + ("Monday", 7), + ("Tuesday", 4), + ("Wednesday", 9), + ("Thursday", 4), + ("Friday", 4), + ("Saturday", 4), + ("Sunday", 4), + ], + columns=["Day", "Wind Speed (m/s)"], +) +plot = data.hvplot( + x="Day", + y="Wind Speed (m/s)", + kind="bar", + color="goldenrod", + title="Wind Speed (m/s)", +) + +pn.Column( + "# Wind Speed", + button, + pn.Row(pn.panel(plot, sizing_mode="stretch_width"), pn.panel(data)), +).servable() +``` + +## Explore the Layouts + +Panel provides a vast collection of layouts to suit your needs. + +Click [this link](https://panel.holoviz.org/reference/index.html#layouts) to explore available layouts and their detailed reference guides. + +## Recap + +In this guide, we have learned: + +- **Layouts**: Available in the `pn` namespace. +- **Layout Techniques**: Utilize `pn.Column` and `pn.Row` to structure your content. +- **Automatic Display**: Layouts use `pn.panel` to determine the optimal display for Python objects. +- **List-like Behavior**: Layouts like `Column` and `Row` behave like lists, allowing for flexible manipulation. +- **Complex Layouts**: Combine and nest layouts for more intricate arrangements. + +Now, you're equipped to create dynamic and visually appealing layouts for your Panel apps! + +## References + +### Tutorials + +- [Display objects with `pn.panel`](pn_panel.md) +- [Display objects with Panes](panes.md) + +### How-to + +- [Align Components](../../how_to/layout/align.md) +- [Control Size](../../how_to/layout/size.md) +- [Customize Spacing](../../how_to/layout/spacing.md) +- [Migrate from Streamlit | Layout Objects](../../how_to/streamlit_migration/layouts.md) + +### Explanation + +- [Components Overview](../../explanation/components/components_overview.md) + +### Component Gallery + +- [Layouts](https://panel.holoviz.org/reference/index.html#layouts) diff --git a/doc/tutorials/basic/panes.md b/doc/tutorials/basic/panes.md new file mode 100644 index 0000000000..29f23d5535 --- /dev/null +++ b/doc/tutorials/basic/panes.md @@ -0,0 +1,389 @@ +# Display Content with Panes + +In this tutorial, we will learn to display objects with *Panes*: + +- *Panes* are available in the `pn.pane` namespace. +- *Panes* take an `object` argument as well as other arguments. +- Discover all *Panes* and their *reference guides* in the [Panes Section](https://panel.holoviz.org/reference/index.html#panes) of the [Component Gallery](../../reference/index.md). + +:::{note} +A *Pane* is a component that can display an object. It takes an `object` as an argument. +::: + +:::{note} +When we ask you to *run the code* in the sections below, you may either execute the code directly in the Panel docs via the green *run* button, in a cell in a notebook, or in a file `app.py` that is served with `panel serve app.py --autoreload`. +::: + +```{pyodide} +import panel as pn +pn.extension("echarts", "plotly", "vega", "vizzu") +``` + +## Display Strings + +The [`Str`](../../reference/panes/Str.ipynb) pane can display any text. + +Run the code: + +```{pyodide} +import panel as pn + +pn.extension() + +pn.pane.Str( + 'This is a raw string that will not be formatted in any way.', +).servable() +``` + +:::{note} +We add `.servable()` to the component to add it to the app served by `panel serve app.py --autoreload`. Adding `.servable()` is not needed to display the component in a notebook. +::: + +:::{note} +To learn in detail how a pane like `Str` works, refer to its *reference guide*. +::: + +Click [this link](../../reference/panes/Str.ipynb) to the `Str` *reference guide* and spend a few minutes to familiarize yourself with its organization and content. + +## Display Markdown + +The [`Markdown`](../../reference/panes/Markdown.ipynb) pane can format and display [*markdown*](https://en.wikipedia.org/wiki/Markdown) strings. + +Run the code: + +```{pyodide} +import panel as pn + +pn.extension() + +pn.pane.Markdown("""\ +# Wind Turbine + +A wind turbine is a device that converts the kinetic energy of wind into \ +[electrical energy](https://en.wikipedia.org/wiki/Electrical_energy). + +Read more [here](https://en.wikipedia.org/wiki/Wind_turbine). +""").servable() +``` + +:::{tip} +It's key for success with Panel to be able to navigate the [Component Gallery](../../reference/index.md) and use the *reference guides*. +::: + +Click [this link](https://panel.holoviz.org/reference/index.html#panes) to the [Panes Section](https://panel.holoviz.org/reference/index.html#panes) of the [Component Gallery](../../reference/index.md). Identify the [Markdown Reference Guide](../../reference/panes/Markdown.ipynb) and open it. You don't have to spend time studying the details right now. + +### Display Alerts + +The [`Alert`](../../reference/panes/Alert.ipynb) pane can format and display [*markdown*](https://en.wikipedia.org/wiki/Markdown) strings inside a nicely styled *Alert* pane. + +Run the code: + +```{pyodide} +import panel as pn + +pn.extension() + +pn.pane.Alert(""" +## Markdown Sample + +This sample text is from [The Markdown Guide](https://www.markdownguide.org)! +""", alert_type="info").servable() +``` + +## Display Plots + +Pick a plotting library below. + +:::::{tab-set} + +::::{tab-item} Altair +:sync: altair + +Run the code below. + +```{pyodide} +import altair as alt +import pandas as pd +import panel as pn + +pn.extension("vega") + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +fig = ( + alt.Chart(data) + .mark_line(point=True) + .encode( + x="Day", + y=alt.Y("Wind Speed (m/s)", scale=alt.Scale(domain=(0, 10))), + tooltip=["Day", "Wind Speed (m/s)"], + ) + .properties(width="container", height="container", title="Wind Speed") +) + +pn.pane.Vega(fig, sizing_mode="stretch_width", height=400).servable() +``` + +:::{note} +Vega is the name of the JavaScript plotting library used by Altair. + +We must add `"vega"` as an argument to `pn.extension` in the example to load the Vega Javascript dependencies in the browser. + +If we forget to add `"vega"` to `pn.extension`, then the Altair figure might not display. +::: + +:::: + +::::{tab-item} ECharts +:sync: echarts + +Run the code below. + +```{pyodide} +import panel as pn + +pn.extension("echarts") + +config = { + 'title': { + 'text': 'Wind Speed' + }, + "tooltip": {}, + 'xAxis': { + 'data': ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] + }, + 'yAxis': {}, + 'series': [{ + 'name': 'Sales', + 'type': 'line', + 'data': [7, 4, 9, 4, 4, 5, 4] + }], +} +pn.pane.ECharts(config, height=400, sizing_mode="stretch_width").servable() +``` + +:::{note} +We must add `"echarts"` as an argument to `pn.extension` in the example to load the ECharts Javascript dependencies in the browser. + +If we forget to add `"echarts"` to `pn.extension`, then the ECharts figure might not display. +::: + +:::: + +::::{tab-item} hvPlot +:sync: hvplot + +Run the code below. + +```{pyodide} +import hvplot.pandas +import numpy as np +import pandas as pd +import panel as pn + +pn.extension() + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +fig = data.hvplot(x="Day", y="Wind Speed (m/s)", line_width=10, ylim=(0,10), title="Wind Speed") + +pn.pane.HoloViews(fig, sizing_mode="stretch_width").servable() +``` + +:::{note} +[hvPlot](https://hvplot.holoviz.org) is the **easy to use** plotting sister of Panel. It works similarly to the familiar [Pandas `.plot` api](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html). hvPlot is built on top of the data visualization library [HoloViews](https://holoviews.org/). hvPlot, HoloViews, and Panel are all part of the [HoloViz](https://holoviz.org/) family. +::: + +:::: + +::::{tab-item} Matplotlib +:sync: matplotlib + +Run the code below. + +```{pyodide} +import matplotlib +import matplotlib.pyplot as plt +import pandas as pd +import panel as pn + +matplotlib.use("agg") + +pn.extension() + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +fig, ax = plt.subplots(figsize=(8,3)) +ax.plot( + data["Day"], data["Wind Speed (m/s)"], marker="o", markersize=10, linewidth=4 +) +ax.set( + xlabel="Day", + ylabel="Wind Speed (m/s)", + title="Wind Speed", + ylim=(0, 10), +) +ax.grid() +plt.close(fig) # CLOSE THE FIGURE TO AVOID MEMORY LEAKS! + +pn.pane.Matplotlib(fig, dpi=144, tight=True, format="svg", sizing_mode="stretch_width").servable() +``` + +:::{note} +In the example, we provide the arguments `dpi`, `format` and `tight` to the Matplotlib pane. + +The `Matplotlib` pane can display figures from any framework that produces Matplotlib `Figure` objects like Seaborn, Plotnine and Pandas `.plot`. + +We can find more details in the [Matplotlib Reference Guide](../../reference/panes/Matplotlib.ipynb). +::: + +:::: + +::::{tab-item} Plotly +:sync: plotly + +Run the code below. + +```{pyodide} +import pandas as pd +import panel as pn +import plotly.express as px + +pn.extension("plotly") + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +fig = px.line(data, x="Day", y="Wind Speed (m/s)") +fig.update_traces(mode="lines+markers", marker=dict(size=10), line=dict(width=4)) +fig.update_yaxes(range=[0, max(data['Wind Speed (m/s)']) + 1]) +fig.layout.autosize = True + +pn.pane.Plotly(fig, height=400, sizing_mode="stretch_width").servable() +``` + +:::{note} +We must add `"plotly"` as an argument to `pn.extension` in the example to load the Plotly JavaScript dependencies in the browser. + +If we forget to add `"plotly"` to `pn.extension`, then the Plotly figure might not display. +::: + +:::: + +::::{tab-item} Vizzu +:sync: vizzu + +Run the code below. + +```{pyodide} +import pandas as pd +import panel as pn + +pn.extension("vizzu") + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +pn.pane.Vizzu( + data, config={'geometry': 'line', 'x': 'Day', 'y': 'Wind Speed (m/s)', 'title': 'Wind Speed'}, + duration=400, height=400, sizing_mode='stretch_width', tooltip=True +).servable() +``` + +:::{note} +We must add `"vizzu"` as an argument to `pn.extension` in the example to load the Vizzu JavaScript dependencies in the browser. + +If we forget to add `"vizzu"` to `pn.extension`, then the Vizzu figure might not display. +::: + +:::: + +::::: + +## Display a DataFrame + +Run the code: + +```{pyodide} +import pandas as pd +import panel as pn + +pn.extension() + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 4), ('Sunday', 4)], columns=['Day', 'Orders'] +) +pn.pane.DataFrame(data).servable() +``` + +:::{note} +If we want to display larger dataframes, customize the way the dataframes are displayed, or make them more interactive, we can find specialized components in the [Component Gallery](../../reference/index.md) supporting these use cases. For example, the [Tabulator](../../reference/widgets/Tabulator.ipynb) widget and [Perspective](../../reference/panes/Perspective.ipynb) pane. +::: + +## Display any Python object + +Provides *Panes* to display (almost) any Python object. + +Run the code below + +```{pyodide} +import panel as pn + +pn.extension() + +pn.Column( + pn.pane.JSON({"Wind Speeds": [0, 3, 6, 9, 12, 15, 18, 21], "Power Output": [0,39,260,780, 1300, 1300, 0, 0]}), + pn.pane.PNG("https://assets.holoviz.org/panel/tutorials/wind_turbine.png", height=100), + pn.pane.Audio("https://assets.holoviz.org/panel/tutorials/wind_turbine.mp3"), +).servable() +``` + +## Recap + +In this guide, we have learned to display Python objects with *Panes*: + +- *Panes* are available in the `pn.pane` namespace +- *Panes* take an `object` argument as well as other arguments +- Display strings with the [`Str`]((../../reference/panes/Str.ipynb)), [`Markdown`]((../../reference/panes/Markdown.ipynb)) and [`Alert`]((../../reference/panes/Alert.ipynb)) panes +- Display plot figures like [Altair](https://altair-viz.github.io/), [ECharts](https://echarts.apache.org/en/index.html), [hvPlot](https://hvplot.holoviz.org), [Matplotlib](https://matplotlib.org/), [Plotly](https://plotly.com/python/) and [Vizzu](https://vizzuhq.com/) with the [`Vega`](../../reference/panes/Vega.ipynb), [`ECharts`](../../reference/panes/ECharts.ipynb), [`HoloViews`](../../reference/panes/HoloViews.ipynb), [`Matplotlib`](../../reference/panes/Matplotlib.ipynb), [`Plotly`](../../reference/panes/Plotly.ipynb) and [`Vizzu`](../../reference/panes/Vizzu.ipynb) *panes*, respectively. +- Display *DataFrames* with the [`DataFrame`](../../reference/panes/DataFrame.ipynb) and [`Perspective`]((../../reference/panes/Perspective.ipynb)) *panes*. +- Add JavaScript dependencies via `pn.extension`. For example `pn.extension("vega")` or `pn.extension("plotly")` +- Discover all *Panes* and their *reference guides* in the [Panes Section](https://panel.holoviz.org/reference/index.html#panes) of the [Component Gallery](../../reference/index.md). + +## Resources + +### Tutorials + +- [Display objects with `pn.panel`](pn_panel.md) + +### How-to + +- [Construct Panes](../../how_to/components/construct_panes.md) +- [Migrate from Streamlit | Display Content with Panes](../../how_to/streamlit_migration/panes.md) +- [Style Altair Plots](../../how_to/styling/altair.md) +- [Style Echarts Plots](../../how_to/styling/echarts.md) +- [Style Matplotlib Plots](../../how_to/styling/matplotlib.md) +- [Style Plotly Plots](../../how_to/styling/plotly.md) +- [Style Vega/ Altair Plots](../../how_to/styling/vega.md) + +### Explanation + +- [Components Overview](../../explanation/components/components_overview.md) + +### Component Gallery + +- [Panes](https://panel.holoviz.org/reference/index.html#panes) diff --git a/doc/tutorials/basic/pn_bind.md b/doc/tutorials/basic/pn_bind.md new file mode 100644 index 0000000000..78591f73b1 --- /dev/null +++ b/doc/tutorials/basic/pn_bind.md @@ -0,0 +1,9 @@ +# React to User Input + +COMING UP + +## Resources + +### How-to + +- [Add interactivity to a function](../../how_to/interactivity/bind_function.md) diff --git a/doc/tutorials/basic/pn_panel.md b/doc/tutorials/basic/pn_panel.md new file mode 100644 index 0000000000..f8a34a7e98 --- /dev/null +++ b/doc/tutorials/basic/pn_panel.md @@ -0,0 +1,377 @@ +# Display Content with `pn.panel` + +In this guide, we will learn to display Python objects easily with `pn.panel`: + +- Display any Python object via `pn.panel(the_object, ...)`. + +:::{note} +When we ask to *run the code* in the sections below, we may execute the code directly in the Panel docs via the green *run* button, in a cell in a notebook, or in a file `app.py` that is served with `panel serve app.py --autoreload`. +::: + +```{pyodide} +import panel as pn +pn.extension("plotly", "vega") +``` + +## Display a String + +To build an app, our first step is to display things. Luckily, Panel provides us with the simple yet powerful `pn.panel()` function. This function effortlessly transforms Python objects into viewable components within our app. Let's start with something simple: a string. + +Run the code: + +```{pyodide} +import panel as pn + +pn.extension() + +pn.panel("Hello World").servable() +``` + +:::{note} +We add `.servable()` to the component to add it to the app served by `panel serve app.py --autoreload`. Adding `.servable()` is not needed to display the component in a notebook. +::: + +`pn.panel` uses a *heuristic* algorithm to determine how to best display the `object`. To make this very explicit, we will `print` the component in all the examples below. + +Run the code: + +```{pyodide} +import panel as pn + +pn.extension() + +component = pn.panel("Hello World") +print(component) + +component.servable() +``` + +Your cell or terminal output should contain `Markdown(str)`. It means `pn.panel` has picked a [`Markdown`](../../reference/panes/Markdown.ipynb) *pane* to display the `str` object. + +Let's verify that *markdown strings* are actually displayed and rendered nicely. + +Run the code: + +```{pyodide} +import panel as pn + +pn.extension() + +component = pn.panel(""" +# Wind Turbine + +A wind turbine is a device that converts the kinetic energy of wind into \ +[electrical energy](https://en.wikipedia.org/wiki/Electrical_energy). + +Read more [here](https://en.wikipedia.org/wiki/Wind_turbine). +""") +print(component) + +component.servable() +``` + +```{tip} +Markdown rendering is very useful in Panel applications, such as for displaying formatted text, headers, links, images, LaTeX formulas and other rich content +``` + +## Display a DataFrame + +Now that we've mastered the art of displaying strings, let's take it up a notch. In our journey to build a data-centric app, we'll often need to display more complex objects like dataframes. With Panel, it's as easy as pie. + +Run the code: + +```{pyodide} +import pandas as pd +import panel as pn + +pn.extension() + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) +component = pn.panel(data) +print(component) +component.servable() +``` + +:::{tip} +If we want to display larger dataframes, customize the way the dataframes are displayed, or make them more interactive, we can find specialized components in the [Component Gallery](../../reference/index.md) supporting these use cases. For example, the [Tabulator](../../reference/widgets/Tabulator.ipynb) *widget* and [Perspective](../../reference/panes/Perspective.ipynb) *pane*. +::: + +## Display Plots + +Many data apps contains one or more plots. Lets try to display some. + +Pick a plotting library below. + +:::::{tab-set} + +::::{tab-item} Altair +:sync: altair + +Run the code below: + +```{pyodide} +import altair as alt +import pandas as pd +import panel as pn + +pn.extension("vega") + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +fig = ( + alt.Chart(data) + .mark_line(point=True) + .encode( + x="Day", + y=alt.Y("Wind Speed (m/s)", scale=alt.Scale(domain=(0, 10))), + tooltip=["Day", "Wind Speed (m/s)"], + ) + .properties(width="container", height="container", title="Wind Speed Over the Week") +) + +component = pn.panel(fig, sizing_mode="stretch_width", height=400) +print(component) + +component.servable() +``` + +Please notice that `pn.panel` chose a [`Vega`](../../reference/panes/Vega.ipynb) pane to display the [Altair](https://altair-viz.github.io/) figure. + +:::{note} +Vega is the name of the JavaScript plotting library used by Altair. + +We must add `"vega"` as an argument to `pn.extension` in the example to load the Vega Javascript dependencies in the browser. + +If we forget to add `"vega"` to `pn.extension`, then the Altair figure might not display. +::: + +:::: + +::::{tab-item} hvPlot +:sync: hvPlot + +Run the code below: + +```{pyodide} +import hvplot.pandas +import numpy as np +import pandas as pd +import panel as pn + +pn.extension() + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +fig = data.hvplot(x="Day", y="Wind Speed (m/s)", line_width=10, ylim=(0,10)) + +component = pn.panel(fig, sizing_mode="stretch_width", ) +print(component) + +component.servable() +``` + +Please notice that `pn.panel` chose a [`HoloViews`](../../reference/panes/HoloViews.ipynb) pane to display the [hvPlot](https://hvplot.holoviz.org/user_guide/Customization.html) figure. + +:::{note} +[hvPlot](https://hvplot.holoviz.org) is the **easy to use** plotting sister of Panel. It works similarly to the familiar [Pandas `.plot` API](https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.html). hvPlot is built on top of the data visualization library [HoloViews](https://holoviews.org/). hvPlot, HoloViews, and Panel are all part of the [HoloViz](https://holoviz.org/) family. +::: + +:::: + +::::{tab-item} Matplotlib +:sync: matplotlib + +Run the code below: + +```{pyodide} +import matplotlib +import matplotlib.pyplot as plt +import pandas as pd +import panel as pn + +matplotlib.use("agg") + +pn.extension() + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +fig, ax = plt.subplots(figsize=(8,3)) +ax.plot( + data["Day"], data["Wind Speed (m/s)"], marker="o", markersize=10, linewidth=4 +) +ax.set( + xlabel="Day", + ylabel="Wind Speed (m/s)", + title="Wind Speed Over the Week", + ylim=(0, 10), +) +ax.grid() +plt.close(fig) # CLOSE THE FIGURE TO AVOID MEMORY LEAKS! + +component = pn.panel( + fig, format="svg", dpi=144, tight=True, sizing_mode="stretch_width" +) +print(component) + +component.servable() +``` + +Please notice `pn.panel` chose a [`Matplotlib`](../../reference/panes/Matplotlib.ipynb) pane to display the Matplotlib figure. + +:::{note} +In the example above we provided arguments to `pn.panel`. These will be applied to the *pane* selected by `pn.panel` to display the object. In this example the [`Matplotlib`](../../reference/panes/Matplotlib.ipynb) pane is selected. + +The arguments `dpi`, `format` and `tight` would not make sense if a string was provided as an argument to `pn.panel`. In that case, `pn.panel` would pick a [Markdown](../../reference/panes/Markdown.ipynb) *pane* and the exception `TypeError: Markdown.__init__() got an unexpected keyword argument 'dpi'` would be raised. +::: + +:::: + +::::{tab-item} Plotly +:sync: plotly + +```{pyodide} +import pandas as pd +import panel as pn +import plotly.express as px + +pn.extension("plotly") + +data = pd.DataFrame([ + ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4), + ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)'] +) + +fig = px.line(data, x="Day", y="Wind Speed (m/s)") +fig.update_traces(mode="lines+markers", marker=dict(size=10), line=dict(width=4)) +fig.update_yaxes(range=[0, max(data['Wind Speed (m/s)']) + 1]) +fig.layout.autosize = True + +component = pn.panel(fig, height=400, sizing_mode="stretch_width") +print(component) + +component.servable() +``` + +Please notice that `pn.panel` chose a [`Plotly`](../../reference/panes/Plotly.ipynb) pane to display the Plotly figure. + +:::{note} +We must add `"plotly"` as an argument to `pn.extension` in the example to load the Plotly Javascript dependencies in the browser. + +If we forget to add `"plotly"` to `pn.extension`, then the Plotly figure might not display. +::: + +:::: + +::::: + +## Display any Python object + +`pn.panel` can display (almost) any Python object. + +Run the code below: + +```{pyodide} +import panel as pn + +pn.extension() + +component = pn.Column( + pn.panel({"Wind Speeds": [0, 3, 6, 9, 12, 15, 18, 21], "Power Output": [0,39,260,780, 1300, 1300, 0, 0]}), + pn.panel("https://assets.holoviz.org/panel/tutorials/wind_turbine.png", height=100), + pn.panel("https://assets.holoviz.org/panel/tutorials/wind_turbine.mp3"), +) +print(component) + +component.servable() +``` + +## Display any Python object in a layout + +If we place objects in a [*layout*](https://panel.holoviz.org/reference/index.html#layouts) like [`pn.Column`](../../reference/layouts/Column.ipynb) (more about layouts later), then the layout will apply `pn.panel` for us automatically. + +Run the code below: + +```{pyodide} +import panel as pn + +pn.extension() + +component = pn.Column( + {"Wind Speeds": [0, 3, 6, 9, 12, 15, 18, 21], "Power Output": [0,39,260,780, 1300, 1300, 0, 0]}, + "https://assets.holoviz.org/panel/tutorials/wind_turbine.png", + "https://assets.holoviz.org/panel/tutorials/wind_turbine.mp3", +) +print(component) + +component.servable() +``` + +Please notice that the image of the dice is very tall. To fine-tune the way it is displayed, we can use `pn.panel` with arguments. + +Run the code below: + +```{pyodide} +import panel as pn + +pn.extension() + +component = pn.Column( + pn.panel({"Wind Speeds": [0, 3, 6, 9, 12, 15, 18, 21], "Power Output": [0,39,260,780, 1300, 1300, 0, 0]}), + pn.panel("https://assets.holoviz.org/panel/tutorials/wind_turbine.png", height=100), + pn.panel("https://assets.holoviz.org/panel/tutorials/wind_turbine.mp3", styles={"background": "orange", "padding": "10px"}), +) +print(component) + +component.servable() +``` + +:::{note} +The example above sets the *css* `styles` of the `Audio` player. The `styles` parameter is introduced in the [Styles](style.md) tutorial. +::: + +## Consider Performance + +`pn.panel` is a versatile helper function that converts objects into a [*Pane*](https://panel.holoviz.org/reference/index.html#panes). It automatically selects the best *representation* for an object based on available [*Pane*](https://panel.holoviz.org/reference/index.html#panes) types, ranking them by priority. + +For optimal performance, specify the desired *Pane* type directly, like `pn.pane.Matplotlib(fig)` instead of using `pn.panel(fig)`. You will learn about *Panes* in the [Display Content with Panes](panes.md) section. + +## Recap + +In this guide, we have learned to display Python objects easily with `pn.panel`: + +- Display a string with `pn.panel(some_string)` +- Display plot figures like [Altair](https://altair-viz.github.io/), [hvPlot](https://hvplot.holoviz.org), [Matplotlib](https://matplotlib.org/) and [Plotly](https://plotly.com/python/) with `pn.panel(fig)` +- Display DataFrames with `pn.panel(df)` +- Display most Python objects with `pn.panel(some_python_object)` +- Configure how an object is displayed by giving arguments to `pn.panel` +- Display most Python objects in *layouts* like `pn.Column` with and without the use of `pn.panel` +- Use a specific *Pane* instead of `pn.panel` if performance is key +- Add JavaScript dependencies via `pn.extension`. For example `pn.extension("vega")` or `pn.extension("plotly")` + +## Resources + +### Tutorials + +- [Display objects with Panes](panes.md) + +### How-to + +- [Access Pane Type](../../how_to/components/pane_type.md) +- [Construct Panes](../../how_to/components/construct_panes.md) +- [Style Components](../../how_to/styling/index.md) + +### Component Gallery + +- [Panes](https://panel.holoviz.org/reference/index.html#panes) diff --git a/doc/tutorials/basic/progressive_layouts.md b/doc/tutorials/basic/progressive_layouts.md new file mode 100644 index 0000000000..d82452ce06 --- /dev/null +++ b/doc/tutorials/basic/progressive_layouts.md @@ -0,0 +1,303 @@ +# Update Progressively + +Welcome to the "Update Progressively" tutorial! Here, we'll explore an exciting aspect of HoloViz Panel: progressively updating content while running a slow classifier model. + +In this tutorial, we'll: + +- Utilize generator functions (`yield`) to progressively return content and update the app. +- Harness the power of reactive expressions (`pn.rx`) to facilitate dynamic updates to the app. + +:::{note} +When instructed to run the code, you can execute it directly in the Panel documentation using the green *run* button, in a notebook cell, or in a file named `app.py` served with `panel serve app.py --autoreload`. +::: + +## Replace Content Progressively + +### Replace using a Generator + +Let's kick off by replacing content progressively with the help of a generator function. + +```{pyodide} +import random +from time import sleep +import panel as pn + +pn.extension() + +OPTIONS = ["Wind Turbine", "Solar Panel", "Battery Storage"] + +# Classifier function +def classify(image): + sleep(2) + return random.choice(OPTIONS) + +# Components +run = pn.widgets.Button(name="Submit", button_type="primary") + +progress_message = pn.Row( + pn.indicators.LoadingSpinner( + value=True, width=25, height=25, align="center", margin=(5, 0, 5, 10) + ), + pn.panel("Running classifier ...", margin=0), +) + +# Generator function +def get_prediction(running): + if not running: + yield "Click Submit" + return + + yield progress_message + prediction = classify(None) + yield f"It's a {prediction}" + +# Display +pn.Column(run, pn.bind(get_prediction, run)).servable() +``` + +Click the *Submit* `Button` to see it in action! + +#### Exercise: Disable the Button + +Now, let's enhance our app by disabling the button while the classification is running. + +:::::{dropdown} Solution + +```{pyodide} +import random +from time import sleep +import panel as pn + +pn.extension() + +OPTIONS = ["Wind Turbine", "Solar Panel", "Battery Storage"] + +# Classifier function +def classify(image): + sleep(2) + return random.choice(OPTIONS) + +# Components +run = pn.widgets.Button(name="Submit", button_type="primary") + +progress_message = pn.Row( + pn.indicators.LoadingSpinner( + value=True, width=25, height=25, align="center", margin=(5, 0, 5, 10) + ), + pn.panel("Running classifier ...", margin=0), +) + +# Generator function +def get_prediction(running): + if not running: + yield "Click Submit" + return + + run.disabled = True + yield progress_message + prediction = classify(None) + yield f"It's a {prediction}" + run.disabled = False + +# Display +pn.Column(run, pn.bind(get_prediction, run)).servable() +``` + +::::: + +### Replace using Reactive Expressions + +Another approach to achieve the same functionality is by using reactive expressions (`pn.rx`). + +```{pyodide} +import random +from time import sleep +import panel as pn + +pn.extension() + +OPTIONS = ["Wind Turbine", "Solar Panel", "Battery Storage"] + +# State +result = pn.rx("") +is_running = pn.rx(False) + +# Classifier function +def classify(image): + sleep(2) + return random.choice(OPTIONS) + +# Reactive expressions +has_result = result.rx.pipe(bool) + +def _show_submit_message(result, is_running): + return not result and not is_running + +show_submit_message = pn.rx(_show_submit_message)(result, is_running) + +def run_classification(_): + result.rx.value = "" + is_running.rx.value = True + prediction = classify(None) + result.rx.value = f"It's a {prediction}" + is_running.rx.value = False + +# Components +click_submit = pn.pane.Markdown("Click Submit", visible=show_submit_message) +run = pn.widgets.Button( + name="Submit", button_type="primary", on_click=run_classification +) +progress_message = pn.Row( + pn.indicators.LoadingSpinner( + value=True, width=25, height=25, align="center", margin=(5, 0, 5, 10) + ), + pn.panel("Running classifier ...", margin=0), + visible=is_running, +) + +# Layout +pn.Column(run, click_submit, result, progress_message).servable() +``` + +Click the *Submit* `Button` to observe the magic! + +## Append Content Progressively + +### Append using Generator + +Now, let's explore how to append content progressively using a generator. + +```{pyodide} +import panel as pn +from time import sleep +import random + +pn.extension() + +OPTIONS = ["Wind Turbine", "Solar Panel", "Battery Storage"] + +# Classifier function +def classify(image): + sleep(2) + return random.choice(OPTIONS) + +# Components +run = pn.widgets.Button(name="Submit", button_type="primary") + +progress_message = pn.Row( + pn.indicators.LoadingSpinner( + value=True, width=25, height=25, align="center", margin=(5, 0, 5, 10) + ), + pn.panel("Running classifier ...", margin=0), +) + +layout = pn.Column("Click Submit") + +# Generator function +def get_prediction(running): + if not running: + yield layout + return + + layout.clear() + for image in range(0, 5): + layout.append(progress_message) + yield layout + prediction = classify(None) + result = f"Image {image} is a {prediction}" + layout[-1] = result + yield layout + +# Display +pn.Column(run, pn.bind(get_prediction, run)).servable() +``` + +### Exercise: Replace the Generator with Reactive Expressions + +Let's reimplement the app using reactive expressions (`pn.rx`), without using a generator function (`yield`). + +:::::{dropdown} Solution + +```{pyodide} +import random +from time import sleep + +import panel as pn + +pn.extension() + +OPTIONS = ["Wind Turbine", "Solar Panel", "Battery Storage"] + +# State +results = pn.rx([]) +is_running = pn.rx(False) + +# Classifier function +def classify(image): + sleep(2) + return random.choice(OPTIONS) + +# Reactive expressions +def _show_submit_message(results, is_running): + return not results and not is_running + +show_submit_message = pn.rx(_show_submit_message)(results, is_running) + +def classify_all(_): + is_running.rx.value = True + results.rx.value = [] + + for image in range(0, 5): + prediction = classify(None) + result = f"Image {image} is a {prediction}" + results.rx.value = results.rx.value + [result] + + is_running.rx.value = False + +# Components +click_submit = pn.pane.Markdown("Click Submit", visible=show_submit_message) +run = pn.widgets.Button( + name="Submit", + button_type="primary", + on_click=classify_all, + disabled=is_running, + loading=is_running, +) + +# Outputs: Views +results_view = results.rx.pipe(lambda value: pn.Column(*value)) +progress_message = pn.Row( + pn.indicators.LoadingSpinner( + value=True, width=25, height=25, align="center", margin=(5, 0, 5, 10) + ), + pn.panel("Running classifier ...", margin=0), + visible=is_running, +) + +# Layout +pn.Column(run, click_submit, results_view, progress_message).servable() +``` + +::::: + +## Recap + +In this tutorial, we've explored how to progressively update content in a Panel app: + +- We learned how to utilize generator functions (`yield`) for progressive updates. +- We also explored the usage of reactive expressions (`pn.rx`) for dynamic content updates. + +Keep experimenting and building with Panel! 🚀 + +## Resources + +### How to + +- [Add interactivity with Generators](../../how_to/interactivity/bind_generators.md) +- [Migrate from Streamlit | Add Interactivity](../../how_to/streamlit_migration/interactivity.md) + +### Component Gallery + +- [ReactiveExpr](../../reference/panes/ReactiveExpr.md) + +Feel free to reach out on [Discord](https://discord.gg/rb6gPXbdAr) if you have any questions or need further assistance! diff --git a/doc/tutorials/basic/serve.md b/doc/tutorials/basic/serve.md new file mode 100644 index 0000000000..78526ee75f --- /dev/null +++ b/doc/tutorials/basic/serve.md @@ -0,0 +1,116 @@ +# Build Hello World App + +Welcome to the "Build Hello World App" tutorial! Get ready to dive into the world of Panel and serve your very first app. + +## Serve the App + +Let's get started by serving our simple *Hello World* app using Panel. Choose a tab below to continue: + +:::::{tab-set} + +::::{tab-item} Script +:sync: script + +In this section, we'll create the simplest Panel `.py` app: + +```python +import panel as pn + +pn.extension() + +pn.panel("Hello World").servable() +``` + +:::{note} +Here's a breakdown of the code: + +- `panel`: The Panel python package, conventionally imported as `pn`. +- `pn.extension()`: Loads javascript dependencies and configures Panel. +- `pn.panel(...)`: Creates a *displayable* Panel component. +- `.servable()`: Displays the component in a *server app*. +::: + +Copy the code into a file named `app.py` and save it. Then, run the Panel server in your terminal with: + +```bash +panel serve app.py --autoreload +``` + +:::: + +::::{tab-item} Notebook +:sync: notebook + +In this section, we'll create the simplest Panel Notebook app: + +```python +import panel as pn + +pn.extension() +``` + +```python +pn.panel("Hello World").servable() +``` + +:::{note} +Here's what the code does: + +- `panel`: The Panel python package, conventionally imported as `pn`. +- `pn.extension()`: **Loads the [`pyviz_comms`](https://github.com/holoviz/pyviz_comms) notebook extension**, loads javascript dependencies and configures Panel. +- `pn.panel(...)`: Creates a *displayable* Panel component, which can be directly displayed in the notebook. +- `.servable()`: Displays the component in a *server app*. +::: + +Copy the above code cells into a clean notebook named `app.ipynb`. Run the cells and save the notebook as `app.ipynb`. + +Then, run the Panel server in your terminal with: + +```bash +panel serve app.ipynb --autoreload +``` + +::::: + +Upon successful startup, you'll see the server's URL in the terminal. + +```bash +2024-01-17 21:05:32,338 Starting Bokeh server version 3.3.3 (running on Tornado 6.4) +2024-01-17 21:05:32,339 User authentication hooks NOT provided (default user enabled) +2024-01-17 21:05:32,342 Bokeh app running at: http://localhost:5006/app +2024-01-17 21:05:32,342 Starting Bokeh server with process id: 42008 +``` + +Open your browser at that URL to view the app. It should look like + +![Panel serve app](../../_static/images/panel-serve-py-app.png). + +Play around with the app by modifying the `"Hello World"` text, saving the file and observe how it updates in real-time. To stop the server, simply press `CTRL+C` in the terminal. + +## Recap + +Congratulations on serving your first Panel app! Let's recap what we've learned: + +- How to serve a Python script or Notebook using `panel serve app.py` or `panel serve app.ipynb`, respectively. +- How to enable *auto reload* with the `--autoreload` flag. +- How to stop the Panel server with `CTRL+C`. + +## Resources + +### Tutorials + +- [Serve Panel Apps (Intermediate)](../intermediate/serve.md) + +### How-to + +- [Launch a server dynamically with `pn.serve` or `pn.show`](../../how_to/server/programmatic.md) +- [Launch a server on the command line](../../how_to/server/commandline.md) +- [Migrate from Streamlit | Serve Apps](../../how_to/streamlit_migration/index.md) +- [Serve multiple applications with `pn.serve`](../../how_to/server/multiple.md) +- [Serve static files with `--static-dirs`](../../how_to/server/static_files.md) +- [Serve with Django](../../how_to/integrations/Django.md) +- [Serve with FastAPI](../../how_to/integrations/FastAPI.md) +- [Serve with Flask](../../how_to/integrations/flask.md) +- [Write and serve apps in Markdown](../../how_to/editor/markdown.md) + +Keep exploring and building with Panel! 🚀 diff --git a/doc/tutorials/basic/size.md b/doc/tutorials/basic/size.md new file mode 100644 index 0000000000..76c57c2e81 --- /dev/null +++ b/doc/tutorials/basic/size.md @@ -0,0 +1,349 @@ +# Control the Size + +Welcome to our tutorial on controlling the size of components in Panel! In this guide, we'll explore how sizing works, including inherent sizing, fixed sizing, and responsive sizing. We'll also delve into fully responsive layouts using the powerful [`FlexBox`](../../reference/layouts/FlexBox.ipynb) layout. + +## Explore Sizing Modes + +Panel provides various options to control the size of components: + +- `sizing_mode`: Allows toggling between fixed sizing and responsive sizing along vertical and/or horizontal dimensions. +- `width`/`height`: Allows setting a fixed width or height. +- `min_width`/`min_height`: Allows setting a minimum width or height if responsive sizing is set along the corresponding dimension. +- `max_width`/`max_height`: Allows setting a maximum width or height if responsive sizing is set along the corresponding dimension. + +:::{note} +Throughout this tutorial, you can execute the provided code directly in the Panel documentation using the green *run* button, in a notebook cell, or within a file named `app.py` served with `panel serve app.py --autoreload`. +::: + +## Inherent and Absolute Sizing + +Many components have inherent sizes. For instance, text will take up space based on its font size and content. Let's see how different sizing parameters affect the display: + +```{pyodide} +import panel as pn + +pn.extension() + +text = """A wind turbine is a renewable energy device that converts the kinetic energy from wind into electricity. It typically consists of a tall tower with large blades attached to a rotor. As the wind blows, it causes the rotor to spin, which in turn rotates a generator to produce electricity. Wind turbines are designed to harness the natural power of the wind and are used to generate clean, sustainable energy. They come in various sizes, from small residential turbines to massive commercial installations, and play a crucial role in reducing greenhouse gas emissions and meeting renewable energy goals.""" + +pn.panel(text).servable() +``` + +By restricting the `width`, we can force the text to wrap: + +```{pyodide} +import panel as pn + +pn.extension() + +text = """A wind turbine is a renewable energy device that converts the kinetic energy from wind into electricity. It typically consists of a tall tower with large blades attached to a rotor. As the wind blows, it causes the rotor to spin, which in turn rotates a generator to produce electricity. Wind turbines are designed to harness the natural power of the wind and are used to generate clean, sustainable energy. They come in various sizes, from small residential turbines to massive commercial installations, and play a crucial role in reducing greenhouse gas emissions and meeting renewable energy goals.""" + +pn.panel(text, width=300).servable() +``` + +Explicitly setting both `width` and `height` will force the display to scroll: + +```{pyodide} +import panel as pn + +pn.extension() + +text = """A wind turbine is a renewable energy device that converts the kinetic energy from wind into electricity. It typically consists of a tall tower with large blades attached to a rotor. As the wind blows, it causes the rotor to spin, which in turn rotates a generator to produce electricity. Wind turbines are designed to harness the natural power of the wind and are used to generate clean, sustainable energy. They come in various sizes, from small residential turbines to massive commercial installations, and play a crucial role in reducing greenhouse gas emissions and meeting renewable energy goals.""" + +pn.panel(text, width=300, height=100).servable() +``` + +## Sizing Mode + +The `sizing_mode` option toggles responsiveness. Let's create a fixed-size `Column` and observe its behavior: + +::::{tab-set} + +:::{tab-item} Stretch Width + +```{pyodide} +import panel as pn + +pn.extension() + +layout = pn.Spacer(styles={'background': 'green'}, sizing_mode='stretch_width', height=200) + +pn.Column(layout, width=400, height=400, styles={'border': '1px solid black'}).servable() +``` + +::: + +:::{tab-item} Stretch Height + +```{pyodide} +import panel as pn + +pn.extension() + +layout = pn.Spacer(styles={'background': 'green'}, sizing_mode='stretch_height', width=200) + +pn.Column(layout, width=400, height=400, styles={'border': '1px solid black'}).servable() +``` + +::: + +:::{tab-item} Stretch Both + +```{pyodide} +import panel as pn + +pn.extension() + +layout = pn.Spacer(styles={'background': 'green'}, sizing_mode='stretch_both') + +pn.Column(layout, width=400, height=400, styles={'border': '1px solid black'}).servable() +``` + +::: + +:::: + +### Exercises + +Let's dive in! + +#### Configure the Sizing Mode + +Rearrange the `Markdown` pane and Bokeh `figure` below such that they fully fill the available space but also ensure that the text never shrinks below 200 pixels and never grows above 500 pixels in width. + +```{pyodide} +import pandas as pd +import panel as pn +from bokeh.plotting import figure + +pn.extension() + +text = """A *wind turbine* is a renewable energy device that converts the kinetic energy from wind into electricity. It typically consists of a tall tower with large blades attached to a rotor. As the wind blows, it causes the rotor to spin, which in turn rotates a generator to produce electricity. Wind turbines are designed to harness the natural power of the wind and are used to generate clean, sustainable energy. They come in various sizes, from small residential turbines to massive commercial installations, and play a crucial role in reducing greenhouse gas emissions and meeting renewable energy goals.""" +data = pd.DataFrame( + [ + ("Monday", 7), + ("Tuesday", 4), + ("Wednesday", 9), + ("Thursday", 4), + ("Friday", 4), + ("Saturday", 5), + ("Sunday", 4), + ], + columns=["Day", "Wind Speed (m/s)"], +) + +fig = figure( + x_range=data["Day"], + title="Wind Speed by Day", + y_axis_label="Wind Speed (m/s)", + # <- insert argument here +) +fig.vbar(x=data["Day"], top=data["Wind Speed (m/s)"], width=0.5, color="navy", ) + +md = pn.pane.Markdown( + text, # <- insert arguments here +) +pn.Row(fig, md, height=500, sizing_mode="stretch_width").servable() +``` + +:::{hint} +**Hint 1**: Use `min_width`, `max_width`, and `sizing_mode` arguments. + +**Hint 2**: To ensure the content displays correctly, you may need to adjust both the underlying object's settings and the Panel component displaying it. +::: + +Test your solution by changing the window size of your browser. It should look like below. + + + +:::{dropdown} Solution + +```{pyodide} +import pandas as pd +import panel as pn +from bokeh.plotting import figure + +pn.extension() + +text = """A *wind turbine* is a renewable energy device that converts the kinetic energy from wind into electricity. It typically consists of a tall tower with large blades attached to a rotor. As the wind blows, it causes the rotor to spin, which in turn rotates a generator to produce electricity. Wind turbines are designed to harness the natural power of the wind and are used to generate clean, sustainable energy. They come in various sizes, from small residential turbines to massive commercial installations, and play a crucial role in reducing greenhouse gas emissions and meeting renewable energy goals.""" +data = pd.DataFrame( + [ + ("Monday", 7), + ("Tuesday", 4), + ("Wednesday", 9), + ("Thursday", 4), + ("Friday", 4), + ("Saturday", 5), + ("Sunday", 4), + ], + columns=["Day", "Wind Speed (m/s)"], +) + +fig = figure( + x_range=data["Day"], + title="Wind Speed by Day", + y_axis_label="Wind Speed (m/s)", + sizing_mode='stretch_both', +) +fig.vbar(x=data["Day"], top=data["Wind Speed (m/s)"], width=0.5, color="navy", ) + +md = pn.pane.Markdown( + text, sizing_mode="stretch_width", min_width=200, max_width=500, +) +pn.Row(fig, md, height=500, sizing_mode="stretch_width").servable() +``` + +::: + +#### Finetune with Widgets + +:::{tip} +**Pro Tip**: Speed up the process of fine-tuning by using interactive widgets instead of code. Panel's `pn.Param` provides an easy way to create widgets to control your Panel component. +::: + +Continue from the previous exercise solution. Add the `settings` component below your `Row`: + +```python +settings = pn.Param(md, parameters=["sizing_mode", "min_width", "max_width"]) +``` + +Try changing the values of the `sizing_mode`, `min_height`, and `max_height` widgets interactively. Observe the effect on the component's display as you adjust the values. + +:::{dropdown} Solution + +```{pyodide} +import pandas as pd +import panel as pn +from bokeh.plotting import figure + +pn.extension() + +text = """A *wind turbine* is a renewable energy device that converts the kinetic energy from wind into electricity. It typically consists of a tall tower with large blades attached to a rotor. As the wind blows, it causes the rotor to spin, which in turn rotates a generator to produce electricity. Wind turbines are designed to harness the natural power of the wind and are used to generate clean, sustainable energy. They come in various sizes, from small residential turbines to massive commercial installations, and play a crucial role in reducing greenhouse gas emissions and meeting renewable energy goals.""" +data = pd.DataFrame( + [ + ("Monday", 7), + ("Tuesday", 4), + ("Wednesday", 9), + ("Thursday", 4), + ("Friday", 4), + ("Saturday", 5), + ("Sunday", 4), + ], + columns=["Day", "Wind Speed (m/s)"], +) + +fig = figure( + x_range=data["Day"], + title="Wind Speed by Day", + y_axis_label="Wind Speed (m/s)", + sizing_mode='stretch_both', +) +fig.vbar(x=data["Day"], top=data["Wind Speed (m/s)"], width=0.5, color="navy", ) + +md = pn.pane.Markdown( + text, min_width=200, max_width=500, +) +settings = pn.Param(md, parameters=["sizing_mode", "min_width", "max_width"]) +pn.Row(fig, md, settings, height=500,).servable() +``` + +::: + +#### Change the Defaults + +:::{note} +The default `sizing_mode` of Panel is `"fixed"`. We can change the default `sizing_mode` via `pn.extension(sizing_mode=...)` or `pn.config.sizing_mode=...`. +::: + +Let's redo the initial `sizing_mode` exercise, but this time with a default `sizing_mode` of `"stretch_width"`. + +:::{dropdown} Solution + +```{pyodide} +import pandas as pd +import panel as pn +from bokeh.plotting import figure + +pn.extension(sizing_mode="stretch_width") + +text = """A *wind turbine* is a renewable energy device that converts the kinetic energy from wind into electricity. It typically consists of a tall tower with large blades attached to a rotor. As the wind blows, it causes the rotor to spin, which in turn rotates a generator to produce electricity. Wind turbines are designed to harness the natural power of the wind and are used to generate clean, sustainable energy. They come in various sizes, from small residential turbines to massive commercial installations, and play a crucial role in reducing greenhouse gas emissions and meeting renewable energy goals.""" +data = pd.DataFrame( + [ + ("Monday", 7), + ("Tuesday", 4), + ("Wednesday", 9), + ("Thursday", 4), + ("Friday", 4), + ("Saturday", 5), + ("Sunday", 4), + ], + columns=["Day", "Wind Speed (m/s)"], +) + +fig = figure( + x_range=data["Day"], + title="Wind Speed by Day", + y_axis_label="Wind Speed (m/s)", + sizing_mode='stretch_both', +) +fig.vbar(x=data["Day"], top=data["Wind Speed (m/s)"], width=0.5, color="navy", ) + +md = pn.pane.Markdown( + text, min_width=200, max_width=500, +) +pn.Row(fig, md, height=500,).servable() +``` + +::: + +## Responsive Layouts with FlexBox + +Responsive layouts are essential for adapting to various screen sizes. Panel's `FlexBox` layout provides this functionality out of the box. + +Let's try out `FlexBox`: + +```{pyodide} +import panel as pn +import random + +pn.extension() + +def create_random_spacer(): + return pn.Spacer( + height=100, + width=random.randint(1, 4) * 100, + styles={"background": "teal"}, + margin=5, + ) +spacers = [create_random_spacer() for _ in range(10)] + +pn.FlexBox(*spacers).servable() +``` + +Adjust the width of your browser window to observe the layout's responsiveness. + +:::{note} +We'll explore `FlexBox` in more detail in the [Control the Size (intermediate)](../intermediate/size.md) tutorial. +::: + +## Recap + +In this tutorial, we've explored various aspects of controlling component size in Panel. We've covered inherent sizing, fixed sizing, and responsive sizing, along with the powerful [`FlexBox`](../../reference/layouts/FlexBox.ipynb) layout: + +- `sizing_mode`: Allows toggling between fixed sizing and responsive sizing along vertical and/or horizontal dimensions. +- `width`/`height`: Allows setting a fixed width or height. +- `min_width`/`min_height`: Allows setting a minimum width or height if responsive sizing is set along the corresponding dimension. +- `max_width`/`max_height`: Allows setting a maximum width or height if responsive sizing is set along the corresponding dimension. + +## Resources + +### Tutorials + +- [Control the Size (intermediate)](../intermediate/size.md) + +### How-to + +- [Control Size](../../how_to/layout/size.md) diff --git a/doc/tutorials/basic/state.md b/doc/tutorials/basic/state.md new file mode 100644 index 0000000000..bfb0f979da --- /dev/null +++ b/doc/tutorials/basic/state.md @@ -0,0 +1,5 @@ +# Handle State + +COMING UP + +Build larger and more complex apps by defining and maintaining state via `pn.rx`. diff --git a/doc/tutorials/basic/style.md b/doc/tutorials/basic/style.md new file mode 100644 index 0000000000..987a3a8383 --- /dev/null +++ b/doc/tutorials/basic/style.md @@ -0,0 +1,103 @@ +# Enhance the Style + +Let's elevate the appearance of our Panel components using the power of [*CSS*](https://www.w3schools.com/css/)! + +By employing *CSS*, we can finely adjust the look and feel of our wind turbine data apps, ensuring they are visually appealing and user-friendly. + +:::{note} +To execute the code snippets below, feel free to run them directly in the Panel documentation via the convenient green *run* button, within a notebook cell, or within a `app.py` file served with `panel serve app.py --autoreload`. +::: + +## Utilize `styles` + +With `styles`, we can effortlessly style the **container** of a component. + +Give it a try: + +```{pyodide} +import panel as pn + +pn.extension() + +outer_style = { + 'background': '#f9f9f9', + 'border-radius': '5px', + 'border': '2px solid black', + 'padding': '20px', + 'box-shadow': '5px 5px 5px #bcbcbc', + 'margin': "10px", +} + +pn.indicators.Number( + name="Wind Speed", + value=8.6, + format="{value} m/s", + colors=[(10, "green"), (100, "red")], + styles=outer_style, +).servable() +``` + +Feel free to experiment by adjusting: + +- The `border` color from `black` to `teal`. +- The `padding` from `20px` to `50px`. + +## Employ `stylesheets` + +While `styles` beautify the outer container, they don't directly impact the styling of the **contents** within the component. Here enters `stylesheets`. + +Give this a go: + +```{pyodide} +import panel as pn + +pn.extension() + +outer_style = { + 'background': '#f9f9f9', + 'border-radius': '5px', + 'border': '2px solid black', + 'padding': '20px', + 'box-shadow': '5px 5px 5px #bcbcbc', + 'margin': "10px", +} + +inner_stylesheet = """ +div:nth-child(2) { + background-color: pink; + border-radius: 5px; + padding: 10px; + margin: 10px; +} +""" + +pn.indicators.Number( + name="Wind Speed", + value=8.6, + format="{value} m/s", + colors=[(10, "green"), (100, "red")], + styles=outer_style, + stylesheets=[inner_stylesheet] +).servable() +``` + +Feel free to play with changing the `background-color` from `pink` to `lightgray`. + +:::{tip} +For more insights into `stylesheets`, refer to the [Apply CSS](../../how_to/styling/apply_css.md) how-to guide. +::: + +## Wrapping Up + +With *CSS*, we can refine the style of our Panel components, ensuring they're visually appealing and provide a delightful user experience. + +## Resources + +### How-to + +- [Apply CSS](../../how_to/styling/apply_css.md) +- [Style Altair Plots](../../how_to/styling/altair.md) +- [Style Echarts Plots](../../how_to/styling/echarts.md) +- [Style Matplotlib Plots](../../how_to/styling/matplotlib.md) +- [Style Plotly Plots](../../how_to/styling/plotly.md) +- [Style Vega/ Altair Plots](../../how_to/styling/vega.md) diff --git a/doc/tutorials/basic/templates.md b/doc/tutorials/basic/templates.md new file mode 100644 index 0000000000..570578eb94 --- /dev/null +++ b/doc/tutorials/basic/templates.md @@ -0,0 +1,192 @@ +# Utilize Templates + +Welcome to the world of Panel templates! In this tutorial, we'll explore how to harness pre-made templates to effortlessly structure your app with a header, sidebar, and main area. + +Templates offer a streamlined approach to app layout and design, providing: + +- Ready-made templates accessible in the `pn.template` namespace. +- A variety of customizable options to suit your specific needs. + +## Crafting a Hello World App + +Let's start by creating a basic app using the [`FastListTemplate`](../../reference/templates/FastListTemplate.ipynb). Copy the following code into a file named `app.py`: + +```python +import panel as pn + +pn.extension() + +pn.template.FastListTemplate( + title="Hello World", + sidebar=["# Hello Sidebar", "This is text for the *sidebar*"], + main=["# Hello Main", "This is text for the *main* area"], +).servable() +``` + +Serve the app with: + +```bash +panel serve app.py --autoreload +``` + +It should resemble the following: + +![Hello World FastListTemplate App](../../_static/images/templates_hello_world.png) + +:::{note} +In the code snippet: + +- `pn.template.FastListTemplate` defines the template to use. +- `title` sets an optional title for the top header. +- `sidebar` and `main` designate content areas for the sidebar and main section, respectively. + +For additional configuration options, refer to the [`FastListTemplate` reference guide](../../reference/templates/FastListTemplate.ipynb). +::: + +:::{tip} +Panel offers a rich assortment of built-in templates, including a versatile [`Slides`](../../reference/templates/Slides.ipynb) template. +::: + +Take a moment to explore the [Templates Section](https://panel.holoviz.org/reference/index.html#templates) in the [Component Gallery](../../reference/index.md), then return here. + +## Integrating Templates in a Notebook + +While templates shine in serving apps, they're currently not displayable within notebooks. Copy the following two code cells into a notebook: + +```python +import panel as pn + +pn.extension() +``` + +```python +pn.template.FastListTemplate( + title="Hello World", + sidebar=["# Hello Sidebar", "This is text for the *sidebar*"], + main=["# Hello Main", "This is text for the *main* area"], +).servable() +``` + +Append a `;` after `.servable()` to prevent template display in the notebook. Preview the app; it should resemble: + +![Hello World FastListTemplate App](../../_static/images/templates_hello_world_notebook.png) + +:::{warning} +Notebook display of templates is not currently supported. Show your support for this feature by upvoting [Issue #2677](https://github.com/holoviz/panel/issues/2677). +::: + +## Tailoring the Template + +Let's customize the template further. Copy the code below into `app.py`: + +```python +import panel as pn +import pandas as pd +import altair as alt + +pn.extension("vega") + +ACCENT = "teal" + +image = pn.pane.JPG("https://assets.holoviz.org/panel/tutorials/wind_turbines_sunset.png") + +if pn.config.theme=="dark": + alt.themes.enable("dark") +else: + alt.themes.enable("default") + +@pn.cache # Add caching to only download data once +def get_data(): + return pd.read_csv("https://assets.holoviz.org/panel/tutorials/turbines.csv.gz") + +df = get_data() + +top_manufacturers = ( + df.groupby("t_manu").p_cap.sum().sort_values().iloc[-10:].index.to_list() +) +df = df[df.t_manu.isin(top_manufacturers)] +fig = ( + alt.Chart( + df.sample(5000), + title="Capacity by Manufacturer", + ) + .mark_circle(size=8) + .encode( + y="t_manu:N", + x="p_cap:Q", + yOffset="jitter:Q", + color=alt.Color("t_manu:N").legend(None), + tooltip=["t_manu", "p_cap"], + ) + .transform_calculate(jitter="sqrt(-2*log(random()))*cos(2*PI*random())") + .properties( + height="container", + width="container", + ) +) +plot = pn.pane.Vega(fig, sizing_mode="stretch_both", max_height=800, margin=20) + +pn.template.FastListTemplate( + title="Wind Turbine Manufacturers", + sidebar=[image, "**Note**: Only the 10 Manufacturers with the largest installed capacity are shown in the plot."], + main=["# Installed Capacity", plot], + accent=ACCENT, + main_layout=None, +).servable() +``` + +Serve the app with: + +```bash +panel serve app.py --autoreload +``` + +It should appear as shown below. Try toggling the theme button in the upper right corner. + +![Customized FastListTemplate App](../../_static/images/templates_customized_default.png) + +Upon toggling, the app should switch to dark mode: + +![Customized FastListTemplate App](../../_static/images/templates_customized_dark.png) + +:::{note} +In the code: + +- `pn.config.theme` determines the selected theme ("default" or "dark"). +- `alt.themes.enable("dark")` applies the "dark" theme to the plot. Panel doesn't do this automatically. +- `accent` sets the primary or accent color for the template, allowing quick branding of the app. +- `main_layout` specifies a layout to wrap each object in the main list. Choose from `"card"` (default) or `None`. + +Note that `accent` and `main_layout` are exclusive to Fast templates like [FastListTemplate](../../reference/templates/FastListTemplate.ipynb) and [FastGridTemplate](../../reference/templates/FastGridTemplate.ipynb). +::: + +## Recap + +In this tutorial, we've explored the power of pre-made templates for structuring your app with ease: + +- Templates are available in the `pn.template` namespace. +- Find a variety of templates in the [Templates Section](https://panel.holoviz.org/reference/index.html#templates) of the [Component Gallery](../../reference/index.md). +- Templates offer high customizability. + +## References + +### How-to Guides + +- [Arrange Components in a Template](../../how_to/templates/template_arrange.md) +- [Build a Custom Template](../../how_to/templates/template_custom.md) +- [Customize Template Theme](../../how_to/templates/template_theme.md) +- [Set a Template](../../how_to/templates/template_set.md) +- [Style Altair Plots](../../how_to/styling/altair.md) +- [Style Echarts Plots](../../how_to/styling/echarts.md) +- [Style Matplotlib Plots](../../how_to/styling/matplotlib.md) +- [Style Plotly Plots](../../how_to/styling/plotly.md) +- [Style Vega/ Altair Plots](../../how_to/styling/vega.md) +- [Toggle Modal](../../how_to/templates/template_modal.md) + +### Explanations + +- [Templates Overview](../../explanation/styling/templates_overview.md) + +### Component Gallery + +- Explore the [Templates Section](https://panel.holoviz.org/reference/index.html#templates) in the [Component Gallery](../../reference/index.md) for more options. diff --git a/doc/tutorials/basic/widgets.md b/doc/tutorials/basic/widgets.md new file mode 100644 index 0000000000..ac5cecac46 --- /dev/null +++ b/doc/tutorials/basic/widgets.md @@ -0,0 +1,124 @@ +# Accept Inputs with Widgets + +Welcome to the tutorial on accepting user inputs with widgets in Panel! Let's dive into the world of interactive components and explore how they can enhance your Panel applications. + +## Introduction to Widgets + +Widgets, found within the `pn.widgets` namespace, are powerful tools for capturing user input and interaction. They offer a wide range of functionality and customization options, making them essential for creating dynamic and engaging apps. + +You can explore the full array of available widgets and their detailed reference guides in the [Widgets Section](https://panel.holoviz.org/reference/index.html#widgets) of the [Component Gallery](../../reference/index.md). + +:::{note} +Widgets typically utilize the `value` parameter to capture user input. +Some widgets, such as the [`Button`](../../reference/widgets/Button.ipynb), even allow you to register callback functions that trigger actions upon interaction. + +For more complex scenarios, widgets like the [`Tabulator`](../../reference/widgets/Tabulator.ipynb) offer versatile input capabilities. + +In some cases, Panes can accept user input too. For example, the [`ECharts`](../../reference/panes/ECharts.ipynb), [`Plotly`](../../reference/panes/Plotly.ipynb), and [`Vega`](../../reference/panes/Vega.ipynb) (Altair) panes can accept user inputs. + +::: + +## Leveraging Widgets for Input + +Beyond just capturing clicks, widgets allow for a myriad of input types, from simple text to selection from lists. Let's explore some common scenarios: + +### Accept Clicks with Buttons + +Let's start by examining how to create a button that users can click to trigger actions: + +```{pyodide} +import panel as pn + +pn.extension() + +pn.widgets.Button( + name="Refresh", + icon="refresh", + button_type="primary", + description="Click to refresh the data", +).servable() +``` + +With features like icons, button types, and descriptions, buttons provide both functionality and visual cues to users. Hover over the button to see its description displayed as a helpful tooltip. + +:::{note} +The `.servable()` method is used to include the component in the app served by `panel serve app.py --autoreload`. This is not necessary for displaying the component in a notebook. +::: + +For a deeper understanding of the `Button` widget and its capabilities, refer to its detailed [reference guide](../../reference/widgets/Button.ipynb). + +### Accept Text Input + +Next, let's explore capturing textual input from users: + +```{pyodide} +import panel as pn + +pn.extension() + +pn.widgets.TextInput( + description="The text given to the AI", + disabled=True, + max_length=15, + name="Prompt", + placeholder="What is Python?", +).servable() +``` + +Hover over the input field to see its description as a tooltip. Try enabling the input by changing `disabled=True` to `disabled=False`. You'll also notice how the `max_length` parameter limits input length. + +To delve into the details of the `TextInput` widget, check out its [reference guide](../../reference/widgets/TextInput.ipynb). + +### Selecting from a List + +Another common scenario is allowing users to select options from a list: + +```{pyodide} +import panel as pn + +pn.extension() + +pn.widgets.Select( + description="Select a Technology", + name="Study", + options=["Wind Turbine", "Solar Panel", "Battery Storage"], +).servable() +``` + +Widgets like `Select` offer straightforward selection mechanisms. You can easily replace it with alternatives like `RadioButtonGroup` for different user experiences: + +```{pyodide} +import panel as pn + +pn.extension() + +pn.widgets.RadioButtonGroup( + description="Select a Technology", + name="Study", + options=["Wind Turbine", "Solar Panel", "Battery Storage"], +).servable() +``` + +## Recap + +In this tutorial, we've covered various ways to accept user input using widgets in Panel. From simple clicks to text input and selection from lists, widgets provide powerful tools for building interactive applications. + +Don't forget to explore the [Component Gallery](https://panel.holoviz.org/reference/index.html#widgets) for more widgets and their detailed reference guides. + +## Resources + +### Tutorials + +- [React to User Input](pn_bind.md) + +### How-To + +- [Migrate from Streamlit | Accept User Input](../../how_to/streamlit_migration/widgets.md) + +### Explanation + +- [Components Overview](../../explanation/components/components_overview.md) + +### Component Gallery + +- [Widgets](https://panel.holoviz.org/reference/index.html#widgets) diff --git a/doc/tutorials/components.md b/doc/tutorials/components.md deleted file mode 100644 index ce61e367d0..0000000000 --- a/doc/tutorials/components.md +++ /dev/null @@ -1,460 +0,0 @@ -# Components - -:::{note} Tutorial 2. **Core Components** -:icon: false - -Panel is a library that provides a lot of object types and while building an app, even a simple one, you will create and interact with many of them. Compare this for instance to the Pandas library, with which you'll normally interact with just a few object types (e.g. `Series`, `DataFrame`). It is important for you to understand the differences between the objects Panel offers and how you can use them. - -::: - -```{pyodide} -import panel as pn - -pn.extension(notifications=True) -``` - -The main objects that Panel provides, and that we are going to call *components* hereafter, short for *visual components*, include: - -- *Widgets*: widgets are components, usually quite small even if there are exceptions, that allow your users to interact with your app. Most importantly, they allow you to get user input! Examples include a text input, a checkbox, a slider, etc. -- *Panes*: panes are wrappers around some data that allow you to render that data, possibly customizing the rendering. Panel is known to support many date types, especially from the PyData ecosystem. You can indeed display a Pandas DataFrame, a Plotly plot, a Matplotlib plot, an Altair plot, all together on the same app! You can of course display HTML text or just raw text. Panes aren't limited to rendering data statically, they can allow for some user interactions and state syncing, like for instance the `Audio` or `Vega` panes. -- *Indicators*: indicators are useful to display some static state, they are indeed implemented as widgets that you can only control programmatically. You'll find for instance a progress indicator or a tooltip. -- *Layouts*: after having built various widgets, panes and indicators, it's time to display them together. Panel provides a dozen of layout components, including of course the most common `Row` and `Column` layouts. -- *Templates*: templates are components that render multiple Panel objects in an HTML document. The basic template, which you get when you serve an app without setting any template, is basically a blank canvas. Instead when you use one of the built-in templates you can easily improve the design and branding of your app, which will get for free a header, a sidebar, etc. -- *Notifications*: notifications are components that display so called "toasts", designed to mimic the push notifications that have been popularized by mobile and desktop operating systems. - -All the Panel components can be visualized on the [Component Gallery](../reference/index.md). - -:::{exercise} - -Visit the *Component Gallery* and spend a few minutes exploring the components it exposes. Spoiler alert, working on a Panel app you will spend a lot of time on this page looking for the right components and learning how they work! -::: - -:::{tip} - -Components usually have in their docstring a link to their documentation page, use `?` in a notebook or your IDE inspection capabilities to access the link. -::: - -## Parameterized components - -All components in Panel are built on the [Param](https://param.holoviz.org/) library. Each component declares a set of *Parameters* that control the behavior and output of the component. The basic idea however is that the *Parameter* values can be controlled both at the class-level: - -```{pyodide} -pn.widgets.IntRangeSlider.width = 350 -``` - -or on each instance: - -```{pyodide} -pn.widgets.IntRangeSlider(width=100) -``` - -## State syncing - -Be it in a notebook, in a served app, or in a Pyodide/PyScript app, Panel components sync their state between all views of the object. To better understand what that means, we create a `TextInput` widget and display it two times. Run the two following cells to render two views of the same widget. - -```{pyodide} -w_text = pn.widgets.TextInput() -w_text -``` - -```{pyodide} -w_text -``` - -This widget has many *Parameters* than can be set and will be synced, i.e. a programmatic update of their value will be reflected in the user interface, affecting its views. Run the two cells below and observe the two views above being updated. - -```{pyodide} -w_text.width = 100 -``` - -```{pyodide} -w_text.value = 'new text' -``` - -What you just experimented is one-way syncing from your code to the user interface, i.e. from your running Python interpreter to your browser tab. - -This can also work the other way around, i.e. when you modify a component from the user interface directly its Python state gets updated accordingly. Try this out by typing some text in one of the widgets above and then execute the cell below. You will see that what you typed is now reflected in the widget `value`. In this case we say that the `value` *Parameter* is bi-directionally synced (or linked). - -```{pyodide} -w_text.value -``` - -## Core components - -### Widgets - -More than 50 different widgets are available in the `pn.widgets` subpackage, varying from a simple text input to a more complex chat bot. The widget classes use a consistent API that allows treating broad categories of widgets as interchangeable. For instance, to select a value from a list of options, you can interchangeably use a `Select` widget, a `RadioButtonGroup`, or a range of other equivalent widgets. - -#### `value` - -Widgets **all** have a `value` *Parameter* that holds the widget state and that is bi-directionally synced. What is accepted for `value` is often a constrained *Parameter*. - -:::{exercise} - -Try setting the `value` of a `TextInput` widget with an object whose type is not `str`. Then run `widget.param` to see exactly what type is accepted for the widget `value`. -::: - -:::{note} - -One gotcha that doesn't only apply to the `value` *Parameter* but that you are more likely to encounter with this *Parameter* than others is when it is referencing a mutable data structure that you mutate inplace. Take for example a `MultiSelect` widget whose `value` is a `list`. If you programmatically update that list directly, with for example `append` or `extend`, Panel will not be able to detect that change. In which case you need to explicitly trigger updates with `w_multiselect.param.trigger('value')` that will run all the same underlying machinery as if you were setting the *Parameter* to a new value. A notable widget that holds a multable datastructure is the `Tabulator` widget whose `value` is a Pandas DataFrame that can be updated inplace with e.g. `df.loc[0, 'A'] = new_value`, its `patch` method allows to both update the data and the user interface. -::: - -```{pyodide} -w_multi = pn.widgets.MultiSelect(options=list('abc')) -w_multi -``` - -```{pyodide} -# Setting `value` updates the user interface as expected. -w_multi.value = ['a', 'b'] -``` - -```{pyodide} -# However, updating `value` in place doesn't. -w_multi.value.append('c') -``` - -```{pyodide} -# Trigger updates to also select 'c' in the user interface -w_multi.param.trigger('value') -``` - -#### Throttling - -There are two types of widgets that can be updated very frequently in the user interface: sliders (e.g. `IntSlider`, `FloatSlider`, `RangerSlider`) and text inputs (e.g. `TextInput`, `AutocompleteInput`). In some cases you may want to react on every state update of these widgets, in some others you only want to react when the user has finished interacting with the widget. For instance, you certainly do not want to run a long simulation on every step of a slider being dragged by your user, but you may want a plot to be updated live when your user moves another slider. Panel provides additional *Parameters* to cover those cases: - -- sliders: the `value` *Parameter* is updated continuously when the slider is being dragged and the `value_throttled` *Parameter* is updated only when the handle on the slider is released (less updates) -- text inputs: the `value` *Parameter* is only updated when the *Enter* key is pressed or the widget loses focus and the `value_input` *Parameter* is updated on every key press (more updates). - -```{pyodide} -pn.config.throttled = True -``` - -```{pyodide} -slider = pn.widgets.IntSlider(start=0, end=10) -``` - -```{pyodide} -text = pn.widgets.TextInput() -``` - -```{pyodide} -text.value_input -``` - -```{pyodide} -slider.param.value_throttled -``` - -#### `name` - -Most widgets can have a caption that is set with the `name` *Parameter*. - -```{pyodide} -pn.widgets.Button(name='Click me!') -``` - -```{pyodide} -pn.widgets.TextInput(name='Age:') -``` - -#### `description` - -The `description` *Parameter* was added in Panel 1.0 to some widgets. It adds a tooltip icon next to the widget label, it supports rendering HTML content. - -```{pyodide} -pn.widgets.Select( - name='Mode', description='
  • Mode 1: ...
  • Mode 2: ...
', - options=[1, 2] -) -``` - -### Panes - -Pane objects makes it possible to display a wide range of plots and other media on a page, including plots (e.g. Matplotlib, Bokeh, Vega/Altair, HoloViews, Plotly, Vizzu), images (e.g. PNGs, SVGs, GIFs, JPEGs), various markup languages (e.g. Markdown, HTML, LaTeX) and DataFrames. Panes are available under the `pn.pane` subpackage. - -#### `pn.panel()` - -Panel provides `pn.panel` as a convenient helper function that will convert objects into a Pane. The utility resolves the appropriate representation for an object by checking all Pane object types available and then ranking them by priority. When passing a string (for instance) there are many representations, but the PNG pane takes precedence if the string is a valid URL or local file path ending in ".png". - -```{pyodide} -png = pn.panel('https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png', width=200) -png -``` - -```{pyodide} -row = pn.Row('# Title') -``` - -We can check that `PNG` is indeed the Pane type inferred by the utility. - -```{pyodide} -print(png) -``` - -#### `object` - -Panes **all** store the object they are wrapping on the `object` *Parameter*. Existing views of a pane will update when `object` is set to a new value. Let's verify that with the `Markdown` pane, run the next cells to display two views of the same pane and update both of them by setting `object` with a new string. - -```{pyodide} -p_md = pn.pane.Markdown('# Title') -p_md -``` - -```{pyodide} -p_md.object -``` - -```{pyodide} -p_md.object = '# New title' -``` - -### Indicators - -Indicators are useful to show transient state (e.g. progress bar), a numerical indicator or a text indicator (e.g. tooltip). They sit in between widgets and panes, they have a `value` *Parameter* that holds their state but cannot be modified from the user interface. - -```{pyodide} -i_number = pn.indicators.Number( - name='Total Amount', value=3.5, format='{value}K', - colors=[(5, 'green'), (10, 'red')] -) -i_number -``` - -```{pyodide} -i_number.value = 6 -``` - -Indicators work well with asynchronous generators to quickly update their state without blocking. - -### Layouts - -Layouts, aka *Panels*, allow arranging other components objects into fixed-size or responsively resizing layouts, building simple apps or complex dashboards. The whole Panel library is designed to make it easy to create such objects, which is why it takes its name from them. - -There are four main types of Panels: - -- A `Row` arranges a list of components horizontally. -- A `Column` arranges a list of components vertically. -- A `Tabs` object lays out a list of components as selectable tabs. -- A `GridSpec` lays out components on a grid. - -We aren't going to explore all these types of layouts, instead we are going to focus on a key difference between some of them that affects how you interact with them: - -- The `Row`, `Column`, `Tabs`, `GridBox`, `FlexBox`, `Accordion` and `FloatPanel` layouts have list-like semantics, which means they have many of the same methods as a simple Python `list`, making it easy to add, replace, and remove components interactively using `append`, `extend`, `clear`, `insert`, `pop`, `remove` and `__setitem__`. These methods make it possible to interactively configure and modify an arrangement of plots, making them an extremely powerful tool for building apps or dashboards. -- The `GridSpec` and `GridStack` layout is quite different from the other layout types in that it isn’t list-like. Instead it behaves more like a 2D array that automatically expands when assigned to. This property makes it a very powerful means of declaring a dashboard layout with either a fixed size or with responsive sizing, i.e. one that will rescale with the browser window. - -All the layout objects can be initialized as empty. When initialized with some objects, or when some new objects are set, if these objects aren't Panel components the `pn.panel` helper will be called internally to convert it into a displayable representation (typically a Pane). - -#### List-like API - -List-like layouts can be initialized with a variable number of objects. To start with, we will declare a `Column` and populate it with a title and a widget. - -```{pyodide} -column = pn.Column('# A title', pn.widgets.FloatSlider()) -column -``` - -Next we add another bit of markdown. - -```{pyodide} -column.append('* Item 1\n* Item 2') -``` - -Then we add a few more widgets. - -```{pyodide} -column.extend([pn.widgets.TextInput(), pn.widgets.Checkbox(name='Tick this!')]) -``` - -And finally we change our mind and replace the `Checkbox` with a button. - -```{pyodide} -column[4] = pn.widgets.Button(name='Click here') -``` - -The ability to add, remove, and replace items using list operations opens up the possibility of building rich and responsive GUIs with the ease of manipulating a Python list! You can inspect the structure of a layout calling `print`. - -```{pyodide} -print(column) -``` - -The `Tabs` and `Accordion` layouts behave similarly, however, when adding or replacing items, it is also possible to pass a tuple providing a custom title for the tab - -```{pyodide} -tabs = pn.Tabs(('Text', 'Some text')) -tabs -``` - -```{pyodide} -# Insert a tab with a title -tabs.insert(0, ('Slider', pn.widgets.FloatSlider())) -``` - -#### Grid-like API - -Grid-like layouts are initialized empty and populated setting 2D assignments to specify the index or span on indices the object in the grid should occupy. Just like a Python array, the indexing is zero-based and specifies the rows first and the columns second, i.e. `gridlike[0, 1]` would assign an object to the first row and second column. - -To demonstrate the abilities, let us declare a grid with a wide range of different objects, including `Spacers`, HoloViews objects, images, and widgets. - -```{pyodide} -import holoviews as hv - -hv.extension('bokeh') - -pn.extension('gridstack') -``` - -```{pyodide} -gspec = pn.GridStack(sizing_mode='stretch_width', height=500) -gspec[0, :3] = pn.Spacer(styles=dict(background='#FF0000')) -gspec[1:3, 0] = pn.Spacer(styles=dict(background='#0000FF')) -gspec[1:3, 1:3] = hv.Scatter([0, 1, 0]).opts(shared_axes=False) -gspec[3:5, 0] = hv.Curve([1, 2, 3]) -gspec[3:5, 1] = 'https://upload.wikimedia.org/wikipedia/commons/4/47/PNG_transparency_demonstration_1.png' -gspec[4:5, 2] = pn.Column( - pn.widgets.FloatSlider(), - pn.widgets.ColorPicker(), - pn.widgets.Toggle(name='Toggle Me!')) - -gspec -``` - -### Shared *Parameters* - -Widgets, indicators, panes and layouts all share a set of *Parameters* which are going to be briefly introduced in the following sections. - -#### `loading` - -Loading spinners are everywhere on modern apps and for good reasons, they let your users know that your app is doing some work for them! Fortunately they are very easy to set up in Panel, just set the `loading` *Parameter* to `True` or `False` on a component to display and hide its loading spinner. - -```{pyodide} -p_md = pn.pane.Markdown('# Title', loading=True) -p_md -``` - -```{pyodide} -pn.config.loading_spinner = 'petal' -pn.config.loading_color = 'red' -``` - -```{pyodide} -p_md.loading = False -``` - -#### `visible` - -Sometimes it's useful to just completely hide a component, for instance to hide some advanced options. Again that's very easy to do, just set the `visible` *Parameter* to `False` on a component you want to hide. - -```{pyodide} -w_text = pn.widgets.TextInput(name='Advanced') -w_text -``` - -```{pyodide} -w_text.visible = True -``` - -#### Style - -A few *Parameters* allow to control the style of components, including `styles`, `stylesheets`, `css_classes` and `design`. These will be explored in more details in one of the next guides. As a teaser, the next cell is a simple example leveraging the `styles` *Parameter* only, that accepts a dictionary of CSS styles. - -```{pyodide} -custom_style = { - 'background': '#f9f9f9', - 'border': '1px solid black', - 'padding': '10px', - 'box-shadow': '5px 5px 5px #bcbcbc' -} - -pn.widgets.FloatSlider(name='Number', styles=custom_style) -``` - -#### Size and responsivity - -A few *Parameters* allow to control the size and responsivity of components, including `height`, `width`, `min_height`, `min_width` and `sizing_mode`. These will be explored in more details in one of the next guides. - -#### `margin` - -The `margin` *Parameter* can be used to create space around an element defined as the number of pixels at the (top, right, bottom, and left). When you set it with a single value the margin is going to be applied to each side of the element, `margin` allows for more fine-grained distributio of the margin. - -```{pyodide} -pn.widgets.Button(name='Click', margin=(25, 0, 0, 0)) -``` - -#### `align` - -The `align` *Parameter* controls how components align vertically and horizontally. It supports `‘start’`, `‘center’`, and `‘end’` values and can be set for both horizontal and vertical directions at once or for each separately by passing in a tuple of the form `(horizontal, vertical)`. - -```{pyodide} -pn.Row( - pn.widgets.IntSlider(), - pn.widgets.IntSlider(align=('center', 'start')), - height=100, - styles={'background': 'lightgrey'}, -) -``` - -## Templates - -A template is the HTML document that ends up being served by your app, it defines what resources (Javascript, CSS) need to be loaded, the page title, where the Panel objects are supposed to be rendered on the page, etc. - -When you serve an app without defining a particular template Panel serves it with its default template, that is pretty much a blank canvas where the served objects, if there are a few of them, will be rendered vertically one after the other. - -Try saving the following snippet in a `app.py` file and serving it with `panel serve app.py --show` - -```python -import panel as pn - -pn.panel('# Title').servable() -pn.panel('Some text').servable() -pn.panel('More text').servable() -``` - -When developing an app, someone (possibly you!) will require at some point to make it prettier! A quick way to achieve that is to wrap your app in one of the templates that Panel provides, that are defined by declaring four main content areas on the page, which can be populated as desired: - -- `header`: The header area of the HTML page -- `sidebar`: A collapsible sidebar -- `main`: The main area of the application -- `modal`: A modal, i.e. a dialog box/popup window - -These four areas behave very similarly to layouts that have list-like semantics. This means we can easily append new components into these areas. Unlike other layout components however, the contents of the areas is fixed once rendered. If you need a dynamic layout you should therefore insert a regular layout (e.g. a `Column` or `Row`) and modify it in place once added to one of the content areas. - - - -:::{warning} -The templates provided by Panel should not be rendered in a notebook, as their CSS styling usually doesn't play well with the CSS styling of the notebook itself. -::: - - -Since an app can only have one template, Panel allows to declare the app template via `pn.extension(template='..')`. Of course you can also explicitly instantiate a template and manipulate it as you would do with other Panel objects. Try serving the content of this snippet. - -```python -import panel as pn - - -template = pn.template.BootstrapTemplate(title='Loving Panel!') - - -def compute(i): - return '❤️' * i - - -w_number = pn.widgets.IntSlider(value=5, start=1, end=5) -p_hearts = pn.panel(pn.bind(compute, w_number)) - -template.sidebar.append(w_number) -template.main.append(p_hearts) -template.show() -``` - -## Notifications - -The web apps you end up building with Panel are often quite interactive. Therefore you will be interested in finding a way to let your users know what's going on, when their operations succeed or abort, etc. This is exactly what notifications are for! Contrary to the components we have just covered, notifications are objects you don't manipulate directly, instead you just call `pn.state.notifications` with one the following methods: `success`, `info`, `warning` and `error`. - -```{pyodide} -pn.state.notifications.success('Your first notification!') -``` - -```{pyodide} -pn.state.notifications.error('Oops!', duration=5000) -``` diff --git a/doc/tutorials/expert/index.md b/doc/tutorials/expert/index.md new file mode 100644 index 0000000000..ef0b0d571a --- /dev/null +++ b/doc/tutorials/expert/index.md @@ -0,0 +1,13 @@ +# Expert Tutorials + +These tutorials are for you that is ready to pioneer and push the boundaries of what can be achieved with Panel. + +Together, we will extend Panel to support your specialized domain and use case. + +We will assume you have an *intermediate skill level* corresponding to the what you get from the [Intermediate Tutorials](../basic/index.md). + +## Extend Panel + +- **Develop Custom Components**: Use `ReactiveESM` or `ReactiveHTML` to build advanced components utilizing JavaScript. +- **Develop Custom Bokeh models**: +- **Customizing Panel for your brand**: diff --git a/doc/tutorials/index.md b/doc/tutorials/index.md index 181e1577ed..f23e26a65d 100644 --- a/doc/tutorials/index.md +++ b/doc/tutorials/index.md @@ -1,59 +1,55 @@ # Tutorials -The [getting started guides](../getting_started/index.md) aims to get you set up with Panel and introduces you to some of the core concepts. The tutorials on the other hand will take you from being a beginner and guide you through your learning. +Welcome to our tutorials designed for those eager to dive into the world of Panel! If you're still undecided, we suggest starting with the [Getting Started Guide](../getting_started/index.md) to get a taste of what Panel has to offer. ---- +Throughout this series, we'll delve into wind turbine data and craft some impressive wind turbine data apps using Panel by HoloViz. We firmly believe that the skills you'll acquire are not only valuable but also easily applicable across various domains, including yours. + +Before we begin, remember to put on your safety helmet – we're about to venture into the world of wind turbine data apps! + +## Select Tutorials + +Choose the tutorials that best match your skill level. ::::{grid} 1 2 2 3 :gutter: 1 1 1 2 -:::{grid-item-card} {octicon}`plug;2.5em;sd-mr-1` Param -:link: param +:::{grid-item-card} {octicon}`rocket;2.5em;sd-mr-1` Basic +:link: basic/index :link-type: doc -Panel and other projects in the HoloViz ecosystem all build on Param. In this section you will learn the basic concepts behind Param that you need to know to become an effective user of Panel. -::: +These tutorials are perfect for those just starting their journey with Panel. -:::{grid-item-card} {octicon}`container;2.5em;sd-mr-1` Components -:link: components -:link-type: doc +Together, we'll explore the wind turbine dataset and create exciting projects like a chatbot, a static report, a todo app, an image classifier app, a dashboard, and even a streaming application. + +By the end, you'll have the skills to develop polished, single-page apps efficiently using a *function-based approach*. -Panel is a library that provides a lot of object types and while building an app, even a simple one, you will create and interact with many of them. In this section you will get a high-level overview of the different component types Panel offers and how to use them. +Prepare to become a Panel *Hero*! ::: -:::{grid-item-card} {octicon}`pulse;2.5em;sd-mr-1` Interactivity -:link: interactivity +:::{grid-item-card} {octicon}`mortar-board;2.5em;sd-mr-1` Intermediate +:link: intermediate/index :link-type: doc -In this section you learn how to leverage Parameters and dependencies on parameters to add interactivity. In particular we will focus on implementing interactivity through reactivity, rather than the more imperative style of programming you might be used to from other UI frameworks. -::: +Ready to dive deeper into the world of Python and Panel? -:::{grid-item-card} {octicon}`browser;2.5em;sd-mr-1` Effective Development -:link: development -:link-type: doc +These tutorials are tailored for those eager to explore more advanced features. -In this section we will introduce you to the most important concepts you need to know to become an effective Panel developer in notebooks and your favorite editor. -::: +We'll enhance our applications from the basic tutorial, focusing on performance, scalability, reusability, and maintainability. -:::{grid-item-card} {octicon}`rows;2.5em;sd-mr-1` Layouts -:link: layouts -:link-type: doc +Upon completion, you'll be equipped to build large, complex multi-page apps using a *class-based approach*. -In this section we will discover how layouts and sizing works in Panel, taking you through the difference between inherent sizing, fixed sizing and responsive sizing and then cover responsive layouts. +You're on your way to becoming a Panel *Expert*! ::: -:::{grid-item-card} {octicon}`code;2.5em;sd-mr-1` Structuring Applications -:link: structure +:::{grid-item-card} {octicon}`star;2.5em;sd-mr-1` Expert +:link: expert/index :link-type: doc -In this section we will take you through the process of structuring a more complex application, discussing different approaches for managing reactivity and how to create a composable architecture for your applications. -::: +Ready to push the boundaries of Panel and become a pioneer in your field? -:::{grid-item-card} {octicon}`paintbrush;2.5em;sd-mr-1` Styling -:link: styling -:link-type: doc +These tutorials are for the daring souls who want to extend Panel's capabilities to suit their specialized domains and use cases. -In this section we will review different approaches for styling components, from applying `Design` components, through applying `stylesheets` and `css_classes`. +Once you've completed these tutorials, you'll truly earn the title of Panel *Rock Star*. ::: :::: @@ -63,11 +59,9 @@ In this section we will review different approaches for styling components, from :hidden: :maxdepth: 2 -param -components -interactivity -development -layouts -structure -styling +basic/index +intermediate/index +expert/index ``` + +Let the wind turbine adventure begin! 🌬️🌀 diff --git a/doc/tutorials/intermediate/develop_editor.md b/doc/tutorials/intermediate/develop_editor.md new file mode 100644 index 0000000000..119173ddd5 --- /dev/null +++ b/doc/tutorials/intermediate/develop_editor.md @@ -0,0 +1,112 @@ +# Develop in an Editor + +In this section you will learn more advances techniques to develop efficiently in an editor: + +- Debug with [Pdb](https://docs.python.org/3/library/pdb.html) by inserting a `breakpoint()` + +:::{note} +This guide builds upon the [Develop in an Editor (beginner)](../basic/develop_editor.md) tutorial. +::: + +:::{note} +Some of the features demonstrated in this guide might require special configuration of your editor. For configuration we refer you to the [Resources](#resources) section below and general resources on the web. +::: + +## Debug your App with Pdb + +A simple way to debug your apps that works in any editor is to insert a `breakpoint()`. + +Copy the code below into a file named `app.py`. + +```python +import panel as pn + +pn.extension(design="material") + +def handle_click(event): + breakpoint() + +pn.widgets.Button(name="Click Me", on_click=handle_click, button_type="primary").servable() +``` + +Serve the app with `panel serve app.py --autoreload`. + +Open [http://localhost:5006/app](http://localhost:5006/app) in a browser. + +The app will look something like + +![App with `Click Me` button](../../_static/images/develop_editor_click_me.png) + +Click the `Click Me` Button. + +Your terminal will look something like + +```bash +$ panel serve app.py --autoreload +2024-01-20 08:12:09,512 Starting Bokeh server version 3.3.3 (running on Tornado 6.4) +2024-01-20 08:12:09,514 User authentication hooks NOT provided (default user enabled) +2024-01-20 08:12:09,516 Bokeh app running at: http://localhost:5006/app +2024-01-20 08:12:09,516 Starting Bokeh server with process id: 9768 +2024-01-20 08:12:10,608 WebSocket connection opened +2024-01-20 08:12:10,608 ServerConnection created +--Return-- +> /home/jovyan/app.py(6)handle_click()->None +-> breakpoint() +(Pdb) +``` + +Write `event` in the terminal. Press `ENTER`. + +It should look like + +![Breakpoint](../../_static/images/develop_editor_breakpoint.png) + +Write `help` and press `ENTER` for more info. It will look like + +```bash +(Pdb) help + +Documented commands (type help ): +======================================== +EOF c d h list q rv undisplay +a cl debug help ll quit s unt +alias clear disable ignore longlist r source until +args commands display interact n restart step up +b condition down j next return tbreak w +break cont enable jump p retval u whatis +bt continue exit l pp run unalias where + +Miscellaneous help topics: +========================== +exec pdb +``` + +Write `c` and press `ENTER` to continue running the code and server. + +:::{note} +For more about debugging with [Pdb](https://docs.python.org/3/library/pdb.html) and `breakpoint` please check out the [PDB Documentation](https://docs.python.org/3/library/pdb.html). +::: + +:::{note} +For *integrated debugging* in your editor, please refer to the [Resources](#resources) section below and general resources on the web. +::: + +## Recap + +You have learned to + +- Debug with [Pdb](https://docs.python.org/3/library/pdb.html) by inserting a `breakpoint()` + +## Resources + +### Tutorials + +- [Develop in an Editor (Beginner)](../basic/develop_editor.md) + +### How-to + +- [Configure VS Code](../../how_to/editor/vscode_configure.md) + +## Explanation + +- [Develop Seamlessly](../../explanation/develop_seamlessly.md) diff --git a/doc/tutorials/intermediate/index.md b/doc/tutorials/intermediate/index.md new file mode 100644 index 0000000000..00df1402ae --- /dev/null +++ b/doc/tutorials/intermediate/index.md @@ -0,0 +1,43 @@ +# Intermediate Tutorials + +Welcome to the Intermediate Tutorials! + +Ready to take your Panel skills to the next level? Dive into these tutorials to explore advanced features, best practices, and techniques for building robust and scalable apps with Panel. Whether you're aiming for better code organization, improved performance, or exploring additional topics, we've got you covered! + +## Prerequisites + +Before delving into the intermediate tutorials, ensure you have a solid understanding of Panel basics. If not, refer to our [Basic Tutorial](../basic/index.md) to get started. + +## Mastering Intermediate Panel Techniques + +Ready to transition from being a *basic* to an *intermediate* Panel user? Let's uncover techniques for constructing reusable components and structuring your projects with maintainability in mind: + +- **[Class-Based Approach](param.md):** Construct reusable components utilizing Param and a class-based approach. +- **[Advanced Interactivity](interactivity.md):** Harness the power of Parameters and parameter dependencies to infuse interactivity. +- **Introduce Side Effects:** Infuse your apps with additional functionality using `.watch` and `watch=True`. +- **Create Reusable Components:** Engineer reusable Panel components using the Viewer class. +- **[Structuring with DataStore](structure_data_store.md):** Employ the DataStore pattern to organize larger applications efficiently. +- **Organize your Project:** Maintain orderliness in larger applications by compartmentalizing them into multiple modules and folders. + +## Enhancing Performance + +Supercharge your app's performance through asynchronous programming, threaded operations, and efficient task scheduling: + +- **Schedule Tasks:** Leverage functionalities such as `pn.state.onload`, `pn.state.schedule_task`, `pn.state.add_periodic_callback`, `pn.state.on_session_created`, `pn.state.on_session_destroyed`, `async` generators, and `pn.state.execute`. +- **Concurrent Execution:** Unleash the full potential of Panel by embracing threads and async operations to execute tasks concurrently. + +## Exploring Additional Topics + +Embark on a deeper exploration of supplementary topics to further hone your Panel development prowess: + +- **[Efficient Development in Editors](develop_editor.md):** Streamline the debugging process within your preferred editor environment. +- **[Serving Panel Apps](serve.md):** Serve multi-page apps effortlessly while customizing the Panel server to suit your needs. +- **[Advanced Layouts](size.md):** Attain responsive sizing with ease using FlexBox and media queries. + +## Projects + +Now that you've mastered the more advanced concepts of Panel, it's time to put your skills to the test: + +- **Create an Interactive Report:** Elevate the interactivity of your reports through embedding. +- **Serve Apps without a Server:** Explore the realm of WASM to serve your apps without traditional servers. +- **Build a Streaming Dashboard:** Engineer a high-performing streaming dashboard employing a *producer/consumer* architecture. diff --git a/doc/tutorials/interactivity.md b/doc/tutorials/intermediate/interactivity.md similarity index 100% rename from doc/tutorials/interactivity.md rename to doc/tutorials/intermediate/interactivity.md diff --git a/doc/tutorials/param.md b/doc/tutorials/intermediate/param.md similarity index 96% rename from doc/tutorials/param.md rename to doc/tutorials/intermediate/param.md index 219afc3530..6fc1ba8442 100644 --- a/doc/tutorials/param.md +++ b/doc/tutorials/intermediate/param.md @@ -1,6 +1,8 @@ -# Param +# Reactive Parameters -:::{note} Tutorial 1. **Param** +NOTE: ChatGPT suggests this is *basic* level stuff: You can include this section within the "Basic" tutorials as it seems like it covers fundamental concepts that newcomers might need to know before diving into more complex topics. I don't agree, but I am in doubt. + +:::{note} Tutorial 1. **Reactive Parameters** :icon: false Panel and other projects in the HoloViz ecosystem all build on Param. Param provides a framework to add validation, documentation and interactivity to a project. It is similar to more modern projects such as Pydantic but focuses primarily on providing APIs that make it easy to express complex dependencies, reactivity and UI interactions. @@ -93,7 +95,7 @@ with text_input.param.update(value='Temporary'): ### Validation -The primary purpose of parameters is to perform validation, e.g. if I try to assign an invalid value to a parameter, we will get an error: +The primary purpose of parameters is to perform validation, e.g. if we try to assign an invalid value to a parameter, we will get an error: ```{pyodide} import traceback as tb diff --git a/doc/tutorials/intermediate/serve.md b/doc/tutorials/intermediate/serve.md new file mode 100644 index 0000000000..4b1568e38b --- /dev/null +++ b/doc/tutorials/intermediate/serve.md @@ -0,0 +1,256 @@ +# Serve Apps + +In this tutorial you will learn more advanced techniques to serve Panel apps: + +- serve a multi page app by providing a list of files or [*globs*](https://en.wikipedia.org/wiki/Glob_(programming)). +- list the configuration options of `panel serve` by adding the flag `--help`. + +:::{note} +This guide builds upon the [Serve Panel Apps (beginner)](../basic/) tutorial. +::: + +## Serve a multi-page app + +You can serve an app with multiple pages just by providing the file paths to `panel serve`. + +Copy the code below into a file named `app.py` and save the file + +```python +import panel as pn + +pn.extension() + +pn.panel("Hello World").servable() +``` + +Copy the 2 code cells below into a clean notebook named `app2.ipynb`. + +```python +import panel as pn + +pn.extension() +``` + +```python +pn.panel("Hello Notebook World").servable() +``` + +Run the cells and save the notebook if you have not already done it. + +Run `panel serve app.py app2.ipynb --autoreload`. + +It should look like + +```bash +$ panel serve app.py app2.ipynb --autoreload +2024-01-17 21:14:43,502 Starting Bokeh server version 3.3.3 (running on Tornado 6.4) +2024-01-17 21:14:43,503 User authentication hooks NOT provided (default user enabled) +2024-01-17 21:14:43,506 Bokeh app running at: http://localhost:5006/app +2024-01-17 21:14:43,506 Bokeh app running at: http://localhost:5006/app2 +2024-01-17 21:14:43,506 Starting Bokeh server with process id: 39832 +``` + +Open [http://localhost:5006](http://localhost:5006), [http://localhost:5006/app](http://localhost:5006/app) and [http://localhost:5006/app2](http://localhost:5006/app2). + +It will look something like + +![Panel serve multi page app](../../_static/images/panel-serve-multipage-app.png) + +:::{note} +You can also use one or more [*globs*](https://en.wikipedia.org/wiki/Glob_(programming)) to serve a long or dynamic list of apps. For example `apps/*`, `pages/*.py`, `examples/*.ipynb` or `docs/*.md`. +::: + +## Configure `panel serve` + +The command **`panel serve` is highly configurable**. + +Run `panel serve --help` in a terminal. + +It should look like the below. + +```bash +$ panel serve --help +usage: panel serve [-h] [--port PORT] [--address ADDRESS] [--unix-socket UNIX-SOCKET] [--log-level LOG-LEVEL] [--log-format LOG-FORMAT] + [--log-file LOG-FILE] [--use-config CONFIG] [--args ...] [--dev [FILES-TO-WATCH ...]] [--show] + [--allow-websocket-origin HOST[:PORT]] [--prefix PREFIX] [--ico-path ICO_PATH] [--keep-alive MILLISECONDS] + [--check-unused-sessions MILLISECONDS] [--unused-session-lifetime MILLISECONDS] [--stats-log-frequency MILLISECONDS] + [--mem-log-frequency MILLISECONDS] [--use-xheaders] [--ssl-certfile CERTFILE] [--ssl-keyfile KEYFILE] [--session-ids MODE] + [--auth-module AUTH_MODULE] [--enable-xsrf-cookies] [--exclude-headers EXCLUDE_HEADERS [EXCLUDE_HEADERS ...]] + [--exclude-cookies EXCLUDE_COOKIES [EXCLUDE_COOKIES ...]] [--include-headers INCLUDE_HEADERS [INCLUDE_HEADERS ...]] + [--include-cookies INCLUDE_COOKIES [INCLUDE_COOKIES ...]] [--cookie-secret COOKIE_SECRET] [--index INDEX] + [--disable-index] [--disable-index-redirect] [--num-procs N] [--session-token-expiration N] + [--websocket-max-message-size BYTES] [--websocket-compression-level LEVEL] [--websocket-compression-mem-level LEVEL] + [--glob] [--static-dirs KEY=VALUE [KEY=VALUE ...]] [--basic-auth BASIC_AUTH] [--oauth-provider OAUTH_PROVIDER] + [--oauth-key OAUTH_KEY] [--oauth-secret OAUTH_SECRET] [--oauth-redirect-uri OAUTH_REDIRECT_URI] + [--oauth-extra-params OAUTH_EXTRA_PARAMS] [--oauth-jwt-user OAUTH_JWT_USER] [--oauth-encryption-key OAUTH_ENCRYPTION_KEY] + [--oauth-error-template OAUTH_ERROR_TEMPLATE] [--oauth-expiry-days OAUTH_EXPIRY_DAYS] [--oauth-refresh-tokens] + [--oauth-guest-endpoints [OAUTH_GUEST_ENDPOINTS ...]] [--oauth-optional] [--login-endpoint LOGIN_ENDPOINT] + [--logout-endpoint LOGOUT_ENDPOINT] [--auth-template AUTH_TEMPLATE] [--logout-template LOGOUT_TEMPLATE] + [--basic-login-template BASIC_LOGIN_TEMPLATE] [--rest-provider REST_PROVIDER] [--rest-endpoint REST_ENDPOINT] + [--rest-session-info] [--session-history SESSION_HISTORY] [--warm] [--admin] [--admin-endpoint ADMIN_ENDPOINT] + [--admin-log-level {debug,info,warning,error,critical}] [--profiler PROFILER] [--autoreload] [--num-threads NUM_THREADS] + [--setup SETUP] [--liveness] [--liveness-endpoint LIVENESS_ENDPOINT] [--reuse-sessions] [--global-loading-spinner] + [DIRECTORY-OR-SCRIPT ...] + +positional arguments: + DIRECTORY-OR-SCRIPT The app directories or scripts to serve (serve empty document if not specified) + +options: + -h, --help show this help message and exit + --port PORT Port to listen on + --address ADDRESS Address to listen on + --unix-socket UNIX-SOCKET + Unix socket to bind. Network options such as port, address, ssl options are incompatible with unix socket + --log-level LOG-LEVEL + One of: trace, debug, info, warning, error or critical + --log-format LOG-FORMAT + A standard Python logging format string (default: '%(asctime)s %(message)s') + --log-file LOG-FILE A filename to write logs to, or None to write to the standard stream (default: None) + --use-config CONFIG Use a YAML config file for settings + --args ... Command line arguments remaining to passed on to the application handler. NOTE: if this argument precedes DIRECTORY- + OR-SCRIPT then some other argument, e.g. --show, must be placed before the directory or script. + --dev [FILES-TO-WATCH ...] + Enable live reloading during app development. By default it watches all *.py *.html *.css *.yaml files in the app + directory tree. Additional files can be passed as arguments. NOTE: if this argument precedes DIRECTORY-OR-SCRIPT then + some other argument, e.g --show, must be placed before the directory or script. NOTE: This setting only works with a + single app. It also restricts the number of processes to 1. NOTE FOR WINDOWS USERS : this option must be invoked + using 'python -m bokeh'. If not Tornado will fail to restart the server + --show Open server app(s) in a browser + --allow-websocket-origin HOST[:PORT] + Public hostnames which may connect to the Bokeh websocket With unix socket, the websocket origin restrictions should + be enforced by the proxy. + --prefix PREFIX URL prefix for Bokeh server URLs + --ico-path ICO_PATH Path to a .ico file to use as the favicon.ico, or 'none' to disable favicon.ico support. If unset, a default Bokeh + .ico file will be used + --keep-alive MILLISECONDS + How often to send a keep-alive ping to clients, 0 to disable. + --check-unused-sessions MILLISECONDS + How often to check for unused sessions + --unused-session-lifetime MILLISECONDS + How long unused sessions last + --stats-log-frequency MILLISECONDS + How often to log stats + --mem-log-frequency MILLISECONDS + How often to log memory usage information + --use-xheaders Prefer X-headers for IP/protocol information + --ssl-certfile CERTFILE + Absolute path to a certificate file for SSL termination + --ssl-keyfile KEYFILE + Absolute path to a private key file for SSL termination + --session-ids MODE One of: unsigned, signed or external-signed + --auth-module AUTH_MODULE + Absolute path to a Python module that implements auth hooks + --enable-xsrf-cookies + Whether to enable Tornado support for XSRF cookies. All PUT, POST, or DELETE handlers must be properly instrumented + when this setting is enabled. + --exclude-headers EXCLUDE_HEADERS [EXCLUDE_HEADERS ...] + A list of request headers to exclude from the session context (by default all headers are included). + --exclude-cookies EXCLUDE_COOKIES [EXCLUDE_COOKIES ...] + A list of request cookies to exclude from the session context (by default all cookies are included). + --include-headers INCLUDE_HEADERS [INCLUDE_HEADERS ...] + A list of request headers to make available in the session context (by default all headers are included). + --include-cookies INCLUDE_COOKIES [INCLUDE_COOKIES ...] + A list of request cookies to make available in the session context (by default all cookies are included). + --cookie-secret COOKIE_SECRET + Configure to enable getting/setting secure cookies + --index INDEX Path to a template to use for the site index + --disable-index Do not use the default index on the root path + --disable-index-redirect + Do not redirect to running app from root path + --num-procs N Number of worker processes for an app. Using 0 will autodetect number of cores (defaults to 1) + --session-token-expiration N + Duration in seconds that a new session token is valid for session creation. After the expiry time has elapsed, the + token will not be able create a new session (defaults to seconds). + --websocket-max-message-size BYTES + Set the Tornado websocket_max_message_size value (default: 20MB) + --websocket-compression-level LEVEL + Set the Tornado WebSocket compression_level + --websocket-compression-mem-level LEVEL + Set the Tornado WebSocket compression mem_level + --glob Process all filename arguments as globs + --static-dirs KEY=VALUE [KEY=VALUE ...] + Static directories to serve specified as key=value pairs mapping from URL route to static file directory. + --basic-auth BASIC_AUTH + Password or filepath to use with Basic Authentication. + --oauth-provider OAUTH_PROVIDER + The OAuth2 provider to use. + --oauth-key OAUTH_KEY + The OAuth2 key to use + --oauth-secret OAUTH_SECRET + The OAuth2 secret to use + --oauth-redirect-uri OAUTH_REDIRECT_URI + The OAuth2 redirect URI + --oauth-extra-params OAUTH_EXTRA_PARAMS + Additional parameters to use. + --oauth-jwt-user OAUTH_JWT_USER + The key in the ID JWT token to consider the user. + --oauth-encryption-key OAUTH_ENCRYPTION_KEY + A random string used to encode the user information. + --oauth-error-template OAUTH_ERROR_TEMPLATE + A random string used to encode the user information. + --oauth-expiry-days OAUTH_EXPIRY_DAYS + Expiry off the OAuth cookie in number of days. + --oauth-refresh-tokens + Whether to automatically OAuth access tokens when they expire. + --oauth-guest-endpoints [OAUTH_GUEST_ENDPOINTS ...] + List of endpoints that can be accessed as a guest without authenticating. + --oauth-optional Whether the user will be forced to go through login flow or if they can access all applications as a guest. + --login-endpoint LOGIN_ENDPOINT + Endpoint to serve the authentication login page on. + --logout-endpoint LOGOUT_ENDPOINT + Endpoint to serve the authentication logout page on. + --auth-template AUTH_TEMPLATE + Template to serve when user is unauthenticated. + --logout-template LOGOUT_TEMPLATE + Template to serve logout page. + --basic-login-template BASIC_LOGIN_TEMPLATE + Template to serve for Basic Authentication login page. + --rest-provider REST_PROVIDER + The interface to use to serve REST API + --rest-endpoint REST_ENDPOINT + Endpoint to store REST API on. + --rest-session-info Whether to serve session info on the REST API + --session-history SESSION_HISTORY + The length of the session history to record. + --warm Whether to execute scripts on startup to warm up the server. + --admin Whether to add an admin panel. + --admin-endpoint ADMIN_ENDPOINT + Name to use for the admin endpoint. + --admin-log-level {debug,info,warning,error,critical} + One of: debug (default), info, warning, error or critical + --profiler PROFILER The profiler to use by default, e.g. pyinstrument, snakeviz or memray. + --autoreload Whether to autoreload source when script changes. + --num-threads NUM_THREADS + Whether to start a thread pool which events are dispatched to. + --setup SETUP Path to a setup script to run before server starts. + --liveness Whether to add a liveness endpoint. + --liveness-endpoint LIVENESS_ENDPOINT + The endpoint for the liveness API. + --reuse-sessions Whether to reuse sessions when serving the initial request. + --global-loading-spinner + Whether to add a global loading spinner to the application(s). +``` + +## Recap + +You can + +- serve a multi page app by providing a list of files or [*globs*](https://en.wikipedia.org/wiki/Glob_(programming)). +- list the configuration options of `panel serve` by adding the flag `--help`. + +## Additional Resources + +### Tutorials + +- [Serve Apps (beginner)](../basic/serve.md) + +### How-to + +- [Launch a server dynamically with `pn.serve` or `pn.show`](../../how_to/server/programmatic.md) +- [Launch a server on the commandline](../../how_to/server/commandline.md) +- [Serve multiple applications with `pn.serve`](../../how_to/server/multiple.md) +- [Serve static files with `--static-dirs`](../../how_to/server/static_files.md) +- [Serve with Django](../../how_to/integrations/django.md) +- [Serve with FastAPI](../../how_to/integrations/FastAPI.md) +- [Serve with Flask](../../how_to/integrations/flask.md) +- [Write and serve apps in Markdown](../../how_to/editor/markdown.md) diff --git a/doc/tutorials/intermediate/size.md b/doc/tutorials/intermediate/size.md new file mode 100644 index 0000000000..395fe88404 --- /dev/null +++ b/doc/tutorials/intermediate/size.md @@ -0,0 +1,220 @@ +# Control the Size + +COMING UP + +```{pyodide} +import panel as pn +pn.extension('tabulator') +``` + +## Responsive Layouts with FlexBox + +So far when we have talked about responsive layouts we have primarily focused on simple `width`/`height` responsiveness of individual components, i.e. whether they will grow and shrink to fill the available space. For a truly responsive experience however we will need responsive layouts that will reflow the content depending on the size of the screen, browser window or the container they are placed inside of, much like how text wraps when there is insufficient width to accommodate it: + +Panel offers one such component out of the box, the [`FlexBox`](../../reference/layouts/FlexBox.ipynb) layout. + +```{pyodide} +import panel as pn +import random + +pn.extension() + +def create_random_spacer(): + return pn.Spacer( + height=100, + width=random.randint(1, 4) * 100, + styles={"background": "teal"}, + margin=5, + ) +spacers = [create_random_spacer() for _ in range(10)] + +pn.FlexBox(*spacers).servable() +``` + +`FlexBox` is based on [CSS Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) and supports many of the same options, such as setting `flex_direction`, `flex-wrap`, `align_items` and `align_content`. + +```{pyodide} +import panel as pn +import random + +pn.extension() + +def create_random_spacer(): + return pn.Spacer( + height=random.randint(1, 2) * 100, + width=random.randint(1, 4) * 100, + styles={"background": "teal"}, + margin=5, + ) +spacers = [create_random_spacer() for _ in range(10)] + +pn.FlexBox(*spacers, align_items="center").servable() +``` + +### Distributing proportions + +To achieve more complex layouts, i.e. specific proportions between different components we can use the `flex` property on the children of our `FlexBox`, e.g. here we declare that the green Spacer should be three times as wide as the red and blue components. + +```{pyodide} +import panel as pn + +pn.extension() + +red = pn.Spacer(height=200, styles={'background': 'red', 'flex': '1 1 auto'}) +green = pn.Spacer(height=200, styles={'background': 'green', 'flex': '3 1 auto'}) +blue = pn.Spacer(height=200, styles={'background': 'blue', 'flex': '1 1 auto'}) + +pn.FlexBox(red, green, blue).servable() +``` + +To learn more about this read [this guide on controlling ratios of flex items](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Controlling_ratios_of_flex_items_along_the_main_axis). + +### Exercise + +Using only the `styles` of the components, distribute the two `dataframe`s and the `markdown` such that the `dataframe`s are both 3 times wider than the `markdown`, then center the `markdown` vertically. + +You might find inspiration in [this guide on aligning items in a flex container](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Aligning_items_in_a_flex_container). + +```{pyodide} +import pandas as pd +import panel as pn + +pn.extension() + +text = """A *wind turbine* is a renewable energy device that converts the kinetic energy from wind into electricity.""" +wind_speed = pd.DataFrame( + [ + ("Monday", 7), + ("Tuesday", 4), + ("Wednesday", 9), + ("Thursday", 4), + ("Friday", 4), + ("Saturday", 5), + ("Sunday", 4), + ], + columns=["Day", "Wind Speed (m/s)"], +) + +dataframe_1 = pn.pane.DataFrame(wind_speed, styles={'flex': '3 1 auto'}) +markdown = pn.pane.Markdown(text, styles={'flex': '3 1 auto'}) +dataframe_2 = pn.pane.DataFrame(wind_speed, styles={'flex': '3 1 auto'}) + +pn.FlexBox(dataframe_1, markdown, dataframe_2).servable() +``` + +:::{dropdown} Solution + +```{pyodide} +import pandas as pd +import panel as pn + +pn.extension() + +text = """A *wind turbine* is a renewable energy device that converts the kinetic energy from wind into electricity.""" +wind_speed = pd.DataFrame( + [ + ("Monday", 7), + ("Tuesday", 4), + ("Wednesday", 9), + ("Thursday", 4), + ("Friday", 4), + ("Saturday", 5), + ("Sunday", 4), + ], + columns=["Day", "Wind Speed (m/s)"], +) + +dataframe_1 = pn.pane.DataFrame(wind_speed, styles={'flex': '3 1 auto'}) +markdown = pn.pane.Markdown(text, styles={'flex': '1 1 0', "align-self": "center"}) +dataframe_2 = pn.pane.DataFrame(wind_speed, styles={'flex': '3 1 auto'}) + +pn.FlexBox(dataframe_1, markdown, dataframe_2).servable() +``` + +::: + +:::{note} +Getting the `FlexBox` `styles` right can be tricky. If you need help try posting a [minimum, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) on [Discourse](https://discourse.holoviz.org/). +::: + +### Media queries + +To achieve layouts depending on the overall screen/browser width, e.g. to have a different layout depending on whether we are working on a desktop or a mobile we can use *media queries*. Media queries allow us to apply different rules depending on a `min-width` or `max-width`, e.g. the example below will force the flexbow into a column layout when the viewport is below a size of 1200px: + +```{pyodide} +import panel as pn + +pn.extension() + + +red = pn.Spacer(height=200, width=400, styles={'background': 'red'}) +green = pn.Spacer(height=200, width=400, styles={'background': 'green'}) +blue = pn.Spacer(height=200, width=400, styles={'background': 'blue'}) + +media_query = """ +@media screen and (max-width: 1200px) { + div[id^="flexbox"] { + flex-flow: column !important; + } +} +""" + +pn.FlexBox(red, green, blue, stylesheets=[media_query]).servable() +``` + +Try changing your browser width to see how the layout changes from row based to column based. + +### Exercise: Challenge + +This exercise is a bit more free-form and can be solved in many ways. + +Please generate a layout that is both responsive and visually pleasing starting from the below code. + +```{pyodide} +import panel as pn +import holoviews as hv +import hvplot.pandas +import pandas as pd + +data_url = 'https://assets.holoviz.org/panel/tutorials/turbines.csv.gz' + +@pn.cache +def get_data(): + return pd.read_csv(data_url) + +df = pn.rx(get_data()) + +CARD_STYLE = """ +:host { + box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px; + padding: 5px 10px; +}""" + +manufacturers = pn.widgets.MultiChoice(options=df.t_manu.unique().rx.pipe(list), name='Manufacturer') +year = pn.widgets.IntRangeSlider(start=df.p_year.min().rx.pipe(int), end=df.p_year.max().rx.pipe(int), name='Year') +columns = ['p_name', 't_state', 't_county', 'p_year', 't_manu', 'p_cap'] + +filtered = df[columns][df.t_manu.isin(manufacturers.rx.where(manufacturers, df.t_manu.unique())) & df.p_year.between(*year.rx())] + +count = pn.indicators.Number(name='Turbine Count', value=filtered.rx.len(), format='{value:,d} TWh', stylesheets=[CARD_STYLE]) +total_cap = pn.indicators.Number(name='Total Capacity', value=filtered.p_cap.mean(), format='{value:.2f} TWh', stylesheets=[CARD_STYLE]) +modal_year = pn.indicators.Number(name='Modal Year', value=filtered.p_year.mode().iloc[0], stylesheets=[CARD_STYLE]) + +widgets = pn.Column(manufacturers, year, stylesheets=[CARD_STYLE], margin=10) +table = pn.widgets.Tabulator(filtered, stylesheets=[CARD_STYLE], max_width=500) + +year_hist = filtered.hvplot.hist(y='p_year', responsive=True, max_width=300, height=312) +cap_hist = filtered.hvplot.hist(y='p_cap', responsive=True, max_width=300, height=312) + +plots = pn.Column(hv.DynamicMap(cap_hist), hv.DynamicMap(year_hist), stylesheets=[CARD_STYLE], max_width=400, margin=5) + +pn.Column(count, total_cap, modal_year, widgets, table, plots).servable() +``` + +:::{dropdown} Solution + +This is just an example solution. The exercise can be solved in many ways. + +COMING UP + +::: diff --git a/doc/tutorials/intermediate/structure_data_store.md b/doc/tutorials/intermediate/structure_data_store.md new file mode 100644 index 0000000000..1d7173521b --- /dev/null +++ b/doc/tutorials/intermediate/structure_data_store.md @@ -0,0 +1,325 @@ +# Structure with a DataStore + +In this tutorial you will learn how to use the `DataStore` *design pattern*: + +- The `DataStore` design pattern has proven quite successful across a wide range of use cases. +- The `DataStore` component receives `data` and `filters`. Based on the `data` and `filters` it *transforms* the data. +- One or more `View` components consumes the *transformed* data. +- The components can be (re-)used for data exploration in notebooks and for building apps. + +:::{note} +When building a larger application we generally recommend using a *class based construction* and following best practices for object-oriented programming, specifically composition. +::: + +```{pyodide} +import panel as pn +pn.extension('tabulator', 'vega', throttled=True) +``` + +
+ +## Install the dependencies + +Please make sure [Altair](https://altair-viz.github.io/), [Pandas](https://pandas.pydata.org) and [PyArrow](https://arrow.apache.org/docs/python/index.html) are installed. + +::::{tab-set} + +:::{tab-item} pip +:sync: pip + +``` bash +pip install altair pandas panel pyarrow +``` + +::: + +:::{tab-item} conda +:sync: conda + +``` bash +conda install -y -c conda-forge altair pandas panel pyarrow +``` + +::: + +:::: + +## Build the app + +### The Data Store + +Copy the code below into a new file and save the file as `data_store.py`. + +```{pyodide} +import param +import panel as pn +import pandas as pd +from panel.viewable import Viewer + +CARD_STYLE = """ +:host {{ + box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px; + padding: {padding}; +}} """ + +TURBINES_URL = "https://datasets.holoviz.org/windturbines/v1/windturbines.parq" + +@pn.cache(ttl=15 * 60) +def get_turbines(): + return pd.read_parquet(TURBINES_URL) + + +class DataStore(Viewer): + data = param.DataFrame() + + filters = param.List(constant=True) + + def __init__(self, **params): + super().__init__(**params) + dfx = self.param.data.rx() + widgets = [] + for filt in self.filters: + dtype = self.data.dtypes[filt] + if dtype.kind == "f": + widget = pn.widgets.RangeSlider( + name=filt, start=dfx[filt].min(), end=dfx[filt].max() + ) + condition = dfx[filt].between(*widget.rx()) + else: + options = dfx[filt].unique().tolist() + widget = pn.widgets.MultiChoice(name=filt, options=options) + condition = dfx[filt].isin(widget.rx().rx.where(widget, options)) + dfx = dfx[condition] + widgets.append(widget) + self.filtered = dfx + self.count = dfx.rx.len() + self.total_capacity = dfx.t_cap.sum() + self.avg_capacity = dfx.t_cap.mean() + self.avg_rotor_diameter = dfx.t_rd.mean() + self.top_manufacturers = ( + dfx.groupby("t_manu").p_cap.sum().sort_values().iloc[-10:].index.to_list() + ) + self._widgets = widgets + + def filter( + self, + ): + return + + def __panel__(self): + return pn.Column( + "## Filters", + *self._widgets, + stylesheets=[CARD_STYLE.format(padding="5px 10px")], + margin=10 + ) +``` + +:::{note} +The `DataStore` class is be responsible for transforming `data` in various ways. + +1. The `DataStore` must receive `data` as an argument when instantiated. +2. When the `filters` value is updated, it triggers an update of the `count`, `total_capacity`, ... etc. values. +::: + +### The Views + +Copy the code below into a new file, remove the `#` from the import and save the file as `views.py`. + +```{pyodide} +import altair as alt +import param +# from data_store import DataStore, CARD_STYLE +from panel.viewable import Viewer +import panel as pn + +class View(Viewer): + data_store = param.ClassSelector(class_=DataStore) + + +class Table(View): + columns = param.List( + default=["p_name", "p_year", "t_state", "t_county", "t_manu", "t_cap", "p_cap"] + ) + + def __panel__(self): + data = self.data_store.filtered[self.param.columns] + return pn.widgets.Tabulator( + data, + pagination="remote", + page_size=13, + stylesheets=[CARD_STYLE.format(padding="10px")], + margin=10, + ) + + +class Histogram(View): + def __panel__(self): + df = self.data_store.filtered + df = df[df.t_manu.isin(self.data_store.top_manufacturers)] + fig = ( + pn.rx(alt.Chart)( + (df.rx.len() > 5000).rx.where(df.sample(5000), df), + title="Capacity by Manufacturer", + ) + .mark_circle(size=8) + .encode( + y="t_manu:N", + x="p_cap:Q", + yOffset="jitter:Q", + color=alt.Color("t_manu:N").legend(None), + ) + .transform_calculate(jitter="sqrt(-2*log(random()))*cos(2*PI*random())") + .properties( + height=400, + width=600, + ) + ) + return pn.pane.Vega( + fig, stylesheets=[CARD_STYLE.format(padding="0")], margin=10 + ) + + +class Indicators(View): + def __panel__(self): + style = {"stylesheets": [CARD_STYLE.format(padding="10px")]} + return pn.FlexBox( + pn.indicators.Number( + value=self.data_store.total_capacity / 1e6, + name="Total Capacity (TW)", + format="{value:,.2f}", + **style + ), + pn.indicators.Number( + value=self.data_store.count, + name="Count", + format="{value:,.0f}", + **style + ), + pn.indicators.Number( + value=self.data_store.avg_capacity, + name="Avg. Capacity (kW)", + format="{value:,.2f}", + **style + ), + pn.indicators.Number( + value=self.data_store.avg_rotor_diameter, + name="Avg. Rotor Diameter (m)", + format="{value:,.2f}", + **style + ), + pn.indicators.Number( + value=self.data_store.avg_rotor_diameter, + name="Avg. Rotor Diameter (m)", + format="{value:,.2f}", + **style + ), + ) +``` + +:::{note} +Here we declared a *base* `View` class that holds a reference to the `DataStore` as a parameter. Now we can have any number of concrete `View` classes that consume data from the `DataStore` and render it in any number of ways: +::: + +### The App + +Copy the code below into a new file and save the file as `app.py`. + +```python +import param +from panel.viewable import Viewer +from data_store import DataStore, get_turbines +from views import Indicators, Histogram, Table + +import panel as pn + +pn.extension("tabulator", "vega", throttled=True) + +class App(Viewer): + data_store = param.ClassSelector(class_=DataStore) + + title = param.String() + + views = param.List() + + def __init__(self, **params): + super().__init__(**params) + updating = self.data_store.filtered.rx.updating() + updating.rx.watch( + lambda updating: pn.state.curdoc.hold() + if updating + else pn.state.curdoc.unhold() + ) + self._views = pn.FlexBox( + *(view(data_store=self.data_store) for view in self.views), loading=updating + ) + self._template = pn.template.MaterialTemplate(title=self.title) + self._template.sidebar.append(self.data_store) + self._template.main.append(self._views) + + def servable(self): + if pn.state.served: + return self._template.servable() + return self + + def __panel__(self): + return pn.Row(self.data_store, self._views) + + +data = get_turbines() +ds = DataStore(data=data, filters=["p_year", "p_cap", "t_manu"]) + +App( + data_store=ds, views=[Indicators, Histogram, Table], title="Windturbine Explorer" +).servable() +``` + +Run `panel serve app.py --autoreload` + +The app will look something like + +![Wind Turbine App with DataStore](../../_static/images/structure_data_store_app.png) + +## Reuse in a Notebook + +:::{note} +The beauty of the *compositional approach* to constructing application components is that they are now usable in multiple contexts. +::: + +Copy the two cells below into a notebook, remove the `#` from the imports and run the cells. + +```{pyodide} +# from data_store import DataStore, get_turbines +# from views import Indicators, Histogram, Table + +import panel as pn + +pn.extension("tabulator", "vega", throttled=True) +``` + +```{pyodide} +turbines = get_turbines() + +ds = DataStore(data=turbines, filters=['p_year', 'p_cap', 't_manu']) + +pn.Row( + ds, + pn.Tabs( + ('Indicators', Indicators(data_store=ds)), + ('Histogram', Histogram(data_store=ds)), + ('Table', Table(data_store=ds)), + sizing_mode='stretch_width', + ) +).servable() +``` + +## Recap + +In this tutorial you have learned: + +- The `DataStore` design pattern has proven quite successful across a wide range of use cases. +- The `DataStore` component receives `data` and `filters`. Based on the `data` and `filters` it *transforms* the data. +- One or more `View` components consumes the *transformed* data. +- The components can be (re-)used for data exploration in notebooks and for building apps. + +## Resources diff --git a/doc/tutorials/layouts.md b/doc/tutorials/layouts.md deleted file mode 100644 index 9dd0a457d5..0000000000 --- a/doc/tutorials/layouts.md +++ /dev/null @@ -1,207 +0,0 @@ -# Layouts - -:::{note} Tutorial 5. **Layout** -:icon: false - -#### Sizing and Responsiveness - -Panel builds on Bokeh which has a CSS based layout engine, while you can fall back to using explicit CSS to lay out components, it has a higher-level abstraction that makes it possible to build both fixed size and responsive layouts easily. - -The main sizing related options that you should know are: - -`width`/`height` -: Allows setting a fixed width or height - -`sizing_mode` -: Allows toggling between fixed sizing and responsive sizing along vertical and/or horizontal dimensions - -`min_width`/`min_height` -: Allows setting a minimum width or height, if responsive sizing is set along the corresponding dimension. - -`max_width`/`max_height` -: Allows setting a maximum width or height, if responsive sizing is set along the corresponding dimension. -::: - -```{pyodide} -import panel as pn -pn.extension('tabulator') -``` - -## Inherent and absolute sizing - -Many components you might want to display have an inherent size, e.g. take some text, based on the font-size and the content of the text it will take up a certain amount of space. When you render it it will fill the available space and wrap if necessary: - -```{pyodide} -lorem_ipsum = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." - -pn.panel(lorem_ipsum) -``` - -By restricting the width, we can force it to rewrap and it will have a different inherent height. - -```{pyodide} -pn.panel(lorem_ipsum, width=300) -``` - -Explicitly setting both width and height will force the resulting display to scroll to ensure that it is not cut off: - -```{pyodide} -pn.panel(lorem_ipsum, width=300, height=100) -``` - -## Responsive sizing - -The `sizing_mode` option can be used to toggle responsiveness in the width or height dimension or both. To see the effect of this we will create a fixed size container that we place the components into: - -```{pyodide} -width_responsive = pn.Spacer(styles={'background': 'red'}, sizing_mode='stretch_width', height=200) - -pn.Column(width_responsive, width=400, height=400, styles={'border': '1px solid black'}) -``` - -```{pyodide} -height_responsive = pn.Spacer(styles={'background': 'green'}, sizing_mode='stretch_height', width=200) - -pn.Column(height_responsive, width=400, height=400, styles={'border': '1px solid black'}) -``` - -```{pyodide} -both_responsive = pn.Spacer(styles={'background': 'blue'}, sizing_mode='stretch_both') - -pn.Column(both_responsive, width=400, height=400, styles={'border': '1px solid black'}) -``` - -### Exercise - -Arrange the Markdown pane and Bokeh figure such that they fully fill the available space but also ensure that the text never shrinks below 200 pixels and never grows above 500 pixels in width. - -```{pyodide} -import numpy as np - -from bokeh.plotting import figure - -md = pn.pane.Markdown(lorem_ipsum, ) # <-- Add options here -fig = figure() # <-- and here - -xs = np.linspace(0, 10) -ys = np.sin(xs) - -fig.line(xs, ys) - -pn.Row(fig, md, height=500, sizing_mode='stretch_width') -``` - -## True Responsive Layouts - -So far when we have talked about responsive layouts we have primarily focused on simple width/height responsiveness of individual components, i.e. whether they will grow and shrink to fill the available space. For a truly responsive experience however we will need responsive layouts that will reflow the content depending on the size of the screen, browser window or the container they are placed inside of, much like how text wraps when there is insufficient width to accommodate it: - -Panel offers one such component out of the box, the `FlexBox` layout. - -```{pyodide} -import random - -pn.FlexBox(*(pn.Spacer(height=100, width=random.randint(1, 4)*100, styles={'background': 'indianred'}, margin=5) for _ in range(10))) -``` - -`FlexBox` is based on [CSS Flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) and supports many of the same options, such as setting `flex_direction`, `flex-wrap`, `align_items` and `align_content`. - -```{pyodide} -pn.FlexBox(*(pn.Spacer(height=random.randint(1, 2)*100, width=random.randint(1, 4)*100, styles={'background': 'indianred'}, margin=5) for _ in range(10)), - align_items='center') -``` - -### Distributing proportions - -To achieve more complex layouts, i.e. specific proportions between different components we can use the `flex` property on the children of our `FlexBox`, e.g. here we declare that the green Spacer should be three times as wide as the red and blue components. - -```{pyodide} -red = pn.Spacer(height=200, styles={'background': 'red', 'flex': '1 1 auto'}) -green = pn.Spacer(height=200, styles={'background': 'green', 'flex': '3 1 auto'}) -blue = pn.Spacer(height=200, styles={'background': 'blue', 'flex': '1 1 auto'}) - -pn.FlexBox(red, green, blue) -``` - -To learn more about this read [this guide on controlling ratios of flex items](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout/Controlling_ratios_of_flex_items_along_the_main_axis). - -#### Exercise - -Using only the `flex` property inside `styles` of the two plots, distribute the two plots and the text such that the plots are both 3 times wider than the text, then center the text vertically. - -```{pyodide} -xs = np.linspace(0, 10) - -sin_fig = figure(height=400, width=None, styles={'flex': '3 1 auto'}) -sin_fig.line(xs, np.sin(xs)) - -cos_fig = figure(height=400, width=None, styles={'flex': '3 1 auto'}) -cos_fig.line(xs, np.cos(xs)) - -text = pn.pane.Markdown(lorem_ipsum, styles={'flex': '1 1 0'}) - -pn.FlexBox(sin_fig, text, cos_fig) -``` - -### Media queries - -To achieve layouts depending on the overall screen/browser width, e.g. to have a different layout depending on whether we are working on a desktop or a mobile we can use media queries. Media queries allow us to apply different rules depending on a `min-width` or `max-width`, e.g. the example below will force the flexbow into a column layout when the viewport is below a size of 1200px: - -```{pyodide} -red = pn.Spacer(height=200, width=400, styles={'background': 'red'}) -green = pn.Spacer(height=200, width=400, styles={'background': 'green'}) -blue = pn.Spacer(height=200, width=400, styles={'background': 'blue'}) - -media_query = """ -@media screen and (max-width: 1200px) { - div[id^="flexbox"] { - flex-flow: column !important; - } -} -""" - -pn.FlexBox(red, green, blue, stylesheets=[media_query]) -``` - -### Exercise - -This exercise is a bit more free-form, the aim will be simply to generate a layout that is both responsive and visually pleasing. We'll start by declaring the data pipeline that will feed our components: - -```{pyodide} -import holoviews as hv -import hvplot.pandas -import pandas as pd - -data_url = 'https://datasets.holoviz.org/windturbines/v1/windturbines.parq' - -df = pn.rx(pd.read_parquet(data_url)) - -CARD_STYLE = """ -:host { - box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px; - padding: 5px 10px; -}""" - -manufacturers = pn.widgets.MultiChoice(options=df.t_manu.unique().rx.pipe(list), name='Manufacturer') -year = pn.widgets.IntRangeSlider(start=df.p_year.min().rx.pipe(int), end=df.p_year.max().rx.pipe(int), name='Year') -columns = ['p_name', 't_state', 't_county', 'p_year', 't_manu', 'p_cap'] - -filtered = df[columns][df.t_manu.isin(manufacturers.rx.where(manufacturers, df.t_manu.unique())) & df.p_year.between(*year.rx())] -``` - -Now we'll declare all the individual components and put them in a column. Use everything you've learned to find a responsive and aesthetically pleasing layout: - -```{pyodide} -count = pn.indicators.Number(name='Turbine Count', value=filtered.rx.len(), format='{value:,d} TWh', stylesheets=[CARD_STYLE]) -total_cap = pn.indicators.Number(name='Total Capacity', value=filtered.p_cap.mean(), format='{value:.2f} TWh', stylesheets=[CARD_STYLE]) -modal_year = pn.indicators.Number(name='Modal Year', value=filtered.p_year.mode().iloc[0], stylesheets=[CARD_STYLE]) - -widgets = pn.Column(manufacturers, year, stylesheets=[CARD_STYLE], margin=10) -table = pn.widgets.Tabulator(filtered, stylesheets=[CARD_STYLE], max_width=500) - -year_hist = filtered.hvplot.hist(y='p_year', responsive=True, max_width=300, height=312) -cap_hist = filtered.hvplot.hist(y='p_cap', responsive=True, max_width=300, height=312) - -plots = pn.Column(hv.DynamicMap(cap_hist), hv.DynamicMap(year_hist), stylesheets=[CARD_STYLE], max_width=400, margin=5) - -pn.Column(count, total_cap, modal_year, widgets, table, plots) -``` diff --git a/doc/tutorials/structure.md b/doc/tutorials/structure.md deleted file mode 100644 index cd440313c6..0000000000 --- a/doc/tutorials/structure.md +++ /dev/null @@ -1,214 +0,0 @@ -# Structuring Applications - -:::{note} Tutorial 6. **Structuring Applications** -:icon: false - -#### Classes and Design Patterns - -Once you go beyond a simple dashboard or data app to a full fleshed application organizing your code and reasoning about state become a lot more difficult. In this section we will explore some common design patterns for structuring the code for your larger application. - -::: - -```{pyodide} -import param -import pandas as pd -import panel as pn - -pn.extension('tabulator', 'vega', throttled=True) -``` - -## Building the app - -Now that we have covered the preliminaries lets actually build an app that more closely resembles something you might actually use in production. - -When building a more complex application we generally recommend using a class based construction and following best practices for object-oriented programming, specifically composition. - -One particular design pattern that has proven quite successful across a wide range of use cases is something we will put into effect here, specifically we will construct: - -- A `DataStore` that loads data from the catalog and defines a variable number of filters. -- A `View` component that consumes the data and then displays it. - -#### The DataStore - -We won't worry too much about the implementation details of this data store implementation but it's worth noting at a high-level it does a few simple things: - -1. Initialize a data catalog using the automatic access controls. -2. Load a `table` from the data store and automatically generating a set of filters for each source. - -```{pyodide} -from panel.viewable import Viewer - -CARD_STYLE = """ -:host {{ - box-shadow: rgba(50, 50, 93, 0.25) 0px 6px 12px -2px, rgba(0, 0, 0, 0.3) 0px 3px 7px -3px; - padding: {padding}; -}} """ - -class DataStore(Viewer): - - data = param.DataFrame() - - filters = param.List(constant=True) - - def __init__(self, **params): - super().__init__(**params) - dfx = self.param.data.rx() - widgets = [] - for filt in self.filters: - dtype = self.data.dtypes[filt] - if dtype.kind == 'f': - widget = pn.widgets.RangeSlider(name=filt, start=dfx[filt].min(), end=dfx[filt].max()) - condition = dfx[filt].between(*widget.rx()) - else: - options = dfx[filt].unique().tolist() - widget = pn.widgets.MultiChoice(name=filt, options=options) - condition = dfx[filt].isin(widget.rx().rx.where(widget, options)) - dfx = dfx[condition] - widgets.append(widget) - self.filtered = dfx - self.count = dfx.rx.len() - self.total_capacity = dfx.t_cap.sum() - self.avg_capacity = dfx.t_cap.mean() - self.avg_rotor_diameter = dfx.t_rd.mean() - self.top_manufacturers = dfx.groupby('t_manu').p_cap.sum().sort_values().iloc[-10:].index.to_list() - self._widgets = widgets - - def filter(self, ): - return - - def __panel__(self): - return pn.Column('## Filters', *self._widgets, stylesheets=[CARD_STYLE.format(padding='5px 10px')], margin=10) - -``` - -Here we declared a `DataStore` class that will be responsible for loading some data and transforming it in various ways and a `View` class that holds a reference to the `DataStore` as a parameter. Now we can have any number of concrete `View` classes that consume data from the DataStore and render it in any number of ways: - -### The Views - -```{pyodide} -import altair as alt -import holoviews as hv -import hvplot.pandas - -class View(Viewer): - - data_store = param.ClassSelector(class_=DataStore) - -class Table(View): - - columns = param.List(default=['p_name', 'p_year', 't_state', 't_county', 't_manu', 't_cap', 'p_cap']) - - def __panel__(self): - data = self.data_store.filtered[self.param.columns] - return pn.widgets.Tabulator( - data, pagination='remote', page_size=13, stylesheets=[CARD_STYLE.format(padding='10px')], - margin=10, - ) - -class Histogram(View): - - def __panel__(self): - df = self.data_store.filtered - df = df[df.t_manu.isin(self.data_store.top_manufacturers)] - fig = pn.rx(alt.Chart)((df.rx.len()>5000).rx.where(df.sample(5000), df), title='Capacity by Manufacturer').mark_circle(size=8).encode( - y="t_manu:N", - x="p_cap:Q", - yOffset="jitter:Q", - color=alt.Color('t_manu:N').legend(None) - ).transform_calculate( - jitter="sqrt(-2*log(random()))*cos(2*PI*random())" - ).properties( - height=400, - width=600, - ) - return pn.pane.Vega(fig, stylesheets=[CARD_STYLE.format(padding='0')], margin=10) - -class Indicators(View): - - def __panel__(self): - style = {'stylesheets': [CARD_STYLE.format(padding='10px')]} - return pn.FlexBox( - pn.indicators.Number( - value=self.data_store.total_capacity / 1e6, name='Total Capacity (TW)', format='{value:,.2f}', **style - ), - pn.indicators.Number( - value=self.data_store.count, name='Count', format='{value:,.0f}', **style - ), - pn.indicators.Number( - value=self.data_store.avg_capacity , name='Avg. Capacity (kW)', format='{value:,.2f}', **style - ), - pn.indicators.Number( - value=self.data_store.avg_rotor_diameter, name='Avg. Rotor Diameter (m)', format='{value:,.2f}', **style - ), - pn.indicators.Number( - value=self.data_store.avg_rotor_diameter, name='Avg. Rotor Diameter (m)', format='{value:,.2f}', **style - ), - ) -``` - -The beauty of this compositional approach to constructing application components is that they are now usable in multiple contexts, e.g. you can use them in a notebook: - -```{pyodide} -data_url = 'https://datasets.holoviz.org/windturbines/v1/windturbines.parq' - -turbines = pd.read_parquet(data_url) - -ds = DataStore(data=turbines, filters=['p_year', 'p_cap', 't_manu']) - -pn.Row( - ds, - pn.Tabs( - ('Table', Table(data_store=ds)), - ('Histogram', Histogram(data_store=ds)), - ('Indicators', Indicators(data_store=ds)), - sizing_mode='stretch_width', - ) -) -``` - -Or we can coordinate these components inside of `App` class that handles the creation of the `View` components and lays them out on the pag or template: - -```{pyodide} -class App(Viewer): - - data_store = param.ClassSelector(class_=DataStore) - - title = param.String() - - views = param.List() - - def __init__(self, **params): - super().__init__(**params) - updating = self.data_store.filtered.rx.updating() - updating.rx.watch(lambda updating: pn.state.curdoc.hold() if updating else pn.state.curdoc.unhold()) - self._views = pn.FlexBox(*(view(data_store=self.data_store) for view in self.views), loading=updating) - self._template = pn.template.MaterialTemplate(title=self.title) - self._template.sidebar.append(self.data_store) - self._template.main.append(self._views) - - def servable(self): - if pn.state.served: - return self._template.servable() - return self - - def __panel__(self): - return pn.Row(self.data_store, self._views) -``` - -Now let's see what we have built: - -```{pyodide} -ds = DataStore(data=turbines, filters=['p_year', 'p_cap', 't_manu']) - -app = App(data_store=ds, views=[Indicators, Histogram, Table], title='Windturbine Explorer') - -app.servable() -``` - -
- -### Exercise - -Now it is time to get started for real and extend the existing application. Build your own `View` and add it to the `App`, then deploy it. - -
diff --git a/doc/tutorials/styling.md b/doc/tutorials/styling.md deleted file mode 100644 index a188931e60..0000000000 --- a/doc/tutorials/styling.md +++ /dev/null @@ -1,212 +0,0 @@ -# Styling - -:::{note} Tutorial 7. **Styling** -:icon: false - -#### How to leverage designs, stylesheets and CSS - -Panel does not require frontend developer experience, instead providing high-level primitives called `Design` components to style your applications. If however, you want to take control over the precise styling of individual components you can write custom templates and CSS stylesheets to modify the look and feel of components. - -::: - -## Designs & Themes - -A `Design` in Panel is a unified system for styling components across your applications. They were introduced in Panel 1.0 and we are still in the process of improving them to achieve a consistent look and feel. - -Applying different design systems in Panel can be achieved globally or per component. To select a `design` globally set it via the extension: - -```{pyodide} -import panel as pn - -pn.extension(design='material') -``` - -Alternatively you can also explicitly import and set a `design` on the config: - -```{pyodide} -from panel.theme import Material - -pn.config.design = Material -``` - -The available `Design` systems include: - -- `Material`: Based on Material Design -- `Bootstrap`: Based on the Bootstrap library -- `Fast`: Based on the Microsoft Fast Design library -- `Native`: The default styling inherited from Bokeh. - -```{pyodide} -from panel.theme import Bootstrap, Material, Fast - -def components(design=None): - design = design or pn.config.design - return pn.Column( - pn.widgets.FloatSlider(name='Slider', design=design), - pn.widgets.TextInput(name='TextInput', design=design), - pn.widgets.Select( - name='Select', options=['Biology', 'Chemistry', 'Physics'], design=design - ), - pn.widgets.Button( - name='Click me!', icon='hand-click', button_type='primary', design=design - ) - ) - -components() -``` - -If you want to style a specific component you can also explicitly pass in the `design` parameter: - -```{pyodide} -components(Bootstrap) -``` - -We can also switch the theme from 'default' to 'dark' by setting it in the `extension` or on the `config`: - -```{pyodide} -with pn.config.set(theme = 'dark'): - display(components()) -``` - -:::{note} -In the notebook the theme adapts to the current JupyterLab theme. Try switching to dark mode to observe this. -::: - -## CSS & Stylesheets - -Panel components are rendered into the [shadow DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM), if you are not familiar with the concept just know that it means that each component is isolated from all others meaning that we can easily apply CSS rules for specific components. - -To set CSS styles we can use one of two parameters, the `styles` and the `stylesheets` parameter. The former is a dictionary of CSS styles that are applied to the container wrapping the component while stylesheets can contain entire CSS files or inline CSS rules. - -## `styles` - -Applying `styles` allows us to style the **container** in a straightforward manner, e.g. here we apply a background and a border around a widget: - - -```{pyodide} -custom_style = { - 'background': '#f9f9f9', - 'border': '1px solid black', - 'padding': '10px', - 'box-shadow': '5px 5px 5px #bcbcbc', - 'font-size': '14pt' -} - -pn.widgets.FloatSlider(name='Number', styles=custom_style) -``` - -## `stylesheets` - -Since `styles` only applies to the `
` that holds the component we cannot use it to directly modify the styling of the **contents** of the component. This is where `stylesheets` come in, allowing us to provide CSS rules that affect each part of the component. This can be done in one of two ways, either we modify CSS variables or we target the specific DOM node that we want to modify using CSS rules. - -:::{warning} 1.0.0 -Note that `styles` and `stylesheets` are new in Panel 1.0.0. As we continue to build on this functionality we will provide ways of modifying the styling of components using CSS variables. -::: - -### CSS Variables - -In the case of the `FloatSlider` we can modify the dimensions of the handle and the slider using CSS variables like `--handle-width`, `--handle-height` and `--slider-size`. In future we aim to provide a comprehensive list of CSS variables for each component. - - -```{pyodide} -stylesheet = """ -:host { - --handle-width: 15px; - --handle-height: 25px; - --slider-size: 5px; -} -""" - -pn.widgets.FloatSlider( - name='Number', styles=custom_style, stylesheets=[stylesheet] -) -``` - -### CSS rules - -If we need full control over the precise styling of a component we can target specific parts of a component using standard CSS rules, e.g. by identifying the CSS classes applied to each component. - -:::{tip} -To discover how exactly a component is structured and how to target specific parts of a component use the inspect functionality of your browser. For example in Chrome right click on the component, then select inspect and look at the DOM structure, e.g. here is the DOM structure of the `FloatSlider`: - -```html -
-
Number: 0
-
-
-
-
-
-
-
-
-
-
0
-
-
-
-``` - -Using the styles pane in the developer console you can then try out various styles before you translate them into specific CSS rules. -::: - -Let's say we want to make the slider handle circular and change its color. Inspecting the HTML we can see that the handle is defined with the CSS class `noUi-handle`. Now we can define a CSS rule with that class that sets a `border-radius` and `background-color`, additionally we unset the `box-shadow`: - -```{pyodide} -stylesheet = """ -:host { - --slider-size: 5px; - --handle-width: 16px; - --handle-height: 16px; -} - -.noUi-handle { - border-radius: 100%; - box-shadow: unset; - background-color: #0081f3; -} -""" - -pn.widgets.FloatSlider( - name='Number', styles=custom_style, stylesheets=[stylesheet] -) -``` - -### External stylesheets - -Inlining stylesheets provides a quick way to override the style of a component but it also means we are sending the stylesheet to the frontend as a string. This can add up when we want to apply this stylesheet to multiple components. Therefore it is recommended that once you move to production the styles are served as an external stylesheet you reference. - -You can either provide a full URL to the stylesheet and host it yourself or you can [serve static assets alongside your application](../server/static_files.md). Here we load the stylesheet from an external URL: - -```{pyodide} -pn.widgets.FloatSlider( - name='Number', stylesheets=['https://assets.holoviz.org/panel/how_to/styling/noUi.css'] -) -``` - -### CSS Classes - -When building complex stylesheets you will sometimes want to have multiple styles for one component. While it is possible to include a separate stylesheet for each you can also use CSS classes to distinguish between different components. The `css_classes` parameter will apply the CSS class to the shadow root (or container). Let us create two sliders with different CSS classes: - - -```{pyodide} -color_stylesheet = """ -:host(.red) .noUi-handle { - background-color: red -} - -:host(.green) .noUi-handle { - background-color: green -} - -:host(.blue) .noUi-handle { - background-color: blue -} -""" - -pn.Column( - *(pn.widgets.FloatSlider(name='Number', stylesheets=[stylesheet, color_stylesheet], css_classes=[cls]) - for cls in ('red', 'green', 'blue')) -) - -``` diff --git a/examples/reference/layouts/Swipe.ipynb b/examples/reference/layouts/Swipe.ipynb index c57819691b..e7b372f4e4 100644 --- a/examples/reference/layouts/Swipe.ipynb +++ b/examples/reference/layouts/Swipe.ipynb @@ -52,10 +52,10 @@ "metadata": {}, "outputs": [], "source": [ - "gis_1880 = 'https://earthobservatory.nasa.gov/ContentWOC/images/globaltemp/global_gis_1945-1949.png'\n", + "gis_1945 = 'https://earthobservatory.nasa.gov/ContentWOC/images/globaltemp/global_gis_1945-1949.png'\n", "gis_2015 = 'https://earthobservatory.nasa.gov/ContentWOC/images/globaltemp/global_gis_2015-2019.png'\n", "\n", - "pn.Swipe(gis_1880, gis_2015)" + "pn.Swipe(gis_1945, gis_2015)" ] }, { diff --git a/examples/reference/panes/Plotly.ipynb b/examples/reference/panes/Plotly.ipynb index 46ef248f0f..4d1740668e 100644 --- a/examples/reference/panes/Plotly.ipynb +++ b/examples/reference/panes/Plotly.ipynb @@ -189,7 +189,7 @@ "\n", "data = pd.DataFrame([\n", " ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4),\n", - " ('Friday', 4), ('Saturday', 4), ('Sunay', 4)], columns=['Day', 'Orders']\n", + " ('Friday', 4), ('Saturday', 4), ('Sunday', 4)], columns=['Day', 'Orders']\n", ")\n", "\n", "fig = px.line(data, x=\"Day\", y=\"Orders\")\n", diff --git a/examples/reference/panes/Str.ipynb b/examples/reference/panes/Str.ipynb index d93f43ad28..4a73e6b6df 100644 --- a/examples/reference/panes/Str.ipynb +++ b/examples/reference/panes/Str.ipynb @@ -15,7 +15,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The ``Str`` pane allows rendering arbitrary text in a panel. Unlike ``Markdown`` and ``HTML``, a ``Str`` is interpreted as a raw string without applying any markup and is displayed in a fixed-width font by default. The pane will render any text, and if given an object will display the object's Python `repr`.\n", + "The ``Str`` pane allows rendering arbitrary text in a panel.\n", + "\n", + "Unlike [``Markdown``](Markdown.ipynb) and [``HTML``](HTML.ipynb) panes, a ``Str`` pane is interpreted as a raw string without applying any markup and is displayed in a fixed-width font by default.\n", + "\n", + "The pane will render any text, and if given an `object` will display the `object`'s Python `repr`.\n", "\n", "#### Parameters:\n", "\n", diff --git a/examples/reference/widgets/Button.ipynb b/examples/reference/widgets/Button.ipynb index 528b1d1dc0..1562f85ef2 100644 --- a/examples/reference/widgets/Button.ipynb +++ b/examples/reference/widgets/Button.ipynb @@ -14,7 +14,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The ``Button`` widget allows triggering events when the button is clicked. In addition to a value parameter, which will toggle from `False` to `True` while the click event is being processed an additional ``clicks`` parameter that can be watched to subscribe to click events.\n", + "The ``Button`` widget allows triggering events when the button is clicked. In addition to a `value` parameter, which will toggle from `False` to `True` while the click event is being processed an additional ``clicks`` parameter that can be watched to subscribe to click events.\n", "\n", "Discover more on using widgets to add interactivity to your applications in the [how-to guides on interactivity](../how_to/interactivity/index.md). Alternatively, learn [how to set up callbacks and (JS-)links between parameters](../../how_to/links/index.md) or [how to use them as part of declarative UIs with Param](../../how_to/param/index.html).\n", "\n",