Hi,
I was able to be close to a solution but I am getting the following warning message when I edit the table and process the edit data:
WARNING:param.ParamMethod02722: HoloViews pane model Figure(id='1952', ...) could not be replaced with new model Figure(id='2238', ...), ensure that the parent is not modified at the same time the panel is being updated.
The code I use is:
import param
import panel as pn
import pandas as pd
import holoviews as hv
from holoviews.streams import PointDraw
hv.extension("bokeh")
pn.extension("tabulator")
base_style = dict(
height=600,
responsive=True,
toolbar="above",
gridstyle={
"grid_line_color": "#e0e0e0",
"grid_line_width": 1,
},
show_grid=True,
fontsize={
"ticks": "8pt",
"title": "12pt",
"xlabel": "10pt",
"ylabel": "10pt",
},
xticks=4,
xaxis="top",
invert_yaxis=True,
)
profile_style = dict(size=10)
class DataProfile(param.Parameterized):
values = param.DataFrame(doc="Values as Pandas DataFrame")
stream = param.ClassSelector(
default=PointDraw(num_objects=10),
class_=(PointDraw,),
precedence=-1,
instantiate=True,
doc="Param that will handle the data reactivity between the table and the plot",
)
sort_col = param.String(
default="y",
doc="column of the Pandas DataFrame which will be used for sorting (i.e: y)",
)
val_col = param.String(
doc="column of the Pandas DataFrame which contain the main value (i.e: x)"
)
x_axis_label = param.String(default=None, doc="Label for the x axis of the plot")
y_axis_label = param.String(default="y", doc="Label for the y axis of the plot")
def __init__(self, **params):
super().__init__(**params)
if self.x_axis_label is None:
self.x_axis_label = self.val_col
def table(self):
table_ = pn.widgets.Tabulator(self.values, width=500, layout="fit_columns")
table_.link(self.values, callbacks={"value": self._callback_tabulator})
return table_
def plot(self):
points = hv.Scatter(self.values, self.val_col, self.sort_col)
points.opts(tools=["hover"], active_tools=["point_draw"], **profile_style)
self.stream.source = points
line = hv.Curve(self.values, self.val_col, self.sort_col)
return (points * line).opts(
**base_style,
xlabel=self.x_axis_label,
ylabel=self.y_axis_label,
)
@pn.depends("stream.data", watch=True)
def _update_dataframe(self):
"""Function to reflect the change in the PointDraw data to the original data"""
stream_df = pd.DataFrame(self.stream.data)
if stream_df.equals(self.values) is False:
# Here the data processing happens
df = stream_df.round(2).sort_values(self.sort_col).reset_index()
if "index" in df.columns:
df = df.drop(["index"], axis=1)
self.values = df
def _callback_tabulator(self, target, event):
"""Simple callback to update the values when the table is updated"""
# This will throw a warning and the connection is lost between the plot and the table
self.values = event.new.sort_values(self.sort_col).reset_index()
def view(self):
return pn.Row(self.plot, self.table, width_policy="max")
class MyPlot(param.Parameterized):
profile_values = param.DataFrame(columns=["x", "y"])
def __init__(self, **params):
super().__init__(**params)
self.profile = DataProfile(
values=self.profile_values,
val_col="x",
sort_col="y",
x_axis_label="A more meaningful title [m]",
)
def view(self):
return pn.Column(
self.profile.table,
self.profile.plot,
width_policy="max",
)
MyPlot(
profile_values=pd.DataFrame(data={"x": [0, 5, 10], "y": [0, 3, 10]}),
).view().servable(title="Example with two way edit")
If I add a point to the plot via the PointDraw
method, it works fine and the table gets updated and the data is sorted as intended along the y
column in this example.
The problem is that if I edit the table, I get the warning message as mentioned above and the table and plot are not in sync anymore?
What I am doing wrong here?
In my use case, this is important that any point added in the graph or table is sorted by the sort_col
(here in the example set to y
).
Any help would be really appreciated as I really don’t understand what is the problem here.