Repository: biolab/orange3

Issue:
Scatter Plot: Don't plot densities of unknown values
If the variable that the user chooses for coloring has many (or even mostly) undefined values, the density plot is very (or even mostly) gray. This is probably uninformative and undesired.

**Describe the solution you'd like**

Exclude points with missing values from the computation of density plot.

![scatterplot](https://user-images.githubusercontent.com/2387315/76139689-fb6c8780-6052-11ea-88d7-cfa463053e67.png)



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
@@ -1101,16 +1101,22 @@ def update_density(self):
             self.plot_widget.removeItem(self.density_img)
             self.density_img = None
         if self.class_density and self.scatterplot_item is not None:
+            c_data = self.master.get_color_data()
+            if c_data is None:
+                return
+            mask = np.isfinite(self._filter_visible(c_data))
+            pens = self.scatterplot_item.data['pen']
             rgb_data = [
                 pen.color().getRgb()[:3] if pen is not None else (255, 255, 255)
-                for pen in self.scatterplot_item.data['pen']]
+                for known, pen in zip(mask, pens)
+                if known]
             if len(set(rgb_data)) <= 1:
                 return
             [min_x, max_x], [min_y, max_y] = self.view_box.viewRange()
             x_data, y_data = self.scatterplot_item.getData()
             self.density_img = classdensity.class_density_image(
                 min_x, max_x, min_y, max_y, self.resolution,
-                x_data, y_data, rgb_data)
+                x_data[mask], y_data[mask], rgb_data)
             self.plot_widget.addItem(self.density_img)
 
     def update_selection_colors(self):


Test patch:
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
@@ -740,6 +740,41 @@ def test_density(self):
             self.assertIsNone(graph.density_img)
             self.assertIs(graph.plot_widget.removeItem.call_args[0][0], density)
 
+    @patch("Orange.widgets.utils.classdensity.class_density_image")
+    def test_density_with_missing(self, class_density_image):
+        graph = self.graph
+        graph.reset_graph()
+        graph.plot_widget.addItem = Mock()
+        graph.plot_widget.removeItem = Mock()
+
+        graph.class_density = True
+        d = np.arange(10, dtype=float) % 2
+        self.master.get_color_data = lambda: d
+
+        # All colors known
+        graph.update_colors()
+        x_data0, y_data0, colors0 = class_density_image.call_args[0][5:]
+
+        # Some missing colors
+        d[:3] = np.nan
+        graph.update_colors()
+        x_data, y_data, colors = class_density_image.call_args[0][5:]
+        np.testing.assert_equal(x_data, x_data0[3:])
+        np.testing.assert_equal(y_data, y_data0[3:])
+        np.testing.assert_equal(colors, colors0[3:])
+
+        # Missing colors + only subsample plotted
+        graph.set_sample_size(8)
+        graph.reset_graph()
+        d_known = np.isfinite(graph._filter_visible(d))
+        x_data0 = graph._filter_visible(x_data0)[d_known]
+        y_data0 = graph._filter_visible(y_data0)[d_known]
+        colors0 = graph._filter_visible(np.array(colors0))[d_known]
+        x_data, y_data, colors = class_density_image.call_args[0][5:]
+        np.testing.assert_equal(x_data, x_data0)
+        np.testing.assert_equal(y_data, y_data0)
+        np.testing.assert_equal(colors, colors0)
+
     def test_labels(self):
         graph = self.graph
         graph.reset_graph()