如何在Python中无限向下显示图形信息?

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

示例:

我需要显示更多像这样的行(沿红色箭头的方向),以及更多这些方块。除了正方形之外,我还可以使用各种颜色的其他形状,并且对象需要放置在非常特定的坐标中。当我将鼠标悬停在任何这些对象上时,它必须显示与该对象相关的工具提示。显示的文本将呈现为图形对象,而不是可以用鼠标指针选择的文本。基本上,一切都是完全图形化渲染的。
随着生成更多数据,将添加更多行,并且这种情况会无限持续下去。显然需要一个滚动条。
到目前为止,从 Matplotlib 这样的库中,我只知道创建固定大小的图形屏幕的选项。我考虑过 PyQt 小部件,但它似乎没有所需的功能。
我考虑过 HTML(因为无限添加新行很容易)和 JavaScript,但是从 Python 导出数据并在 JavaScript 中加载和解析它太麻烦了。

Python有没有办法实现这样的显示?展示一种有效实现这一目标的方法就足够了。
目的:我创建这个是为了直观地了解某些食物和睡眠不足如何导致健康问题。像这样显示它可以让我看到几周或几个月的模式。这不仅仅是关于绘制点和文本,还在于能够在单击任何图形元素时动态更新其颜色和大小。

更新:
我已经编写了一些代码来创建基本窗口,但无法放大主窗口并且工具提示不起作用。另外,考虑到

setGeometry
的使用方式,我认为我无法无限动态地添加行。我还在想办法。如果你们能帮忙,请帮忙。

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QScrollArea, QLabel, QToolTip
from PyQt5.QtGui import QPainter, QColor, QFont
from PyQt5.QtCore import Qt, QTimer
import random
import datetime


class CustomWidget(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.rows = 200
        self.row_height = 25
        self.scroll_offset = 0

    def initUI(self):
        self.setGeometry(100, 100, 800, 1200)
        self.setWindowTitle('Scrollable Window')
        self.setMouseTracking(True)
        self.setMinimumSize(1000, 1800)  # Adjust the size here        
        self.setFixedWidth(1500)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setFont(QFont('Arial', 10))

        start_row = max(0, int(self.scroll_offset / self.row_height) - 1)
        end_row = min(start_row + int(self.height() / self.row_height) + 2, self.rows)

        for i in range(start_row, end_row):
            row_y = i * self.row_height - self.scroll_offset

            # Displaying datetime at the left
            current_time = datetime.datetime.now() + datetime.timedelta(hours=i)
            time_str = current_time.strftime("%H:%M")
            painter.drawText(8, row_y + 20, time_str)

            # Drawing small randomly colored circles/squares
            for j in range(5):
                random.seed(i * j + j)
                shape = random.choice(["circle", "square"])
                color = QColor(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
                painter.setBrush(color)
                shapeSize = 5
                if shape == "circle":
                    painter.drawEllipse(70 + j * 30, row_y + 10, shapeSize, shapeSize)
                else:
                    painter.drawRect(70 + j * 30, row_y + 10, shapeSize, shapeSize)

    def mouseMoveEvent(self, event):
        for i in range(5):
            if (70 + i * 30) <= event.x() <= (90 + i * 30):
                row = int((event.y() + self.scroll_offset) / self.row_height)
                tooltip_text = ''.join(random.choice('ABCDEFGHIJKLMNOPQRSTUVWXYZ') for _ in range(5))
                QToolTip.showText(event.globalPos(), tooltip_text, self)
                break
        else:
            QToolTip.hideText()

    def wheelEvent(self, event):
        scroll_amount = 20
        self.scroll_offset += -event.angleDelta().y() / 8
        self.scroll_offset = max(0, min(self.scroll_offset, (self.rows * self.row_height) - self.height()))
        self.update()


class ScrollableWidget(QWidget):
    def __init__(self):
        super().__init__()

        layout = QVBoxLayout(self)
        scroll = QScrollArea()
        scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

        content = CustomWidget()
        scroll.setWidget(content)
        # Change the size of the content widget
        #content.setMinimumSize(1000, 1800)  # Adjust the size here        

        layout.addWidget(scroll)
        self.setLayout(layout)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = ScrollableWidget()
    ex.show()
    sys.exit(app.exec_())
python matplotlib pyqt
1个回答
0
投票

使用 QScrollArea 的一种可能性是执行类似于 Qt 项视图对提供

fetchMore()
功能的模型所做的操作。

项目视图的作用是在滚动条达到最大值时询问模型是否有更多项目可用,这也可以在视图大小调整时间接发生(没有

valueChanged
信号发射)。

解决方案是创建类似的行为并最终在需要时添加项目。

以下示例显示了使用自定义小部件的基本解决方案,该小部件在随机时间显示“事件”。请注意,事件位置使用任意值,为了获得更一致的解决方案,您应该使用适当的集中管理来考虑字体指标和样式指标。

class TimeWidget(QWidget):
    types = 'square', 'circle'
    cache = None
    def __init__(self):
        super().__init__()
        self.setMinimumSize(600, 16)
        self.values = []
        for i in range(randint(2, 5)):
            self.values.append((
                randrange(1440), 
                choice(self.types), 
                QColor(randrange(256), randrange(256), randrange(256))
            ))
        self.values.sort(key=lambda i: i[0])

    def event(self, event):
        if event.type() == event.ToolTip:
            pos = event.pos()
            for i, (rect, *args) in enumerate(self.cache):
                if rect.contains(pos):
                    time, shape, color = self.values[i]
                    h, m = divmod(time, 60)
                    text = '{} at {:02}:{}'.format(
                        shape, h, m
                    )
                    QToolTip.showText(event.globalPos(), text, self, rect)
                    break
        return super().event(event)

    def updateCache(self):
        size = max(12, self.height() - 4)
        margin = size // 2
        r = QRect(-margin, (self.height() - size) // 2, size, size)
        self.cache = []
        extent = self.width() - size
        for time, shape, color in self.values:
            x = int(margin + time / 1440 * extent)
            if shape == 'circle':
                func = QPainter.drawEllipse
            else:
                func = QPainter.drawRect
            self.cache.append((
                r.translated(x, 0), color, func
            ))

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.updateCache()

    def paintEvent(self, event):
        if not self.values:
            return
        if self.values and not self.cache:
            self.updateCache()
        qp = QPainter(self)
        qp.setPen(Qt.NoPen)
        for rect, color, func in self.cache:
            qp.setBrush(color)
            func(qp, rect)


class DateSlot(QWidget):
    def __init__(self, date):
        super().__init__()
        self.date = date

        self.timeLabel = QLabel(date.strftime("%Y-%m-%d"))
        self.timeWidget = TimeWidget()

        layout = QHBoxLayout(self)
        layout.addWidget(self.timeLabel)
        layout.addWidget(self.timeWidget, stretch=1)


class InfiniteScroll(QScrollArea):
    fetchAmount = 5
    def __init__(self):
        super().__init__()
        self.setWidgetResizable(True)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)

        self.content = QWidget()
        self.layout = QVBoxLayout(self.content)
        self.layout.setAlignment(Qt.AlignTop)
        self.layout.setSpacing(1)
        
        self.setWidget(self.content)
        self.items = []

        self.verticalScrollBar().valueChanged.connect(self.fetchMore)

    def addItem(self):
        if self.items:
            date = self.items[-1].date + datetime.timedelta(days=1)
        else:
            date = datetime.datetime.now()
        item = DateSlot(date)
        self.items.append(item)
        self.layout.addWidget(item)
        return item

    def fetchMore(self):
        if self.verticalScrollBar().maximum() == self.verticalScrollBar().value():
            if self.items:
                for i in range(self.fetchAmount):
                    self.addItem()
            else:
                baseHeight = self.addItem().sizeHint().height()
                maxHeight = self.viewport().height()
                count = maxHeight // (baseHeight + self.layout.spacing())
                for i in range(count):
                    self.addItem()

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.fetchMore()

    def sizeHint(self):
        return QSize(640, 480)

另一种选择是使用实际的 QTableView 和实际实现

canFetchMore
fetchMore
的自定义模型。这种方法的好处是,您可以拥有日期和时间的标题,然后您只需要一个自定义项目委托以及表格跨越:这样,每一行只有一个项目,并且委托最终将绘制“基于分配给左上角项目的矩形(跨越整行),事件”位于正确的位置。

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