Skip to content

Commit a818a97

Browse files
gaborbernatibdafna
authored andcommitted
Fix selection helper iterator
Multiple iterations might happen at the same time, so it's important that we don't store the iterator state within the selection itself. Signed-off-by: Bernát Gábor <[email protected]>
1 parent 9c523d5 commit a818a97

File tree

2 files changed

+66
-61
lines changed

2 files changed

+66
-61
lines changed

ipydatagrid/datagrid.py

Lines changed: 50 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright (c) Bloomberg.
22
# Distributed under the terms of the Modified BSD License.
33

4+
from collections.abc import Iterator
45
from copy import deepcopy
56
from math import floor
67

@@ -23,26 +24,11 @@
2324
from .cellrenderer import CellRenderer, TextRenderer
2425

2526

26-
class SelectionHelper:
27-
28-
"""A Helper Class for processing selections. Provides an iterator
29-
to traverse selected cells.
30-
"""
31-
32-
def __init__(self, grid, **kwargs):
33-
super().__init__(**kwargs)
34-
self._grid = grid
35-
self._num_columns = -1
36-
self._num_rows = -1
37-
38-
def __iter__(self):
27+
class SelectionIterator(Iterator):
28+
def __init__(self, selections):
3929
self._rect_index = 0
4030
self._cell_index = 0
41-
self._selections = [
42-
self._transform_rect_for_selection_mode(rect)
43-
for rect in self._grid.selections
44-
]
45-
return self
31+
self._selections = selections
4632

4733
def __next__(self):
4834
if self._rect_index >= len(self._selections):
@@ -61,6 +47,51 @@ def __next__(self):
6147
else:
6248
return row_col
6349

50+
@staticmethod
51+
def _index_to_row_col(rect, index):
52+
num_rows = rect["r2"] - rect["r1"] + 1
53+
num_cols = rect["c2"] - rect["c1"] + 1
54+
if index > (num_rows * num_cols - 1):
55+
return None
56+
57+
return {
58+
"r": rect["r1"] + floor(index / num_cols),
59+
"c": rect["c1"] + index % num_cols,
60+
}
61+
62+
def _cell_in_previous_selected_rects(self, cell):
63+
return any(
64+
self._cell_in_rect(cell, self._selections[i])
65+
for i in range(0, self._rect_index)
66+
)
67+
68+
@staticmethod
69+
def _cell_in_rect(cell, rect):
70+
return (
71+
rect["r1"] <= cell["r"] <= rect["r2"]
72+
and rect["c1"] <= cell["c"] <= rect["c2"]
73+
)
74+
75+
76+
class SelectionHelper:
77+
78+
"""A Helper Class for processing selections. Provides an iterator
79+
to traverse selected cells.
80+
"""
81+
82+
def __init__(self, grid, **kwargs):
83+
super().__init__(**kwargs)
84+
self._grid = grid
85+
self._num_columns = -1
86+
self._num_rows = -1
87+
88+
def __iter__(self):
89+
selections = [
90+
self._transform_rect_for_selection_mode(rect)
91+
for rect in self._grid.selections
92+
]
93+
return SelectionIterator(selections)
94+
6495
def __len__(self):
6596
return sum(1 for _ in self)
6697

@@ -70,7 +101,7 @@ def all(self):
70101
represented as a dictionary
71102
with keys 'r': row and 'c': column
72103
"""
73-
return [cell for cell in self] # noqa: C416
104+
return list(self)
74105

75106
def all_values(self):
76107
"""
@@ -81,31 +112,6 @@ def all_values(self):
81112
for cell in self
82113
]
83114

84-
@staticmethod
85-
def _cell_in_rect(cell, rect):
86-
return (
87-
rect["r1"] <= cell["r"] <= rect["r2"]
88-
and rect["c1"] <= cell["c"] <= rect["c2"]
89-
)
90-
91-
def _cell_in_previous_selected_rects(self, cell):
92-
return any(
93-
self._cell_in_rect(cell, self._selections[i])
94-
for i in range(0, self._rect_index)
95-
)
96-
97-
@staticmethod
98-
def _index_to_row_col(rect, index):
99-
num_rows = rect["r2"] - rect["r1"] + 1
100-
num_cols = rect["c2"] - rect["c1"] + 1
101-
if index > (num_rows * num_cols - 1):
102-
return None
103-
104-
return {
105-
"r": rect["r1"] + floor(index / num_cols),
106-
"c": rect["c1"] + index % num_cols,
107-
}
108-
109115
def _transform_rect_for_selection_mode(self, rect):
110116
selection_mode = self._grid.selection_mode
111117
if selection_mode == "row":

tests/test_datagrid.py

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,22 @@
11
import pandas as pd
2+
import pytest
23

34
from ipydatagrid import DataGrid
45

56

6-
def test_selections():
7+
@pytest.mark.parametrize("clear", [True, False])
8+
def test_selections(clear: bool) -> None:
79
df = pd.DataFrame(data={"A": [1, 2, 3], "B": [4, 5, 6]})
8-
datagrid = DataGrid(
9-
df, selection_mode="cell", layout={"height": "100px"}, editable=True
10-
)
11-
datagrid.select(1, 0, 2, 1) # Select 1A to 2B
12-
13-
assert datagrid.selections == [{"r1": 1, "c1": 0, "r2": 2, "c2": 1}]
14-
15-
16-
def test_selection_clearing():
17-
df = pd.DataFrame(data={"A": [1, 2, 3], "B": [4, 5, 6]})
18-
datagrid = DataGrid(
19-
df, selection_mode="cell", layout={"height": "100px"}, editable=True
20-
)
21-
datagrid.select(1, 0, 2, 1) # Select 1A to 2B
22-
datagrid.clear_selection()
23-
assert datagrid.selections == []
10+
layout = {"height": "100px"}
11+
grid = DataGrid(df, selection_mode="cell", layout=layout, editable=True)
12+
grid.select(1, 0, 2, 1) # Select 1A to 2B
13+
if clear:
14+
grid.clear_selection()
15+
assert grid.selected_cells == []
16+
else:
17+
assert grid.selected_cells == [
18+
{"c": 0, "r": 1},
19+
{"c": 1, "r": 1},
20+
{"c": 0, "r": 2},
21+
{"c": 1, "r": 2},
22+
]

0 commit comments

Comments
 (0)