Skip to content

Commit

Permalink
Merge pull request #3444 from VesnaT/fix_radviz
Browse files Browse the repository at this point in the history
[FIX] Radviz: Enable projection for less than two selected variables
  • Loading branch information
janezd authored Dec 8, 2018
2 parents 6481150 + d2e49ff commit e6b6c39
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 40 deletions.
42 changes: 11 additions & 31 deletions Orange/widgets/visualize/owradviz.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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()
Expand All @@ -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]
Expand All @@ -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 \
Expand All @@ -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()
Expand All @@ -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()

Expand Down
47 changes: 38 additions & 9 deletions Orange/widgets/visualize/tests/test_owradviz.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
4 changes: 4 additions & 0 deletions Orange/widgets/visualize/utils/widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down

0 comments on commit e6b6c39

Please sign in to comment.