Editable version of Gridstack

Hey,

As mentioned in the documentation, try programatically edit or remove objects from a GridStack is not recommended as it can lead to errors.

The following allows you to clear the component list of a gridstack to add some over. Note that the resize tools might stop working after some clearing and requires refreshing the page.

After/before a component is added to the gridstack using grid[range_x, range_y], the component has to be added using grid.add_object(…).

from __future__ import annotations

from typing import Dict, Tuple

import param
import panel as pn
pn.extension("gridstack")
from panel.layout.gridstack import GridStack



class CustomGridStack(GridStack):
    """
    Custom version of the panel.layout.gridstack.GridStack to allow clearing the objects properly.
    """
    object_dict:Dict[str, pn.viewable.Viewable] = {}
    """Dictionnary of viewables that have to be manually added using the function add_object"""

    @param.depends('state', watch=True)
    def _update_objects(self):
        """Updates the range of the objects after a state change triggered by the javascript.
        """
        objects = {}

        for p in self.state:
            objects[(p['y0'], p['x0'], p['y1'], p['x1'])] = self.object_dict[p['id']]
            
        self.objects.clear()
        self.objects.update(objects)

        # self.print_stored_items()
        # self.print_state()

        self._update_sizing()


    @param.depends('objects', watch=True)
    def _update_sizing(self):
        """Updates the objects internal size to match the data stored in the gridstack and in the state attribute
        """
        if self.ncols and self.width:
            width = self.width/self.ncols
        else:
            width = 0

        if self.nrows and self.height:
            height = self.height/self.nrows
        else:
            height = 0

        for (y0, x0, y1, x1), obj in self.objects.items():
            x0 = 0 if x0 is None else x0
            x1 = (self.ncols) if x1 is None else x1
            y0 = 0 if y0 is None else y0
            y1 = (self.nrows) if y1 is None else y1
            h, w = y1-y0, x1-x0

            properties = {}
            if self.sizing_mode in ['fixed', None]:
                if width:
                    properties['width'] = int(w*width)
                if height:
                    properties['height'] = int(h*height)
            else:
                properties['sizing_mode'] = self.sizing_mode
                if 'width' in self.sizing_mode and height:
                    properties['height'] = int(h*height)
                elif 'height' in self.sizing_mode and width:
                    properties['width'] = int(w*width)
            obj.param.update(**{
                k: v for k, v in properties.items()
                if not obj.param[k].readonly
            })

    # def update_render(self,):
    def clear_objects(self,):
        """Clears the objects list and the object dictionnary.
        """
        self.objects.clear()
        self.object_dict = {}
        
    def print_stored_items(self,):
        """Prints the information of the objects stored
        """
        print("Stored objects : ")
        for loc, obj in self.objects.items():
            print(f" - {loc} - {id(obj)}: {obj}")

        print()

    def print_state(self,):
        """Prints the current gridstack state (viewable id to coordinates in the grid).
        """
        print("State : ")
        for p in self.state:
            print(f" - {p['id']} : {p['y0']}, {p['x0']}, {p['y1']}, {p['x1']}")
        print()
        
    def add_object(self, obj:pn.viewable.Viewable, 
                            x_range:Tuple[int, int],
                            y_range:Tuple[int, int]):
        """Adds an object to the gridstack objects dict

        Parameters
        ----------
        obj : pn.viewable.Viewable
            Panel added
        x_range : Tuple[int, int]
            Horizontal range used to update the number of rows and columns
        y_range : Tuple[int, int]
            Vertical range used to update the number of rows and columns
        """
        print(f"Adding object : {id(obj)}")
        self.nrows = max(self.nrows, max(x_range))
        self.ncols = max(self.ncols, max(y_range))

        self.object_dict[str(id(obj))] = obj

1 Like