def __init__(self, source, dest): super().__init__() self.setAcceptedMouseButtons(Qt.LeftButton) self.setCacheMode( self.DeviceCoordinateCache) # Without this, burn thy CPU self.setZValue(1) pen = QPen(Edge.Color.DEFAULT[0], 1) pen.setJoinStyle(Qt.MiterJoin) self.setPen(pen) self.arrowHead = QPolygonF() self._selected = False self._weights = [] self._labels = [] self.squares = GroupOfSquares(self) self.source = source self.dest = dest if source is dest: source.edges.append(self) else: source.edges.insert(0, self) dest.edges.insert(0, self) # Add text labels label = self.label = TextItem('', self) label.setFont(Edge.Font.DEFAULT) label.setZValue(3) self.adjust()
def __init__(self, i, mu1, mu2, sigma1, sigma2, phi, color): OWPlotItem.__init__(self) self.outer_box = QGraphicsPolygonItem(self) self.inner_box = QGraphicsPolygonItem(self) self.i = i self.mu1 = mu1 self.mu2 = mu2 self.sigma1 = sigma1 self.sigma2 = sigma2 self.phi = phi self.twosigmapolygon = QPolygonF([ QPointF(i, mu1 - sigma1), QPointF(i, mu1 + sigma1), QPointF(i + 1, mu2 + sigma2), QPointF(i + 1, mu2 - sigma2), QPointF(i, mu1 - sigma1) ]) self.sigmapolygon = QPolygonF([ QPointF(i, mu1 - .5 * sigma1), QPointF(i, mu1 + .5 * sigma1), QPointF(i + 1, mu2 + .5 * sigma2), QPointF(i + 1, mu2 - .5 * sigma2), QPointF(i, mu1 - .5 * sigma1) ]) if isinstance(color, tuple): color = QColor(*color) color.setAlphaF(.3) self.outer_box.setBrush(color) self.outer_box.setPen(QColor(0, 0, 0, 0)) self.inner_box.setBrush(color) self.inner_box.setPen(color)
def paint(self, painter, _options, _widget): if self.hidden: return if self.expanded: tot = np.sum(self.freqs) if tot == 0: return freqs = self.freqs / tot else: freqs = self.freqs if not self.padding: padding = self.mapRectFromDevice(QRectF(0, 0, 0.5, 0)).width() else: padding = min(20, self.width * self.padding) sx = self.x + padding padded_width = self.width - 2 * padding if self.stacked: painter.setPen(Qt.NoPen) y = 0 for freq, color in zip(freqs, self.colors): painter.setBrush(QBrush(color)) painter.drawRect(QRectF(sx, y, padded_width, freq)) y += freq self.polygon = QPolygonF(QRectF(sx, 0, padded_width, y)) else: polypoints = [QPointF(sx, 0)] pen = QPen(QBrush(Qt.white), 0.5) pen.setCosmetic(True) painter.setPen(pen) wsingle = padded_width / len(self.freqs) for i, freq, color in zip(count(), freqs, self.colors): painter.setBrush(QBrush(color)) x = sx + wsingle * i painter.drawRect( QRectF(x, 0, wsingle, freq)) polypoints += [QPointF(x, freq), QPointF(x + wsingle, freq)] polypoints += [QPointF(polypoints[-1].x(), 0), QPointF(sx, 0)] self.polygon = QPolygonF(polypoints) if self.hovered: pen = QPen(QBrush(Qt.blue), 2, Qt.DashLine) pen.setCosmetic(True) painter.setPen(pen) painter.setBrush(Qt.NoBrush) painter.drawPolygon(self.polygon)
def __init__(self, source, dest): super().__init__() self.setAcceptedMouseButtons(Qt.LeftButton) self.setCacheMode(self.DeviceCoordinateCache) # Without this, burn thy CPU self.setZValue(1) pen = QPen(Edge.Color.DEFAULT[0], 1) pen.setJoinStyle(Qt.MiterJoin) self.setPen(pen) self.arrowHead = QPolygonF() self._selected = False self._weights = [] self._labels = [] self.squares = GroupOfSquares(self) self.source = source self.dest = dest if source is dest: source.edges.append(self) else: source.edges.insert(0, self) dest.edges.insert(0, self) # Add text labels label = self.label = TextItem('', self) label.setFont(Edge.Font.DEFAULT) label.setZValue(3) self.adjust()
def polygon_from_data(xData, yData): """ Creates a polygon from a list of x and y coordinates. :returns: A polygon with point corresponding to ``xData`` and ``yData``. :rtype: QPolygonF """ if xData and yData: n = min(len(xData), len(yData)) p = QPolygonF(n+1) for i in range(n): p[i] = QPointF(xData[i], yData[i]) p[n] = QPointF(xData[0], yData[0]) return p else: return QPolygonF()
def violin_shape(x, p): # type: (Sequence[float], Sequence[float]) -> QPainterPath points = [QPointF(pi, xi) for xi, pi in zip(x, p)] points += [QPointF(-pi, xi) for xi, pi in reversed(list(zip(x, p)))] poly = QPolygonF(points) path = QPainterPath() path.addPolygon(poly) return path
def qgraphicsview_map_rect_from_scene(view: QGraphicsView, rect: QRectF) -> QPolygonF: """Like QGraphicsView.mapFromScene(QRectF) but returning a QPolygonF (without rounding). """ tr = view.viewportTransform() p1 = tr.map(rect.topLeft()) p2 = tr.map(rect.topRight()) p3 = tr.map(rect.bottomRight()) p4 = tr.map(rect.bottomLeft()) return QPolygonF([p1, p2, p3, p4])
def select_by_rectangle(self, rect: QRectF): """ Find regions that intersect with selected rectangle. """ poly_rect = QPolygonF(rect) indices = set() for ci in self.choropleth_items: if ci.intersects(poly_rect): indices.add(np.where(self.master.region_ids == ci.region.id)[0][0]) if indices: self.select_by_indices(np.array(list(indices)))
def _contains_point(item: pg.FillBetweenItem, point: QPointF) -> bool: curve1, curve2 = item.curves x_data_lower, y_data_lower = curve1.curve.getData() x_data_upper, y_data_upper = curve2.curve.getData() pts = [QPointF(x, y) for x, y in zip(x_data_lower, y_data_lower)] pts += [QPointF(x, y) for x, y in reversed(list(zip(x_data_upper, y_data_upper)))] pts += pts[:1] path = QPainterPath() path.addPolygon(QPolygonF(pts)) return path.contains(point)
def _selection_poly(self, item): # type: (Tree) -> QPolygonF """ Return an selection geometry covering item and all its children. """ def left(item): return [self._items[ch] for ch in item.node.branches[:1]] def right(item): return [self._items[ch] for ch in item.node.branches[-1:]] itemsleft = list(preorder(item, left))[::-1] itemsright = list(preorder(item, right)) # itemsleft + itemsright walks from the leftmost leaf up to the root # and down to the rightmost leaf assert itemsleft[0].node.is_leaf assert itemsright[-1].node.is_leaf if item.node.is_leaf: # a single anchor point vert = [itemsleft[0].element.anchor] else: vert = [] for it in itemsleft[1:]: vert.extend([ it.element.path[0], it.element.path[1], it.element.anchor ]) for it in itemsright[:-1]: vert.extend([ it.element.anchor, it.element.path[-2], it.element.path[-1] ]) # close the polygon vert.append(vert[0]) def isclose(a, b, rel_tol=1e-6): return abs(a - b) < rel_tol * max(abs(a), abs(b)) def isclose_p(p1, p2, rel_tol=1e-6): return isclose(p1.x, p2.x, rel_tol) and \ isclose(p1.y, p2.y, rel_tol) # merge consecutive vertices that are (too) close acc = [vert[0]] for v in vert[1:]: if not isclose_p(v, acc[-1]): acc.append(v) vert = acc return QPolygonF([QPointF(*p) for p in vert])
def arrow_path_concave(line, width): # type: (QLineF, float) -> QPainterPath """ Return a :class:`QPainterPath` of a pretty looking arrow. """ path = QPainterPath() p1, p2 = line.p1(), line.p2() if p1 == p2: return path baseline = QLineF(line) # Require some minimum length. baseline.setLength(max(line.length() - width * 3, width * 3)) start, end = baseline.p1(), baseline.p2() mid = (start + end) / 2.0 normal = QLineF.fromPolar(1.0, baseline.angle() + 90).p2() path.moveTo(start) path.lineTo(start + (normal * width / 4.0)) path.quadTo(mid + (normal * width / 4.0), end + (normal * width / 1.5)) path.lineTo(end - (normal * width / 1.5)) path.quadTo(mid - (normal * width / 4.0), start - (normal * width / 4.0)) path.closeSubpath() arrow_head_len = width * 4 arrow_head_angle = 50 line_angle = line.angle() - 180 angle_1 = line_angle - arrow_head_angle / 2.0 angle_2 = line_angle + arrow_head_angle / 2.0 points = [p2, p2 + QLineF.fromPolar(arrow_head_len, angle_1).p2(), baseline.p2(), p2 + QLineF.fromPolar(arrow_head_len, angle_2).p2(), p2] poly = QPolygonF(points) path_head = QPainterPath() path_head.addPolygon(poly) path = path.united(path_head) return path
def arrow_path_plain(line, width): """ Return an :class:`QPainterPath` of a plain looking arrow. """ path = QPainterPath() p1, p2 = line.p1(), line.p2() if p1 == p2: return path baseline = QLineF(line) # Require some minimum length. baseline.setLength(max(line.length() - width * 3, width * 3)) path.moveTo(baseline.p1()) path.lineTo(baseline.p2()) stroker = QPainterPathStroker() stroker.setWidth(width) path = stroker.createStroke(path) arrow_head_len = width * 4 arrow_head_angle = 50 line_angle = line.angle() - 180 angle_1 = line_angle - arrow_head_angle / 2.0 angle_2 = line_angle + arrow_head_angle / 2.0 points = [ p2, p2 + QLineF.fromPolar(arrow_head_len, angle_1).p2(), p2 + QLineF.fromPolar(arrow_head_len, angle_2).p2(), p2, ] poly = QPolygonF(points) path_head = QPainterPath() path_head.addPolygon(poly) path = path.united(path_head) return path
def _create_violin(self, data: np.ndarray) -> Tuple[QPainterPath, float]: if self.__kde is None: x, p, max_density = np.zeros(1), np.zeros(1), 0 else: x = np.linspace(data.min() - self.__bandwidth * 2, data.max() + self.__bandwidth * 2, 1000) p = np.exp(self.__kde.score_samples(x.reshape(-1, 1))) max_density = p.max() p = scale_density(self.__scale, p, len(data), max_density) if self.__orientation == Qt.Vertical: pts = [QPointF(pi, xi) for xi, pi in zip(x, p)] pts += [QPointF(-pi, xi) for xi, pi in reversed(list(zip(x, p)))] else: pts = [QPointF(xi, pi) for xi, pi in zip(x, p)] pts += [QPointF(xi, -pi) for xi, pi in reversed(list(zip(x, p)))] pts += pts[:1] polygon = QPolygonF(pts) path = QPainterPath() path.addPolygon(polygon) return path, max_density
def poly2qpoly(poly: Polygon) -> QPolygonF: return QPolygonF([QPointF(x, y) for x, y in poly.exterior.coords])
class Edge(_SelectableItem, QGraphicsLineItem): ARROW_SIZE = 16 class Font: DEFAULT = QFont('Sans', 12, QFont.Normal) SELECTED = QFont('Sans', 12, QFont.DemiBold) class Color: # = pen color, font brush DEFAULT = Qt.gray, QBrush(Qt.black) SELECTED = QColor('#dd4455'), QBrush(QColor('#770000')) def __init__(self, source, dest): super().__init__() self.setAcceptedMouseButtons(Qt.LeftButton) self.setCacheMode(self.DeviceCoordinateCache) # Without this, burn thy CPU self.setZValue(1) pen = QPen(Edge.Color.DEFAULT[0], 1) pen.setJoinStyle(Qt.MiterJoin) self.setPen(pen) self.arrowHead = QPolygonF() self._selected = False self._weights = [] self._labels = [] self.squares = GroupOfSquares(self) self.source = source self.dest = dest if source is dest: source.edges.append(self) else: source.edges.insert(0, self) dest.edges.insert(0, self) # Add text labels label = self.label = TextItem('', self) label.setFont(Edge.Font.DEFAULT) label.setZValue(3) self.adjust() def addRelation(self, name, shape, is_constraint): if not is_constraint: self.squares.addSquare(np.multiply(*shape)) self._labels.append((name, shape)) tooltip = '\n'.join('{} ({}×{})'.format(i[0], *i[1]) for i in self._labels) self.setToolTip(tooltip) self.label.setToolTip(tooltip) self.squares.setToolTip(tooltip) text = ', '.join(i[0] for i in self._labels) text = text[:15] + ('…' if len(text) > 15 else '') self.label.setText(text) def __contains__(self, node): return node == self.source or node == self.dest def weight(self): return sum(self._weights) def addWeight(self, weight): self._weights.append(weight) @_SelectableItem.selected.setter def selected(self, value): self._selected = value pencolor, fontbrush = Edge.Color.SELECTED if value else Edge.Color.DEFAULT pen = self.pen() pen.setColor(QColor(pencolor)) self.setPen(pen) self.label.setBrush(fontbrush) self.label.setFont(Edge.Font.SELECTED if value else Edge.Font.DEFAULT) self.squares.selected(value) def adjust(self): line = QLineF(self.source.pos(), self.dest.pos()) self.setLine(line) self.label.setPos(line.pointAt(.5) - self.label.boundingRect().center()) self.squares.placeBelow(self.label) def boundingRect(self): extra = (self.pen().width() + Edge.ARROW_SIZE) / 2 p1, p2 = self.line().p1(), self.line().p2() return QRectF(p1, QSizeF(p2.x() - p1.x(), p2.y() - p1.y())).normalized().adjusted(-extra, -extra, extra, extra) def shape(self): path = super().shape() path.addPolygon(self.arrowHead) return path def _arrowhead_points(self, line): ARROW_WIDTH = 2.5 angle = acos(line.dx() / line.length()) if line.dy() >= 0: angle = 2*PI - angle p1 = line.p2() - QPointF(sin(angle + PI / ARROW_WIDTH) * Edge.ARROW_SIZE, cos(angle + PI / ARROW_WIDTH) * Edge.ARROW_SIZE) p2 = line.p2() - QPointF(sin(angle + PI - PI / ARROW_WIDTH) * Edge.ARROW_SIZE, cos(angle + PI - PI / ARROW_WIDTH) * Edge.ARROW_SIZE) return line.p2(), p1, p2 def paintArc(self, painter, option, widget): assert self.source is self.dest node = self.source def best_angle(): """...is the one furthest away from all other angles""" angles = [QLineF(node.pos(), other.pos()).angle() for other in chain((edge.source for edge in node.edges if edge.dest == node and edge.source != node), (edge.dest for edge in node.edges if edge.dest != node and edge.source == node))] angles.sort() if not angles: # If this self-constraint is the only edge return 225 deltas = np.array(angles[1:] + [360 + angles[0]]) - angles return (angles[deltas.argmax()] + deltas.max()/2) % 360 angle = best_angle() inf = QPointF(-1e20, -1e20) # Doesn't work with real -np.inf! line0 = QLineF(node.pos(), inf) line1 = QLineF(node.pos(), inf) line2 = QLineF(node.pos(), inf) line0.setAngle(angle) line1.setAngle(angle - 13) line2.setAngle(angle + 13) p0 = shape_line_intersection(node.shape(), node.pos(), line0) p1 = shape_line_intersection(node.shape(), node.pos(), line1) p2 = shape_line_intersection(node.shape(), node.pos(), line2) path = QPainterPath() path.moveTo(p1) line = QLineF(node.pos(), p0) line.setLength(3*line.length()) pt = line.p2() path.quadTo(pt, p2) line = QLineF(node.pos(), pt) self.setLine(line) # This invalidates DeviceCoordinateCache painter.drawPath(path) # Draw arrow head line = QLineF(pt, p2) self.arrowHead.clear() for point in self._arrowhead_points(line): self.arrowHead.append(point) painter.setBrush(self.pen().color()) painter.drawPolygon(self.arrowHead) # Update label position self.label.setPos(path.pointAtPercent(.5)) if 90 < angle < 270: # Right-align the label pos = self.label.pos() x, y = pos.x(), pos.y() self.label.setPos(x - self.label.boundingRect().width(), y) self.squares.placeBelow(self.label) def paint(self, painter, option, widget=None): color, _ = Edge.Color.SELECTED if self.selected else Edge.Color.DEFAULT pen = self.pen() pen.setColor(color) pen.setBrush(QBrush(color)) pen.setWidth(np.clip(2 * self.weight(), .5, 4)) painter.setPen(pen) self.setPen(pen) if self.source == self.dest: return self.paintArc(painter, option, widget) if self.source.collidesWithItem(self.dest): return have_two_edges = len([edge for edge in self.source.edges if self.source in edge and self.dest in edge and edge is not self]) source_pos = self.source.pos() dest_pos = self.dest.pos() color = self.pen().color() painter.setBrush(color) point = shape_line_intersection(self.dest.shape(), dest_pos, QLineF(source_pos, dest_pos)) line = QLineF(source_pos, point) if have_two_edges: normal = line.normalVector() normal.setLength(15) line = QLineF(normal.p2(), point) self.label.setPos(line.pointAt(.5)) self.squares.placeBelow(self.label) self.setLine(line) painter.drawLine(line) # Draw arrow head self.arrowHead.clear() for point in self._arrowhead_points(line): self.arrowHead.append(point) painter.drawPolygon(self.arrowHead)
def _get_polygon(self) -> QPolygonF: return QPolygonF([QPointF(x, y) for x, y in self.get_points()])
class Edge(_SelectableItem, QGraphicsLineItem): ARROW_SIZE = 16 class Font: DEFAULT = QFont('Sans', 12, QFont.Normal) SELECTED = QFont('Sans', 12, QFont.DemiBold) class Color: # = pen color, font brush DEFAULT = Qt.gray, QBrush(Qt.black) SELECTED = QColor('#dd4455'), QBrush(QColor('#770000')) def __init__(self, source, dest): super().__init__() self.setAcceptedMouseButtons(Qt.LeftButton) self.setCacheMode( self.DeviceCoordinateCache) # Without this, burn thy CPU self.setZValue(1) pen = QPen(Edge.Color.DEFAULT[0], 1) pen.setJoinStyle(Qt.MiterJoin) self.setPen(pen) self.arrowHead = QPolygonF() self._selected = False self._weights = [] self._labels = [] self.squares = GroupOfSquares(self) self.source = source self.dest = dest if source is dest: source.edges.append(self) else: source.edges.insert(0, self) dest.edges.insert(0, self) # Add text labels label = self.label = TextItem('', self) label.setFont(Edge.Font.DEFAULT) label.setZValue(3) self.adjust() def addRelation(self, name, shape, is_constraint): if not is_constraint: self.squares.addSquare(np.multiply(*shape)) self._labels.append((name, shape)) tooltip = '\n'.join('{} ({}×{})'.format(i[0], *i[1]) for i in self._labels) self.setToolTip(tooltip) self.label.setToolTip(tooltip) self.squares.setToolTip(tooltip) text = ', '.join(i[0] for i in self._labels) text = text[:15] + ('…' if len(text) > 15 else '') self.label.setText(text) def __contains__(self, node): return node == self.source or node == self.dest def weight(self): return sum(self._weights) def addWeight(self, weight): self._weights.append(weight) @_SelectableItem.selected.setter def selected(self, value): self._selected = value pencolor, fontbrush = Edge.Color.SELECTED if value else Edge.Color.DEFAULT pen = self.pen() pen.setColor(QColor(pencolor)) self.setPen(pen) self.label.setBrush(fontbrush) self.label.setFont(Edge.Font.SELECTED if value else Edge.Font.DEFAULT) self.squares.selected(value) def adjust(self): line = QLineF(self.source.pos(), self.dest.pos()) self.setLine(line) self.label.setPos( line.pointAt(.5) - self.label.boundingRect().center()) self.squares.placeBelow(self.label) def boundingRect(self): extra = (self.pen().width() + Edge.ARROW_SIZE) / 2 p1, p2 = self.line().p1(), self.line().p2() return QRectF(p1, QSizeF(p2.x() - p1.x(), p2.y() - p1.y())).normalized().adjusted( -extra, -extra, extra, extra) def shape(self): path = super().shape() path.addPolygon(self.arrowHead) return path def _arrowhead_points(self, line): ARROW_WIDTH = 2.5 angle = acos(line.dx() / line.length()) if line.dy() >= 0: angle = 2 * PI - angle p1 = line.p2() - QPointF( sin(angle + PI / ARROW_WIDTH) * Edge.ARROW_SIZE, cos(angle + PI / ARROW_WIDTH) * Edge.ARROW_SIZE) p2 = line.p2() - QPointF( sin(angle + PI - PI / ARROW_WIDTH) * Edge.ARROW_SIZE, cos(angle + PI - PI / ARROW_WIDTH) * Edge.ARROW_SIZE) return line.p2(), p1, p2 def paintArc(self, painter, option, widget): assert self.source is self.dest node = self.source def best_angle(): """...is the one furthest away from all other angles""" angles = [ QLineF(node.pos(), other.pos()).angle() for other in chain(( edge.source for edge in node.edges if edge.dest == node and edge.source != node), ( edge.dest for edge in node.edges if edge.dest != node and edge.source == node)) ] angles.sort() if not angles: # If this self-constraint is the only edge return 225 deltas = np.array(angles[1:] + [360 + angles[0]]) - angles return (angles[deltas.argmax()] + deltas.max() / 2) % 360 angle = best_angle() inf = QPointF(-1e20, -1e20) # Doesn't work with real -np.inf! line0 = QLineF(node.pos(), inf) line1 = QLineF(node.pos(), inf) line2 = QLineF(node.pos(), inf) line0.setAngle(angle) line1.setAngle(angle - 13) line2.setAngle(angle + 13) p0 = shape_line_intersection(node.shape(), node.pos(), line0) p1 = shape_line_intersection(node.shape(), node.pos(), line1) p2 = shape_line_intersection(node.shape(), node.pos(), line2) path = QPainterPath() path.moveTo(p1) line = QLineF(node.pos(), p0) line.setLength(3 * line.length()) pt = line.p2() path.quadTo(pt, p2) line = QLineF(node.pos(), pt) self.setLine(line) # This invalidates DeviceCoordinateCache painter.drawPath(path) # Draw arrow head line = QLineF(pt, p2) self.arrowHead.clear() for point in self._arrowhead_points(line): self.arrowHead.append(point) painter.setBrush(self.pen().color()) painter.drawPolygon(self.arrowHead) # Update label position self.label.setPos(path.pointAtPercent(.5)) if 90 < angle < 270: # Right-align the label pos = self.label.pos() x, y = pos.x(), pos.y() self.label.setPos(x - self.label.boundingRect().width(), y) self.squares.placeBelow(self.label) def paint(self, painter, option, widget=None): color, _ = Edge.Color.SELECTED if self.selected else Edge.Color.DEFAULT pen = self.pen() pen.setColor(color) pen.setBrush(QBrush(color)) pen.setWidth(np.clip(2 * self.weight(), .5, 4)) painter.setPen(pen) self.setPen(pen) if self.source == self.dest: return self.paintArc(painter, option, widget) if self.source.collidesWithItem(self.dest): return have_two_edges = len([ edge for edge in self.source.edges if self.source in edge and self.dest in edge and edge is not self ]) source_pos = self.source.pos() dest_pos = self.dest.pos() color = self.pen().color() painter.setBrush(color) point = shape_line_intersection(self.dest.shape(), dest_pos, QLineF(source_pos, dest_pos)) line = QLineF(source_pos, point) if have_two_edges: normal = line.normalVector() normal.setLength(15) line = QLineF(normal.p2(), point) self.label.setPos(line.pointAt(.5)) self.squares.placeBelow(self.label) self.setLine(line) painter.drawLine(line) # Draw arrow head self.arrowHead.clear() for point in self._arrowhead_points(line): self.arrowHead.append(point) painter.drawPolygon(self.arrowHead)