class OWRuleViewer(widget.OWWidget): name = "CN2 Rule Viewer" description = "Review rules induced from data." icon = "icons/CN2RuleViewer.svg" priority = 1140 class Inputs: data = Input("Data", Table) classifier = Input("Classifier", _RuleClassifier) class Outputs: selected_data = Output("Selected Data", Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table) compact_view = settings.Setting(False) want_basic_layout = True want_main_area = True want_control_area = False def __init__(self): self.data = None self.classifier = None self.selected = None self.model = CustomRuleViewerTableModel(parent=self) self.model.set_horizontal_header_labels( ["IF conditions", "", "THEN class", "Distribution", "Probabilities [%]", "Quality", "Length"]) self.proxy_model = QSortFilterProxyModel(parent=self) self.proxy_model.setSourceModel(self.model) self.proxy_model.setSortRole(self.model.SortRole) self.view = gui.TableView(self, wordWrap=False) self.view.setModel(self.proxy_model) self.view.verticalHeader().setVisible(True) self.view.horizontalHeader().setStretchLastSection(False) self.view.selectionModel().selectionChanged.connect(self.commit) self.dist_item_delegate = DistributionItemDelegate(self) self.view.setItemDelegateForColumn(3, self.dist_item_delegate) self.mainArea.layout().setContentsMargins(0, 0, 0, 0) self.mainArea.layout().addWidget(self.view) bottom_box = gui.hBox(widget=self.mainArea, box=None, margin=0, spacing=0) original_order_button = QPushButton( "Restore original order", autoDefault=False) original_order_button.setFixedWidth(180) bottom_box.layout().addWidget(original_order_button) original_order_button.clicked.connect(self.restore_original_order) gui.separator(bottom_box, width=5, height=0) gui.checkBox(widget=bottom_box, master=self, value="compact_view", label="Compact view", callback=self.on_update) self.report_button.setFixedWidth(180) bottom_box.layout().addWidget(self.report_button) @Inputs.data def set_data(self, data): self.data = data self.commit() @Inputs.classifier def set_classifier(self, classifier): self.classifier = classifier self.selected = None self.model.clear() if classifier is not None and hasattr(classifier, "rule_list"): self.model.set_vertical_header_labels( list(range(len(classifier.rule_list)))) self.dist_item_delegate.color_schema = \ [QColor(*c) for c in classifier.domain.class_var.colors] self.model.wrap(self.classifier.domain, self.classifier.rule_list) self.on_update() self.commit() def on_update(self): self._save_selected() self.model.set_compact_view(self.compact_view) if self.compact_view: self.view.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Interactive) # QHeaderView.Stretch else: self.view.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) self.view.resizeColumnsToContents() self.view.resizeRowsToContents() self._restore_selected() def _save_selected(self, actual=False): self.selected = None selection_model = self.view.selectionModel() if selection_model.hasSelection(): selection = (selection_model.selection() if not actual else self.proxy_model.mapSelectionToSource( selection_model.selection())) self.selected = sorted(set(index.row() for index in selection.indexes())) def _restore_selected(self): if self.selected is not None: selection_model = self.view.selectionModel() for row in self.selected: selection_model.select(self.proxy_model.index(row, 0), selection_model.Select | selection_model.Rows) def restore_original_order(self): self.proxy_model.sort(-1) def copy_to_clipboard(self): self._save_selected(actual=True) if self.selected is not None: output = "\n".join([str(self.classifier.rule_list[i]) for i in self.selected]) QApplication.clipboard().setText(output) def commit(self): data_output = None self._save_selected(actual=True) selected_indices = [] data = self.data or self.classifier and self.classifier.instances if (self.selected is not None and data is not None and self.classifier is not None and data.domain.attributes == self.classifier.original_domain.attributes): status = np.ones(data.X.shape[0], dtype=bool) for i in self.selected: rule = self.classifier.rule_list[i] status &= rule.evaluate_data(data.X) selected_indices = status.nonzero()[0] data_output = data.from_table_rows(data, selected_indices) \ if len(selected_indices) else None self.Outputs.selected_data.send(data_output) self.Outputs.annotated_data.send(create_annotated_table(data, selected_indices)) def send_report(self): if self.classifier is not None: self.report_domain("Data domain", self.classifier.original_domain) self.report_items("Rule induction algorithm", self.classifier.params) self.report_table("Induced rules", self.view) def sizeHint(self): return QSize(800, 450)
class OWRuleViewer(widget.OWWidget): name = "CN2 Rule Viewer" description = "Review rules induced from data." icon = "icons/CN2RuleViewer.svg" priority = 1140 keywords = [] class Inputs: data = Input("Data", Table) classifier = Input("Classifier", _RuleClassifier) class Outputs: selected_data = Output("Selected Data", Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table) compact_view = settings.Setting(False) want_basic_layout = True want_main_area = True want_control_area = False def __init__(self): self.data = None self.classifier = None self.selected = None self.model = CustomRuleViewerTableModel(parent=self) self.model.set_horizontal_header_labels([ "IF conditions", "", "THEN class", "Distribution", "Probabilities [%]", "Quality", "Length" ]) self.proxy_model = QSortFilterProxyModel(parent=self) self.proxy_model.setSourceModel(self.model) self.proxy_model.setSortRole(self.model.SortRole) self.view = gui.TableView(self, wordWrap=False) self.view.setModel(self.proxy_model) self.view.verticalHeader().setVisible(True) self.view.horizontalHeader().setStretchLastSection(False) self.view.selectionModel().selectionChanged.connect(self.commit) self.dist_item_delegate = DistributionItemDelegate(self) self.view.setItemDelegateForColumn(3, self.dist_item_delegate) self.mainArea.layout().setContentsMargins(0, 0, 0, 0) self.mainArea.layout().addWidget(self.view) bottom_box = gui.hBox(widget=self.mainArea, box=None, margin=0, spacing=0) original_order_button = QPushButton("Restore original order", autoDefault=False) original_order_button.setFixedWidth(180) bottom_box.layout().addWidget(original_order_button) original_order_button.clicked.connect(self.restore_original_order) gui.separator(bottom_box, width=5, height=0) gui.checkBox(widget=bottom_box, master=self, value="compact_view", label="Compact view", callback=self.on_update) @Inputs.data def set_data(self, data): self.data = data self.commit() @Inputs.classifier def set_classifier(self, classifier): self.classifier = classifier self.selected = None self.model.clear() if classifier is not None and hasattr(classifier, "rule_list"): self.model.set_vertical_header_labels( list(range(len(classifier.rule_list)))) self.dist_item_delegate.color_schema = \ [QColor(*c) for c in classifier.domain.class_var.colors] self.model.wrap(self.classifier.domain, self.classifier.rule_list) self.on_update() self.commit() def on_update(self): self._save_selected() self.model.set_compact_view(self.compact_view) if self.compact_view: self.view.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Interactive) # QHeaderView.Stretch else: self.view.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) self.view.resizeColumnsToContents() self.view.resizeRowsToContents() self._restore_selected() def _save_selected(self, actual=False): self.selected = None selection_model = self.view.selectionModel() if selection_model.hasSelection(): if not actual: selection = selection_model.selection() else: selection = self.proxy_model.mapSelectionToSource( selection_model.selection()) self.selected = sorted( set(index.row() for index in selection.indexes())) def _restore_selected(self): if self.selected is not None: selection_model = self.view.selectionModel() for row in self.selected: selection_model.select( self.proxy_model.index(row, 0), selection_model.Select | selection_model.Rows) def restore_original_order(self): self.proxy_model.sort(-1) def copy_to_clipboard(self): self._save_selected(actual=True) if self.selected is not None: output = "\n".join( [str(self.classifier.rule_list[i]) for i in self.selected]) QApplication.clipboard().setText(output) def commit(self): data_output = None self._save_selected(actual=True) selected_indices = [] data = self.data or self.classifier and self.classifier.instances if (self.selected is not None and data is not None and self.classifier is not None and data.domain.attributes == self.classifier.original_domain.attributes): status = np.ones(data.X.shape[0], dtype=bool) for i in self.selected: rule = self.classifier.rule_list[i] status &= rule.evaluate_data(data.X) selected_indices = status.nonzero()[0] data_output = data.from_table_rows(data, selected_indices) \ if len(selected_indices) else None self.Outputs.selected_data.send(data_output) self.Outputs.annotated_data.send( create_annotated_table(data, selected_indices)) def send_report(self): if self.classifier is not None: self.report_domain("Data domain", self.classifier.original_domain) self.report_items("Rule induction algorithm", self.classifier.params) self.report_table("Induced rules", self.view) def sizeHint(self): return QSize(800, 450)
def showPopup(self): # type: () -> None """ Reimplemented from QComboBox.showPopup Popup up a customized view and filter edit line. Note ---- The .popup(), .lineEdit(), .completer() of the base class are not used. """ if self.__popup is not None: # We have user entered state that cannot be disturbed # (entered filter text, scroll offset, ...) return # pragma: no cover if self.count() == 0: return opt = QStyleOptionComboBox() self.initStyleOption(opt) popup = QListView( uniformItemSizes=True, horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff, verticalScrollBarPolicy=Qt.ScrollBarAsNeeded, iconSize=self.iconSize(), ) popup.setFocusProxy(self.__searchline) popup.setParent(self, Qt.Popup | Qt.FramelessWindowHint) popup.setItemDelegate(_ComboBoxListDelegate(popup)) proxy = QSortFilterProxyModel( popup, filterCaseSensitivity=Qt.CaseInsensitive ) proxy.setFilterKeyColumn(self.modelColumn()) proxy.setSourceModel(self.model()) popup.setModel(proxy) root = proxy.mapFromSource(self.rootModelIndex()) popup.setRootIndex(root) self.__popup = popup self.__proxy = proxy self.__searchline.setText("") self.__searchline.setPlaceholderText("Filter...") self.__searchline.setVisible(True) self.__searchline.textEdited.connect(proxy.setFilterFixedString) style = self.style() # type: QStyle popuprect_origin = style.subControlRect( QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxListBoxPopup, self ) # type: QRect popuprect_origin = QRect( self.mapToGlobal(popuprect_origin.topLeft()), popuprect_origin.size() ) editrect = style.subControlRect( QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxEditField, self ) # type: QRect self.__searchline.setGeometry(editrect) desktop = QApplication.desktop() screenrect = desktop.availableGeometry(self) # type: QRect # get the height for the view listrect = QRect() for i in range(min(proxy.rowCount(root), self.maxVisibleItems())): index = proxy.index(i, self.modelColumn(), root) if index.isValid(): listrect = listrect.united(popup.visualRect(index)) if listrect.height() >= screenrect.height(): break window = popup.window() # type: QWidget window.ensurePolished() if window.layout() is not None: window.layout().activate() else: QApplication.sendEvent(window, QEvent(QEvent.LayoutRequest)) margins = qwidget_margin_within(popup.viewport(), window) height = (listrect.height() + 2 * popup.spacing() + margins.top() + margins.bottom()) popup_size = (QSize(popuprect_origin.width(), height) .expandedTo(window.minimumSize()) .boundedTo(window.maximumSize()) .boundedTo(screenrect.size())) popuprect = QRect(popuprect_origin.bottomLeft(), popup_size) popuprect = dropdown_popup_geometry( popuprect, popuprect_origin, screenrect) popup.setGeometry(popuprect) current = proxy.mapFromSource( self.model().index(self.currentIndex(), self.modelColumn(), self.rootModelIndex())) popup.setCurrentIndex(current) popup.scrollTo(current, QAbstractItemView.EnsureVisible) popup.show() popup.setFocus(Qt.PopupFocusReason) popup.installEventFilter(self) popup.viewport().installEventFilter(self) popup.viewport().setMouseTracking(True) self.update() self.__popupTimer.restart()
def showPopup(self): # type: () -> None """ Reimplemented from QComboBox.showPopup Popup up a customized view and filter edit line. Note ---- The .popup(), .lineEdit(), .completer() of the base class are not used. """ if self.__popup is not None: # We have user entered state that cannot be disturbed # (entered filter text, scroll offset, ...) return # pragma: no cover if self.count() == 0: return opt = QStyleOptionComboBox() self.initStyleOption(opt) popup = QListView( uniformItemSizes=True, horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff, verticalScrollBarPolicy=Qt.ScrollBarAsNeeded, iconSize=self.iconSize(), ) popup.setFocusProxy(self.__searchline) popup.setParent(self, Qt.Popup | Qt.FramelessWindowHint) popup.setItemDelegate(_ComboBoxListDelegate(popup)) proxy = QSortFilterProxyModel( popup, filterCaseSensitivity=Qt.CaseInsensitive ) proxy.setFilterKeyColumn(self.modelColumn()) proxy.setSourceModel(self.model()) popup.setModel(proxy) root = proxy.mapFromSource(self.rootModelIndex()) popup.setRootIndex(root) self.__popup = popup self.__proxy = proxy self.__searchline.setText("") self.__searchline.setPlaceholderText("Filter...") self.__searchline.setVisible(True) self.__searchline.textEdited.connect(proxy.setFilterFixedString) style = self.style() # type: QStyle popuprect_origin = style.subControlRect( QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxListBoxPopup, self ) # type: QRect popuprect_origin = QRect( self.mapToGlobal(popuprect_origin.topLeft()), popuprect_origin.size() ) editrect = style.subControlRect( QStyle.CC_ComboBox, opt, QStyle.SC_ComboBoxEditField, self ) # type: QRect self.__searchline.setGeometry(editrect) desktop = QApplication.desktop() screenrect = desktop.availableGeometry(self) # type: QRect # get the height for the view listrect = QRect() for i in range(min(proxy.rowCount(root), self.maxVisibleItems())): index = proxy.index(i, self.modelColumn(), root) if index.isValid(): listrect = listrect.united(popup.visualRect(index)) if listrect.height() >= screenrect.height(): break window = popup.window() # type: QWidget window.ensurePolished() if window.layout() is not None: window.layout().activate() else: QApplication.sendEvent(window, QEvent(QEvent.LayoutRequest)) margins = qwidget_margin_within(popup.viewport(), window) height = (listrect.height() + 2 * popup.spacing() + margins.top() + margins.bottom()) popup_size = (QSize(popuprect_origin.width(), height) .expandedTo(window.minimumSize()) .boundedTo(window.maximumSize()) .boundedTo(screenrect.size())) popuprect = QRect(popuprect_origin.bottomLeft(), popup_size) popuprect = dropdown_popup_geometry( popuprect, popuprect_origin, screenrect) popup.setGeometry(popuprect) current = proxy.mapFromSource( self.model().index(self.currentIndex(), self.modelColumn(), self.rootModelIndex())) popup.setCurrentIndex(current) popup.scrollTo(current, QAbstractItemView.EnsureVisible) popup.show() popup.setFocus(Qt.PopupFocusReason) popup.installEventFilter(self) popup.viewport().installEventFilter(self) popup.viewport().setMouseTracking(True) self.update() self.__popupTimer.restart()
class OWRuleViewer(widget.OWWidget): name = "CN2规则查看器(CN2 Rule Viewer)" description = "查看由数据引发的规则。" icon = "icons/CN2RuleViewer.svg" priority = 1140 keywords = ["cn2guize", "guize"] category = "可视化(Visualize)" class Inputs: data = Input("数据(Data)", Table, replaces=["Data"]) classifier = Input("分类器(Classifier)", _RuleClassifier, replaces=["Classifier"]) class Outputs: selected_data = Output("选定的数据(Selected Data)", Table, default=True, replaces=["Selected Data"]) annotated_data = Output("数据(Data)", Table, replaces=["Data"]) compact_view = settings.Setting(False) want_basic_layout = True want_main_area = False want_control_area = True def __init__(self): super().__init__() self.data = None self.classifier = None self.selected = None self.model = CustomRuleViewerTableModel(parent=self) self.model.set_horizontal_header_labels( ["如果条件", "", "然后类别", "分布", "概率 [%]", "质量", "长度"]) self.proxy_model = QSortFilterProxyModel(parent=self) self.proxy_model.setSourceModel(self.model) self.proxy_model.setSortRole(self.model.SortRole) self.view = gui.TableView(self, wordWrap=False) self.view.setModel(self.proxy_model) self.view.verticalHeader().setVisible(True) self.view.horizontalHeader().setStretchLastSection(False) self.view.selectionModel().selectionChanged.connect(self.commit) self.dist_item_delegate = DistributionItemDelegate(self) self.view.setItemDelegateForColumn(3, self.dist_item_delegate) self.controlArea.layout().addWidget(self.view) gui.checkBox( widget=self.buttonsArea, master=self, value="compact_view", label="紧凑型视图", callback=self.on_update, ) gui.rubber(self.buttonsArea) original_order_button = gui.button( self.buttonsArea, self, "恢复原始顺序", autoDefault=False, callback=self.restore_original_order, attribute=Qt.WA_LayoutUsesWidgetRect, ) original_order_button.clicked.connect(self.restore_original_order) @Inputs.data def set_data(self, data): self.data = data self.commit() @Inputs.classifier def set_classifier(self, classifier): self.classifier = classifier self.selected = None self.model.clear() if classifier is not None and hasattr(classifier, "rule_list"): self.model.set_vertical_header_labels( list(range(len(classifier.rule_list)))) self.dist_item_delegate.color_schema = [ QColor(*c) for c in classifier.domain.class_var.colors ] self.model.wrap(self.classifier.domain, self.classifier.rule_list) self.on_update() self.commit() def on_update(self): self._save_selected() self.model.set_compact_view(self.compact_view) if self.compact_view: self.view.horizontalHeader().setSectionResizeMode( 0, QHeaderView.Interactive) # QHeaderView.Stretch else: self.view.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) self.view.resizeColumnsToContents() self.view.resizeRowsToContents() self._restore_selected() def _save_selected(self, actual=False): self.selected = None selection_model = self.view.selectionModel() if selection_model.hasSelection(): if not actual: selection = selection_model.selection() else: selection = self.proxy_model.mapSelectionToSource( selection_model.selection()) self.selected = sorted( set(index.row() for index in selection.indexes())) def _restore_selected(self): if self.selected is not None: selection_model = self.view.selectionModel() for row in self.selected: selection_model.select( self.proxy_model.index(row, 0), selection_model.Select | selection_model.Rows, ) def restore_original_order(self): self.proxy_model.sort(-1) def copy_to_clipboard(self): self._save_selected(actual=True) if self.selected is not None: output = "\n".join( [str(self.classifier.rule_list[i]) for i in self.selected]) QApplication.clipboard().setText(output) def commit(self): data_output = None self._save_selected(actual=True) selected_indices = [] data = self.data or self.classifier and self.classifier.instances if (self.selected is not None and data is not None and self.classifier is not None and data.domain.attributes == self.classifier.original_domain.attributes): status = np.ones(data.X.shape[0], dtype=bool) for i in self.selected: rule = self.classifier.rule_list[i] status &= rule.evaluate_data(data.X) selected_indices = status.nonzero()[0] data_output = (data.from_table_rows(data, selected_indices) if len(selected_indices) else None) self.Outputs.selected_data.send(data_output) self.Outputs.annotated_data.send( create_annotated_table(data, selected_indices)) def send_report(self): if self.classifier is not None: self.report_domain("Data domain", self.classifier.original_domain) self.report_items("Rule induction algorithm", self.classifier.params) self.report_table("Induced rules", self.view) def sizeHint(self): return QSize(800, 450)