一、QDateTimeAxis简介

1. 官方描述

https://doc.qt.io/qtforpython-6/PySide6/QtCharts/QDateTimeAxis.html

QDateTimeAxis可以用作带有刻度线、网格线以及阴影的轴。可以通过设置适当的日期时间格式来配置标签。QDateTimeAxis有效的时间范围为4714 BCE(公元前4714)到287396 CE(公元287396)。对于其他有关于QDateTime的限制,请参考QDateTime的官方文档。

1.1 属性

1.2 信号

1.3 使用方法

QDateTimeAxis可以与所有QXYSeries(QScatterSeries, QLineSeries, QSplineSeries)搭配使用。使用时,通过调用toMSecsSinceEpoch()方法向series中加点。

2. 官方用例

https://doc.qt.io/qtforpython-6/overviews/qtcharts-datetimeaxis-example.html

2.1 创建折线图

为了创建折线图,我们需要使用QLineSeries对象:

series = QLineSeries()

在图表中,我们将展示太阳黑子的数量随时间变化的情况,该数据从文本文件中读入。数据来源于Space Weather Prediction Center。

在下面的代码片段中,请注意如何使用toMSecsSinceEpoch()方法将QDateTime对象转换为可以传递给QLineSeries.append()方法的数字类型【译注:相当于整数型或浮点型时间戳】。

# 获取数据,存入series
# data from http://www.swpc.noaa.gov/ftpdir/weekly/RecentIndices.txt
# http://www.swpc.noaa.gov/ftpdir/weekly/README
# http://www.weather.gov/disclaimer
sunSpots = QFile(":sun_spots")
if not sunSpots.open(QIODevice.ReadOnly | QIODevice.Text):
    m_loadError = "Failed to load '%1' file.".arg(sunSpots.fileName())
    return False

stream = QTextStream(sunSpots)
while not stream.atEnd():
    line = stream.readLine()
    if line.startsWith("#") or line.startsWith(":"):
        continue
    values = line.split(' ', Qt.SkipEmptyParts)
    momentInTime = QDateTime()
    momentInTime.setDate(QDate(values[0].toInt(), values[1].toInt() , 15))
    # 下面这句为官方给出的语句,会报错 OverflowError: Python int too large to convert to C long
    # 请参考 “三、问题与总结” 中的内容
    # series.append(momentInTime.toMSecsSinceEpoch(), values[2].toDouble())

    # 解决方式如下
    series.append(numpy.int64(momentInTime.toMSecsSinceEpoch()), values[2].toDouble())

sunSpots.close()

为了在图表上显示数据,我们需要QChart实例。我们向其中添加series,隐藏图例(legend),创建默认轴,并设置图表的标题。

chart = QChart()
chart.addSeries(series)
chart.legend().hide()
chart.setTitle("Sunspots count (by Space Weather Prediction Center)")

由于我们使用的是QLineSeries对象,调用createDefaultAxes()方法将创建QValueAxis对象作为X轴和Y轴。要使用QDateTimeAxis,我们需要将它手动设置到图表中【意思是,不能图省事,直接调用 createDefaultAxes() 方法了。其中 createDefaultAxes() 方法会根据 QChart 对象已绑定的 QAbstractSeries 类型自动重新创建合适的坐标轴】。

首先,创建QDateTimeAxis的实例,然后设置要显示的刻度数。太阳黑子数量的含义是某月的平均值,因此,轴标签中无需包含时间(time)和日号(day)的信息,可以通过设置自定义标签格式来实现只展示年月。

更多自定义标签格式请参考QDateTime.toString()方法文档来了解可用的格式选项。
【译注:以下代码中,MMM表示缩写的本地化月份名称(e.g. 'Jan' to 'Dec')。可在 QDateTime.toString() 方法文档中查阅】

axisX = QDateTimeAxis()
axisX.setTickCount(10)
axisX.setFormat("MMM yyyy")
axisX.setTitleText("Date")
chart.addAxis(axisX, Qt.AlignBottom)
series.attachAxis(axisX)
axisY = QValueAxis()
axisY.setLabelFormat("%i")
axisY.setTitleText("Sunspots count")
chart.addAxis(axisY, Qt.AlignLeft)
series.attachAxis(axisY)

然后我们创建一个QChartView对象,将chart作为参数。这样我们就不需要自己创建QGraphicsView场景了。我们还设置了抗锯齿功能,让渲染后的线条看起来更漂亮。

createDefaultChartView(chart)

现在可以展示图表了。

二、实践

1. 用例说明

在同一个QChart中显示两条折线,其中x轴为QDateTimeAxis类型。

【PySide6】QChart笔记(一)—— 用QDateTimeAxis作为x轴绘制多条折线图-LMLPHP

2. 代码实现

from PySide6.QtCharts import QChart, QChartView, QLineSeries, QDateTimeAxis, QValueAxis
from PySide6.QtGui import QPainter
from PySide6.QtCore import Qt, QDateTime
from PySide6.QtWidgets import QApplication, QMainWindow
import numpy as np

app = QApplication([])
window = QMainWindow()
chart = QChart()
chart.setTitle("Two Lines Chart")

# 准备数据
axisX_date = [QDateTime.currentDateTime().addDays(i) for i in range(5)]
axisY_value1 = [10 - 2 * i for i in range(5)]
axisY_value2 = [5 + i * (-1) ** i for i in range(5)]

series1 = QLineSeries()
for i in range(5):
    series1.append(np.int64(axisX_date[i].toMSecsSinceEpoch()), axisY_value1[i])

series2 = QLineSeries()
for i in range(5):
    series2.append(np.int64(axisX_date[i].toMSecsSinceEpoch()), axisY_value2[i])

# 将series添加到chart中
chart.addSeries(series1)
chart.addSeries(series2)

# 创建x轴
axisX = QDateTimeAxis()
axisX.setFormat("yyyy/MM/dd")
axisX.setTitleText("Date")
axisX.setTickCount(5)
axisX.setRange(QDateTime.currentDateTime(), QDateTime.currentDateTime().addDays(4))
# 将x轴与chart和series绑定
chart.addAxis(axisX, Qt.AlignBottom)
series1.attachAxis(axisX)
series2.attachAxis(axisX)

# 创建y轴
axisY = QValueAxis()
axisY.setTitleText("Value")
axisY.setRange(0, 10)
# 将y轴与chart和series绑定
chart.addAxis(axisY, Qt.AlignLeft)
series1.attachAxis(axisY)
series2.attachAxis(axisY)

# 显示图表
chartView = QChartView(chart)
chartView.setRenderHint(QPainter.Antialiasing)
window.setCentralWidget(chartView)
window.show()
app.exec()

三、问题与总结

1. OverflowError: Python int too large to convert to C long

问题描述

根据官方文档的指引,将QDateTime对象加入QChart图表的series时,需要用toMSecsSinceEpoch()方法转换为数值型,C++中完全没有问题,但在Python中却会出现该错误。

解决方法

通过numpy.int64()将调用toMSecsSinceEpoch()后过大的值转换为numpy的Int64类型,然后再传给QLineSeries.append()方法即可。即调用:

numpy.int64(QDateTime().toMSecsSinceEpoch())

2. 更新series后,图像或坐标轴缺失

问题描述

修改series中的点集后,重新绘制QChart时,出现仅显示折线而不显示坐标轴、或仅显示坐标轴而不显示折线图的情况。

解决方法

其原因是,绘制图像时,各对象的创建、绑定顺序不正确,同时坐标轴也需要重新创建。应当遵循如下顺序:

  • 清除 QXYseries 对象中的旧数据,即调用QXYseries.clear()
  • 将数据写入 QXYseries 对象, 即调用QXYseries.append()
  • 【仅需一次】将 QXYseries 对象与 QChart 对象绑定,即调用QChart.addSeries()
  • 删除旧坐标轴 QAbstractAxis 对象,并重新创建
  • 将新 QAbstractAxis 对象与 QChart 对象绑定,即调用QChart.addAxis()
  • 将新 QAbstractAxis 对象与 QXYseries 对象绑定,即调用QXYseries.attachAxis()

对于一般的坐标轴类型,可直接调用 QChart.createDefaultAxes() 方法,相当于直接完成了后面三步。而对于 QDateTimeAxis 类型的坐标轴,需要自行实现类似的方法。一个例子:

    def createDateTimeAxis(self, x_range, y_range):
        """
        Describe: 更新series后需要重新创建坐标轴标轴,这样才能展示出新的series;
                  又因为x轴类型为QDateTimeAxis,无法直接调用QChart.createDefaultAxes()方法创建坐标轴,
                  因此需要自行实现该方法

        Args:
            x_range: tuple[QDateTime]
                x轴范围
            y_range: tuple[float]
                y轴范围
        """
        # 先删除旧坐标轴
        self.removeAxis(self._axisX)
        self.removeAxis(self._axisY)
        # 创建x轴
        self._axisX = QDateTimeAxis()
        self._axisX.setRange(x_range[0], x_range[1])
        self._axisX.setFormat("yyyy/MM")
        # 创建y轴
        self._axisY = QValueAxis()
        self._axisY.setRange(y_range[0], y_range[1])
        # 将新坐标轴与QChart和series绑定
        self.addAxis(self._axisX, Qt.AlignBottom)
        self.addAxis(self._axisY, Qt.AlignLeft)
        for series in self.list_series_line:
            series.attachAxis(self._axisX)
            series.attachAxis(self._axisY)

3. 同一QChart显示多条折线时,其中一条显示不完全

问题描述

同一QChart显示多条折线时,y轴的范围限定在第一条折线series点集的数值范围中,导致另一条显示不完全。

解决方法

每次更新series后,在自定义的createDateTimeAxis()方法中,通过形参y_range,传入合适的范围。

11-06 06:00