├── .dockerignore
├── .gitignore
├── .lightning
├── .lightningignore
├── README.md
├── app.py
├── assets
├── awesome-panel-lightning.gif
└── awesome-panel-lightning.mp4
├── pages
├── __init__.py
├── big_data_viz.py
├── cross_filter.py
├── shared.py
└── streaming.py
├── panel_lightning.py
└── requirements.txt
/.dockerignore:
--------------------------------------------------------------------------------
1 | .venv
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # poetry
98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
99 | # This is especially recommended for binary packages to ensure reproducibility, and is more
100 | # commonly ignored for libraries.
101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
102 | #poetry.lock
103 |
104 | # pdm
105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
106 | #pdm.lock
107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
108 | # in version control.
109 | # https://pdm.fming.dev/#use-with-ide
110 | .pdm.toml
111 |
112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
113 | __pypackages__/
114 |
115 | # Celery stuff
116 | celerybeat-schedule
117 | celerybeat.pid
118 |
119 | # SageMath parsed files
120 | *.sage.py
121 |
122 | # Environments
123 | .env
124 | .venv
125 | env/
126 | venv/
127 | ENV/
128 | env.bak/
129 | venv.bak/
130 |
131 | # Spyder project settings
132 | .spyderproject
133 | .spyproject
134 |
135 | # Rope project settings
136 | .ropeproject
137 |
138 | # mkdocs documentation
139 | /site
140 |
141 | # mypy
142 | .mypy_cache/
143 | .dmypy.json
144 | dmypy.json
145 |
146 | # Pyre type checker
147 | .pyre/
148 |
149 | # pytype static type analyzer
150 | .pytype/
151 |
152 | # Cython debug symbols
153 | cython_debug/
154 |
155 | # PyCharm
156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
158 | # and can be added to the global gitignore or merged into this file. For a more nuclear
159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
160 | #.idea/
161 | .venv/
162 | .vscode/
163 |
--------------------------------------------------------------------------------
/.lightning:
--------------------------------------------------------------------------------
1 | name: awesome-panel-lightning
2 |
--------------------------------------------------------------------------------
/.lightningignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .git
3 | .gitignore
4 | .venv
5 | .vscode
6 | assets
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |   [](https://twitter.com/MarcSkovMadsen)
2 |
3 | # ⚡ Awesome Panel Lightning
4 |
5 | Repository demonstrating the power of combining [Panel](https://panel.holoviz.org) and [lightning.ai](https://lightning.ai/) to build, scale and deploy powerful machinelearning and deeplearning data apps.
6 |
7 | [](https://01g6g6axybbecmx47d9m419pe5.litng-ai-03.litng.ai/view?id=01g6g6axybbecmx47d9m419pe5)
8 |
9 | Check out the live [Example App](https://01g6g6axybbecmx47d9m419pe5.litng-ai-03.litng.ai/view?id=01g6g6axybbecmx47d9m419pe5).
10 |
11 | ## ⚙️ Install Locally
12 |
13 | ```bash
14 | git clone https://github.com/MarcSkovMadsen/awesome-panel-lightning.git
15 | cd awesome-panel-lightning
16 | ```
17 |
18 | [Create](https://realpython.com/python-virtual-environments-a-primer/#create-it) and [activate](https://realpython.com/python-virtual-environments-a-primer/#activate-it) your local environment.
19 |
20 | Then install the requirements via
21 |
22 | ```bash
23 | pip install -r requirements.txt
24 | ```
25 |
26 | Finally you can update the `name` of the app in the [.lightning](.lightning) file.
27 |
28 | ## 🏃 Run Locally
29 |
30 | Activate your virtual environment and run
31 |
32 | ```bash
33 | lightning run app app.py
34 | ```
35 |
36 | ## ☁️ Run in lightning.ai cloud
37 |
38 | Activate your virtual environment and run
39 |
40 | ```bash
41 | lightning run app app.py --cloud
42 | ```
43 |
44 | and follow the instructions
45 |
46 | ## Add or Update the App Pages
47 |
48 | The different pages of the app of configured in the [app.py](`app.py`) file.
49 |
50 | Most pages are configured as files. But you can also use functions that return a Panel `Viewable`
51 | like the `introduction` function.
52 |
53 | ```python
54 | class LitApp(lapp.LightningFlow):
55 | def __init__(self):
56 | super().__init__()
57 | self.lit_intro = LitPanelPage(page=introduction, parallel=True)
58 | self.lit_big_data_viz = LitPanelPage(page="pages/big_data_viz.py", parallel=True)
59 | self.lit_crossfilter = LitPanelPage(page="pages/cross_filter.py", parallel=True)
60 | self.lit_streaming = LitPanelPage(page="pages/streaming.py", parallel=True)
61 |
62 | def run(self):
63 | self.lit_intro.run()
64 | self.lit_big_data_viz.run()
65 | self.lit_crossfilter.run()
66 | self.lit_streaming.run()
67 |
68 | def configure_layout(self):
69 | return [
70 | self.lit_intro.get_tab(name="Introduction"),
71 | self.lit_crossfilter.get_tab(name="Crossfiltering"),
72 | self.lit_streaming.get_tab(name="Streaming"),
73 | self.lit_big_data_viz.get_tab(name="Big Data Viz"),
74 | ]
75 |
76 | app = lapp.LightningApp(LitApp())
77 | ```
78 |
79 | ## 🐛 Active issues
80 |
81 | - [LightningFlow expects too fast an initial response from other servers](https://github.com/Lightning-AI/lightning/issues/13381)
--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
1 | """A lightning.ai app serving multiple Panel pages"""
2 | from __future__ import annotations
3 |
4 | import lightning_app as lapp
5 | import panel as pn
6 |
7 | from panel_lightning import LitPanelPage
8 |
9 | def introduction():
10 | ACCENT = "#792EE5"
11 |
12 | pn.extension(sizing_mode="stretch_width")
13 |
14 | info = """
15 | **[Panel](https://panel.holoviz.org/)** and the [HoloViz](https://holoviz.org/) ecosystem provides unique and powerful features such as big data viz via [DataShader](https://datashader.org/), easy cross filtering via [HoloViews](https://holoviews.org/), streaming and much more.
16 |
17 | Panel works with the tools you know and love ❤️. Panel ties into the PyData and Jupyter ecosystems as you can develop in notebooks and use ipywidgets. You can also develop in .py files.
18 |
19 | Panel is one of the 4 most popular data app frameworks in Python with [more than 400.000 downloads a month](https://pyviz.org/tools.html#dashboarding). It's especially popular in the scientific community.
20 |
21 | Panel is used by for example Rapids to power [CuxFilter](https://github.com/rapidsai/cuxfilter), a CuDF based big data viz framework.
22 |
23 | Panel can be deployed on your favorite server or cloud including **[lightning.ai](https://lightning.ai)**.
24 | """
25 |
26 | def lightning_string(value):
27 | return "# " + "⚡" * value
28 |
29 | lightning = pn.widgets.IntSlider(value=5, start=0, end=10, name="⚡")
30 | ilightning_string = pn.bind(lightning_string, value=lightning)
31 |
32 | check_out = """
33 | Check out my site [awesome-panel.org](https://awesome-panel.org) for more inspiration.
34 | """
35 |
36 | component = pn.Column(info, lightning, ilightning_string, check_out, sizing_mode="stretch_width")
37 |
38 | return pn.template.FastListTemplate(
39 | site="Awesome Panel",
40 | title="Introduction",
41 | accent_base_color=ACCENT,
42 | header_background=ACCENT,
43 | main=[component],
44 | )
45 |
46 |
47 | class LitApp(lapp.LightningFlow):
48 | def __init__(self):
49 | super().__init__()
50 | self.lit_intro = LitPanelPage(page=introduction, parallel=True)
51 | self.lit_big_data_viz = LitPanelPage(page="pages/big_data_viz.py", parallel=True)
52 | self.lit_crossfilter = LitPanelPage(page="pages/cross_filter.py", parallel=True)
53 | self.lit_streaming = LitPanelPage(page="pages/streaming.py", parallel=True)
54 |
55 | def run(self):
56 | self.lit_intro.run()
57 | self.lit_big_data_viz.run()
58 | self.lit_crossfilter.run()
59 | self.lit_streaming.run()
60 |
61 | def configure_layout(self):
62 | return [
63 | self.lit_intro.get_tab(name="Introduction"),
64 | self.lit_crossfilter.get_tab(name="Crossfiltering"),
65 | self.lit_streaming.get_tab(name="Streaming"),
66 | self.lit_big_data_viz.get_tab(name="Big Data Viz"),
67 | ]
68 |
69 |
70 |
71 | # lightning run app app.py
72 | app = lapp.LightningApp(LitApp())
73 |
--------------------------------------------------------------------------------
/assets/awesome-panel-lightning.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awesome-panel/awesome-panel-lightning/58458c420c96d161b5211c5341dfecd438acc4df/assets/awesome-panel-lightning.gif
--------------------------------------------------------------------------------
/assets/awesome-panel-lightning.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awesome-panel/awesome-panel-lightning/58458c420c96d161b5211c5341dfecd438acc4df/assets/awesome-panel-lightning.mp4
--------------------------------------------------------------------------------
/pages/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/awesome-panel/awesome-panel-lightning/58458c420c96d161b5211c5341dfecd438acc4df/pages/__init__.py
--------------------------------------------------------------------------------
/pages/big_data_viz.py:
--------------------------------------------------------------------------------
1 | import panel as pn
2 |
3 | pn.extension()
4 |
5 | pn.Column(
6 | "# Example coming up",
7 | pn.pane.HTML(
8 | "",
9 | sizing_mode="stretch_both",
10 | ),
11 | sizing_mode="stretch_both",
12 | ).servable()
--------------------------------------------------------------------------------
/pages/cross_filter.py:
--------------------------------------------------------------------------------
1 | from __future__ import annotations
2 |
3 | from typing import Any, Dict
4 |
5 | import holoviews as hv
6 | import panel as pn
7 | from bokeh.sampledata import iris
8 | from holoviews import opts
9 |
10 | pn.extension()
11 |
12 | print("starting cross filter")
13 |
14 | ACCENT = "#792EE5"
15 | LIGHTNING_SPINNER_URL = (
16 | "https://cdn.jsdelivr.net/gh/MarcSkovMadsen/awesome-panel-assets@master/spinners/material/"
17 | "bar_chart_lightning_purple.svg"
18 | )
19 | LIGHTNING_SPINNER = pn.pane.HTML(
20 | f"
"
21 | )
22 | OPTS: Dict[str, Dict[str, Any]] = {
23 | "all": {
24 | "scatter": {"color": ACCENT, "responsive": True, "size": 10},
25 | "hist": {"color": ACCENT, "responsive": True},
26 | },
27 | "bokeh": {
28 | "scatter": {"tools": ["hover"], "active_tools": ["box_select"]},
29 | "hist": {"tools": ["hover"], "active_tools": ["box_select"]},
30 | },
31 | }
32 |
33 | MAXIMIZE_FIRST_PANEL = """
34 | .bk-root { height: calc( 100vh - 150px ) !important; }
35 | """
36 | NO_HEADER_RAW_CSS = """
37 | nav#header {display: None}
38 | """
39 |
40 | pn.config.raw_css.append(NO_HEADER_RAW_CSS)
41 | pn.config.raw_css.append(MAXIMIZE_FIRST_PANEL)
42 |
43 |
44 | component = pn.Column(LIGHTNING_SPINNER, sizing_mode="stretch_both")
45 |
46 | def load(*args):
47 |
48 | pn.extension(sizing_mode="stretch_width")
49 | hv.extension("bokeh")
50 | dataset = hv.Dataset(iris.flowers)
51 |
52 | scatter = hv.Scatter(dataset, kdims=["sepal_length"], vdims=["sepal_width"]).opts(
53 | responsive=True
54 | )
55 | hist = hv.operation.histogram(dataset, dimension="petal_width", normed=False)
56 |
57 | selection_linker = hv.selection.link_selections.instance()
58 | scatter = selection_linker(scatter).opts(
59 | opts.Scatter(**OPTS["all"]["scatter"], **OPTS["bokeh"]["scatter"])
60 | )
61 | hist = selection_linker(hist).opts(
62 | opts.Histogram(**OPTS["all"]["hist"], **OPTS["bokeh"]["hist"], height=300)
63 | )
64 | component[:] = [
65 | pn.pane.HoloViews(scatter, sizing_mode="stretch_both"),
66 | hist,
67 | ]
68 |
69 |
70 | pn.state.onload(load)
71 |
72 | pn.template.FastListTemplate(
73 | title="Cross Filter", theme="dark", theme_toggle=False, main=[component]
74 | ).servable()
75 |
--------------------------------------------------------------------------------
/pages/shared.py:
--------------------------------------------------------------------------------
1 | import panel as pn
2 |
3 | LIGHTNING_PURPLE = "#792EE5"
4 |
5 | LIGHTNING_SPINNER_URL = (
6 | "https://cdn.jsdelivr.net/gh/MarcSkovMadsen/awesome-panel-assets@master/spinners/material/"
7 | "bar_chart_lightning_purple.svg"
8 | )
9 |
10 | def get_lightning_spinner(url=LIGHTNING_SPINNER_URL, **params) -> pn.pane.HTML:
11 | """Returns a loding spinner for your lightning.ai app"""
12 | return pn.pane.HTML(
13 | f"
", **params
14 | )
15 |
16 | LIGHTNING_SPINNER = get_lightning_spinner(sizing_mode="fixed", width=100, height=100)
17 |
--------------------------------------------------------------------------------
/pages/streaming.py:
--------------------------------------------------------------------------------
1 | import panel as pn
2 | import numpy as np
3 |
4 | pn.extension()
5 |
6 | print("starting streaming")
7 |
8 | ACCENT = "#792EE5"
9 | LIGHTNING_SPINNER_URL = (
10 | "https://cdn.jsdelivr.net/gh/MarcSkovMadsen/awesome-panel-assets@master/spinners/material/"
11 | "bar_chart_lightning_purple.svg"
12 | )
13 | LIGHTNING_SPINNER = pn.pane.HTML(
14 | f"
"
15 | )
16 |
17 | ncols = 4
18 | objects = [LIGHTNING_SPINNER]
19 | grid = pn.layout.GridBox(objects=objects, ncols=ncols)
20 |
21 |
22 | def load(*args):
23 | objects = [
24 | pn.indicators.Trend(
25 | title=f"⚡ Panel {index+1}",
26 | data={"x": np.arange(50), "y": np.random.rand(50).cumsum()},
27 | plot_type="area",
28 | sizing_mode="stretch_both",
29 | plot_color=ACCENT,
30 | )
31 | for index in range(0, ncols**2)
32 | ]
33 |
34 | grid.objects = objects
35 |
36 | def stream_data():
37 | for trend in objects:
38 | trend.stream(
39 | {
40 | "x": [trend.data["x"][-1] + 1],
41 | "y": [trend.data["y"][-1] + np.random.randn()],
42 | },
43 | rollover=50,
44 | )
45 |
46 | pn.state.add_periodic_callback(stream_data, period=500)
47 |
48 |
49 | pn.state.onload(load)
50 |
51 | grid.servable()
52 |
--------------------------------------------------------------------------------
/panel_lightning.py:
--------------------------------------------------------------------------------
1 | """The panel_lightning module provides the `LitPanelPage` that makes it easy
2 | to add a Panel page to your `LightningFlow`.
3 | """
4 | from __future__ import annotations
5 |
6 | import pathlib
7 | from types import ModuleType
8 | from typing import Callable, Dict, Union
9 |
10 | import lightning_app as lapp
11 | import panel as pn
12 | from bokeh.application.handlers import code
13 | from bokeh.server.server import Server
14 | from lightning_app.core import constants
15 |
16 | Page = Union[Callable[[], pn.viewable.Viewable], str, pathlib.Path]
17 |
18 |
19 | class _CodeRunnerWithFastInitialResponse(code.CodeRunner):
20 | """Helper class to provide a fast initial response to the lightning.ai server
21 |
22 | C.f. https://github.com/Lightning-AI/lightning/issues/13335
23 | """
24 |
25 | _is_patched = False
26 | _initial_response = True
27 |
28 | def run(self, module: ModuleType, post_check: Callable[[], None] | None = None) -> None:
29 | """Patches the code.CodeRunner to provide a fast initial response"""
30 | if self._initial_response:
31 | pn.pane.HTML("