def _updateLabel(self, is_left): """ Called by updatePositionAndAppearance during init, or later by updateConnectivity. Updates drawing and position of the label. """ lbl = self._label if self._idx != None: bw = _BASE_WIDTH num = self._partner_virtual_helix.number() tBR = _FM.tightBoundingRect(str(num)) half_label_h = tBR.height()/2.0 half_label_w = tBR.width()/2.0 # determine x and y positions label_x = bw/2.0 - half_label_w if self._is_on_top: label_y = -0.25*half_label_h - 0.5 - 0.5*bw else: label_y = 2*half_label_h + 0.5 + 0.5*bw # adjust x for left vs right label_x_offset = 0.25*bw if is_left else -0.25*bw label_x += label_x_offset # adjust x for numeral 1 if num == 1: label_x -= half_label_w/2.0 # create text item if lbl == None: lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(label_x, label_y) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_TO_HELIX_NUM_FONT) self._label = lbl lbl.setText( str(self._partner_virtual_helix.number()) ) lbl.show()
def _updateLabel(self, is_left): """ Called by updatePositionAndAppearance during init, or later by updateConnectivity. Updates drawing and position of the label. """ lbl = self._label if self._idx != None: bw = _BASE_WIDTH num = self._partner_virtual_helix.number() tBR = _FM.tightBoundingRect(str(num)) half_label_h = tBR.height() / 2.0 half_label_w = tBR.width() / 2.0 # determine x and y positions label_x = bw / 2.0 - half_label_w if self._is_on_top: label_y = -0.25 * half_label_h - 0.5 - 0.5 * bw else: label_y = 2 * half_label_h + 0.5 + 0.5 * bw # adjust x for left vs right label_x_offset = 0.25 * bw if is_left else -0.25 * bw label_x += label_x_offset # adjust x for numeral 1 if num == 1: label_x -= half_label_w / 2.0 # create text item if lbl == None: lbl = QGraphicsSimpleTextItem(str(num), self) lbl.setPos(label_x, label_y) lbl.setBrush(_ENAB_BRUSH) lbl.setFont(_TO_HELIX_NUM_FONT) self._label = lbl lbl.setText(str(self._partner_virtual_helix.number())) lbl.show()
class EdgeItem(QGraphicsItem): LINE_WIDTH = 1 OFFSET = 8 # 方向线偏离中心线的距离 MIN_ARROW_WIDTH, MAX_ARROW_WIDTH = 1, 8 double_click_callback = EMPTY_FUNC def __init__(self, edge_id): super().__init__() self.setZValue(1) self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.setAcceptHoverEvents(True) self.edge_id = edge_id self.text_item = QGraphicsSimpleTextItem('', self) self.text_item.setZValue(4) self.style = { 'name': f'Edge{edge_id}', 'color': Qt.black, 'width': 0.5, # 0~1 的中间值 'line': Qt.SolidLine, 'show_arrow': False, 'text': '', 'text_color': Qt.black, 'show_text': False, } self.hover = False def type(self): return QGraphicsItem.UserType + abs(hash(EdgeItem)) def boundingRect(self): return self.bounding_rect def shape(self): path = QPainterPath() path.addPolygon(self.shape_polygon) path.closeSubpath() return path # ------------------------------------------------------------------------- def adjust(self, src_p: QPointF, dst_p: QPointF): self.angle = getAngle(src_p, dst_p) self.src_p = src_p self.arrow_p = (src_p + 2 * dst_p) / 3 # 箭头开始位置, 前端2/3处 self.dst_p = dst_p W1 = 1 * self.OFFSET W2 = 2 * self.OFFSET W3 = 3 * self.OFFSET vec = getRightOffsetVector(self.angle) self.arrow_polygon = QPolygonF([ src_p + vec * W1, dst_p + vec * W1, self.arrow_p + vec * W2, self.arrow_p + vec * W1 ]) self.shape_polygon = QPolygonF( [src_p, src_p + vec * W2, dst_p + vec * W2, dst_p]) self.bounding_rect = QRectF(src_p, dst_p).normalized() # normalized 正方向 self.bounding_rect.adjust(-W3, -W3, W3, W3) self.text_p = ((src_p + dst_p) / 2) + vec * W1 self.text_item.setPos(self.text_p) self.prepareGeometryChange() # ------------------------------------------------------------------------- def paint(self, painter, option, widget=None): if self.style['show_arrow'] or self.hover: width = threshold(0.0, self.style['width'], 1.0) width = width * (self.MAX_ARROW_WIDTH - self.MIN_ARROW_WIDTH) + self.MIN_ARROW_WIDTH painter.setPen(QPen(self.style['color'], width, self.style['line'])) painter.setBrush(self.style['color']) painter.drawPolygon(self.arrow_polygon) else: # TODO 定制线类型 虚线或实线 painter.setPen(QPen(Qt.black, self.LINE_WIDTH)) painter.drawLine(self.src_p, self.dst_p) if (self.style['show_arrow'] and self.style['show_text']) or self.hover: self.text_item.setPen(self.style['text_color']) self.text_item.setText( f"{self.style['name']}\n{self.style['text']}") self.text_item.show() else: self.text_item.hide() # ------------------------------------------------------------------------- def mouseDoubleClickEvent(self, event): self.double_click_callback(self.edge_id) super().mouseDoubleClickEvent(event) def hoverEnterEvent(self, event): self.hover = True self.update() super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): self.hover = False self.update() super().hoverLeaveEvent(event) # ------------------------------------------------------------------------- def setStyle(self, style) -> None: for key in self.style: try: self.style[key] = style[key] except KeyError: pass self.update()
class NodeItem(QGraphicsItem): # 面向图形界面, 负责控制显示效果 MIN_SIZE, MAX_SIZE = 10, 100 press_callback = EMPTY_FUNC release_callback = EMPTY_FUNC double_click_callback = EMPTY_FUNC move_callback = EMPTY_FUNC def __init__(self, node_id): super().__init__() self.setZValue(2) self.setAcceptHoverEvents(True) self.setFlag(QGraphicsItem.ItemIsMovable) # 可以移动 self.setFlag(QGraphicsItem.ItemSendsGeometryChanges) self.setCacheMode(QGraphicsItem.DeviceCoordinateCache) # 面向UINet 负责增添逻辑操作 self.node_id = node_id self.hover = False self.cached_size = None self.bounding_rect = QRectF() # XXX 在重绘时会被更新,重绘前可能会节点可能会被覆盖显示 # self.call_backs = CallTable() self.text_item = QGraphicsSimpleTextItem(self) self.text_item.setZValue(3) # 面向图形界面, 负责控制显示效果 self.style = { 'name': f' {node_id}', 'color': Qt.white, 'shape': 'Pie', # ('Pie', 'Rect', QPixmap) 'size': 0.5, # 0~1 的中间值 'text': '', 'text_color': Qt.black, 'show_text': False, } def type(self) -> int: return QGraphicsItem.UserType + abs(hash(NodeItem)) def boundingRect(self): return self.bounding_rect def shape(self): path = QPainterPath() path.addRect(self.bounding_rect) return path def paint(self, painter, option, widget=None) -> None: # 绘制尺寸 size = threshold(0.0, self.style['size'], 1.0) size = size * (self.MAX_SIZE - self.MIN_SIZE) + self.MIN_SIZE if size != self.cached_size: self.bounding_rect = QRectF(-size / 2, -size / 2, size, size) self.cached_size = size # 绘制图标或颜色和形状 if isinstance(self.style['shape'], QPixmap): pixmap = self.style['shape'] painter.drawPixmap(self.bounding_rect, pixmap, QRectF(pixmap.rect())) elif self.style['shape'] == 'Pie': painter.setBrush(self.style['color']) painter.drawEllipse(self.bounding_rect) # or drawRect elif self.style['shape'] == 'Rect': painter.setBrush(self.style['color']) painter.drawRect(self.bounding_rect) # or drawRect else: raise ValueError('未知shape类型', self.style['shape']) # 绘制说明 text = f"{self.style['name']}\n" if self.style['show_text'] or self.hover: text += str(self.style['text']) self.text_item.setPen(self.style['text_color']) self.text_item.setText(text) self.text_item.show() # ------------------------------------------------------------------------------------------------------------------ def itemChange(self, change, value): if change == QGraphicsItem.ItemPositionHasChanged: self.style['pos'] = self.pos() # 更新位置变化 self.move_callback(self.node_id) return super().itemChange(change, value) def mousePressEvent(self, event): self.press_callback(self.node_id) return super().mousePressEvent(event) def mouseReleaseEvent(self, event): self.release_callback(self.node_id) return super().mouseReleaseEvent(event) def mouseDoubleClickEvent(self, event): self.double_click_callback(self.node_id) return super().mouseDoubleClickEvent(event) def hoverEnterEvent(self, event): self.hover = True return super().hoverEnterEvent(event) def hoverLeaveEvent(self, event): self.hover = False return super().hoverLeaveEvent(event) # ------------------------------------------------------------------------------------------------------------------ def setStyle(self, style) -> None: for key in self.style: if key in style: self.style[key] = style[key] self.update() def checkPos(self, x, y): pos = QPointF(x, y) if self.pos() != pos: self.setPos(pos)