line chart
This commit is contained in:
parent
d1cdca3f2c
commit
2d39e3b679
7 changed files with 395 additions and 204 deletions
8
PyQtChart练习/charts/README.md
Normal file
8
PyQtChart练习/charts/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# PyQtChart练习
|
||||
|
||||
[百度 echarts 折线图demo](http://echarts.baidu.com/examples.html#chart-type-line)
|
||||
|
||||
### [Python3.5 or latter][PyQt5 PyQtChart]
|
||||
|
||||
### [1.折线图](line/)
|
||||
- [1.折线图堆叠](line/LineStack.py)
|
|
@ -12,10 +12,12 @@ Created on 2017年12月28日
|
|||
|
||||
import sys
|
||||
|
||||
from PyQt5.QtChart import QChartView, QChart, QLineSeries
|
||||
from PyQt5.QtCore import Qt, QPointF
|
||||
from PyQt5.QtChart import QChartView, QChart, QLineSeries, QLegend,\
|
||||
QCategoryAxis
|
||||
from PyQt5.QtCore import Qt, QPointF, QRectF, QPoint
|
||||
from PyQt5.QtGui import QPainter, QPen
|
||||
from PyQt5.QtWidgets import QApplication, QGraphicsLineItem
|
||||
from PyQt5.QtWidgets import QApplication, QGraphicsLineItem, QWidget, \
|
||||
QHBoxLayout, QLabel, QVBoxLayout, QGraphicsProxyWidget
|
||||
|
||||
|
||||
__Author__ = "By: Irony.\"[讽刺]\nQQ: 892768447\nEmail: 892768447@qq.com"
|
||||
|
@ -23,14 +25,86 @@ __Copyright__ = "Copyright (c) 2017 Irony.\"[讽刺]"
|
|||
__Version__ = "Version 1.0"
|
||||
|
||||
|
||||
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, points):
|
||||
self.titleLabel.setText(title)
|
||||
for serie, point in points:
|
||||
if serie not in self.Cache:
|
||||
item = ToolTipItem(
|
||||
serie.color(),
|
||||
(serie.name() or "-") + ":" + str(point.y()), self)
|
||||
self.layout().addWidget(item)
|
||||
self.Cache[serie] = item
|
||||
else:
|
||||
self.Cache[serie].setText(
|
||||
(serie.name() or "-") + ":" + str(point.y()))
|
||||
|
||||
|
||||
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, points, pos):
|
||||
self.setGeometry(QRectF(pos, self.size()))
|
||||
self.tipWidget.updateUi(title, points)
|
||||
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) # 抗锯齿
|
||||
# 自定义x轴label
|
||||
self.category = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
|
||||
self.initChart()
|
||||
|
||||
# 提示widget
|
||||
self.toolTipWidget = GraphicsProxyWidget(self._chart)
|
||||
|
||||
# line
|
||||
self.lineItem = QGraphicsLineItem(self._chart)
|
||||
pen = QPen(Qt.gray)
|
||||
|
@ -39,29 +113,110 @@ class ChartView(QChartView):
|
|||
self.lineItem.setZValue(998)
|
||||
self.lineItem.hide()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
super(ChartView, self).mouseMoveEvent(event)
|
||||
# 一些固定计算,减少mouseMoveEvent中的计算量
|
||||
# 获取x和y轴的最小最大值
|
||||
axisX, axisY = self._chart.axisX(), self._chart.axisY()
|
||||
min_x, max_x = axisX.min(), axisX.max()
|
||||
min_y, max_y = axisY.min(), axisY.max()
|
||||
self.min_x, self.max_x = axisX.min(), axisX.max()
|
||||
self.min_y, self.max_y = axisY.min(), axisY.max()
|
||||
# 坐标系中左上角顶点
|
||||
point_top = self._chart.mapToPosition(QPointF(min_x, max_y))
|
||||
self.point_top = self._chart.mapToPosition(
|
||||
QPointF(self.min_x, self.max_y))
|
||||
# 坐标原点坐标
|
||||
point_bottom = self._chart.mapToPosition(QPointF(min_x, min_y))
|
||||
step_x = (max_x - min_x) / (axisX.tickCount() - 1)
|
||||
self.point_bottom = self._chart.mapToPosition(
|
||||
QPointF(self.min_x, self.min_y))
|
||||
self.step_x = (self.max_x - self.min_x) / (axisX.tickCount() - 1)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
super(ChartView, self).mouseMoveEvent(event)
|
||||
pos = event.pos()
|
||||
# 把鼠标位置所在点转换为对应的xy值
|
||||
x = self._chart.mapToValue(event.pos()).x()
|
||||
index = round((x - min_x) / step_x)
|
||||
x = self._chart.mapToValue(pos).x()
|
||||
y = self._chart.mapToValue(pos).y()
|
||||
index = round((x - self.min_x) / self.step_x)
|
||||
# 得到在坐标系中的所有series的类型和点
|
||||
points = [(serie, serie.at(index))
|
||||
for serie in self._chart.series() if self.min_x <= x <= self.max_x and self.min_y <= y <= self.max_y]
|
||||
if points:
|
||||
pos_x = self._chart.mapToPosition(
|
||||
QPointF(index * step_x + min_x, min_y))
|
||||
self.lineItem.setLine(pos_x.x(), point_top.y(),
|
||||
pos_x.x(), point_bottom.y())
|
||||
QPointF(index * self.step_x + self.min_x, self.min_y))
|
||||
self.lineItem.setLine(pos_x.x(), self.point_top.y(),
|
||||
pos_x.x(), self.point_bottom.y())
|
||||
self.lineItem.show()
|
||||
try:
|
||||
title = self.category[index]
|
||||
except:
|
||||
title = ""
|
||||
t_width = self.toolTipWidget.width()
|
||||
t_height = self.toolTipWidget.height()
|
||||
# 如果鼠标位置离右侧的距离小于tip宽度
|
||||
x = pos.x() - t_width if self.width() - \
|
||||
pos.x() - 20 < t_width else pos.x()
|
||||
# 如果鼠标位置离底部的高度小于tip高度
|
||||
y = pos.y() - t_height if self.height() - \
|
||||
pos.y() - 20 < t_height else pos.y()
|
||||
self.toolTipWidget.show(
|
||||
title, points, QPoint(x, y))
|
||||
else:
|
||||
self.toolTipWidget.hide()
|
||||
self.lineItem.hide()
|
||||
|
||||
def handleMarkerClicked(self):
|
||||
marker = self.sender() # 信号发送者
|
||||
if not marker:
|
||||
return
|
||||
visible = not marker.series().isVisible()
|
||||
# # 隐藏或显示series
|
||||
marker.series().setVisible(visible)
|
||||
marker.setVisible(True) # 要保证marker一直显示
|
||||
# 透明度
|
||||
alpha = 1.0 if visible else 0.4
|
||||
# 设置label的透明度
|
||||
brush = marker.labelBrush()
|
||||
color = brush.color()
|
||||
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)
|
||||
# 设置画笔透明度
|
||||
pen = marker.pen()
|
||||
color = pen.color()
|
||||
color.setAlphaF(alpha)
|
||||
pen.setColor(color)
|
||||
marker.setPen(pen)
|
||||
|
||||
def handleMarkerHovered(self, status):
|
||||
# 设置series的画笔宽度
|
||||
marker = self.sender() # 信号发送者
|
||||
if not marker:
|
||||
return
|
||||
series = marker.series()
|
||||
if not series:
|
||||
return
|
||||
pen = series.pen()
|
||||
if not pen:
|
||||
return
|
||||
pen.setWidth(pen.width() + (1 if status else -1))
|
||||
series.setPen(pen)
|
||||
|
||||
def handleSeriesHoverd(self, point, state):
|
||||
# 设置series的画笔宽度
|
||||
series = self.sender() # 信号发送者
|
||||
pen = series.pen()
|
||||
if not pen:
|
||||
return
|
||||
pen.setWidth(pen.width() + (1 if state else -1))
|
||||
series.setPen(pen)
|
||||
|
||||
def initChart(self):
|
||||
self._chart = QChart(title="折线图堆叠")
|
||||
self._chart.setAcceptHoverEvents(True)
|
||||
# Series动画
|
||||
self._chart.setAnimationOptions(QChart.SeriesAnimations)
|
||||
dataTable = [
|
||||
["邮件营销", [120, 132, 101, 134, 90, 230, 210]],
|
||||
["联盟广告", [220, 182, 191, 234, 290, 330, 310]],
|
||||
|
@ -75,6 +230,7 @@ class ChartView(QChartView):
|
|||
series.append(j, v)
|
||||
series.setName(series_name)
|
||||
series.setPointsVisible(True) # 显示圆点
|
||||
series.hovered.connect(self.handleSeriesHoverd) # 鼠标悬停
|
||||
self._chart.addSeries(series)
|
||||
self._chart.createDefaultAxes() # 创建默认的轴
|
||||
axisX = self._chart.axisX() # x轴
|
||||
|
@ -83,6 +239,27 @@ class ChartView(QChartView):
|
|||
axisY = self._chart.axisY()
|
||||
axisY.setTickCount(7) # y轴设置7个刻度
|
||||
axisY.setRange(0, 1500) # 设置y轴范围
|
||||
# 自定义x轴
|
||||
axis_x = QCategoryAxis(
|
||||
self._chart, labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue)
|
||||
axis_x.setTickCount(7)
|
||||
axis_x.setGridLineVisible(False)
|
||||
min_x = axisX.min()
|
||||
max_x = axisX.max()
|
||||
step = (max_x - min_x) / (7 - 1) # 7个tick
|
||||
for i in range(0, 7):
|
||||
axis_x.append(self.category[i], min_x + i * step)
|
||||
self._chart.setAxisX(axis_x, self._chart.series()[-1])
|
||||
# chart的图例
|
||||
legend = self._chart.legend()
|
||||
# 设置图例由Series来决定样式
|
||||
legend.setMarkerShape(QLegend.MarkerShapeFromSeries)
|
||||
# 遍历图例上的标记并绑定信号
|
||||
for marker in legend.markers():
|
||||
# 点击事件
|
||||
marker.clicked.connect(self.handleMarkerClicked)
|
||||
# 鼠标悬停事件
|
||||
marker.hovered.connect(self.handleMarkerHovered)
|
||||
self.setChart(self._chart)
|
||||
|
||||
|
||||
|
|
4
PyQtChart练习/charts/line/README.md
Normal file
4
PyQtChart练习/charts/line/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# 折线图
|
||||
|
||||
### [1.折线图堆叠](LineStack.py)
|
||||
![折线图堆叠](ScreenShot/LineStack.gif)
|
BIN
PyQtChart练习/charts/line/ScreenShot/LineStack.gif
Normal file
BIN
PyQtChart练习/charts/line/ScreenShot/LineStack.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 327 KiB |
|
@ -144,9 +144,11 @@ class ChartView(QChartView):
|
|||
self.min_x, self.max_x = axisX.min(), axisX.max()
|
||||
self.min_y, self.max_y = axisY.min(), axisY.max()
|
||||
# 坐标系中左上角顶点
|
||||
self.point_top = self._chart.mapToPosition(QPointF(self.min_x, self.max_y))
|
||||
self.point_top = self._chart.mapToPosition(
|
||||
QPointF(self.min_x, self.max_y))
|
||||
# 坐标原点坐标
|
||||
self.point_bottom = self._chart.mapToPosition(QPointF(self.min_x, self.min_y))
|
||||
self.point_bottom = self._chart.mapToPosition(
|
||||
QPointF(self.min_x, self.min_y))
|
||||
self.step_x = (self.max_x - self.min_x) / (axisX.tickCount() - 1)
|
||||
# self.step_y = (self.max_y - self.min_y) / (axisY.tickCount() - 1)
|
||||
|
||||
|
|
Loading…
Reference in a new issue