class SettingChangedEvent(QEvent): """ A settings has changed. This event is sent by Settings instance to itself when a setting for a key has changed. """ SettingChanged = QEvent.registerEventType() """Setting was changed""" SettingAdded = QEvent.registerEventType() """A setting was added""" SettingRemoved = QEvent.registerEventType() """A setting was removed""" 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 key(self): return self.__key def value(self): return self.__value def oldValue(self): return self.__oldValue
class QueuedCallEvent(QEvent): QueuedCall = QEvent.registerEventType() def __init__(self, function, args, kwargs): QEvent.__init__(self, QueuedCallEvent.QueuedCall) self.function = function self.args = args self.kwargs = kwargs self._result = None self._exc_info = None self._state = 0 def call(self): try: self._result = self.function(*self.args, **self.kwargs) self._state = 1 except Exception as ex: self._exc_info = (type(ex), ex.args, None) raise def result(self): if self._state == 1: return self._result elif self._exc_info: raise self._exc_info[0](self._exc_info[1]) else: # Should this block, add timeout? raise RuntimeError("Result not yet ready") def isready(self): return self._state == 1 or self._exc_info
class ThunkEvent( QEvent ): """ A QEvent subclass that holds a callable which can be executed by its listeners. Sort of like a "queued connection" signal. """ EventType = QEvent.Type(QEvent.registerEventType()) def __init__(self, func, *args): QEvent.__init__(self, self.EventType) if len(args) > 0: self.thunk = partial(func, *args) else: self.thunk = func def __call__(self): self.thunk() @classmethod def post(cls, handlerObject, func, *args): e = ThunkEvent( func, *args ) QApplication.postEvent(handlerObject, e) @classmethod def send(cls, handlerObject, func, *args): e = ThunkEvent( func, *args ) QApplication.sendEvent(handlerObject, e)
class EventFlusher(QObject): SetEvent = QEvent.Type(QEvent.registerEventType()) def __init__(self, parent=None): QObject.__init__(self, parent) self._state = threading.Event() def event(self, e): if e.type() == EventFlusher.SetEvent: assert threading.current_thread().name == "MainThread" self.set() return True return False def eventFilter(self, watched, event): return self.event(event) def set(self): QApplication.sendPostedEvents() QApplication.processEvents() QApplication.flush() assert not self._state.is_set() self._state.set() def clear(self): self._state.clear() def wait(self): assert threading.current_thread().name != "MainThread" self._state.wait()
class CallbackEvent(QEvent): _evtype = QEvent.Type(QEvent.registerEventType()) def __init__(self, callback, *args): super(CallbackEvent, self).__init__(self._evtype) self.callback = callback self.args = args
class WidgetInitEvent(QEvent): DelayedInit = QEvent.registerEventType() def __init__(self, initstate): super().__init__(WidgetManager.WidgetInitEvent.DelayedInit) self._initstate = initstate def initstate(self): return self._initstate
class ExecuteCallEvent(QEvent): """ Represents an function call from the event loop (used by :class:`Task` to schedule the :func:`Task.run` method to be invoked) """ ExecuteCall = QEvent.registerEventType() def __init__(self): QEvent.__init__(self, ExecuteCallEvent.ExecuteCall)
class Updater(): QUIT_EVENT_TYPE = QEvent.Type(QEvent.registerEventType()) def __init__(self, window): pass def checkForUpdates(self): pass def close(self): pass
class StateChangedEvent(QEvent): """ Represents a change in the internal state of a :class:`Future`. """ StateChanged = QEvent.registerEventType() def __init__(self, state): QEvent.__init__(self, StateChangedEvent.StateChanged) self._state = state def state(self): """ Return the new state (Future.Pending, Future.Cancelled, ...). """ return self._state
class QueuedCallEvent(QEvent): QueuedCall = QEvent.registerEventType() def __init__(self, function, args, kwargs): QEvent.__init__(self, QueuedCallEvent.QueuedCall) self.function = function self.args = args self.kwargs = kwargs self._result = None self._exc_info = None self._state = 0 def call(self): try: self._result = self.function(*self.args, **self.kwargs) self._state = 1 except Exception, ex: self._exc_info = (type(ex), ex.args, None) raise
class Updater(): QUIT_EVENT_TYPE = QEvent.Type(QEvent.registerEventType()) # https://github.com/vslavik/winsparkle/wiki/Basic-Setup def __init__(self, window): try: sys.frozen # don't want to try updating python.exe self.updater = ctypes.cdll.WinSparkle self.updater.win_sparkle_set_appcast_url( 'http://eliteocr.sourceforge.net/appcast.xml' ) # py2exe won't let us embed this in resources self.updater.win_sparkle_set_automatic_check_for_updates(1) self.updater.win_sparkle_set_update_check_interval(47 * 60 * 60) # set up shutdown callback global mainwindow mainwindow = window self.callback_t = ctypes.CFUNCTYPE(None) # keep reference self.callback_fn = self.callback_t(shutdown_request) self.updater.win_sparkle_set_shutdown_request_callback( self.callback_fn) self.updater.win_sparkle_init() except: from traceback import print_exc print_exc() self.updater = None def checkForUpdates(self): if self.updater: self.updater.win_sparkle_check_update_with_ui() def close(self): if self.updater: self.updater.win_sparkle_cleanup() self.updater = None
class Updater(): QUIT_EVENT_TYPE = QEvent.Type(QEvent.registerEventType()) # http://sparkle-project.org/documentation/customization/ def __init__(self, window): try: objc.loadBundle( 'Sparkle', globals(), join(dirname(sys.executable), os.pardir, 'Frameworks', 'Sparkle.framework')) self.updater = SUUpdater.sharedUpdater() except: # can't load framework - not frozen or not included in app bundle? self.updater = None def checkForUpdates(self): if self.updater: self.updater.checkForUpdates_(None) def close(self): self.updater = None
class QueuedCallEvent(QEvent): QueuedCall = QEvent.registerEventType() def __init__(self, function, args, kwargs, semaphore=None): QEvent.__init__(self, QueuedCallEvent.QueuedCall) self.function = function self.args = args self.kwargs = kwargs self.semaphore = semaphore self._result = None self._exc_info = None self._state = 0 def call(self): try: self._result = self.function(*self.args, **self.kwargs) self._state = 1 if self.semaphore is not None: self.semaphore.release() except BaseException, ex: self._exc_info = (type(ex), ex.args, None) if self.semaphore is not None: self.semaphore.release() raise
class UpdateNbImageEvent(QEvent): def __init__(self, nb): QEvent.__init__(self, self.event_type) self.nb = nb event_type = QEvent.Type(QEvent.registerEventType())
class FinishLoadingEvent(QEvent): def __init__(self): QEvent.__init__(self, self.event_type) event_type = QEvent.Type(QEvent.registerEventType())
def __init__(self, navdb): super(Gui, self).__init__([]) self.acdata = ACDataEvent() self.navdb = navdb self.radarwidget = [] self.command_history = [] self.cmdargs = [] self.history_pos = 0 self.command_mem = '' self.command_line = '' self.prev_cmdline = '' self.simevent_target = 0 self.mousepos = (0, 0) self.prevmousepos = (0, 0) self.panzoomchanged = False # Register our custom pan/zoom event for etype in [PanZoomEventType, ACDataEventType, SimInfoEventType, StackTextEventType, ShowDialogEventType, DisplayFlagEventType, RouteDataEventType, DisplayShapeEventType]: reg_etype = QEvent.registerEventType(etype) if reg_etype != etype: print('Warning: Registered event type differs from requested type id (%d != %d)' % (reg_etype, etype)) self.splash = Splash() self.splash.show() self.splash.showMessage('Constructing main window') self.processEvents() # Install error message handler handler = QErrorMessage.qtHandler() handler.setWindowFlags(Qt.WindowStaysOnTopHint) # Check and set OpenGL capabilities if not QGLFormat.hasOpenGL(): raise RuntimeError('No OpenGL support detected for this system!') else: f = QGLFormat() f.setVersion(3, 3) f.setProfile(QGLFormat.CoreProfile) f.setDoubleBuffer(True) QGLFormat.setDefaultFormat(f) print('QGLWidget initialized for OpenGL version %d.%d' % (f.majorVersion(), f.minorVersion())) # Create the main window and related widgets self.radarwidget = RadarWidget(navdb) self.win = MainWindow(self, self.radarwidget) self.nd = ND(shareWidget=self.radarwidget) # Enable HiDPI support (Qt5 only) if QT_VERSION == 5: self.setAttribute(Qt.AA_UseHighDpiPixmaps) timer = QTimer(self) timer.timeout.connect(self.radarwidget.updateGL) timer.timeout.connect(self.nd.updateGL) timer.start(50) # Load geo data if False: pb = QProgressDialog('Binary buffer file not found, or file out of date: Constructing vertex buffers from geo data.', 'Cancel', 0, 100) pb.setWindowFlags(Qt.WindowStaysOnTopHint) pb.show() for i in range(101): pb.setValue(i) self.processEvents() QThread.msleep(100) pb.close()
class OWCorrespondenceAnalysis(widget.OWWidget): name = "Correspondence Analysis" description = "Correspondence analysis" icon = "icons/CorrespondenceAnalysis.svg" inputs = [("Data", Orange.data.Table, "set_data")] Invalidate = QEvent.registerEventType() settingsHandler = settings.DomainContextHandler() selected_var_indices = settings.ContextSetting([]) def __init__(self, parent=None): super().__init__(parent) self.data = None self.component_x = 0 self.component_y = 1 box = gui.widgetBox(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.widgetBox(self.controlArea, "Axes") box = gui.widgetBox(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.widgetBox(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.widgetBox(self.controlArea, "Contribution to Inertia"), "\n" ) gui.rubber(self.controlArea) self.plot = pg.PlotWidget(background="w") self.mainArea.layout().addWidget(self.plot) def set_data(self, data): self.closeContext() self.clear() self.warning(0) self.data = data if data is not None: self.varlist[:] = data.domain.variables 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()) 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): 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, ) axes = ["{}".format(i + 1) for i in range(self.ca.row_factors.shape[1])] self.axis_x_cb.clear() self.axis_x_cb.addItems(axes) self.axis_y_cb.clear() self.axis_y_cb.addItems(axes) self.component_x, self.component_y = self.component_x, self.component_y self._setup_plot() self._update_info() 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 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() 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() inertia = 100 * inertia / numpy.sum(inertia) ax1, ax2 = self._p_axes() self.infotext.setText(fmt.format(inertia[ax1], inertia[ax2]))
class AbortPlottingEvent(QEvent): def __init__(self, reason): QEvent.__init__(self, self.event_type) self.reason = reason event_type = QEvent.Type(QEvent.registerEventType())
def __init__(cls, name, bases, dct): super(EventMeta, cls).__init__(name, bases, dct) cls.id = QEvent.registerEventType() if name != 'EventBase' else None
class ActivateParentEvent(QEvent): ActivateParent = QEvent.registerEventType() def __init__(self): QEvent.__init__(self, self.ActivateParent)
class RuntimeEvent(QEvent): Init = QEvent.registerEventType()
class ImageReadyPlottingEvent(QEvent): def __init__(self): QEvent.__init__(self, self.event_type) event_type = QEvent.Type(QEvent.registerEventType())
class ScaleEvent(QEvent): Type = QEvent.Type(QEvent.registerEventType()) def __init__(self, config): QEvent.__init__(self, 11003) self.config = config
class NextImageEvent(QEvent): def __init__(self): QEvent.__init__(self, self.event_type) event_type = QEvent.Type(QEvent.registerEventType())
def __init__(cls, name, bases, dct): super(EventMeta, cls).__init__(name, bases, dct) cls.id = QEvent.registerEventType() if name != 'EventBase' else None
class FinishImageGrowthEvent(QEvent): def __init__(self): QEvent.__init__(self, FinishImageGrowthEvent.event_type) event_type = QEvent.Type(QEvent.registerEventType())
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 QtCore.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)
class OWLinearProjection(widget.OWWidget): name = "Linear Projection" description = "A multi-axes projection of data to a two-dimension plane." icon = "icons/LinearProjection.svg" priority = 2000 inputs = [("Data", Orange.data.Table, "set_data", widget.Default), ("Data Subset", Orange.data.Table, "set_subset_data")] # #TODO: Allow for axes to be supplied from an external source. # ("Projection", numpy.ndarray, "set_axes"),] outputs = [("Selected Data", Orange.data.Table)] settingsHandler = settings.DomainContextHandler() selected_variables = settings.ContextSetting( [], required=settings.ContextSetting.REQUIRED ) variable_state = settings.ContextSetting({}) color_index = settings.ContextSetting(0) shape_index = settings.ContextSetting(0) size_index = settings.ContextSetting(0) label_index = settings.ContextSetting(0) point_size = settings.Setting(10) alpha_value = settings.Setting(255) jitter_value = settings.Setting(0) auto_commit = settings.Setting(True) MinPointSize = 6 ReplotRequest = QEvent.registerEventType() def __init__(self, parent=None): super().__init__(parent) self.data = None self.subset_data = None self._subset_mask = None self._selection_mask = None self._item = None self.__selection_item = None self.__replot_requested = False box = gui.widgetBox(self.controlArea, "Axes") box1 = gui.widgetBox(box, "Displayed", margin=0) box1.setFlat(True) self.active_view = view = QListView( sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Ignored), selectionMode=QListView.ExtendedSelection, dragEnabled=True, defaultDropAction=Qt.MoveAction, dragDropOverwriteMode=False, dragDropMode=QListView.DragDrop, showDropIndicator=True, minimumHeight=50, ) view.viewport().setAcceptDrops(True) movedown = QAction( "Move down", view, shortcut=QKeySequence(Qt.AltModifier | Qt.Key_Down), triggered=self.__deactivate_selection ) view.addAction(movedown) self.varmodel_selected = model = DnDVariableListModel( parent=self) model.rowsInserted.connect(self._invalidate_plot) model.rowsRemoved.connect(self._invalidate_plot) model.rowsMoved.connect(self._invalidate_plot) view.setModel(model) box1.layout().addWidget(view) box1 = gui.widgetBox(box, "Other", margin=0) box1.setFlat(True) self.other_view = view = QListView( sizePolicy=QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Ignored), selectionMode=QListView.ExtendedSelection, dragEnabled=True, defaultDropAction=Qt.MoveAction, dragDropOverwriteMode=False, dragDropMode=QListView.DragDrop, showDropIndicator=True, minimumHeight=50 ) view.viewport().setAcceptDrops(True) moveup = QtGui.QAction( "Move up", view, shortcut=QKeySequence(Qt.AltModifier | Qt.Key_Up), triggered=self.__activate_selection ) view.addAction(moveup) self.varmodel_other = model = DnDVariableListModel(parent=self) view.setModel(model) box1.layout().addWidget(view) box = gui.widgetBox(self.controlArea, "Jittering") gui.comboBox(box, self, "jitter_value", items=["None", "0.01%", "0.1%", "0.5%", "1%", "2%"], callback=self._invalidate_plot) box.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) box = gui.widgetBox(self.controlArea, "Points") box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum) self.colorvar_model = itemmodels.VariableListModel(parent=self) self.shapevar_model = itemmodels.VariableListModel(parent=self) self.sizevar_model = itemmodels.VariableListModel(parent=self) self.labelvar_model = itemmodels.VariableListModel(parent=self) form = QtGui.QFormLayout( formAlignment=Qt.AlignLeft, labelAlignment=Qt.AlignLeft, fieldGrowthPolicy=QtGui.QFormLayout.AllNonFixedFieldsGrow, spacing=8 ) box.layout().addLayout(form) cb = gui.comboBox(box, self, "color_index", callback=self._on_color_change) cb.setModel(self.colorvar_model) form.addRow("Colors", cb) alpha_slider = QSlider( Qt.Horizontal, minimum=10, maximum=255, pageStep=25, tickPosition=QSlider.TicksBelow, value=self.alpha_value) alpha_slider.valueChanged.connect(self._set_alpha) form.addRow("Opacity", alpha_slider) cb = gui.comboBox(box, self, "shape_index", callback=self._on_shape_change) cb.setModel(self.shapevar_model) form.addRow("Shape", cb) cb = gui.comboBox(box, self, "size_index", callback=self._on_size_change) cb.setModel(self.sizevar_model) form.addRow("Size", cb) size_slider = QSlider( Qt.Horizontal, minimum=3, maximum=30, value=self.point_size, pageStep=3, tickPosition=QSlider.TicksBelow) size_slider.valueChanged.connect(self._set_size) form.addRow("", size_slider) toolbox = gui.widgetBox(self.controlArea, "Zoom/Select") toollayout = QtGui.QHBoxLayout() toolbox.layout().addLayout(toollayout) gui.auto_commit(self.controlArea, self, "auto_commit", "Commit") # Main area plot self.view = pg.GraphicsView(background="w") self.view.setRenderHint(QtGui.QPainter.Antialiasing, True) self.view.setFrameStyle(QtGui.QFrame.StyledPanel) self.viewbox = pg.ViewBox(enableMouse=True, enableMenu=False) self.viewbox.grabGesture(Qt.PinchGesture) self.view.setCentralItem(self.viewbox) self.mainArea.layout().addWidget(self.view) self.selection = PlotSelectionTool( self, selectionMode=PlotSelectionTool.Lasso) self.selection.setViewBox(self.viewbox) self.selection.selectionFinished.connect(self._selection_finish) self.zoomtool = PlotZoomTool(self) self.pantool = PlotPanTool(self) self.pinchtool = PlotPinchZoomTool(self) self.pinchtool.setViewBox(self.viewbox) self.continuous_palette = colorpalette.ContinuousPaletteGenerator( QtGui.QColor(220, 220, 220), QtGui.QColor(0, 0, 0), False ) self.discrete_palette = colorpalette.ColorPaletteGenerator(13) def icon(name): path = "icons/Dlg_{}.png".format(name) path = pkg_resources.resource_filename(widget.__name__, path) return QtGui.QIcon(path) actions = namespace( zoomtofit=QAction( "Zoom to fit", self, icon=icon("zoom_reset"), shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0), triggered=lambda: self.viewbox.setRange(QRectF(-1.05, -1.05, 2.1, 2.1))), zoomin=QAction( "Zoom in", self, shortcut=QKeySequence(QKeySequence.ZoomIn), triggered=lambda: self.viewbox.scaleBy((1 / 1.25, 1 / 1.25))), zoomout=QAction( "Zoom out", self, shortcut=QKeySequence(QKeySequence.ZoomOut), triggered=lambda: self.viewbox.scaleBy((1.25, 1.25))), select=QAction( "Select", self, checkable=True, icon=icon("arrow"), shortcut=QKeySequence(Qt.ControlModifier + Qt.Key_1)), zoom=QAction( "Zoom", self, checkable=True, icon=icon("zoom"), shortcut=QKeySequence(Qt.ControlModifier + Qt.Key_2)), pan=QAction( "Pan", self, checkable=True, icon=icon("pan_hand"), shortcut=QKeySequence(Qt.ControlModifier + Qt.Key_3)), ) self.addActions([actions.zoomtofit, actions.zoomin, actions.zoomout]) group = QtGui.QActionGroup(self, exclusive=True) group.addAction(actions.select) group.addAction(actions.zoom) group.addAction(actions.pan) actions.select.setChecked(True) currenttool = self.selection def activated(action): nonlocal currenttool if action is actions.select: tool, cursor = self.selection, Qt.ArrowCursor elif action is actions.zoom: tool, cursor = self.zoomtool, Qt.ArrowCursor elif action is actions.pan: tool, cursor = self.pantool, Qt.OpenHandCursor else: assert False currenttool.setViewBox(None) tool.setViewBox(self.viewbox) self.viewbox.setCursor(QtGui.QCursor(cursor)) currenttool = tool group.triggered[QAction].connect(activated) def button(action): b = QtGui.QToolButton() b.setDefaultAction(action) return b toollayout.addWidget(button(actions.select)) toollayout.addWidget(button(actions.zoom)) toollayout.addWidget(button(actions.pan)) toollayout.addSpacing(4) toollayout.addWidget(button(actions.zoomtofit)) toollayout.addStretch() toolbox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Maximum) def sizeHint(self): return QSize(800, 500) def clear(self): self.data = None self._subset_mask = None self._selection_mask = None self.varmodel_selected[:] = [] self.varmodel_other[:] = [] self.colorvar_model[:] = [] self.sizevar_model[:] = [] self.shapevar_model[:] = [] self.labelvar_model[:] = [] self.clear_plot() def clear_plot(self): if self._item is not None: self._item.setParentItem(None) self.viewbox.removeItem(self._item) self._item = None self.viewbox.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 set_data(self, data): """ Set the input dataset. """ self.closeContext() self.clear() self.data = data if data is not None: self._initialize(data) # get the default encoded state, replacing the position with Inf state = self._encode_var_state( [list(self.varmodel_selected), list(self.varmodel_other)] ) state = {key: (source_ind, numpy.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 = self._decode_var_state( state, [list(self.varmodel_selected), list(self.varmodel_other)] ) self.varmodel_selected[:] = selected self.varmodel_other[:] = other self._invalidate_plot() def set_subset_data(self, subset): """ Set the supplementary input subset dataset. """ self.subset_data = subset self._subset_mask = None def handleNewSignals(self): if self.subset_data is not None and self._subset_mask is None: # Update the plot's highlight items if self.data is not None: dataids = self.data.ids.ravel() subsetids = numpy.unique(self.subset_data.ids) self._subset_mask = numpy.in1d( dataids, subsetids, assume_unique=True) self._invalidate_plot() self.commit() def customEvent(self, event): if event.type() == OWLinearProjection.ReplotRequest: self.__replot_requested = False self._setup_plot() else: super().customEvent(event) def closeContext(self): self.variable_state = self._encode_var_state( [list(self.varmodel_selected), list(self.varmodel_other)] ) super().closeContext() def _encode_var_state(self, lists): return {(type(var), var.name): (source_ind, pos) for source_ind, var_list in enumerate(lists) for pos, var in enumerate(var_list) if isinstance(var, Orange.data.Variable)} def _decode_var_state(self, state, lists): all_vars = reduce(list.__iadd__, lists, []) newlists = [[] for _ in lists] for var in all_vars: source, pos = state[(type(var), var.name)] newlists[source].append((pos, var)) return [[var for _, var in sorted(newlist, key=itemgetter(0))] for newlist in newlists] def color_var(self): """ Current selected color variable or None (if not selected). """ if 1 <= self.color_index < len(self.colorvar_model): return self.colorvar_model[self.color_index] else: return None def size_var(self): """ Current selected size variable or None (if not selected). """ if 1 <= self.size_index < len(self.sizevar_model): return self.sizevar_model[self.size_index] else: return None def shape_var(self): """ Current selected shape variable or None (if not selected). """ if 1 <= self.shape_index < len(self.shapevar_model): return self.shapevar_model[self.shape_index] else: return None def _initialize(self, data): # Initialize the GUI controls from data's domain. all_vars = list(data.domain.variables) cont_vars = [var for var in data.domain.variables if var.is_continuous] disc_vars = [var for var in data.domain.variables if var.is_discrete] string_vars = [var for var in data.domain.variables if var.is_string] self.all_vars = data.domain.variables self.varmodel_selected[:] = cont_vars[:3] self.varmodel_other[:] = cont_vars[3:] self.colorvar_model[:] = ["Same color"] + all_vars self.sizevar_model[:] = ["Same size"] + cont_vars self.shapevar_model[:] = ["Same shape"] + disc_vars self.labelvar_model[:] = ["No label"] + string_vars if data.domain.has_discrete_class: self.color_index = all_vars.index(data.domain.class_var) + 1 def __activate_selection(self): view = self.other_view model = self.varmodel_other indices = view.selectionModel().selectedRows() variables = [model.data(ind, Qt.EditRole) for ind in indices] for i in sorted((ind.row() for ind in indices), reverse=True): del model[i] self.varmodel_selected.extend(variables) def __deactivate_selection(self): view = self.active_view model = self.varmodel_selected indices = view.selectionModel().selectedRows() variables = [model.data(ind, Qt.EditRole) for ind in indices] for i in sorted((ind.row() for ind in indices), reverse=True): del model[i] self.varmodel_other.extend(variables) def _get_data(self, var): """Return the column data for variable `var`.""" X, _ = self.data.get_column_view(var) return X.ravel() def _setup_plot(self): self.__replot_requested = False self.clear_plot() variables = list(self.varmodel_selected) if not variables: return coords = [self._get_data(var) for var in variables] coords = numpy.vstack(coords) p, N = coords.shape assert N == len(self.data), p == len(variables) axes = linproj.defaultaxes(len(variables)) assert axes.shape == (2, p) mask = ~numpy.logical_or.reduce(numpy.isnan(coords), axis=0) coords = coords[:, mask] X, Y = numpy.dot(axes, coords) X = plotutils.normalized(X) Y = plotutils.normalized(Y) pen_data, brush_data = self._color_data(mask) size_data = self._size_data(mask) shape_data = self._shape_data(mask) if self.jitter_value > 0: value = [0, 0.01, 0.1, 0.5, 1, 2][self.jitter_value] rstate = numpy.random.RandomState(0) jitter_x = (rstate.random_sample(X.shape) * 2 - 1) * value / 100 rstate = numpy.random.RandomState(1) jitter_y = (rstate.random_sample(Y.shape) * 2 - 1) * value / 100 X += jitter_x Y += jitter_y self._item = ScatterPlotItem( X, Y, pen=pen_data, brush=brush_data, size=size_data, shape=shape_data, antialias=True, data=numpy.arange(len(self.data))[mask] ) self._item._mask = mask self.viewbox.addItem(self._item) for i, axis in enumerate(axes.T): axis_item = AxisItem(line=QLineF(0, 0, axis[0], axis[1]), label=variables[i].name) self.viewbox.addItem(axis_item) self.viewbox.setRange(QtCore.QRectF(-1.05, -1.05, 2.1, 2.1)) def _color_data(self, mask=None): color_var = self.color_var() if color_var is not None: color_data = self._get_data(color_var) if color_var.is_continuous: color_data = plotutils.continuous_colors(color_data) else: color_data = plotutils.discrete_colors( color_data, len(color_var.values) ) if mask is not None: color_data = color_data[mask] pen_data = numpy.array( [pg.mkPen((r, g, b, self.alpha_value / 2)) for r, g, b in color_data], dtype=object) brush_data = numpy.array( [pg.mkBrush((r, g, b, self.alpha_value)) for r, g, b in color_data], dtype=object) else: color = QtGui.QColor(Qt.lightGray) color.setAlpha(self.alpha_value) pen_data = QtGui.QPen(color) pen_data.setCosmetic(True) color = QtGui.QColor(Qt.darkGray) color.setAlpha(self.alpha_value) brush_data = QtGui.QBrush(color) if self._subset_mask is not None: assert self._subset_mask.shape == (len(self.data),) if mask is not None: subset_mask = self._subset_mask[mask] else: subset_mask = self._subset_mask if isinstance(brush_data, QtGui.QBrush): brush_data = numpy.array([brush_data] * subset_mask.size, dtype=object) brush_data[~subset_mask] = QtGui.QBrush(Qt.NoBrush) if self._selection_mask is not None: assert self._selection_mask.shape == (len(self.data),) if mask is not None: selection_mask = self._selection_mask[mask] else: selection_mask = self._selection_mask if isinstance(pen_data, QtGui.QPen): pen_data = numpy.array([pen_data] * selection_mask.size, dtype=object) pen_data[selection_mask] = pg.mkPen((200, 200, 0, 150), width=4) return pen_data, brush_data def _on_color_change(self): if self.data is None or self._item is None: return pen, brush = self._color_data() if isinstance(pen, QtGui.QPen): # Reset the brush for all points self._item.data["pen"] = None self._item.setPen(pen) else: self._item.setPen(pen[self._item._mask]) if isinstance(brush, QtGui.QBrush): # Reset the brush for all points self._item.data["brush"] = None self._item.setBrush(brush) else: self._item.setBrush(brush[self._item._mask]) def _shape_data(self, mask): shape_var = self.shape_var() if shape_var is None: shape_data = numpy.array(["o"] * len(self.data)) else: assert shape_var.is_discrete max_symbol = len(ScatterPlotItem.Symbols) - 1 shape = self._get_data(shape_var) shape_mask = numpy.isnan(shape) shape = shape % (max_symbol - 1) shape[shape_mask] = max_symbol symbols = numpy.array(list(ScatterPlotItem.Symbols)) shape_data = symbols[numpy.asarray(shape, dtype=int)] if mask is None: return shape_data else: return shape_data[mask] def _on_shape_change(self): if self.data is None: return self.set_shape(self._shape_data(mask=None)) def _size_data(self, mask=None): size_var = self.size_var() if size_var is None: size_data = numpy.full((len(self.data),), self.point_size) else: size_data = plotutils.normalized(self._get_data(size_var)) size_data -= numpy.nanmin(size_data) size_mask = numpy.isnan(size_data) size_data = \ size_data * self.point_size + OWLinearProjection.MinPointSize size_data[size_mask] = OWLinearProjection.MinPointSize - 2 if mask is None: return size_data else: return size_data[mask] def _on_size_change(self): if self.data is None: return self.set_size(self._size_data(mask=None)) def set_shape(self, shape): """ Set (update) the current point shape map. """ if self._item is not None: self._item.setSymbol(shape[self._item._mask]) def set_size(self, size): """ Set (update) the current point size. """ if self._item is not None: self._item.setSize(size[self._item._mask]) def _set_alpha(self, value): self.alpha_value = value self._on_color_change() def _set_size(self, value): self.point_size = value self._on_size_change() def _selection_finish(self, path): self.select(path) def select(self, selectionshape): item = self._item if item is None: return indices = [spot.data() for spot in item.points() if selectionshape.contains(spot.pos())] if QApplication.keyboardModifiers() & Qt.ControlModifier: self.select_indices(indices) else: self._selection_mask = None self.select_indices(indices) def select_indices(self, indices): if self.data is None: return if self._selection_mask is None: self._selection_mask = numpy.zeros(len(self.data), dtype=bool) self._selection_mask[indices] = True self._on_color_change() self.commit() def commit(self): subset = None if self.data is not None and self._selection_mask is not None: indices = numpy.flatnonzero(self._selection_mask) if len(indices) > 0: subset = self.data[indices] self.send("Selected Data", subset)