PyQt/Test/ChartView/ChartView.py
2021-07-13 14:52:26 +08:00

289 lines
10 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on 2017年12月18日
@author: Irony
@site: https://pyqt.site , https://github.com/PyQt5
@email: 892768447@qq.com
@file: ChartView
@description:
"""
import json
import os
import chardet
from PyQt5.QtChart import QChart, QChartView, QLineSeries, QCategoryAxis
from PyQt5.QtCore import QMargins, Qt, QEasingCurve
from PyQt5.QtGui import QColor, QBrush, QFont, QPainter, QPen, QPixmap
# QEasingCurve 类型枚举
EasingCurve = dict(
[(c, getattr(QEasingCurve, n)) for n, c in QEasingCurve.__dict__.items()
if isinstance(c, QEasingCurve.Type)])
# 动画 类型枚举
AnimationOptions = {
0: QChart.NoAnimation,
1: QChart.GridAxisAnimations,
2: QChart.SeriesAnimations,
3: QChart.AllAnimations
}
class ChartView(QChartView):
def __init__(self, file, parent=None):
super(ChartView, self).__init__(parent)
self._chart = QChart()
self._chart.setAcceptHoverEvents(True)
self.setChart(self._chart)
self.initUi(file)
def initUi(self, file):
if isinstance(file, dict):
return self.__analysis(file)
if isinstance(file, str):
if not os.path.isfile(file):
return self.__analysis(json.loads(file))
with open(file, "rb") as fp:
data = fp.read()
encoding = chardet.detect(data) or {}
data = data.decode(encoding.get("encoding") or "utf-8")
self.__analysis(json.loads(data))
# def onSeriesHoverd(self, point, state):
# print(point, state)
def mouseMoveEvent(self, event):
super(ChartView, self).mouseMoveEvent(event)
# 获取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()
# 把鼠标位置所在点转换为对应的xy值
x = self._chart.mapToValue(event.pos()).x()
y = self._chart.mapToValue(event.pos()).y()
index = round(x) # 四舍五入
print(x, y, index)
# 得到在坐标系中的所有series的类型和点
points = [(s.type(), s.at(index))
for s in self._chart.series() if min_x <= x <= max_x and min_y <= y <= max_y]
print(points)
def __getColor(self, color=None, default=Qt.white):
'''
:param color: int|str|[r,g,b]|[r,g,b,a]
'''
if not color:
return QColor(default)
if isinstance(color, QBrush):
return color
# 比如[r,g,b]或[r,g,b,a]
if isinstance(color, list) and 3 <= len(color) <= 4:
return QColor(*color)
else:
return QColor(color)
def __getPen(self, pen=None, default=QPen(
Qt.white, 1, Qt.SolidLine,
Qt.SquareCap, Qt.BevelJoin)):
'''
:param pen: pen json
'''
if not pen or not isinstance(pen, dict):
return default
return QPen(
self.__getColor(pen.get("color", None) or default.color()),
pen.get("width", 1) or 1,
pen.get("style", 0) or 0,
pen.get("capStyle", 16) or 16,
pen.get("joinStyle", 64) or 64
)
def __getAlignment(self, alignment):
'''
:param alignment: left|top|right|bottom
'''
try:
return getattr(Qt, "Align" + alignment.capitalize())
except:
return Qt.AlignTop
# if alignment == "left":
# return Qt.AlignLeft
# if alignment == "right":
# return Qt.AlignRight
# if alignment == "bottom":
# return Qt.AlignBottom
# return Qt.AlignTop
def __setTitle(self, title=None):
'''
:param title: title json
'''
if not title or not isinstance(title, dict):
return
# 设置标题
self._chart.setTitle(title.get("text", "") or "")
# 设置标题颜色
self._chart.setTitleBrush(self.__getColor(
title.get("color", self._chart.titleBrush()) or self._chart.titleBrush()))
# 设置标题字体
font = QFont(title.get("font", "") or self._chart.titleFont())
pointSize = title.get("pointSize", -1) or -1
if pointSize > 0:
font.setPointSize(pointSize)
font.setWeight(title.get("weight", -1) or -1)
font.setItalic(title.get("italic", False) or False)
self._chart.setTitleFont(font)
def __setAnimation(self, animation=None):
'''
:param value: animation json
'''
if not animation or not isinstance(animation, dict):
return
# 动画持续时间
self._chart.setAnimationDuration(
animation.get("duration", 1000) or 1000)
# 设置动画曲线
self._chart.setAnimationEasingCurve(EasingCurve.get(
animation.get("curve", 10) or 10, None) or QEasingCurve.OutQuart)
# 设置开启何种动画
self._chart.setAnimationOptions(AnimationOptions.get(
animation.get("options", 0) or 0, None) or QChart.NoAnimation)
def __setBackground(self, background=None):
'''
:param background:background json
'''
if not background or not isinstance(background, dict):
return
# 设置是否背景可用
self._chart.setBackgroundVisible(
background.get("visible", True) or True)
# 设置背景矩形的圆角
self._chart.setBackgroundRoundness(
background.get("radius", 0) or 0)
# 设置下拉阴影
self._chart.setDropShadowEnabled(
background.get("dropShadow", True) or True)
# 设置pen
self._chart.setBackgroundPen(self.__getPen(
background.get("pen", None), self._chart.backgroundPen()))
# 设置背景
image = background.get("image", None)
color = background.get("color", None)
if image:
self._chart.setBackgroundBrush(QBrush(QPixmap(image)))
elif color:
self._chart.setBackgroundBrush(self.__getColor(
color, self._chart.backgroundBrush()))
def __setMargins(self, margins=None):
'''
:param margins: margins json
'''
if not margins or not isinstance(margins, dict):
return
left = margins.get("left", 20) or 20
top = margins.get("top", 20) or 20
right = margins.get("right", 20) or 20
bottom = margins.get("bottom", 20) or 20
self._chart.setMargins(QMargins(left, top, right, bottom))
def __setLegend(self, legend=None):
'''
:param legend: legend json
'''
if not legend or not isinstance(legend, dict):
return
_legend = self._chart.legend()
_legend.setAlignment(self.__getAlignment(
legend.get("alignment", None)))
_legend.setShowToolTips(legend.get("showToolTips", True) or True)
def __getSerie(self, serie=None):
if not serie or not isinstance(serie, dict):
return None
types = serie.get("type", "") or ""
data = serie.get("data", []) or []
if not data or not isinstance(data, list):
return None
if types == "line":
_series = QLineSeries(self._chart)
else:
return None
# 设置series名字
_series.setName(serie.get("name", "") or "")
# 添加数据到series中
for index, value in enumerate(data):
# 保证vlaue必须是数字
_series.append(index, value if type(value) in (int, float) else 0)
return _series
def __setSeries(self, series=None):
if not series or not isinstance(series, list):
return
for serie in series:
_serie = self.__getSerie(serie)
if _serie:
# _serie.hovered.connect(self.onSeriesHoverd)
self._chart.addSeries(_serie)
# 创建默认的xy轴
self._chart.createDefaultAxes()
def __setAxisX(self, axisx=None):
if not axisx or not isinstance(axisx, dict):
return
series = self._chart.series()
if not series:
return
types = axisx.get("type", None)
data = axisx.get("data", []) or []
if not data or not isinstance(data, list):
return None
minx = self._chart.axisX().min()
maxx = self._chart.axisX().max()
if types == "category":
xaxis = QCategoryAxis(
self._chart, labelsPosition=QCategoryAxis.AxisLabelsPositionOnValue)
# 隐藏网格
xaxis.setGridLineVisible(False)
# 刻度条数
tickc_d = len(data)
tickc = tickc_d if tickc_d > 1 else self._chart.axisX().tickCount()
xaxis.setTickCount(tickc)
# 强制x轴刻度与新刻度条数一致
self._chart.axisX().setTickCount(tickc)
step = (maxx - minx) / (tickc - 1)
for i in range(min(tickc_d, tickc)):
xaxis.append(data[i], minx + i * step)
self._chart.setAxisX(xaxis, series[-1])
def __analysis(self, datas):
'''
analysis json data
:param datas: json data
'''
# 标题
self.__setTitle(datas.get("title", None))
# 抗锯齿
if (datas.get("antialiasing", False) or False):
self.setRenderHint(QPainter.Antialiasing)
# 主题
self._chart.setTheme(datas.get("theme", 0) or 0)
# 动画
self.__setAnimation(datas.get("animation", None))
# 背景设置
self.__setBackground(datas.get("background", None))
# 边距设置
self.__setMargins(datas.get("margins", None))
# 设置图例
self.__setLegend(datas.get("legend", None))
# 设置series
self.__setSeries(datas.get("series", None))
# 自定义的x轴
self.__setAxisX(datas.get("axisx", None))
# 自定义的y轴