Repository: enthought/chaco

Issue:
RangeSelection improperly clips on mouse leave
The `RangeSelection` tool clips the selection when the mouse leaves without considering the origin of the plot.  This leads to the `selection` trait having low value greater than the high value. Subsequently, other parts of chaco crash because of this.

A fix is coming.



Discussion:

Resolving patch:
diff --git a/chaco/tools/range_selection.py b/chaco/tools/range_selection.py
--- a/chaco/tools/range_selection.py
+++ b/chaco/tools/range_selection.py
@@ -510,9 +510,16 @@ def selecting_mouse_leave(self, event):
 
         pos = self._get_axis_coord(event)
         if pos >= high:
-            selection_high = self.mapper.map_data(high)
+            # clip to the boundary appropriate for the mapper's orientation.
+            if self.mapper.sign == 1:
+                selection_high = self.mapper.map_data(high)
+            else:
+                selection_high = self.mapper.map_data(low)
         elif pos <= low:
-            selection_low = self.mapper.map_data(low)
+            if self.mapper.sign == 1:
+                selection_low = self.mapper.map_data(low)
+            else:
+                selection_low = self.mapper.map_data(high)
 
         self.selection = (selection_low, selection_high)
         event.window.set_pointer("arrow")


Test patch:
diff --git a/chaco/tools/tests/range_selection_test_case.py b/chaco/tools/tests/range_selection_test_case.py
new file mode 100644
--- /dev/null
+++ b/chaco/tools/tests/range_selection_test_case.py
@@ -0,0 +1,57 @@
+import unittest
+
+import numpy as np
+
+from chaco.array_plot_data import ArrayPlotData
+from chaco.plot import Plot
+from chaco.tools.range_selection import RangeSelection
+from enable.testing import EnableTestAssistant
+
+
+class RangeSelectionTestCase(EnableTestAssistant, unittest.TestCase):
+
+    def test_selecting_mouse_leave_clipping(self):
+        # Regression test for #216.
+        plot_data = ArrayPlotData()
+        arr = np.arange(4.0)
+        plot_data.set_data("x", arr)
+        plot_data.set_data("y", arr)
+
+        for origin in ('bottom left', 'top left', 'bottom right', 'top right'):
+            for orientation in ('h', 'v'):
+                for axis in ('index', 'value'):
+                    plot = Plot(
+                        plot_data, orientation=orientation, origin='top right'
+                    )
+
+                    renderer = plot.plot(('x', 'y'))[0]
+                    renderer.bounds = [10, 20]
+                    tool = RangeSelection(
+                        renderer, left_button_selects=True, axis=axis,
+                    )
+                    renderer.tools.append(tool)
+
+                    low_x, low_y = plot.position
+                    high_x = low_x + renderer.bounds[0] - 1
+                    high_y = low_y + renderer.bounds[1] - 1
+
+                    cx = 5
+                    cy = 5
+
+                    bounds = (
+                        (low_x - 1, low_y),
+                        (high_x + 1, low_y),
+                        (low_x, low_y - 1),
+                        (low_x, high_y + 1),
+                    )
+                    for x, y in bounds:
+                        self.mouse_down(tool, x=cx, y=cy)
+                        self.mouse_leave(tool, x=x, y=y)
+                        selection = tool.selection
+                        self.assertTrue(selection[0] <= selection[1])
+                        self.mouse_up(tool, x=x, y=y)
+
+
+if __name__ == '__main__':
+    import nose
+    nose.run()