PyQt/QtChart/BarStack.py

264 lines
9.2 KiB
Python
Raw Normal View History

2018-12-28 10:48:24 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
2021-07-13 14:52:26 +08:00
"""
2018-12-28 10:48:24 +08:00
Created on 2017年12月28日
2021-07-13 14:52:26 +08:00
@author: Irony
@site: https://pyqt.site , https://github.com/PyQt5
2018-12-28 10:48:24 +08:00
@email: 892768447@qq.com
@file: charts.bar.BarStack
@description: like http://echarts.baidu.com/demo.html#bar-stack
2021-07-13 14:52:26 +08:00
"""
2018-12-28 10:48:24 +08:00
import sys
2019-10-04 10:18:34 +08:00
from random import randint
2018-12-28 10:48:24 +08:00
2021-07-13 14:52:26 +08:00
try:
from PyQt5.QtChart import QChartView, QChart, QBarSeries, QBarSet, QBarCategoryAxis
from PyQt5.QtCore import Qt, QPointF, QRectF, QPoint
from PyQt5.QtGui import QPainter, QPen
from PyQt5.QtWidgets import QApplication, QGraphicsLineItem, QWidget, \
QHBoxLayout, QLabel, QVBoxLayout, QGraphicsProxyWidget
except ImportError:
from PySide2.QtCore import Qt, QPointF, QRectF, QPoint
from PySide2.QtGui import QPainter, QPen
from PySide2.QtWidgets import QApplication, QGraphicsLineItem, QWidget, \
QHBoxLayout, QLabel, QVBoxLayout, QGraphicsProxyWidget
from PySide2.QtCharts import QtCharts
QChartView = QtCharts.QChartView
QChart = QtCharts.QChart
QBarSeries = QtCharts.QBarSeries
QBarSet = QtCharts.QBarSet
QBarCategoryAxis = QtCharts.QBarCategoryAxis
2018-12-28 10:48:24 +08:00
class ToolTipItem(QWidget):
def __init__(self, color, text, parent=None):
super(ToolTipItem, self).__init__(parent)
layout = QHBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
clabel = QLabel(self)
clabel.setMinimumSize(12, 12)
clabel.setMaximumSize(12, 12)
clabel.setStyleSheet("border-radius:6px;background: rgba(%s,%s,%s,%s);" % (
color.red(), color.green(), color.blue(), color.alpha()))
layout.addWidget(clabel)
self.textLabel = QLabel(text, self, styleSheet="color:white;")
layout.addWidget(self.textLabel)
def setText(self, text):
self.textLabel.setText(text)
class ToolTipWidget(QWidget):
Cache = {}
def __init__(self, *args, **kwargs):
super(ToolTipWidget, self).__init__(*args, **kwargs)
self.setAttribute(Qt.WA_StyledBackground, True)
self.setStyleSheet(
"ToolTipWidget{background: rgba(50, 50, 50, 100);}")
layout = QVBoxLayout(self)
self.titleLabel = QLabel(self, styleSheet="color:white;")
layout.addWidget(self.titleLabel)
def updateUi(self, title, bars):
self.titleLabel.setText(title)
for bar, value in bars:
if bar not in self.Cache:
item = ToolTipItem(
bar.color(),
(bar.label() or "-") + ":" + str(value), self)
self.layout().addWidget(item)
self.Cache[bar] = item
else:
self.Cache[bar].setText(
(bar.label() or "-") + ":" + str(value))
brush = bar.brush()
color = brush.color()
self.Cache[bar].setVisible(color.alphaF() == 1.0) # 隐藏那些不可用的项
self.adjustSize() # 调整大小
class GraphicsProxyWidget(QGraphicsProxyWidget):
def __init__(self, *args, **kwargs):
super(GraphicsProxyWidget, self).__init__(*args, **kwargs)
self.setZValue(999)
self.tipWidget = ToolTipWidget()
self.setWidget(self.tipWidget)
self.hide()
def width(self):
return self.size().width()
def height(self):
return self.size().height()
def show(self, title, bars, pos):
self.setGeometry(QRectF(pos, self.size()))
self.tipWidget.updateUi(title, bars)
super(GraphicsProxyWidget, self).show()
class ChartView(QChartView):
def __init__(self, *args, **kwargs):
super(ChartView, self).__init__(*args, **kwargs)
self.resize(800, 600)
self.setRenderHint(QPainter.Antialiasing) # 抗锯齿
self.initChart()
# 提示widget
self.toolTipWidget = GraphicsProxyWidget(self._chart)
# line 宽度需要调整
self.lineItem = QGraphicsLineItem(self._chart)
pen = QPen(Qt.gray)
self.lineItem.setPen(pen)
self.lineItem.setZValue(998)
self.lineItem.hide()
# 一些固定计算减少mouseMoveEvent中的计算量
# 获取x和y轴的最小最大值
axisX, axisY = self._chart.axisX(), self._chart.axisY()
self.category_len = len(axisX.categories())
self.min_x, self.max_x = -0.5, self.category_len - 0.5
self.min_y, self.max_y = axisY.min(), axisY.max()
# 坐标系中左上角顶点
self.point_top = self._chart.mapToPosition(
QPointF(self.min_x, self.max_y))
def mouseMoveEvent(self, event):
super(ChartView, self).mouseMoveEvent(event)
pos = event.pos()
# 把鼠标位置所在点转换为对应的xy值
x = self._chart.mapToValue(pos).x()
y = self._chart.mapToValue(pos).y()
index = round(x)
# 得到在坐标系中的所有bar的类型和点
serie = self._chart.series()[0]
bars = [(bar, bar.at(index))
for bar in serie.barSets() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y]
2021-07-13 14:52:26 +08:00
# print(bars)
2018-12-28 10:48:24 +08:00
if bars:
right_top = self._chart.mapToPosition(
QPointF(self.max_x, self.max_y))
# 等分距离比例
step_x = round(
(right_top.x() - self.point_top.x()) / self.category_len)
posx = self._chart.mapToPosition(QPointF(x, self.min_y))
self.lineItem.setLine(posx.x(), self.point_top.y(),
posx.x(), posx.y())
self.lineItem.show()
try:
title = self.categories[index]
except:
title = ""
t_width = self.toolTipWidget.width()
t_height = self.toolTipWidget.height()
# 如果鼠标位置离右侧的距离小于tip宽度
x = pos.x() - t_width if self.width() - \
2021-07-13 14:52:26 +08:00
pos.x() - 20 < t_width else pos.x()
2018-12-28 10:48:24 +08:00
# 如果鼠标位置离底部的高度小于tip高度
y = pos.y() - t_height if self.height() - \
2021-07-13 14:52:26 +08:00
pos.y() - 20 < t_height else pos.y()
2018-12-28 10:48:24 +08:00
self.toolTipWidget.show(
title, bars, QPoint(x, y))
else:
self.toolTipWidget.hide()
self.lineItem.hide()
def handleMarkerClicked(self):
marker = self.sender() # 信号发送者
if not marker:
return
bar = marker.barset()
if not bar:
return
# bar透明度
brush = bar.brush()
color = brush.color()
alpha = 0.0 if color.alphaF() == 1.0 else 1.0
color.setAlphaF(alpha)
brush.setColor(color)
bar.setBrush(brush)
# marker
brush = marker.labelBrush()
color = brush.color()
alpha = 0.4 if color.alphaF() == 1.0 else 1.0
# 设置label的透明度
color.setAlphaF(alpha)
brush.setColor(color)
marker.setLabelBrush(brush)
# 设置marker的透明度
brush = marker.brush()
color = brush.color()
color.setAlphaF(alpha)
brush.setColor(color)
marker.setBrush(brush)
def handleMarkerHovered(self, status):
# 设置bar的画笔宽度
marker = self.sender() # 信号发送者
if not marker:
return
bar = marker.barset()
if not bar:
return
pen = bar.pen()
if not pen:
return
pen.setWidth(pen.width() + (1 if status else -1))
bar.setPen(pen)
def handleBarHoverd(self, status, index):
# 设置bar的画笔宽度
bar = self.sender() # 信号发送者
pen = bar.pen()
if not pen:
return
pen.setWidth(pen.width() + (1 if status else -1))
bar.setPen(pen)
def initChart(self):
self._chart = QChart(title="柱状图堆叠")
self._chart.setAcceptHoverEvents(True)
# Series动画
self._chart.setAnimationOptions(QChart.SeriesAnimations)
self.categories = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
names = ["邮件营销", "联盟广告", "视频广告", "直接访问", "搜索引擎"]
series = QBarSeries(self._chart)
for name in names:
bar = QBarSet(name)
# 随机数据
for _ in range(7):
bar.append(randint(0, 10))
series.append(bar)
bar.hovered.connect(self.handleBarHoverd) # 鼠标悬停
self._chart.addSeries(series)
self._chart.createDefaultAxes() # 创建默认的轴
# x轴
axis_x = QBarCategoryAxis(self._chart)
axis_x.append(self.categories)
self._chart.setAxisX(axis_x, series)
# chart的图例
legend = self._chart.legend()
legend.setVisible(True)
# 遍历图例上的标记并绑定信号
for marker in legend.markers():
# 点击事件
marker.clicked.connect(self.handleMarkerClicked)
# 鼠标悬停事件
marker.hovered.connect(self.handleMarkerHovered)
self.setChart(self._chart)
if __name__ == "__main__":
app = QApplication(sys.argv)
view = ChartView()
view.show()
sys.exit(app.exec_())