Hello,
In my dashboard, I am trying to create a button that gets data from a number of sources and saves it into a Pandas DataFrame, like this:
import pandas as pd
import panel as pn
df = 'Whatever'
button = pn.widgets.Button(name='Get data', button_type='primary')
def getData(event):
df = pd.read_csv('mydata.csv')
return df
df = button.on_click(getData)
button
With the code above, pushing the button leaves the df
variable empty, since on_click
doesn’t actually return anything.
I have worked around the problem by using a global variable to change df
directly from within the getData
function, like this:
def getData(event):
global df
df = pd.read_csv('mydata.csv')
button.on_click(getData)
I am quite new to Python and even newer to coding UI:s, but I have read that using global variables should be avoided as much as possible, so I am wondering whether there is a better way to do this?
Marc
June 1, 2020, 5:26am
2
UPDATE: More recent version below
HI @dabbatelli
Welcome to the community. Good question.
There are several ways to accomplish what you want to do. I love to used param.Parameterized
classes. The reason being
You will get code that can be used from code and without the UI. But of course also from the UI.
You will get code that can be maintained and further developed over time. I.e. a solution that can scale.
So I will provide a solution based on param.Parameterized
.
I know there are a few things to learn if you are a new user to Python and Panel. Feel free to ask questions.
I hope others will contribute their preferred solution.
"""Answer to [Discourse Question]\
(https://discourse.holoviz.org/t/handling-returns-from-button-on-click/733)"""
import pandas as pd
import panel as pn
import param
class MyDataframeExtractorApp(param.Parameterized):
"""Enables a user to extract and view a DataFrame"""
data = param.DataFrame(precedence=2)
extract = param.Action(label="Get data", precedence=1)
view = param.Parameter()
def __init__(self, **params):
params["data"] = pd.DataFrame()
params["extract"] = self._extract
params["view"] = self._get_view()
super().__init__(**params)
def _extract(self, event=None):
# self.data = pd.read_csv('mydata.csv')
self.data = pd.DataFrame({"x": [1, 2, 3], "y": [2, 4, 6]})
def _get_view(self):
top_app_bar = pn.Row(
pn.pane.Markdown("# Panel Data Extractor"),
sizing_mode="stretch_width",
)
content = pn.Param(
self,
parameters=["extract", "data"],
widgets={
"extract": {"button_type": "success", "width": 150, "sizing_mode": "fixed"},
"data": {
"height": 600
}, # Needed due to https://github.com/holoviz/panel/issues/919
},
show_name=False,
)
return pn.Column(
top_app_bar,
content,
max_width=1000,
sizing_mode="stretch_width",
)
app = MyDataframeExtractorApp()
app.view # In notebook
# app.view.servable() # with panel serve
Hello!
I had been reading about parameterized classes in the documentation, but I had not looked into it closely: I guess now it is a good time to start reading more about it.
Thanks for the great example!
1 Like
gbl
June 18, 2023, 2:34pm
4
I get a:
AttributeError: ‘MyDataframeExtractorApp’ object has no attribute ‘_param_watchers’
when I run this in a notebook.
Marc
June 29, 2023, 2:56am
5
Hi @gbl
Try changing the __init__
method to
def __init__(self, **params):
params["data"] = pd.DataFrame()
params["extract"] = self._extract
super().__init__(**params)
self.view = self._get_view()
It works for me
Marc
June 29, 2023, 3:09am
6
Panel 1.1.1 version
With Panel 1.1.1 I would probably do something like
"""Answer to [Discourse Question]\
(https://discourse.holoviz.org/t/handling-returns-from-button-on-click/733)"""
import pandas as pd
import panel as pn
import param
class MyDataframeExtractorApp(pn.viewable.Viewer):
"""Enables a user to extract and view a DataFrame"""
data = param.DataFrame(precedence=2)
extract = param.Action(label="Get data", precedence=1)
def __init__(self, **params):
params["data"] = pd.DataFrame(columns=["x", "y"])
params["extract"] = self._extract
super().__init__(**params)
self._view = None
def _extract(self, event=None):
# self.data = pd.read_csv('mydata.csv')
self.data = pd.DataFrame({"x": list(range(0, 100)), "y": list(range(0, 100))})
def _get_view(self):
if not self._view:
top_app_bar = pn.Row(
pn.pane.Markdown("# Panel Data Extractor"),
sizing_mode="stretch_width",
)
extract_button = pn.widgets.Button.from_param(
self.param.extract, width=150, sizing_mode="fixed", button_type="primary"
)
table = pn.pane.Perspective(object=self.param.data, height=600)
self._view = pn.Column(
top_app_bar,
extract_button,
table,
max_width=1000,
sizing_mode="stretch_width",
)
return self._view
def __panel__(self):
return self._get_view()
# In notebook:
# pn.extension("perspective", sizing_mode="stretch_width", design="bootstrap")
# app = MyDataframeExtractorApp()
# pn.panel(app)
if pn.state.served: # with panel serve
pn.extension("perspective", sizing_mode="stretch_width", template="fast")
app = MyDataframeExtractorApp()
app.servable()
gbl
June 29, 2023, 2:18pm
8
Thanks for the update to Panel 1.1.1
1 Like