def draw(self):
        """Uses GraphAttributes class to draw the explanaitons """
        self.box_scene.clear()
        wp = self.box_view.viewport().rect()
        header_height = 30
        if self.explanations is not None:
            self.painter = GraphAttributes(
                self.box_scene,
                min(self.gui_num_atr, self.explanations.Y.shape[0]))
            self.painter.paint(wp, self.explanations, header_h=header_height)
        """set appropriate boxes for different views"""
        rect = QRectF(self.box_scene.itemsBoundingRect().x(),
                      self.box_scene.itemsBoundingRect().y(),
                      self.box_scene.itemsBoundingRect().width(),
                      self.box_scene.itemsBoundingRect().height())

        self.box_scene.setSceneRect(rect)
        self.box_view.setSceneRect(rect.x(),
                                   rect.y() + header_height + 2, rect.width(),
                                   rect.height() - 80)
        self.header_view.setSceneRect(rect.x(), rect.y(), rect.width(), 10)
        self.header_view.setFixedHeight(header_height)
        self.footer_view.setSceneRect(rect.x(),
                                      rect.y() + rect.height() - 50,
                                      rect.width(), 35)
Пример #2
0
 def __collides_with_indicator(self, rect: QRectF) -> bool:
     y1 = rect.y()
     y2 = y1 + rect.height()
     for indicator in self.__plot_indicators:
         if not indicator.isVisible():
             continue
         ind_y1 = indicator.y()
         ind_y2 = ind_y1 + indicator.boundingRect().height()
         if _collides(ind_y1, ind_y2, y1, y2):
             return True
     return False
Пример #3
0
    def _update_selection(self, p1: QPointF, p2: QPointF, finished: bool):
        # When finished, emit selection_changed.
        if len(self.__selection_rects) == 0:
            return
        assert self._max_item_width > 0

        rect = QRectF(p1, p2).normalized()
        if self.__orientation == Qt.Vertical:
            min_max = rect.y(), rect.y() + rect.height()
            index = int(
                (p1.x() + self._max_item_width / 2) / self._max_item_width)
        else:
            min_max = rect.x(), rect.x() + rect.width()
            index = int(
                (-p1.y() + self._max_item_width / 2) / self._max_item_width)

        index = min(index, len(self.__selection_rects) - 1)
        index = self._sorted_group_indices[index]

        self.__selection_rects[index].selection_range = min_max

        if not finished:
            return

        mask = np.bitwise_and(self.__values >= min_max[0],
                              self.__values <= min_max[1])
        if self.__group_values is not None:
            mask = np.bitwise_and(mask, self.__group_values == index)

        selection = set(np.flatnonzero(mask))
        keys = QApplication.keyboardModifiers()
        if keys & Qt.ShiftModifier:
            remove_mask = self.__group_values == index
            selection |= self.__selection - set(np.flatnonzero(remove_mask))
        if self.__selection != selection:
            self.__selection = selection
            self.selection_changed.emit(sorted(self.__selection),
                                        self._selection_ranges)
    def draw(self):
        """Uses GraphAttributes class to draw the explanaitons """
        self.box_scene.clear()
        wp = self.box_view.viewport().rect()
        header_height = 30
        if self.explanations is not None:
            self.painter = GraphAttributes(self.box_scene, min(
                self.gui_num_atr, self.explanations.Y.shape[0]))
            self.painter.paint(wp, self.explanations, header_h=header_height)

        """set appropriate boxes for different views"""
        rect = QRectF(self.box_scene.itemsBoundingRect().x(),
                      self.box_scene.itemsBoundingRect().y(),
                      self.box_scene.itemsBoundingRect().width(),
                      self.box_scene.itemsBoundingRect().height())

        self.box_scene.setSceneRect(rect)
        self.box_view.setSceneRect(
            rect.x(), rect.y()+header_height+2, rect.width(), rect.height() - 80)
        self.header_view.setSceneRect(
            rect.x(), rect.y(), rect.width(), 10)
        self.header_view.setFixedHeight(header_height)
        self.footer_view.setSceneRect(
            rect.x(), rect.y() + rect.height() - 50, rect.width(), 35)
Пример #5
0
    def rescale(self, width):
        self._width = width
        self.updateGeometry()

        for x, item in zip(self._values_to_pixels(self._x_data),
                           self._group.childItems()):
            item.setX(x)

        if self._selection_rect is not None:
            old_width = self._selection_rect.parent_width
            rect = self._selection_rect.rect()
            x1 = self._width * rect.x() / old_width
            x2 = self._width * (rect.x() + rect.width()) / old_width
            rect = QRectF(x1, rect.y(), x2 - x1, rect.height())
            self._selection_rect.setRect(rect)
            self._selection_rect.parent_width = self._width
Пример #6
0
    def __doLayout(self, rect: QRectF) -> Iterable[QRectF]:
        x = y = 0
        rowheight = 0
        width = rect.width()
        spacing_x, spacing_y = self.__spacing
        first_in_row = True
        rows: List[List[QRectF]] = [[]]

        def break_():
            nonlocal x, y, rowheight, first_in_row
            y += rowheight + spacing_y
            x = 0
            rowheight = 0
            first_in_row = True
            rows.append([])

        items = [
            _FlowLayoutItem(item=item, geom=QRectF(), size=QSizeF())
            for item in self.__items
        ]

        for flitem in items:
            item = flitem.item
            sh = item.effectiveSizeHint(Qt.PreferredSize)
            if x + sh.width() > width and not first_in_row:
                break_()
            r = QRectF(rect.x() + x, rect.y() + y, sh.width(), sh.height())
            flitem.geom = r
            flitem.size = sh
            flitem.row = len(rows) - 1
            rowheight = max(rowheight, sh.height())
            x += sh.width() + spacing_x
            first_in_row = False
            rows[-1].append(flitem.geom)

        alignment = Qt.AlignVCenter | Qt.AlignLeft
        for flitem in items:
            row = rows[flitem.row]
            row_rect = reduce(QRectF.united, row, QRectF())
            if row_rect.isEmpty():
                continue
            flitem.geom = qrect_aligned_to(flitem.geom, row_rect,
                                           alignment & Qt.AlignVertical_Mask)
        return [fli.geom for fli in items]
Пример #7
0
    def relayout(self):
        """Approximate Fruchterman-Reingold spring layout"""
        nodes = list(self.nodes.values())
        pos = np.array([(np.cos(i / len(nodes) * 2 * np.pi + np.pi / 4),
                         np.sin(i / len(nodes) * 2 * np.pi + np.pi / 4))
                        for i in range(1, 1 + len(nodes))])
        K = 1 / np.sqrt(pos.shape[0])
        GRAVITY, ITERATIONS = 10, 20
        TEMPERATURES = np.linspace(.3, .01, ITERATIONS)
        for temp in chain([.8, .5], TEMPERATURES):
            # Repulsive forces
            delta = pos[:, np.newaxis, :] - pos
            delta /= np.abs(delta).sum(
                2)[:, :, np.newaxis]**2  # NOTE: This warning was expected
            delta = np.nan_to_num(delta)  # Reverse the effect of zero-division
            disp = -delta.sum(0) * K * K
            # Attractive forces
            for edge in self.edges:
                n1, n2 = nodes.index(edge.source), nodes.index(edge.dest)
                delta = pos[n1] - pos[n2]
                magnitude = np.abs(delta).sum()
                disp[n1] -= delta * magnitude / K
                disp[n2] += delta * magnitude / K
            # Gravity; tend toward center
            magnitude = np.sqrt(np.sum(np.abs(pos)**2, 1))
            disp -= (pos.T * K * GRAVITY * magnitude).T
            # Limit max displacement and reposition
            magnitude = np.sqrt(np.sum(np.abs(disp)**2, 1))
            pos += (disp.T / magnitude).T * np.clip(np.abs(disp), 0, temp)

        for node, position in zip(nodes, 500 * pos):
            node.setPos(*position)
        for edge in self.edges:
            edge.adjust()

        MARGIN, rect = 10, self.scene().itemsBoundingRect()
        rect = QRectF(rect.x() - MARGIN,
                      rect.y() - MARGIN,
                      rect.width() + 2 * MARGIN,
                      rect.height() + 2 * MARGIN)
        self.scene().setSceneRect(rect)
        self.scene().invalidate()
Пример #8
0
    def rescale(self, width):
        def move_point(_x, *_):
            item = next(points)
            item.setX(_x)

        self.__width = width
        self.updateGeometry()
        points = (item for item in self.__group.childItems())

        x_data_unique, dist, x_data = self.prepare_data()
        for x, d in zip(x_data_unique, dist):
            self.plot_data(move_point, x, 0, d)

        if self.__selection_rect is not None:
            old_width = self.__selection_rect.parent_width
            rect = self.__selection_rect.rect()
            x1 = self.__width * rect.x() / old_width
            x2 = self.__width * (rect.x() + rect.width()) / old_width
            rect = QRectF(x1, rect.y(), x2 - x1, rect.height())
            self.__selection_rect.setRect(rect)
            self.__selection_rect.parent_width = self.__width
Пример #9
0
    def relayout(self):
        """Approximate Fruchterman-Reingold spring layout"""
        nodes = list(self.nodes.values())
        pos = np.array([(np.cos(i/len(nodes)*2*np.pi + np.pi/4),
                         np.sin(i/len(nodes)*2*np.pi + np.pi/4))
                        for i in range(1, 1 + len(nodes))])
        K = 1 / np.sqrt(pos.shape[0])
        GRAVITY, ITERATIONS = 10, 20
        TEMPERATURES = np.linspace(.3, .01, ITERATIONS)
        for temp in chain([.8, .5], TEMPERATURES):
            # Repulsive forces
            delta = pos[:, np.newaxis, :] - pos
            delta /= np.abs(delta).sum(2)[:, :, np.newaxis]**2  # NOTE: This warning was expected
            delta = np.nan_to_num(delta)  # Reverse the effect of zero-division
            disp = -delta.sum(0)*K*K
            # Attractive forces
            for edge in self.edges:
                n1, n2 = nodes.index(edge.source), nodes.index(edge.dest)
                delta = pos[n1] - pos[n2]
                magnitude = np.abs(delta).sum()
                disp[n1] -= delta*magnitude/K
                disp[n2] += delta*magnitude/K
            # Gravity; tend toward center
            magnitude = np.sqrt(np.sum(np.abs(pos)**2, 1))
            disp -= (pos.T*K*GRAVITY*magnitude).T
            # Limit max displacement and reposition
            magnitude = np.sqrt(np.sum(np.abs(disp)**2, 1))
            pos += (disp.T / magnitude).T * np.clip(np.abs(disp), 0, temp)

        for node, position in zip(nodes, 500*pos):
            node.setPos(*position)
        for edge in self.edges:
            edge.adjust()

        MARGIN, rect = 10, self.scene().itemsBoundingRect()
        rect = QRectF(rect.x() - MARGIN, rect.y() - MARGIN,
                      rect.width() + 2*MARGIN, rect.height() + 2*MARGIN)
        self.scene().setSceneRect(rect)
        self.scene().invalidate()
Пример #10
0
    def update_scene(self):
        self.clear_scene()
        if self.domain is None or not len(self.points[0]):
            return

        n_attrs = self.n_attributes if self.display_index else int(1e10)
        attr_inds, attributes = zip(*self.get_ordered_attributes()[:n_attrs])

        name_items = [QGraphicsTextItem(attr.name) for attr in attributes]
        point_text = QGraphicsTextItem("Points")
        probs_text = QGraphicsTextItem("Probabilities (%)")
        all_items = name_items + [point_text, probs_text]
        name_offset = -max(t.boundingRect().width() for t in all_items) - 10
        w = self.view.viewport().rect().width()
        max_width = w + name_offset - 30

        points = [self.points[i][self.target_class_index]
                  for i in attr_inds]
        if self.align == OWNomogram.ALIGN_LEFT:
            points = [p - p.min() for p in points]
        max_ = np.nan_to_num(max(max(abs(p)) for p in points))
        d = 100 / max_ if max_ else 1
        minimums = [p[self.target_class_index].min() for p in self.points]
        if self.scale == OWNomogram.POINT_SCALE:
            points = [p * d for p in points]

            if self.align == OWNomogram.ALIGN_LEFT:
                self.scale_marker_values = lambda x: (x - minimums) * d
            else:
                self.scale_marker_values = lambda x: x * d
        else:
            if self.align == OWNomogram.ALIGN_LEFT:
                self.scale_marker_values = lambda x: x - minimums
            else:
                self.scale_marker_values = lambda x: x

        point_item, nomogram_head = self.create_main_nomogram(
            attributes, attr_inds,
            name_items, points, max_width, point_text, name_offset)
        probs_item, nomogram_foot = self.create_footer_nomogram(
            probs_text, d, minimums, max_width, name_offset)
        for item in self.feature_items.values():
            item.dot.point_dot = point_item.dot
            item.dot.probs_dot = probs_item.dot
            item.dot.vertical_line = self.hidden_vertical_line

        self.nomogram = nomogram = NomogramItem()
        nomogram.add_items([nomogram_head, self.nomogram_main, nomogram_foot])
        self.scene.addItem(nomogram)

        self.set_feature_marker_values()

        rect = QRectF(self.scene.itemsBoundingRect().x(),
                      self.scene.itemsBoundingRect().y(),
                      self.scene.itemsBoundingRect().width(),
                      self.nomogram.preferredSize().height()).adjusted(10, 0, 20, 0)
        self.scene.setSceneRect(rect)

        # Clip top and bottom (60 and 150) parts from the main view
        self.view.setSceneRect(rect.x(), rect.y() + 80, rect.width() - 10, rect.height() - 160)
        self.view.viewport().setMaximumHeight(rect.height() - 160)
        # Clip main part from top/bottom views
        # below point values are imprecise (less/more than required) but this
        # is not a problem due to clipped scene content still being drawn
        self.top_view.setSceneRect(rect.x(), rect.y() + 3, rect.width() - 10, 20)
        self.bottom_view.setSceneRect(rect.x(), rect.height() - 110, rect.width() - 10, 30)
Пример #11
0
    def update_scene(self):
        self.clear_scene()
        if self.domain is None or not len(self.points[0]):
            return

        n_attrs = self.n_attributes if self.display_index else int(1e10)
        attr_inds, attributes = zip(*self.get_ordered_attributes()[:n_attrs])

        name_items = [QGraphicsTextItem(attr.name) for attr in attributes]
        point_text = QGraphicsTextItem("Points")
        probs_text = QGraphicsTextItem("Probabilities (%)")
        all_items = name_items + [point_text, probs_text]
        name_offset = -max(t.boundingRect().width() for t in all_items) - 10
        w = self.view.viewport().rect().width()
        max_width = w + name_offset - 30

        points = [self.points[i][self.target_class_index]
                  for i in attr_inds]
        if self.align == OWNomogram.ALIGN_LEFT:
            points = [p - p.min() for p in points]
        max_ = np.nan_to_num(max(max(abs(p)) for p in points))
        d = 100 / max_ if max_ else 1
        minimums = [p[self.target_class_index].min() for p in self.points]
        if self.scale == OWNomogram.POINT_SCALE:
            points = [p * d for p in points]

            if self.align == OWNomogram.ALIGN_LEFT:
                self.scale_marker_values = lambda x: (x - minimums) * d
            else:
                self.scale_marker_values = lambda x: x * d
        else:
            if self.align == OWNomogram.ALIGN_LEFT:
                self.scale_marker_values = lambda x: x - minimums
            else:
                self.scale_marker_values = lambda x: x

        point_item, nomogram_head = self.create_main_nomogram(
            attributes, attr_inds,
            name_items, points, max_width, point_text, name_offset)
        probs_item, nomogram_foot = self.create_footer_nomogram(
            probs_text, d, minimums, max_width, name_offset)
        for item in self.feature_items.values():
            item.dot.point_dot = point_item.dot
            item.dot.probs_dot = probs_item.dot
            item.dot.vertical_line = self.hidden_vertical_line

        self.nomogram = nomogram = NomogramItem()
        nomogram.add_items([nomogram_head, self.nomogram_main, nomogram_foot])
        self.scene.addItem(nomogram)

        self.set_feature_marker_values()

        rect = QRectF(self.scene.itemsBoundingRect().x(),
                      self.scene.itemsBoundingRect().y(),
                      self.scene.itemsBoundingRect().width(),
                      self.nomogram.preferredSize().height()).adjusted(10, 0, 20, 0)
        self.scene.setSceneRect(rect)

        # Clip top and bottom (60 and 150) parts from the main view
        self.view.setSceneRect(rect.x(), rect.y() + 80, rect.width() - 10, rect.height() - 160)
        self.view.viewport().setMaximumHeight(rect.height() - 160)
        # Clip main part from top/bottom views
        # below point values are imprecise (less/more than required) but this
        # is not a problem due to clipped scene content still being drawn
        self.top_view.setSceneRect(rect.x(), rect.y() + 3, rect.width() - 10, 20)
        self.bottom_view.setSceneRect(rect.x(), rect.height() - 110, rect.width() - 10, 30)
def qrect_pos_relative(rect: QRectF, rx: float, ry: float) -> QPointF:
    return QPointF(rect.x() + rect.width() * rx, rect.y() + rect.height() * ry)