def test_arrowannotation(self):
        item = ArrowItem()
        self.scene.addItem(item)
        item.setLine(QLineF(100, 100, 100, 200))
        item.setLineWidth(5)

        item = ArrowItem()
        item.setLine(QLineF(150, 100, 150, 200))
        item.setLineWidth(10)
        item.setArrowStyle(ArrowItem.Concave)
        self.scene.addItem(item)

        item = ArrowAnnotation()
        item.setPos(10, 10)
        item.setLine(QLineF(10, 10, 200, 200))

        self.scene.addItem(item)
        item.setLineWidth(5)

        def advance():
            clock = time.process_time() * 10
            item.setLineWidth(5 + math.sin(clock) * 5)
            item.setColor(QColor(Qt.red).lighter(100 + 30 * math.cos(clock)))

        timer = QTimer(item, interval=10)
        timer.timeout.connect(advance)
        timer.start()
        self.app.exec_()
Exemple #2
0
    def commit(self):
        self.cancel()
        self.clear_messages()

        # Some time may pass before the new scores are computed, so clear the
        # old scores to avoid potential confusion. Hiding the mainArea could
        # cause flickering when the clusters are computed quickly, so this is
        # the better alternative
        self.table_model.clear_scores()
        self.mainArea.setVisible(self.optimize_k and self.data is not None and
                                 self.has_attributes)

        if self.data is None:
            self.send_data()
            return

        if not self.has_attributes:
            self.Error.no_attributes()
            self.send_data()
            return

        if self.optimize_k:
            self.run_optimization()
        else:
            self.cluster()

        QTimer.singleShot(100, self.adjustSize)
Exemple #3
0
    def evalJS(self, code):
        """
        Evaluate JavaScript code synchronously (or sequentially, at least).

        Parameters
        ----------
        code : str
            The JavaScript code to evaluate in main page frame. The scope is
            not assured. Assign properties to window if you want to make them
            available elsewhere.

        Warnings
        --------
        In the case of Qt WebEngine implementation, this function calls:

            QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents)

        until the page is fully loaded, all the objects exposed via
        exposeObject() method are indeed exposed in JS, and the code `code`
        has finished evaluating.
        """

        def _later():
            if not self.__is_init and self.__js_queue:
                return QTimer.singleShot(1, _later)
            if self.__js_queue:
                # '/n' is required when the last line is a comment
                code = '\n;'.join(self.__js_queue)
                self.__js_queue.clear()
                self._evalJS(code)

        self.__js_queue.append(code)
        QTimer.singleShot(1, _later)
Exemple #4
0
    def _create_layout(self):
        box = gui.widgetBox(self.controlArea,
                            orientation='horizontal')
        self.varmodel = VariableListModel(parent=self)
        self.attr_combo = gui.comboBox(box, self, 'selected_attr',
                                       orientation=Qt.Horizontal,
                                       label='Region attribute:',
                                       callback=self.on_attr_change,
                                       sendSelectedValue=True)
        self.attr_combo.setModel(self.varmodel)
        self.map_combo = gui.comboBox(box, self, 'selected_map',
                                      orientation=Qt.Horizontal,
                                      label='Map type:',
                                      callback=self.on_map_change,
                                      items=Map.all)
        hexpand = QSizePolicy(QSizePolicy.Expanding,
                              QSizePolicy.Fixed)
        self.attr_combo.setSizePolicy(hexpand)
        self.map_combo.setSizePolicy(hexpand)

        url = urljoin('file:',
                      pathname2url(os.path.join(
                          os.path.dirname(__file__),
                          'resources',
                         'owgeomap.html')))
        self.webview = gui.WebviewWidget(self.controlArea, self, url=QUrl(url))
        self.controlArea.layout().addWidget(self.webview)

        QTimer.singleShot(
            0, lambda: self.webview.evalJS('REGIONS = {};'.format({Map.WORLD: CC_WORLD,
                                                                   Map.EUROPE: CC_EUROPE,
                                                                   Map.USA: CC_USA})))
Exemple #5
0
class QCoreAppTestCase(unittest.TestCase):
    _AppClass = QCoreApplication

    @classmethod
    def setUpClass(cls):
        super(QCoreAppTestCase, cls).setUpClass()
        app = cls._AppClass.instance()
        if app is None:
            app = cls._AppClass([])
        cls.app = app

    def setUp(self):
        super(QCoreAppTestCase, self).setUp()
        self._quittimer = QTimer(interval=1000)
        self._quittimer.timeout.connect(self.app.quit)
        self._quittimer.start()

    def tearDown(self):
        self._quittimer.stop()
        self._quittimer.timeout.disconnect(self.app.quit)
        self._quittimer = None
        super(QCoreAppTestCase, self).tearDown()

    @classmethod
    def tearDownClass(cls):
        gc.collect()
        cls.app = None
        super(QCoreAppTestCase, cls).tearDownClass()
    def test_dynamic_link(self):
        link = LinkItem()
        anchor1 = AnchorPoint()
        anchor2 = AnchorPoint()

        self.scene.addItem(link)
        self.scene.addItem(anchor1)
        self.scene.addItem(anchor2)

        link.setSourceItem(None, anchor1)
        link.setSinkItem(None, anchor2)

        anchor2.setPos(100, 100)

        link.setSourceName("1")
        link.setSinkName("2")

        link.setDynamic(True)
        self.assertTrue(link.isDynamic())

        link.setDynamicEnabled(True)
        self.assertTrue(link.isDynamicEnabled())

        def advance():
            clock = time.process_time()
            link.setDynamic(clock > 1)
            link.setDynamicEnabled(int(clock) % 2 == 0)

        timer = QTimer(link, interval=0)
        timer.timeout.connect(advance)
        timer.start()
        self.app.exec_()
Exemple #7
0
 def show_settings(self):
     self._hotkey.unregister(winid=self.winId())
     dlg = SettingsDialog(self)
     dlg.show()
     dlg.exec_()
     # WORKAROUND: On windows calling register_hotkeys() directly often results in a crash.
     QTimer.singleShot(10, self.register_hotkeys)
 def x11_event(self, event, data):
     if event.type == X.KeyPress:
         key = (event.detail, int(event.state) & (X.ShiftMask | X.ControlMask | X.Mod1Mask | X.Mod4Mask))
         if key in self.shortcuts:
             # noinspection PyCallByClass,PyTypeChecker
             QTimer.singleShot(0, self.shortcuts[key])
     return False
    def test_outputview(self):
        output = OutputView()
        output.show()

        line1 = "A line \n"
        line2 = "A different line\n"
        output.write(line1)
        self.assertEqual(output.toPlainText(), line1)

        output.write(line2)
        self.assertEqual(output.toPlainText(), line1 + line2)

        output.clear()
        self.assertEqual(output.toPlainText(), "")

        output.writelines([line1, line2])
        self.assertEqual(output.toPlainText(), line1 + line2)

        output.setMaximumLines(5)

        def advance():
            now = datetime.now().strftime("%c\n")
            output.write(now)

            text = output.toPlainText()
            self.assertLessEqual(len(text.splitlines()), 5)

        timer = QTimer(output, interval=500)
        timer.timeout.connect(advance)
        timer.start()
        self.app.exec_()
Exemple #10
0
    def setUp(self):
        import logging

        from AnyQt.QtWidgets import \
            QApplication, QGraphicsScene, QGraphicsView
        from AnyQt.QtGui import  QPainter
        from AnyQt.QtCore import QTimer

        logging.basicConfig()

        self.app = QApplication([])
        self.scene = QGraphicsScene()
        self.view = QGraphicsView(self.scene)
        self.view.setRenderHints(
            QPainter.Antialiasing | \
            QPainter.SmoothPixmapTransform | \
            QPainter.TextAntialiasing
            )
        self.view.resize(500, 300)
        self.view.show()
        QTimer.singleShot(10000, self.app.exit)

        def my_excepthook(*args):
            sys.setrecursionlimit(1010)
            traceback.print_exc(limit=4)

        self._orig_excepthook = sys.excepthook
        sys.excepthook = my_excepthook
        self.singleShot = QTimer.singleShot
 def test_drop_shadow_old(self):
     w = dropshadow._DropShadowWidget()
     w.setContentsMargins(20, 20, 20, 20)
     w.setLayout(QHBoxLayout())
     w.layout().setContentsMargins(0, 0, 0, 0)
     w.layout().addWidget(QListView())
     w.show()
     QTimer.singleShot(1500, lambda: w.setRadius(w.radius + 5))
     self.app.exec_()
Exemple #12
0
        def add_points():
            nonlocal cur, image_token
            if image_token != self._image_token:
                return
            batch = visible[cur:cur + self.N_POINTS_PER_ITER]

            batch_lat = lat[batch]
            batch_lon = lon[batch]

            x, y = self.Projection.latlon_to_easting_northing(batch_lat, batch_lon)
            x, y = self.Projection.easting_northing_to_pixel(x, y, zoom, origin, map_pane_pos)

            if self._jittering:
                dx, dy = self._jittering_offsets[batch].T
                x, y = x + dx, y + dy

            colors = (self._colorgen.getRGB(self._scaled_color_values[batch]).tolist()
                      if self._color_attr else
                      repeat((0xff, 0, 0)))
            sizes = self._size_coef * \
                (self._sizes[batch] if self._size_attr else np.tile(10, len(batch)))

            for x, y, is_selected, size, color, _in_subset in \
                    zip(x, y, selected[batch], sizes, colors, in_subset[batch]):

                pensize2, selpensize2 = (.35, 1.5) if size >= 5 else (.15, .7)
                pensize2 *= self._size_coef
                selpensize2 *= self._size_coef

                size2 = size / 2
                if is_selected:
                    painter.setPen(QPen(QBrush(Qt.green), 2 * selpensize2))
                    painter.drawEllipse(x - size2 - selpensize2,
                                        y - size2 - selpensize2,
                                        size + selpensize2,
                                        size + selpensize2)
                color = QColor(*color)
                color.setAlpha(self._opacity)
                painter.setBrush(QBrush(color) if _in_subset else Qt.NoBrush)
                painter.setPen(QPen(QBrush(color.darker(180)), 2 * pensize2))
                painter.drawEllipse(x - size2 - pensize2,
                                    y - size2 - pensize2,
                                    size + pensize2,
                                    size + pensize2)

            im.save(self._overlay_image_path, 'PNG')
            self.evalJS('markersImageLayer.setUrl("{}#{}"); 0;'
                        .format(self.toFileURL(self._overlay_image_path),
                                np.random.random()))

            cur += self.N_POINTS_PER_ITER
            if cur < len(visible):
                QTimer.singleShot(10, add_points)
                self._owwidget.progressBarAdvance(100 / n_iters, None)
            else:
                self._owwidget.progressBarFinished(None)
Exemple #13
0
 def __init__(self):
     super().__init__()
     self.name_lineedit = None
     self.data = None
     self.learner = None
     self.model = None
     self.preprocessors = None
     self.outdated_settings = False
     self.setup_layout()
     QTimer.singleShot(0, self.apply)
Exemple #14
0
 def _translate_message_hook(self, pmsg: MSG):
     msg = pmsg.contents
     if msg.message == WM_HOTKEY:
         keycode = (msg.lParam >> 16) & 0xFFFF
         modifiers = msg.lParam & 0xFFFF
         key = (keycode, modifiers)
         if key in self.shortcuts:
             QTimer.singleShot(0, self.shortcuts[key])
             return False
     return self._TranslateMessageReal(pmsg)
Exemple #15
0
 def __init__(self):
     super().__init__()
     self.data = None
     self.valid_data = False
     self.learner = None
     self.model = None
     self.preprocessors = None
     self.outdated_settings = False
     self.setup_layout()
     QTimer.singleShot(0, getattr(self, "unconditional_apply", self.apply))
Exemple #16
0
 def on_data(self, data):
     if data and not isinstance(data, Corpus):
         data = Corpus.from_table(data.domain, data)
     self.data = data
     self._repopulate_attr_combo(data)
     if not data:
         self.region_selected('')
         QTimer.singleShot(0, lambda: self.webview.evalJS('DATA = {}; renderMap();'))
     else:
         QTimer.singleShot(0, self.on_attr_change)
    def test_framelesswindow(self):
        window = FramelessWindow()
        window.show()

        def cycle():
            window.setRadius((window.radius() + 3) % 30)

        timer = QTimer(window, interval=250)
        timer.timeout.connect(cycle)
        timer.start()
        self.app.exec_()
 def __init__(self):
     super().__init__()
     self.embedders = sorted(list(EMBEDDERS_INFO),
                             key=lambda k: EMBEDDERS_INFO[k]['order'])
     self._image_attributes = None
     self._input_data = None
     self._log = logging.getLogger(__name__)
     self._task = None
     self._setup_layout()
     self._image_embedder = None
     self._executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
     self.setBlocking(True)
     QTimer.singleShot(0, self._init_server_connection)
Exemple #19
0
 def apply(self):
     self.clear_messages()
     if self.data is not None:
         if self.optimize_k and self.run_optimization():
             self.mainArea.show()
             self.update_results()
         else:
             self.cluster()
             self.mainArea.hide()
     else:
         self.mainArea.hide()
     QTimer.singleShot(100, self.adjustSize)
     self.send_data()
    def test_layout(self):
        one_desc, negate_desc, cons_desc = self.widget_desc()
        one_item = NodeItem()
        one_item.setWidgetDescription(one_desc)
        one_item.setPos(0, 150)
        self.scene.add_node_item(one_item)

        cons_item = NodeItem()
        cons_item.setWidgetDescription(cons_desc)
        cons_item.setPos(200, 0)
        self.scene.add_node_item(cons_item)

        negate_item = NodeItem()
        negate_item.setWidgetDescription(negate_desc)
        negate_item.setPos(200, 300)
        self.scene.add_node_item(negate_item)

        link = LinkItem()
        link.setSourceItem(one_item)
        link.setSinkItem(negate_item)
        self.scene.add_link_item(link)

        link = LinkItem()
        link.setSourceItem(one_item)
        link.setSinkItem(cons_item)
        self.scene.add_link_item(link)

        layout = AnchorLayout()
        self.scene.addItem(layout)
        self.scene.set_anchor_layout(layout)

        layout.invalidateNode(one_item)
        layout.activate()

        p1, p2 = one_item.outputAnchorItem.anchorPositions()
        self.assertTrue(p1 > p2)

        self.scene.node_item_position_changed.connect(layout.invalidateNode)

        path = QPainterPath()
        path.addEllipse(125, 0, 50, 300)

        def advance():
            t = time.process_time()
            cons_item.setPos(path.pointAtPercent(t % 1.0))
            negate_item.setPos(path.pointAtPercent((t + 0.5) % 1.0))

        timer = QTimer(negate_item, interval=20)
        timer.start()
        timer.timeout.connect(advance)
        self.app.exec_()
Exemple #21
0
    def __init__(self, parent=None, items=None):
        QStandardItemModel.__init__(self, parent)

        if items is not None:
            self.insertColumn(0, items)

        self.__timer = QTimer(self)
Exemple #22
0
    def __init__(self):
        super().__init__()
        #: Input dissimilarity matrix
        self.matrix = None  # type: Optional[DistMatrix]
        #: Data table from the `self.matrix.row_items` (if present)
        self.matrix_data = None  # type: Optional[Table]
        #: Input data table
        self.signal_data = None

        self.__invalidated = True
        self.embedding = None
        self.effective_matrix = None

        self.__update_loop = None
        # timer for scheduling updates
        self.__timer = QTimer(self, singleShot=True, interval=0)
        self.__timer.timeout.connect(self.__next_step)
        self.__state = OWMDS.Waiting
        self.__in_next_step = False

        self.graph.pause_drawing_pairs()

        self.size_model = self.gui.points_models[2]
        self.size_model.order = \
            self.gui.points_models[2].order[:1] \
            + ("Stress", ) + \
            self.gui.points_models[2].order[1:]
Exemple #23
0
    def __init__(self, parent=None):
        QObject.__init__(self, parent)
        self.__scheme = None
        self.__signal_manager = None
        self.__widgets = []
        self.__initstate_for_node = {}
        self.__creation_policy = WidgetManager.Normal
        #: a queue of all nodes whose widgets are scheduled for
        #: creation/initialization
        self.__init_queue = deque()  # type: Deque[SchemeNode]
        #: Timer for scheduling widget initialization
        self.__init_timer = QTimer(self, interval=0, singleShot=True)
        self.__init_timer.timeout.connect(self.__create_delayed)

        #: A mapping of SchemeNode -> OWWidget (note: a mapping is only added
        #: after the widget is actually created)
        self.__widget_for_node = {}
        #: a mapping of OWWidget -> SchemeNode
        self.__node_for_widget = {}

        # Widgets that were 'removed' from the scheme but were at
        # the time in an input update loop and could not be deleted
        # immediately
        self.__delay_delete = set()

        #: Deleted/removed during creation/initialization.
        self.__delete_after_create = []

        #: processing state flags for all widgets (including the ones
        #: in __delay_delete).
        #: Note: widgets which have not yet been created do not have an entry
        self.__widget_processing_state = {}

        # Tracks the widget in the update loop by the SignalManager
        self.__updating_widget = None
Exemple #24
0
    def __init__(self, *args):
        super().__init__(*args)
        self.setAlignment(Qt.AlignTop | Qt.AlignLeft)

        self.__backgroundIcon = QIcon()

        self.__autoScroll = False
        self.__autoScrollMargin = 16
        self.__autoScrollTimer = QTimer(self)
        self.__autoScrollTimer.timeout.connect(self.__autoScrollAdvance)

        # scale factor accumulating partial increments from wheel events
        self.__zoomLevel = 100
        # effective scale level(rounded to whole integers)
        self.__effectiveZoomLevel = 100

        self.__zoomInAction = QAction(
            self.tr("Zoom in"), self, objectName="action-zoom-in",
            shortcut=QKeySequence.ZoomIn,
            triggered=self.zoomIn,
        )

        self.__zoomOutAction = QAction(
            self.tr("Zoom out"), self, objectName="action-zoom-out",
            shortcut=QKeySequence.ZoomOut,
            triggered=self.zoomOut
        )
        self.__zoomResetAction = QAction(
            self.tr("Reset Zoom"), self, objectName="action-zoom-reset",
            triggered=self.zoomReset,
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0)
        )
Exemple #25
0
 def __init__(self, parent, plot):
     super().__init__(parent, plot)
     self.__timer = QTimer(self, interval=50)
     self.__timer.timeout.connect(self.__timeout)
     self._radius = 20.0
     self._density = 4.0
     self._pos = None
Exemple #26
0
    def __init__(self, parent=None, **kwargs):
        QWidget.__init__(self, parent, **kwargs)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        self.setLayout(layout)

        self.setSizePolicy(QSizePolicy.Fixed,
                           QSizePolicy.Expanding)
        self.__tabs = []

        self.__currentIndex = -1
        self.__changeOnHover = False

        self.__iconSize = QSize(26, 26)

        self.__group = QButtonGroup(self, exclusive=True)
        self.__group.buttonPressed[QAbstractButton].connect(
            self.__onButtonPressed
        )
        self.setMouseTracking(True)

        self.__sloppyButton = None
        self.__sloppyRegion = QRegion()
        self.__sloppyTimer = QTimer(self, singleShot=True)
        self.__sloppyTimer.timeout.connect(self.__onSloppyTimeout)
    def __init__(self, parent=None, items=None):
        super().__init__(parent)

        if items is not None:
            self.insertColumn(0, items)

        self.__timer = QTimer(self)
Exemple #28
0
 def _later():
     if not self.__is_init and self.__js_queue:
         return QTimer.singleShot(1, _later)
     if self.__js_queue:
         code = ";".join(self.__js_queue)
         self.__js_queue.clear()
         self._evalJS(code)
Exemple #29
0
def test_main():
    from AnyQt.QtWidgets import QApplication
    from Orange.modelling import KNNLearner as Learner
    a = QApplication([])

    ow = OWMap()
    ow.show()
    ow.raise_()
    data = Table('philadelphia-crime')
    ow.set_data(data)

    QTimer.singleShot(10, lambda: ow.set_learner(Learner()))

    ow.handleNewSignals()
    a.exec()
    ow.saveSettings()
Exemple #30
0
 def _later():
     if not self.__is_init and self.__js_queue:
         return QTimer.singleShot(1, _later)
     if self.__js_queue:
         # '/n' is required when the last line is a comment
         code = '\n;'.join(self.__js_queue)
         self.__js_queue.clear()
         self._evalJS(code)
Exemple #31
0
 def update_edge(self):
     for edge in self.graph_edges():
         if edge.node1 is self:
             QTimer.singleShot(0, edge.update_ends)
         elif edge.node2 is self:
             edge.setVisible(self.isVisible())
Exemple #32
0
class VizRankDialog(QDialog, ProgressBarMixin, WidgetMessagesMixin):
    """
    Base class for VizRank dialogs, providing a GUI with a table and a button,
    and the skeleton for managing the evaluation of visualizations.

    Derived classes must provide methods

    - `iterate_states` for generating combinations (e.g. pairs of attritutes),
    - `compute_score(state)` for computing the score of a combination,
    - `row_for_state(state)` that returns a list of items inserted into the
       table for the given state.

    and, optionally,

    - `state_count` that returns the number of combinations (used for progress
       bar)
    - `on_selection_changed` that handles event triggered when the user selects
      a table row. The method should emit signal
      `VizRankDialog.selectionChanged(object)`.
    - `bar_length` returns the length of the bar corresponding to the score.

    The class provides a table and a button. A widget constructs a single
    instance of this dialog in its `__init__`, like (in Sieve) by using a
    convenience method :obj:`add_vizrank`::

        self.vizrank, self.vizrank_button = SieveRank.add_vizrank(
            box, self, "Score Combinations", self.set_attr)

    When the widget receives new data, it must call the VizRankDialog's
    method :obj:`VizRankDialog.initialize()` to clear the GUI and reset the
    state.

    Clicking the Start button calls method `run` (and renames the button to
    Pause). Run sets up a progress bar by getting the number of combinations
    from :obj:`VizRankDialog.state_count()`. It restores the paused state
    (if any) and calls generator :obj:`VizRankDialog.iterate_states()`. For
    each generated state, it calls :obj:`VizRankDialog.score(state)`, which
    must return the score (lower is better) for this state. If the returned
    state is not `None`, the data returned by `row_for_state` is inserted at
    the appropriate place in the table.

    Args:
        master (Orange.widget.OWWidget): widget to which the dialog belongs

    Attributes:
        master (Orange.widget.OWWidget): widget to which the dialog belongs
        captionTitle (str): the caption for the dialog. This can be a class
          attribute. `captionTitle` is used by the `ProgressBarMixin`.
    """

    captionTitle = ""

    processingStateChanged = Signal(int)
    progressBarValueChanged = Signal(float)
    messageActivated = Signal(Msg)
    messageDeactivated = Signal(Msg)
    selectionChanged = Signal(object)

    class Information(WidgetMessagesMixin.Information):
        nothing_to_rank = Msg("There is nothing to rank.")

    def __init__(self, master):
        """Initialize the attributes and set up the interface"""
        QDialog.__init__(self, master, windowTitle=self.captionTitle)
        WidgetMessagesMixin.__init__(self)
        self.setLayout(QVBoxLayout())

        self.insert_message_bar()
        self.layout().insertWidget(0, self.message_bar)
        self.master = master

        self.keep_running = False
        self.scheduled_call = None
        self.saved_state = None
        self.saved_progress = 0
        self.scores = []
        self.add_to_model = queue.Queue()

        self.update_timer = QTimer(self)
        self.update_timer.timeout.connect(self._update)
        self.update_timer.setInterval(200)

        self._thread = None
        self._worker = None

        self.filter = QLineEdit()
        self.filter.setPlaceholderText("Filter ...")
        self.filter.textChanged.connect(self.filter_changed)
        self.layout().addWidget(self.filter)
        # Remove focus from line edit
        self.setFocus(Qt.ActiveWindowFocusReason)

        self.rank_model = QStandardItemModel(self)
        self.model_proxy = QSortFilterProxyModel(self)
        self.model_proxy.setSourceModel(self.rank_model)
        self.rank_table = view = QTableView(
            selectionBehavior=QTableView.SelectRows,
            selectionMode=QTableView.SingleSelection,
            showGrid=False)
        if self._has_bars:
            view.setItemDelegate(TableBarItem())
        else:
            view.setItemDelegate(HorizontalGridDelegate())
        view.setModel(self.model_proxy)
        view.selectionModel().selectionChanged.connect(
            self.on_selection_changed)
        view.horizontalHeader().setStretchLastSection(True)
        view.horizontalHeader().hide()
        self.layout().addWidget(view)

        self.button = gui.button(
            self, self, "Start", callback=self.toggle, default=True)

    @property
    def _has_bars(self):
        return type(self).bar_length is not VizRankDialog.bar_length

    @classmethod
    def add_vizrank(cls, widget, master, button_label, set_attr_callback):
        """
        Equip the widget with VizRank button and dialog, and monkey patch the
        widget's `closeEvent` and `hideEvent` to close/hide the vizrank, too.

        Args:
            widget (QWidget): the widget into whose layout to insert the button
            master (Orange.widgets.widget.OWWidget): the master widget
            button_label: the label for the button
            set_attr_callback: the callback for setting the projection chosen
                in the vizrank

        Returns:
            tuple with Vizrank dialog instance and push button
        """
        # Monkey patching could be avoided by mixing-in the class (not
        # necessarily a good idea since we can make a mess of multiple
        # defined/derived closeEvent and hideEvent methods). Furthermore,
        # per-class patching would be better than per-instance, but we don't
        # want to mess with meta-classes either.

        vizrank = cls(master)
        button = gui.button(
            widget, master, button_label, callback=vizrank.reshow,
            enabled=False)
        vizrank.selectionChanged.connect(lambda args: set_attr_callback(*args))

        master_close_event = master.closeEvent
        master_hide_event = master.hideEvent
        master_delete_event = master.onDeleteWidget

        def closeEvent(event):
            vizrank.close()
            master_close_event(event)

        def hideEvent(event):
            vizrank.hide()
            master_hide_event(event)

        def deleteEvent():
            vizrank.keep_running = False
            if vizrank._thread is not None and vizrank._thread.isRunning():
                vizrank._thread.quit()
                vizrank._thread.wait()

            master_delete_event()

        master.closeEvent = closeEvent
        master.hideEvent = hideEvent
        master.onDeleteWidget = deleteEvent
        return vizrank, button

    def reshow(self):
        """Put the widget on top of all windows
        """
        self.show()
        self.raise_()
        self.activateWindow()

    def initialize(self):
        """
        Clear and initialize the dialog.

        This method must be called by the widget when the data is reset,
        e.g. from `set_data` handler.
        """
        if self._thread is not None and self._thread.isRunning():
            self.keep_running = False
            self._thread.quit()
            self._thread.wait()
        self.keep_running = False
        self.scheduled_call = None
        self.saved_state = None
        self.saved_progress = 0
        self.update_timer.stop()
        self.progressBarFinished()
        self.scores = []
        self._update_model()  # empty queue
        self.rank_model.clear()
        self.button.setText("Start")
        self.button.setEnabled(self.check_preconditions())
        self._thread = QThread(self)
        self._worker = Worker(self)
        self._worker.moveToThread(self._thread)
        self._worker.stopped.connect(self._thread.quit)
        self._worker.stopped.connect(self._select_first_if_none)
        self._worker.stopped.connect(self._stopped)
        self._worker.done.connect(self._done)
        self._thread.started.connect(self._worker.do_work)

    def filter_changed(self, text):
        self.model_proxy.setFilterFixedString(text)

    def stop_and_reset(self, reset_method=None):
        if self.keep_running:
            self.scheduled_call = reset_method or self.initialize
            self.keep_running = False
        else:
            self.initialize()

    def check_preconditions(self):
        """Check whether there is sufficient data for ranking."""
        return True

    def on_selection_changed(self, selected, deselected):
        """
        Set the new visualization in the widget when the user select a
        row in the table.

        If derived class does not reimplement this, the table gives the
        information but the user can't click it to select the visualization.

        Args:
            selected: the index of the selected item
            deselected: the index of the previously selected item
        """
        pass

    def iterate_states(self, initial_state):
        """
        Generate all possible states (e.g. attribute combinations) for the
        given data. The content of the generated states is specific to the
        visualization.

        This method must be defined in the derived classes.

        Args:
            initial_state: initial state; None if this is the first call
        """
        raise NotImplementedError

    def state_count(self):
        """
        Return the number of states for the progress bar.

        Derived classes should implement this to ensure the proper behaviour of
        the progress bar"""
        return 0

    def compute_score(self, state):
        """
        Abstract method for computing the score for the given state. Smaller
        scores are better.

        Args:
            state: the state, e.g. the combination of attributes as generated
                by :obj:`state_count`.
        """
        raise NotImplementedError

    def bar_length(self, score):
        """Compute the bar length (between 0 and 1) corresponding to the score.
        Return `None` if the score cannot be normalized.
        """
        return None

    def row_for_state(self, score, state):
        """
        Abstract method that return the items that are inserted into the table.

        Args:
            score: score, computed by :obj:`compute_score`
            state: the state, e.g. combination of attributes
            """
        raise NotImplementedError

    def _select_first_if_none(self):
        if not self.rank_table.selectedIndexes():
            self.rank_table.selectRow(0)

    def _done(self):
        self.button.setText("Finished")
        self.button.setEnabled(False)
        self.keep_running = False
        self.saved_state = None

    def _stopped(self):
        self.update_timer.stop()
        self.progressBarFinished()
        self._update_model()
        self.stopped()
        if self.scheduled_call:
            self.scheduled_call()

    def _update(self):
        self._update_model()
        self._update_progress()

    def _update_progress(self):
        self.progressBarSet(int(self.saved_progress * 100 / max(1, self.state_count())))

    def _update_model(self):
        try:
            while True:
                pos, row_items = self.add_to_model.get_nowait()
                self.rank_model.insertRow(pos, row_items)
        except queue.Empty:
            pass

    def toggle(self):
        """Start or pause the computation."""
        self.keep_running = not self.keep_running
        if self.keep_running:
            self.button.setText("Pause")
            self.progressBarInit()
            self.update_timer.start()
            self.before_running()
            self._thread.start()
        else:
            self.button.setText("Continue")
            self._thread.quit()
            # Need to sync state (the worker must read the keep_running
            # state and stop) for reliable restart.
            self._thread.wait()

    def before_running(self):
        """Code that is run before running vizrank in its own thread"""
        pass

    def stopped(self):
        """Code that is run after stopping the vizrank thread"""
        pass
class WidgetManager(QObject):
    """
    WidgetManager class is responsible for creation, tracking and deletion
    of UI elements constituting an interactive workflow.

    It does so by reacting to changes in the underlying workflow model,
    creating and destroying the components when needed.

    This is an abstract class, subclassed MUST reimplement at least
    :func:`create_widget_for_node` and :func:`delete_widget_for_node`.

    The widgets created with :func:`create_widget_for_node` will automatically
    receive dispatched events:

        * :data:`WorkflowEvent.InputLinkAdded` - when a new input link is added to
          the workflow.
        * :data:`LinkEvent.InputLinkRemoved` - when a input link is removed
        * :data:`LinkEvent.OutputLinkAdded` - when a new output link is added to
          the workflow
        * :data:`LinkEvent.InputLinkRemoved` - when a output link is removed
        * :data:`WorkflowEnvEvent.WorkflowEnvironmentChanged` - when the
          workflow environment changes.

    .. seealso:: :func:`.Scheme.add_link()`, :func:`Scheme.remove_link`,
                 :func:`.Scheme.runtime_env`
    """
    #: A new QWidget was created and added by the manager.
    widget_for_node_added = Signal(SchemeNode, QWidget)

    #: A QWidget was removed, hidden and will be deleted when appropriate.
    widget_for_node_removed = Signal(SchemeNode, QWidget)

    class CreationPolicy(enum.Enum):
        """
        Widget Creation Policy.
        """
        #: Widgets are scheduled to be created from the event loop, or when
        #: first accessed with `widget_for_node`
        Normal = "Normal"
        #: Widgets are created immediately when a node is added to the
        #: workflow model.
        Immediate = "Immediate"
        #: Widgets are created only when first accessed with `widget_for_node`
        #: (e.g. when activated in the view).
        OnDemand = "OnDemand"

    Normal = CreationPolicy.Normal
    Immediate = CreationPolicy.Immediate
    OnDemand = CreationPolicy.OnDemand

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__workflow = None  # type: Optional[Scheme]
        self.__creation_policy = WidgetManager.Normal
        self.__float_widgets_on_top = False

        self.__item_for_node = {}  # type: Dict[SchemeNode, Item]
        self.__item_for_widget = {}  # type: Dict[QWidget, Item]

        self.__init_queue = deque()  # type: Deque[SchemeNode]

        self.__init_timer = QTimer(self, singleShot=True)
        self.__init_timer.timeout.connect(self.__process_init_queue)

        self.__activation_monitor = ActivationMonitor(self)
        self.__activation_counter = itertools.count()
        self.__activation_monitor.activated.connect(self.__mark_activated)

    def set_workflow(self, workflow):
        # type: (Scheme) -> None
        """
        Set the workflow.
        """
        if workflow is self.__workflow:
            return

        if self.__workflow is not None:
            # cleanup
            for node in self.__workflow.nodes:
                self.__remove_node(node)
            self.__workflow.node_added.disconnect(self.__on_node_added)
            self.__workflow.node_removed.disconnect(self.__on_node_removed)
            self.__workflow.link_added.disconnect(self.__on_link_added)
            self.__workflow.link_removed.disconnect(self.__on_link_removed)
            self.__workflow.runtime_env_changed.disconnect(
                self.__on_env_changed)
            self.__workflow.removeEventFilter(self)

        self.__workflow = workflow

        workflow.node_added.connect(self.__on_node_added, Qt.UniqueConnection)
        workflow.node_removed.connect(self.__on_node_removed,
                                      Qt.UniqueConnection)
        workflow.link_added.connect(self.__on_link_added, Qt.UniqueConnection)
        workflow.link_removed.connect(self.__on_link_removed,
                                      Qt.UniqueConnection)
        workflow.runtime_env_changed.connect(self.__on_env_changed,
                                             Qt.UniqueConnection)
        workflow.installEventFilter(self)
        for node in workflow.nodes:
            self.__add_node(node)

    def workflow(self):
        # type: () -> Optional[Workflow]
        return self.__workflow

    scheme = workflow
    set_scheme = set_workflow

    def set_creation_policy(self, policy):
        # type: (CreationPolicy) -> None
        """
        Set the widget creation policy.
        """
        if self.__creation_policy != policy:
            self.__creation_policy = policy
            if self.__creation_policy == WidgetManager.Immediate:
                self.__init_timer.stop()
                # create all
                if self.__workflow is not None:
                    for node in self.__workflow.nodes:
                        self.ensure_created(node)
            elif self.__creation_policy == WidgetManager.Normal:
                if not self.__init_timer.isActive() and self.__init_queue:
                    self.__init_timer.start()
            elif self.__creation_policy == WidgetManager.OnDemand:
                self.__init_timer.stop()
            else:
                assert False

    def creation_policy(self):
        """
        Return the current widget creation policy.
        """
        return self.__creation_policy

    def create_widget_for_node(self, node):
        # type: (SchemeNode) -> QWidget
        """
        Create and initialize a widget for node.

        This is an abstract method. Subclasses must reimplemented it.
        """
        raise NotImplementedError()

    def delete_widget_for_node(self, node, widget):
        # type: (SchemeNode, QWidget) -> None
        """
        Remove and delete widget for node.

        This is an abstract method. Subclasses must reimplemented it.
        """
        raise NotImplementedError()

    def node_for_widget(self, widget):
        # type: (QWidget) -> Optional[SchemeNode]
        """
        Return the node for widget.
        """
        item = self.__item_for_widget.get(widget)
        if item is not None:
            return item.node
        else:
            return None

    def widget_for_node(self, node):
        # type: (SchemeNode) -> Optional[QWidget]
        """
        Return the widget for node.
        """
        self.ensure_created(node)
        item = self.__item_for_node.get(node)
        return item.widget if item is not None else None

    def __add_widget_for_node(self, node):
        # type: (SchemeNode) -> None
        item = self.__item_for_node.get(node)
        if item is not None:
            return
        if self.__workflow is None:
            return

        if node not in self.__workflow.nodes:
            return

        if node in self.__init_queue:
            self.__init_queue.remove(node)

        item = Item(node, None, -1)
        # Insert on the node -> item mapping.
        self.__item_for_node[node] = item
        log.debug("Creating widget for node %s", node)
        try:
            w = self.create_widget_for_node(node)
        except Exception:  # pylint: disable=broad-except
            log.critical("", exc_info=True)
            lines = traceback.format_exception(*sys.exc_info())
            text = "".join(lines)
            errorwidget = QLabel(textInteractionFlags=Qt.TextSelectableByMouse,
                                 wordWrap=True,
                                 objectName="widgetmanager-error-placeholder",
                                 text="<pre>" + escape(text) + "</pre>")
            item.errorwidget = errorwidget
            node.set_state_message(UserMessage(text, UserMessage.Error, ""))
            raise
        else:
            item.widget = w
            self.__item_for_widget[w] = item

        self.__set_float_on_top_flag(w)

        if w.windowIcon().isNull():
            desc = node.description
            w.setWindowIcon(icon_loader.from_description(desc).get(desc.icon))
        if not w.windowTitle():
            w.setWindowTitle(node.title)

        w.installEventFilter(self.__activation_monitor)
        raise_canvas = QAction(
            self.tr("Raise Canvas to Front"),
            w,
            objectName="action-canvas-raise-canvas",
            toolTip=self.tr("Raise containing canvas workflow window"),
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_Up))
        raise_canvas.triggered.connect(self.__on_activate_parent)
        raise_descendants = QAction(
            self.tr("Raise Descendants"),
            w,
            objectName="action-canvas-raise-descendants",
            toolTip=self.tr("Raise all immediate descendants of this node"),
            shortcut=QKeySequence(Qt.ControlModifier | Qt.ShiftModifier
                                  | Qt.Key_Right))
        raise_descendants.triggered.connect(
            partial(self.__on_raise_descendants, node))
        raise_ancestors = QAction(
            self.tr("Raise Ancestors"),
            w,
            objectName="action-canvas-raise-ancestors",
            toolTip=self.tr("Raise all immediate ancestors of this node"),
            shortcut=QKeySequence(Qt.ControlModifier | Qt.ShiftModifier
                                  | Qt.Key_Left))
        raise_ancestors.triggered.connect(
            partial(self.__on_raise_ancestors, node))
        w.addActions([raise_canvas, raise_descendants, raise_ancestors])

        # send all the post creation notification events
        workflow = self.__workflow
        assert workflow is not None
        inputs = workflow.find_links(sink_node=node)
        for link in inputs:
            ev = LinkEvent(LinkEvent.InputLinkAdded, link)
            QCoreApplication.sendEvent(w, ev)
        outputs = workflow.find_links(source_node=node)
        for link in outputs:
            ev = LinkEvent(LinkEvent.OutputLinkAdded, link)
            QCoreApplication.sendEvent(w, ev)

        self.widget_for_node_added.emit(node, w)

    def ensure_created(self, node):
        # type: (SchemeNode) -> None
        """
        Ensure that the widget for node is created.
        """
        if self.__workflow is None:
            return
        if node not in self.__workflow.nodes:
            return
        item = self.__item_for_node.get(node)
        if item is None:
            self.__add_widget_for_node(node)

    def __on_node_added(self, node):
        # type: (SchemeNode) -> None
        assert self.__workflow is not None
        assert node in self.__workflow.nodes
        assert node not in self.__item_for_node
        self.__add_node(node)

    def __add_node(self, node):
        # type: (SchemeNode) -> None
        # add node for tracking
        node.installEventFilter(self)
        if self.__creation_policy == WidgetManager.Immediate:
            self.ensure_created(node)
        elif self.__creation_policy == WidgetManager.Normal:
            self.__init_queue.append(node)
            self.__init_timer.start()

    def __on_node_removed(self, node):  # type: (SchemeNode) -> None
        assert self.__workflow is not None
        assert node not in self.__workflow.nodes
        self.__remove_node(node)

    def __remove_node(self, node):  # type: (SchemeNode) -> None
        # remove the node and its widget from tracking.
        node.removeEventFilter(self)
        if node in self.__init_queue:
            self.__init_queue.remove(node)
        item = self.__item_for_node.get(node)

        if item is not None and item.widget is not None:
            widget = item.widget
            assert widget in self.__item_for_widget
            del self.__item_for_widget[widget]
            widget.removeEventFilter(self.__activation_monitor)
            item.widget = None
            self.widget_for_node_removed.emit(node, widget)
            self.delete_widget_for_node(node, widget)

        if item is not None:
            del self.__item_for_node[node]

    @Slot()
    def __process_init_queue(self):
        if self.__init_queue:
            node = self.__init_queue.popleft()
            assert self.__workflow is not None
            assert node in self.__workflow.nodes
            log.debug("__process_init_queue: '%s'", node.title)
            try:
                self.ensure_created(node)
            finally:
                if self.__init_queue:
                    self.__init_timer.start()

    def __on_link_added(self, link):  # type: (SchemeLink) -> None
        assert self.__workflow is not None
        assert link.source_node in self.__workflow.nodes
        assert link.sink_node in self.__workflow.nodes
        source = self.__item_for_node.get(link.source_node)
        sink = self.__item_for_node.get(link.sink_node)
        # notify the node gui of an added link
        if source is not None and source.widget is not None:
            ev = LinkEvent(LinkEvent.OutputLinkAdded, link)
            QCoreApplication.sendEvent(source.widget, ev)
        if sink is not None and sink.widget is not None:
            ev = LinkEvent(LinkEvent.InputLinkAdded, link)
            QCoreApplication.sendEvent(sink.widget, ev)

    def __on_link_removed(self, link):  # type: (SchemeLink) -> None
        assert self.__workflow is not None
        assert link.source_node in self.__workflow.nodes
        assert link.sink_node in self.__workflow.nodes
        source = self.__item_for_node.get(link.source_node)
        sink = self.__item_for_node.get(link.sink_node)
        # notify the node gui of an removed link
        if source is not None and source.widget is not None:
            ev = LinkEvent(LinkEvent.OutputLinkRemoved, link)
            QCoreApplication.sendEvent(source.widget, ev)
        if sink is not None and sink.widget is not None:
            ev = LinkEvent(LinkEvent.InputLinkRemoved, link)
            QCoreApplication.sendEvent(sink.widget, ev)

    def __mark_activated(self, widget):  # type: (QWidget) ->  None
        # Update the tracked stacking order for `widget`
        item = self.__item_for_widget.get(widget)
        if item is not None:
            item.activation_order = next(self.__activation_counter)

    def activate_widget_for_node(self, node, widget):
        # type: (SchemeNode, QWidget) -> None
        """
        Activate the widget for node (show and raise above other)
        """
        if widget.windowState() == Qt.WindowMinimized:
            widget.showNormal()
        widget.setVisible(True)
        widget.raise_()
        widget.activateWindow()

    def activate_window_group(self, group):
        # type: (Scheme.WindowGroup) -> None
        self.restore_window_state(group.state)

    def raise_widgets_to_front(self):
        """
        Raise all current visible widgets to the front.

        The widgets will be stacked by activation order.
        """
        workflow = self.__workflow
        if workflow is None:
            return

        items = filter(
            lambda item:
            (item.widget.isVisible()
             if item is not None and item.widget is not None else False),
            map(self.__item_for_node.get, workflow.nodes))
        self.__raise_and_activate(items)

    def set_float_widgets_on_top(self, float_on_top):
        """
        Set `Float Widgets on Top` flag on all widgets.
        """
        self.__float_widgets_on_top = float_on_top
        for item in self.__item_for_node.values():
            if item.widget is not None:
                self.__set_float_on_top_flag(item.widget)

    def save_window_state(self):
        # type: () -> List[Tuple[SchemeNode, bytes]]
        """
        Save current open window arrangement.
        """
        if self.__workflow is None:
            return []

        workflow = self.__workflow  # type: Scheme
        state = []
        for node in workflow.nodes:  # type: SchemeNode
            item = self.__item_for_node.get(node, None)
            if item is None:
                continue
            stackorder = item.activation_order
            if item.widget is not None and not item.widget.isHidden():
                data = self.save_widget_geometry(node, item.widget)
                state.append((stackorder, node, data))

        return [(node, data)
                for _, node, data in sorted(state, key=lambda t: t[0])]

    def restore_window_state(self, state):
        # type: (List[Tuple[Node, bytes]]) -> None
        """
        Restore the window state.
        """
        assert self.__workflow is not None
        workflow = self.__workflow  # type: Scheme
        visible = {node for node, _ in state}
        # first hide all other widgets
        for node in workflow.nodes:
            if node not in visible:
                # avoid creating widgets if not needed
                item = self.__item_for_node.get(node, None)
                if item is not None and item.widget is not None:
                    item.widget.hide()
        allnodes = set(workflow.nodes)
        # restore state for visible group; windows are stacked as they appear
        # in the state list.
        w = None
        for node, node_state in filter(lambda t: t[0] in allnodes, state):
            w = self.widget_for_node(node)  # also create it if needed
            if w is not None:
                w.show()
                self.restore_widget_geometry(node, w, node_state)
                w.raise_()
                self.__mark_activated(w)

        # activate (give focus to) the last window
        if w is not None:
            w.activateWindow()

    def save_widget_geometry(self, node, widget):
        # type: (SchemeNode, QWidget) -> bytes
        """
        Save and return the current geometry and state for node.
        """
        return b''

    def restore_widget_geometry(self, node, widget, state):
        # type: (SchemeNode, QWidget, bytes) -> bool
        """
        Restore the widget geometry and state for node.

        Return True if the geometry was restored successfully.

        The default implementation does nothing.
        """
        return False

    @Slot(SchemeNode)
    def __on_raise_ancestors(self, node):
        # type: (SchemeNode) -> None
        """
        Raise all the ancestor widgets of `widget`.
        """
        item = self.__item_for_node.get(node)
        if item is not None:
            scheme = self.scheme()
            assert scheme is not None
            ancestors = [
                self.__item_for_node.get(p) for p in scheme.parents(item.node)
            ]
            self.__raise_and_activate(filter(None, reversed(ancestors)))

    @Slot(SchemeNode)
    def __on_raise_descendants(self, node):
        # type: (SchemeNode) -> None
        """
        Raise all the descendants widgets of `widget`.
        """
        item = self.__item_for_node.get(node)
        if item is not None:
            scheme = self.scheme()
            assert scheme is not None
            descendants = [
                self.__item_for_node.get(p) for p in scheme.children(item.node)
            ]
            self.__raise_and_activate(filter(None, reversed(descendants)))

    def __raise_and_activate(self, items):
        # type: (Iterable[Item]) -> None
        """Show and raise a set of widgets."""
        # preserve the tracked stacking order
        items = sorted(items, key=lambda item: item.activation_order)
        w = None
        for item in items:
            if item.widget is not None:
                w = item.widget
            elif item.errorwidget is not None:
                w = item.errorwidget
            else:
                continue
            w.show()
            w.raise_()
        if w is not None:
            # give focus to the last activated top window
            w.activateWindow()

    def __activate_widget_for_node(self, node):  # type: (SchemeNode) -> None
        # activate the widget for the node.
        self.ensure_created(node)
        item = self.__item_for_node.get(node)
        if item is None:
            return
        if item.widget is not None:
            self.activate_widget_for_node(node, item.widget)
        elif item.errorwidget is not None:
            item.errorwidget.show()
            item.errorwidget.raise_()
            item.errorwidget.activateWindow()

    def __on_activate_parent(self):
        event = WorkflowEvent(WorkflowEvent.ActivateParentRequest)
        QCoreApplication.sendEvent(self.scheme(), event)

    def eventFilter(self, recv, event):
        # type: (QObject, QEvent) -> bool
        if event.type() == NodeEvent.NodeActivateRequest \
                and isinstance(recv, SchemeNode):
            self.__activate_widget_for_node(recv)
        return False

    def __set_float_on_top_flag(self, widget):
        # type: (QWidget) -> None
        """Set or unset widget's float on top flag"""
        should_float_on_top = self.__float_widgets_on_top
        float_on_top = bool(widget.windowFlags() & Qt.WindowStaysOnTopHint)

        if float_on_top == should_float_on_top:
            return

        widget_was_visible = widget.isVisible()
        if should_float_on_top:
            widget.setWindowFlags(widget.windowFlags()
                                  | Qt.WindowStaysOnTopHint)
        else:
            widget.setWindowFlags(widget.windowFlags()
                                  & ~Qt.WindowStaysOnTopHint)

        # Changing window flags hid the widget
        if widget_was_visible:
            widget.show()

    def __on_env_changed(self, key, newvalue, oldvalue):
        # Notify widgets of a runtime environment change
        for item in self.__item_for_node.values():
            if item.widget is not None:
                ev = WorkflowEnvChanged(key, newvalue, oldvalue)
                QCoreApplication.sendEvent(item.widget, ev)

    def actions_for_context_menu(self, node):
        # type: (SchemeNode) -> List[QAction]
        """
        Return a list of extra actions that can be inserted into context
        menu in the workflow editor.

        Subclasses can reimplement this method to extend the default context
        menu.

        Parameters
        ----------
        node: SchemeNode
            The node for which the context menu is requested.

        Return
        ------
        actions: List[QAction]
            Actions that are appended to the default menu.
        """
        return []
Exemple #34
0
 def __schedule_next(self):
     if not self.__next_pending:
         self.__next_pending = True
         QTimer.singleShot(10, self.__on_timeout)
class OWScatterPlot(OWWidget):
    """Scatterplot visualization with explorative analysis and intelligent
    data visualization enhancements."""

    name = 'Scatter Plot'
    description = "Interactive scatter plot visualization with " \
                  "intelligent data visualization enhancements."
    icon = "icons/ScatterPlot.svg"
    priority = 140

    class Inputs:
        data = Input("Data", Table, default=True)
        data_subset = Input("Data Subset", Table)
        features = Input("Features", AttributeList)

    class Outputs:
        selected_data = Output("Selected Data", Table, default=True)
        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table)
        features = Output("Features", AttributeList, dynamic=False)

    settings_version = 2
    settingsHandler = DomainContextHandler()

    auto_send_selection = Setting(True)
    auto_sample = Setting(True)
    toolbar_selection = Setting(0)

    attr_x = ContextSetting(None)
    attr_y = ContextSetting(None)

    #: Serialized selection state to be restored
    selection_group = Setting(None, schema_only=True)

    graph = SettingProvider(OWScatterPlotGraph)

    jitter_sizes = [0, 0.1, 0.5, 1, 2, 3, 4, 5, 7, 10]

    graph_name = "graph.plot_widget.plotItem"

    class Information(OWWidget.Information):
        sampled_sql = Msg("Large SQL table; showing a sample.")

    def __init__(self):
        super().__init__()

        box = gui.vBox(self.mainArea, True, margin=0)
        self.graph = OWScatterPlotGraph(self, box, "ScatterPlot")
        box.layout().addWidget(self.graph.plot_widget)
        plot = self.graph.plot_widget

        axispen = QPen(self.palette().color(QPalette.Text))
        axis = plot.getAxis("bottom")
        axis.setPen(axispen)

        axis = plot.getAxis("left")
        axis.setPen(axispen)

        self.data = None  # Orange.data.Table
        self.subset_data = None  # Orange.data.Table
        self.sql_data = None  # Orange.data.sql.table.SqlTable
        self.attribute_selection_list = None  # list of Orange.data.Variable
        self.__timer = QTimer(self, interval=1200)
        self.__timer.timeout.connect(self.add_data)
        #: Remember the saved state to restore
        self.__pending_selection_restore = self.selection_group
        self.selection_group = None

        common_options = dict(labelWidth=50,
                              orientation=Qt.Horizontal,
                              sendSelectedValue=True,
                              valueType=str)
        box = gui.vBox(self.controlArea, "Axis Data")
        dmod = DomainModel
        self.xy_model = DomainModel(dmod.MIXED, valid_types=dmod.PRIMITIVE)
        self.cb_attr_x = gui.comboBox(box,
                                      self,
                                      "attr_x",
                                      label="Axis x:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        self.cb_attr_y = gui.comboBox(box,
                                      self,
                                      "attr_y",
                                      label="Axis y:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)

        vizrank_box = gui.hBox(box)
        gui.separator(vizrank_box, width=common_options["labelWidth"])
        self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank(
            vizrank_box, self, "Find Informative Projections", self.set_attr)

        gui.separator(box)

        g = self.graph.gui
        g.add_widgets([g.JitterSizeSlider, g.JitterNumericValues], box)

        self.sampling = gui.auto_commit(self.controlArea,
                                        self,
                                        "auto_sample",
                                        "Sample",
                                        box="Sampling",
                                        callback=self.switch_sampling,
                                        commit=lambda: self.add_data(1))
        self.sampling.setVisible(False)

        g.point_properties_box(self.controlArea)
        self.models = [self.xy_model] + g.points_models

        box_plot_prop = gui.vBox(self.controlArea, "Plot Properties")
        g.add_widgets([
            g.ShowLegend, g.ShowGridLines, g.ToolTipShowsAll, g.ClassDensity,
            g.RegressionLine, g.LabelOnlySelected
        ], box_plot_prop)

        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_send_selection",
                        "Send Selection", "Send Automatically")

        self.graph.zoom_actions(self)

    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 reset_graph_data(self, *_):
        if self.data is not None:
            self.graph.rescale_data()
            self.update_graph()

    @Inputs.data
    def set_data(self, data):
        self.clear_messages()
        self.Information.sampled_sql.clear()
        self.__timer.stop()
        self.sampling.setVisible(False)
        self.sql_data = None
        if isinstance(data, SqlTable):
            if data.approx_len() < 4000:
                data = Table(data)
            else:
                self.Information.sampled_sql()
                self.sql_data = data
                data_sample = data.sample_time(0.8, no_cache=True)
                data_sample.download_data(2000, partial=True)
                data = Table(data_sample)
                self.sampling.setVisible(True)
                if self.auto_sample:
                    self.__timer.start()

        if data is not None and (len(data) == 0 or len(data.domain) == 0):
            data = None
        if self.data and data and self.data.checksum() == data.checksum():
            return

        self.closeContext()
        same_domain = (self.data and data and data.domain.checksum()
                       == self.data.domain.checksum())
        self.data = data

        if not same_domain:
            self.init_attr_values()
        self.vizrank.initialize()
        self.vizrank.attrs = self.data.domain.attributes if self.data is not None else []
        self.vizrank_button.setEnabled(
            self.data is not None and not self.data.is_sparse()
            and self.data.domain.class_var is not None
            and not np.isnan(self.data.Y).all()
            and len(self.data.domain.attributes) > 1 and len(self.data) > 1)
        if self.data is not None and self.data.domain.class_var is None \
            and len(self.data.domain.attributes) > 1 and len(self.data) > 1:
            self.vizrank_button.setToolTip(
                "Data with a class variable is required.")
        else:
            self.vizrank_button.setToolTip("")
        self.openContext(self.data)

        def findvar(name, iterable):
            """Find a Orange.data.Variable in `iterable` by name"""
            for el in iterable:
                if isinstance(el, Orange.data.Variable) and el.name == name:
                    return el
            return None

        # handle restored settings from  < 3.3.9 when attr_* were stored
        # by name
        if isinstance(self.attr_x, str):
            self.attr_x = findvar(self.attr_x, self.xy_model)
        if isinstance(self.attr_y, str):
            self.attr_y = findvar(self.attr_y, self.xy_model)
        if isinstance(self.graph.attr_label, str):
            self.graph.attr_label = findvar(self.graph.attr_label,
                                            self.graph.gui.label_model)
        if isinstance(self.graph.attr_color, str):
            self.graph.attr_color = findvar(self.graph.attr_color,
                                            self.graph.gui.color_model)
        if isinstance(self.graph.attr_shape, str):
            self.graph.attr_shape = findvar(self.graph.attr_shape,
                                            self.graph.gui.shape_model)
        if isinstance(self.graph.attr_size, str):
            self.graph.attr_size = findvar(self.graph.attr_size,
                                           self.graph.gui.size_model)

    def add_data(self, time=0.4):
        if self.data and len(self.data) > 2000:
            return self.__timer.stop()
        data_sample = self.sql_data.sample_time(time, no_cache=True)
        if data_sample:
            data_sample.download_data(2000, partial=True)
            data = Table(data_sample)
            self.data = Table.concatenate((self.data, data), axis=0)
            self.handleNewSignals()

    def switch_sampling(self):
        self.__timer.stop()
        if self.auto_sample and self.sql_data:
            self.add_data()
            self.__timer.start()

    @Inputs.data_subset
    def set_subset_data(self, subset_data):
        self.warning()
        if isinstance(subset_data, SqlTable):
            if subset_data.approx_len() < AUTO_DL_LIMIT:
                subset_data = Table(subset_data)
            else:
                self.warning("Data subset does not support large Sql tables")
                subset_data = None
        self.subset_data = subset_data
        self.controls.graph.alpha_value.setEnabled(subset_data is None)

    # called when all signals are received, so the graph is updated only once
    def handleNewSignals(self):
        self.graph.new_data(self.data, self.subset_data)
        if self.attribute_selection_list and self.graph.domain and \
                all(attr in self.graph.domain
                        for attr in self.attribute_selection_list):
            self.attr_x = self.attribute_selection_list[0]
            self.attr_y = self.attribute_selection_list[1]
        self.attribute_selection_list = None
        self.update_graph()
        self.cb_class_density.setEnabled(self.graph.can_draw_density())
        self.cb_reg_line.setEnabled(self.graph.can_draw_regresssion_line())
        if self.data is not None and self.__pending_selection_restore is not None:
            self.apply_selection(self.__pending_selection_restore)
            self.__pending_selection_restore = None
        self.unconditional_commit()

    def apply_selection(self, selection):
        """Apply `selection` to the current plot."""
        if self.data is not None:
            self.graph.selection = np.zeros(len(self.data), dtype=np.uint8)
            self.selection_group = [
                x for x in selection if x[0] < len(self.data)
            ]
            selection_array = np.array(self.selection_group).T
            self.graph.selection[selection_array[0]] = selection_array[1]
            self.graph.update_colors(keep_colors=True)

    @Inputs.features
    def set_shown_attributes(self, attributes):
        if attributes and len(attributes) >= 2:
            self.attribute_selection_list = attributes[:2]
        else:
            self.attribute_selection_list = None

    def init_attr_values(self):
        domain = self.data and self.data.domain
        for model in self.models:
            model.set_domain(domain)
        self.attr_x = self.xy_model[0] if self.xy_model else None
        self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \
            else self.attr_x
        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 set_attr(self, attr_x, attr_y):
        self.attr_x, self.attr_y = attr_x, attr_y
        self.update_attr()

    def update_attr(self):
        self.update_graph()
        self.cb_class_density.setEnabled(self.graph.can_draw_density())
        self.cb_reg_line.setEnabled(self.graph.can_draw_regresssion_line())
        self.send_features()

    def update_colors(self):
        self.cb_class_density.setEnabled(self.graph.can_draw_density())

    def update_density(self):
        self.update_graph(reset_view=False)

    def update_regression_line(self):
        self.update_graph(reset_view=False)

    def update_graph(self, reset_view=True, **_):
        self.graph.zoomStack = []
        if self.graph.data is None:
            return
        self.graph.update_data(self.attr_x, self.attr_y, reset_view)

    def selection_changed(self):

        # Store current selection in a setting that is stored in workflow
        if isinstance(self.data, SqlTable):
            selection = None
        elif self.data is not None:
            selection = self.graph.get_selection()
        else:
            selection = None
        if selection is not None and len(selection):
            self.selection_group = list(
                zip(selection, self.graph.selection[selection]))
        else:
            self.selection_group = None

        self.commit()

    def send_data(self):
        # TODO: Implement selection for sql data
        def _get_selected():
            if not len(selection):
                return None
            return create_groups_table(data, graph.selection, False, "Group")

        def _get_annotated():
            if graph.selection is not None and np.max(graph.selection) > 1:
                return create_groups_table(data, graph.selection)
            else:
                return create_annotated_table(data, selection)

        graph = self.graph
        data = self.data
        selection = graph.get_selection()
        self.Outputs.annotated_data.send(_get_annotated())
        self.Outputs.selected_data.send(_get_selected())

    def send_features(self):
        features = [attr for attr in [self.attr_x, self.attr_y] if attr]
        self.Outputs.features.send(features or None)

    def commit(self):
        self.send_data()
        self.send_features()

    def get_widget_name_extension(self):
        if self.data is not None:
            return "{} vs {}".format(self.attr_x.name, self.attr_y.name)

    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.attr_x.is_discrete or self.attr_y.is_discrete
                            or self.graph.jitter_continuous)
              and self.graph.jitter_size)))
        self.report_plot()
        if caption:
            self.report_caption(caption)

    def onDeleteWidget(self):
        super().onDeleteWidget()
        self.graph.plot_widget.getViewBox().deleteLater()
        self.graph.plot_widget.clear()

    @classmethod
    def migrate_settings(cls, settings, version):
        if version < 2 and "selection" in settings and settings["selection"]:
            settings["selection_group"] = [(a, 1)
                                           for a in settings["selection"]]
Exemple #36
0
 def runJavaScript(self, javascript, resultCallback=None):
     result = self.page().mainFrame().evaluateJavaScript(javascript)
     if resultCallback is not None:
         # Emulate the QtWebEngine's interface and return the result
         # in a event queue invoked callback
         QTimer.singleShot(0, lambda: resultCallback(result))
Exemple #37
0
    def showEvent(self, event):
        super().showEvent(event)

        if not self._f_pypi_addons.done() and self.__progress is not None:
            QTimer.singleShot(0, self.__progress.show)
Exemple #38
0
 def showEvent(self, event):
     super().showEvent(event)
     if self._should_fit_bounds:
         QTimer.singleShot(500, self.fit_to_bounds)
         self._should_fit_bounds = False
Exemple #39
0
 def focusInEvent(self, event):
     super().focusInEvent(event)
     # If selectAll is called directly, placing the cursor unselects the text
     QTimer.singleShot(0, self.selectAll)
Exemple #40
0
    def redraw_markers_overlay_image(self, *args, new_image=False):
        if not args and not self._drawing_args or self.data is None:
            return

        if args:
            self._drawing_args = args
        north, east, south, west, width, height, zoom, origin, map_pane_pos = self._drawing_args

        lat, lon = self._latlon_data.T
        visible = ((lat <= north) & (lat >= south) & (lon <= east) &
                   (lon >= west)).nonzero()[0]
        in_subset = (np.in1d(self.data.ids, self._subset_ids)
                     if self._subset_ids.size else np.tile(True, len(lon)))

        is_js_path = self.is_js_path = len(visible) < self.N_POINTS_PER_ITER

        self._update_legend(is_js_path)

        np.random.shuffle(visible)
        # Sort points in subset to be painted last
        visible = visible[np.lexsort((in_subset[visible], ))]

        if is_js_path:
            self.evalJS('clear_markers_overlay_image()')
            self._update_js_markers(visible, in_subset[visible])
            self._owwidget.disable_some_controls(False)
            return

        self.evalJS('clear_markers_js();')
        self._owwidget.disable_some_controls(True)

        selected = (self._selected_indices if self._selected_indices
                    is not None else np.zeros(len(lat), dtype=bool))
        cur = 0

        im = QImage(self._overlay_image_path)
        if im.isNull() or self._prev_origin != origin or new_image:
            im = QImage(width, height, QImage.Format_ARGB32)
            im.fill(Qt.transparent)
        else:
            dx, dy = self._prev_map_pane_pos - map_pane_pos
            im = im.copy(dx, dy, width, height)
        self._prev_map_pane_pos = np.array(map_pane_pos)
        self._prev_origin = origin

        painter = QPainter(im)
        painter.setRenderHint(QPainter.Antialiasing, True)
        self.evalJS(
            'clear_markers_overlay_image(); markersImageLayer.setBounds(map.getBounds());0'
        )

        self._image_token = image_token = np.random.random()

        n_iters = np.ceil(len(visible) / self.N_POINTS_PER_ITER)

        def add_points():
            nonlocal cur, image_token
            if image_token != self._image_token:
                return
            batch = visible[cur:cur + self.N_POINTS_PER_ITER]

            batch_lat = lat[batch]
            batch_lon = lon[batch]

            x, y = self.Projection.latlon_to_easting_northing(
                batch_lat, batch_lon)
            x, y = self.Projection.easting_northing_to_pixel(
                x, y, zoom, origin, map_pane_pos)

            if self._jittering:
                dx, dy = self._jittering_offsets[batch].T
                x, y = x + dx, y + dy

            colors = (self._colorgen.getRGB(
                self._scaled_color_values[batch]).tolist()
                      if self._color_attr else repeat((0xff, 0, 0)))
            sizes = self._size_coef * \
                (self._sizes[batch] if self._size_attr else np.tile(10, len(batch)))

            opacity_subset, opacity_rest = self._opacity, int(.8 *
                                                              self._opacity)
            for x, y, is_selected, size, color, _in_subset in \
                    zip(x, y, selected[batch], sizes, colors, in_subset[batch]):

                pensize2, selpensize2 = (.35, 1.5) if size >= 5 else (.15, .7)
                pensize2 *= self._size_coef
                selpensize2 *= self._size_coef

                size2 = size / 2
                if is_selected:
                    painter.setPen(QPen(QBrush(Qt.green), 2 * selpensize2))
                    painter.drawEllipse(x - size2 - selpensize2,
                                        y - size2 - selpensize2,
                                        size + selpensize2, size + selpensize2)
                color = QColor(*color)
                if _in_subset:
                    color.setAlpha(opacity_subset)
                    painter.setBrush(QBrush(color))
                    painter.setPen(
                        QPen(QBrush(color.darker(180)), 2 * pensize2))
                else:
                    color.setAlpha(opacity_rest)
                    painter.setBrush(Qt.NoBrush)
                    painter.setPen(
                        QPen(QBrush(color.lighter(120)), 2 * pensize2))

                painter.drawEllipse(x - size2 - pensize2, y - size2 - pensize2,
                                    size + pensize2, size + pensize2)

            im.save(self._overlay_image_path, 'PNG')
            self.evalJS('markersImageLayer.setUrl("{}#{}"); 0;'.format(
                self.toFileURL(self._overlay_image_path), np.random.random()))

            cur += self.N_POINTS_PER_ITER
            if self.stop:
                return
            elif cur < len(visible):
                QTimer.singleShot(10, add_points)
                self._owwidget.progressBarAdvance(100 / n_iters, None)
            else:
                self._owwidget.progressBarFinished(None)
                self._image_token = None

        self._owwidget.progressBarFinished(None)
        self._owwidget.progressBarInit(None)
        QTimer.singleShot(10, add_points)
class WidgetManager(QObject):
    """
    OWWidget instance manager class.

    This class handles the lifetime of OWWidget instances in a
    :class:`WidgetsScheme`.

    """

    #: A new OWWidget was created and added by the manager.
    widget_for_node_added = Signal(SchemeNode, QWidget)

    #: An OWWidget was removed, hidden and will be deleted when appropriate.
    widget_for_node_removed = Signal(SchemeNode, QWidget)

    class ProcessingState(enum.IntEnum):
        """Widget processing state flags"""

        #: Signal manager is updating/setting the widget's inputs
        InputUpdate = 1
        #: Widget has entered a blocking state (OWWidget.isBlocking)
        BlockingUpdate = 2
        #: Widget has entered processing state
        ProcessingUpdate = 4
        #: Widget is still in the process of initialization
        Initializing = 8

    InputUpdate, BlockingUpdate, ProcessingUpdate, Initializing = ProcessingState

    #: State mask for widgets that cannot be deleted immediately
    #: (see __try_delete)
    _DelayDeleteMask = InputUpdate | BlockingUpdate

    #: Widget initialization states
    Delayed = namedtuple("Delayed", ["node"])
    PartiallyInitialized = namedtuple("Materializing",
                                      ["node", "partially_initialized_widget"])
    Materialized = namedtuple("Materialized", ["node", "widget"])

    class CreationPolicy(enum.Enum):
        """Widget Creation Policy"""

        #: Widgets are scheduled to be created from the event loop, or when
        #: first accessed with `widget_for_node`
        Normal = "Normal"
        #: Widgets are created immediately when added to the workflow model
        Immediate = "Immediate"
        #: Widgets are created only when first accessed with `widget_for_node`
        OnDemand = "OnDemand"

    Normal, Immediate, OnDemand = CreationPolicy

    def __init__(self, parent=None):
        QObject.__init__(self, parent)
        self.__scheme = None
        self.__signal_manager = None
        self.__widgets = []
        self.__initstate_for_node = {}
        self.__creation_policy = WidgetManager.Normal
        #: a queue of all nodes whose widgets are scheduled for
        #: creation/initialization
        self.__init_queue = deque()  # type: Deque[SchemeNode]
        #: Timer for scheduling widget initialization
        self.__init_timer = QTimer(self, interval=0, singleShot=True)
        self.__init_timer.timeout.connect(self.__create_delayed)

        #: A mapping of SchemeNode -> OWWidget (note: a mapping is only added
        #: after the widget is actually created)
        self.__widget_for_node = {}
        #: a mapping of OWWidget -> SchemeNode
        self.__node_for_widget = {}

        # Widgets that were 'removed' from the scheme but were at
        # the time in an input update loop and could not be deleted
        # immediately
        self.__delay_delete = set()

        #: processing state flags for all widgets (including the ones
        #: in __delay_delete).
        #: Note: widgets which have not yet been created do not have an entry
        self.__widget_processing_state = {}

        # Tracks the widget in the update loop by the SignalManager
        self.__updating_widget = None

    def set_scheme(self, scheme):
        """
        Set the :class:`WidgetsScheme` instance to manage.
        """
        self.__scheme = scheme
        self.__signal_manager = scheme.findChild(SignalManager)

        self.__signal_manager.processingStarted[SchemeNode].connect(
            self.__on_processing_started)
        self.__signal_manager.processingFinished[SchemeNode].connect(
            self.__on_processing_finished)
        scheme.node_added.connect(self.add_widget_for_node)
        scheme.node_removed.connect(self.remove_widget_for_node)
        scheme.runtime_env_changed.connect(self.__on_env_changed)
        scheme.installEventFilter(self)

    def scheme(self):
        """
        Return the scheme instance on which this manager is installed.
        """
        return self.__scheme

    def signal_manager(self):
        """
        Return the signal manager in use on the :func:`scheme`.
        """
        return self.__signal_manager

    def widget_for_node(self, node):
        """
        Return the OWWidget instance for the scheme node.
        """
        state = self.__initstate_for_node[node]
        if isinstance(state, WidgetManager.Delayed):
            # Create the widget now if it is still pending
            state = self.__materialize(state)
            return state.widget
        elif isinstance(state, WidgetManager.PartiallyInitialized):
            widget = state.partially_initialized_widget
            log.warning(
                "WidgetManager.widget_for_node: "
                "Accessing a partially created widget instance. "
                "This is most likely a result of explicit "
                "QApplication.processEvents call from the '%s.%s' "
                "widgets __init__.",
                type(widget).__module__,
                type(widget).__name__,
            )
            return widget
        elif isinstance(state, WidgetManager.Materialized):
            return state.widget
        else:
            assert False

    def node_for_widget(self, widget):
        """
        Return the SchemeNode instance for the OWWidget.

        Raise a KeyError if the widget does not map to a node in the scheme.
        """
        return self.__node_for_widget[widget]

    def widget_properties(self, node):
        """
        Return the current widget properties/settings.

        Parameters
        ----------
        node : SchemeNode

        Returns
        -------
        settings : dict
        """
        state = self.__initstate_for_node[node]
        if isinstance(state, WidgetManager.Materialized):
            return state.widget.settingsHandler.pack_data(state.widget)
        else:
            return node.properties

    def set_creation_policy(self, policy):
        """
        Set the widget creation policy

        Parameters
        ----------
        policy : WidgetManager.CreationPolicy
        """
        if self.__creation_policy != policy:
            self.__creation_policy = policy

            if self.__creation_policy == WidgetManager.Immediate:
                self.__init_timer.stop()
                while self.__init_queue:
                    state = self.__init_queue.popleft()
                    self.__materialize(state)
            elif self.__creation_policy == WidgetManager.Normal:
                if not self.__init_timer.isActive() and self.__init_queue:
                    self.__init_timer.start()
            elif self.__creation_policy == WidgetManager.OnDemand:
                self.__init_timer.stop()
            else:
                assert False

    def creation_policy(self):
        """
        Return the current widget creation policy

        Returns
        -------
        policy: WidgetManager.CreationPolicy
        """
        return self.__creation_policy

    def add_widget_for_node(self, node):
        """
        Create a new OWWidget instance for the corresponding scheme node.
        """
        state = WidgetManager.Delayed(node)
        self.__initstate_for_node[node] = state

        if self.__creation_policy == WidgetManager.Immediate:
            self.__initstate_for_node[node] = self.__materialize(state)
        elif self.__creation_policy == WidgetManager.Normal:
            self.__init_queue.append(state)
            if not self.__init_timer.isActive():
                self.__init_timer.start()
        elif self.__creation_policy == WidgetManager.OnDemand:
            self.__init_queue.append(state)

    def __materialize(self, state):
        # Create and initialize an OWWidget for a Delayed
        # widget initialization
        assert isinstance(state, WidgetManager.Delayed)
        if state in self.__init_queue:
            self.__init_queue.remove(state)

        node = state.node

        widget = self.create_widget_instance(node)

        self.__widgets.append(widget)
        self.__widget_for_node[node] = widget
        self.__node_for_widget[widget] = node

        self.__initialize_widget_state(node, widget)

        state = WidgetManager.Materialized(node, widget)
        self.__initstate_for_node[node] = state
        self.widget_for_node_added.emit(node, widget)

        return state

    def remove_widget_for_node(self, node):
        """
        Remove the OWWidget instance for node.
        """
        state = self.__initstate_for_node[node]
        if isinstance(state, WidgetManager.Delayed):
            del self.__initstate_for_node[node]
            self.__init_queue.remove(state)
        elif isinstance(state, WidgetManager.Materialized):
            # Update the node's stored settings/properties dict before
            # removing the widget.
            # TODO: Update/sync whenever the widget settings change.
            node.properties = self._widget_settings(state.widget)
            self.__widgets.remove(state.widget)
            del self.__initstate_for_node[node]
            del self.__widget_for_node[node]
            del self.__node_for_widget[state.widget]
            node.title_changed.disconnect(state.widget.setCaption)
            state.widget.progressBarValueChanged.disconnect(node.set_progress)

            self.widget_for_node_removed.emit(node, state.widget)
            self._delete_widget(state.widget)
        elif isinstance(state, WidgetManager.PartiallyInitialized):
            widget = state.partially_initialized_widget
            raise RuntimeError(
                "A widget/node {} was removed while being initialized. "
                "This is most likely a result of an explicit "
                "QApplication.processEvents call from the '{}.{}' "
                "widgets __init__.\n".format(state.node.title,
                                             type(widget).__module__,
                                             type(widget).__init__))

    def _widget_settings(self, widget):
        return widget.settingsHandler.pack_data(widget)

    def _delete_widget(self, widget):
        """
        Delete the OWBaseWidget instance.
        """
        widget.close()
        # Save settings to user global settings.
        widget.saveSettings()
        # Notify the widget it will be deleted.
        widget.onDeleteWidget()

        state = self.__widget_processing_state[widget]
        if state & WidgetManager._DelayDeleteMask:
            # If the widget is in an update loop and/or blocking we
            # delay the scheduled deletion until the widget is done.
            log.debug(
                "Widget %s removed but still in state :%s. "
                "Deferring deletion.",
                widget,
                state,
            )
            self.__delay_delete.add(widget)
        else:
            widget.deleteLater()
            del self.__widget_processing_state[widget]

    def create_widget_instance(self, node):
        """
        Create a OWWidget instance for the node.
        """
        desc = node.description
        klass = widget = None
        initialized = False
        error = None
        # First try to actually retrieve the class.
        try:
            klass = name_lookup(desc.qualified_name)
        except (ImportError, AttributeError):
            sys.excepthook(*sys.exc_info())
            error = "Could not import {0!r}\n\n{1}".format(
                node.description.qualified_name, traceback.format_exc())
        except Exception:
            sys.excepthook(*sys.exc_info())
            error = "An unexpected error during import of {0!r}\n\n{1}".format(
                node.description.qualified_name, traceback.format_exc())

        if klass is None:
            widget = mock_error_owwidget(node, error)
            initialized = True

        if widget is None:
            log.info(
                "WidgetManager: Creating '%s.%s' instance '%s'.",
                klass.__module__,
                klass.__name__,
                node.title,
            )

            widget = klass.__new__(
                klass,
                None,
                captionTitle=node.title,
                signal_manager=self.signal_manager(),
                stored_settings=node.properties,
                # NOTE: env is a view of the real env and reflects
                # changes to the environment.
                env=self.scheme().runtime_env(),
            )
            initialized = False

        # Init the node/widget mapping and state before calling __init__
        # Some OWWidgets might already send data in the constructor
        # (should this be forbidden? Raise a warning?) triggering the signal
        # manager which would request the widget => node mapping or state
        # Furthermore they can (though they REALLY REALLY REALLY should not)
        # explicitly call qApp.processEvents.
        assert node not in self.__widget_for_node
        self.__widget_for_node[node] = widget
        self.__node_for_widget[widget] = node
        self.__widget_processing_state[widget] = WidgetManager.Initializing
        self.__initstate_for_node[node] = WidgetManager.PartiallyInitialized(
            node, widget)

        if not initialized:
            try:
                widget.__init__()
            except Exception:
                sys.excepthook(*sys.exc_info())
                msg = traceback.format_exc()
                msg = "Could not create {0!r}\n\n{1}".format(
                    node.description.name, msg)
                # remove state tracking for widget ...
                del self.__widget_for_node[node]
                del self.__node_for_widget[widget]
                del self.__widget_processing_state[widget]

                # ... and substitute it with a mock error widget.
                widget = mock_error_owwidget(node, msg)
                self.__widget_for_node[node] = widget
                self.__node_for_widget[widget] = node
                self.__widget_processing_state[widget] = 0
                self.__initstate_for_node[node] = WidgetManager.Materialized(
                    node, widget)

        self.__initstate_for_node[node] = WidgetManager.Materialized(
            node, widget)
        # Clear Initializing flag
        self.__widget_processing_state[widget] &= ~WidgetManager.Initializing

        node.title_changed.connect(widget.setCaption)

        # Widget's info/warning/error messages.
        widget.messageActivated.connect(self.__on_widget_state_changed)
        widget.messageDeactivated.connect(self.__on_widget_state_changed)

        # Widget's statusTip
        node.set_status_message(widget.statusMessage())
        widget.statusMessageChanged.connect(node.set_status_message)

        # Widget's progress bar value state.
        widget.progressBarValueChanged.connect(node.set_progress)

        # Widget processing state (progressBarInit/Finished)
        # and the blocking state.
        widget.processingStateChanged.connect(
            self.__on_processing_state_changed)
        widget.blockingStateChanged.connect(self.__on_blocking_state_changed)

        if widget.isBlocking():
            # A widget can already enter blocking state in __init__
            self.__widget_processing_state[widget] |= self.BlockingUpdate

        if widget.processingState != 0:
            # It can also start processing (initialization of resources, ...)
            self.__widget_processing_state[widget] |= self.ProcessingUpdate
            node.set_processing_state(1)
            node.set_progress(widget.progressBarValue)

        # Install a help shortcut on the widget
        help_action = widget.findChild(QAction, "action-help")
        if help_action is not None:
            help_action.setEnabled(True)
            help_action.setVisible(True)
            help_action.triggered.connect(self.__on_help_request)

        # Up shortcut (activate/open parent)
        up_shortcut = QShortcut(QKeySequence(Qt.ControlModifier + Qt.Key_Up),
                                widget)
        up_shortcut.activated.connect(self.__on_activate_parent)

        # Call setters only after initialization.
        widget.setWindowIcon(icon_loader.from_description(desc).get(desc.icon))
        widget.setCaption(node.title)

        # Schedule an update with the signal manager, due to the cleared
        # implicit Initializing flag
        self.signal_manager()._update()

        return widget

    def node_processing_state(self, node):
        """
        Return the processing state flags for the node.

        Same as `manager.widget_processing_state(manger.widget_for_node(node))`

        """
        state = self.__initstate_for_node[node]
        if isinstance(state, WidgetManager.Materialized):
            return self.__widget_processing_state[state.widget]
        elif isinstance(state, WidgetManager.PartiallyInitialized):
            return self.__widget_processing_state[
                state.partially_initialized_widget]
        else:
            return WidgetManager.Initializing

    def widget_processing_state(self, widget):
        """
        Return the processing state flags for the widget.

        The state is an bitwise or of `InputUpdate` and `BlockingUpdate`.

        """
        return self.__widget_processing_state[widget]

    def __create_delayed(self):
        if self.__init_queue:
            state = self.__init_queue.popleft()
            node = state.node
            self.__initstate_for_node[node] = self.__materialize(state)

        if self.__creation_policy == WidgetManager.Normal and self.__init_queue:
            # restart the timer if pending widgets still in the queue
            self.__init_timer.start()

    def eventFilter(self, receiver, event):
        if event.type() == QEvent.Close and receiver is self.__scheme:
            self.signal_manager().stop()

            # Notify the widget instances.
            for widget in list(self.__widget_for_node.values()):
                widget.close()
                widget.saveSettings()
                widget.onDeleteWidget()
                widget.deleteLater()

            event.accept()
            return True

        return QObject.eventFilter(self, receiver, event)

    def __on_help_request(self):
        """
        Help shortcut was pressed. We send a `QWhatsThisClickedEvent` to
        the scheme and hope someone responds to it.

        """
        # Sender is the QShortcut, and parent the OWBaseWidget
        widget = self.sender().parent()
        try:
            node = self.node_for_widget(widget)
        except KeyError:
            pass
        else:
            qualified_name = node.description.qualified_name
            help_url = "help://search?" + urlencode({"id": qualified_name})
            event = QWhatsThisClickedEvent(help_url)
            QCoreApplication.sendEvent(self.scheme(), event)

    def __on_activate_parent(self):
        """
        Activate parent shortcut was pressed.
        """
        event = ActivateParentEvent()
        QCoreApplication.sendEvent(self.scheme(), event)

    def __initialize_widget_state(self, node, widget):
        """
        Initialize the tracked info/warning/error message state.
        """
        for message_group in widget.message_groups:
            message = user_message_from_state(message_group)
            if message:
                node.set_state_message(message)

    def __on_widget_state_changed(self, msg):
        """
        The OWBaseWidget info/warning/error state has changed.
        """
        widget = msg.group.widget
        try:
            node = self.node_for_widget(widget)
        except KeyError:
            pass
        else:
            self.__initialize_widget_state(node, widget)

    def __on_processing_state_changed(self, state):
        """
        A widget processing state has changed (progressBarInit/Finished)
        """
        widget = self.sender()

        if state:
            self.__widget_processing_state[widget] |= self.ProcessingUpdate
        else:
            self.__widget_processing_state[widget] &= ~self.ProcessingUpdate

        # propagate the change to the workflow model.
        try:
            # we can still track widget state after it was removed from the
            # workflow model (`__delay_delete`)
            node = self.node_for_widget(widget)
        except KeyError:
            pass
        else:
            self.__update_node_processing_state(node)

    def __on_processing_started(self, node):
        """
        Signal manager entered the input update loop for the node.
        """
        widget = self.widget_for_node(node)
        # Remember the widget instance. The node and the node->widget mapping
        # can be removed between this and __on_processing_finished.
        self.__updating_widget = widget
        self.__widget_processing_state[widget] |= self.InputUpdate
        self.__update_node_processing_state(node)

    def __on_processing_finished(self, node):
        """
        Signal manager exited the input update loop for the node.
        """
        widget = self.__updating_widget
        self.__widget_processing_state[widget] &= ~self.InputUpdate

        if widget in self.__node_for_widget:
            self.__update_node_processing_state(node)
        elif widget in self.__delay_delete:
            self.__try_delete(widget)
        else:
            raise ValueError("%r is not managed" % widget)

        self.__updating_widget = None

    def __on_blocking_state_changed(self, state):
        """
        OWWidget blocking state has changed.
        """
        if not state:
            # schedule an update pass.
            self.signal_manager()._update()

        widget = self.sender()
        if state:
            self.__widget_processing_state[widget] |= self.BlockingUpdate
        else:
            self.__widget_processing_state[widget] &= ~self.BlockingUpdate

        if widget in self.__node_for_widget:
            node = self.node_for_widget(widget)
            self.__update_node_processing_state(node)

        elif widget in self.__delay_delete:
            self.__try_delete(widget)

    def __update_node_processing_state(self, node):
        """
        Update the `node.processing_state` to reflect the widget state.
        """
        state = self.node_processing_state(node)
        node.set_processing_state(1 if state else 0)

    def __try_delete(self, widget):
        if not (self.__widget_processing_state[widget]
                & WidgetManager._DelayDeleteMask):
            log.debug("Delayed delete for widget %s", widget)
            self.__delay_delete.remove(widget)
            del self.__widget_processing_state[widget]
            widget.blockingStateChanged.disconnect(
                self.__on_blocking_state_changed)
            widget.processingStateChanged.disconnect(
                self.__on_processing_state_changed)
            widget.deleteLater()

    def __on_env_changed(self, key, newvalue, oldvalue):
        # Notify widgets of a runtime environment change
        for widget in self.__widget_for_node.values():
            widget.workflowEnvChanged(key, newvalue, oldvalue)
    def __init__(self):
        super().__init__()
        #: Input dissimilarity matrix
        self.matrix = None  # type: Optional[Orange.misc.DistMatrix]
        #: Effective data used for plot styling/annotations. Can be from the
        #: input signal (`self.signal_data`) or the input matrix
        #: (`self.matrix.data`)
        self.data = None  # type: Optional[Orange.data.Table]
        #: Input subset data table
        self.subset_data = None  # type: Optional[Orange.data.Table]
        #: Data table from the `self.matrix.row_items` (if present)
        self.matrix_data = None  # type: Optional[Orange.data.Table]
        #: Input data table
        self.signal_data = None

        self._similar_pairs = None
        self._subset_mask = None  # type: Optional[np.ndarray]
        self._invalidated = False
        self.effective_matrix = None
        self._curve = None

        self.variable_x = ContinuousVariable("mds-x")
        self.variable_y = ContinuousVariable("mds-y")

        self.__update_loop = None
        # timer for scheduling updates
        self.__timer = QTimer(self, singleShot=True, interval=0)
        self.__timer.timeout.connect(self.__next_step)
        self.__state = OWMDS.Waiting
        self.__in_next_step = False
        self.__draw_similar_pairs = False

        box = gui.vBox(self.controlArea, "MDS Optimization")
        form = QFormLayout(
            labelAlignment=Qt.AlignLeft,
            formAlignment=Qt.AlignLeft,
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow,
            verticalSpacing=10
        )

        form.addRow(
            "Max iterations:",
            gui.spin(box, self, "max_iter", 10, 10 ** 4, step=1))

        form.addRow(
            "Initialization:",
            gui.radioButtons(box, self, "initialization", btnLabels=("PCA (Torgerson)", "Random"),
                             callback=self.__invalidate_embedding))

        box.layout().addLayout(form)
        form.addRow(
            "Refresh:",
            gui.comboBox(box, self, "refresh_rate", items=[t for t, _ in OWMDS.RefreshRate],
                         callback=self.__invalidate_refresh))
        gui.separator(box, 10)
        self.runbutton = gui.button(box, self, "Run", callback=self._toggle_run)

        box = gui.vBox(self.mainArea, True, margin=0)
        self.graph = OWMDSGraph(self, box, "MDSGraph", view_box=MDSInteractiveViewBox)
        box.layout().addWidget(self.graph.plot_widget)
        self.plot = self.graph.plot_widget

        g = self.graph.gui
        box = g.point_properties_box(self.controlArea)
        self.models = g.points_models
        self.size_model = self.models[2]
        self.label_model = self.models[3]
        self.size_model.order = \
            self.size_model.order[:1] + ("Stress", ) + self.models[2].order[1:]

        gui.hSlider(box, self, "connected_pairs", label="Show similar pairs:", minValue=0,
                    maxValue=20, createLabel=False, callback=self._on_connected_changed)
        g.add_widgets(ids=[g.JitterSizeSlider], widget=box)

        box = gui.vBox(self.controlArea, "Plot Properties")
        g.add_widgets([g.ShowLegend,
                       g.ToolTipShowsAll,
                       g.ClassDensity,
                       g.LabelOnlySelected], box)

        self.controlArea.layout().addStretch(100)
        self.icons = gui.attributeIconDict

        palette = self.graph.plot_widget.palette()
        self.graph.set_palette(palette)

        gui.rubber(self.controlArea)

        self.graph.box_zoom_select(self.controlArea)

        gui.auto_commit(box, self, "auto_commit", "Send Selected",
                        checkbox_label="Send selected automatically",
                        box=None)

        self.plot.getPlotItem().hideButtons()
        self.plot.setRenderHint(QPainter.Antialiasing)

        self.graph.jitter_continuous = True
        self._initialize()
Exemple #43
0
        def add_points():
            nonlocal cur, image_token
            if image_token != self._image_token:
                return
            batch = visible[cur:cur + self.N_POINTS_PER_ITER]

            batch_lat = lat[batch]
            batch_lon = lon[batch]

            x, y = self.Projection.latlon_to_easting_northing(
                batch_lat, batch_lon)
            x, y = self.Projection.easting_northing_to_pixel(
                x, y, zoom, origin, map_pane_pos)

            if self._jittering:
                dx, dy = self._jittering_offsets[batch].T
                x, y = x + dx, y + dy

            colors = (self._colorgen.getRGB(
                self._scaled_color_values[batch]).tolist()
                      if self._color_attr else repeat((0xff, 0, 0)))
            sizes = self._size_coef * \
                (self._sizes[batch] if self._size_attr else np.tile(10, len(batch)))

            opacity_subset, opacity_rest = self._opacity, int(.8 *
                                                              self._opacity)
            for x, y, is_selected, size, color, _in_subset in \
                    zip(x, y, selected[batch], sizes, colors, in_subset[batch]):

                pensize2, selpensize2 = (.35, 1.5) if size >= 5 else (.15, .7)
                pensize2 *= self._size_coef
                selpensize2 *= self._size_coef

                size2 = size / 2
                if is_selected:
                    painter.setPen(QPen(QBrush(Qt.green), 2 * selpensize2))
                    painter.drawEllipse(x - size2 - selpensize2,
                                        y - size2 - selpensize2,
                                        size + selpensize2, size + selpensize2)
                color = QColor(*color)
                if _in_subset:
                    color.setAlpha(opacity_subset)
                    painter.setBrush(QBrush(color))
                    painter.setPen(
                        QPen(QBrush(color.darker(180)), 2 * pensize2))
                else:
                    color.setAlpha(opacity_rest)
                    painter.setBrush(Qt.NoBrush)
                    painter.setPen(
                        QPen(QBrush(color.lighter(120)), 2 * pensize2))

                painter.drawEllipse(x - size2 - pensize2, y - size2 - pensize2,
                                    size + pensize2, size + pensize2)

            im.save(self._overlay_image_path, 'PNG')
            self.evalJS('markersImageLayer.setUrl("{}#{}"); 0;'.format(
                self.toFileURL(self._overlay_image_path), np.random.random()))

            cur += self.N_POINTS_PER_ITER
            if self.stop:
                return
            elif cur < len(visible):
                QTimer.singleShot(10, add_points)
                self._owwidget.progressBarAdvance(100 / n_iters, None)
            else:
                self._owwidget.progressBarFinished(None)
                self._image_token = None
Exemple #44
0
 def start(self):
     QTimer.singleShot(0, self._next)
Exemple #45
0
    def __init__(self):
        super().__init__()
        self._current_path = ""
        self._data_loader = Loader()
        icon_open_dir = self.style().standardIcon(QStyle.SP_DirOpenIcon)

        # Top grid with file selection combo box
        self.file_layout = grid = QGridLayout()
        lb = QLabel("File:")
        lb.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.recent_combo = cb = QComboBox(
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            minimumContentsLength=20,
            toolTip="Select a recent file")
        self.recent_model = cb.model()  # type: QStandardItemModel
        self.recent_combo.activated[int].connect(self._select_recent)

        browse = QPushButton("...",
                             autoDefault=False,
                             icon=icon_open_dir,
                             clicked=self.browse)

        # reload = QPushButton("Reload", autoDefault=False, icon=icon_reload)

        grid.addWidget(lb, 0, 0, Qt.AlignVCenter)
        grid.addWidget(cb, 0, 1)
        grid.addWidget(browse, 0, 2)
        # grid.addWidget(reload, 0, 3)

        self.summary_label = label = QLabel("", self)
        label.ensurePolished()
        f = label.font()
        if f.pointSizeF() != -1:
            f.setPointSizeF(f.pointSizeF() * 5 / 6)
        else:
            f.setPixelSize(f.pixelSize() * 5 / 6)
        label.setFont(f)
        grid.addWidget(label, 1, 1, 1, 3)

        self.controlArea.layout().addLayout(grid)

        box = gui.widgetBox(self.controlArea,
                            "Headers and Row Labels",
                            spacing=-1)
        hl = QHBoxLayout()
        hl.setContentsMargins(0, 0, 0, 0)
        self.header_rows_spin = spin = QSpinBox(box,
                                                minimum=0,
                                                maximum=3,
                                                value=self._header_rows_count,
                                                keyboardTracking=False)
        spin.valueChanged.connect(self.set_header_rows_count)
        hl.addWidget(QLabel("Data starts with", box))
        hl.addWidget(self.header_rows_spin)
        hl.addWidget(QLabel("header row(s)", box))
        hl.addStretch(10)
        box.layout().addLayout(hl)

        hl = QHBoxLayout()
        hl.setContentsMargins(0, 0, 0, 0)
        self.header_cols_spin = spin = QSpinBox(box,
                                                minimum=0,
                                                maximum=3,
                                                value=self._header_cols_count,
                                                keyboardTracking=False)
        spin.valueChanged.connect(self.set_header_cols_count)

        hl.addWidget(QLabel("First", box))
        hl.addWidget(self.header_cols_spin)
        hl.addWidget(QLabel("column(s) are row labels", box))
        hl.addStretch(10)
        box.layout().addLayout(hl)

        self.data_struct_box = box = gui.widgetBox(self.controlArea,
                                                   "Input Data Structure")
        gui.radioButtons(box,
                         self,
                         "_cells_in_rows", [
                             "Genes in rows, cells in columns",
                             "Cells in rows, genes in columns"
                         ],
                         callback=self._cells_in_rows_changed)

        box = gui.widgetBox(self.controlArea, "Sample Data", spacing=-1)

        grid = QGridLayout()
        grid.setContentsMargins(0, 0, 0, 0)
        box.layout().addLayout(grid)

        self.sample_rows_cb = cb = QCheckBox(checked=self._sample_rows_enabled)
        self.sample_rows_p_spin = spin = QSpinBox(minimum=0,
                                                  maximum=100,
                                                  value=self._sample_rows_p)
        spin.valueChanged.connect(self.set_sample_rows_p)
        suffix = QLabel("% of cells")
        cb.toggled.connect(self.set_sample_rows_enabled)

        grid.addWidget(cb, 0, 0)
        grid.addWidget(spin, 0, 1)
        grid.addWidget(suffix, 0, 2)

        self.sample_cols_cb = cb = QCheckBox(checked=self._sample_cols_enabled)
        self.sample_cols_p_spin = spin = QSpinBox(minimum=0,
                                                  maximum=100,
                                                  value=self._sample_cols_p)
        spin.valueChanged.connect(self.set_sample_cols_p)
        suffix = QLabel("% of genes")
        cb.toggled.connect(self.set_sample_cols_enabled)

        grid.addWidget(cb, 1, 0)
        grid.addWidget(spin, 1, 1)
        grid.addWidget(suffix, 1, 2)
        grid.setColumnStretch(3, 10)

        self.annotation_files_box = box = gui.widgetBox(
            self.controlArea, "Cell && Gene Annotation Files")
        form = QFormLayout(
            formAlignment=Qt.AlignLeft,
            rowWrapPolicy=QFormLayout.WrapAllRows,
        )
        box.layout().addLayout(form)

        self.row_annotations_cb = cb = QCheckBox(
            "Cell annotations", checked=self._row_annotations_enabled)
        self._row_annotations_w = w = QWidget(
            enabled=self._row_annotations_enabled)
        cb.toggled.connect(self.set_row_annotations_enabled)
        cb.toggled.connect(w.setEnabled)
        hl = QHBoxLayout()
        hl.setContentsMargins(0, 0, 0, 0)
        w.setLayout(hl)
        self.row_annotations_combo = QComboBox(
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            minimumContentsLength=18)
        self.row_annotations_combo.activated.connect(
            self._row_annotations_combo_changed)
        hl.addWidget(self.row_annotations_combo)
        hl.addWidget(
            QPushButton("...",
                        box,
                        autoDefault=False,
                        icon=icon_open_dir,
                        clicked=self.browse_row_annotations))
        # hl.addWidget(QPushButton("Reload", box, autoDefault=False,
        #                          icon=icon_reload))
        form.addRow(cb, w)

        self.col_annotations_cb = cb = QCheckBox(
            "Gene annotations", checked=self._col_annotations_enabled)
        self._col_annotations_w = w = QWidget(
            enabled=self._col_annotations_enabled)
        cb.toggled.connect(self.set_col_annotations_enabled)
        cb.toggled.connect(w.setEnabled)
        hl = QHBoxLayout()
        hl.setContentsMargins(0, 0, 0, 0)
        w.setLayout(hl)
        self.col_annotations_combo = QComboBox(
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            minimumContentsLength=18)
        self.col_annotations_combo.activated.connect(
            self._col_annotations_combo_changed)
        hl.addWidget(self.col_annotations_combo)
        hl.addWidget(
            QPushButton("...",
                        box,
                        autoDefault=False,
                        icon=icon_open_dir,
                        clicked=self.browse_col_annotations))
        # hl.addWidget(QPushButton("Reload", box, autoDefault=False,
        #                          icon=icon_reload))
        form.addRow(cb, w)

        self.controlArea.layout().addStretch(10)
        self.load_data_button = button = VariableTextPushButton(
            "Load data",
            autoDefault=True,
            textChoiceList=["Load data", "Reload"])
        self.load_data_button.setAutoDefault(True)
        button.clicked.connect(self.commit, Qt.QueuedConnection)
        self.controlArea.layout().addWidget(button, alignment=Qt.AlignRight)

        init_recent_paths_model(
            self.recent_model,
            [RecentPath.create(p, []) for p in self._recent],
        )
        init_recent_paths_model(
            self.row_annotations_combo.model(),
            [RecentPath.create(p, []) for p in self._recent_row_annotations])
        init_recent_paths_model(
            self.col_annotations_combo.model(),
            [RecentPath.create(p, []) for p in self._recent_col_annotations])
        self._update_summary()
        self._update_warning()

        if self._last_path != "" and os.path.exists(self._last_path):
            QTimer.singleShot(0,
                              lambda: self.set_current_path(self._last_path))
        else:
            self.recent_combo.setCurrentIndex(-1)
class EventSpy(QObject):
    """
    A testing utility class (similar to QSignalSpy) to record events
    delivered to a QObject instance.

    Note
    ----
    Only event types can be recorded (as QEvent instances are deleted
    on delivery).

    Note
    ----
    Can only be used with a QCoreApplication running.

    Parameters
    ----------
    object : QObject
        An object whose events need to be recorded.
    etype : Union[QEvent.Type, Sequence[QEvent.Type]
        A event type (or types) that should be recorded
    """
    def __init__(self, object, etype, **kwargs):
        super().__init__(**kwargs)
        if not isinstance(object, QObject):
            raise TypeError

        self.__object = object
        try:
            len(etype)
        except TypeError:
            etypes = {etype}
        else:
            etypes = set(etype)

        self.__etypes = etypes
        self.__record = []
        self.__loop = QEventLoop()
        self.__timer = QTimer(self, singleShot=True)
        self.__timer.timeout.connect(self.__loop.quit)
        self.__object.installEventFilter(self)

    def wait(self, timeout=5000):
        """
        Start an event loop that runs until a spied event or a timeout occurred.

        Parameters
        ----------
        timeout : int
            Timeout in milliseconds.

        Returns
        -------
        res : bool
            True if the event occurred and False otherwise.

        Example
        -------
        >>> app = QCoreApplication.instance() or QCoreApplication([])
        >>> obj = QObject()
        >>> spy = EventSpy(obj, QEvent.User)
        >>> app.postEvent(obj, QEvent(QEvent.User))
        >>> spy.wait()
        True
        >>> print(spy.events())
        [1000]
        """
        count = len(self.__record)
        self.__timer.stop()
        self.__timer.setInterval(timeout)
        self.__timer.start()
        self.__loop.exec_()
        self.__timer.stop()
        return len(self.__record) != count

    def eventFilter(self, reciever, event):
        if reciever is self.__object and event.type() in self.__etypes:
            self.__record.append(event.type())
            if self.__loop.isRunning():
                self.__loop.quit()
        return super().eventFilter(reciever, event)

    def events(self):
        """
        Return a list of all (listened to) event types that occurred.

        Returns
        -------
        events : List[QEvent.Type]
        """
        return list(self.__record)
    def __init__(self):
        super().__init__()

        box = gui.vBox(self.mainArea, True, margin=0)
        self.graph = OWScatterPlotGraph(self, box, "ScatterPlot")
        box.layout().addWidget(self.graph.plot_widget)
        plot = self.graph.plot_widget

        axispen = QPen(self.palette().color(QPalette.Text))
        axis = plot.getAxis("bottom")
        axis.setPen(axispen)

        axis = plot.getAxis("left")
        axis.setPen(axispen)

        self.data = None  # Orange.data.Table
        self.subset_data = None  # Orange.data.Table
        self.sql_data = None  # Orange.data.sql.table.SqlTable
        self.attribute_selection_list = None  # list of Orange.data.Variable
        self.__timer = QTimer(self, interval=1200)
        self.__timer.timeout.connect(self.add_data)
        #: Remember the saved state to restore
        self.__pending_selection_restore = self.selection_group
        self.selection_group = None

        common_options = dict(labelWidth=50,
                              orientation=Qt.Horizontal,
                              sendSelectedValue=True,
                              valueType=str)
        box = gui.vBox(self.controlArea, "Axis Data")
        dmod = DomainModel
        self.xy_model = DomainModel(dmod.MIXED, valid_types=dmod.PRIMITIVE)
        self.cb_attr_x = gui.comboBox(box,
                                      self,
                                      "attr_x",
                                      label="Axis x:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        self.cb_attr_y = gui.comboBox(box,
                                      self,
                                      "attr_y",
                                      label="Axis y:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)

        vizrank_box = gui.hBox(box)
        gui.separator(vizrank_box, width=common_options["labelWidth"])
        self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank(
            vizrank_box, self, "Find Informative Projections", self.set_attr)

        gui.separator(box)

        g = self.graph.gui
        g.add_widgets([g.JitterSizeSlider, g.JitterNumericValues], box)

        self.sampling = gui.auto_commit(self.controlArea,
                                        self,
                                        "auto_sample",
                                        "Sample",
                                        box="Sampling",
                                        callback=self.switch_sampling,
                                        commit=lambda: self.add_data(1))
        self.sampling.setVisible(False)

        g.point_properties_box(self.controlArea)
        self.models = [self.xy_model] + g.points_models

        box_plot_prop = gui.vBox(self.controlArea, "Plot Properties")
        g.add_widgets([
            g.ShowLegend, g.ShowGridLines, g.ToolTipShowsAll, g.ClassDensity,
            g.RegressionLine, g.LabelOnlySelected
        ], box_plot_prop)

        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_send_selection",
                        "Send Selection", "Send Automatically")

        self.graph.zoom_actions(self)
Exemple #48
0
 def __init__(self, parent, plot):
     super().__init__(parent, plot)
     self.__timer = QTimer(self, interval=50)
     self.__timer.timeout.connect(self.__timout)
     self.__count = itertools.count()
     self.__pos = None
Exemple #49
0
    def eventFilter(self, receiver, event):
        if receiver is self.flow_view and event.type() == QEvent.LayoutRequest:
            QTimer.singleShot(0, self.__update_size_constraint)

        return super().eventFilter(receiver, event)
Exemple #50
0
    def test_anchoritem(self):
        anchoritem = NodeAnchorItem(None)
        anchoritem.setAnimationEnabled(False)
        self.scene.addItem(anchoritem)

        path = QPainterPath()
        path.addEllipse(0, 0, 100, 100)

        anchoritem.setAnchorPath(path)

        anchor = AnchorPoint()
        anchoritem.addAnchor(anchor)

        ellipse1 = QGraphicsEllipseItem(-3, -3, 6, 6)
        ellipse2 = QGraphicsEllipseItem(-3, -3, 6, 6)
        self.scene.addItem(ellipse1)
        self.scene.addItem(ellipse2)

        anchor.scenePositionChanged.connect(ellipse1.setPos)

        with self.assertRaises(ValueError):
            anchoritem.addAnchor(anchor)

        anchor1 = AnchorPoint()
        anchoritem.addAnchor(anchor1)

        anchor1.scenePositionChanged.connect(ellipse2.setPos)

        self.assertSequenceEqual(anchoritem.anchorPoints(), [anchor, anchor1])

        self.assertSequenceEqual(anchoritem.anchorPositions(), [2 / 3, 1 / 3])

        anchoritem.setAnchorPositions([0.5, 0.0])
        self.assertSequenceEqual(anchoritem.anchorPositions(), [0.5, 0.0])

        def advance():
            t = anchoritem.anchorPositions()
            t = [(t + 0.05) % 1.0 for t in t]
            anchoritem.setAnchorPositions(t)

        timer = QTimer(anchoritem, interval=10)
        timer.start()
        timer.timeout.connect(advance)

        self.qWait()
        timer.stop()

        anchoritem.setAnchorOpen(True)
        anchoritem.setHovered(True)
        self.assertEqual(*[p.scenePos() for p in anchoritem.anchorPoints()])
        anchoritem.setAnchorOpen(False)
        self.assertNotEqual(*[p.scenePos() for p in anchoritem.anchorPoints()])
        anchoritem.setAnchorOpen(False)
        anchoritem.setHovered(True)
        self.assertNotEqual(*[p.scenePos() for p in anchoritem.anchorPoints()])

        anchoritem = NodeAnchorItem(None)

        anchoritem.setSignals([
            InputSignal("first", "object", "set_first"),
            InputSignal("second", "object", "set_second")
        ])
        self.assertListEqual(
            anchoritem._NodeAnchorItem__pathStroker.dashPattern(),
            list(anchoritem._NodeAnchorItem__unanchoredDash))
        anchoritem.setAnchorOpen(True)
        anchoritem.setHovered(True)
        self.assertListEqual(
            anchoritem._NodeAnchorItem__pathStroker.dashPattern(),
            list(anchoritem._NodeAnchorItem__channelDash))
Exemple #51
0
    def test(self):
        window = QWidget()
        layout = QVBoxLayout()
        window.setLayout(layout)

        stack = stackedwidget.AnimatedStackedWidget(animationEnabled=False)

        layout.addStretch(2)
        layout.addWidget(stack)
        layout.addStretch(2)
        window.show()

        widget1 = QLabel("A label " * 10)
        widget1.setWordWrap(True)

        widget2 = QGroupBox("Group")

        widget3 = QListView()
        self.assertEqual(stack.count(), 0)
        self.assertEqual(stack.currentIndex(), -1)

        stack.addWidget(widget1)
        self.assertEqual(stack.count(), 1)
        self.assertEqual(stack.currentIndex(), 0)

        stack.addWidget(widget2)
        stack.addWidget(widget3)
        self.assertEqual(stack.count(), 3)
        self.assertEqual(stack.currentIndex(), 0)

        def widgets():
            return [stack.widget(i) for i in range(stack.count())]

        self.assertSequenceEqual([widget1, widget2, widget3], widgets())
        stack.show()

        stack.removeWidget(widget2)
        self.assertEqual(stack.count(), 2)
        self.assertEqual(stack.currentIndex(), 0)
        self.assertSequenceEqual([widget1, widget3], widgets())

        stack.setCurrentIndex(1)
        self.qWait()

        self.assertEqual(stack.currentIndex(), 1)

        widget2 = QGroupBox("Group")
        stack.insertWidget(1, widget2)
        self.assertEqual(stack.count(), 3)
        self.assertEqual(stack.currentIndex(), 2)
        self.assertSequenceEqual([widget1, widget2, widget3], widgets())

        def toogle():
            idx = stack.currentIndex()
            stack.setCurrentIndex((idx + 1) % stack.count())

        timer = QTimer(stack, interval=100)
        timer.timeout.connect(toogle)
        timer.start()
        self.qWait(200)
        timer.stop()
        window.deleteLater()
Exemple #52
0
    def test_nodeitem(self):
        one_item = NodeItem()
        one_item.setWidgetDescription(self.one_desc)
        one_item.setWidgetCategory(self.const_desc)

        one_item.setTitle("Neo")
        self.assertEqual(one_item.title(), "Neo")

        one_item.setProcessingState(True)
        self.assertEqual(one_item.processingState(), True)

        one_item.setProgress(50)
        self.assertEqual(one_item.progress(), 50)

        one_item.setProgress(100)
        self.assertEqual(one_item.progress(), 100)

        one_item.setProgress(101)
        self.assertEqual(one_item.progress(), 100, "Progress overshots")

        one_item.setProcessingState(False)
        self.assertEqual(one_item.processingState(), False)
        self.assertEqual(one_item.progress(), -1,
                         "setProcessingState does not clear the progress.")

        self.scene.addItem(one_item)
        one_item.setPos(100, 100)

        negate_item = NodeItem()
        negate_item.setWidgetDescription(self.negate_desc)
        negate_item.setWidgetCategory(self.const_desc)

        self.scene.addItem(negate_item)
        negate_item.setPos(300, 100)

        nb_item = NodeItem()
        nb_item.setWidgetDescription(self.add_desc)
        nb_item.setWidgetCategory(self.operator_desc)

        self.scene.addItem(nb_item)
        nb_item.setPos(500, 100)

        positions = []
        anchor = one_item.newOutputAnchor()
        anchor.scenePositionChanged.connect(positions.append)

        one_item.setPos(110, 100)
        self.assertTrue(len(positions) > 0)

        one_item.setErrorMessage("message")
        one_item.setWarningMessage("message")
        one_item.setInfoMessage("I am alive")

        one_item.setErrorMessage(None)
        one_item.setWarningMessage(None)
        one_item.setInfoMessage(None)

        one_item.setInfoMessage("I am back.")
        nb_item.setProcessingState(1)
        negate_item.setProcessingState(1)
        negate_item.shapeItem.startSpinner()

        def progress():
            p = (nb_item.progress() + 25) % 100
            nb_item.setProgress(p)

            if p > 50:
                nb_item.setInfoMessage("Over 50%")
                one_item.setWarningMessage("Second")
            else:
                nb_item.setInfoMessage(None)
                one_item.setWarningMessage(None)

            negate_item.setAnchorRotation(50 - p)

        timer = QTimer(nb_item, interval=5)
        timer.start()
        timer.timeout.connect(progress)
        self.qWait()
        timer.stop()
Exemple #53
0
 def showEvent(self, event):
     super().showEvent(event)
     QTimer.singleShot(0, self._update_splitter)
Exemple #54
0
class OWScatterPlot(OWWidget):
    """Scatterplot visualization with explorative analysis and intelligent
    data visualization enhancements."""

    name = 'Scatter Plot'
    description = "Interactive scatter plot visualization with " \
                  "intelligent data visualization enhancements."
    icon = "icons/ScatterPlot.svg"
    priority = 140

    inputs = [("Data", Table, "set_data", Default),
              ("Data Subset", Table, "set_subset_data"),
              ("Features", AttributeList, "set_shown_attributes")]

    outputs = [("Selected Data", Table, Default),
               (ANNOTATED_DATA_SIGNAL_NAME, Table),
               ("Features", Table)]

    settingsHandler = DomainContextHandler()

    auto_send_selection = Setting(True)
    auto_sample = Setting(True)
    toolbar_selection = Setting(0)

    attr_x = ContextSetting(None)
    attr_y = ContextSetting(None)

    graph = SettingProvider(OWScatterPlotGraph)

    jitter_sizes = [0, 0.1, 0.5, 1, 2, 3, 4, 5, 7, 10]

    graph_name = "graph.plot_widget.plotItem"

    class Information(OWWidget.Information):
        sampled_sql = Msg("Large SQL table; showing a sample.")

    def __init__(self):
        super().__init__()

        box = gui.vBox(self.mainArea, True, margin=0)
        self.graph = OWScatterPlotGraph(self, box, "ScatterPlot")
        box.layout().addWidget(self.graph.plot_widget)
        plot = self.graph.plot_widget

        axispen = QPen(self.palette().color(QPalette.Text))
        axis = plot.getAxis("bottom")
        axis.setPen(axispen)

        axis = plot.getAxis("left")
        axis.setPen(axispen)

        self.data = None  # Orange.data.Table
        self.subset_data = None  # Orange.data.Table
        self.data_metas_X = None  # self.data, where primitive metas are moved to X
        self.sql_data = None  # Orange.data.sql.table.SqlTable
        self.attribute_selection_list = None  # list of Orange.data.Variable
        self.__timer = QTimer(self, interval=1200)
        self.__timer.timeout.connect(self.add_data)

        common_options = dict(
            labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True,
            valueType=str)
        box = gui.vBox(self.controlArea, "Axis Data")
        dmod = DomainModel
        self.xy_model = DomainModel(dmod.MIXED, valid_types=dmod.PRIMITIVE)
        gui.comboBox(
            box, self, "attr_x", label="Axis x:", callback=self.update_attr,
            model=self.xy_model, **common_options)
        self.cb_attr_y = gui.comboBox(
            box, self, "attr_y", label="Axis y:", callback=self.update_attr,
            model=self.xy_model, **common_options)

        vizrank_box = gui.hBox(box)
        gui.separator(vizrank_box, width=common_options["labelWidth"])
        self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank(
            vizrank_box, self, "Find Informative Projections", self.set_attr)

        gui.separator(box)

        gui.valueSlider(
            box, self, value='graph.jitter_size', label='Jittering: ',
            values=self.jitter_sizes, callback=self.reset_graph_data,
            labelFormat=lambda x:
            "None" if x == 0 else ("%.1f %%" if x < 1 else "%d %%") % x)
        gui.checkBox(
            gui.indentedBox(box), self, 'graph.jitter_continuous',
            'Jitter continuous values', callback=self.reset_graph_data)

        self.sampling = gui.auto_commit(
            self.controlArea, self, "auto_sample", "Sample", box="Sampling",
            callback=self.switch_sampling, commit=lambda: self.add_data(1))
        self.sampling.setVisible(False)

        box = gui.vBox(self.controlArea, "Points")
        self.color_model = DomainModel(
            placeholder="(Same color)", valid_types=dmod.PRIMITIVE)
        self.cb_attr_color = gui.comboBox(
            box, self, "graph.attr_color", label="Color:",
            callback=self.update_colors,
            model=self.color_model, **common_options)
        self.label_model = DomainModel(
            placeholder="(No labels)", valid_types=dmod.PRIMITIVE)
        self.cb_attr_label = gui.comboBox(
            box, self, "graph.attr_label", label="Label:",
            callback=self.graph.update_labels,
            model=self.label_model, **common_options)
        self.shape_model = DomainModel(
            placeholder="(Same shape)", valid_types=DiscreteVariable)
        self.cb_attr_shape = gui.comboBox(
            box, self, "graph.attr_shape", label="Shape:",
            callback=self.graph.update_shapes,
            model=self.shape_model, **common_options)
        self.size_model = DomainModel(
            placeholder="(Same size)", valid_types=ContinuousVariable)
        self.cb_attr_size = gui.comboBox(
            box, self, "graph.attr_size", label="Size:",
            callback=self.graph.update_sizes,
            model=self.size_model, **common_options)
        self.models = [self.xy_model, self.color_model, self.label_model,
                       self.shape_model, self.size_model]

        g = self.graph.gui
        g.point_properties_box(self.controlArea, box)
        box = gui.vBox(self.controlArea, "Plot Properties")
        g.add_widgets([g.ShowLegend, g.ShowGridLines], box)
        gui.checkBox(
            box, self, value='graph.tooltip_shows_all',
            label='Show all data on mouse hover')
        self.cb_class_density = gui.checkBox(
            box, self, value='graph.class_density', label='Show class density',
            callback=self.update_density)
        gui.checkBox(
            box, self, 'graph.label_only_selected',
            'Label only selected points', callback=self.graph.update_labels)

        self.zoom_select_toolbar = g.zoom_select_toolbar(
            gui.vBox(self.controlArea, "Zoom/Select"), nomargin=True,
            buttons=[g.StateButtonsBegin, g.SimpleSelect, g.Pan, g.Zoom,
                     g.StateButtonsEnd, g.ZoomReset]
        )
        buttons = self.zoom_select_toolbar.buttons
        buttons[g.Zoom].clicked.connect(self.graph.zoom_button_clicked)
        buttons[g.Pan].clicked.connect(self.graph.pan_button_clicked)
        buttons[g.SimpleSelect].clicked.connect(self.graph.select_button_clicked)
        buttons[g.ZoomReset].clicked.connect(self.graph.reset_button_clicked)
        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_send_selection",
                        "Send Selection", "Send Automatically")

        def zoom(s):
            """Zoom in/out by factor `s`."""
            viewbox = plot.getViewBox()
            # scaleBy scales the view's bounds (the axis range)
            viewbox.scaleBy((1 / s, 1 / s))

        def fit_to_view():
            viewbox = plot.getViewBox()
            viewbox.autoRange()

        zoom_in = QAction(
            "Zoom in", self, triggered=lambda: zoom(1.25)
        )
        zoom_in.setShortcuts([QKeySequence(QKeySequence.ZoomIn),
                              QKeySequence(self.tr("Ctrl+="))])
        zoom_out = QAction(
            "Zoom out", self, shortcut=QKeySequence.ZoomOut,
            triggered=lambda: zoom(1 / 1.25)
        )
        zoom_fit = QAction(
            "Fit in view", self,
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),
            triggered=fit_to_view
        )
        self.addActions([zoom_in, zoom_out, zoom_fit])

    # def settingsFromWidgetCallback(self, handler, context):
    #     context.selectionPolygons = []
    #     for curve in self.graph.selectionCurveList:
    #         xs = [curve.x(i) for i in range(curve.dataSize())]
    #         ys = [curve.y(i) for i in range(curve.dataSize())]
    #         context.selectionPolygons.append((xs, ys))

    # def settingsToWidgetCallback(self, handler, context):
    #     selections = getattr(context, "selectionPolygons", [])
    #     for (xs, ys) in selections:
    #         c = SelectionCurve("")
    #         c.setData(xs,ys)
    #         c.attach(self.graph)
    #         self.graph.selectionCurveList.append(c)

    def reset_graph_data(self, *_):
        self.graph.rescale_data()
        self.update_graph()

    def set_data(self, data):
        self.clear_messages()
        self.Information.sampled_sql.clear()
        self.__timer.stop()
        self.sampling.setVisible(False)
        self.sql_data = None
        if isinstance(data, SqlTable):
            if data.approx_len() < 4000:
                data = Table(data)
            else:
                self.Information.sampled_sql()
                self.sql_data = data
                data_sample = data.sample_time(0.8, no_cache=True)
                data_sample.download_data(2000, partial=True)
                data = Table(data_sample)
                self.sampling.setVisible(True)
                if self.auto_sample:
                    self.__timer.start()

        if data is not None and (len(data) == 0 or len(data.domain) == 0):
            data = None
        if self.data and data and self.data.checksum() == data.checksum():
            return

        self.closeContext()
        same_domain = (self.data and data and
                       data.domain.checksum() == self.data.domain.checksum())
        self.data = data
        self.data_metas_X = self.move_primitive_metas_to_X(data)

        if not same_domain:
            self.init_attr_values()
        self.vizrank.initialize()
        self.vizrank.attrs = self.data.domain.attributes if self.data is not None else []
        self.vizrank_button.setEnabled(
            self.data is not None and self.data.domain.class_var is not None
            and len(self.data.domain.attributes) > 1 and len(self.data) > 1)
        if self.data is not None and self.data.domain.class_var is None \
            and len(self.data.domain.attributes) > 1 and len(self.data) > 1:
            self.vizrank_button.setToolTip(
                "Data with a class variable is required.")
        else:
            self.vizrank_button.setToolTip("")
        self.openContext(self.data)

        def findvar(name, iterable):
            """Find a Orange.data.Variable in `iterable` by name"""
            for el in iterable:
                if isinstance(el, Orange.data.Variable) and el.name == name:
                    return el
            else:
                return None
        # handle restored settings from  < 3.3.9 when attr_* were stored
        # by name
        if isinstance(self.attr_x, str):
            self.attr_x = findvar(self.attr_x, self.xy_model)
        if isinstance(self.attr_y, str):
            self.attr_y = findvar(self.attr_y, self.xy_model)
        if isinstance(self.graph.attr_label, str):
            self.graph.attr_label = findvar(
                self.graph.attr_label, self.label_model)
        if isinstance(self.graph.attr_color, str):
            self.graph.attr_color = findvar(
                self.graph.attr_color, self.color_model)
        if isinstance(self.graph.attr_shape, str):
            self.graph.attr_shape = findvar(
                self.graph.attr_shape, self.shape_model)
        if isinstance(self.graph.attr_size, str):
            self.graph.attr_size = findvar(
                self.graph.attr_size, self.size_model)

    def add_data(self, time=0.4):
        if self.data and len(self.data) > 2000:
            return self.__timer.stop()
        data_sample = self.sql_data.sample_time(time, no_cache=True)
        if data_sample:
            data_sample.download_data(2000, partial=True)
            data = Table(data_sample)
            self.data = Table.concatenate((self.data, data), axis=0)
            self.data_metas_X = self.move_primitive_metas_to_X(self.data)
            self.handleNewSignals()

    def switch_sampling(self):
        self.__timer.stop()
        if self.auto_sample and self.sql_data:
            self.add_data()
            self.__timer.start()

    def move_primitive_metas_to_X(self, data):
        if data is not None:
            new_attrs = [a for a in data.domain.attributes + data.domain.metas
                         if a.is_primitive()]
            new_metas = [m for m in data.domain.metas if not m.is_primitive()]
            data = Table.from_table(Domain(new_attrs, data.domain.class_vars,
                                           new_metas), data)
        return data

    def set_subset_data(self, subset_data):
        self.warning()
        if isinstance(subset_data, SqlTable):
            if subset_data.approx_len() < AUTO_DL_LIMIT:
                subset_data = Table(subset_data)
            else:
                self.warning("Data subset does not support large Sql tables")
                subset_data = None
        self.subset_data = self.move_primitive_metas_to_X(subset_data)
        self.controls.graph.alpha_value.setEnabled(subset_data is None)

    # called when all signals are received, so the graph is updated only once
    def handleNewSignals(self):
        self.graph.new_data(self.data_metas_X, self.subset_data)
        if self.attribute_selection_list and \
                all(attr in self.graph.domain
                    for attr in self.attribute_selection_list):
            self.attr_x = self.attribute_selection_list[0]
            self.attr_y = self.attribute_selection_list[1]
        self.attribute_selection_list = None
        self.update_graph()
        self.cb_class_density.setEnabled(self.graph.can_draw_density())
        self.unconditional_commit()

    def set_shown_attributes(self, attributes):
        if attributes and len(attributes) >= 2:
            self.attribute_selection_list = attributes[:2]
        else:
            self.attribute_selection_list = None

    def get_shown_attributes(self):
        return self.attr_x, self.attr_y

    def init_attr_values(self):
        domain = self.data and self.data.domain
        for model in self.models:
            model.set_domain(domain)
        self.attr_x = self.xy_model[0] if self.xy_model else None
        self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \
            else self.attr_x
        self.graph.attr_color = domain and self.data.domain.class_var or None
        self.graph.attr_shape = None
        self.graph.attr_size = None
        self.graph.attr_label = None

    def set_attr(self, attr_x, attr_y):
        self.attr_x, self.attr_y = attr_x, attr_y
        self.update_attr()

    def update_attr(self):
        self.update_graph()
        self.cb_class_density.setEnabled(self.graph.can_draw_density())
        self.send_features()

    def update_colors(self):
        self.graph.update_colors()
        self.cb_class_density.setEnabled(self.graph.can_draw_density())

    def update_density(self):
        self.update_graph(reset_view=False)

    def update_graph(self, reset_view=True, **_):
        self.graph.zoomStack = []
        if self.graph.data is None:
            return
        self.graph.update_data(self.attr_x, self.attr_y, reset_view)

    def selection_changed(self):
        self.send_data()

    def send_data(self):
        selected = None
        selection = None
        # TODO: Implement selection for sql data
        if isinstance(self.data, SqlTable):
            selected = self.data
        elif self.data is not None:
            selection = self.graph.get_selection()
            if len(selection) > 0:
                selected = self.data[selection]
        self.send("Selected Data", selected)
        self.send(ANNOTATED_DATA_SIGNAL_NAME,
                  create_annotated_table(self.data, selection))

    def send_features(self):
        features = None
        if self.attr_x or self.attr_y:
            dom = Domain([], metas=(StringVariable(name="feature"),))
            features = Table(dom, [[self.attr_x], [self.attr_y]])
            features.name = "Features"
        self.send("Features", features)

    def commit(self):
        self.send_data()
        self.send_features()

    def get_widget_name_extension(self):
        if self.data is not None:
            return "{} vs {}".format(self.attr_x.name, self.attr_y.name)

    def send_report(self):
        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.attr_x.is_discrete or
                           self.attr_y.is_discrete or
                           self.graph.jitter_continuous) and
             self.graph.jitter_size)))
        self.report_plot()
        if caption:
            self.report_caption(caption)

    def onDeleteWidget(self):
        super().onDeleteWidget()
        self.graph.plot_widget.getViewBox().deleteLater()
        self.graph.plot_widget.clear()
class OWMDS(OWWidget):
    name = "MDS"
    description = "Two-dimensional data projection by multidimensional " \
                  "scaling constructed from a distance matrix."
    icon = "icons/MDS.svg"

    class Inputs:
        data = Input("Data", Orange.data.Table, default=True)
        distances = Input("Distances", Orange.misc.DistMatrix)
        data_subset = Input("Data Subset", Orange.data.Table)

    class Outputs:
        selected_data = Output("Selected Data", Orange.data.Table, default=True)
        annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Orange.data.Table)

    settings_version = 2

    #: Initialization type
    PCA, Random = 0, 1

    #: Refresh rate
    RefreshRate = [
        ("Every iteration", 1),
        ("Every 5 steps", 5),
        ("Every 10 steps", 10),
        ("Every 25 steps", 25),
        ("Every 50 steps", 50),
        ("None", -1)
    ]

    #: Runtime state
    Running, Finished, Waiting = 1, 2, 3

    settingsHandler = settings.DomainContextHandler()

    max_iter = settings.Setting(300)
    initialization = settings.Setting(PCA)
    refresh_rate = settings.Setting(3)

    # output embedding role.
    NoRole, AttrRole, AddAttrRole, MetaRole = 0, 1, 2, 3

    auto_commit = settings.Setting(True)

    selection_indices = settings.Setting(None, schema_only=True)

    #: Percentage of all pairs displayed (ranges from 0 to 20)
    connected_pairs = settings.Setting(5)

    legend_anchor = settings.Setting(((1, 0), (1, 0)))

    graph = SettingProvider(OWMDSGraph)

    jitter_sizes = [0, 0.1, 0.5, 1, 2, 3, 4, 5, 7, 10]

    graph_name = "graph.plot_widget.plotItem"

    class Error(OWWidget.Error):
        not_enough_rows = Msg("Input data needs at least 2 rows")
        matrix_too_small = Msg("Input matrix must be at least 2x2")
        no_attributes = Msg("Data has no attributes")
        mismatching_dimensions = \
            Msg("Data and distances dimensions do not match.")
        out_of_memory = Msg("Out of memory")
        optimization_error = Msg("Error during optimization\n{}")

    def __init__(self):
        super().__init__()
        #: Input dissimilarity matrix
        self.matrix = None  # type: Optional[Orange.misc.DistMatrix]
        #: Effective data used for plot styling/annotations. Can be from the
        #: input signal (`self.signal_data`) or the input matrix
        #: (`self.matrix.data`)
        self.data = None  # type: Optional[Orange.data.Table]
        #: Input subset data table
        self.subset_data = None  # type: Optional[Orange.data.Table]
        #: Data table from the `self.matrix.row_items` (if present)
        self.matrix_data = None  # type: Optional[Orange.data.Table]
        #: Input data table
        self.signal_data = None

        self._similar_pairs = None
        self._subset_mask = None  # type: Optional[np.ndarray]
        self._invalidated = False
        self.effective_matrix = None
        self._curve = None

        self.variable_x = ContinuousVariable("mds-x")
        self.variable_y = ContinuousVariable("mds-y")

        self.__update_loop = None
        # timer for scheduling updates
        self.__timer = QTimer(self, singleShot=True, interval=0)
        self.__timer.timeout.connect(self.__next_step)
        self.__state = OWMDS.Waiting
        self.__in_next_step = False
        self.__draw_similar_pairs = False

        box = gui.vBox(self.controlArea, "MDS Optimization")
        form = QFormLayout(
            labelAlignment=Qt.AlignLeft,
            formAlignment=Qt.AlignLeft,
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow,
            verticalSpacing=10
        )

        form.addRow(
            "Max iterations:",
            gui.spin(box, self, "max_iter", 10, 10 ** 4, step=1))

        form.addRow(
            "Initialization:",
            gui.radioButtons(box, self, "initialization", btnLabels=("PCA (Torgerson)", "Random"),
                             callback=self.__invalidate_embedding))

        box.layout().addLayout(form)
        form.addRow(
            "Refresh:",
            gui.comboBox(box, self, "refresh_rate", items=[t for t, _ in OWMDS.RefreshRate],
                         callback=self.__invalidate_refresh))
        gui.separator(box, 10)
        self.runbutton = gui.button(box, self, "Run", callback=self._toggle_run)

        box = gui.vBox(self.mainArea, True, margin=0)
        self.graph = OWMDSGraph(self, box, "MDSGraph", view_box=MDSInteractiveViewBox)
        box.layout().addWidget(self.graph.plot_widget)
        self.plot = self.graph.plot_widget

        g = self.graph.gui
        box = g.point_properties_box(self.controlArea)
        self.models = g.points_models
        self.size_model = self.models[2]
        self.label_model = self.models[3]
        self.size_model.order = \
            self.size_model.order[:1] + ("Stress", ) + self.models[2].order[1:]

        gui.hSlider(box, self, "connected_pairs", label="Show similar pairs:", minValue=0,
                    maxValue=20, createLabel=False, callback=self._on_connected_changed)
        g.add_widgets(ids=[g.JitterSizeSlider], widget=box)

        box = gui.vBox(self.controlArea, "Plot Properties")
        g.add_widgets([g.ShowLegend,
                       g.ToolTipShowsAll,
                       g.ClassDensity,
                       g.LabelOnlySelected], box)

        self.controlArea.layout().addStretch(100)
        self.icons = gui.attributeIconDict

        palette = self.graph.plot_widget.palette()
        self.graph.set_palette(palette)

        gui.rubber(self.controlArea)

        self.graph.box_zoom_select(self.controlArea)

        gui.auto_commit(box, self, "auto_commit", "Send Selected",
                        checkbox_label="Send selected automatically",
                        box=None)

        self.plot.getPlotItem().hideButtons()
        self.plot.setRenderHint(QPainter.Antialiasing)

        self.graph.jitter_continuous = True
        self._initialize()

    def reset_graph_data(self, *_):
        if self.data is not None:
            self.graph.rescale_data()
            self.update_graph()
        self.connect_pairs()

    def update_colors(self):
        pass

    def update_density(self):
        self.update_graph(reset_view=False)

    def update_regression_line(self):
        self.update_graph(reset_view=False)

    def init_attr_values(self):
        self.graph.set_domain(self.data)

    def prepare_data(self):
        pass

    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, True)

    def selection_changed(self):
        self.commit()

    @Inputs.data
    @check_sql_input
    def set_data(self, data):
        """Set the input dataset.

        Parameters
        ----------
        data : Optional[Orange.data.Table]
        """
        if data is not None and len(data) < 2:
            self.Error.not_enough_rows()
            data = None
        else:
            self.Error.not_enough_rows.clear()

        self.signal_data = data

        if self.matrix is not None and data is not None and len(self.matrix) == len(data):
            self.closeContext()
            self.data = data
            self.init_attr_values()
            self.openContext(data)
        else:
            self._invalidated = True

    @Inputs.distances
    def set_disimilarity(self, matrix):
        """Set the dissimilarity (distance) matrix.

        Parameters
        ----------
        matrix : Optional[Orange.misc.DistMatrix]
        """

        if matrix is not None and len(matrix) < 2:
            self.Error.matrix_too_small()
            matrix = None
        else:
            self.Error.matrix_too_small.clear()

        self.matrix = matrix
        self.matrix_data = matrix.row_items if matrix is not None else None
        self._invalidated = True

    @Inputs.data_subset
    def set_subset_data(self, subset_data):
        """Set a subset of `data` input to highlight in the plot.

        Parameters
        ----------
        subset_data: Optional[Orange.data.Table]
        """
        self.subset_data = subset_data
        # invalidate the pen/brush when the subset is changed
        self._subset_mask = None  # type: Optional[np.ndarray]
        self.controls.graph.alpha_value.setEnabled(subset_data is None)

    def _clear(self):
        self._similar_pairs = None

        self.__set_update_loop(None)
        self.__state = OWMDS.Waiting

    def _clear_plot(self):
        self.graph.plot_widget.clear()

    def _initialize(self):
        # clear everything
        self.closeContext()
        self._clear()
        self.Error.clear()
        self.data = None
        self.effective_matrix = None
        self.embedding = None
        self.init_attr_values()

        # if no data nor matrix is present reset plot
        if self.signal_data is None and self.matrix is None:
            return

        if self.signal_data is not None and self.matrix is not None and \
                len(self.signal_data) != len(self.matrix):
            self.Error.mismatching_dimensions()
            self._update_plot()
            return

        if self.signal_data is not None:
            self.data = self.signal_data
        elif self.matrix_data is not None:
            self.data = self.matrix_data

        if self.matrix is not None:
            self.effective_matrix = self.matrix
            if self.matrix.axis == 0 and self.data is self.matrix_data:
                self.data = None
        elif self.data.domain.attributes:
            preprocessed_data = Orange.projection.MDS().preprocess(self.data)
            self.effective_matrix = Orange.distance.Euclidean(preprocessed_data)
        else:
            self.Error.no_attributes()
            return

        self.init_attr_values()
        self.openContext(self.data)

    def _toggle_run(self):
        if self.__state == OWMDS.Running:
            self.stop()
            self._invalidate_output()
        else:
            self.start()

    def start(self):
        if self.__state == OWMDS.Running:
            return
        elif self.__state == OWMDS.Finished:
            # Resume/continue from a previous run
            self.__start()
        elif self.__state == OWMDS.Waiting and \
                self.effective_matrix is not None:
            self.__start()

    def stop(self):
        if self.__state == OWMDS.Running:
            self.__set_update_loop(None)

    def __start(self):
        self.__draw_similar_pairs = False
        X = self.effective_matrix
        init = self.embedding

        # number of iterations per single GUI update step
        _, step_size = OWMDS.RefreshRate[self.refresh_rate]
        if step_size == -1:
            step_size = self.max_iter

        def update_loop(X, max_iter, step, init):
            """
            return an iterator over successive improved MDS point embeddings.
            """
            # NOTE: this code MUST NOT call into QApplication.processEvents
            done = False
            iterations_done = 0
            oldstress = np.finfo(np.float).max
            init_type = "PCA" if self.initialization == OWMDS.PCA else "random"

            while not done:
                step_iter = min(max_iter - iterations_done, step)
                mds = Orange.projection.MDS(
                    dissimilarity="precomputed", n_components=2,
                    n_init=1, max_iter=step_iter,
                    init_type=init_type, init_data=init)

                mdsfit = mds(X)
                iterations_done += step_iter

                embedding, stress = mdsfit.embedding_, mdsfit.stress_
                stress /= np.sqrt(np.sum(embedding ** 2, axis=1)).sum()

                if iterations_done >= max_iter:
                    done = True
                elif (oldstress - stress) < mds.params["eps"]:
                    done = True
                init = embedding
                oldstress = stress

                yield embedding, mdsfit.stress_, iterations_done / max_iter

        self.__set_update_loop(update_loop(X, self.max_iter, step_size, init))
        self.progressBarInit(processEvents=None)

    def __set_update_loop(self, loop):
        """
        Set the update `loop` coroutine.

        The `loop` is a generator yielding `(embedding, stress, progress)`
        tuples where `embedding` is a `(N, 2) ndarray` of current updated
        MDS points, `stress` is the current stress and `progress` a float
        ratio (0 <= progress <= 1)

        If an existing update coroutine loop is already in place it is
        interrupted (i.e. closed).

        .. note::
            The `loop` must not explicitly yield control flow to the event
            loop (i.e. call `QApplication.processEvents`)

        """
        if self.__update_loop is not None:
            self.__update_loop.close()
            self.__update_loop = None
            self.progressBarFinished(processEvents=None)

        self.__update_loop = loop

        if loop is not None:
            self.setBlocking(True)
            self.progressBarInit(processEvents=None)
            self.setStatusMessage("Running")
            self.runbutton.setText("Stop")
            self.__state = OWMDS.Running
            self.__timer.start()
        else:
            self.setBlocking(False)
            self.setStatusMessage("")
            self.runbutton.setText("Start")
            self.__state = OWMDS.Finished
            self.__timer.stop()

    def __next_step(self):
        if self.__update_loop is None:
            return

        assert not self.__in_next_step
        self.__in_next_step = True

        loop = self.__update_loop
        self.Error.out_of_memory.clear()
        try:
            embedding, _, progress = next(self.__update_loop)
            assert self.__update_loop is loop
        except StopIteration:
            self.__set_update_loop(None)
            self.unconditional_commit()
            self.__draw_similar_pairs = True
            self._update_plot()
        except MemoryError:
            self.Error.out_of_memory()
            self.__set_update_loop(None)
            self.__draw_similar_pairs = True
        except Exception as exc:
            self.Error.optimization_error(str(exc))
            self.__set_update_loop(None)
            self.__draw_similar_pairs = True
        else:
            self.progressBarSet(100.0 * progress, processEvents=None)
            self.embedding = embedding
            self._update_plot()
            # schedule next update
            self.__timer.start()

        self.__in_next_step = False

    def __invalidate_embedding(self):
        # reset/invalidate the MDS embedding, to the default initialization
        # (Random or PCA), restarting the optimization if necessary.
        if self.embedding is None:
            return
        state = self.__state
        if self.__update_loop is not None:
            self.__set_update_loop(None)

        X = self.effective_matrix

        if self.initialization == OWMDS.PCA:
            self.embedding = torgerson(X)
        else:
            self.embedding = np.random.rand(len(X), 2)

        self._update_plot()

        # restart the optimization if it was interrupted.
        if state == OWMDS.Running:
            self.__start()

    def __invalidate_refresh(self):
        state = self.__state

        if self.__update_loop is not None:
            self.__set_update_loop(None)

        # restart the optimization if it was interrupted.
        # TODO: decrease the max iteration count by the already
        # completed iterations count.
        if state == OWMDS.Running:
            self.__start()

    def handleNewSignals(self):
        if self._invalidated:
            self.__draw_similar_pairs = False
            self._invalidated = False
            self._initialize()
            self.start()

        if self._subset_mask is None and self.subset_data is not None and \
                self.data is not None:
            self._subset_mask = np.in1d(self.data.ids, self.subset_data.ids)

        self._update_plot(new=True)
        self.unconditional_commit()

    def _invalidate_output(self):
        self.commit()

    def _on_connected_changed(self):
        self._similar_pairs = None
        self.connect_pairs()

    def _update_plot(self, new=False):
        self._clear_plot()

        if self.embedding is not None:
            self._setup_plot(new=new)
        else:
            self.graph.new_data(None)

    def connect_pairs(self):
        if self._curve:
            self.graph.plot_widget.removeItem(self._curve)
        if not (self.connected_pairs and self.__draw_similar_pairs):
            return
        emb_x, emb_y = self.graph.get_xy_data_positions(
            self.variable_x, self.variable_y, self.graph.valid_data)
        if self._similar_pairs is None:
            # This code requires storing lower triangle of X (n x n / 2
            # doubles), n x n / 2 * 2 indices to X, n x n / 2 indices for
            # argsort result. If this becomes an issue, it can be reduced to
            # n x n argsort indices by argsorting the entire X. Then we
            # take the first n + 2 * p indices. We compute their coordinates
            # i, j in the original matrix. We keep those for which i < j.
            # n + 2 * p will suffice to exclude the diagonal (i = j). If the
            # number of those for which i < j is smaller than p, we instead
            # take i > j. Among those that remain, we take the first p.
            # Assuming that MDS can't show so many points that memory could
            # become an issue, I preferred using simpler code.
            m = self.effective_matrix
            n = len(m)
            p = min(n * (n - 1) // 2 * self.connected_pairs // 100,
                    MAX_N_PAIRS * self.connected_pairs // 20)
            indcs = np.triu_indices(n, 1)
            sorted = np.argsort(m[indcs])[:p]
            self._similar_pairs = fpairs = np.empty(2 * p, dtype=int)
            fpairs[::2] = indcs[0][sorted]
            fpairs[1::2] = indcs[1][sorted]
        emb_x_pairs = emb_x[self._similar_pairs].reshape((-1, 2))
        emb_y_pairs = emb_y[self._similar_pairs].reshape((-1, 2))

        # Filter out zero distance lines (in embedding coords).
        # Null (zero length) line causes bad rendering artifacts
        # in Qt when using the raster graphics system (see gh-issue: 1668).
        (x1, x2), (y1, y2) = (emb_x_pairs.T, emb_y_pairs.T)
        pairs_mask = ~(np.isclose(x1, x2) & np.isclose(y1, y2))
        emb_x_pairs = emb_x_pairs[pairs_mask, :]
        emb_y_pairs = emb_y_pairs[pairs_mask, :]
        self._curve = pg.PlotCurveItem(
            emb_x_pairs.ravel(), emb_y_pairs.ravel(),
            pen=pg.mkPen(0.8, width=2, cosmetic=True),
            connect="pairs", antialias=True)
        self.graph.plot_widget.addItem(self._curve)

    def _setup_plot(self, new=False):
        emb_x, emb_y = self.embedding[:, 0], self.embedding[:, 1]
        coords = np.vstack((emb_x, emb_y)).T

        data = self.data
        attributes = data.domain.attributes + (self.variable_x, self.variable_y)
        domain = Domain(attributes=attributes,
                        class_vars=data.domain.class_vars,
                        metas=data.domain.metas)
        data = Table.from_numpy(domain, X=hstack((data.X, coords)),
                                Y=data.Y, metas=data.metas)
        subset_data = data[self._subset_mask] if self._subset_mask is not None else None
        self.graph.new_data(data, subset_data=subset_data, new=new)
        self.graph.update_data(self.variable_x, self.variable_y, True)
        self.connect_pairs()

    def commit(self):
        if self.embedding is not None:
            names = get_unique_names([v.name for v in self.data.domain.variables],
                                     ["mds-x", "mds-y"])
            output = embedding = Orange.data.Table.from_numpy(
                Orange.data.Domain([ContinuousVariable(names[0]), ContinuousVariable(names[1])]),
                self.embedding
            )
        else:
            output = embedding = None

        if self.embedding is not None and self.data is not None:
            domain = self.data.domain
            domain = Orange.data.Domain(domain.attributes,
                                        domain.class_vars,
                                        domain.metas + embedding.domain.attributes)
            output = self.data.transform(domain)
            output.metas[:, -2:] = embedding.X

        selection = self.graph.get_selection()
        if output is not None and len(selection) > 0:
            selected = output[selection]
        else:
            selected = None
        if self.graph.selection is not None and np.max(self.graph.selection) > 1:
            annotated = create_groups_table(output, self.graph.selection)
        else:
            annotated = create_annotated_table(output, selection)
        self.Outputs.selected_data.send(selected)
        self.Outputs.annotated_data.send(annotated)

    def onDeleteWidget(self):
        super().onDeleteWidget()
        self._clear_plot()
        self._clear()

    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)

    @classmethod
    def migrate_settings(cls, settings_, version):
        if version < 2:
            settings_graph = {}
            for old, new in (("label_only_selected", "label_only_selected"),
                             ("symbol_opacity", "alpha_value"),
                             ("symbol_size", "point_width"),
                             ("jitter", "jitter_size")):
                settings_graph[new] = settings_[old]
            settings_["graph"] = settings_graph
            settings_["auto_commit"] = settings_["autocommit"]


    @classmethod
    def migrate_context(cls, context, version):
        if version < 2:
            domain = context.ordered_domain
            n_domain = [t for t in context.ordered_domain if t[1] == 2]
            c_domain = [t for t in context.ordered_domain if t[1] == 1]
            context_values_graph = {}
            for _, old_val, new_val in ((domain, "color_value", "attr_color"),
                                        (c_domain, "shape_value", "attr_shape"),
                                        (n_domain, "size_value", "attr_size"),
                                        (domain, "label_value", "attr_label")):
                tmp = context.values[old_val]
                if tmp[1] >= 0:
                    context_values_graph[new_val] = (tmp[0], tmp[1] + 100)
                elif tmp[0] != "Stress":
                    context_values_graph[new_val] = None
                else:
                    context_values_graph[new_val] = tmp
            context.values["graph"] = context_values_graph
Exemple #56
0
    def __init__(self):
        super().__init__()
        RecentPathsWComboMixin.__init__(self)
        self.domain = None
        self.data = None
        self.loaded_file = ""
        self.reader = None

        layout = QGridLayout()
        layout.setSpacing(4)
        gui.widgetBox(self.controlArea, orientation=layout, box='Source')
        vbox = gui.radioButtons(None,
                                self,
                                "source",
                                box=True,
                                callback=self.load_data,
                                addToLayout=False)

        rb_button = gui.appendRadioButton(vbox, "File:", addToLayout=False)
        layout.addWidget(rb_button, 0, 0, Qt.AlignVCenter)

        box = gui.hBox(None, addToLayout=False, margin=0)
        box.setSizePolicy(Policy.MinimumExpanding, Policy.Fixed)
        self.file_combo.setSizePolicy(Policy.MinimumExpanding, Policy.Fixed)
        self.file_combo.activated[int].connect(self.select_file)
        box.layout().addWidget(self.file_combo)
        layout.addWidget(box, 0, 1)

        file_button = gui.button(None,
                                 self,
                                 '...',
                                 callback=self.browse_file,
                                 autoDefault=False)
        file_button.setIcon(self.style().standardIcon(QStyle.SP_DirOpenIcon))
        file_button.setSizePolicy(Policy.Maximum, Policy.Fixed)
        layout.addWidget(file_button, 0, 2)

        reload_button = gui.button(None,
                                   self,
                                   "Reload",
                                   callback=self.load_data,
                                   autoDefault=False)
        reload_button.setIcon(self.style().standardIcon(
            QStyle.SP_BrowserReload))
        reload_button.setSizePolicy(Policy.Fixed, Policy.Fixed)
        layout.addWidget(reload_button, 0, 3)

        self.sheet_box = gui.hBox(None, addToLayout=False, margin=0)
        self.sheet_combo = QComboBox()
        self.sheet_combo.activated[str].connect(self.select_sheet)
        self.sheet_combo.setSizePolicy(Policy.MinimumExpanding, Policy.Fixed)
        self.sheet_label = QLabel()
        self.sheet_label.setText('Sheet')
        self.sheet_label.setSizePolicy(Policy.MinimumExpanding, Policy.Fixed)
        self.sheet_box.layout().addWidget(self.sheet_label, Qt.AlignLeft)
        self.sheet_box.layout().addWidget(self.sheet_combo, Qt.AlignVCenter)
        layout.addWidget(self.sheet_box, 2, 1)
        self.sheet_box.hide()

        rb_button = gui.appendRadioButton(vbox, "URL:", addToLayout=False)
        layout.addWidget(rb_button, 3, 0, Qt.AlignVCenter)

        self.url_combo = url_combo = QComboBox()
        url_model = NamedURLModel(self.sheet_names)
        url_model.wrap(self.recent_urls)
        url_combo.setLineEdit(LineEditSelectOnFocus())
        url_combo.setModel(url_model)
        url_combo.setSizePolicy(Policy.Ignored, Policy.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)

        box = gui.vBox(self.controlArea, "Info")
        self.infolabel = gui.widgetLabel(box, 'No data loaded.')
        self.warnings = gui.widgetLabel(box, '')

        box = gui.widgetBox(self.controlArea, "Columns (Double click to edit)")
        self.domain_editor = DomainEditor(self)
        self.editor_model = self.domain_editor.model()
        box.layout().addWidget(self.domain_editor)

        box = gui.hBox(box)
        gui.button(box,
                   self,
                   "Reset",
                   callback=self.reset_domain_edit,
                   autoDefault=False)
        gui.rubber(box)
        self.apply_button = gui.button(box,
                                       self,
                                       "Apply",
                                       callback=self.apply_domain_edit)
        self.apply_button.setEnabled(False)
        self.apply_button.setFixedWidth(170)
        self.editor_model.dataChanged.connect(
            lambda: self.apply_button.setEnabled(True))

        hBox = gui.hBox(self.controlArea)
        gui.rubber(hBox)
        gui.button(hBox,
                   self,
                   "Browse documentation datasets",
                   callback=lambda: self.browse_file(True),
                   autoDefault=False)
        gui.rubber(hBox)

        self.set_file_list()
        # Must not call open_file from within __init__. open_file
        # explicitly re-enters the event loop (by a progress bar)

        self.setAcceptDrops(True)

        if self.source == self.LOCAL_FILE:
            last_path = self.last_path()
            if last_path and os.path.exists(last_path) and \
                    os.path.getsize(last_path) > self.SIZE_LIMIT:
                self.Warning.file_too_big()
                return

        QTimer.singleShot(0, self.load_data)
Exemple #57
0
class OWScatterPlot(OWDataProjectionWidget):
    """Scatterplot visualization with explorative analysis and intelligent
    data visualization enhancements."""

    name = 'Scatter Plot'
    description = "Interactive scatter plot visualization with " \
                  "intelligent data visualization enhancements."
    icon = "icons/ScatterPlot.svg"
    priority = 140
    keywords = []

    class Inputs(OWDataProjectionWidget.Inputs):
        features = Input("Features", AttributeList)

    class Outputs(OWDataProjectionWidget.Outputs):
        features = Output("Features", AttributeList, dynamic=False)

    settings_version = 4
    auto_sample = Setting(True)
    attr_x = ContextSetting(None)
    attr_y = ContextSetting(None)
    tooltip_shows_all = Setting(True)

    GRAPH_CLASS = OWScatterPlotGraph
    graph = SettingProvider(OWScatterPlotGraph)
    embedding_variables_names = None

    xy_changed_manually = Signal(Variable, Variable)

    class Warning(OWDataProjectionWidget.Warning):
        missing_coords = Msg("Plot cannot be displayed because '{}' or '{}' "
                             "is missing for all data points")
        no_continuous_vars = Msg("Data has no continuous variables")

    class Information(OWDataProjectionWidget.Information):
        sampled_sql = Msg("Large SQL table; showing a sample.")
        missing_coords = Msg(
            "Points with missing '{}' or '{}' are not displayed")

    def __init__(self):
        self.sql_data = None  # Orange.data.sql.table.SqlTable
        self.attribute_selection_list = None  # list of Orange.data.Variable
        self.__timer = QTimer(self, interval=1200)
        self.__timer.timeout.connect(self.add_data)
        super().__init__()

        # manually register Matplotlib file writers
        self.graph_writers = self.graph_writers.copy()
        for w in [MatplotlibFormat, MatplotlibPDFFormat]:
            self.graph_writers.append(w)

    def _add_controls(self):
        self._add_controls_axis()
        self._add_controls_sampling()
        super()._add_controls()
        self.gui.add_widgets([
            self.gui.ShowGridLines, self.gui.ToolTipShowsAll,
            self.gui.RegressionLine
        ], self._plot_box)
        gui.checkBox(
            gui.indentedBox(self._plot_box),
            self,
            value="graph.orthonormal_regression",
            label="Treat variables as independent",
            callback=self.graph.update_regression_line,
            tooltip=
            "If checked, fit line to group (minimize distance from points);\n"
            "otherwise fit y as a function of x (minimize vertical distances)")

    def _add_controls_axis(self):
        common_options = dict(labelWidth=50,
                              orientation=Qt.Horizontal,
                              sendSelectedValue=True,
                              contentsLength=14)
        self.attr_box = gui.vBox(self.controlArea, True)
        dmod = DomainModel
        self.xy_model = DomainModel(dmod.MIXED, valid_types=ContinuousVariable)
        self.cb_attr_x = gui.comboBox(self.attr_box,
                                      self,
                                      "attr_x",
                                      label="Axis x:",
                                      callback=self.set_attr_from_combo,
                                      model=self.xy_model,
                                      **common_options,
                                      searchable=True)
        self.cb_attr_y = gui.comboBox(self.attr_box,
                                      self,
                                      "attr_y",
                                      label="Axis y:",
                                      callback=self.set_attr_from_combo,
                                      model=self.xy_model,
                                      **common_options,
                                      searchable=True)
        vizrank_box = gui.hBox(self.attr_box)
        self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank(
            vizrank_box, self, "Find Informative Projections", self.set_attr)

    def _add_controls_sampling(self):
        self.sampling = gui.auto_commit(self.controlArea,
                                        self,
                                        "auto_sample",
                                        "Sample",
                                        box="Sampling",
                                        callback=self.switch_sampling,
                                        commit=lambda: self.add_data(1))
        self.sampling.setVisible(False)

    @property
    def effective_variables(self):
        return [self.attr_x, self.attr_y
                ] if self.attr_x and self.attr_y else []

    def _vizrank_color_change(self):
        self.vizrank.initialize()
        err_msg = ""
        if self.data is None:
            err_msg = "No data on input"
        elif self.data.is_sparse():
            err_msg = "Data is sparse"
        elif len(self.xy_model) < 3:
            err_msg = "Not enough features for ranking"
        elif self.attr_color is None:
            err_msg = "Color variable is not selected"
        elif np.isnan(
                self.data.get_column_view(
                    self.attr_color)[0].astype(float)).all():
            err_msg = "Color variable has no values"
        self.vizrank_button.setEnabled(not err_msg)
        self.vizrank_button.setToolTip(err_msg)

    def set_data(self, data):
        super().set_data(data)
        self._vizrank_color_change()

        def findvar(name, iterable):
            """Find a Orange.data.Variable in `iterable` by name"""
            for el in iterable:
                if isinstance(el, Variable) and el.name == name:
                    return el
            return None

        # handle restored settings from  < 3.3.9 when attr_* were stored
        # by name
        if isinstance(self.attr_x, str):
            self.attr_x = findvar(self.attr_x, self.xy_model)
        if isinstance(self.attr_y, str):
            self.attr_y = findvar(self.attr_y, self.xy_model)
        if isinstance(self.attr_label, str):
            self.attr_label = findvar(self.attr_label, self.gui.label_model)
        if isinstance(self.attr_color, str):
            self.attr_color = findvar(self.attr_color, self.gui.color_model)
        if isinstance(self.attr_shape, str):
            self.attr_shape = findvar(self.attr_shape, self.gui.shape_model)
        if isinstance(self.attr_size, str):
            self.attr_size = findvar(self.attr_size, self.gui.size_model)

    def check_data(self):
        super().check_data()
        self.__timer.stop()
        self.sampling.setVisible(False)
        self.sql_data = None
        if isinstance(self.data, SqlTable):
            if self.data.approx_len() < 4000:
                self.data = Table(self.data)
            else:
                self.Information.sampled_sql()
                self.sql_data = self.data
                data_sample = self.data.sample_time(0.8, no_cache=True)
                data_sample.download_data(2000, partial=True)
                self.data = Table(data_sample)
                self.sampling.setVisible(True)
                if self.auto_sample:
                    self.__timer.start()

        if self.data is not None:
            if not self.data.domain.has_continuous_attributes(True, True):
                self.Warning.no_continuous_vars()
                self.data = None

        if self.data is not None and (len(self.data) == 0
                                      or len(self.data.domain) == 0):
            self.data = None

    def get_embedding(self):
        self.valid_data = None
        if self.data is None:
            return None

        x_data = self.get_column(self.attr_x, filter_valid=False)
        y_data = self.get_column(self.attr_y, filter_valid=False)
        if x_data is None or y_data is None:
            return None

        self.Warning.missing_coords.clear()
        self.Information.missing_coords.clear()
        self.valid_data = np.isfinite(x_data) & np.isfinite(y_data)
        if self.valid_data is not None and not np.all(self.valid_data):
            msg = self.Information if np.any(self.valid_data) else self.Warning
            msg.missing_coords(self.attr_x.name, self.attr_y.name)
        return np.vstack((x_data, y_data)).T

    # Tooltip
    def _point_tooltip(self, point_id, skip_attrs=()):
        point_data = self.data[point_id]
        xy_attrs = (self.attr_x, self.attr_y)
        text = "<br/>".join(
            escape('{} = {}'.format(var.name, point_data[var]))
            for var in xy_attrs)
        if self.tooltip_shows_all:
            others = super()._point_tooltip(point_id, skip_attrs=xy_attrs)
            if others:
                text = "<b>{}</b><br/><br/>{}".format(text, others)
        return text

    def add_data(self, time=0.4):
        if self.data and len(self.data) > 2000:
            self.__timer.stop()
            return
        data_sample = self.sql_data.sample_time(time, no_cache=True)
        if data_sample:
            data_sample.download_data(2000, partial=True)
            data = Table(data_sample)
            self.data = Table.concatenate((self.data, data), axis=0)
            self.handleNewSignals()

    def init_attr_values(self):
        super().init_attr_values()
        data = self.data
        domain = data.domain if data and len(data) else None
        self.xy_model.set_domain(domain)
        self.attr_x = self.xy_model[0] if self.xy_model else None
        self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \
            else self.attr_x

    def switch_sampling(self):
        self.__timer.stop()
        if self.auto_sample and self.sql_data:
            self.add_data()
            self.__timer.start()

    def set_subset_data(self, subset_data):
        self.warning()
        if isinstance(subset_data, SqlTable):
            if subset_data.approx_len() < AUTO_DL_LIMIT:
                subset_data = Table(subset_data)
            else:
                self.warning("Data subset does not support large Sql tables")
                subset_data = None
        super().set_subset_data(subset_data)

    # called when all signals are received, so the graph is updated only once
    def handleNewSignals(self):
        self.attr_box.setEnabled(True)
        self.vizrank.setEnabled(True)
        if self.attribute_selection_list and self.data is not None and \
                self.data.domain is not None and \
                all(attr in self.data.domain for attr
                        in self.attribute_selection_list):
            self.attr_x, self.attr_y = self.attribute_selection_list[:2]
            self.attr_box.setEnabled(False)
            self.vizrank.setEnabled(False)
        super().handleNewSignals()
        if self._domain_invalidated:
            self.graph.update_axes()
            self._domain_invalidated = False

    @Inputs.features
    def set_shown_attributes(self, attributes):
        if attributes and len(attributes) >= 2:
            self.attribute_selection_list = attributes[:2]
            self._invalidated = self._invalidated \
                or self.attr_x != attributes[0] \
                or self.attr_y != attributes[1]
        else:
            self.attribute_selection_list = None

    def set_attr(self, attr_x, attr_y):
        if attr_x != self.attr_x or attr_y != self.attr_y:
            self.attr_x, self.attr_y = attr_x, attr_y
            self.attr_changed()

    def set_attr_from_combo(self):
        self.attr_changed()
        self.xy_changed_manually.emit(self.attr_x, self.attr_y)

    def attr_changed(self):
        self.setup_plot()
        self.commit()

    def get_axes(self):
        return {"bottom": self.attr_x, "left": self.attr_y}

    def colors_changed(self):
        super().colors_changed()
        self._vizrank_color_change()

    def commit(self):
        super().commit()
        self.send_features()

    def send_features(self):
        features = [attr for attr in [self.attr_x, self.attr_y] if attr]
        self.Outputs.features.send(features or None)

    def get_widget_name_extension(self):
        if self.data is not None:
            return "{} vs {}".format(self.attr_x.name, self.attr_y.name)
        return None

    @classmethod
    def migrate_settings(cls, settings, version):
        if version < 2 and "selection" in settings and settings["selection"]:
            settings["selection_group"] = [(a, 1)
                                           for a in settings["selection"]]
        if version < 3:
            if "auto_send_selection" in settings:
                settings["auto_commit"] = settings["auto_send_selection"]
            if "selection_group" in settings:
                settings["selection"] = settings["selection_group"]

    @classmethod
    def migrate_context(cls, context, version):
        values = context.values
        if version < 3:
            values["attr_color"] = values["graph"]["attr_color"]
            values["attr_size"] = values["graph"]["attr_size"]
            values["attr_shape"] = values["graph"]["attr_shape"]
            values["attr_label"] = values["graph"]["attr_label"]
        if version < 4:
            if values["attr_x"][1] % 100 == 1 or values["attr_y"][1] % 100 == 1:
                raise IncompatibleContext()
Exemple #58
0
    def __init__(self):
        super().__init__()

        box = gui.vBox(self.mainArea, True, margin=0)
        self.graph = OWScatterPlotGraph(self, box, "ScatterPlot")
        box.layout().addWidget(self.graph.plot_widget)
        plot = self.graph.plot_widget

        axispen = QPen(self.palette().color(QPalette.Text))
        axis = plot.getAxis("bottom")
        axis.setPen(axispen)

        axis = plot.getAxis("left")
        axis.setPen(axispen)

        self.data = None  # Orange.data.Table
        self.subset_data = None  # Orange.data.Table
        self.data_metas_X = None  # self.data, where primitive metas are moved to X
        self.sql_data = None  # Orange.data.sql.table.SqlTable
        self.attribute_selection_list = None  # list of Orange.data.Variable
        self.__timer = QTimer(self, interval=1200)
        self.__timer.timeout.connect(self.add_data)

        common_options = dict(
            labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True,
            valueType=str)
        box = gui.vBox(self.controlArea, "Axis Data")
        dmod = DomainModel
        self.xy_model = DomainModel(dmod.MIXED, valid_types=dmod.PRIMITIVE)
        gui.comboBox(
            box, self, "attr_x", label="Axis x:", callback=self.update_attr,
            model=self.xy_model, **common_options)
        self.cb_attr_y = gui.comboBox(
            box, self, "attr_y", label="Axis y:", callback=self.update_attr,
            model=self.xy_model, **common_options)

        vizrank_box = gui.hBox(box)
        gui.separator(vizrank_box, width=common_options["labelWidth"])
        self.vizrank, self.vizrank_button = ScatterPlotVizRank.add_vizrank(
            vizrank_box, self, "Find Informative Projections", self.set_attr)

        gui.separator(box)

        gui.valueSlider(
            box, self, value='graph.jitter_size', label='Jittering: ',
            values=self.jitter_sizes, callback=self.reset_graph_data,
            labelFormat=lambda x:
            "None" if x == 0 else ("%.1f %%" if x < 1 else "%d %%") % x)
        gui.checkBox(
            gui.indentedBox(box), self, 'graph.jitter_continuous',
            'Jitter continuous values', callback=self.reset_graph_data)

        self.sampling = gui.auto_commit(
            self.controlArea, self, "auto_sample", "Sample", box="Sampling",
            callback=self.switch_sampling, commit=lambda: self.add_data(1))
        self.sampling.setVisible(False)

        box = gui.vBox(self.controlArea, "Points")
        self.color_model = DomainModel(
            placeholder="(Same color)", valid_types=dmod.PRIMITIVE)
        self.cb_attr_color = gui.comboBox(
            box, self, "graph.attr_color", label="Color:",
            callback=self.update_colors,
            model=self.color_model, **common_options)
        self.label_model = DomainModel(
            placeholder="(No labels)", valid_types=dmod.PRIMITIVE)
        self.cb_attr_label = gui.comboBox(
            box, self, "graph.attr_label", label="Label:",
            callback=self.graph.update_labels,
            model=self.label_model, **common_options)
        self.shape_model = DomainModel(
            placeholder="(Same shape)", valid_types=DiscreteVariable)
        self.cb_attr_shape = gui.comboBox(
            box, self, "graph.attr_shape", label="Shape:",
            callback=self.graph.update_shapes,
            model=self.shape_model, **common_options)
        self.size_model = DomainModel(
            placeholder="(Same size)", valid_types=ContinuousVariable)
        self.cb_attr_size = gui.comboBox(
            box, self, "graph.attr_size", label="Size:",
            callback=self.graph.update_sizes,
            model=self.size_model, **common_options)
        self.models = [self.xy_model, self.color_model, self.label_model,
                       self.shape_model, self.size_model]

        g = self.graph.gui
        g.point_properties_box(self.controlArea, box)
        box = gui.vBox(self.controlArea, "Plot Properties")
        g.add_widgets([g.ShowLegend, g.ShowGridLines], box)
        gui.checkBox(
            box, self, value='graph.tooltip_shows_all',
            label='Show all data on mouse hover')
        self.cb_class_density = gui.checkBox(
            box, self, value='graph.class_density', label='Show class density',
            callback=self.update_density)
        gui.checkBox(
            box, self, 'graph.label_only_selected',
            'Label only selected points', callback=self.graph.update_labels)

        self.zoom_select_toolbar = g.zoom_select_toolbar(
            gui.vBox(self.controlArea, "Zoom/Select"), nomargin=True,
            buttons=[g.StateButtonsBegin, g.SimpleSelect, g.Pan, g.Zoom,
                     g.StateButtonsEnd, g.ZoomReset]
        )
        buttons = self.zoom_select_toolbar.buttons
        buttons[g.Zoom].clicked.connect(self.graph.zoom_button_clicked)
        buttons[g.Pan].clicked.connect(self.graph.pan_button_clicked)
        buttons[g.SimpleSelect].clicked.connect(self.graph.select_button_clicked)
        buttons[g.ZoomReset].clicked.connect(self.graph.reset_button_clicked)
        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_send_selection",
                        "Send Selection", "Send Automatically")

        def zoom(s):
            """Zoom in/out by factor `s`."""
            viewbox = plot.getViewBox()
            # scaleBy scales the view's bounds (the axis range)
            viewbox.scaleBy((1 / s, 1 / s))

        def fit_to_view():
            viewbox = plot.getViewBox()
            viewbox.autoRange()

        zoom_in = QAction(
            "Zoom in", self, triggered=lambda: zoom(1.25)
        )
        zoom_in.setShortcuts([QKeySequence(QKeySequence.ZoomIn),
                              QKeySequence(self.tr("Ctrl+="))])
        zoom_out = QAction(
            "Zoom out", self, shortcut=QKeySequence.ZoomOut,
            triggered=lambda: zoom(1 / 1.25)
        )
        zoom_fit = QAction(
            "Fit in view", self,
            shortcut=QKeySequence(Qt.ControlModifier | Qt.Key_0),
            triggered=fit_to_view
        )
        self.addActions([zoom_in, zoom_out, zoom_fit])
Exemple #59
0
    def __init__(self):
        super().__init__()
        self.map = map = LeafletMap(self)  # type: LeafletMap
        self.mainArea.layout().addWidget(map)
        self.selection = None
        self.data = None
        self.learner = None

        def selectionChanged(indices):
            self.selection = self.data[
                indices] if self.data is not None and indices else None
            self._indices = indices
            self.commit()

        map.selectionChanged.connect(selectionChanged)

        def _set_map_provider():
            map.set_map_provider(self.TILE_PROVIDERS[self.tile_provider])

        box = gui.vBox(self.controlArea, 'Map')
        gui.comboBox(box,
                     self,
                     'tile_provider',
                     orientation=Qt.Horizontal,
                     label='Map:',
                     items=tuple(self.TILE_PROVIDERS.keys()),
                     sendSelectedValue=True,
                     callback=_set_map_provider)

        self._latlon_model = DomainModel(parent=self,
                                         valid_types=ContinuousVariable)
        self._class_model = DomainModel(parent=self,
                                        placeholder='(None)',
                                        valid_types=DomainModel.PRIMITIVE)
        self._color_model = DomainModel(parent=self,
                                        placeholder='(Same color)',
                                        valid_types=DomainModel.PRIMITIVE)
        self._shape_model = DomainModel(parent=self,
                                        placeholder='(Same shape)',
                                        valid_types=DiscreteVariable)
        self._size_model = DomainModel(parent=self,
                                       placeholder='(Same size)',
                                       valid_types=ContinuousVariable)
        self._label_model = DomainModel(parent=self, placeholder='(No labels)')

        def _set_lat_long():
            self.map.set_data(self.data, self.lat_attr, self.lon_attr)
            self.train_model()

        self._combo_lat = combo = gui.comboBox(box,
                                               self,
                                               'lat_attr',
                                               orientation=Qt.Horizontal,
                                               label='Latitude:',
                                               sendSelectedValue=True,
                                               callback=_set_lat_long)
        combo.setModel(self._latlon_model)
        self._combo_lon = combo = gui.comboBox(box,
                                               self,
                                               'lon_attr',
                                               orientation=Qt.Horizontal,
                                               label='Longitude:',
                                               sendSelectedValue=True,
                                               callback=_set_lat_long)
        combo.setModel(self._latlon_model)

        def _toggle_legend():
            self.map.toggle_legend(self.show_legend)

        gui.checkBox(box,
                     self,
                     'show_legend',
                     label='Show legend',
                     callback=_toggle_legend)

        box = gui.vBox(self.controlArea, 'Overlay')
        self._combo_class = combo = gui.comboBox(box,
                                                 self,
                                                 'class_attr',
                                                 orientation=Qt.Horizontal,
                                                 label='Target:',
                                                 sendSelectedValue=True,
                                                 callback=self.train_model)
        self.controls.class_attr.setModel(self._class_model)
        self.set_learner(self.learner)

        box = gui.vBox(self.controlArea, 'Points')
        self._combo_color = combo = gui.comboBox(
            box,
            self,
            'color_attr',
            orientation=Qt.Horizontal,
            label='Color:',
            sendSelectedValue=True,
            callback=lambda: self.map.set_marker_color(self.color_attr))
        combo.setModel(self._color_model)
        self._combo_label = combo = gui.comboBox(
            box,
            self,
            'label_attr',
            orientation=Qt.Horizontal,
            label='Label:',
            sendSelectedValue=True,
            callback=lambda: self.map.set_marker_label(self.label_attr))
        combo.setModel(self._label_model)
        self._combo_shape = combo = gui.comboBox(
            box,
            self,
            'shape_attr',
            orientation=Qt.Horizontal,
            label='Shape:',
            sendSelectedValue=True,
            callback=lambda: self.map.set_marker_shape(self.shape_attr))
        combo.setModel(self._shape_model)
        self._combo_size = combo = gui.comboBox(
            box,
            self,
            'size_attr',
            orientation=Qt.Horizontal,
            label='Size:',
            sendSelectedValue=True,
            callback=lambda: self.map.set_marker_size(self.size_attr))
        combo.setModel(self._size_model)

        def _set_opacity():
            map.set_marker_opacity(self.opacity)

        def _set_zoom():
            map.set_marker_size_coefficient(self.zoom)

        def _set_jittering():
            map.set_jittering(self.jittering)

        def _set_clustering():
            map.set_clustering(self.cluster_points)

        self._opacity_slider = gui.hSlider(box,
                                           self,
                                           'opacity',
                                           None,
                                           1,
                                           100,
                                           5,
                                           label='Opacity:',
                                           labelFormat=' %d%%',
                                           callback=_set_opacity)
        self._zoom_slider = gui.valueSlider(box,
                                            self,
                                            'zoom',
                                            None,
                                            values=(20, 50, 100, 200, 300, 400,
                                                    500, 700, 1000),
                                            label='Symbol size:',
                                            labelFormat=' %d%%',
                                            callback=_set_zoom)
        self._jittering = gui.valueSlider(box,
                                          self,
                                          'jittering',
                                          label='Jittering:',
                                          values=(0, .5, 1, 2, 5),
                                          labelFormat=' %.1f%%',
                                          ticks=True,
                                          callback=_set_jittering)
        self._clustering_check = gui.checkBox(box,
                                              self,
                                              'cluster_points',
                                              label='Cluster points',
                                              callback=_set_clustering)

        gui.rubber(self.controlArea)
        gui.auto_commit(self.controlArea, self, 'autocommit', 'Send Selection')

        QTimer.singleShot(0, _set_map_provider)
        QTimer.singleShot(0, _toggle_legend)
        QTimer.singleShot(0, _set_opacity)
        QTimer.singleShot(0, _set_zoom)
        QTimer.singleShot(0, _set_jittering)
        QTimer.singleShot(0, _set_clustering)
Exemple #60
0
class OWMDS(OWDataProjectionWidget):
    name = "MDS"
    description = "Two-dimensional data projection by multidimensional " \
                  "scaling constructed from a distance matrix."
    icon = "icons/MDS.svg"
    keywords = ["multidimensional scaling", "multi dimensional scaling"]

    class Inputs(OWDataProjectionWidget.Inputs):
        distances = Input("Distances", DistMatrix)

    settings_version = 3

    #: Initialization type
    PCA, Random, Jitter = 0, 1, 2

    #: Refresh rate
    RefreshRate = [("Every iteration", 1), ("Every 5 steps", 5),
                   ("Every 10 steps", 10), ("Every 25 steps", 25),
                   ("Every 50 steps", 50), ("None", -1)]

    #: Runtime state
    Running, Finished, Waiting = 1, 2, 3

    max_iter = settings.Setting(300)
    initialization = settings.Setting(PCA)
    refresh_rate = settings.Setting(3)

    GRAPH_CLASS = OWMDSGraph
    graph = SettingProvider(OWMDSGraph)
    embedding_variables_names = ("mds-x", "mds-y")

    class Error(OWDataProjectionWidget.Error):
        not_enough_rows = Msg("Input data needs at least 2 rows")
        matrix_too_small = Msg("Input matrix must be at least 2x2")
        no_attributes = Msg("Data has no attributes")
        mismatching_dimensions = \
            Msg("Data and distances dimensions do not match.")
        out_of_memory = Msg("Out of memory")
        optimization_error = Msg("Error during optimization\n{}")

    def __init__(self):
        super().__init__()
        #: Input dissimilarity matrix
        self.matrix = None  # type: Optional[DistMatrix]
        #: Data table from the `self.matrix.row_items` (if present)
        self.matrix_data = None  # type: Optional[Table]
        #: Input data table
        self.signal_data = None

        self._invalidated = False
        self.embedding = None
        self.effective_matrix = None

        self.__update_loop = None
        # timer for scheduling updates
        self.__timer = QTimer(self, singleShot=True, interval=0)
        self.__timer.timeout.connect(self.__next_step)
        self.__state = OWMDS.Waiting
        self.__in_next_step = False

        self.graph.pause_drawing_pairs()

        g = self.graph.gui
        self.size_model = g.points_models[2]
        self.size_model.order = g.points_models[2].order[:1] + ("Stress", ) + \
                                g.points_models[2].order[1:]
        # self._initialize()

    def _add_controls(self):
        self._add_controls_optimization()
        super()._add_controls()
        self.graph.gui.add_control(self._effects_box,
                                   gui.hSlider,
                                   "Show similar pairs:",
                                   master=self.graph,
                                   value="connected_pairs",
                                   minValue=0,
                                   maxValue=20,
                                   createLabel=False,
                                   callback=self._on_connected_changed)

    def _add_controls_optimization(self):
        box = gui.vBox(self.controlArea, box=True)
        self.runbutton = gui.button(box,
                                    self,
                                    "Run optimization",
                                    callback=self._toggle_run)
        gui.comboBox(box,
                     self,
                     "refresh_rate",
                     label="Refresh: ",
                     orientation=Qt.Horizontal,
                     items=[t for t, _ in OWMDS.RefreshRate],
                     callback=self.__invalidate_refresh)
        hbox = gui.hBox(box, margin=0)
        gui.button(hbox, self, "PCA", callback=self.do_PCA)
        gui.button(hbox, self, "Randomize", callback=self.do_random)
        gui.button(hbox, self, "Jitter", callback=self.do_jitter)

    def set_data(self, data):
        """Set the input dataset.

        Parameters
        ----------
        data : Optional[Table]
        """
        if data is not None and len(data) < 2:
            self.Error.not_enough_rows()
            data = None
        else:
            self.Error.not_enough_rows.clear()

        self.signal_data = data

        if self.matrix is not None and data is not None and \
                len(self.matrix) == len(data):
            self.closeContext()
            self.data = data
            self.init_attr_values()
            self.openContext(data)
        else:
            self._invalidated = True

    @Inputs.distances
    def set_disimilarity(self, matrix):
        """Set the dissimilarity (distance) matrix.

        Parameters
        ----------
        matrix : Optional[Orange.misc.DistMatrix]
        """

        if matrix is not None and len(matrix) < 2:
            self.Error.matrix_too_small()
            matrix = None
        else:
            self.Error.matrix_too_small.clear()

        self.matrix = matrix
        self.matrix_data = matrix.row_items if matrix is not None else None
        self._invalidated = True

    def clear(self):
        super().clear()
        self.embedding = None
        self.effective_matrix = None
        self.graph.set_effective_matrix(None)
        self.__set_update_loop(None)
        self.__state = OWMDS.Waiting

    def _initialize(self):
        self.closeContext()
        self.clear()
        self.clear_messages()

        # if no data nor matrix is present reset plot
        if self.signal_data is None and self.matrix is None:
            self.data = None
            self.init_attr_values()
            return

        if self.signal_data is not None and self.matrix is not None and \
                len(self.signal_data) != len(self.matrix):
            self.Error.mismatching_dimensions()
            self.init_attr_values()
            return

        if self.signal_data is not None:
            self.data = self.signal_data
        elif self.matrix_data is not None:
            self.data = self.matrix_data

        if self.matrix is not None:
            self.effective_matrix = self.matrix
            if self.matrix.axis == 0 and self.data is self.matrix_data:
                self.data = None
        elif self.data.domain.attributes:
            preprocessed_data = MDS().preprocess(self.data)
            self.effective_matrix = Euclidean(preprocessed_data)
        else:
            self.Error.no_attributes()
            self.init_attr_values()
            return

        self.init_attr_values()
        self.openContext(self.data)
        self.graph.set_effective_matrix(self.effective_matrix)

    def _toggle_run(self):
        if self.__state == OWMDS.Running:
            self.stop()
            self._invalidate_output()
        else:
            self.start()

    def start(self):
        if self.__state == OWMDS.Running:
            return
        elif self.__state == OWMDS.Finished:
            # Resume/continue from a previous run
            self.__start()
        elif self.__state == OWMDS.Waiting and \
                self.effective_matrix is not None:
            self.__start()

    def stop(self):
        if self.__state == OWMDS.Running:
            self.__set_update_loop(None)

    def __start(self):
        self.graph.pause_drawing_pairs()
        X = self.effective_matrix
        init = self.embedding

        # number of iterations per single GUI update step
        _, step_size = OWMDS.RefreshRate[self.refresh_rate]
        if step_size == -1:
            step_size = self.max_iter

        def update_loop(X, max_iter, step, init):
            """
            return an iterator over successive improved MDS point embeddings.
            """
            # NOTE: this code MUST NOT call into QApplication.processEvents
            done = False
            iterations_done = 0
            oldstress = np.finfo(np.float).max
            init_type = "PCA" if self.initialization == OWMDS.PCA else "random"

            while not done:
                step_iter = min(max_iter - iterations_done, step)
                mds = MDS(dissimilarity="precomputed",
                          n_components=2,
                          n_init=1,
                          max_iter=step_iter,
                          init_type=init_type,
                          init_data=init)

                mdsfit = mds(X)
                iterations_done += step_iter

                embedding, stress = mdsfit.embedding_, mdsfit.stress_
                stress /= np.sqrt(np.sum(embedding**2, axis=1)).sum()

                if iterations_done >= max_iter:
                    done = True
                elif (oldstress - stress) < mds.params["eps"]:
                    done = True
                init = embedding
                oldstress = stress

                yield embedding, mdsfit.stress_, iterations_done / max_iter

        self.__set_update_loop(update_loop(X, self.max_iter, step_size, init))
        self.progressBarInit(processEvents=None)

    def __set_update_loop(self, loop):
        """
        Set the update `loop` coroutine.

        The `loop` is a generator yielding `(embedding, stress, progress)`
        tuples where `embedding` is a `(N, 2) ndarray` of current updated
        MDS points, `stress` is the current stress and `progress` a float
        ratio (0 <= progress <= 1)

        If an existing update coroutine loop is already in place it is
        interrupted (i.e. closed).

        .. note::
            The `loop` must not explicitly yield control flow to the event
            loop (i.e. call `QApplication.processEvents`)

        """
        if self.__update_loop is not None:
            self.__update_loop.close()
            self.__update_loop = None
            self.progressBarFinished(processEvents=None)

        self.__update_loop = loop

        if loop is not None:
            self.setBlocking(True)
            self.progressBarInit(processEvents=None)
            self.setStatusMessage("Running")
            self.runbutton.setText("Stop")
            self.__state = OWMDS.Running
            self.__timer.start()
        else:
            self.setBlocking(False)
            self.setStatusMessage("")
            self.runbutton.setText("Start")
            self.__state = OWMDS.Finished
            self.__timer.stop()

    def __next_step(self):
        if self.__update_loop is None:
            return

        assert not self.__in_next_step
        self.__in_next_step = True

        loop = self.__update_loop
        self.Error.out_of_memory.clear()
        try:
            embedding, _, progress = next(self.__update_loop)
            assert self.__update_loop is loop
        except StopIteration:
            self.__set_update_loop(None)
            self.unconditional_commit()
            self.graph.resume_drawing_pairs()
            self.graph.update_coordinates()
        except MemoryError:
            self.Error.out_of_memory()
            self.__set_update_loop(None)
            self.graph.resume_drawing_pairs()
        except Exception as exc:
            self.Error.optimization_error(str(exc))
            self.__set_update_loop(None)
            self.graph.resume_drawing_pairs()
        else:
            self.progressBarSet(100.0 * progress, processEvents=None)
            self.embedding = embedding
            self.graph.update_coordinates()
            # schedule next update
            self.__timer.start()

        self.__in_next_step = False

    def do_PCA(self):
        self.__invalidate_embedding(self.PCA)

    def do_random(self):
        self.__invalidate_embedding(self.Random)

    def do_jitter(self):
        self.__invalidate_embedding(self.Jitter)

    def __invalidate_embedding(self, initialization=PCA):
        def jitter_coord(part):
            span = np.max(part) - np.min(part)
            part += np.random.uniform(-span / 20, span / 20, len(part))

        # reset/invalidate the MDS embedding, to the default initialization
        # (Random or PCA), restarting the optimization if necessary.
        state = self.__state
        if self.__update_loop is not None:
            self.__set_update_loop(None)

        if self.effective_matrix is None:
            return

        X = self.effective_matrix

        if initialization == OWMDS.PCA:
            self.embedding = torgerson(X)
        elif initialization == OWMDS.Random:
            self.embedding = np.random.rand(len(X), 2)
        else:
            jitter_coord(self.embedding[:, 0])
            jitter_coord(self.embedding[:, 1])

        self.setup_plot()

        # restart the optimization if it was interrupted.
        if state == OWMDS.Running:
            self.__start()

    def __invalidate_refresh(self):
        state = self.__state

        if self.__update_loop is not None:
            self.__set_update_loop(None)

        # restart the optimization if it was interrupted.
        # TODO: decrease the max iteration count by the already
        # completed iterations count.
        if state == OWMDS.Running:
            self.__start()

    def handleNewSignals(self):
        if self._invalidated:
            self.graph.pause_drawing_pairs()
            self._invalidated = False
            self._initialize()
            self.__invalidate_embedding()
            self.cb_class_density.setEnabled(self.can_draw_density())
            self.start()

        super().handleNewSignals()

    def _invalidate_output(self):
        self.commit()

    def _on_connected_changed(self):
        self.graph.set_effective_matrix(self.effective_matrix)
        self.graph.update_pairs(reconnect=True)

    def setup_plot(self):
        super().setup_plot()
        if self.embedding is not None:
            self.graph.update_pairs(reconnect=True)

    def get_size_data(self):
        if self.attr_size == "Stress":
            return stress(self.embedding, self.effective_matrix)
        else:
            return super().get_size_data()

    def get_embedding(self):
        self.valid_data = np.ones(len(self.embedding), dtype=bool) \
            if self.embedding is not None else None
        return self.embedding

    def _get_projection_data(self):
        if self.embedding is None:
            return None

        if self.data is None:
            x_name, y_name = self.embedding_variables_names
            variables = ContinuousVariable(x_name), ContinuousVariable(y_name)
            return Table(Domain(variables), self.embedding)
        return super()._get_projection_data()

    @classmethod
    def migrate_settings(cls, settings_, version):
        if version < 2:
            settings_graph = {}
            for old, new in (("label_only_selected", "label_only_selected"),
                             ("symbol_opacity", "alpha_value"),
                             ("symbol_size", "point_width"), ("jitter",
                                                              "jitter_size")):
                settings_graph[new] = settings_[old]
            settings_["graph"] = settings_graph
            settings_["auto_commit"] = settings_["autocommit"]

        if version < 3:
            if "connected_pairs" in settings_:
                connected_pairs = settings_["connected_pairs"]
                settings_["graph"]["connected_pairs"] = connected_pairs

    @classmethod
    def migrate_context(cls, context, version):
        if version < 2:
            domain = context.ordered_domain
            n_domain = [t for t in context.ordered_domain if t[1] == 2]
            c_domain = [t for t in context.ordered_domain if t[1] == 1]
            context_values = {}
            for _, old_val, new_val in ((domain, "color_value", "attr_color"),
                                        (c_domain, "shape_value",
                                         "attr_shape"),
                                        (n_domain, "size_value", "attr_size"),
                                        (domain, "label_value", "attr_label")):
                tmp = context.values[old_val]
                if tmp[1] >= 0:
                    context_values[new_val] = (tmp[0], tmp[1] + 100)
                elif tmp[0] != "Stress":
                    context_values[new_val] = None
                else:
                    context_values[new_val] = tmp
            context.values = context_values

        if version < 3 and "graph" in context.values:
            values = context.values
            values["attr_color"] = values["graph"]["attr_color"]
            values["attr_size"] = values["graph"]["attr_size"]
            values["attr_shape"] = values["graph"]["attr_shape"]
            values["attr_label"] = values["graph"]["attr_label"]