diff --git a/Orange/widgets/visualize/owradviz.py b/Orange/widgets/visualize/owradviz.py index 2cbd6b141d5..4b6dae2c0b0 100644 --- a/Orange/widgets/visualize/owradviz.py +++ b/Orange/widgets/visualize/owradviz.py @@ -275,16 +275,10 @@ class OWRadviz(OWAnchorProjectionWidget): graph = SettingProvider(OWRadvizGraph) class Warning(OWAnchorProjectionWidget.Warning): - no_features = widget.Msg("Radviz requires at least two features.") invalid_embedding = widget.Msg("No projection for selected features") removed_vars = widget.Msg("Categorical variables with more than" " two values are not shown.") - class Error(OWAnchorProjectionWidget.Error): - no_features = widget.Msg( - "At least three numeric or categorical variables are required" - ) - def __init__(self): self.model_selected = VariableListModel(enable_dnd=True) self.model_selected.removed.connect(self.__model_selected_changed) @@ -331,12 +325,6 @@ def __vizrank_set_attrs(self, attrs): def __model_selected_changed(self): self.selected_vars = [(var.name, vartype(var)) for var in self.model_selected] - - self.Warning.no_features.clear() - if len(self.model_selected) < 2: - self.Warning.no_features() - return - self.init_projection() self.setup_plot() self.commit() @@ -347,6 +335,12 @@ def colors_changed(self): def set_data(self, data): super().set_data(data) + self._init_vizrank() + self.init_projection() + + def use_context(self): + self.model_selected.clear() + self.model_other.clear() if self.data is not None and len(self.selected_vars): d, selected = self.data.domain, [v[0] for v in self.selected_vars] self.model_selected[:] = [d[name] for name in selected] @@ -360,9 +354,6 @@ def set_data(self, data): self.model_selected[:] = variables[:5] self.model_other[:] = variables[5:] + class_var - self._init_vizrank() - self.init_projection() - def _init_vizrank(self): is_enabled = self.data is not None and \ len(self.primitive_variables) > 3 and \ @@ -376,20 +367,13 @@ def _init_vizrank(self): self.vizrank.initialize() def check_data(self): - def error(err): - err() - self.data = None - super().check_data() if self.data is not None: - if len(self.primitive_variables) < 3: - error(self.Error.no_features) - else: - domain = self.data.domain - vars_ = chain(domain.variables, domain.metas) - n_vars = sum(v.is_primitive() for v in vars_) - if len(self.primitive_variables) < n_vars: - self.Warning.removed_vars() + domain = self.data.domain + vars_ = chain(domain.variables, domain.metas) + n_vars = sum(v.is_primitive() for v in vars_) + if len(self.primitive_variables) < n_vars: + self.Warning.removed_vars() def init_attr_values(self): super().init_attr_values() @@ -408,10 +392,6 @@ def _send_components_metas(self): return np.vstack((super()._send_components_metas(), ["angle"])) def clear(self): - if self.model_selected: - self.model_selected.clear() - if self.model_other: - self.model_other.clear() super().clear() self.projector = RadViz() diff --git a/Orange/widgets/visualize/tests/test_owradviz.py b/Orange/widgets/visualize/tests/test_owradviz.py index 84ba6fbe996..5be9fe90965 100644 --- a/Orange/widgets/visualize/tests/test_owradviz.py +++ b/Orange/widgets/visualize/tests/test_owradviz.py @@ -1,8 +1,9 @@ # Test methods with long descriptive names can omit docstrings # pylint: disable=missing-docstring +from unittest.mock import Mock import numpy as np -from Orange.data import Table, Domain +from Orange.data import Table from Orange.widgets.tests.base import ( WidgetTest, WidgetOutputsTestMixin, AnchorProjectionWidgetTestMixin, datasets @@ -43,14 +44,7 @@ def check_vizrank(data): check_vizrank(ds) def test_no_features(self): - w = self.widget - data2 = self.data.transform(Domain(self.data.domain.attributes[:1], - self.data.domain.class_vars)) - self.assertFalse(w.Error.no_features.is_shown()) - self.send_signal(w.Inputs.data, data2) - self.assertTrue(w.Error.no_features.is_shown()) - self.send_signal(w.Inputs.data, None) - self.assertFalse(w.Error.no_features.is_shown()) + self.send_signal(self.widget.Inputs.data, self.data[:, :0]) def test_not_enough_instances(self): w = self.widget @@ -101,3 +95,38 @@ def test_discrete_attributes(self): self.assertTrue(self.widget.Warning.removed_vars.is_shown()) self.send_signal(self.widget.Inputs.data, None) self.assertFalse(self.widget.Warning.removed_vars.is_shown()) + + def test_saved_selected_vars(self): + self.send_signal(self.widget.Inputs.data, self.data) + + self.widget.model_selected[:] = self.data.domain[:1] + self.widget.variables_selection.removed.emit() + self.send_signal(self.widget.Inputs.data, self.data) + self.assertEqual(len(self.widget.model_selected[:]), 1) + + self.widget.model_selected[:] = self.data.domain[:0] + self.widget.variables_selection.removed.emit() + self.send_signal(self.widget.Inputs.data, self.data) + self.assertEqual(len(self.widget.model_selected[:]), 4) + + def test_invalidated_model_selected(self): + self.widget.setup_plot = Mock() + self.send_signal(self.widget.Inputs.data, self.data) + self.widget.setup_plot.assert_called_once() + + self.widget.setup_plot.reset_mock() + self.widget.model_selected[:] = self.data.domain[2:] + self.widget.variables_selection.removed.emit() + self.widget.setup_plot.assert_called_once() + + self.widget.setup_plot.reset_mock() + self.send_signal(self.widget.Inputs.data, self.data[:, 2:]) + self.widget.setup_plot.assert_not_called() + + self.widget.model_selected[:] = self.data.domain[3:] + self.widget.variables_selection.removed.emit() + self.widget.setup_plot.assert_called_once() + + self.widget.setup_plot.reset_mock() + self.send_signal(self.widget.Inputs.data, self.data) + self.widget.setup_plot.assert_called_once() diff --git a/Orange/widgets/visualize/utils/widget.py b/Orange/widgets/visualize/utils/widget.py index dd3894cf15c..21b8fe12eba 100644 --- a/Orange/widgets/visualize/utils/widget.py +++ b/Orange/widgets/visualize/utils/widget.py @@ -423,6 +423,7 @@ def set_data(self, data): if not same_domain: self.init_attr_values() self.openContext(self.data) + self.use_context() self.__invalidated = not ( data_existed and self.data is not None and effective_data.X.shape == self.effective_data.X.shape and @@ -436,6 +437,9 @@ def check_data(self): self.valid_data = None self.clear_messages() + def use_context(self): + pass + @Inputs.data_subset @check_sql_input def set_subset_data(self, subset):