def __init__(self, etype, key, value=None, oldValue=None): """ Initialize the event instance """ QEvent.__init__(self, etype) self.__key = key self.__value = value self.__oldValue = oldValue
def eventFilter(self, recv: QObject, event: QEvent) -> bool: if event.type() == QEvent.LayoutRequest \ and recv is self.__centralWidget: self._layout() return super().eventFilter(recv, event)
def changeEvent(self, event: QEvent) -> None: if event.type() == QEvent.StyleChange: self.styleChange.emit() super().changeEvent(event)
def eventFilter(self, obj: Union[QSlider, QDoubleSpinBox], event: QEvent) \ -> bool: if event.type() == QEvent.Wheel: return True return super().eventFilter(obj, event)
class OWRadviz(widget.OWWidget): name = "Radviz" description = "Radviz" icon = "icons/Radviz.svg" priority = 240 class Inputs: data = Input("Data", Table, default=True) data_subset = Input("Data Subset", Table) class Outputs: selected_data = Output("Selected Data", Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table) components = Output("Components", Table) settings_version = 1 settingsHandler = settings.DomainContextHandler() variable_state = settings.ContextSetting({}) auto_commit = settings.Setting(True) graph = settings.SettingProvider(OWRadvizGraph) vizrank = settings.SettingProvider(RadvizVizRank) jitter_sizes = [0, 0.1, 0.5, 1.0, 2.0] ReplotRequest = QEvent.registerEventType() graph_name = "graph.plot_widget.plotItem" class Information(widget.OWWidget.Information): sql_sampled_data = widget.Msg("Data has been sampled") class Warning(widget.OWWidget.Warning): no_features = widget.Msg("At least 2 features have to be chosen") class Error(widget.OWWidget.Error): sparse_data = widget.Msg("Sparse data is not supported") no_features = widget.Msg("At least 3 numeric or categorical variables are required") no_instances = widget.Msg("At least 2 data instances are required") def __init__(self): super().__init__() self.data = None self.subset_data = None self._subset_mask = None self._selection = None # np.array self.__replot_requested = False self._new_plotdata() self.variable_x = ContinuousVariable("radviz-x") self.variable_y = ContinuousVariable("radviz-y") box = gui.vBox(self.mainArea, True, margin=0) self.graph = OWRadvizGraph(self, box, "Plot", view_box=RadvizInteractiveViewBox) self.graph.hide_axes() box.layout().addWidget(self.graph.plot_widget) plot = self.graph.plot_widget SIZE_POLICY = (QSizePolicy.Minimum, QSizePolicy.Maximum) self.variables_selection = VariablesSelection() self.model_selected = VariableListModel(enable_dnd=True) self.model_other = VariableListModel(enable_dnd=True) self.variables_selection(self, self.model_selected, self.model_other) self.vizrank, self.btn_vizrank = RadvizVizRank.add_vizrank( self.controlArea, self, "Suggest features", self.vizrank_set_attrs) self.btn_vizrank.setSizePolicy(*SIZE_POLICY) self.variables_selection.add_remove.layout().addWidget(self.btn_vizrank) self.viewbox = plot.getViewBox() self.replot = None g = self.graph.gui pp_box = g.point_properties_box(self.controlArea) pp_box.setSizePolicy(*SIZE_POLICY) self.models = g.points_models box = gui.vBox(self.controlArea, "Plot Properties") box.setSizePolicy(*SIZE_POLICY) g.add_widget(g.JitterSizeSlider, box) g.add_widgets([g.ShowLegend, g.ClassDensity, g.LabelOnlySelected], box) zoom_select = self.graph.box_zoom_select(self.controlArea) zoom_select.setSizePolicy(*SIZE_POLICY) self.icons = gui.attributeIconDict p = self.graph.plot_widget.palette() self.graph.set_palette(p) gui.auto_commit(self.controlArea, self, "auto_commit", "Send Selection", auto_label="Send Automatically") self.graph.zoom_actions(self) self._circle = QGraphicsEllipseItem() self._circle.setRect(QRectF(-1., -1., 2., 2.)) self._circle.setPen(pg.mkPen(QColor(0, 0, 0), width=2)) def resizeEvent(self, event): self._update_points_labels() def keyPressEvent(self, event): super().keyPressEvent(event) self.graph.update_tooltip(event.modifiers()) def keyReleaseEvent(self, event): super().keyReleaseEvent(event) self.graph.update_tooltip(event.modifiers()) def vizrank_set_attrs(self, attrs): if not attrs: return self.variables_selection.display_none() self.model_selected[:] = attrs[:] self.model_other[:] = [v for v in self.model_other if v not in attrs] def _new_plotdata(self): self.plotdata = namespace( valid_mask=None, embedding_coords=None, points=None, arcarrows=[], point_labels=[], rand=None, data=None, ) def update_colors(self): self._vizrank_color_change() self.cb_class_density.setEnabled(self.graph.can_draw_density()) def sizeHint(self): return QSize(800, 500) def clear(self): """ Clear/reset the widget state """ self.data = None self.model_selected.clear() self.model_other.clear() self._clear_plot() def _clear_plot(self): self._new_plotdata() self.graph.plot_widget.clear() def invalidate_plot(self): """ Schedule a delayed replot. """ if not self.__replot_requested: self.__replot_requested = True QApplication.postEvent(self, QEvent(self.ReplotRequest), Qt.LowEventPriority - 10) def init_attr_values(self): domain = self.data and len(self.data) and self.data.domain or None for model in self.models: model.set_domain(domain) self.graph.attr_color = self.data.domain.class_var if domain else None self.graph.attr_shape = None self.graph.attr_size = None self.graph.attr_label = None def _vizrank_color_change(self): attr_color = self.graph.attr_color is_enabled = self.data is not None and not self.data.is_sparse() and \ (len(self.model_other) + len(self.model_selected)) > 3 and len(self.data) > 1 self.btn_vizrank.setEnabled( is_enabled and attr_color is not None and not np.isnan(self.data.get_column_view(attr_color)[0].astype(float)).all()) self.vizrank.initialize() @Inputs.data def set_data(self, data): """ Set the input dataset and check if data is valid. Args: data (Orange.data.table): data instances """ def sql(data): self.Information.sql_sampled_data.clear() if isinstance(data, SqlTable): if data.approx_len() < 4000: data = Table(data) else: self.Information.sql_sampled_data() data_sample = data.sample_time(1, no_cache=True) data_sample.download_data(2000, partial=True) data = Table(data_sample) return data def settings(data): # get the default encoded state, replacing the position with Inf state = VariablesSelection.encode_var_state( [list(self.model_selected), list(self.model_other)] ) state = {key: (source_ind, np.inf) for key, (source_ind, _) in state.items()} self.openContext(data.domain) selected_keys = [key for key, (sind, _) in self.variable_state.items() if sind == 0] if set(selected_keys).issubset(set(state.keys())): pass # update the defaults state (the encoded state must contain # all variables in the input domain) state.update(self.variable_state) # ... and restore it with saved positions taking precedence over # the defaults selected, other = VariablesSelection.decode_var_state( state, [list(self.model_selected), list(self.model_other)]) return selected, other def is_sparse(data): if data.is_sparse(): self.Error.sparse_data() data = None return data def are_features(data): domain = data.domain vars = [var for var in chain(domain.class_vars, domain.metas, domain.attributes) if var.is_primitive()] if len(vars) < 3: self.Error.no_features() data = None return data def are_instances(data): if len(data) < 2: self.Error.no_instances() data = None return data self.clear_messages() self.btn_vizrank.setEnabled(False) self.closeContext() self.clear() self.information() self.Error.clear() for f in [sql, is_sparse, are_features, are_instances]: if data is None: break data = f(data) if data is not None: self.data = data self.init_attr_values() domain = data.domain vars = [v for v in chain(domain.metas, domain.attributes) if v.is_primitive()] self.model_selected[:] = vars[:5] self.model_other[:] = vars[5:] + list(domain.class_vars) self.model_selected[:], self.model_other[:] = settings(data) self._selection = np.zeros(len(data), dtype=np.uint8) self.invalidate_plot() else: self.data = None @Inputs.data_subset def set_subset_data(self, subset): """ Set the supplementary input subset dataset. Args: subset (Orange.data.table): subset of data instances """ self.subset_data = subset self._subset_mask = None self.controls.graph.alpha_value.setEnabled(subset is None) def handleNewSignals(self): if self.data is not None: self._clear_plot() if self.subset_data is not None and self._subset_mask is None: dataids = self.data.ids.ravel() subsetids = np.unique(self.subset_data.ids) self._subset_mask = np.in1d( dataids, subsetids, assume_unique=True) self.setup_plot(reset_view=True) self.cb_class_density.setEnabled(self.graph.can_draw_density()) else: self.init_attr_values() self.graph.new_data(None) self._vizrank_color_change() self.commit() def customEvent(self, event): if event.type() == OWRadviz.ReplotRequest: self.__replot_requested = False self._clear_plot() self.setup_plot(reset_view=True) else: super().customEvent(event) def closeContext(self): self.variable_state = VariablesSelection.encode_var_state( [list(self.model_selected), list(self.model_other)] ) super().closeContext() def prepare_radviz_data(self, variables): ec, points, valid_mask = radviz(self.data, variables, self.plotdata.points) self.plotdata.embedding_coords = ec self.plotdata.points = points self.plotdata.valid_mask = valid_mask def setup_plot(self, reset_view=True): if self.data is None: return self.graph.jitter_continuous = True self.__replot_requested = False variables = list(self.model_selected) if len(variables) < 2: self.Warning.no_features() self.graph.new_data(None) return self.Warning.clear() self.prepare_radviz_data(variables) if self.plotdata.embedding_coords is None: return domain = self.data.domain new_metas = domain.metas + (self.variable_x, self.variable_y) domain = Domain(attributes=domain.attributes, class_vars=domain.class_vars, metas=new_metas) mask = self.plotdata.valid_mask array = np.zeros((len(self.data), 2), dtype=np.float) array[mask] = self.plotdata.embedding_coords data = Table.from_numpy( domain, X=self.data.X, Y=self.data.Y, metas=np.hstack((self.data.metas, array))) subset_data = data[self._subset_mask & mask]\ if self._subset_mask is not None and len(self._subset_mask) else None self.plotdata.data = data self.graph.new_data(data[mask], subset_data) if self._selection is not None: self.graph.selection = self._selection[self.plotdata.valid_mask] self.graph.update_data(self.variable_x, self.variable_y, reset_view=reset_view) self.graph.plot_widget.addItem(self._circle) self.graph.scatterplot_points = ScatterPlotItem( x=self.plotdata.points[:, 0], y=self.plotdata.points[:, 1] ) self._update_points_labels() self.graph.plot_widget.addItem(self.graph.scatterplot_points) def randomize_indices(self): ec = self.plotdata.embedding_coords self.plotdata.rand = np.random.choice(len(ec), MAX_POINTS, replace=False) \ if len(ec) > MAX_POINTS else None def manual_move(self): self.__replot_requested = False if self.plotdata.rand is not None: rand = self.plotdata.rand valid_mask = self.plotdata.valid_mask data = self.data[valid_mask] selection = self._selection[valid_mask] selection = selection[rand] ec, _, valid_mask = radviz(data, list(self.model_selected), self.plotdata.points) assert sum(valid_mask) == len(data) data = data[rand] ec = ec[rand] data_x = data.X data_y = data.Y data_metas = data.metas else: self.prepare_radviz_data(list(self.model_selected)) ec = self.plotdata.embedding_coords valid_mask = self.plotdata.valid_mask data_x = self.data.X[valid_mask] data_y = self.data.Y[valid_mask] data_metas = self.data.metas[valid_mask] selection = self._selection[valid_mask] attributes = (self.variable_x, self.variable_y) + self.data.domain.attributes domain = Domain(attributes=attributes, class_vars=self.data.domain.class_vars, metas=self.data.domain.metas) data = Table.from_numpy(domain, X=np.hstack((ec, data_x)), Y=data_y, metas=data_metas) self.graph.new_data(data, None) self.graph.selection = selection self.graph.update_data(self.variable_x, self.variable_y, reset_view=True) self.graph.plot_widget.addItem(self._circle) self.graph.scatterplot_points = ScatterPlotItem( x=self.plotdata.points[:, 0], y=self.plotdata.points[:, 1]) self._update_points_labels() self.graph.plot_widget.addItem(self.graph.scatterplot_points) def _update_points_labels(self): if self.plotdata.points is None: return for point_label in self.plotdata.point_labels: self.graph.plot_widget.removeItem(point_label) self.plotdata.point_labels = [] sx, sy = self.graph.view_box.viewPixelSize() for row in self.plotdata.points: ti = TextItem() metrics = QFontMetrics(ti.textItem.font()) text_width = ((RANGE.width())/2. - np.abs(row[0])) / sx name = row[2].name ti.setText(name) ti.setTextWidth(text_width) ti.setColor(QColor(0, 0, 0)) br = ti.boundingRect() width = metrics.width(name) if metrics.width(name) < br.width() else br.width() width = sx * (width + 5) height = sy * br.height() ti.setPos(row[0] - (row[0] < 0) * width, row[1] + (row[1] > 0) * height) self.plotdata.point_labels.append(ti) self.graph.plot_widget.addItem(ti) def _update_jitter(self): self.invalidate_plot() def reset_graph_data(self, *_): if self.data is not None: self.graph.rescale_data() self._update_graph() def _update_graph(self, reset_view=True, **_): self.graph.zoomStack = [] if self.graph.data is None: return self.graph.update_data(self.variable_x, self.variable_y, reset_view=reset_view) def update_density(self): self._update_graph(reset_view=True) def selection_changed(self): if self.graph.selection is not None: self._selection[self.plotdata.valid_mask] = self.graph.selection self.commit() def prepare_data(self): pass def commit(self): selected = annotated = components = None graph = self.graph if self.plotdata.data is not None: name = self.data.name data = self.plotdata.data mask = self.plotdata.valid_mask.astype(int) mask[mask == 1] = graph.selection if graph.selection is not None \ else [False * len(mask)] selection = np.array([], dtype=np.uint8) if mask is None else np.flatnonzero(mask) if len(selection): selected = data[selection] selected.name = name + ": selected" selected.attributes = self.data.attributes if graph.selection is not None and np.max(graph.selection) > 1: annotated = create_groups_table(data, mask) else: annotated = create_annotated_table(data, selection) annotated.attributes = self.data.attributes annotated.name = name + ": annotated" comp_domain = Domain( self.plotdata.points[:, 2], metas=[StringVariable(name='component')]) metas = np.array([["RX"], ["RY"], ["angle"]]) angle = np.arctan2(np.array(self.plotdata.points[:, 1].T, dtype=float), np.array(self.plotdata.points[:, 0].T, dtype=float)) components = Table.from_numpy( comp_domain, X=np.row_stack((self.plotdata.points[:, :2].T, angle)), metas=metas) components.name = name + ": components" self.Outputs.selected_data.send(selected) self.Outputs.annotated_data.send(annotated) self.Outputs.components.send(components) def send_report(self): if self.data is None: return def name(var): return var and var.name caption = report.render_items_vert(( ("Color", name(self.graph.attr_color)), ("Label", name(self.graph.attr_label)), ("Shape", name(self.graph.attr_shape)), ("Size", name(self.graph.attr_size)), ("Jittering", self.graph.jitter_size != 0 and "{} %".format(self.graph.jitter_size)))) self.report_plot() if caption: self.report_caption(caption)
def __init__(self, state): QEvent.__init__(self, StateChangedEvent.StateChanged) self._state = state
class FutureWatcher(QObject): """ An `QObject` watching the state changes of a `concurrent.futures.Future` Note ---- The state change notification signals (`done`, `finished`, ...) are always emitted when the control flow reaches the event loop (even if the future is already completed when set). Note ---- An event loop must be running, otherwise the notifier signals will not be emitted. Parameters ---------- parent : QObject Parent object. future : Future The future instance to watch. Example ------- >>> app = QCoreApplication.instance() or QCoreApplication([]) >>> f = submit(lambda i, j: i ** j, 10, 3) >>> watcher = FutureWatcher(f) >>> watcher.resultReady.connect(lambda res: print("Result:", res)) >>> watcher.done.connect(app.quit) >>> _ = app.exec() Result: 1000 >>> f.result() 1000 """ #: Signal emitted when the future is done (cancelled or finished) done = Signal(Future) #: Signal emitted when the future is finished (i.e. returned a result #: or raised an exception - but not if cancelled) finished = Signal(Future) #: Signal emitted when the future was cancelled cancelled = Signal(Future) #: Signal emitted with the future's result when successfully finished. resultReady = Signal(object) #: Signal emitted with the future's exception when finished with an #: exception. exceptionReady = Signal(BaseException) # A private event type used to notify the watcher of a Future's completion __FutureDone = QEvent.registerEventType() def __init__(self, future=None, parent=None, **kwargs): super().__init__(parent, **kwargs) self.__future = None if future is not None: self.setFuture(future) def setFuture(self, future): # type: (Future) -> None """ Set the future to watch. Raise a `RuntimeError` if a future is already set. Parameters ---------- future : Future """ if self.__future is not None: raise RuntimeError("Future already set") self.__future = future selfweakref = weakref.ref(self) def on_done(f): assert f is future selfref = selfweakref() if selfref is None: return try: QCoreApplication.postEvent(selfref, QEvent(FutureWatcher.__FutureDone)) except RuntimeError: # Ignore RuntimeErrors (when C++ side of QObject is deleted) # (? Use QObject.destroyed and remove the done callback ?) pass future.add_done_callback(on_done) def future(self): # type: () -> Future """ Return the future instance. """ return self.__future def isCancelled(self): warnings.warn("isCancelled is deprecated", DeprecationWarning, stacklevel=2) return self.__future.cancelled() def isDone(self): warnings.warn("isDone is deprecated", DeprecationWarning, stacklevel=2) return self.__future.done() def result(self): # type: () -> Any """ Return the future's result. Note ---- This method is non-blocking. If the future has not yet completed it will raise an error. """ try: return self.__future.result(timeout=0) except TimeoutError: raise RuntimeError("Future is not yet done") def exception(self): # type: () -> Optional[BaseException] """ Return the future's exception. Note ---- This method is non-blocking. If the future has not yet completed it will raise an error. """ try: return self.__future.exception(timeout=0) except TimeoutError: raise RuntimeError("Future is not yet done") def __emitSignals(self): assert self.__future is not None assert self.__future.done() if self.__future.cancelled(): self.cancelled.emit(self.__future) self.done.emit(self.__future) elif self.__future.done(): self.finished.emit(self.__future) self.done.emit(self.__future) if self.__future.exception(): self.exceptionReady.emit(self.__future.exception()) else: self.resultReady.emit(self.__future.result()) else: assert False def customEvent(self, event): # Reimplemented. if event.type() == FutureWatcher.__FutureDone: self.__emitSignals() super().customEvent(event)
class RuntimeEvent(QEvent): Init = QEvent.registerEventType()
def _invalidate(self): self.__invalidated = True QApplication.postEvent(self, QEvent(self.Invalidate))
def event(self, event: QEvent) -> bool: if event.type() == QEvent.LayoutRequest: self.__layout() elif event.type() == QEvent.ContentsRectChange: self.__layout() return super().event(event)
def __scheduleLayout(self): if not self.__reflowPending: self.__reflowPending = True QApplication.postEvent(self, QEvent(QEvent.LayoutRequest), Qt.HighEventPriority)
class OWCorrespondenceAnalysis(widget.OWWidget): name = "Correspondence Analysis" description = "Correspondence analysis for categorical multivariate data." icon = "icons/CorrespondenceAnalysis.svg" keywords = [] class Inputs: data = Input("Data", Table) class Outputs: coordinates = Output("Coordinates", Table) Invalidate = QEvent.registerEventType() settingsHandler = settings.DomainContextHandler() selected_var_indices = settings.ContextSetting([]) auto_commit = Setting(True) graph_name = "plot.plotItem" class Error(widget.OWWidget.Error): empty_data = widget.Msg("Empty dataset") no_disc_vars = widget.Msg("No categorical data") def __init__(self): super().__init__() self.data = None self.component_x = 0 self.component_y = 1 self._set_input_summary(None) self._set_output_summary(None) box = gui.vBox(self.controlArea, "Variables") self.varlist = itemmodels.VariableListModel() self.varview = view = ListViewSearch( selectionMode=QListView.MultiSelection, uniformItemSizes=True ) view.setModel(self.varlist) view.selectionModel().selectionChanged.connect(self._var_changed) box.layout().addWidget(view) axes_box = gui.vBox(self.controlArea, "Axes") self.axis_x_cb = gui.comboBox( axes_box, self, "component_x", label="X:", callback=self._component_changed, orientation=Qt.Horizontal, sizePolicy=(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) ) self.axis_y_cb = gui.comboBox( axes_box, self, "component_y", label="Y:", callback=self._component_changed, orientation=Qt.Horizontal, sizePolicy=(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) ) self.infotext = gui.widgetLabel( gui.vBox(self.controlArea, "Contribution to Inertia"), "\n" ) gui.auto_send(self.buttonsArea, self, "auto_commit") self.plot = pg.PlotWidget(background="w") self.plot.setMenuEnabled(False) self.mainArea.layout().addWidget(self.plot) @Inputs.data def set_data(self, data): self.closeContext() self.clear() self.Error.clear() if data is not None and not len(data): self.Error.empty_data() data = None self.data = data self._set_input_summary(self.data) if data is not None: self.varlist[:] = [var for var in data.domain.variables if var.is_discrete] if not len(self.varlist[:]): self.Error.no_disc_vars() self.data = None else: self.selected_var_indices = [0, 1][:len(self.varlist)] # This widget's update flow is broken in many ways, starting # from using context domain handler without having any valid # context settings. Getting rid of these warnings would require # rewriting large portins; @ales-erjavec is doing it and will # finish it eventually, so let us these warnings are # uninformative and would better be silenced. with warnings.catch_warnings(): warnings.filterwarnings( "ignore", "combo box 'component_[xy]' .*", UserWarning) self.component_x = 0 self.component_y = int(len(self.varlist[self.selected_var_indices[-1]].values) > 1) self.openContext(data) self._restore_selection() self._update_CA() def commit(self): output_table = None if self.ca is not None: sel_vars = self.selected_vars() if len(sel_vars) == 2: rf = np.vstack((self.ca.row_factors, self.ca.col_factors)) else: rf = self.ca.row_factors vars_data = [(val.name, var) for val in sel_vars for var in val.values] output_table = Table( Domain([ContinuousVariable(f"Component {i + 1}") for i in range(rf.shape[1])], metas=[StringVariable("Variable"), StringVariable("Value")]), rf, metas=vars_data ) self._set_output_summary(output_table) self.Outputs.coordinates.send(output_table) def clear(self): self.data = None self.ca = None self.plot.clear() self.varlist[:] = [] def _set_input_summary(self, data): summary = len(data) if data else self.info.NoInput details = format_summary_details(data) if data else "" self.info.set_input_summary(summary, details) def _set_output_summary(self, output): summary = len(output) if output else self.info.NoOutput details = format_summary_details(output) if output else "" self.info.set_output_summary(summary, details) def selected_vars(self): rows = sorted( ind.row() for ind in self.varview.selectionModel().selectedRows()) return [self.varlist[i] for i in rows] def _restore_selection(self): def restore(view, indices): with itemmodels.signal_blocking(view.selectionModel()): select_rows(view, indices) restore(self.varview, self.selected_var_indices) def _p_axes(self): return self.component_x, self.component_y def _var_changed(self): self.selected_var_indices = sorted( ind.row() for ind in self.varview.selectionModel().selectedRows()) rfs = self.update_XY() if rfs is not None: if self.component_x >= rfs: self.component_x = rfs-1 if self.component_y >= rfs: self.component_y = rfs-1 self._invalidate() def _component_changed(self): if self.ca is not None: self._setup_plot() self._update_info() def _invalidate(self): self.__invalidated = True QApplication.postEvent(self, QEvent(self.Invalidate)) def customEvent(self, event): if event.type() == self.Invalidate: self.ca = None self.plot.clear() self._update_CA() return return super().customEvent(event) def _update_CA(self): self.update_XY() # See the comment about catch_warnings above. with warnings.catch_warnings(): warnings.filterwarnings( "ignore", "combo box 'component_[xy]' .*", UserWarning) self.component_x, self.component_y = \ self.component_x, self.component_y self._setup_plot() self._update_info() self.commit() def update_XY(self): self.axis_x_cb.clear() self.axis_y_cb.clear() ca_vars = self.selected_vars() if len(ca_vars) == 0: return multi = len(ca_vars) != 2 if multi: _, ctable = burt_table(self.data, ca_vars) else: ctable = contingency.get_contingency(self.data, *ca_vars[::-1]) self.ca = correspondence(ctable, ) rfs = self.ca.row_factors.shape[1] axes = ["{}".format(i + 1) for i in range(rfs)] self.axis_x_cb.addItems(axes) self.axis_y_cb.addItems(axes) return rfs def _setup_plot(self): def get_minmax(points): minmax = [float('inf'), float('-inf'), float('inf'), float('-inf')] for pp in points: for p in pp: minmax[0] = min(p[0], minmax[0]) minmax[1] = max(p[0], minmax[1]) minmax[2] = min(p[1], minmax[2]) minmax[3] = max(p[1], minmax[3]) return minmax self.plot.clear() points = self.ca variables = self.selected_vars() colors = colorpalettes.LimitedDiscretePalette(len(variables)) p_axes = self._p_axes() if points is None: return if len(variables) == 2: row_points = self.ca.row_factors[:, p_axes] col_points = self.ca.col_factors[:, p_axes] points = [row_points, col_points] else: points = self.ca.row_factors[:, p_axes] counts = [len(var.values) for var in variables] range_indices = np.cumsum([0] + counts) ranges = zip(range_indices, range_indices[1:]) points = [points[s:e] for s, e in ranges] minmax = get_minmax(points) margin = abs(minmax[0] - minmax[1]) margin = margin * 0.05 if margin > 1e-10 else 1 self.plot.setXRange(minmax[0] - margin, minmax[1] + margin) margin = abs(minmax[2] - minmax[3]) margin = margin * 0.05 if margin > 1e-10 else 1 self.plot.setYRange(minmax[2] - margin, minmax[3] + margin) for i, (v, points) in enumerate(zip(variables, points)): color_outline = colors[i] color_outline.setAlpha(200) color = QColor(color_outline) color.setAlpha(120) item = ScatterPlotItem( x=points[:, 0], y=points[:, 1], brush=QBrush(color), pen=pg.mkPen(color_outline.darker(120), width=1.5), size=np.full((points.shape[0],), 10.1), ) self.plot.addItem(item) for name, point in zip(v.values, points): item = pg.TextItem(name, anchor=(0.5, 0)) self.plot.addItem(item) item.setPos(point[0], point[1]) inertia = self.ca.inertia_of_axis() if np.sum(inertia) == 0: inertia = 100 * inertia else: inertia = 100 * inertia / np.sum(inertia) ax = self.plot.getAxis("bottom") ax.setLabel("Component {} ({:.1f}%)" .format(p_axes[0] + 1, inertia[p_axes[0]])) ax = self.plot.getAxis("left") ax.setLabel("Component {} ({:.1f}%)" .format(p_axes[1] + 1, inertia[p_axes[1]])) def _update_info(self): if self.ca is None: self.infotext.setText("\n\n") else: fmt = ("Axis 1: {:.2f}\n" "Axis 2: {:.2f}") inertia = self.ca.inertia_of_axis() if np.sum(inertia) == 0: inertia = 100 * inertia else: inertia = 100 * inertia / np.sum(inertia) ax1, ax2 = self._p_axes() self.infotext.setText(fmt.format(inertia[ax1], inertia[ax2])) def send_report(self): if self.data is None: return vars = self.selected_vars() if not vars: return items = OrderedDict() items["Data instances"] = len(self.data) if len(vars) == 1: items["Selected variable"] = vars[0] else: items["Selected variables"] = "{} and {}".format( ", ".join(var.name for var in vars[:-1]), vars[-1].name) self.report_items(items) self.report_plot()
def commit(self): if not self._invalidated: self._invalidated = True QApplication.postEvent(self, QEvent(QEvent.User))
class OWFreeViz(widget.OWWidget): name = "FreeViz" description = "Displays FreeViz projection" icon = "icons/Freeviz.svg" priority = 240 class Inputs: data = Input("Data", Table, default=True) data_subset = Input("Data Subset", Table) class Outputs: selected_data = Output("Selected Data", Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table) components = Output("Components", Table) #: Initialization type Circular, Random = 0, 1 jitter_sizes = [0, 0.1, 0.5, 1, 2] settings_version = 2 settingsHandler = settings.DomainContextHandler() radius = settings.Setting(0) initialization = settings.Setting(Circular) auto_commit = settings.Setting(True) resolution = 256 graph = settings.SettingProvider(OWFreeVizGraph) ReplotRequest = QEvent.registerEventType() graph_name = "graph.plot_widget.plotItem" class Warning(widget.OWWidget.Warning): sparse_not_supported = widget.Msg("Sparse data is ignored.") class Error(widget.OWWidget.Error): no_class_var = widget.Msg("Need a class variable") not_enough_class_vars = widget.Msg("Needs discrete class variable " \ "with at lest 2 values") features_exceeds_instances = widget.Msg("Algorithm should not be used when " \ "number of features exceeds the number " \ "of instances.") too_many_data_instances = widget.Msg("Cannot handle so large data.") no_valid_data = widget.Msg("No valid data.") def __init__(self): super().__init__() self.data = None self.subset_data = None self._subset_mask = None self._validmask = None self._X = None self._Y = None self._selection = None self.__replot_requested = False self.variable_x = ContinuousVariable("freeviz-x") self.variable_y = ContinuousVariable("freeviz-y") box0 = gui.vBox(self.mainArea, True, margin=0) self.graph = OWFreeVizGraph(self, box0, "Plot", view_box=FreeVizInteractiveViewBox) box0.layout().addWidget(self.graph.plot_widget) plot = self.graph.plot_widget box = gui.widgetBox(self.controlArea, "Optimization", spacing=10) form = QFormLayout( labelAlignment=Qt.AlignLeft, formAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow, verticalSpacing=10 ) form.addRow( "Initialization", gui.comboBox(box, self, "initialization", items=["Circular", "Random"], callback=self.reset_initialization) ) box.layout().addLayout(form) self.btn_start = gui.button(widget=box, master=self, label="Optimize", callback=self.toogle_start, enabled=False) self.viewbox = plot.getViewBox() self.replot = None g = self.graph.gui g.point_properties_box(self.controlArea) self.models = g.points_models box = gui.widgetBox(self.controlArea, "Show anchors") self.rslider = gui.hSlider( box, self, "radius", minValue=0, maxValue=100, step=5, label="Radius", createLabel=False, ticks=True, callback=self.update_radius) self.rslider.setTickInterval(0) self.rslider.setPageStep(10) box = gui.vBox(self.controlArea, "Plot Properties") g.add_widgets([g.JitterSizeSlider], box) g.add_widgets([g.ShowLegend, g.ClassDensity, g.LabelOnlySelected], box) self.graph.box_zoom_select(self.controlArea) self.controlArea.layout().addStretch(100) self.icons = gui.attributeIconDict p = self.graph.plot_widget.palette() self.graph.set_palette(p) gui.auto_commit(self.controlArea, self, "auto_commit", "Send Selection", "Send Automatically") self.graph.zoom_actions(self) # FreeViz self._loop = AsyncUpdateLoop(parent=self) self._loop.yielded.connect(self.__set_projection) self._loop.finished.connect(self.__freeviz_finished) self._loop.raised.connect(self.__on_error) self._new_plotdata() def keyPressEvent(self, event): super().keyPressEvent(event) self.graph.update_tooltip(event.modifiers()) def keyReleaseEvent(self, event): super().keyReleaseEvent(event) self.graph.update_tooltip(event.modifiers()) def update_radius(self): # Update the anchor/axes visibility assert not self.plotdata is None if self.plotdata.hidecircle is None: return minradius = self.radius / 100 + 1e-5 for anchor, item in zip(self.plotdata.anchors, self.plotdata.anchoritem): item.setVisible(np.linalg.norm(anchor) > minradius) self.plotdata.hidecircle.setRect( QRectF(-minradius, -minradius, 2 * minradius, 2 * minradius)) def toogle_start(self): if self._loop.isRunning(): self._loop.cancel() if isinstance(self, OWFreeViz): self.btn_start.setText("Optimize") self.progressBarFinished(processEvents=False) else: self._start() def _start(self): """ Start the projection optimization. """ assert not self.plotdata is None X, Y = self.plotdata.X, self.plotdata.Y anchors = self.plotdata.anchors def update_freeviz(interval, initial): anchors = initial while True: res = FreeViz.freeviz(X, Y, scale=False, center=False, initial=anchors, maxiter=interval) _, anchors_new = res[:2] yield res[:2] if np.allclose(anchors, anchors_new, rtol=1e-5, atol=1e-4): return anchors = anchors_new interval = 10 # TODO self._loop.setCoroutine( update_freeviz(interval, anchors)) self.btn_start.setText("Stop") self.progressBarInit(processEvents=False) self.setBlocking(True) self.setStatusMessage("Optimizing") def reset_initialization(self): """ Reset the current 'anchor' initialization, and restart the optimization if necessary. """ running = self._loop.isRunning() if running: self._loop.cancel() if self.data is not None: self._clear_plot() self.setup_plot() if running: self._start() def __set_projection(self, res): # Set/update the projection matrix and coordinate embeddings # assert self.plotdata is not None, "__set_projection call unexpected" assert not self.plotdata is None increment = 1 # TODO self.progressBarAdvance( increment * 100. / MAX_ITERATIONS, processEvents=False) # TODO embedding_coords, projection = res self.plotdata.embedding_coords = embedding_coords self.plotdata.anchors = projection self._update_xy() self.update_radius() self.update_density() def __freeviz_finished(self): # Projection optimization has finished self.btn_start.setText("Optimize") self.setStatusMessage("") self.setBlocking(False) self.progressBarFinished(processEvents=False) self.commit() def __on_error(self, err): sys.excepthook(type(err), err, getattr(err, "__traceback__")) def _update_xy(self): # Update the plotted embedding coordinates self.graph.plot_widget.clear() coords = self.plotdata.embedding_coords radius = np.max(np.linalg.norm(coords, axis=1)) self.plotdata.embedding_coords = coords / radius self.plot(show_anchors=(len(self.data.domain.attributes) < MAX_ANCHORS)) def _new_plotdata(self): self.plotdata = namespace( validmask=None, embedding_coords=None, anchors=[], anchoritem=[], X=None, Y=None, indicators=[], hidecircle=None, data=None, items=[], topattrs=None, rand=None, selection=None, # np.array ) def _anchor_circle(self): # minimum visible anchor radius (radius) minradius = self.radius / 100 + 1e-5 for item in chain(self.plotdata.anchoritem, self.plotdata.items): self.viewbox.removeItem(item) self.plotdata.anchoritem = [] self.plotdata.items = [] for anchor, var in zip(self.plotdata.anchors, self.data.domain.attributes): if True or np.linalg.norm(anchor) > minradius: axitem = AnchorItem( line=QLineF(0, 0, *anchor), text=var.name,) axitem.setVisible(np.linalg.norm(anchor) > minradius) axitem.setPen(pg.mkPen((100, 100, 100))) axitem.setArrowVisible(True) self.plotdata.anchoritem.append(axitem) self.viewbox.addItem(axitem) hidecircle = QGraphicsEllipseItem() hidecircle.setRect( QRectF(-minradius, -minradius, 2 * minradius, 2 * minradius)) _pen = QPen(Qt.lightGray, 1) _pen.setCosmetic(True) hidecircle.setPen(_pen) self.viewbox.addItem(hidecircle) self.plotdata.items.append(hidecircle) self.plotdata.hidecircle = hidecircle def update_colors(self): pass def sizeHint(self): return QSize(800, 500) def _clear(self): """ Clear/reset the widget state """ self._loop.cancel() self.data = None self._selection = None self._clear_plot() def _clear_plot(self): for item in chain(self.plotdata.anchoritem, self.plotdata.items): self.viewbox.removeItem(item) self.graph.plot_widget.clear() self._new_plotdata() def init_attr_values(self): self.graph.set_domain(self.data) @Inputs.data def set_data(self, data): self.clear_messages() self._clear() self.closeContext() if data is not None: if data and data.is_sparse(): self.Warning.sparse_not_supported() data = None elif data.domain.class_var is None: self.Error.no_class_var() data = None elif data.domain.class_var.is_discrete and \ len(data.domain.class_var.values) < 2: self.Error.not_enough_class_vars() data = None if data and len(data.domain.attributes) > data.X.shape[0]: self.Error.features_exceeds_instances() data = None if data is not None: valid_instances_count = self._prepare_freeviz_data(data) if valid_instances_count > MAX_INSTANCES: self.Error.too_many_data_instances() data = None elif valid_instances_count == 0: self.Error.no_valid_data() data = None self.data = data self.init_attr_values() if data is not None: self.cb_class_density.setEnabled(data.domain.has_discrete_class) self.openContext(data) self.btn_start.setEnabled(True) else: self.btn_start.setEnabled(False) self._X = self._Y = None self.graph.new_data(None, None) @Inputs.data_subset def set_subset_data(self, subset): self.subset_data = subset self.plotdata.subset_mask = None self.controls.graph.alpha_value.setEnabled(subset is None) def handleNewSignals(self): if all(v is not None for v in [self.data, self.subset_data]): dataids = self.data.ids.ravel() subsetids = np.unique(self.subset_data.ids) self._subset_mask = np.in1d(dataids, subsetids, assume_unique=True) if self._X is not None: self.setup_plot(True) self.commit() def customEvent(self, event): if event.type() == OWFreeViz.ReplotRequest: self.__replot_requested = False self.setup_plot() else: super().customEvent(event) def _prepare_freeviz_data(self, data): X = data.X Y = data.Y mask = np.bitwise_or.reduce(np.isnan(X), axis=1) mask |= np.isnan(Y) validmask = ~mask X = X[validmask, :] Y = Y[validmask] if not len(X): self._X = None return 0 if data.domain.class_var.is_discrete: Y = Y.astype(int) X = (X - np.mean(X, axis=0)) span = np.ptp(X, axis=0) X[:, span > 0] /= span[span > 0].reshape(1, -1) self._X = X self._Y = Y self._validmask = validmask return len(X) def setup_plot(self, reset_view=True): assert not self._X is None self.graph.jitter_continuous = True self.__replot_requested = False X = self.plotdata.X = self._X self.plotdata.Y = self._Y self.plotdata.validmask = self._validmask self.plotdata.selection = self._selection if self._selection is not None else \ np.zeros(len(self._validmask), dtype=np.uint8) anchors = self.plotdata.anchors if len(anchors) == 0: if self.initialization == self.Circular: anchors = FreeViz.init_radial(X.shape[1]) else: anchors = FreeViz.init_random(X.shape[1], 2) EX = np.dot(X, anchors) c = np.zeros((X.shape[0], X.shape[1])) for i in range(X.shape[0]): c[i] = np.argsort((np.power(X[i] * anchors[:, 0], 2) + np.power(X[i] * anchors[:, 1], 2)))[::-1] self.plotdata.topattrs = np.array(c, dtype=int)[:, :10] radius = np.max(np.linalg.norm(EX, axis=1)) self.plotdata.anchors = anchors coords = (EX / radius) self.plotdata.embedding_coords = coords if reset_view: self.viewbox.setRange(RANGE) self.viewbox.setAspectLocked(True, 1) self.plot(reset_view=reset_view) def randomize_indices(self): X = self._X self.plotdata.rand = np.random.choice(len(X), MAX_POINTS, replace=False) \ if len(X) > MAX_POINTS else None def manual_move_anchor(self, show_anchors=True): self.__replot_requested = False X = self.plotdata.X = self._X anchors = self.plotdata.anchors validmask = self.plotdata.validmask EX = np.dot(X, anchors) data_x = self.data.X[validmask] data_y = self.data.Y[validmask] radius = np.max(np.linalg.norm(EX, axis=1)) if self.plotdata.rand is not None: rand = self.plotdata.rand EX = EX[rand] data_x = data_x[rand] data_y = data_y[rand] selection = self.plotdata.selection[validmask] selection = selection[rand] else: selection = self.plotdata.selection[validmask] coords = (EX / radius) if show_anchors: self._anchor_circle() attributes = () + self.data.domain.attributes + (self.variable_x, self.variable_y) domain = Domain(attributes=attributes, class_vars=self.data.domain.class_vars) data = Table.from_numpy(domain, X=np.hstack((data_x, coords)), Y=data_y) self.graph.new_data(data, None) self.graph.selection = selection self.graph.update_data(self.variable_x, self.variable_y, reset_view=False) def plot(self, reset_view=False, show_anchors=True): if show_anchors: self._anchor_circle() attributes = () + self.data.domain.attributes + (self.variable_x, self.variable_y) domain = Domain(attributes=attributes, class_vars=self.data.domain.class_vars, metas=self.data.domain.metas) mask = self.plotdata.validmask array = np.zeros((len(self.data), 2), dtype=np.float) array[mask] = self.plotdata.embedding_coords data = self.data.transform(domain) data[:, self.variable_x] = array[:, 0].reshape(-1, 1) data[:, self.variable_y] = array[:, 1].reshape(-1, 1) subset_data = data[self._subset_mask & mask]\ if self._subset_mask is not None and len(self._subset_mask) else None self.plotdata.data = data self.graph.new_data(data[mask], subset_data) if self.plotdata.selection is not None: self.graph.selection = self.plotdata.selection[self.plotdata.validmask] self.graph.update_data(self.variable_x, self.variable_y, reset_view=reset_view) def reset_graph_data(self, *_): if self.data is not None: self.graph.rescale_data() self._update_graph() def _update_graph(self, reset_view=True, **_): self.graph.zoomStack = [] assert not self.graph.data is None self.graph.update_data(self.variable_x, self.variable_y, reset_view) def update_density(self): if self.graph.data is None: return self._update_graph(reset_view=False) def selection_changed(self): if self.graph.selection is not None: pd = self.plotdata pd.selection[pd.validmask] = self.graph.selection self._selection = pd.selection self.commit() def prepare_data(self): pass def commit(self): selected = annotated = components = None graph = self.graph if self.data is not None and self.plotdata.validmask is not None: name = self.data.name metas = () + self.data.domain.metas + (self.variable_x, self.variable_y) domain = Domain(attributes=self.data.domain.attributes, class_vars=self.data.domain.class_vars, metas=metas) data = self.plotdata.data.transform(domain) validmask = self.plotdata.validmask mask = np.array(validmask, dtype=int) mask[mask == 1] = graph.selection if graph.selection is not None \ else [False * len(mask)] selection = np.array([], dtype=np.uint8) if mask is None else np.flatnonzero(mask) if len(selection): selected = data[selection] selected.name = name + ": selected" selected.attributes = self.data.attributes if graph.selection is not None and np.max(graph.selection) > 1: annotated = create_groups_table(data, mask) else: annotated = create_annotated_table(data, selection) annotated.attributes = self.data.attributes annotated.name = name + ": annotated" comp_domain = Domain( self.data.domain.attributes, metas=[StringVariable(name='component')]) metas = np.array([["FreeViz 1"], ["FreeViz 2"]]) components = Table.from_numpy( comp_domain, X=self.plotdata.anchors.T, metas=metas) components.name = name + ": components" self.Outputs.selected_data.send(selected) self.Outputs.annotated_data.send(annotated) self.Outputs.components.send(components) def send_report(self): if self.data is None: return def name(var): return var and var.name caption = report.render_items_vert(( ("Color", name(self.graph.attr_color)), ("Label", name(self.graph.attr_label)), ("Shape", name(self.graph.attr_shape)), ("Size", name(self.graph.attr_size)), ("Jittering", self.graph.jitter_size != 0 and "{} %".format(self.graph.jitter_size)))) self.report_plot() if caption: self.report_caption(caption)
class OWCorrespondenceAnalysis(widget.OWWidget): name = "Correspondence Analysis" description = "Correspondence analysis for categorical multivariate data." icon = "icons/CorrespondenceAnalysis.svg" inputs = [("Data", Orange.data.Table, "set_data")] Invalidate = QEvent.registerEventType() settingsHandler = settings.DomainContextHandler() selected_var_indices = settings.ContextSetting([]) graph_name = "plot.plotItem" class Error(widget.OWWidget.Error): empty_data = widget.Msg("Empty data set") def __init__(self): super().__init__() self.data = None self.component_x = 0 self.component_y = 1 box = gui.vBox(self.controlArea, "Variables") self.varlist = itemmodels.VariableListModel() self.varview = view = QListView(selectionMode=QListView.MultiSelection) view.setModel(self.varlist) view.selectionModel().selectionChanged.connect(self._var_changed) box.layout().addWidget(view) axes_box = gui.vBox(self.controlArea, "Axes") box = gui.vBox(axes_box, "Axis X", margin=0) box.setFlat(True) self.axis_x_cb = gui.comboBox(box, self, "component_x", callback=self._component_changed) box = gui.vBox(axes_box, "Axis Y", margin=0) box.setFlat(True) self.axis_y_cb = gui.comboBox(box, self, "component_y", callback=self._component_changed) self.infotext = gui.widgetLabel( gui.vBox(self.controlArea, "Contribution to Inertia"), "\n") gui.rubber(self.controlArea) self.plot = pg.PlotWidget(background="w") self.plot.setMenuEnabled(False) self.mainArea.layout().addWidget(self.plot) def set_data(self, data): self.closeContext() self.clear() if data is not None and not len(data): self.Error.empty_data() data = None else: self.Error.empty_data.clear() self.data = data if data is not None: self.varlist[:] = [ var for var in data.domain.variables if var.is_discrete ] self.selected_var_indices = [0, 1][:len(self.varlist)] self.component_x, self.component_y = 0, 1 self.openContext(data) self._restore_selection() # self._invalidate() self._update_CA() def clear(self): self.data = None self.ca = None self.plot.clear() self.varlist[:] = [] def selected_vars(self): rows = sorted(ind.row() for ind in self.varview.selectionModel().selectedRows()) return [self.varlist[i] for i in rows] def _restore_selection(self): def restore(view, indices): with itemmodels.signal_blocking(view.selectionModel()): select_rows(view, indices) restore(self.varview, self.selected_var_indices) def _p_axes(self): # return (0, 1) return (self.component_x, self.component_y) def _var_changed(self): self.selected_var_indices = sorted( ind.row() for ind in self.varview.selectionModel().selectedRows()) rfs = self.update_XY() if rfs is not None: if self.component_x >= rfs: self.component_x = rfs - 1 if self.component_y >= rfs: self.component_y = rfs - 1 self._invalidate() def _component_changed(self): if self.ca is not None: self._setup_plot() self._update_info() def _invalidate(self): self.__invalidated = True QApplication.postEvent(self, QEvent(self.Invalidate)) def customEvent(self, event): if event.type() == self.Invalidate: self.ca = None self.plot.clear() self._update_CA() return return super().customEvent(event) def _update_CA(self): self.update_XY() self.component_x, self.component_y = self.component_x, self.component_y self._setup_plot() self._update_info() def update_XY(self): self.axis_x_cb.clear() self.axis_y_cb.clear() ca_vars = self.selected_vars() if len(ca_vars) == 0: return multi = len(ca_vars) != 2 if multi: _, ctable = burt_table(self.data, ca_vars) else: ctable = contingency.get_contingency(self.data, *ca_vars[::-1]) self.ca = correspondence(ctable, ) rfs = self.ca.row_factors.shape[1] axes = ["{}".format(i + 1) for i in range(rfs)] self.axis_x_cb.addItems(axes) self.axis_y_cb.addItems(axes) return rfs def _setup_plot(self): self.plot.clear() points = self.ca variables = self.selected_vars() colors = colorpalette.ColorPaletteGenerator(len(variables)) p_axes = self._p_axes() if points == None: return if len(variables) == 2: row_points = self.ca.row_factors[:, p_axes] col_points = self.ca.col_factors[:, p_axes] points = [row_points, col_points] else: points = self.ca.row_factors[:, p_axes] counts = [len(var.values) for var in variables] range_indices = numpy.cumsum([0] + counts) ranges = zip(range_indices, range_indices[1:]) points = [points[s:e] for s, e in ranges] for i, (v, points) in enumerate(zip(variables, points)): color_outline = colors[i] color_outline.setAlpha(200) color = QColor(color_outline) color.setAlpha(120) item = ScatterPlotItem( x=points[:, 0], y=points[:, 1], brush=QBrush(color), pen=pg.mkPen(color_outline.darker(120), width=1.5), size=numpy.full((points.shape[0], ), 10.1), ) self.plot.addItem(item) for name, point in zip(v.values, points): item = pg.TextItem(name, anchor=(0.5, 0)) self.plot.addItem(item) item.setPos(point[0], point[1]) inertia = self.ca.inertia_of_axis() if numpy.sum(inertia) == 0: inertia = 100 * inertia else: inertia = 100 * inertia / numpy.sum(inertia) ax = self.plot.getAxis("bottom") ax.setLabel("Component {} ({:.1f}%)".format(p_axes[0] + 1, inertia[p_axes[0]])) ax = self.plot.getAxis("left") ax.setLabel("Component {} ({:.1f}%)".format(p_axes[1] + 1, inertia[p_axes[1]])) def _update_info(self): if self.ca is None: self.infotext.setText("\n\n") else: fmt = ("Axis 1: {:.2f}\n" "Axis 2: {:.2f}") inertia = self.ca.inertia_of_axis() if numpy.sum(inertia) == 0: inertia = 100 * inertia else: inertia = 100 * inertia / numpy.sum(inertia) ax1, ax2 = self._p_axes() self.infotext.setText(fmt.format(inertia[ax1], inertia[ax2])) def send_report(self): if self.data is None: return vars = self.selected_vars() if not vars: return items = OrderedDict() items["Data instances"] = len(self.data) if len(vars) == 1: items["Selected variable"] = vars[0] else: items["Selected variables"] = "{} and {}".format( ", ".join(var.name for var in vars[:-1]), vars[-1].name) self.report_items(items) self.report_plot()
def scheduleDelayedActivate(self): if self.isEnabled() and not self.__layoutPending: self.__layoutPending = True QApplication.postEvent(self, QEvent(QEvent.LayoutRequest))
def close(self): QCoreApplication.sendEvent(self, QEvent(QEvent.Close))
def start(self): QCoreApplication.postEvent(self, QEvent(Task.__ExecuteCall))
class ActivateParentEvent(QEvent): ActivateParent = QEvent.registerEventType() def __init__(self): QEvent.__init__(self, self.ActivateParent)
def __init__(self): QEvent.__init__(self, ExecuteCallEvent.ExecuteCall)
def __init__(self): QEvent.__init__(self, self.ActivateParent)
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 if sys.platform == "darwin": slmargin = self.__searchline.style() \ .pixelMetric(QStyle.PM_FocusFrameVMargin) popuprect_origin.adjust(slmargin / 2, 0, -slmargin * 1.5, slmargin) 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 __init__(self): super().__init__() #: widget's runtime state self.__state = State.NoState self.corpus = None self.n_text_categories = 0 self.n_text_data = 0 self.n_skipped = 0 self.__invalidated = False self.__pendingTask = None vbox = gui.vBox(self.controlArea) hbox = gui.hBox(vbox) self.recent_cb = QComboBox( sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, minimumContentsLength=16, acceptDrops=True ) self.recent_cb.installEventFilter(self) self.recent_cb.activated[int].connect(self.__onRecentActivated) browseaction = QAction( "Open/Load Documents", self, iconText="\N{HORIZONTAL ELLIPSIS}", icon=self.style().standardIcon(QStyle.SP_DirOpenIcon), toolTip="Select a folder from which to load the documents" ) browseaction.triggered.connect(self.__runOpenDialog) reloadaction = QAction( "Reload", self, icon=self.style().standardIcon(QStyle.SP_BrowserReload), toolTip="Reload current document set" ) reloadaction.triggered.connect(self.reload) self.__actions = namespace( browse=browseaction, reload=reloadaction, ) browsebutton = QPushButton( browseaction.iconText(), icon=browseaction.icon(), toolTip=browseaction.toolTip(), clicked=browseaction.trigger ) reloadbutton = QPushButton( reloadaction.iconText(), icon=reloadaction.icon(), clicked=reloadaction.trigger, default=True, ) hbox.layout().addWidget(self.recent_cb) hbox.layout().addWidget(browsebutton) hbox.layout().addWidget(reloadbutton) self.addActions([browseaction, reloadaction]) reloadaction.changed.connect( lambda: reloadbutton.setEnabled(reloadaction.isEnabled()) ) box = gui.vBox(vbox, "Info") self.infostack = QStackedWidget() self.info_area = QLabel( text="No document set selected", wordWrap=True ) self.progress_widget = QProgressBar( minimum=0, maximum=100 ) self.cancel_button = QPushButton( "Cancel", icon=self.style().standardIcon(QStyle.SP_DialogCancelButton), ) self.cancel_button.clicked.connect(self.cancel) w = QWidget() vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) hlayout.addWidget(self.progress_widget) hlayout.addWidget(self.cancel_button) vlayout.addLayout(hlayout) self.pathlabel = TextLabel() self.pathlabel.setTextElideMode(Qt.ElideMiddle) self.pathlabel.setAttribute(Qt.WA_MacSmallSize) vlayout.addWidget(self.pathlabel) w.setLayout(vlayout) self.infostack.addWidget(self.info_area) self.infostack.addWidget(w) box.layout().addWidget(self.infostack) self.__initRecentItemsModel() self.__invalidated = True self.__executor = ThreadExecutor(self) QApplication.postEvent(self, QEvent(RuntimeEvent.Init))
def setPalette(self, palette): # type: (QPalette) -> None if self.__palette != palette: self.__palette = QPalette(palette) QApplication.sendEvent(self, QEvent(QEvent.PaletteChange))
class WorkflowEvent(QEvent): #: Delivered to Scheme when a node has been added NodeAdded = QEvent.registerEventType() #: Delivered to Scheme when a node has been removed NodeRemoved = QEvent.registerEventType() #: A Link has been added to the scheme LinkAdded = QEvent.registerEventType() #: A Link has been removed from the scheme LinkRemoved = QEvent.registerEventType() #: Node's (runtime) state has changed NodeStateChange = QEvent.registerEventType() #: Link's (runtime) state has changed LinkStateChange = QEvent.registerEventType() #: Request for Node's runtime initialization (e.g. #: load required data, establish connection, ...) NodeInitialize = QEvent.registerEventType() #: Restore the node from serialized state NodeRestore = QEvent.registerEventType() NodeSaveStateRequest = QEvent.registerEventType() # ? #: Node user activate request (e.g. on double click in the #: canvas GUI) NodeActivateRequest = QEvent.registerEventType() # Workflow runtime changed (Running/Paused/Stopped, ...) RuntimeStateChange = QEvent.registerEventType() #: Workflow resource changed (e.g. work directory, env variable) WorkflowResourceChange = QEvent.registerEventType() #: Workflow is about to close. WorkflowAboutToClose = QEvent.registerEventType() WorkflowClose = QEvent.registerEventType() AnnotationAdded = QEvent.registerEventType() AnnotationRemoved = QEvent.registerEventType() AnnotationChange = QEvent.registerEventType() #: Request activation (show and raise) of the window containing #: the workflow view ActivateParentRequest = QEvent.registerEventType() def __init__(self, etype): QEvent.__init__(self, etype)
def eventFilter(self, obj: QDateTimeEdit, event: QEvent) -> bool: if event.type() == QEvent.Wheel: return True return super().eventFilter(obj, event)
def __init__(self, etype): QEvent.__init__(self, etype)
def viewportEvent(self, event: QEvent) -> bool: if event.type() == QEvent.Resize: self._layout() return super().viewportEvent(event)
class AsyncUpdateLoop(QObject): """ Run/drive an coroutine from the event loop. This is a utility class which can be used for implementing asynchronous update loops. I.e. coroutines which periodically yield control back to the Qt event loop. """ Next = QEvent.registerEventType() #: State flags Idle, Running, Cancelled, Finished = 0, 1, 2, 3 #: The coroutine has yielded control to the caller (with `object`) yielded = Signal(object) #: The coroutine has finished/exited (either with an exception #: or with a return statement) finished = Signal() #: The coroutine has returned (normal return statement / StopIteration) returned = Signal(object) #: The coroutine has exited with with an exception. raised = Signal(object) #: The coroutine was cancelled/closed. cancelled = Signal() def __init__(self, parent=None, **kwargs): super().__init__(parent, **kwargs) self.__coroutine = None self.__next_pending = False # Flag for compressing scheduled events self.__in_next = False self.__state = AsyncUpdateLoop.Idle @Slot(object) def setCoroutine(self, loop): """ Set the coroutine. The coroutine will be resumed (repeatedly) from the event queue. If there is an existing coroutine set it is first closed/cancelled. Raises an RuntimeError if the current coroutine is running. """ if self.__coroutine is not None: self.__coroutine.close() self.__coroutine = None self.__state = AsyncUpdateLoop.Cancelled self.cancelled.emit() self.finished.emit() if loop is not None: self.__coroutine = loop self.__state = AsyncUpdateLoop.Running self.__schedule_next() @Slot() def cancel(self): """ Cancel/close the current coroutine. Raises an RuntimeError if the current coroutine is running. """ self.setCoroutine(None) def state(self): """ Return the current state. """ return self.__state def isRunning(self): return self.__state == AsyncUpdateLoop.Running def __schedule_next(self): if not self.__next_pending: self.__next_pending = True QTimer.singleShot(10, self.__on_timeout) def __next(self): if self.__coroutine is not None: try: rval = next(self.__coroutine) except StopIteration as stop: self.__state = AsyncUpdateLoop.Finished self.returned.emit(stop.value) self.finished.emit() self.__coroutine = None except BaseException as er: self.__state = AsyncUpdateLoop.Finished self.raised.emit(er) self.finished.emit() self.__coroutine = None else: self.yielded.emit(rval) self.__schedule_next() @Slot() def __on_timeout(self): assert self.__next_pending self.__next_pending = False if not self.__in_next: self.__in_next = True try: self.__next() finally: self.__in_next = False else: # warn self.__schedule_next() def customEvent(self, event): if event.type() == AsyncUpdateLoop.Next: self.__on_timeout() else: super().customEvent(event)
def __init__(self): super().__init__() #: widget's runtime state self.__state = State.NoState self.corpus = None self.n_text_categories = 0 self.n_text_data = 0 self.skipped_documents = [] self.__invalidated = False self.__pendingTask = None layout = QGridLayout() layout.setSpacing(4) gui.widgetBox(self.controlArea, orientation=layout, box='Source') source_box = gui.radioButtons(None, self, "source", box=True, callback=self.start, addToLayout=False) rb_button = gui.appendRadioButton(source_box, "Folder:", addToLayout=False) layout.addWidget(rb_button, 0, 0, Qt.AlignVCenter) box = gui.hBox(None, addToLayout=False, margin=0) box.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) self.recent_cb = QComboBox( sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon, minimumContentsLength=16, acceptDrops=True ) self.recent_cb.installEventFilter(self) self.recent_cb.activated[int].connect(self.__onRecentActivated) browseaction = QAction( "Open/Load Documents", self, iconText="\N{HORIZONTAL ELLIPSIS}", icon=self.style().standardIcon(QStyle.SP_DirOpenIcon), toolTip="Select a folder from which to load the documents" ) browseaction.triggered.connect(self.__runOpenDialog) reloadaction = QAction( "Reload", self, icon=self.style().standardIcon(QStyle.SP_BrowserReload), toolTip="Reload current document set" ) reloadaction.triggered.connect(self.reload) self.__actions = namespace( browse=browseaction, reload=reloadaction, ) browsebutton = QPushButton( browseaction.iconText(), icon=browseaction.icon(), toolTip=browseaction.toolTip(), clicked=browseaction.trigger, default=False, autoDefault=False, ) reloadbutton = QPushButton( reloadaction.iconText(), icon=reloadaction.icon(), clicked=reloadaction.trigger, default=False, autoDefault=False, ) box.layout().addWidget(self.recent_cb) layout.addWidget(box, 0, 1) layout.addWidget(browsebutton, 0, 2) layout.addWidget(reloadbutton, 0, 3) rb_button = gui.appendRadioButton(source_box, "URL:", addToLayout=False) layout.addWidget(rb_button, 3, 0, Qt.AlignVCenter) self.url_combo = url_combo = QComboBox() url_model = PyListModel() url_model.wrap(self.recent_urls) url_combo.setLineEdit(LineEditSelectOnFocus()) url_combo.setModel(url_model) url_combo.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Fixed) url_combo.setEditable(True) url_combo.setInsertPolicy(url_combo.InsertAtTop) url_edit = url_combo.lineEdit() l, t, r, b = url_edit.getTextMargins() url_edit.setTextMargins(l + 5, t, r, b) layout.addWidget(url_combo, 3, 1, 1, 3) url_combo.activated.connect(self._url_set) # whit completer we set that combo box is case sensitive when # matching the history completer = QCompleter() completer.setCaseSensitivity(Qt.CaseSensitive) url_combo.setCompleter(completer) self.addActions([browseaction, reloadaction]) reloadaction.changed.connect( lambda: reloadbutton.setEnabled(reloadaction.isEnabled()) ) box = gui.vBox(self.controlArea, "Info") self.infostack = QStackedWidget() self.info_area = QLabel( text="No document set selected", wordWrap=True ) self.progress_widget = QProgressBar( minimum=0, maximum=100 ) self.cancel_button = QPushButton( "Cancel", icon=self.style().standardIcon(QStyle.SP_DialogCancelButton), default=False, autoDefault=False, ) self.cancel_button.clicked.connect(self.cancel) w = QWidget() vlayout = QVBoxLayout() vlayout.setContentsMargins(0, 0, 0, 0) hlayout = QHBoxLayout() hlayout.setContentsMargins(0, 0, 0, 0) hlayout.addWidget(self.progress_widget) hlayout.addWidget(self.cancel_button) vlayout.addLayout(hlayout) self.pathlabel = TextLabel() self.pathlabel.setTextElideMode(Qt.ElideMiddle) self.pathlabel.setAttribute(Qt.WA_MacSmallSize) vlayout.addWidget(self.pathlabel) w.setLayout(vlayout) self.infostack.addWidget(self.info_area) self.infostack.addWidget(w) box.layout().addWidget(self.infostack) self.__initRecentItemsModel() self.__invalidated = True self.__executor = ThreadExecutor(self) QApplication.postEvent(self, QEvent(RuntimeEvent.Init))
def main(argv=None): app = QApplication(list(argv) if argv else []) argv = app.arguments() parser = argparse.ArgumentParser( description=( "Run an orange workflow without showing a GUI and exit " "when it completes.\n\n" "WARNING: This is experimental as Orange is not designed to run " "non-interactive." ) ) parser.add_argument("--log-level", "-l", metavar="LEVEL", type=int, default=logging.CRITICAL, dest="log_level") parser.add_argument("--config", default="Orange.canvas.config.Config", type=str) parser.add_argument("file") args = parser.parse_args(argv[1:]) log_level = args.log_level filename = args.file logging.basicConfig(level=log_level) cfg_class = utils.name_lookup(args.config) cfg: config.Config = cfg_class() config.set_default(cfg) config.init() reg = WidgetRegistry() widget_discovery = cfg.widget_discovery( reg, cached_descriptions=cache.registry_cache() ) widget_discovery.run(cfg.widgets_entry_points()) model = cfg.workflow_constructor() model.set_runtime_env( "basedir", os.path.abspath(os.path.dirname(filename)) ) sigprop = model.findChild(signalmanager.SignalManager) sigprop.pause() # Pause signal propagation during load with open(filename, "rb") as f: model.load_from(f, registry=reg) # Ensure all widgets are created (this is required for the workflow # to even start - relies to much on OWWidget behaviour). for _ in map(model.widget_for_node, model.nodes): pass sigprop.resume() # Resume inter-widget signal propagation def on_finished(): severity = 0 for node in model.nodes: for msg in node.state_messages(): if msg.contents and msg.severity == msg.Error: print(msg.contents, msg.message_id, file=sys.stderr) severity = msg.Error if severity == UserMessage.Error: app.exit(1) else: app.exit() sigprop.finished.connect(on_finished) rval = app.exec_() model.clear() # Notify the workflow model to 'close'. QApplication.sendEvent(model, QEvent(QEvent.Close)) app.processEvents() return rval