如何在没有Matplotlib或PyQtChart等库的情况下用Python实现图表?

问题描述 投票:0回答:2

我正在开始一个编程项目作为我学习的一部分。我的项目将是一个数据可视化程序,它必须至少能够可视化或“绘制”折线图以及可能的其他图表,例如条形图或饼图。但是,我不允许使用使此操作变得简单的 Python 库(例如 Matplotlib、QtDesigner、NumPy 或 PyQtChart)。可视化应该使用 PyQt 完成。

这个项目对我来说似乎真的很难,因为我对编程还很陌生。我想知道是否有人有类似编程项目的经验或提示来帮助我完成该项目。预先感谢!

另外,如果这不是 StackOverflow 的相关问题,请告诉我,我会将其删除。

python pyqt data-visualization
2个回答
3
投票

考虑到这些限制,唯一剩下的解决方案是通过QPainter使用自定义小部件绘画,或者使用图形视图框架,它更强大,但也更复杂。

在下面的两个简单示例中,您可以看到两种方法之间的一些差异,它们将显示相同的数据。

QWidget 子类化通常要简单得多,因为您可以直接绘制基本形状(包括椭圆形、弧形和饼形)。你只需要实现

paintEvent
(记住,小部件上的QPainter只能发生在绘画事件中!你不能自己调用paintEvent())在小部件上构造一个QPainter,然后开始绘图。

这种简单性的缺点是,如果您需要更高的模块化性和更好的控制,则可能会导致修改更加麻烦。

图形视图(这正是 QtCharts 使用的)允许更高的可扩展性,因为它提供了面向对象的框架(使用 QGraphicsItem 子类),因此允许操作现有数据,因此您可以更轻松地添加/删除条形图/饼图等;不幸的是,它既强大又难以相处,需要长期的学习和实验才能
开始真正掌握它。

from PyQt5 import QtCore, QtGui, QtWidgets from random import random, randrange class ChartWidget(QtWidgets.QWidget): def __init__(self, data): super().__init__() self.data = data self.setMinimumSize(480, 320) def paintEvent(self, event): if not self.data: return columnSpace = self.width() / len(self.data) columnWidth = columnSpace * .6 qp = QtGui.QPainter(self) labelHeight = self.fontMetrics().height() columnHeight = self.height() - labelHeight for i, (value, color) in enumerate(self.data): height = columnHeight * value x = i * columnSpace bar = QtCore.QRect(x, columnHeight - height, columnWidth, height) qp.setBrush(color) qp.drawRect(bar) labelRect = QtCore.QRect(x, columnHeight, columnSpace, labelHeight) qp.drawText(labelRect, QtCore.Qt.AlignLeft, 'Value {}'.format(i + 1)) class ChartView(QtWidgets.QGraphicsView): def __init__(self, data): super().__init__() scene = QtWidgets.QGraphicsScene() self.setScene(scene) self.setRenderHint(QtGui.QPainter.Antialiasing) pen = QtGui.QPen(QtCore.Qt.black, 1) pen.setCosmetic(True) for i, (value, color) in enumerate(data): # for simplicity, I created rectangles from the *bottom* bar = scene.addRect(QtCore.QRectF(0, 0, 600, -value * 1000)) bar.setX(i * 1000) bar.setPen(pen) bar.setBrush(color) label = scene.addSimpleText('Value {}'.format(i + 1)) label.setFlags(QtWidgets.QGraphicsItem.ItemIgnoresTransformations) label.setX(i * 1000) def resizeEvent(self, event): super().resizeEvent(event) self.fitInView(self.sceneRect().adjusted(-10, -10, 10, 10)) import sys app = QtWidgets.QApplication(sys.argv) data = [] for i in range(randrange(5, 10)): data.append(( random(), QtGui.QColor(randrange(255), randrange(255), randrange(255)))) test = QtWidgets.QWidget() layout = QtWidgets.QHBoxLayout(test) layout.addWidget(ChartWidget(data)) layout.addWidget(ChartView(data)) test.show() sys.exit(app.exec_())
一些注意事项:

    小部件可以有孩子;您可以为单个“栏”创建一个基本的小部件类,然后将它们添加到具有布局的另一个小部件中;
  • 上述实现中的饼图有点棘手;另请注意,QPainter 使用 1/16 度的角度单位;
  • 与为条形图创建 QWidget 类类似,您可以为图形视图创建 QGraphicsRectItem 子类,而不是使用上面的便利函数
  • addRect()
  • 请记住,
  • 所有图形项最初都有 0x0 坐标,这还包括在 (100, 100) 处创建一个矩形,这是一个 positioned 位于 0x0 的矩形项,但它显示矩形位于 100x100 relative 到该位置;

0
投票
谢谢音乐!

我在运行此程序时遇到了一些抱怨,

QRectF/QRect(int, int, int, int): argument has unexpected type 'float'


将 QRectF/QRect 行更改为:

bar = QtCore.QRect(*[int(x), int(columnHeight - height), int(columnWidth), int(height)]) labelRect = QtCore.QRect(int(x), int(columnHeight), int(columnSpace), int(labelHeight))
代码完美运行。

© www.soinside.com 2019 - 2024. All rights reserved.