Repository: napari/napari

Issue:
Selecting points in 3D
## 🐛 Bug

When I select a 3D point while `ndisplay == 2` and then try to change the viewer to a 3D one napari crashes.

## To Reproduce
```python
with napari.gui_qt():
    v = napari.view_points(np.array([[1, 0, 0], [0, 0, 1]]))
```
Then select a point with the cursor and try to change the display to a 3D one.

<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->

## Expected behavior

I expect napari to transition to a 3D view of the data without crashing. When I come back to the 2D view it would be nice if the points would still be highlighted. This is not currently the case with 2D points, though.

## Environment

Python 3.7, napari 0.2.10


## Additional context
```
WARNING: Traceback (most recent call last):
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/_qt/qt_viewer_buttons.py", line 173, in <lambda>
    lambda state=self: self.change_ndisplay(state)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/_qt/qt_viewer_buttons.py", line 178, in change_ndisplay
    self.viewer.dims.ndisplay = 3
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/components/dims.py", line 296, in ndisplay
    self.events.ndisplay()
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/utils/event.py", line 508, in __call__
    self._invoke_callback(cb, event)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/utils/event.py", line 529, in _invoke_callback
    cb_event=(cb, event),
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/utils/event.py", line 523, in _invoke_callback
    cb(event)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/components/viewer_model.py", line 88, in <lambda>
    self.dims.events.ndisplay.connect(lambda e: self._update_layers())
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/components/viewer_model.py", line 1037, in _update_layers
    layer.dims.ndisplay = self.dims.ndisplay
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/components/dims.py", line 296, in ndisplay
    self.events.ndisplay()
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/utils/event.py", line 508, in __call__
    self._invoke_callback(cb, event)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/utils/event.py", line 529, in _invoke_callback
    cb_event=(cb, event),
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/utils/event.py", line 523, in _invoke_callback
    cb(event)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/layers/base/base.py", line 180, in <lambda>
    self.dims.events.ndisplay.connect(lambda e: self._update_dims())
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/layers/base/base.py", line 383, in _update_dims
    self.refresh()
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/layers/base/base.py", line 600, in refresh
    self._set_view_slice()
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/layers/points/points.py", line 654, in _set_view_slice
    self._selected_box = self.interaction_box(self._selected_view)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/layers/points/points.py", line 480, in interaction_box
    data = points_to_squares(data, size)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/layers/points/points.py", line 972, in points_to_squares
    points + np.sqrt(2) / 2 * np.array([sizes, sizes]).T,
ValueError: operands could not be broadcast together with shapes (1,3) (1,2)
WARNING:vispy:Traceback (most recent call last):
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/_qt/qt_viewer_buttons.py", line 173, in <lambda>
    lambda state=self: self.change_ndisplay(state)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/_qt/qt_viewer_buttons.py", line 178, in change_ndisplay
    self.viewer.dims.ndisplay = 3
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/components/dims.py", line 296, in ndisplay
    self.events.ndisplay()
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/utils/event.py", line 508, in __call__
    self._invoke_callback(cb, event)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/utils/event.py", line 529, in _invoke_callback
    cb_event=(cb, event),
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/utils/event.py", line 523, in _invoke_callback
    cb(event)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/components/viewer_model.py", line 88, in <lambda>
    self.dims.events.ndisplay.connect(lambda e: self._update_layers())
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/components/viewer_model.py", line 1037, in _update_layers
    layer.dims.ndisplay = self.dims.ndisplay
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/components/dims.py", line 296, in ndisplay
    self.events.ndisplay()
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/utils/event.py", line 508, in __call__
    self._invoke_callback(cb, event)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/utils/event.py", line 529, in _invoke_callback
    cb_event=(cb, event),
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/utils/event.py", line 523, in _invoke_callback
    cb(event)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/layers/base/base.py", line 180, in <lambda>
    self.dims.events.ndisplay.connect(lambda e: self._update_dims())
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/layers/base/base.py", line 383, in _update_dims
    self.refresh()
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/layers/base/base.py", line 600, in refresh
    self._set_view_slice()
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/layers/points/points.py", line 654, in _set_view_slice
    self._selected_box = self.interaction_box(self._selected_view)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/layers/points/points.py", line 480, in interaction_box
    data = points_to_squares(data, size)
  File "/home/hagai/.conda/envs/ncd/lib/python3.7/site-packages/napari/layers/points/points.py", line 972, in points_to_squares
    points + np.sqrt(2) / 2 * np.array([sizes, sizes]).T,
ValueError: operands could not be broadcast together with shapes (1,3) (1,2)
```



Discussion:
> I expect napari to transition to a 3D view of the data without crashing.

For sure, a bug must have crept in here - I can work on a fix + test to make sure it doesn't happen again

> When I come back to the 2D view it would be nice if the points would still be highlighted.

Right now we don't support any type of selection in 3D - which maybe isn't so great, but doing so isn't trivial. There has been some discussion around this in #515, but maybe until that get's implemented we might just unselect everything when you move to 3D, which I know isn't ideal either, but is definitely better than crashing
I understand selecting in 3D is hard - any Blender\3D modeling software user can tell you that :)
I think that the best outcome of this bug fix is that if I have data selected before transitioning to 3D - the data will still be selected after the transition. It might also be nice to be able to select data from the command line (by writing `Layer.selected_data = [1, 13, 27, 30]`).

If this is too complicated then I'd be happy if the selected points will be cleared during the 3D phase, but when we go back to 2D the points will be re-selected. 

Finally - simply not crashing is also an improvement :D
> For sure, a bug must have crept in here - I can work on a fix + test to make sure it doesn't happen again

@sofroniewn , it sounds like you're on it, but I have time this afternoon to work on it if you're busy.
@kevinyamauchi I havn't started yet - would be great if you could take this one!!
In order to address this quickly, I made a small PR (#902) that fixes the bug.

I like the other suggestions made by @HagaiHargil to add support for: (1) the selected points get "remembered" when going to 3D and then re-selected when transitioning back to 2D and (2) allow points to be selected via that command line when in 3D. I didn't include them in that initial PR because they will require a bit more of a refactor and I didn't want to hold up the bug fix. I can look into adding those futures later this week.
Hey, @HagaiHargil. We just merged the quick fix (i.e., napari no longer crashes when switching from 2D to 3D with a point selected). Feel free to pull master and give it a go. I like your other suggestions, so I'll work on getting a prototype together in the next couple of days.

Resolving patch:
diff --git a/napari/_qt/layers/qt_points_layer.py b/napari/_qt/layers/qt_points_layer.py
--- a/napari/_qt/layers/qt_points_layer.py
+++ b/napari/_qt/layers/qt_points_layer.py
@@ -22,8 +22,12 @@ def __init__(self, layer):
         self.layer.events.n_dimensional.connect(self._on_n_dim_change)
         self.layer.events.symbol.connect(self._on_symbol_change)
         self.layer.events.size.connect(self._on_size_change)
-        self.layer.events.edge_color.connect(self._on_edge_color_change)
-        self.layer.events.face_color.connect(self._on_face_color_change)
+        self.layer.events.current_edge_color.connect(
+            self._on_edge_color_change
+        )
+        self.layer.events.current_face_color.connect(
+            self._on_face_color_change
+        )
         self.layer.events.editable.connect(self._on_editable_change)
 
         sld = QSlider(Qt.Horizontal)
diff --git a/napari/_vispy/vispy_points_layer.py b/napari/_vispy/vispy_points_layer.py
--- a/napari/_vispy/vispy_points_layer.py
+++ b/napari/_vispy/vispy_points_layer.py
@@ -4,11 +4,12 @@
 from vispy.visuals.transforms import ChainTransform
 
 from .vispy_base_layer import VispyBaseLayer
+from ..utils.colormaps.standardize_color import transform_color
 
 
 class VispyPointsLayer(VispyBaseLayer):
     _highlight_color = (0, 0.6, 1)
-    _highlight_width = 1.5
+    _highlight_width = 5
 
     def __init__(self, layer):
         # Create a compound visual with the following four subvisuals:
@@ -35,18 +36,16 @@ def _on_display_change(self):
         if self.layer.dims.ndisplay == 2:
             self.node = Compound([Markers(), Markers(), Line()])
         else:
-            self.node = Markers()
+            self.node = Compound([Markers(), Markers()])
         self.node.parent = parent
         self._reset_base()
 
     def _on_data_change(self, event=None):
         # Check if ndisplay has changed current node type needs updating
         if (
-            self.layer.dims.ndisplay == 3
-            and not isinstance(self.node, Markers)
+            self.layer.dims.ndisplay == 3 and len(self.node._subvisuals) != 2
         ) or (
-            self.layer.dims.ndisplay == 2
-            and not isinstance(self.node, Compound)
+            self.layer.dims.ndisplay == 2 and len(self.node._subvisuals) != 3
         ):
             self._on_display_change()
             self._on_highlight_change()
@@ -68,10 +67,7 @@ def _on_data_change(self, event=None):
             data = self.layer._data_view
             size = self.layer._size_view
 
-        if self.layer.dims.ndisplay == 2:
-            set_data = self.node._subvisuals[0].set_data
-        else:
-            set_data = self.node.set_data
+        set_data = self.node._subvisuals[0].set_data
 
         set_data(
             data[:, ::-1] + 0.5,
@@ -85,22 +81,15 @@ def _on_data_change(self, event=None):
         self.node.update()
 
     def _on_highlight_change(self, event=None):
-        if self.layer.dims.ndisplay == 3:
-            return
-
         if len(self.layer._highlight_index) > 0:
             # Color the hovered or selected points
             data = self.layer._data_view[self.layer._highlight_index]
             if data.ndim == 1:
                 data = np.expand_dims(data, axis=0)
             size = self.layer._size_view[self.layer._highlight_index]
-            face_color = self.layer.face_color[
-                self.layer._indices_view[self.layer._highlight_index]
-            ]
         else:
             data = np.zeros((1, self.layer.dims.ndisplay))
             size = 0
-            face_color = np.array([[1.0, 1.0, 1.0, 1.0]], dtype=np.float32)
 
         self.node._subvisuals[1].set_data(
             data[:, ::-1] + 0.5,
@@ -108,20 +97,26 @@ def _on_highlight_change(self, event=None):
             edge_width=self._highlight_width,
             symbol=self.layer.symbol,
             edge_color=self._highlight_color,
-            face_color=face_color,
+            face_color=transform_color('transparent'),
             scaling=True,
         )
 
-        if (
-            self.layer._highlight_box is None
-            or 0 in self.layer._highlight_box.shape
-        ):
-            pos = np.zeros((1, self.layer.dims.ndisplay))
-            width = 0
-        else:
-            pos = self.layer._highlight_box
-            width = self._highlight_width
+        # only draw a box in 2D
+        if self.layer.dims.ndisplay == 2:
+            if (
+                self.layer._highlight_box is None
+                or 0 in self.layer._highlight_box.shape
+            ):
+                pos = np.zeros((1, self.layer.dims.ndisplay))
+                width = 0
+            else:
+                pos = self.layer._highlight_box
+                width = self._highlight_width
+
+            self.node._subvisuals[2].set_data(
+                pos=pos[:, ::-1] + 0.5,
+                color=self._highlight_color,
+                width=width,
+            )
 
-        self.node._subvisuals[2].set_data(
-            pos=pos[:, ::-1] + 0.5, color=self._highlight_color, width=width
-        )
+        self.node.update()
diff --git a/napari/layers/points/points.py b/napari/layers/points/points.py
--- a/napari/layers/points/points.py
+++ b/napari/layers/points/points.py
@@ -250,11 +250,17 @@ def __init__(
             size=Event,
             edge_width=Event,
             face_color=Event,
+            current_face_color=Event,
             edge_color=Event,
+            current_edge_color=Event,
             symbol=Event,
             n_dimensional=Event,
             highlight=Event,
         )
+        # update highlights when the layer is selected/deselected
+        self.events.select.connect(self._set_highlight)
+        self.events.deselect.connect(self._set_highlight)
+
         self._colors = get_color_namelist()
 
         # Save the point coordinates
@@ -291,7 +297,6 @@ def __init__(
         # Index of hovered point
         self._value = None
         self._value_stored = None
-        self._selected_box = None
         self._mode = Mode.PAN_ZOOM
         self._mode_history = self._mode
         self._status = self.mode
@@ -445,6 +450,7 @@ def data(self, data: np.ndarray):
                 )
 
                 self.size = np.concatenate((self._size, size), axis=0)
+                self.selected_data = list(np.arange(cur_npoints, len(data)))
 
         self._update_dims()
         self.events.data()
@@ -545,12 +551,16 @@ def current_size(self) -> Union[int, float]:
     @current_size.setter
     def current_size(self, size: Union[None, float]) -> None:
         self._current_size = size
-        if self._update_properties and len(self.selected_data) > 0:
+        if (
+            self._update_properties
+            and len(self.selected_data) > 0
+            and self._mode != Mode.ADD
+        ):
             for i in self.selected_data:
                 self.size[i, :] = (self.size[i, :] > 0) * size
             self.refresh()
+            self.events.size()
         self.status = format_float(self.current_size)
-        self.events.size()
 
     @property
     def edge_width(self) -> Union[None, int, float]:
@@ -562,7 +572,6 @@ def edge_width(self, edge_width: Union[None, float]) -> None:
         self._edge_width = edge_width
         self.status = format_float(self.edge_width)
         self.events.edge_width()
-        self.events.highlight()
 
     @property
     def edge_color(self):
@@ -595,7 +604,6 @@ def edge_color(self, edge_color):
             self._edge_color_property = ''
 
             self.events.edge_color()
-            self.events.highlight()
 
     @property
     def edge_color_cycle(self):
@@ -660,12 +668,15 @@ def current_edge_color(self) -> str:
     @current_edge_color.setter
     def current_edge_color(self, edge_color: ColorType) -> None:
         self._current_edge_color = transform_color(edge_color)
-        if self._update_properties and len(self.selected_data) > 0:
+        if (
+            self._update_properties
+            and len(self.selected_data) > 0
+            and self._mode != Mode.ADD
+        ):
             cur_colors: np.ndarray = self.edge_color
             cur_colors[self.selected_data] = self._current_edge_color
             self.edge_color = cur_colors
-        self.events.edge_color()
-        self.events.highlight()
+        self.events.current_edge_color()
 
     @property
     def edge_color_mode(self):
@@ -740,7 +751,6 @@ def face_color(self, face_color):
             self.face_color_mode = ColorMode.DIRECT
 
             self.events.face_color()
-            self.events.highlight()
 
     @property
     def face_color_cycle(self):
@@ -800,12 +810,16 @@ def current_face_color(self) -> str:
     @current_face_color.setter
     def current_face_color(self, face_color: ColorType) -> None:
         self._current_face_color = transform_color(face_color)
-        if self._update_properties and len(self.selected_data) > 0:
+        if (
+            self._update_properties
+            and len(self.selected_data) > 0
+            and self._mode != Mode.ADD
+        ):
             cur_colors: np.ndarray = self.face_color
             cur_colors[self.selected_data] = self._current_face_color
             self.face_color = cur_colors
-        self.events.face_color()
-        self.events.highlight()
+
+        self.events.current_face_color()
 
     @property
     def face_color_mode(self):
@@ -887,7 +901,6 @@ def refresh_colors(self, update_color_mapping: bool = True):
                 self._face_color = face_colors
 
                 self.events.face_color()
-                self.events.highlight()
             elif self._face_color_mode == ColorMode.COLORMAP:
                 face_color_properties = self.properties[
                     self._face_color_property
@@ -945,7 +958,6 @@ def refresh_colors(self, update_color_mapping: bool = True):
                 self._edge_color = edge_colors
             self.events.face_color()
             self.events.edge_color()
-            self.events.highlight()
 
     def _is_color_mapped(self, color):
         """ determines if the new color argument is for directly setting or cycle/colormap"""
@@ -1004,7 +1016,6 @@ def selected_data(self, selected_data):
                 ind = list(self._indices_view).index(c)
                 selected.append(ind)
         self._selected_view = selected
-        self._selected_box = self.interaction_box(self._selected_view)
 
         # Update properties based on selected points
         if len(self._selected_data) == 0:
@@ -1097,6 +1108,8 @@ def mode(self, mode):
             self.cursor = 'pointing'
             self.interactive = False
             self.help = 'hold <space> to pan/zoom'
+            self.selected_data = []
+            self._set_highlight()
         elif mode == Mode.SELECT:
             self.cursor = 'standard'
             self.interactive = False
@@ -1109,11 +1122,11 @@ def mode(self, mode):
             raise ValueError("Mode not recognized")
 
         if not (mode == Mode.SELECT and old_mode == Mode.SELECT):
-            self.selected_data = []
-            self._set_highlight()
+            self._selected_data_stored = []
 
         self.status = str(mode)
         self._mode = mode
+        self._set_highlight()
 
         self.events.mode(mode=mode)
 
@@ -1233,62 +1246,71 @@ def _set_view_slice(self):
                 ind = list(self._indices_view).index(c)
                 selected.append(ind)
         self._selected_view = selected
-        if self.dims.ndisplay == 2:
-            self._selected_box = self.interaction_box(self._selected_view)
-        else:
-            self._selected_box = None
+        self._set_highlight()
 
     def _set_highlight(self, force=False):
         """Render highlights of shapes including boundaries, vertices,
-        interaction boxes, and the drag selection box when appropriate
+        interaction boxes, and the drag selection box when appropriate.
+        Highlighting only occurs in Mode.SELECT.
 
         Parameters
         ----------
         force : bool
             Bool that forces a redraw to occur when `True`
         """
+        # if self._mode == Mode.SELECT:
         # Check if any point ids have changed since last call
-        if (
-            self.selected_data == self._selected_data_stored
-            and self._value == self._value_stored
-            and np.all(self._drag_box == self._drag_box_stored)
-        ) and not force:
-            return
-        self._selected_data_stored = copy(self.selected_data)
-        self._value_stored = copy(self._value)
-        self._drag_box_stored = copy(self._drag_box)
-
-        if self._mode == Mode.SELECT and (
-            self._value is not None or len(self._selected_view) > 0
-        ):
-            if len(self._selected_view) > 0:
-                index = copy(self._selected_view)
-                if self._value is not None:
-                    hover_point = list(self._indices_view).index(self._value)
-                    if hover_point in index:
-                        pass
+        if self.selected:
+            if (
+                self.selected_data == self._selected_data_stored
+                and self._value == self._value_stored
+                and np.all(self._drag_box == self._drag_box_stored)
+            ) and not force:
+                return
+            self._selected_data_stored = copy(self.selected_data)
+            self._value_stored = copy(self._value)
+            self._drag_box_stored = copy(self._drag_box)
+
+            if self._value is not None or len(self._selected_view) > 0:
+                if len(self._selected_view) > 0:
+                    index = copy(self._selected_view)
+                    # highlight the hovered point if not in adding mode
+                    if self._value is not None and self._mode != Mode.ADD:
+                        hover_point = list(self._indices_view).index(
+                            self._value
+                        )
+                        if hover_point in index:
+                            pass
+                        else:
+                            index.append(hover_point)
+                    index.sort()
+                else:
+                    # don't highlight hovered points in add mode
+                    if self._mode != Mode.ADD:
+                        hover_point = list(self._indices_view).index(
+                            self._value
+                        )
+                        index = [hover_point]
                     else:
-                        index.append(hover_point)
-                index.sort()
+                        index = []
+
+                self._highlight_index = index
             else:
-                hover_point = list(self._indices_view).index(self._value)
-                index = [hover_point]
+                self._highlight_index = []
 
-            self._highlight_index = index
-        else:
-            self._highlight_index = []
+            # only display dragging selection box in 2D
+            if self.dims.ndisplay == 2 and self._is_selecting:
+                pos = create_box(self._drag_box)
+                pos = pos[list(range(4)) + [0]]
+            else:
+                pos = None
 
-        pos = self._selected_box
-        if pos is None and not self._is_selecting:
-            pos = np.zeros((0, 2))
-        elif self._is_selecting:
-            pos = create_box(self._drag_box)
-            pos = pos[list(range(4)) + [0]]
+            self._highlight_box = pos
+            self.events.highlight()
         else:
-            pos = pos[list(range(4)) + [0]]
-
-        self._highlight_box = pos
-        self.events.highlight()
+            self._highlight_box = None
+            self._highlight_index = []
+            self.events.highlight()
 
     def _update_thumbnail(self):
         """Update thumbnail with current points and colors."""
@@ -1332,7 +1354,7 @@ def add(self, coord):
         ----------
         coord : sequence of indices to add point at
         """
-        self.data = np.append(self.data, [coord], axis=0)
+        self.data = np.append(self.data, np.atleast_2d(coord), axis=0)
 
     def remove_selected(self):
         """Removes selected points if any."""


Test patch:
diff --git a/napari/layers/points/_tests/test_points.py b/napari/layers/points/_tests/test_points.py
--- a/napari/layers/points/_tests/test_points.py
+++ b/napari/layers/points/_tests/test_points.py
@@ -109,11 +109,31 @@ def test_selecting_points():
     data = 20 * np.random.random(shape)
     layer = Points(data)
     layer.mode = 'select'
-    layer.selected_data = [0, 1]
-    assert layer.selected_data == [0, 1]
+    data_to_select = [1, 2]
+    layer.selected_data = data_to_select
+    assert layer.selected_data == data_to_select
 
     # test switching to 3D
     layer.dims.ndisplay = 3
+    assert layer.selected_data == data_to_select
+
+    # select different points while in 3D mode
+    other_data_to_select = [0]
+    layer.selected_data = other_data_to_select
+    assert layer.selected_data == other_data_to_select
+
+    # selection should persist when going back to 2D mode
+    layer.dims.ndisplay = 2
+    assert layer.selected_data == other_data_to_select
+
+    # selection should persist when switching between between select and pan_zoom
+    layer.mode = 'pan_zoom'
+    assert layer.selected_data == other_data_to_select
+    layer.mode = 'select'
+    assert layer.selected_data == other_data_to_select
+
+    # add mode should clear the selection
+    layer.mode = 'add'
     assert layer.selected_data == []
 
 
@@ -129,6 +149,14 @@ def test_adding_points():
     layer.add(coord)
     assert len(layer.data) == 11
     assert np.all(layer.data[10] == coord)
+    # the added point should be selected
+    assert layer.selected_data == [10]
+
+    # test adding multiple points
+    coords = [[10, 10], [15, 15]]
+    layer.add(coords)
+    assert len(layer.data) == 13
+    assert np.all(layer.data[11:, :] == coords)
 
 
 def test_adding_points_to_empty():
@@ -955,24 +983,6 @@ def test_size_with_3D_arrays():
     assert np.all(layer.size[0] == [0, 16, 16])
 
 
-def test_interaction_box():
-    """Test the creation of the interaction box."""
-    shape = (10, 2)
-    np.random.seed(0)
-    data = 20 * np.random.random(shape)
-    layer = Points(data)
-    assert layer._selected_box is None
-
-    layer.selected_data = [0]
-    assert len(layer._selected_box) == 4
-
-    layer.selected_data = [0, 1]
-    assert len(layer._selected_box) == 4
-
-    layer.selected_data = []
-    assert layer._selected_box is None
-
-
 def test_copy_and_paste():
     """Test copying and pasting selected points."""
     shape = (10, 2)