diff --git a/Orange/widgets/data/owcsvimport.py b/Orange/widgets/data/owcsvimport.py index 92cde2191ef..d1a72212fd4 100644 --- a/Orange/widgets/data/owcsvimport.py +++ b/Orange/widgets/data/owcsvimport.py @@ -70,6 +70,7 @@ QSettings_readArray, QSettings_writeArray ) from Orange.widgets.utils.state_summary import format_summary_details +from Orange.widgets.utils.textimport import columntype_for_vartype if typing.TYPE_CHECKING: # pylint: disable=invalid-name @@ -221,6 +222,42 @@ def spec_from_encodable(spec, enumtype): r.append((range(start, stop), enum_get(enumtype, name, None))) return r + def update_type(self, var_i, type): + ctypes = [ctype + for r, ctype in self.columntypes + for _ in r] + ctypes[var_i] = type + self.__update_types(ctypes) + + def __update_types(self, types): + """ + Parameters + ---------- + + types (List[ColumnType]): list of types denoting not-skipped features + """ + new_ctypes: [(range, ColumnType)] = [] + i = 0 + skip_c = 0 + for r, prevtype in self.columntypes: + if prevtype == ColumnType.Skip: + new_ctypes.append([r, prevtype]) + rlen = r.stop - r.start + skip_c += rlen + i += rlen + continue + for _ in r: + newtype = types[i - skip_c] + if new_ctypes and newtype == new_ctypes[-1][1]: + oldr = new_ctypes[-1][0] + new_ctypes[-1] = \ + (range(oldr.start, oldr.stop + 1), + new_ctypes[-1][1]) + else: + new_ctypes.append((range(i, i + 1), newtype)) + i += 1 + self.columntypes = new_ctypes + class CSVImportDialog(QDialog): """ @@ -662,6 +699,7 @@ class Warning(widget.OWWidget.Warning): def __init__(self, *args, **kwargs): super().__init__(self, *args, **kwargs) + self.data = None self.settingsAboutToBePacked.connect(self._saveState) self.__committimer = QTimer(self, singleShot=True) @@ -1240,13 +1278,24 @@ def __handle_result(self, f): domain = None self.data = table - self.domain_editor.set_domain(domain) + self.domain_editor.set_domain(domain, + variable_order=list(df.columns)) self.send("Data Frame", df) self.send('Data', table) self._update_status_messages(table) - def __handle_domain_edit(self): + def __handle_domain_edit(self, index): + self.Warning.clear() + # set type in import dialogue + vartype = self.domain_editor.type_for_index(index) + if vartype: + t = columntype_for_vartype(vartype) + var_i = self.domain_editor.original_index(index) + opts = self.current_item().options() + opts.update_type(var_i, t) + + # commit data to output if self.data is None: table = None else: diff --git a/Orange/widgets/utils/domaineditor.py b/Orange/widgets/utils/domaineditor.py index c2b7e75f79a..724a5ce1916 100644 --- a/Orange/widgets/utils/domaineditor.py +++ b/Orange/widgets/utils/domaineditor.py @@ -102,6 +102,7 @@ def setData(self, index, value, role=Qt.EditRole): row, col = index.row(), index.column() row_data = self.variables[row] if role == Qt.EditRole: + end_index = index if col == Column.name and not (value.isspace() or value == ""): row_data[col] = value elif col == Column.tpe: @@ -110,12 +111,13 @@ def setData(self, index, value, role=Qt.EditRole): if not vartype.is_primitive() and \ row_data[Column.place] < Place.meta: row_data[Column.place] = Place.meta + end_index = index.sibling(row, Column.place) elif col == Column.place: row_data[col] = self.places.index(value) else: return False # Settings may change background colors - self.dataChanged.emit(index.sibling(row, 0), index.sibling(row, 3)) + self.dataChanged.emit(index, end_index) return True return False @@ -205,6 +207,8 @@ class DomainEditor(QTableView): def __init__(self, variables=None): super().__init__() + self.variables = variables + self.ordered_variables = variables self.setModel(VarTableModel(self, variables)) self.setSelectionMode(QTableView.NoSelection) @@ -372,6 +376,16 @@ def numbers_are_round(var, col_data): else: return domain, [X, Y, m] + def original_index(self, index): + var = self.variables[index.row()] + return self.ordered_variables.index(var) + + def type_for_index(self, index): + if index.column() != Column.tpe: + return None + var = self.variables[index.row()] + return var[Column.tpe] + @staticmethod def _get_column(data, source_var, source_place): """ Extract column from data and preserve sparsity. """ @@ -383,9 +397,16 @@ def _get_column(data, source_var, source_place): col_data = data[:, source_var].X return col_data - def set_domain(self, domain): - variables = self.parse_domain(domain) - self.model().set_variables(variables) + def set_domain(self, domain, variable_order=None): + self.variables = self.parse_domain(domain) + if variable_order: + self.ordered_variables = sorted( + self.variables, + key=lambda a: variable_order.index(a[0]) + ) + else: + self.ordered_variables = self.variables + self.model().set_variables(self.variables) def reset_domain(self): self.model().reset_variables() @@ -459,7 +480,3 @@ def __init__(self, widget): widget.contextAboutToBeOpened.connect(lambda args: self.set_orig_domain(args[0])) widget.contextOpened.connect(lambda: self.model().set_variables(self.variables)) widget.contextClosed.connect(lambda: self.model().set_variables([])) - - def set_orig_domain(self, domain): - self.variables = self.parse_domain(domain) - self.model().set_orig_variables(self.variables) \ No newline at end of file diff --git a/Orange/widgets/utils/textimport.py b/Orange/widgets/utils/textimport.py index ab8d6fb884a..e498d704f96 100644 --- a/Orange/widgets/utils/textimport.py +++ b/Orange/widgets/utils/textimport.py @@ -53,6 +53,10 @@ QApplication, QAbstractItemView, QToolTip, QStyleOption ) +from Orange.data import ( + DiscreteVariable, ContinuousVariable, StringVariable, \ + TimeVariable +) from Orange.widgets.utils import encodings from Orange.widgets.utils.overlay import OverlayWidget @@ -1449,6 +1453,18 @@ def icon_for_column_type(coltype): return icon +def columntype_for_vartype(vartype): + # type: (Variable) -> ColumnType + if vartype == DiscreteVariable: + return ColumnType.Categorical + if vartype == ContinuousVariable: + return ColumnType.Numeric + if vartype == StringVariable: + return ColumnType.Text + if vartype == TimeVariable: + return ColumnType.Time + + class SkipItemDelegate(PreviewItemDelegate): def initStyleOption(self, option, index): # type: (QStyleOptionViewItem, QModelIndex) -> None