Repository: biolab/orange3

Issue:
Geo Map disables Opacity when data subset signal present
**What's wrong?**

Feeding a data subset into Geo Map disables Opacity. Which is wrong, since opacity allows to see beyond the data points and perhaps read some of the labels on the map, which are now, with disabled opacity setting not readable.

![image](https://user-images.githubusercontent.com/726604/101166830-37ac3300-3639-11eb-963b-5086eb83559f.png)

In the old version of the widget a similar visualization looked like this:

![old-one](https://user-images.githubusercontent.com/726604/101167333-097b2300-363a-11eb-8f4c-6d0dd12460d5.png)

Notice also that the opacity in the old version of the widget was such that it was possible to read the map labels.

**How can we reproduce the problem?**

Here is the workflow. Use the Philadelphia Crime data set.

![image](https://user-images.githubusercontent.com/726604/101167561-5b23ad80-363a-11eb-978d-4cf1051f7182.png)

[philly.ows.zip](https://github.com/biolab/orange3/files/5642912/philly.ows.zip)

**What's your environment?**

- Operating system: macOS Catalina
- Orange version: 3.27
- How you installed Orange: dmg



Discussion:
This behaviour is inherited from the base widget for projections, e.g. scatter plot behaves the same. This makes the subset easier to identify.

Have you reported this in orange3 repository instead of in https://github.com/biolab/orange3-geo because you would like to change this general behaviour, or by accident and you're only suggesting a change in this widget?
Either we increase the opacity if the subset is present OR we display a warning if opacity is too low in presenece of subset.

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
@@ -531,7 +531,7 @@ def get_size_data(self):
 
     COLOR_NOT_SUBSET = (128, 128, 128, 0)
     COLOR_SUBSET = (128, 128, 128, 255)
-    COLOR_DEFAULT = (128, 128, 128, 0)
+    COLOR_DEFAULT = (128, 128, 128, 255)
 
     MAX_VISIBLE_LABELS = 500
 
@@ -1048,15 +1048,19 @@ def _get_same_colors(self, subset):
         """
         color = self.plot_widget.palette().color(OWPalette.Data)
         pen = [_make_pen(color, 1.5)] * self.n_shown  # use a single QPen instance
+
+        # Prepare all brushes; we use the first two or the last
+        brushes = []
+        for c in (self.COLOR_SUBSET, self.COLOR_NOT_SUBSET, self.COLOR_DEFAULT):
+            color = QColor(*c)
+            if color.alpha():
+                color.setAlpha(self.alpha_value)
+            brushes.append(QBrush(color))
+
         if subset is not None:
-            brush = np.where(
-                subset,
-                *(QBrush(QColor(*col))
-                  for col in (self.COLOR_SUBSET, self.COLOR_NOT_SUBSET)))
+            brush = np.where(subset, *brushes[:2])
         else:
-            color = QColor(*self.COLOR_DEFAULT)
-            color.setAlpha(self.alpha_value)
-            brush = [QBrush(color)] * self.n_shown  # use a single QBrush instance
+            brush = brushes[-1:] * self.n_shown  # use a single QBrush instance
         return pen, brush
 
     def _get_continuous_colors(self, c_data, subset):
@@ -1102,7 +1106,7 @@ def create_brush(col):
 
         if subset is not None:
             brush[:, 3] = 0
-            brush[subset, 3] = 255
+            brush[subset, 3] = self.alpha_value
 
         cached_brushes = {}
         brush = np.array([reuse(cached_brushes, create_brush, *col)
@@ -1133,7 +1137,7 @@ def _get_discrete_colors(self, c_data, subset):
         pens = np.array(
             [_make_pen(col.darker(self.DarkerValue), 1.5) for col in colors])
         pen = pens[c_data]
-        if subset is None and self.alpha_value < 255:
+        if self.alpha_value < 255:
             for col in colors:
                 col.setAlpha(self.alpha_value)
         brushes = np.array([QBrush(col) for col in colors])
@@ -1504,7 +1508,7 @@ def _update_colored_legend(self, legend, labels, symbols):
         for color, label, symbol in zip(colors, labels, symbols):
             color = QColor(*color)
             pen = _make_pen(color.darker(self.DarkerValue), 1.5)
-            color.setAlpha(255 if self.subset_is_shown else self.alpha_value)
+            color.setAlpha(self.alpha_value)
             brush = QBrush(color)
             legend.addItem(
                 SymbolItemSample(pen=pen, brush=brush, size=10, symbol=symbol),
diff --git a/Orange/widgets/visualize/utils/widget.py b/Orange/widgets/visualize/utils/widget.py
--- a/Orange/widgets/visualize/utils/widget.py
+++ b/Orange/widgets/visualize/utils/widget.py
@@ -250,6 +250,7 @@ def can_draw_density(self):
 
     def colors_changed(self):
         self.graph.update_colors()
+        self._update_opacity_warning()
         self.cb_class_density.setEnabled(self.can_draw_density())
 
     # Labels
@@ -378,6 +379,8 @@ class Warning(OWProjectionWidgetBase.Warning):
             "input data")
         subset_independent = Msg(
             "No subset data instances appear in input data")
+        transparent_subset = Msg(
+            "Increase opacity if subset is difficult to see")
 
     settingsHandler = DomainContextHandler()
     selection = Setting(None, schema_only=True)
@@ -473,7 +476,6 @@ def enable_controls(self):
     @check_sql_input
     def set_subset_data(self, subset):
         self.subset_data = subset
-        self.controls.graph.alpha_value.setEnabled(subset is None)
 
     def handleNewSignals(self):
         self._handle_subset_data()
@@ -482,6 +484,7 @@ def handleNewSignals(self):
             self.setup_plot()
         else:
             self.graph.update_point_props()
+        self._update_opacity_warning()
         self.unconditional_commit()
 
     def _handle_subset_data(self):
@@ -497,6 +500,10 @@ def _handle_subset_data(self):
             elif self.subset_indices - ids:
                 self.Warning.subset_not_subset()
 
+    def _update_opacity_warning(self):
+        self.Warning.transparent_subset(
+            shown=self.subset_indices and self.graph.alpha_value < 128)
+
     def set_input_summary(self, data):
         summary = len(data) if data else self.info.NoInput
         detail = format_summary_details(data) if data else ""


Test patch:
diff --git a/Orange/widgets/visualize/tests/test_owscatterplot.py b/Orange/widgets/visualize/tests/test_owscatterplot.py
--- a/Orange/widgets/visualize/tests/test_owscatterplot.py
+++ b/Orange/widgets/visualize/tests/test_owscatterplot.py
@@ -567,6 +567,21 @@ def test_subset_data(self):
         self.send_signal(w.Inputs.data_subset, data[::30])
         self.assertEqual(len(w.subset_indices), 5)
 
+    def test_opacity_warning(self):
+        data = Table("iris")
+        w = self.widget
+        self.send_signal(w.Inputs.data, data)
+        w.graph.controls.alpha_value.setSliderPosition(10)
+        self.assertFalse(w.Warning.transparent_subset.is_shown())
+        self.send_signal(w.Inputs.data_subset, data[::30])
+        self.assertTrue(w.Warning.transparent_subset.is_shown())
+        w.graph.controls.alpha_value.setSliderPosition(200)
+        self.assertFalse(w.Warning.transparent_subset.is_shown())
+        w.graph.controls.alpha_value.setSliderPosition(10)
+        self.assertTrue(w.Warning.transparent_subset.is_shown())
+        self.send_signal(w.Inputs.data_subset, None)
+        self.assertFalse(w.Warning.transparent_subset.is_shown())
+
     def test_metas_zero_column(self):
         """
         Prevent crash when metas column is zero.
diff --git a/Orange/widgets/visualize/tests/test_owscatterplotbase.py b/Orange/widgets/visualize/tests/test_owscatterplotbase.py
--- a/Orange/widgets/visualize/tests/test_owscatterplotbase.py
+++ b/Orange/widgets/visualize/tests/test_owscatterplotbase.py
@@ -614,9 +614,9 @@ def run_tests():
             self.assertEqual(brushes[0].color().alpha(), 0)
             self.assertEqual(brushes[1].color().alpha(), 0)
             self.assertEqual(brushes[4].color().alpha(), 0)
-            self.assertEqual(brushes[5].color().alpha(), 255)
-            self.assertEqual(brushes[6].color().alpha(), 255)
-            self.assertEqual(brushes[7].color().alpha(), 255)
+            self.assertEqual(brushes[5].color().alpha(), 123)
+            self.assertEqual(brushes[6].color().alpha(), 123)
+            self.assertEqual(brushes[7].color().alpha(), 123)
 
         graph = self.graph