jupyter-widgets / ipydatagrid

Fast Datagrid widget for the Jupyter Notebook and JupyterLab
BSD 3-Clause "New" or "Revised" License
579 stars 51 forks source link

methods for robustly retrieving `key` of `selected_cells` #340

Open jgunstone opened 2 years ago

jgunstone commented 2 years ago

Is your feature request related to a problem? Please describe.

I have a workflow where users select an ipydatagrid row, press a button to edit the row data (using ipywidgets), and then save the edit back to the grid. Users continually use the column filters to navigate to appropriate rows, and ideally the workflow can work independently of where a filter is applied.

the process i'd like to follow is:

  1. user selects row
  2. user pushes "edit" button, the code uses the ipydatagrid API to retrieve the data from that row
  3. the row data is edited
  4. the users presses "save" and the row is updated

the problem I'm having is with retrieving the row index for a selected row. the grid.selected_cells method returns the cells, but the row indexes match up with filtered table rather than the un-filtered table

unfiltered: image

filtered: image

so grid.selected_cells returns rows == 0 and 1 whereas the keys to look up the data from grid.data or grid._data['data'] is actually 0 and 4

Describe the solution you'd like it would be great if the the grid.selected_cells method returned the row numbers for the base data rather than the filtered grid (or if there was an additional method for this).

Describe alternatives you've considered I did have a method where I:

this was a bit hacky, but appeared to work. but then it fell down if sorting was applied...

image

many thanks!

jgunstone commented 2 years ago

I noted this as a "feature-request" but i think that the following linked behaviour could be considered a bug...

image

here:

grid.selected_cell_iterator.all_values() returns incorrect values as the SelectionHelper is initiated with self._data whereas the selected_cell_values method is initiates the SelectionHelper with sliced version of the data object

https://github.com/bloomberg/ipydatagrid/blob/f45a84df204137163c1a14607bc6318d21745d9f/ipydatagrid/datagrid.py#L655-L673

jgunstone commented 2 years ago

digging a bit deeper - it appears that selections and transforms and mutually exclusive... i.e. applying a transform will clear any selections

jgunstone commented 2 years ago

for my immediate requirement I did something akin to:


class GridWrapper(DataGrid):
    def __init__(df, **kwargs):
        super().__init__(df, **kwargs)

    @property
    def selected_rows_data(self):
        """Get the data selected in the table which is returned as a dataframe."""
        s = self.selected_visible_cell_iterator
        rows = set([l["r"] for l in s])
        return [s._data["data"][r] for r in rows]

    @property
    def selected_keys(self):
        """Return the keys of the selected rows."""
        s = self.selected_visible_cell_iterator
        index = self.get_dataframe_index(self.data)
        rows = set([l["r"] for l in s])
        return [s._data["data"][r][index] for r in rows]

    @property
    def selected_visible_cell_iterator(self):
        """
        An iterator to traverse selected cells one by one.
        Identical to the `selected_cell_iterator` property but the SelectionHelper
        is initiated from the visible data only.
        """
        # Copy of the front-end data model
        view_data = self.get_visible_data()

        # Get primary key from dataframe
        index_key = self.get_dataframe_index(view_data)

        # Serielize to JSON table schema
        view_data_object = self.generate_data_object(view_data, "ipydguuid", index_key)

        return SelectionHelper(view_data_object, self.selections, self.selection_mode)

this way accessing the SelectionHelper with the visible data only (i.e. the transformed grid, self.get_visible_data() .

I'll leave this open as it took me a while (and some digging in the source code) to understand that was what I needed to do. If you think that these methods (or similar) would be a useful addition to the source code I'll happily make a PR, or if I've missed something about how to achieve the desired outcome another way I'm keen to understand it.

many thanks

ibdafna commented 2 years ago

@jgunstone Thank you so much for taking the time to compose and post your findings 🙏. I will be pushing bug fixes and some enhancements in the next couple of weeks, so will definitely get back to you.

jgunstone commented 2 years ago

you're most welcome and I'm looking forward to the updates! (apologies if the above is a little hard to follow, I understood the issue better by the end than I did when I first created the issue!)

many thanks