diff --git a/Orange/widgets/data/owcolor.py b/Orange/widgets/data/owcolor.py
index 3028cca3ce2..a385a238b4d 100644
--- a/Orange/widgets/data/owcolor.py
+++ b/Orange/widgets/data/owcolor.py
@@ -1,6 +1,7 @@
import os
from itertools import chain
import json
+from typing import List
import numpy as np
@@ -10,7 +11,7 @@
from AnyQt.QtWidgets import QHeaderView, QColorDialog, QTableView, QComboBox, \
QFileDialog, QMessageBox
-from orangewidget.settings import IncompatibleContext
+from orangewidget.settings import IncompatibleContext, TypeSupport
import Orange
from Orange.preprocess.transformation import Identity
@@ -232,6 +233,48 @@ def from_dict(cls, var, data):
return obj, warnings
+class AttrDescTypeSupport(TypeSupport):
+ @classmethod
+ def pack_value(cls, value, _=None):
+ packed = {k: list(tuple(x) for x in v) if k == "new_colors" else v
+ for k, v in value.__dict__.items()
+ if k.startswith("new_") and v is not None}
+ packed["var"] = value.var.name
+ return packed
+
+ @classmethod
+ def unpack_value(cls, value, _, domain, *_a):
+ var = domain[value["var"]]
+ desc = cls.supported_types[0](var)
+ if "new_name" in value:
+ desc.name = value["new_name"]
+ return desc
+
+
+class DiscAttrDescTypeSupport(AttrDescTypeSupport):
+ supported_types = (DiscAttrDesc, )
+
+ @classmethod
+ def unpack_value(cls, value, tp, domain, *_):
+ desc = super().unpack_value(value, tp, domain)
+ for i, color in enumerate(value.get("new_colors", ())):
+ desc.set_color(i, color)
+ for i, color in enumerate(value.get("new_values", ())):
+ desc.set_value(i, color)
+ return desc
+
+
+class ContAttrDescTypeSupport(AttrDescTypeSupport):
+ supported_types = (ContAttrDesc, )
+
+ @classmethod
+ def unpack_value(cls, value, tp, domain, *_):
+ desc = super().unpack_value(value, tp, domain)
+ if "new_palette_name" in value:
+ desc.palette_name = value["new_palette_name"]
+ return desc
+
+
class ColorTableModel(QAbstractTableModel):
"""
Base color model for discrete and continuous variables. The model handles:
@@ -547,12 +590,12 @@ class Outputs:
settingsHandler = settings.PerfectDomainContextHandler(
match_values=settings.PerfectDomainContextHandler.MATCH_VALUES_ALL)
- disc_descs = settings.ContextSetting([])
- cont_descs = settings.ContextSetting([])
+ disc_descs: List[DiscAttrDesc] = settings.ContextSetting([])
+ cont_descs: List[ContAttrDesc] = settings.ContextSetting([])
selected_schema_index = settings.Setting(0)
auto_apply = settings.Setting(True)
- settings_version = 2
+ settings_version = 3
want_main_area = False
@@ -801,9 +844,17 @@ def was(n, o):
self.report_raw(f"
")
@classmethod
- def migrate_context(cls, _, version):
+ def migrate_context(cls, context, version):
if not version or version < 2:
raise IncompatibleContext
+ if version < 3:
+ values = context.values
+ values["disc_descs"] = [
+ DiscAttrDescTypeSupport.pack_value(desc)
+ for desc in values["disc_descs"]]
+ values["cont_descs"] = [
+ ContAttrDescTypeSupport.pack_value(desc)
+ for desc in values["cont_descs"]]
if __name__ == "__main__": # pragma: no cover
diff --git a/Orange/widgets/data/owcorrelations.py b/Orange/widgets/data/owcorrelations.py
index 427d6545e83..9d9a4cde897 100644
--- a/Orange/widgets/data/owcorrelations.py
+++ b/Orange/widgets/data/owcorrelations.py
@@ -5,6 +5,7 @@
from operator import attrgetter
from types import SimpleNamespace
from itertools import combinations, groupby, chain
+from typing import List
import numpy as np
from scipy.stats import spearmanr, pearsonr
@@ -254,8 +255,8 @@ class Outputs:
settings_version = 3
settingsHandler = DomainContextHandler()
- selection = ContextSetting([])
- feature = ContextSetting(None)
+ selection: List[ContinuousVariable] = ContextSetting([])
+ feature: ContinuousVariable = ContextSetting(None)
correlation_type = Setting(0)
class Information(OWWidget.Information):
diff --git a/Orange/widgets/data/owcreateclass.py b/Orange/widgets/data/owcreateclass.py
index 5d704e0739a..52d976b5e77 100644
--- a/Orange/widgets/data/owcreateclass.py
+++ b/Orange/widgets/data/owcreateclass.py
@@ -1,13 +1,14 @@
"""Widget for creating classes from non-numeric attribute by substrings"""
import re
from itertools import count
+from typing import List, Dict
import numpy as np
from AnyQt.QtWidgets import QGridLayout, QLabel, QLineEdit, QSizePolicy, QWidget
from AnyQt.QtCore import QSize, Qt
-from Orange.data import StringVariable, DiscreteVariable, Domain
+from Orange.data import StringVariable, DiscreteVariable, Domain, Variable
from Orange.data.table import Table
from Orange.statistics.util import bincount
from Orange.preprocess.transformation import Transformation, Lookup
@@ -176,9 +177,9 @@ class Outputs:
buttons_area_orientation = Qt.Vertical
settingsHandler = DomainContextHandler()
- attribute = ContextSetting(None)
+ attribute: Variable = ContextSetting(None)
class_name = ContextSetting("class")
- rules = ContextSetting({})
+ rules: Dict[str, List[List[str]]] = ContextSetting({})
match_beginning = ContextSetting(False)
case_sensitive = ContextSetting(False)
diff --git a/Orange/widgets/data/owdatasets.py b/Orange/widgets/data/owdatasets.py
index caf3cafb5cd..aac0200851c 100644
--- a/Orange/widgets/data/owdatasets.py
+++ b/Orange/widgets/data/owdatasets.py
@@ -180,7 +180,7 @@ class Outputs:
data = Output("Data", Orange.data.Table)
#: Selected dataset id
- selected_id = settings.Setting(None) # type: Optional[str]
+ selected_id: Optional[str] = settings.Setting(None)
#: main area splitter state
splitter_state = settings.Setting(b'') # type: bytes
diff --git a/Orange/widgets/data/owfeaturestatistics.py b/Orange/widgets/data/owfeaturestatistics.py
index 91ab9a3f3ad..53542cdf543 100644
--- a/Orange/widgets/data/owfeaturestatistics.py
+++ b/Orange/widgets/data/owfeaturestatistics.py
@@ -716,11 +716,11 @@ class Outputs:
settings_version = 2
auto_commit = Setting(True)
- color_var = ContextSetting(None) # type: Optional[Variable]
+ color_var: Optional[Variable] = ContextSetting(None)
# filter_string = ContextSetting('')
- sorting = Setting((0, Qt.DescendingOrder))
- selected_vars = ContextSetting([], schema_only=True)
+ sorting: Tuple[int, int] = Setting((0, Qt.DescendingOrder))
+ selected_vars: List[Variable] = ContextSetting([], schema_only=True)
def __init__(self):
super().__init__()
@@ -859,8 +859,9 @@ def commit(self):
# Send a table with only selected columns to output
variables = self.selected_vars
- self.info.set_output_summary(len(self.data[:, variables]),
- format_summary_details(self.data[:, variables]))
+ reduced_data = self.data[:, variables]
+ self.info.set_output_summary(len(reduced_data),
+ format_summary_details(reduced_data))
self.Outputs.reduced_data.send(self.data[:, variables])
# Send the statistics of the selected variables to ouput
@@ -895,7 +896,7 @@ def migrate_context(cls, context, version):
# is no suitable conversion function, and StringVariable (3)
# was the only hidden var when settings_version < 2, so:
if tpe != 3]
- selected_vars = [all_vars[i] for i in selected_rows]
+ selected_vars = [all_vars[i] for i in selected_rows[0]]
context.values["selected_vars"] = selected_vars, -3
diff --git a/Orange/widgets/data/owfile.py b/Orange/widgets/data/owfile.py
index 31f6ee147eb..fa4ec02b7dc 100644
--- a/Orange/widgets/data/owfile.py
+++ b/Orange/widgets/data/owfile.py
@@ -2,7 +2,7 @@
import logging
from itertools import chain
from urllib.parse import urlparse
-from typing import List
+from typing import List, Dict
import numpy as np
from AnyQt.QtWidgets import \
@@ -14,8 +14,9 @@
from Orange.data.io import FileFormat, UrlReader, class_from_qualified_name
from Orange.util import log_warnings
from Orange.widgets import widget, gui
-from Orange.widgets.settings import Setting, ContextSetting, \
- PerfectDomainContextHandler, SettingProvider
+from Orange.widgets.settings import \
+ Setting, PerfectDomainContextHandler, SettingProvider
+from Orange.widgets.utils import vartype
from Orange.widgets.utils.domaineditor import DomainEditor
from Orange.widgets.utils.itemmodels import PyListModel
from Orange.widgets.utils.filedialogs import RecentPathsWComboMixin, \
@@ -23,13 +24,8 @@
from Orange.widgets.utils.widgetpreview import WidgetPreview
from Orange.widgets.utils.state_summary import format_summary_details
from Orange.widgets.widget import Output, Msg
-
-# Backward compatibility: class RecentPath used to be defined in this module,
-# and it is used in saved (pickled) settings. It must be imported into the
-# module's namespace so that old saved settings still work
from Orange.widgets.utils.filedialogs import RecentPath
-
log = logging.getLogger(__name__)
@@ -95,10 +91,11 @@ class Outputs:
LOCAL_FILE, URL = range(2)
settingsHandler = PerfectDomainContextHandler(
- match_values=PerfectDomainContextHandler.MATCH_VALUES_ALL
- )
+ match_values=PerfectDomainContextHandler.MATCH_VALUES_ALL)
# pylint seems to want declarations separated from definitions
+ # This must be in the widget, not the mixin - otherwise all widgets will
+ # share the same paths
recent_paths: List[RecentPath]
recent_urls: List[str]
variables: list
@@ -114,11 +111,9 @@ class Outputs:
])
recent_urls = Setting([])
source = Setting(LOCAL_FILE)
- sheet_names = Setting({})
+ sheet_names: Dict[str, str] = Setting({})
url = Setting("")
- variables = ContextSetting([])
-
domain_editor = SettingProvider(DomainEditor)
class Warning(widget.OWWidget.Warning):
@@ -471,13 +466,6 @@ def missing_prop(prop):
f"Last entry: {table[-1, 'Timestamp']}"
return text
- def storeSpecificSettings(self):
- self.current_context.modified_variables = self.variables[:]
-
- def retrieveSpecificSettings(self):
- if hasattr(self.current_context, "modified_variables"):
- self.variables[:] = self.current_context.modified_variables
-
def reset_domain_edit(self):
self.domain_editor.reset_domain()
self.apply_domain_edit()
@@ -578,6 +566,13 @@ def workflowEnvChanged(self, key, value, oldvalue):
"""
self.update_file_list(key, value, oldvalue)
+ @classmethod
+ def migrate_context(cls, context, _):
+ if hasattr(context, "modified_variables"):
+ delattr(context, "modified_variables")
+ de_vars = context.values["domain_editor"]["variables"]
+ de_vars[:] = [(x[0], vartype(x[1]), *x[2:]) for x in de_vars]
+
if __name__ == "__main__": # pragma: no cover
WidgetPreview(OWFile).run()
diff --git a/Orange/widgets/data/owmergedata.py b/Orange/widgets/data/owmergedata.py
index 7080dea450b..2204ed9e005 100644
--- a/Orange/widgets/data/owmergedata.py
+++ b/Orange/widgets/data/owmergedata.py
@@ -1,5 +1,6 @@
from collections import namedtuple
from itertools import chain, product
+from typing import List, Tuple, Dict
import numpy as np
@@ -7,6 +8,8 @@
from AnyQt.QtWidgets import (
QWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout
)
+
+from orangewidget.settings import Context
from orangewidget.utils.combobox import ComboBoxSearch
import Orange
@@ -173,12 +176,19 @@ def data(self, index, role=Qt.DisplayRole):
return super().data(index, role)
+class MergeDataContext(Context):
+ variables1: Dict[str, int]
+ variables2: Dict[str, int]
+
+
class MergeDataContextHandler(ContextHandler):
# `widget` is used as an argument in most methods
# pylint: disable=redefined-outer-name
# context handlers override methods using different signatures
# pylint: disable=arguments-differ
+ ContextType = MergeDataContext
+
def new_context(self, variables1, variables2):
context = super().new_context()
context.variables1 = variables1
@@ -286,7 +296,8 @@ class Outputs:
"merging_types")]
settingsHandler = MergeDataContextHandler()
- attr_pairs = ContextSetting(None, schema_only=True)
+ attr_pairs: List[Tuple[Variable, Variable]] \
+ = ContextSetting([], schema_only=True)
merging = Setting(LeftJoin)
auto_apply = Setting(True)
settings_version = 2
diff --git a/Orange/widgets/data/owpaintdata.py b/Orange/widgets/data/owpaintdata.py
index 5fcdf7fd36c..ae8014040d1 100644
--- a/Orange/widgets/data/owpaintdata.py
+++ b/Orange/widgets/data/owpaintdata.py
@@ -4,6 +4,7 @@
import itertools
from functools import partial, singledispatch
from collections import namedtuple
+from typing import List
import numpy as np
@@ -761,8 +762,8 @@ class Outputs:
symbol_size = Setting(10)
#: current data array (shape=(N, 3)) as presented on the output
- data = Setting(None, schema_only=True)
- labels = Setting(["C1", "C2"], schema_only=True)
+ data: List[List[float]] = Setting(None, schema_only=True)
+ labels: List[str] = Setting(["C1", "C2"], schema_only=True)
buttons_area_orientation = Qt.Vertical
graph_name = "plot"
diff --git a/Orange/widgets/data/owpivot.py b/Orange/widgets/data/owpivot.py
index 9b04718d53c..d1ee8bd166a 100644
--- a/Orange/widgets/data/owpivot.py
+++ b/Orange/widgets/data/owpivot.py
@@ -1,5 +1,6 @@
# pylint: disable=missing-docstring
-from typing import Iterable, Set
+from enum import IntEnum
+from typing import Iterable, Set, Tuple
from collections import defaultdict
from itertools import product, chain
@@ -20,7 +21,6 @@
from Orange.data.filter import FilterContinuous, FilterDiscrete, Values
from Orange.statistics.util import (nanmin, nanmax, nanunique, nansum, nanvar,
nanmean, nanmedian, nanmode, bincount)
-from Orange.util import Enum
from Orange.widgets import gui
from Orange.widgets.settings import (Setting, ContextSetting,
DomainContextHandler)
@@ -36,7 +36,7 @@
BorderColorRole = next(gui.OrangeUserRole)
-class AggregationFunctionsEnum(Enum):
+class AggregationFunctionsEnum(IntEnum):
(Count, Count_defined, Sum, Mean, Min, Max,
Mode, Median, Var, Majority) = range(10)
@@ -45,7 +45,7 @@ def __init__(self, *_, **__):
self.func = None
@property
- def value(self):
+ def value(self): # pylint: disable=invalid-overridden-method
return self._value_
def __call__(self, *args):
@@ -748,11 +748,11 @@ class Warning(OWWidget.Warning):
too_many_values = Msg("Selected variable has too many values.")
settingsHandler = DomainContextHandler()
- row_feature = ContextSetting(None)
- col_feature = ContextSetting(None)
- val_feature = ContextSetting(None)
- sel_agg_functions = Setting(set([Pivot.Count]))
- selection = Setting(set(), schema_only=True)
+ row_feature: Variable = ContextSetting(None)
+ col_feature: DiscreteVariable = ContextSetting(None)
+ val_feature: Variable = ContextSetting(None)
+ sel_agg_functions: Set[AggregationFunctionsEnum] = Setting({Pivot.Count})
+ selection: Set[Tuple[int, int]] = Setting(set(), schema_only=True)
auto_commit = Setting(True)
AGGREGATIONS = (Pivot.Count,
diff --git a/Orange/widgets/data/owrank.py b/Orange/widgets/data/owrank.py
index 640da176c06..1cfd7d411c2 100644
--- a/Orange/widgets/data/owrank.py
+++ b/Orange/widgets/data/owrank.py
@@ -4,7 +4,7 @@
from functools import partial
from itertools import chain
from types import SimpleNamespace
-from typing import Any, Callable, List, Tuple
+from typing import Any, Callable, List, Tuple, Set
import numpy as np
from AnyQt.QtCore import (
@@ -20,8 +20,8 @@
from scipy.sparse import issparse
from Orange.data import (
- ContinuousVariable, DiscreteVariable, Domain, StringVariable, Table
-)
+ Table, Domain,
+ ContinuousVariable, DiscreteVariable, StringVariable, Variable)
from Orange.data.util import get_unique_names_duplicates
from Orange.preprocess import score
from Orange.widgets import gui, report
@@ -269,12 +269,12 @@ class Outputs:
nSelected = ContextSetting(5)
auto_apply = Setting(True)
- sorting = Setting((0, Qt.DescendingOrder))
- selected_methods = Setting(set())
+ sorting: Tuple[int, int] = Setting((0, Qt.DescendingOrder))
+ selected_methods: Set[str] = Setting(set())
settings_version = 3
settingsHandler = DomainContextHandler()
- selected_attrs = ContextSetting([], schema_only=True)
+ selected_attrs: List[Variable] = ContextSetting([], schema_only=True)
selectionMethod = ContextSetting(SelectNBest)
class Information(OWWidget.Information):
diff --git a/Orange/widgets/data/owselectcolumns.py b/Orange/widgets/data/owselectcolumns.py
index 3b1b1f1d108..aea3e611585 100644
--- a/Orange/widgets/data/owselectcolumns.py
+++ b/Orange/widgets/data/owselectcolumns.py
@@ -166,7 +166,7 @@ class Outputs:
want_control_area = True
settingsHandler = SelectAttributesDomainContextHandler(first_match=False)
- domain_role_hints = ContextSetting({})
+ domain_role_hints: Dict[Tuple[str, int], Tuple[str, int]] = ContextSetting({})
use_input_features = Setting(False)
ignore_new_features = Setting(False)
auto_commit = Setting(True)
diff --git a/Orange/widgets/data/owtable.py b/Orange/widgets/data/owtable.py
index 7ebaa6da5c3..e7d91745ad3 100644
--- a/Orange/widgets/data/owtable.py
+++ b/Orange/widgets/data/owtable.py
@@ -6,6 +6,7 @@
from collections import OrderedDict, namedtuple
from math import isnan
+from typing import List
import numpy
from scipy.sparse import issparse
@@ -177,6 +178,9 @@ class OWDataTable(OWWidget):
priority = 50
keywords = []
+ buttons_area_orientation = Qt.Vertical
+ dist_color_RGB = (220, 220, 220, 255)
+
class Inputs:
data = Input("Data", Table, multiple=True)
@@ -184,17 +188,14 @@ class Outputs:
selected_data = Output("Selected Data", Table, default=True)
annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)
- buttons_area_orientation = Qt.Vertical
-
show_distributions = Setting(False)
- dist_color_RGB = Setting((220, 220, 220, 255))
show_attribute_labels = Setting(True)
select_rows = Setting(True)
auto_commit = Setting(True)
color_by_class = Setting(True)
- selected_rows = Setting([], schema_only=True)
- selected_cols = Setting([], schema_only=True)
+ selected_rows: List[int] = Setting([], schema_only=True)
+ selected_cols: List[int] = Setting([], schema_only=True)
settings_version = 2
diff --git a/Orange/widgets/data/owtranspose.py b/Orange/widgets/data/owtranspose.py
index cbbb84bd552..81eb7c891d9 100644
--- a/Orange/widgets/data/owtranspose.py
+++ b/Orange/widgets/data/owtranspose.py
@@ -1,4 +1,4 @@
-from Orange.data import Table, ContinuousVariable, StringVariable
+from Orange.data import Table, ContinuousVariable, StringVariable, Variable
from Orange.widgets.settings import (Setting, ContextSetting,
DomainContextHandler)
from Orange.widgets.utils.itemmodels import DomainModel
@@ -32,7 +32,7 @@ class Outputs:
settingsHandler = DomainContextHandler()
feature_type = ContextSetting(GENERIC)
feature_name = ContextSetting("")
- feature_names_column = ContextSetting(None)
+ feature_names_column: Variable = ContextSetting(None)
auto_apply = Setting(True)
class Warning(OWWidget.Warning):
diff --git a/Orange/widgets/data/tests/test_owfeaturestatistics.py b/Orange/widgets/data/tests/test_owfeaturestatistics.py
index 269a6e543c2..42f3c808d10 100644
--- a/Orange/widgets/data/tests/test_owfeaturestatistics.py
+++ b/Orange/widgets/data/tests/test_owfeaturestatistics.py
@@ -476,7 +476,7 @@ def test_restores_previous_selection(self):
def test_settings_migration_to_ver21(self):
settings = {
- 'controlAreaVisible': True, 'savedWidgetGeometry': '',
+ 'controlAreaVisible': True, 'savedWidgetGeometry': None,
'__version__': 1,
'context_settings': [
Context(
diff --git a/Orange/widgets/evaluate/owpredictions.py b/Orange/widgets/evaluate/owpredictions.py
index 483961e7ee7..19394ee3a70 100644
--- a/Orange/widgets/evaluate/owpredictions.py
+++ b/Orange/widgets/evaluate/owpredictions.py
@@ -73,8 +73,8 @@ class Error(OWWidget.Error):
score_table = settings.SettingProvider(ScoreTable)
#: List of selected class value indices in the `class_values` list
- selected_classes = settings.ContextSetting([])
- selection = settings.Setting([], schema_only=True)
+ selected_classes: List[int] = settings.ContextSetting([])
+ selection: List[int] = settings.Setting([], schema_only=True)
def __init__(self):
super().__init__()
diff --git a/Orange/widgets/evaluate/utils.py b/Orange/widgets/evaluate/utils.py
index b22dbfdea23..98372700cf1 100644
--- a/Orange/widgets/evaluate/utils.py
+++ b/Orange/widgets/evaluate/utils.py
@@ -1,6 +1,7 @@
import warnings
from functools import partial
from itertools import chain
+from typing import Set
import numpy as np
@@ -131,7 +132,7 @@ def is_bad(x):
class ScoreTable(OWComponent, QObject):
- shown_scores = \
+ shown_scores: Set[str] = \
Setting(set(chain(*BUILTIN_SCORERS_ORDER.values())))
shownScoresChanged = Signal()
diff --git a/Orange/widgets/settings.py b/Orange/widgets/settings.py
index 3bbe31eb2c0..71fdff99896 100644
--- a/Orange/widgets/settings.py
+++ b/Orange/widgets/settings.py
@@ -36,13 +36,13 @@
import itertools
import logging
import warnings
+from typing import Tuple, Dict, Union, List, Optional
from orangewidget.settings import (
Setting, SettingProvider, SettingsHandler, ContextSetting,
ContextHandler, Context, IncompatibleContext, SettingsPrinter,
- rename_setting, widget_settings_dir
-)
-from orangewidget.settings import _apply_setting
+ rename_setting, widget_settings_dir,
+ TypeSupport)
from Orange.data import Domain, Variable
from Orange.util import OrangeDeprecationWarning
@@ -62,11 +62,58 @@
]
+class VariableTypeSupport(TypeSupport):
+ supported_types = (Variable, )
+ handle_derived_types = True
+
+ @classmethod
+ def pack_value(cls, value, tp=None):
+ if isinstance(value, Variable):
+ return value.name, 100 + vartype(value)
+ return value
+
+ @classmethod
+ # pylint: disable=keyword-arg-before-vararg
+ def unpack_value(cls, value, tp, domain=None, *args):
+ # This essentially decodes Tuple[str, int] to Union[Variable, Any]:
+ # if vartype is 100 (or 0), it leaves value as it is. Not nice, but
+ # needed for backward compatiblity -- and also practical in cases where
+ # a combo gives a few fixed options + variables. See Merge Data.
+ if isinstance(tp, type) and issubclass(tp, Variable):
+ if value is None:
+ return None
+ if domain is None:
+ warnings.warn("unpacking variables requires having a domain")
+ return None
+ # Compatibility with ancient settings that stored Variable as
+ # string, without any type-checking
+ if isinstance(value, str):
+ if value in domain:
+ return domain[value]
+ else:
+ saved_type = value[1] % 100
+ if saved_type == 0:
+ return value[0]
+ var = domain[value[0]]
+ if vartype(var) != saved_type:
+ warnings.warn(
+ f"'{var.name}' is of wrong type ({type(var).__name__})")
+ return var
+ return super().unpack_value(value, tp, domain, *args)
+
+
+class DomainContext(Context):
+ attributes: Dict[str, int]
+ metas: Dict[str, int]
+
+
class DomainContextHandler(ContextHandler):
"""Context handler for widgets with settings that depend on
the input dataset. Suitable settings are selected based on the
data domain."""
+ ContextType = DomainContext
+
MATCH_VALUES_NONE, MATCH_VALUES_CLASS, MATCH_VALUES_ALL = range(3)
def __init__(self, *, match_values=0, first_match=True, **kwargs):
@@ -80,6 +127,45 @@ def __init__(self, *, match_values=0, first_match=True, **kwargs):
.format(name), OrangeDeprecationWarning
)
+ def analyze_settings(self, provider, prefix, class_name):
+ super().analyze_settings(provider, prefix, class_name)
+ if type(self) is DomainContextHandler and \
+ not any(isinstance(setting, ContextSetting)
+ for setting, *_ in self.provider.traverse_settings()):
+ warnings.warn(f"{class_name} uses DomainContextHandler without "
+ "having any context settings")
+
+ def _migrate_contexts(self, contexts: List[Context]) -> None:
+ super()._migrate_contexts(contexts)
+ self._migrate_annotated(contexts)
+
+ def _migrate_annotated(self, contexts: List[Context]):
+ for setting, *_ in self.provider.traverse_settings():
+ if not isinstance(setting, ContextSetting) \
+ or setting.type is None \
+ or not self.is_allowed_type(setting.type):
+ continue
+ name = setting.name
+ for context in contexts:
+ if name not in context.values:
+ continue
+ value = context.values[name]
+ if isinstance(value, tuple) and len(value) == 2 and (
+ value[1] == -2 # encoded any
+ or ( # encoded list or dict
+ (value[1] == -3 and isinstance(value[0], list)
+ or value[1] == -4 and isinstance(value[0], dict))
+ and all( # check that all elements/keys encode vars
+ x is None or
+ isinstance(x, tuple)
+ and len(x) == 2
+ and isinstance(x[0], str)
+ and isinstance(x[1], int)
+ and 1 <= x[1] % 100 <= 4
+ for x in value[0])
+ )):
+ context.values[name] = value[0]
+
def encode_domain(self, domain):
"""
domain: Orange.data.domain to encode
@@ -140,7 +226,7 @@ def filter_value(self, setting, data, domain, attrs, metas):
@staticmethod
def encode_variable(var):
- return var.name, 100 + vartype(var)
+ return VariableTypeSupport.pack_value(var)
@classmethod
def encode_setting(cls, context, setting, value):
@@ -181,9 +267,9 @@ def get_var(name):
for name_type in data]
if dtype == -4:
return {get_var(name): val for (name, _), val in data.items()}
- if dtype >= 100:
+ if dtype > 0:
return get_var(data)
- return value[0]
+ return data
else:
return value
@@ -251,7 +337,7 @@ def match_list(self, setting, value, context, attrs, metas):
def match_value(self, setting, value, attrs, metas):
"""Match a single value """
- if value[1] < 0:
+ if not isinstance(value, tuple) or value[1] < 0:
return 0, 0
if self._var_exists(setting, value, attrs, metas):
@@ -278,22 +364,30 @@ def is_encoded_var(value):
and isinstance(value[0], str) and isinstance(value[1], int) \
and value[1] >= 0
+
+class ClassValueContext(Context):
+ classes: Optional[Tuple[str, ...]]
+
+
class ClassValuesContextHandler(ContextHandler):
"""Context handler used for widgets that work with
a single discrete variable"""
+ ContextType = ClassValueContext
+
def open_context(self, widget, classes):
if isinstance(classes, Variable):
if classes.is_discrete:
classes = classes.values
else:
classes = None
-
+ if classes is not None:
+ classes = tuple(classes)
super().open_context(widget, classes)
def new_context(self, classes):
context = super().new_context()
- context.classes = classes
+ context.classes = None if classes is None else tuple(classes)
return context
def match(self, context, classes):
@@ -309,6 +403,13 @@ def match(self, context, classes):
return self.NO_MATCH
+class PerfectDomainContext(Context):
+ VarDesc = Tuple[str, Union[List[str], int]]
+ attributes: Tuple[VarDesc, ...]
+ class_vars: Tuple[VarDesc, ...]
+ metas: Tuple[VarDesc, ...]
+
+
class PerfectDomainContextHandler(DomainContextHandler):
"""Context handler that matches a context only when
the same domain is available.
@@ -316,6 +417,8 @@ class PerfectDomainContextHandler(DomainContextHandler):
It uses a different encoding than the DomainContextHandler.
"""
+ ContextType = PerfectDomainContext
+
def new_context(self, domain, attributes, class_vars, metas):
"""Same as DomainContextHandler, but also store class_vars"""
context = super().new_context(domain, attributes, metas)
diff --git a/Orange/widgets/unsupervised/owdistancematrix.py b/Orange/widgets/unsupervised/owdistancematrix.py
index 65ce2050f2e..4bfb1c3596e 100644
--- a/Orange/widgets/unsupervised/owdistancematrix.py
+++ b/Orange/widgets/unsupervised/owdistancematrix.py
@@ -1,5 +1,6 @@
import itertools
import math
+from typing import List
import numpy as np
@@ -8,15 +9,18 @@
from AnyQt.QtGui import QColor, QPen, QBrush
from AnyQt.QtCore import Qt, QAbstractTableModel, QSize
+from orangewidget.settings import Context
+
from Orange.data import Table, Variable, StringVariable
from Orange.misc import DistMatrix
-from Orange.widgets import widget, gui
+
+from Orange.widgets import gui
from Orange.widgets.gui import OrangeUserRole
from Orange.widgets.settings import Setting, ContextSetting, ContextHandler
from Orange.widgets.utils.itemmodels import VariableListModel
from Orange.widgets.utils.itemselectionmodel import SymmetricSelectionModel
from Orange.widgets.utils.widgetpreview import WidgetPreview
-from Orange.widgets.widget import Input, Output
+from Orange.widgets.widget import OWWidget, AttributeList, Input, Output
class DistanceMatrixModel(QAbstractTableModel):
@@ -147,7 +151,16 @@ def sizeHintForColumn(self, column: int) -> int:
return hint + 1 if self.showGrid() else hint
+class DistanceMatrixContext(Context):
+ dim: int
+ annotations: List[str]
+ annotation: str
+ selection: List[int]
+
+
class DistanceMatrixContextHandler(ContextHandler):
+ ContextType = DistanceMatrixContext
+
@staticmethod
def _var_names(annotations):
return [a.name if isinstance(a, Variable) else a for a in annotations]
@@ -180,7 +193,7 @@ def settings_to_widget(self, widget, *args):
widget.tableview.selectionModel().setSelectedItems(context.selection)
-class OWDistanceMatrix(widget.OWWidget):
+class OWDistanceMatrix(OWWidget):
name = "Distance Matrix"
description = "View distance matrix."
icon = "icons/DistanceMatrix.svg"
@@ -197,7 +210,7 @@ class Outputs:
settingsHandler = DistanceMatrixContextHandler()
auto_commit = Setting(True)
annotation_idx = ContextSetting(1)
- selection = ContextSetting([])
+ selection: List[int] = ContextSetting([])
want_control_area = True
want_main_area = False
@@ -288,7 +301,7 @@ def _update_labels(self):
attr = self.distances.row_items.domain.attributes
labels = [str(attr[i]) for i in range(self.distances.shape[0])]
elif self.annotation_idx == 2 and \
- isinstance(self.items, widget.AttributeList):
+ isinstance(self.items, AttributeList):
labels = [v.name for v in self.items]
elif isinstance(self.items, Table):
var = self.annot_combo.model()[self.annotation_idx]
diff --git a/Orange/widgets/utils/__init__.py b/Orange/widgets/utils/__init__.py
index e974de1cc2f..0a39c8e10e5 100644
--- a/Orange/widgets/utils/__init__.py
+++ b/Orange/widgets/utils/__init__.py
@@ -9,11 +9,20 @@
from AnyQt.QtCore import QObject
-from Orange.data.variable import TimeVariable
+from Orange.data.variable import \
+ Variable, DiscreteVariable, ContinuousVariable, StringVariable, TimeVariable
from Orange.util import deepgetattr
+_vartype2int = {DiscreteVariable: 1,
+ ContinuousVariable: 2,
+ StringVariable: 3,
+ TimeVariable: 4}
+
def vartype(var):
+ if isinstance(var, type) and issubclass(var, Variable):
+ return _vartype2int[var]
+
if var.is_discrete:
return 1
elif var.is_continuous:
diff --git a/Orange/widgets/utils/domaineditor.py b/Orange/widgets/utils/domaineditor.py
index d9f9b016540..9247639d687 100644
--- a/Orange/widgets/utils/domaineditor.py
+++ b/Orange/widgets/utils/domaineditor.py
@@ -1,5 +1,6 @@
from itertools import chain
from copy import deepcopy
+from typing import List, Tuple
import numpy as np
import scipy.sparse as sp
@@ -16,6 +17,7 @@
from Orange.widgets import gui
from Orange.widgets.gui import HorizontalGridDelegate
from Orange.widgets.settings import ContextSetting
+from Orange.widgets.utils import vartype
from Orange.widgets.utils.itemmodels import TableModel
@@ -40,8 +42,9 @@ class VarTableModel(QAbstractTableModel):
places = "feature", "target", "meta", "skip"
typenames = "categorical", "numeric", "text", "datetime"
vartypes = DiscreteVariable, ContinuousVariable, StringVariable, TimeVariable
- name2type = dict(zip(typenames, vartypes))
- type2name = dict(zip(vartypes, typenames))
+ vtype2name = {vartype(vt): name for vt, name in zip(vartypes, typenames)}
+ name2vtype = {v: k for k, v in vtype2name.items()}
+ vtype2type = {vartype(vt): vt for vt in vartypes}
def __init__(self, variables, *args):
super().__init__(*args)
@@ -72,14 +75,14 @@ def data(self, index, role):
val = self.variables[row][col]
if role in (Qt.DisplayRole, Qt.EditRole):
if col == Column.tpe:
- return self.type2name[val]
+ return self.vtype2name[val]
if col == Column.place:
return self.places[val]
else:
return val
if role == Qt.DecorationRole:
if col == Column.tpe:
- return gui.attributeIconDict[self.vartypes.index(val) + 1]
+ return gui.attributeIconDict[val]
if role == Qt.ForegroundRole:
if self.variables[row][Column.place] == Place.skip \
and col != Column.place:
@@ -96,19 +99,22 @@ def data(self, index, role):
return None
def setData(self, index, value, role=Qt.EditRole):
+ def set_at(c, value):
+ row_data = self.variables[row]
+ self.variables[row] = row_data[:c] + (value, ) + row_data[c + 1:]
+
row, col = index.row(), index.column()
- row_data = self.variables[row]
if role == Qt.EditRole:
if col == Column.name and not (value.isspace() or value == ""):
- row_data[col] = value
+ set_at(Column.name, value)
elif col == Column.tpe:
- vartype = self.name2type[value]
- row_data[col] = vartype
- if not vartype.is_primitive() and \
- row_data[Column.place] < Place.meta:
- row_data[Column.place] = Place.meta
+ vtype = self.name2vtype[value]
+ set_at(Column.tpe, vtype)
+ if not self.vtype2type[vtype].is_primitive() and \
+ self.variables[row][Column.place] < Place.meta:
+ set_at(Column.place, Place.meta)
elif col == Column.place:
- row_data[col] = self.places.index(value)
+ set_at(Column.place, self.places.index(value))
else:
return False
# Settings may change background colors
@@ -185,8 +191,9 @@ def setEditorData(self, combo, index):
class PlaceDelegate(ComboDelegate):
def setEditorData(self, combo, index):
combo.clear()
- to_meta = not self.view.model().variables[
- index.row()][Column.tpe].is_primitive()
+ model = self.view.model()
+ vtype = model.variables[index.row()][Column.tpe]
+ to_meta = not model.vtype2type[vtype].is_primitive()
combo.addItems(self.items[2 * to_meta:])
combo.setCurrentIndex(self.items.index(index.data()) - 2 * to_meta)
combo.showPopup()
@@ -200,7 +207,7 @@ class DomainEditor(QTableView):
widget : parent widget
"""
- variables = ContextSetting([])
+ variables: List[Tuple[str, int, int, str, bool]] = ContextSetting([])
def __init__(self, widget):
super().__init__()
@@ -285,14 +292,14 @@ def get_domain(self, domain, data, deduplicate=False):
cols = [[], [], []] # Xcols, Ycols, Mcols
def numbers_are_round(var, col_data):
- if type(var) == ContinuousVariable:
+ if type(var) is ContinuousVariable:
data = np.asarray(col_data.data) # Works for dense and sparse
data = data[~np.isnan(data)]
return (data == data.astype(int)).all()
return False
# Exit early with original domain if the user didn't actually change anything
- if all((name == orig_var.name and tpe == type(orig_var) and place == orig_plc)
+ if all((name == orig_var.name and tpe == vartype(orig_var) and place == orig_plc)
for (name, tpe, place, _, _), (orig_var, orig_plc) in
zip(variables,
chain(((at, Place.feature) for at in domain.attributes),
@@ -309,7 +316,7 @@ def numbers_are_round(var, col_data):
else:
renamed_iter = iter(relevant_names)
renamed = []
- for (name, tpe, place, _, may_be_numeric), (orig_var, orig_plc) in \
+ for (name, vartpe, place, _, may_be_numeric), (orig_var, orig_plc) in \
zip(variables,
chain([(at, Place.feature) for at in domain.attributes],
[(cl, Place.class_var) for cl in domain.class_vars],
@@ -317,6 +324,7 @@ def numbers_are_round(var, col_data):
if place == Place.skip:
continue
+ tpe = VarTableModel.vtype2type[vartpe]
new_name = next(renamed_iter)
if new_name != name and name not in renamed:
renamed.append(name)
@@ -324,11 +332,11 @@ def numbers_are_round(var, col_data):
col_data = self._get_column(data, orig_var, orig_plc)
is_sparse = sp.issparse(col_data)
- if new_name == orig_var.name and tpe == type(orig_var):
+ if new_name == orig_var.name and tpe is type(orig_var):
var = orig_var
- elif tpe == type(orig_var):
+ elif tpe is type(orig_var):
var = orig_var.copy(name=new_name)
- elif tpe == DiscreteVariable:
+ elif tpe is DiscreteVariable:
values = natural_sorted(
list(str(i) for i in unique(col_data)
if not self._is_missing(i))
@@ -340,7 +348,7 @@ def numbers_are_round(var, col_data):
values = [str(int(float(v))) for v in values]
var = tpe(new_name, values)
col_data = self._to_column(col_data, is_sparse)
- elif tpe == StringVariable:
+ elif tpe is StringVariable:
var = tpe.make(new_name)
if type(orig_var) in [DiscreteVariable, TimeVariable]:
col_data = [orig_var.repr_val(x) if not np.isnan(x) else ""
@@ -354,7 +362,7 @@ def numbers_are_round(var, col_data):
# don't obey sparsity for StringVariable since they are
# in metas which are transformed to dense below
col_data = self._to_column(col_data, False, dtype=object)
- elif tpe == ContinuousVariable and type(orig_var) == DiscreteVariable:
+ elif tpe is ContinuousVariable and type(orig_var) is DiscreteVariable:
var = tpe.make(new_name)
if may_be_numeric:
col_data = [np.nan if self._is_missing(x) else float(orig_var.values[int(x)])
@@ -439,9 +447,9 @@ def discrete_value_display(value_list):
return result
return [
- [var.name, type(var), place,
+ (var.name, vartype(var), place,
discrete_value_display(var.values) if var.is_discrete else "",
- may_be_numeric(var)]
+ may_be_numeric(var))
for place, vars in enumerate(
(domain.attributes, domain.class_vars, domain.metas))
for var in vars
diff --git a/Orange/widgets/utils/tests/test_domaineditor.py b/Orange/widgets/utils/tests/test_domaineditor.py
index 44e7bb42e31..b0d3bd1c829 100644
--- a/Orange/widgets/utils/tests/test_domaineditor.py
+++ b/Orange/widgets/utils/tests/test_domaineditor.py
@@ -23,13 +23,15 @@ def setUp(self):
self.widget = MockWidget()
self.editor = self.widget.domain_editor
+ discrete, continuous, string, time = range(1, 5)
+
self.orig_variables = [
- ["d1", DiscreteVariable, 0, "x, y, z, ...", False],
- ["d2", DiscreteVariable, 0, "1, 2, 3, ...", True],
- ["c1", ContinuousVariable, 0, "", True],
- ["d3", DiscreteVariable, 1, "4, 3, 6, ...", True],
- ["s", StringVariable, 2, "", False],
- ["t", TimeVariable, 2, "", True]
+ ("d1", discrete, 0, "x, y, z, ...", False),
+ ("d2", discrete, 0, "1, 2, 3, ...", True),
+ ("c1", continuous, 0, "", True),
+ ("d3", discrete, 1, "4, 3, 6, ...", True),
+ ("s", string, 2, "", False),
+ ("t", time, 2, "", True)
]
self.domain = Domain(
[DiscreteVariable("d1", values=list("xyzw")),
@@ -51,7 +53,7 @@ def test_deduplication(self):
# No duplicates
- domain, _ = \
+ domain, *_ = \
editor.get_domain(self.domain, data)
self.assertEqual([var.name for var in domain.attributes],
["d1", "d2", "c1"])
diff --git a/Orange/widgets/visualize/owbarplot.py b/Orange/widgets/visualize/owbarplot.py
index a5e61a849df..6ee77f3cc43 100644
--- a/Orange/widgets/visualize/owbarplot.py
+++ b/Orange/widgets/visualize/owbarplot.py
@@ -383,13 +383,13 @@ class Outputs:
buttons_area_orientation = Qt.Vertical
settingsHandler = DomainContextHandler()
- selected_var = ContextSetting(None)
- group_var = ContextSetting(None)
- annot_var = ContextSetting(None)
- color_var = ContextSetting(None)
+ selected_var: ContinuousVariable = ContextSetting(None)
+ group_var: DiscreteVariable = ContextSetting(None)
+ annot_var: Variable = ContextSetting(None)
+ color_var: DiscreteVariable = ContextSetting(None)
auto_commit = Setting(True)
- selection = Setting(None, schema_only=True)
- visual_settings = Setting({}, schema_only=True)
+ selection: List[int] = Setting(None, schema_only=True)
+ visual_settings: Dict[KeyType, ValueType] = Setting({}, schema_only=True)
graph = SettingProvider(BarPlotGraph)
graph_name = "graph.plotItem"
diff --git a/Orange/widgets/visualize/owboxplot.py b/Orange/widgets/visualize/owboxplot.py
index c5c3584e64d..c9819a2862b 100644
--- a/Orange/widgets/visualize/owboxplot.py
+++ b/Orange/widgets/visualize/owboxplot.py
@@ -2,6 +2,8 @@
from collections import namedtuple
from itertools import chain, count
import numpy as np
+import scipy.special
+from scipy.stats import f_oneway, chi2_contingency
from AnyQt.QtWidgets import (
QGraphicsView, QGraphicsScene, QGraphicsItem, QGraphicsSimpleTextItem,
@@ -10,10 +12,9 @@
)
from AnyQt.QtGui import QPen, QColor, QBrush, QPainterPath, QPainter, QFont
from AnyQt.QtCore import Qt, QEvent, QRectF, QSize, QSortFilterProxyModel
-from orangewidget.utils.listview import ListViewSearch
-import scipy.special
-from scipy.stats import f_oneway, chi2_contingency
+from Orange.data import Variable, DiscreteVariable
+from orangewidget.utils.listview import ListViewSearch
import Orange.data
from Orange.data.filter import FilterDiscrete, FilterContinuous, Values, \
@@ -145,10 +146,10 @@ class Warning(widget.OWWidget.Warning):
# variable names!
selection = ContextSetting((), schema_only=True)
- attribute = ContextSetting(None)
+ attribute: Variable = ContextSetting(None)
order_by_importance = Setting(False)
order_grouping_by_importance = Setting(False)
- group_var = ContextSetting(None)
+ group_var: DiscreteVariable = ContextSetting(None)
show_annotations = Setting(True)
compare = Setting(CompareMeans)
stattest = Setting(0)
diff --git a/Orange/widgets/visualize/owdistributions.py b/Orange/widgets/visualize/owdistributions.py
index 49c6da95088..13c63aca765 100644
--- a/Orange/widgets/visualize/owdistributions.py
+++ b/Orange/widgets/visualize/owdistributions.py
@@ -1,5 +1,6 @@
from functools import partial, reduce
from itertools import count, groupby, repeat
+from typing import Set
from xml.sax.saxutils import escape
import numpy as np
@@ -11,7 +12,8 @@
from orangewidget.utils.listview import ListViewSearch
import pyqtgraph as pg
-from Orange.data import Table, DiscreteVariable, ContinuousVariable, Domain
+from Orange.data import Table, DiscreteVariable, ContinuousVariable, Domain, \
+ Variable
from Orange.preprocess.discretize import decimal_binnings, time_binnings, \
short_time_units
from Orange.statistics import distribution, contingency
@@ -270,9 +272,9 @@ class Warning(OWWidget.Warning):
ignored_nans = Msg("Data instances with missing values are ignored")
settingsHandler = settings.DomainContextHandler()
- var = settings.ContextSetting(None)
- cvar = settings.ContextSetting(None)
- selection = settings.ContextSetting(set(), schema_only=True)
+ var: Variable = settings.ContextSetting(None)
+ cvar: DiscreteVariable = settings.ContextSetting(None)
+ selection: Set[int] = settings.ContextSetting(set(), schema_only=True)
# number_of_bins must be a context setting because selection depends on it
number_of_bins = settings.ContextSetting(5, schema_only=True)
diff --git a/Orange/widgets/visualize/owheatmap.py b/Orange/widgets/visualize/owheatmap.py
index c4738209e6f..b66d69eab73 100644
--- a/Orange/widgets/visualize/owheatmap.py
+++ b/Orange/widgets/visualize/owheatmap.py
@@ -168,13 +168,13 @@ class Outputs:
legend: bool = settings.Setting(True)
# Annotations
#: text row annotation (row names)
- annotation_var = settings.ContextSetting(None)
+ annotation_var: Variable = settings.ContextSetting(None)
#: color row annotation
- annotation_color_var = settings.ContextSetting(None)
+ annotation_color_var: Variable = settings.ContextSetting(None)
column_annotation_color_key: Optional[Tuple[str, str]] = settings.ContextSetting(None)
# Discrete variable used to split that data/heatmaps (vertically)
- split_by_var = settings.ContextSetting(None)
+ split_by_var: DiscreteVariable = settings.ContextSetting(None)
# Split heatmap columns by 'key' (horizontal)
split_columns_key: Optional[Tuple[str, str]] = settings.ContextSetting(None)
# Selected row/column clustering method (name)
diff --git a/Orange/widgets/visualize/owlinearprojection.py b/Orange/widgets/visualize/owlinearprojection.py
index 3b946afa22c..57631633b5a 100644
--- a/Orange/widgets/visualize/owlinearprojection.py
+++ b/Orange/widgets/visualize/owlinearprojection.py
@@ -2,9 +2,10 @@
Linear Projection widget
------------------------
"""
-
+from enum import IntEnum
from itertools import islice, permutations, chain
from math import factorial
+from typing import List
import numpy as np
@@ -17,11 +18,10 @@
import pyqtgraph as pg
-from Orange.data import Table, Domain
+from Orange.data import Table, Domain, ContinuousVariable
from Orange.preprocess import Normalize
from Orange.preprocess.score import ReliefF, RReliefF
from Orange.projection import PCA, LDA, LinearProjector
-from Orange.util import Enum
from Orange.widgets import gui, report
from Orange.widgets.gui import OWComponent
from Orange.widgets.settings import Setting, ContextSetting, SettingProvider
@@ -257,8 +257,8 @@ def update_circle(self):
self.circle_item.setPen(pen)
-Placement = Enum("Placement", dict(Circular=0, LDA=1, PCA=2), type=int,
- qualname="Placement")
+Placement = IntEnum("Placement", dict(Circular=0, LDA=1, PCA=2),
+ qualname="Placement")
class OWLinearProjection(OWAnchorProjectionWidget):
@@ -276,7 +276,7 @@ class OWLinearProjection(OWAnchorProjectionWidget):
settings_version = 6
placement = Setting(Placement.Circular)
- selected_vars = ContextSetting([])
+ selected_vars: List[ContinuousVariable] = ContextSetting([])
vizrank = SettingProvider(LinearProjectionVizRank)
GRAPH_CLASS = OWLinProjGraph
graph = SettingProvider(OWLinProjGraph)
diff --git a/Orange/widgets/visualize/owlineplot.py b/Orange/widgets/visualize/owlineplot.py
index bce51e2b49b..1eb27c56f59 100644
--- a/Orange/widgets/visualize/owlineplot.py
+++ b/Orange/widgets/visualize/owlineplot.py
@@ -1,4 +1,4 @@
-from typing import List
+from typing import List, Dict
from xml.sax.saxutils import escape
import numpy as np
@@ -13,6 +13,7 @@
from pyqtgraph.graphicsItems.ViewBox import ViewBox
from orangewidget.utils.listview import ListViewSearch
+from orangewidget.utils import visual_settings_dlg as vissettings
from orangewidget.utils.visual_settings_dlg import VisualSettingsDialog
from Orange.data import Table, DiscreteVariable
@@ -608,15 +609,15 @@ class Outputs:
annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)
settingsHandler = DomainContextHandler()
- group_var = ContextSetting(None)
+ group_var: DiscreteVariable = ContextSetting(None)
show_profiles = Setting(False)
show_range = Setting(True)
show_mean = Setting(True)
show_error = Setting(False)
auto_commit = Setting(True)
- selection = Setting(None, schema_only=True)
- visual_settings = Setting({}, schema_only=True)
-
+ selection: List[int] = Setting(None, schema_only=True)
+ visual_settings: Dict[vissettings.KeyType, vissettings.ValueType] \
+ = Setting({}, schema_only=True)
graph_name = "graph.plotItem"
class Error(OWWidget.Error):
diff --git a/Orange/widgets/visualize/owmosaic.py b/Orange/widgets/visualize/owmosaic.py
index 13948fd2c92..fbbec57ed2c 100644
--- a/Orange/widgets/visualize/owmosaic.py
+++ b/Orange/widgets/visualize/owmosaic.py
@@ -3,6 +3,7 @@
from itertools import product, chain, repeat
from math import sqrt, log
from operator import mul, attrgetter
+from typing import Set
import numpy as np
from scipy.stats import distributions
@@ -312,7 +313,7 @@ class Outputs:
variable3: Variable = ContextSetting(None)
variable4: Variable = ContextSetting(None)
variable_color: DiscreteVariable = ContextSetting(None)
- selection = Setting(set(), schema_only=True)
+ selection: Set[str] = Setting(set(), schema_only=True)
BAR_WIDTH = 5
SPACING = 4
diff --git a/Orange/widgets/visualize/owscatterplot.py b/Orange/widgets/visualize/owscatterplot.py
index 1aa0dfd292f..cfd6da9aa0b 100644
--- a/Orange/widgets/visualize/owscatterplot.py
+++ b/Orange/widgets/visualize/owscatterplot.py
@@ -240,8 +240,8 @@ class Outputs(OWDataProjectionWidget.Outputs):
settings_version = 4
auto_sample = Setting(True)
- attr_x = ContextSetting(None)
- attr_y = ContextSetting(None)
+ attr_x: ContinuousVariable = ContextSetting(None)
+ attr_y: ContinuousVariable = ContextSetting(None)
tooltip_shows_all = Setting(True)
GRAPH_CLASS = OWScatterPlotGraph
diff --git a/Orange/widgets/visualize/tests/test_owscatterplot.py b/Orange/widgets/visualize/tests/test_owscatterplot.py
index 39cf81dcc2a..0ff0280f39b 100644
--- a/Orange/widgets/visualize/tests/test_owscatterplot.py
+++ b/Orange/widgets/visualize/tests/test_owscatterplot.py
@@ -298,7 +298,7 @@ def test_set_strings_settings(self):
"""
self.send_signal(self.widget.Inputs.data, self.data)
settings = self.widget.settingsHandler.pack_data(self.widget)
- plot_settings = settings["context_settings"][0].values
+ plot_settings = settings["context_settings"][0]["values"]
plot_settings["attr_label"] = ("sepal length", -2)
plot_settings["attr_color"] = ("sepal width", -2)
plot_settings["attr_shape"] = ("iris", -2)
diff --git a/Orange/widgets/visualize/utils/widget.py b/Orange/widgets/visualize/utils/widget.py
index 11eeb9fef43..f909139b389 100644
--- a/Orange/widgets/visualize/utils/widget.py
+++ b/Orange/widgets/visualize/utils/widget.py
@@ -1,3 +1,4 @@
+from typing import List, Tuple, Dict
from xml.sax.saxutils import escape
import numpy as np
@@ -5,6 +6,7 @@
from AnyQt.QtCore import QSize, Signal, Qt
from AnyQt.QtWidgets import QApplication
+from orangewidget.utils import visual_settings_dlg as vissettings
from orangewidget.utils.visual_settings_dlg import VisualSettingsDialog
from Orange.data import (
@@ -53,10 +55,10 @@ class OWProjectionWidgetBase(OWWidget, openclass=True):
and a bool `np.ndarray` with indicators of valid (that is, shown)
data points.
"""
- attr_color = ContextSetting(None, required=ContextSetting.OPTIONAL)
- attr_label = ContextSetting(None, required=ContextSetting.OPTIONAL)
- attr_shape = ContextSetting(None, required=ContextSetting.OPTIONAL)
- attr_size = ContextSetting(None, required=ContextSetting.OPTIONAL)
+ attr_color: Variable = ContextSetting(None, required=ContextSetting.OPTIONAL)
+ attr_label: Variable = ContextSetting(None, required=ContextSetting.OPTIONAL)
+ attr_shape: Variable = ContextSetting(None, required=ContextSetting.OPTIONAL)
+ attr_size: Variable = ContextSetting(None, required=ContextSetting.OPTIONAL)
class Information(OWWidget.Information):
missing_size = Msg(
@@ -383,8 +385,9 @@ class Warning(OWProjectionWidgetBase.Warning):
"Increase opacity if subset is difficult to see")
settingsHandler = DomainContextHandler()
- selection = Setting(None, schema_only=True)
- visual_settings = Setting({}, schema_only=True)
+ selection: List[Tuple[int, int]] = Setting(None, schema_only=True)
+ visual_settings: Dict[vissettings.KeyType, vissettings.ValueType] \
+ = Setting({}, schema_only=True)
auto_commit = Setting(True)
GRAPH_CLASS = OWScatterPlotBase