python - 在 QGraphicsScene 中的 GGraphicsEllipseItem 的中心对齐 QLabel
问题描述
我想知道是否有任何方法可以将 Qlabel 放置在我用 PyQt 设计的饼图中的“扇区”中间。这些扇区是 QgraphicsEllipseItems,我已经分配了相应的起始角和跨度角。到目前为止,我已经尝试将标签放在 QGraphicsProxyItem 中并将“切片”或“扇区”设置为父级。然后,我尝试将标签放置在“扇区”的中间。但是,问题在于 boundingRect 是一个矩形,而不仅仅是“扇区”本身。
当前饼图
在上图中,您可以看到问题所在。标签位置不正确。我正在尝试使用标签来实现这样的目标:
带有正确位置标签的模型图表
当前代码:
pie = QtWidgets.QGraphicsEllipseItem(60, 110, self.piechart.width, self.piechart.height)
pie.setTransformOriginPoint(pie.boundingRect().center())
pie.setZValue(-1)
self.scene.addItem(pie)
start_angle = 0
for sector in self.piechart.sectors:
slice = QtWidgets.QGraphicsEllipseItem(60, 110, self.piechart.width, self.piechart.height, parent=pie)
color = QtGui.QBrush(sector.color)
slice.setBrush(color)
span_angle = sector.proportional_volume*360*16
slice.setStartAngle(start_angle)
slice.setSpanAngle(span_angle)
start_angle += span_angle
label = QtWidgets.QLabel(str(round(sector.proportional_volume*100,2))+'%', alignment = QtCore.Qt.AlignCenter)
label.setAutoFillBackground(False)
label.setStyleSheet('background-color: transparent')
font = QtGui.QFont('Times', 8)
font.setBold(True)
label.setFont(font)
proxy = QtWidgets.QGraphicsProxyWidget(slice)
proxy.setWidget(label)
proxy.setPos(slice.boundingRect().center())
self.scene.addItem(slice)
self.scene.addItem(proxy)
解决方案
由于几何原因(来自基本QGraphicsItem.boundingRect()定义),您不能使用边界矩形的中心:
这个纯虚函数将项目的外部边界定义为一个矩形
这意味着无论项目的形状是什么,边界矩形都是一个矩形,包括项目的整个形状。
在下图中,您可以清楚地看到“这不是您要寻找的中心”[cit.]:
您实际需要的是每个切片的中轴中心,因此只需将该切片的范围分成一半并使用一半的半径即可。
请注意,图形项(就像小部件一样)通常使用它们的左上角作为其位置的参考,如果您为此使用“中间”,您将不会得到正确的结果:
为避免这种情况,您必须减去标签矩形的中心,以便它正确地居中于您想要的位置。
# create a common rectangle, since we're going to use the same coordinates
# for all ellipses
rect = QtCore.QRectF(60, 110, self.piechart.width, self.piechart.height)
pie = QtWidgets.QGraphicsEllipseItem(rect)
pie.setTransformOriginPoint(pie.boundingRect().center())
pie.setZValue(-1)
self.scene.addItem(pie)
# the center position is half of the radius, or a quarter of the size
labelRadius = self.piechart.width * .25
center = rect.center()
start_angle = 0
for sector in self.piechart.sectors:
slice = QtWidgets.QGraphicsEllipseItem(rect, parent=pie)
slice.setPen(QtGui.QPen(QtCore.Qt.NoPen))
color = QtGui.QBrush(sector.color)
slice.setBrush(color)
span_angle = sector.proportional_volume*360*16
slice.setStartAngle(start_angle)
slice.setSpanAngle(span_angle)
# we need the current start_angle later, let's do this later
# start_angle += span_angle
label = QtWidgets.QLabel(str(round(sector.proportional_volume*100,2))+'%', alignment = QtCore.Qt.AlignCenter)
label.setAutoFillBackground(False)
label.setStyleSheet('background-color: transparent')
font = QtGui.QFont('Times', 8)
font.setBold(True)
label.setFont(font)
proxy = QtWidgets.QGraphicsProxyWidget(slice)
proxy.setWidget(label)
# get the middle angle of the slice
angle = start_angle / 16 + sector.proportional_volume * 180
# create a line that has a length that's a quarter of the radius, and
# has an angle that is in the middle of the current slice; finally,
# get its end point (p2)
line = QtCore.QLineF.fromPolar(labelRadius, angle)
middlePoint = line.p2()
# the above can also be achieved by using trigonometry functions; note
# that the sine is negative, since QGraphicsScene coordinates are
# inverted vertically (downwards coordinates are positive)
# x = cos(radians(angle)) * labelRadius
# y = -sin(radians(angle)) * labelRadius
# middlePoint = QtCore.QPointF(x, y)
# finally, translate the point to the center of the ellipse
middlePoint += center
r = QtCore.QRectF(-1, -1, 2, 2)
self.scene.addEllipse(r.translated(middlePoint))
# center the label at that point by subtracting its own center
proxy.setPos(middlePoint - label.rect().center())
start_angle += span_angle
# when you create a QGraphicsItem with a parent that has already been
# added to the scene, there's no need to add it again
# self.scene.addItem(slice)
# self.scene.addItem(proxy)
考虑到从图形的角度来看,实际的“中间”位置通常会被认为更接近中心,这是由于基于切片几何形状和字符视觉感知的光学错觉;下图基于上面的代码,您可以看到右侧的小浅绿色切片 (4.76%) 似乎偏移了,即使它正确居中:
因此,您可能更喜欢使用稍高的比率labelRadius
(例如 0.3)。
最后,有两个考虑:
- 某些百分比可能非常小,最终可能会导致某些标签被下一个切片部分隐藏;您可能应该更愿意避免将标签作为切片的父对象
- 对简单的 QLabel 使用 QGraphicsProxyWidget 是不必要的,请考虑使用QGraphicsSimpleTextItem或QGraphicsTextItem,但请仔细阅读他们的文档,因为它们的坐标系的行为方式并不完全相同。
推荐阅读
- java - 如何为单个 Spring Boot 应用程序创建两个日志文件
- python - 时间戳到日期时间的转换结果在 python 中为 2082
- postgresql - 如何在 PostgreSQL 中提取表的唯一列的名称?
- php - 将多个帖子值排序到数组组 PHP
- python - 有序列表的可逆组合生成器
- python - 将 pandas 写入 SQL 时指定 NOT NULL
- javascript - 在创建之前不适用于我的续集模型
- angular - 电子错误系统找不到指定的路径
- python - MariaDB:SELECT 命令被拒绝
- javascript - 如何读取文件并在其中搜索字符串