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)
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
def grab_svg(scene): # type: (QGraphicsScene) -> str """ Return a SVG rendering of the scene contents. Parameters ---------- scene : :class:`CanvasScene` """ svg_buffer = QBuffer() gen = _QSvgGenerator() gen.setOutputDevice(svg_buffer) items_rect = scene.itemsBoundingRect().adjusted(-10, -10, 10, 10) if items_rect.isNull(): items_rect = QRectF(0, 0, 10, 10) width, height = items_rect.width(), items_rect.height() rect_ratio = float(width) / height # Keep a fixed aspect ratio. aspect_ratio = 1.618 if rect_ratio > aspect_ratio: height = int(height * rect_ratio / aspect_ratio) else: width = int(width * aspect_ratio / rect_ratio) target_rect = QRectF(0, 0, width, height) source_rect = QRectF(0, 0, width, height) source_rect.moveCenter(items_rect.center()) gen.setSize(target_rect.size().toSize()) gen.setViewBox(target_rect) painter = QPainter(gen) # Draw background. painter.setPen(Qt.NoPen) painter.setBrush(scene.palette().base()) painter.drawRect(target_rect) # Render the scene scene.render(painter, target_rect, source_rect) painter.end() buffer_str = bytes(svg_buffer.buffer()) return buffer_str.decode("utf-8")
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)
def grab_svg(scene): """ Return a SVG rendering of the scene contents. Parameters ---------- scene : :class:`CanvasScene` """ from AnyQt.QtSvg import QSvgGenerator svg_buffer = QBuffer() gen = QSvgGenerator() gen.setOutputDevice(svg_buffer) items_rect = scene.itemsBoundingRect().adjusted(-10, -10, 10, 10) if items_rect.isNull(): items_rect = QRectF(0, 0, 10, 10) width, height = items_rect.width(), items_rect.height() rect_ratio = float(width) / height # Keep a fixed aspect ratio. aspect_ratio = 1.618 if rect_ratio > aspect_ratio: height = int(height * rect_ratio / aspect_ratio) else: width = int(width * aspect_ratio / rect_ratio) target_rect = QRectF(0, 0, width, height) source_rect = QRectF(0, 0, width, height) source_rect.moveCenter(items_rect.center()) gen.setSize(target_rect.size().toSize()) gen.setViewBox(target_rect) painter = QPainter(gen) # Draw background. painter.setBrush(QBrush(Qt.white)) painter.drawRect(target_rect) # Render the scene scene.render(painter, target_rect, source_rect) painter.end() buffer_str = bytes(svg_buffer.buffer()) return buffer_str.decode("utf-8")
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
def updateScaleBox(self, p1, p2): """ Overload to use ViewBox.mapToView instead of mapRectFromParent mapRectFromParent (from Qt) uses QTransform.invert() which has floating-point issues and can't invert the matrix with large coefficients. ViewBox.mapToView uses invertQTransform from pyqtgraph. This code, except for first three lines, are copied from the overloaded method. """ p1 = self.mapToView(p1) p2 = self.mapToView(p2) r = QRectF(p1, p2) self.rbScaleBox.setPos(r.topLeft()) tr = QTransform.fromScale(r.width(), r.height()) self.rbScaleBox.setTransform(tr) self.rbScaleBox.show()
def updateScaleBox(self, p1, p2): """ Overload to use ViewBox.mapToView instead of mapRectFromParent mapRectFromParent (from Qt) uses QTransform.invert() which has floating-point issues and can't invert the matrix with large coefficients. ViewBox.mapToView uses invertQTransform from pyqtgraph. This code, except for first three lines, are copied from the overloaded method. """ p1 = self.mapToView(p1) p2 = self.mapToView(p2) r = QRectF(p1, p2) self.rbScaleBox.setPos(r.topLeft()) self.rbScaleBox.resetTransform() self.rbScaleBox.scale(r.width(), r.height()) self.rbScaleBox.show()
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()
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
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()
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 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)
def update_rect(self, p1: QPointF, p2: QPointF): rect = QRectF(p1, p2) self.setPos(rect.topLeft()) trans = QTransform.fromScale(rect.width(), rect.height()) self.setTransform(trans)