Repository: biolab/orange3

Issue:
MDS does not show labels
##### Orange version

3.8

##### Expected behavior

Connect File Distances -> MDS, load a files that has labels, e.g. slovenian-towns.dst.

- MDS allows the user to label the points with the labels. 
- Labels are default choice in the combo (not "None")

##### Actual behavior

Labels can't be selected.

##### Steps to reproduce the behavior

See above. Note that this happens only if the data comes from File Distance, not File -> Distance.

##### Additional info (worksheets, data, screenshots, ...)





Discussion:

Resolving patch:
diff --git a/Orange/widgets/unsupervised/owmds.py b/Orange/widgets/unsupervised/owmds.py
--- a/Orange/widgets/unsupervised/owmds.py
+++ b/Orange/widgets/unsupervised/owmds.py
@@ -216,8 +216,10 @@ def __init__(self):
         g = self.graph.gui
         box = g.point_properties_box(self.controlArea)
         self.models = g.points_models
-        self.models[2].order = \
-            self.models[2].order[:1] + ("Stress", ) + self.models[2].order[1:]
+        self.size_model = self.models[2]
+        self.label_model = self.models[3]
+        self.size_model.order = \
+            self.size_model.order[:1] + ("Stress", ) + self.models[2].order[1:]
 
         gui.hSlider(box, self, "connected_pairs", label="Show similar pairs:", minValue=0,
                     maxValue=20, createLabel=False, callback=self._on_connected_changed)
@@ -265,7 +267,7 @@ def update_regression_line(self):
         self.update_graph(reset_view=False)
 
     def init_attr_values(self):
-        domain = self.data and len(self.data) and self.data.domain or None
+        domain = self.data.domain if self.data and len(self.data) else None
         for model in self.models:
             model.set_domain(domain)
         self.graph.attr_color = self.data.domain.class_var if domain else None
@@ -326,10 +328,7 @@ def set_disimilarity(self, matrix):
             self.Error.matrix_too_small.clear()
 
         self.matrix = matrix
-        if matrix is not None and matrix.row_items:
-            self.matrix_data = matrix.row_items
-        if matrix is None:
-            self.matrix_data = None
+        self.matrix_data = matrix.row_items if matrix is not None else None
         self._invalidated = True
 
     @Inputs.data_subset


Test patch:
diff --git a/Orange/widgets/unsupervised/tests/test_owmds.py b/Orange/widgets/unsupervised/tests/test_owmds.py
--- a/Orange/widgets/unsupervised/tests/test_owmds.py
+++ b/Orange/widgets/unsupervised/tests/test_owmds.py
@@ -1,10 +1,15 @@
 # Test methods with long descriptive names can omit docstrings
 # pylint: disable=missing-docstring
+import os
+from itertools import chain
+import unittest
 from unittest.mock import patch, Mock
 
 import numpy as np
 from AnyQt.QtCore import QRectF, QPointF
 
+from Orange.data import Table
+from Orange.misc import DistMatrix
 from Orange.distance import Euclidean
 from Orange.widgets.settings import Context
 from Orange.widgets.unsupervised.owmds import OWMDS
@@ -22,6 +27,10 @@ def setUpClass(cls):
         cls.signal_data = Euclidean(cls.data)
         cls.same_input_output_domain = False
 
+        my_dir = os.path.dirname(__file__)
+        datasets_dir = os.path.join(my_dir, '..', '..', '..', 'datasets')
+        cls.datasets_dir = os.path.realpath(datasets_dir)
+
     def setUp(self):
         self.widget = self.create_widget(
             OWMDS, stored_settings={
@@ -30,6 +39,9 @@ def setUp(self):
                 "initialization": OWMDS.PCA,
             }
         )  # type: OWMDS
+        self.towns = DistMatrix.from_file(
+            os.path.join(self.datasets_dir, "slovenian-towns.dst"))
+
 
     def tearDown(self):
         self.widget.onDeleteWidget()
@@ -174,3 +186,91 @@ def test_migrate_settings_from_version_1(self):
                          (g.jitter_size, 0.5)):
             self.assertTrue(a, value)
         self.assertFalse(w.auto_commit)
+
+    def test_attr_label_from_dist_matrix_from_file(self):
+        w = self.widget
+        # Don't run the MDS optimization to save time and to prevent the
+        # widget be in a blocking state when trying to send the next signal
+        w.start = Mock()
+        row_items = self.towns.row_items
+
+        # Distance matrix with labels
+        self.send_signal(w.Inputs.distances, self.towns)
+        self.assertIn(row_items.domain["label"], w.label_model)
+
+        # Distances matrix without labels
+        self.towns.row_items = None
+        self.send_signal(w.Inputs.distances, self.towns)
+        self.assertEqual(list(w.label_model), [None])
+
+        # No data
+        self.send_signal(w.Inputs.distances, None)
+        self.assertEqual(list(w.label_model), [None])
+
+        # Distances matrix with labels again
+        self.towns.row_items = row_items
+        self.send_signal(w.Inputs.distances, self.towns)
+        self.assertIn(row_items.domain["label"], w.label_model)
+
+        # Followed by no data
+        self.towns.row_items = None
+        self.send_signal(w.Inputs.distances, self.towns)
+        self.assertEqual(list(w.label_model), [None])
+
+    def test_attr_label_from_dist_matrix_from_data(self):
+        w = self.widget
+        # Don't run the MDS optimization to save time and to prevent the
+        # widget be in a blocking state when trying to send the next signal
+        w.start = Mock()
+
+        data = Table("zoo")
+        dist = Euclidean(data)
+        self.send_signal(w.Inputs.distances, dist)
+        self.send_signal(w.Inputs.data, data)
+        self.assertTrue(set(chain(data.domain.variables, data.domain.metas))
+                        < set(w.label_model))
+
+    def test_attr_label_from_data(self):
+        w = self.widget
+        # Don't run the MDS optimization to save time and to prevent the
+        # widget be in a blocking state when trying to send the next signal
+        w.start = Mock()
+
+        data = Table("zoo")
+        dist = Euclidean(data)
+        self.send_signal(w.Inputs.distances, dist)
+        self.assertTrue(set(chain(data.domain.variables, data.domain.metas))
+                        < set(w.label_model))
+
+    def test_attr_label_matrix_and_data(self):
+        w = self.widget
+        # Don't run the MDS optimization to save time and to prevent the
+        # widget be in a blocking state when trying to send the next signal
+        w.start = Mock()
+
+        # Data and matrix
+        data = Table("zoo")
+        dist = Euclidean(data)
+        self.send_signal(w.Inputs.distances, dist)
+        self.send_signal(w.Inputs.data, data)
+        self.assertTrue(set(chain(data.domain.variables, data.domain.metas))
+                        < set(w.label_model))
+
+        # Has data, but receives a signal without data: has to keep the label
+        dist.row_items = None
+        self.send_signal(w.Inputs.distances, dist)
+        self.assertTrue(set(chain(data.domain.variables, data.domain.metas))
+                        < set(w.label_model))
+
+        # Has matrix without data, and loses the data: remove the label
+        self.send_signal(w.Inputs.data, None)
+        self.assertEqual(list(w.label_model), [None])
+
+        # Has matrix without data, receives data: add attrs to combo, select
+        self.send_signal(w.Inputs.data, data)
+        self.assertTrue(set(chain(data.domain.variables, data.domain.metas))
+                        < set(w.label_model))
+
+
+if __name__ == "__main__":
+    unittest.main()