Sync PointDraw, Dataframe and Tabulator

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.