Repository: biolab/orange3

Issue:
Scatter Plot: selection is not working with very high feature values
##### Orange version
3.8.0.dev0+301d13a

##### Expected behavior
If a feature value is much higher than the rest of the data, scatter plot shall perform normally.

##### Actual behavior
If a feature value is much higher than the rest of the data, scatter plot does not allow point selection. Mouse rectangle selection is also not working ok (e.g. in my two instances dataset if I select the instance with the extra-high value with rectangle, the second instance is selected :) ).

##### Steps to reproduce the behaviour

Load attached data, go with them to Scatter Plot, try to select data



##### Additional info (worksheets, data, screenshots, ...)
![screen shot 2017-11-21 at 17 19 06](https://user-images.githubusercontent.com/11367976/33083610-2077b8bc-cee0-11e7-8b0e-9221e6a0bf8e.png)



[data.xlsx](https://github.com/biolab/orange3/files/1492119/data.xlsx)




Discussion:

Resolving patch:
diff --git a/Orange/widgets/visualize/owscatterplotgraph.py b/Orange/widgets/visualize/owscatterplotgraph.py
--- a/Orange/widgets/visualize/owscatterplotgraph.py
+++ b/Orange/widgets/visualize/owscatterplotgraph.py
@@ -1233,10 +1233,10 @@ def select_by_click(self, _, points):
         if self.scatterplot_item is not None:
             self.select(points)
 
-    def select_by_rectangle(self, value_rect):
+    def select_by_rectangle(self, rect):
         if self.scatterplot_item is not None:
-            x0, y0 = value_rect.topLeft().x(), value_rect.topLeft().y()
-            x1, y1 = value_rect.bottomRight().x(), value_rect.bottomRight().y()
+            x0, x1 = sorted((rect.topLeft().x(), rect.bottomRight().x()))
+            y0, y1 = sorted((rect.topLeft().y(), rect.bottomRight().y()))
             x, y = self.master.get_coordinates_data()
             indices = np.flatnonzero(
                 (x0 <= x) & (x <= x1) & (y0 <= y) & (y <= y1))
diff --git a/Orange/widgets/visualize/utils/plotutils.py b/Orange/widgets/visualize/utils/plotutils.py
--- a/Orange/widgets/visualize/utils/plotutils.py
+++ b/Orange/widgets/visualize/utils/plotutils.py
@@ -148,6 +148,24 @@ def __init__(self, graph, enable_menu=False):
     def _dragtip_pos(self):
         return 10, 10
 
+    def updateScaleBox(self, p1, p2):
+        """
+        Overload to use ViewBox.mapToView instead of mapRectFromParent
+        mapRectFromParent (from Qt) uses QTransform.invert() which has
+        floating-point issues and can't invert the matrix with large
+        coefficients. ViewBox.mapToView uses invertQTransform from pyqtgraph.
+
+        This code, except for first three lines, are copied from the overloaded
+        method.
+        """
+        p1 = self.mapToView(p1)
+        p2 = self.mapToView(p2)
+        r = QRectF(p1, p2)
+        self.rbScaleBox.setPos(r.topLeft())
+        self.rbScaleBox.resetTransform()
+        self.rbScaleBox.scale(r.width(), r.height())
+        self.rbScaleBox.show()
+
     def safe_update_scale_box(self, buttonDownPos, currentPos):
         x, y = currentPos
         if buttonDownPos[0] == x:
@@ -158,9 +176,14 @@ def safe_update_scale_box(self, buttonDownPos, currentPos):
 
     # noinspection PyPep8Naming,PyMethodOverriding
     def mouseDragEvent(self, ev, axis=None):
-        if self.graph.state == SELECT and axis is None:
+        def get_mapped_rect():
+            p1, p2 = ev.buttonDownPos(ev.button()), ev.pos()
+            p1 = self.mapToView(p1)
+            p2 = self.mapToView(p2)
+            return QRectF(p1, p2)
+
+        def select():
             ev.accept()
-            pos = ev.pos()
             if ev.button() == Qt.LeftButton:
                 self.safe_update_scale_box(ev.buttonDownPos(), ev.pos())
                 scene = self.scene()
@@ -168,16 +191,36 @@ def mouseDragEvent(self, ev, axis=None):
                 if ev.isFinish():
                     dragtip.hide()
                     self.rbScaleBox.hide()
-                    pixel_rect = QRectF(ev.buttonDownPos(ev.button()), pos)
-                    value_rect = self.childGroup.mapRectFromParent(pixel_rect)
+                    value_rect = get_mapped_rect()
                     self.graph.select_by_rectangle(value_rect)
                 else:
                     dragtip.setPos(*self._dragtip_pos())
                     dragtip.show()  # although possibly already shown
                     self.safe_update_scale_box(ev.buttonDownPos(), ev.pos())
+
+        def zoom():
+            # A fixed version of the code from the inherited mouseDragEvent
+            # Use mapToView instead of mapRectFromParent
+            ev.accept()
+            self.rbScaleBox.hide()
+            ax = get_mapped_rect()
+            self.showAxRect(ax)
+            self.axHistoryPointer += 1
+            self.axHistory = self.axHistory[:self.axHistoryPointer] + [ax]
+
+        if self.graph.state == SELECT and axis is None:
+            select()
         elif self.graph.state == ZOOMING or self.graph.state == PANNING:
-            ev.ignore()
-            super().mouseDragEvent(ev, axis=axis)
+            # Inherited mouseDragEvent doesn't work for large zooms because it
+            # uses mapRectFromParent. We don't want to copy the parts of the
+            # method that work, hence we only use our code under the following
+            # conditions.
+            if ev.button() & (Qt.LeftButton | Qt.MidButton) \
+                    and self.state['mouseMode'] == pg.ViewBox.RectMode \
+                    and ev.isFinish():
+                zoom()
+            else:
+                super().mouseDragEvent(ev, axis=axis)
         else:
             ev.ignore()
 


Test patch: