用PyQt5设计饼状统计图(QPainterpath画扇形饼圆,画圆角矩形,鼠标mouseMoveEvent)

项目重难点

    • 概要
    • 成品图
    • 用QPainterpath绘制扇形饼圆
      • paintEvent重绘
      • 用painterpath绘制扇形饼圆
      • 图例中的文字和圆角矩形
    • 重写mouseMoveEvent实现鼠标悬停动作

概要

该控件是参考了feiyangqingyun的广告占比图(Qt作品),更改了配色,重用PyQt5编写而成。原版在此:
https://blog.csdn.net/feiyangqingyun/article/details/98472081

成品图

先上成品图

当鼠标停在饼圆对应区域,该区域面积会扩大弹出,同时,对应下方的图例字体会变成黄色。
该项目的重难点主要有两个:1. 绘制饼圆; 2. 鼠标进入事件。
用PyQt5设计饼状统计图(QPainterpath画扇形饼圆,画圆角矩形,鼠标mouseMoveEvent)_第1张图片

用QPainterpath绘制扇形饼圆

paintEvent重绘

当用户对窗口进行拉伸,移动,或者鼠标悬停在某个扇形饼圆区域,则触发paintEvent 对窗口元素进行重绘,其核心代码如下:

def paintEvent(self, event):
    # 绘制准备工作, 启用反锯齿
    painter = QtGui.QPainter(self)
    painter.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing)
    
    # 平移坐标中心,等比例缩放
    width = self.width()
    height = self.height()
    side = min(width, height)
    painter.translate(width / 2, height / 2) # 坐标中心移至窗口中心位置
    painter.scale(side / 200.0, side / 200.0) # 坐标刻度缩放为原来的(side/200)倍 

    # 画圆
    self.drawOuterCircle(painter) # 绘制外侧圆
    self.drawMidCircle(painter) # 绘制内侧圆
    self.drawOuterPie(painter) # 绘制外侧大的三色扇形饼圆
    self.drawInnerPie(painter) # 绘制内侧小的三色扇形饼圆
    self.drawLegend(painter)   # 绘制图例
    self.drawTitle(painter)    # 绘制左上角文字

用painterpath绘制扇形饼圆

扇形饼圆的绘制是由大扇形减去小扇形得来的。以绘制外侧香槟色饼圆为例,其核心代码如下所示:

def drawOuterPie(self, painter):
    painter.save() # 以下绘图保存至painter的坐标系统
    # 设置标志位,判断鼠标是否进入该区域。如果进入该区域,则半径扩大
    if self.mouseOuterChampagne: 
        radius = self.outerPieRadius + 3
    else:
        radius = self.outerPieRadius

    # 绘制大扇形
    rect = QtCore.QRectF(-radius, -radius - 10, radius * 2, radius * 2) # 该扇形饼圆所在的region
    pathOuterChampagnePie = QtGui.QPainterPath() # 新建QPainterPath对象
    pathOuterChampagnePie.arcMoveTo(rect, 0) # 画弧线的起点角度,从0°开始
    pathOuterChampagnePie.arcTo(rect, 0, self.outerChampagnePercent * 360) # 扇形饼圆弧度为self.outerChampagnePercent * 360
    pathOuterChampagnePie.lineTo(0, -10) # 画直线
    pathOuterChampagnePie.lineTo(radius, -10) # 另一条直线
    pathOuterChampagnePie.closeSubpath() # 使该路径闭合

    # 以下同理绘制小扇形
    radius = self.midCircleRadius
    rect1 = QtCore.QRectF(-radius, -radius - 10, radius * 2, radius * 2)
    pathMidPie = QtGui.QPainterPath()
    pathMidPie.arcMoveTo(rect1, 0)
    pathMidPie.arcTo(rect1, 0, self.outerChampagnePercent * 360)
    pathMidPie.lineTo(0, -10)
    pathMidPie.lineTo(radius, -10)
    pathMidPie.closeSubpath()

    # 大扇形减去小扇形,得到扇形饼圆
    self.pathOuterChampagne = pathOuterChampagnePie.subtracted(pathMidPie)
    painter.setPen(self.outerPieChampagne)
    painter.setBrush(self.outerPieChampagne)
    painter.drawPath(self.pathOuterChampagne)
    ......

   painter.restore() # 恢复坐标系

图例中的文字和圆角矩形

圆角矩形的绘制用painter.drawRoundedRect() 函数来实现,其核心代码如下:

# 穿衣
painter.setPen(QtCore.Qt.NoPen)
painter.setBrush(self.outerPiePurple)
painter.drawRoundedRect(-120, 64, 10, 10, 2, 2) # 绘制圆角矩形
if self.mouseOuterPurple == True:
    painter.setPen(self.emphasizedTextColor)
else:
    painter.setPen(QtCore.Qt.white)
painter.drawText(-106, 72, str("穿衣 : 30%"))

重写mouseMoveEvent实现鼠标悬停动作

在鼠标相应事件中,所参考的坐标系是以窗口左上角为零点的坐标系统。因此在判断鼠标是否落入扇形饼圆时,应将鼠标位置坐标转换为坐标原点为窗口中心 的坐标系下的坐标。若鼠标悬停在某区域,则改变标志位,使得在调用painterEvent 重绘时突出该区域。核心代码如下:

def mouseMoveEvent(self, event):
    # 坐标系转换,和之前painter转换坐标系相对应
    width = self.width()
    height = self.height()
    side = min(width, height)
    enterPoint = QtCore.QPoint((event.pos().x() - width/2) /side * 200.0,
                               (event.pos().y() - height/2)/side * 200.0)

    # 判断鼠标是否进入,并置标志位
    if self.pathOuterChampagne.contains(enterPoint): self.mouseOuterChampagne = True
    else: self.mouseOuterChampagne = False

    if self.pathOuterCream.contains(enterPoint): self.mouseOuterCream = True
    else: self.mouseOuterCream = False
    ......

    
    self.update()  # 重绘

以上,就是该项目的几个关键点。在此写博客记录一下~

最后,上海的Qter们,欢迎关注公众号“Qter欢聚在上海”,让我们共同学习,共同成长?

你可能感兴趣的:(PyQt5,Qt,Python)