#!/usr/bin/env python # -*- coding: utf-8 -*- ''' Created on 2017年12月18日 @author: Irony."[讽刺] @site: http://alyl.vip, http://orzorz.vip, http://coding.net/u/892768447, http://github.com/892768447 @email: 892768447@qq.com @file: ChartView @description: ''' import json import os 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 import chardet __version__ = "0.0.1" # 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轴