【问题标题】:How to prevent real time graph from squeezing, Python3-Matplotlib如何防止实时图形被挤压,Python3-Matplotlib
【发布时间】:2021-10-18 02:10:39
【问题描述】:

我的程序连续(如实时)生成数据,然后用图表绘制它。但是过了一会儿,它把图形从屏幕的左边挤到了右边。

我怎样才能防止它但能够滚动查看过去的结果?


import random
import pandas as pd
import numpy as np

from datetime import datetime,timedelta
from dateutil.relativedelta import relativedelta
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation as funca


#plt.style.use('fivethirtyeight')
plt.rcParams.update({'font.size':9})

limit = 10000
datetime_format = '%Y-%m-%d %I:%M-%p'
date_string = '2021-01-01 12:10-am'

#here I'm creating a dataframe, to simulate a company income over time

df = pd.DataFrame({"DATE":pd.to_datetime([],format=datetime_format),"INCOME":np.array([],dtype=np.int64)})
start_date = datetime.strptime(date_string,datetime_format)

#This will add +1 month for each frame of the graph animation.
incrementing = relativedelta(years=+0,months=+1,days=+0,hours=+0,minutes=+0)

#This function is responsible for the animation
def animating_graph(i):

    #dataframe related code
    global df
    global limit
    global start_date
    global incrementing
    datetime_quantity = len(df['DATE'])
    INDEX = np.arange(datetime_quantity)

    #Graph related code

    plt.cla()
    plt.plot(INDEX,df['INCOME'],linestyle='-',linewidth=2,marker='o',label='Income')
#   plt.xticks(ticks=INDEX,labels=df['DATE'].dt.date)

   #it will mark an area<=limit, which represents lost of money
    plt.fill_between(INDEX,df['INCOME'],limit,where=(df['INCOME']<=limit),interpolate=True,alpha=0.5,label='Loss of Income')
    plt.title('INCOME OVER TIME')
    plt.xlabel('Income over each month')
    plt.ylabel('Income(USD)')

    #here I am appending to df each month that passed, and the income(random number)
    df = df.append({'DATE':start_date,'INCOME':random.randint(5000,30000)},ignore_index=True,sort=False)
    start_date = start_date + incrementing


animating = funca(plt.gcf(),animating_graph,interval=1000)
plt.tight_layout()
plt.grid(False)
plt.show()

编辑:我找到了我想要的这个解决方案(不是我的),但没有向左或向右滚动的选项。

import sys
import os
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import functools
import numpy as np
import random as rd
import matplotlib
matplotlib.use("Qt5Agg")
from matplotlib.figure import Figure
from matplotlib.animation import TimedAnimation
from matplotlib.lines import Line2D
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import time
import threading

class CustomMainWindow(QMainWindow):
    def __init__(self):
        super(CustomMainWindow, self).__init__()
        # Define the geometry of the main window
        self.setGeometry(300, 300, 800, 400)
        self.setWindowTitle("my first window")
        # Create FRAME_A
        self.FRAME_A = QFrame(self)
        self.FRAME_A.setStyleSheet("QWidget { background-color: %s }" % QColor(210,210,235,255).name())
        self.LAYOUT_A = QGridLayout()
        self.FRAME_A.setLayout(self.LAYOUT_A)
        self.setCentralWidget(self.FRAME_A)
        # Place the zoom button
        self.zoomBtn = QPushButton(text = 'zoom')
        self.zoomBtn.setFixedSize(100, 50)
        self.zoomBtn.clicked.connect(self.zoomBtnAction)
        self.LAYOUT_A.addWidget(self.zoomBtn, *(0,0))
        # Place the matplotlib figure
        self.myFig = CustomFigCanvas()
        self.LAYOUT_A.addWidget(self.myFig, *(0,1))
        # Add the callbackfunc to ..
        myDataLoop = threading.Thread(name = 'myDataLoop', target = dataSendLoop, daemon = True, args = (self.addData_callbackFunc,))
        myDataLoop.start()
        self.show()
        return

    def zoomBtnAction(self):
        print("zoom in")
        self.myFig.zoomIn(5)
        return

    def addData_callbackFunc(self, value):
        # print("Add data: " + str(value))
        self.myFig.addData(value)
        return

''' End Class '''


class CustomFigCanvas(FigureCanvas, TimedAnimation):
    def __init__(self):
        self.addedData = []
        print(matplotlib.__version__)
        # The data
        self.xlim = 200
        self.n = np.linspace(0, self.xlim - 1, self.xlim)
        a = []
        b = []
        a.append(2.0)
        a.append(4.0)
        a.append(2.0)
        b.append(4.0)
        b.append(3.0)
        b.append(4.0)
        self.y = (self.n * 0.0) + 50
        # The window
        self.fig = Figure(figsize=(5,5), dpi=100)
        self.ax1 = self.fig.add_subplot(111)
        # self.ax1 settings
        self.ax1.set_xlabel('time')
        self.ax1.set_ylabel('raw data')
        self.line1 = Line2D([], [], color='blue')
        self.line1_tail = Line2D([], [], color='red', linewidth=2)
        self.line1_head = Line2D([], [], color='red', marker='o', markeredgecolor='r')
        self.ax1.add_line(self.line1)
        self.ax1.add_line(self.line1_tail)
        self.ax1.add_line(self.line1_head)
        self.ax1.set_xlim(0, self.xlim - 1)
        self.ax1.set_ylim(0, 100)
        FigureCanvas.__init__(self, self.fig)
        TimedAnimation.__init__(self, self.fig, interval = 50, blit = True)
        return

    def new_frame_seq(self):
        return iter(range(self.n.size))

    def _init_draw(self):
        lines = [self.line1, self.line1_tail, self.line1_head]
        for l in lines:
            l.set_data([], [])
        return

    def addData(self, value):
        self.addedData.append(value)
        return

    def zoomIn(self, value):
        bottom = self.ax1.get_ylim()[0]
        top = self.ax1.get_ylim()[1]
        bottom += value
        top -= value
        self.ax1.set_ylim(bottom,top)
        self.draw()
        return

    def _step(self, *args):
        # Extends the _step() method for the TimedAnimation class.
        try:
            TimedAnimation._step(self, *args)
        except Exception as e:
            self.abc += 1
            print(str(self.abc))
            TimedAnimation._stop(self)
            pass
        return

    def _draw_frame(self, framedata):
        margin = 2
        while(len(self.addedData) > 0):
            self.y = np.roll(self.y, -1)
            self.y[-1] = self.addedData[0]
            del(self.addedData[0])

        self.line1.set_data(self.n[ 0 : self.n.size - margin ], self.y[ 0 : self.n.size - margin ])
        self.line1_tail.set_data(np.append(self.n[-10:-1 - margin], self.n[-1 - margin]), np.append(self.y[-10:-1 - margin], self.y[-1 - margin]))
        self.line1_head.set_data(self.n[-1 - margin], self.y[-1 - margin])
        self._drawn_artists = [self.line1, self.line1_tail, self.line1_head]
        return

''' End Class '''


# You need to setup a signal slot mechanism, to
# send data to your GUI in a thread-safe way.
# Believe me, if you don't do this right, things
# go very very wrong..
class Communicate(QObject):
    data_signal = pyqtSignal(float)

''' End Class '''



def dataSendLoop(addData_callbackFunc):
    # Setup the signal-slot mechanism.
    mySrc = Communicate()
    mySrc.data_signal.connect(addData_callbackFunc)

    # Simulate some data
    n = np.linspace(0, 499, 500)
    y = 50 + 25*(np.sin(n / 8.3)) + 10*(np.sin(n / 7.5)) - 5*(np.sin(n / 1.5))
    i = 0

    while(True):
        if(i > 499):
            i = 0
        time.sleep(0.1)
        mySrc.data_signal.emit(y[i]) # <- Here you emit a signal!
        i += 1
    ###
###

if __name__== '__main__':
    app = QApplication(sys.argv)
    QApplication.setStyle(QStyleFactory.create('Plastique'))
    myGUI = CustomMainWindow()
    sys.exit(app.exec_())

【问题讨论】:

    标签: python pandas dataframe matplotlib animation


    【解决方案1】:

    您可以设置matplotlib.widgets.RangeSlider 来控制要显示的数据框的下限和上限。一个例子可能是:

    fig, ax = plt.subplots()
    plt.subplots_adjust(left = 0.1, top = 0.75, bottom = 0.1, right = 0.9)
    ax_slider = plt.axes([0.1, 0.85, 0.8, 0.1])
    slider = RangeSlider(ax = ax_slider, label = 'Period', valmin = 0, valmax = 1, valinit = (0, 1))
    

    此滑块有一个最小值和最大值,您可以使用它们来定义要绘制的数据帧的开始和停止索引:

    start, stop = slider.val
    INDEX = np.arange(int(start*datetime_quantity), int(stop*datetime_quantity))
    ax.plot(INDEX,df.loc[INDEX, 'INCOME'],linestyle='',linewidth=2,marker='o',label='Income')
    ax.fill_between(INDEX,df.loc[INDEX, 'INCOME'],limit,where=(df.loc[INDEX, 'INCOME']<=limit),interpolate=True,alpha=0.5,label='Loss of Income')
    

    如果滑块值为(0, 1),则将绘制整个数据框;如果滑块值为(0.2, 0.6),则只会绘制从 20% 到 60% 的数据帧切片,依此类推。
    由于您的数据框会在每次迭代中增长,因此切片 (start, stop) 也会在每次迭代中发生变化。

    完整代码

    import random
    import pandas as pd
    import numpy as np
    
    from datetime import datetime,timedelta
    from dateutil.relativedelta import relativedelta
    from matplotlib import pyplot as plt
    from matplotlib.animation import FuncAnimation as funca
    from matplotlib.widgets import RangeSlider
    
    
    #plt.style.use('fivethirtyeight')
    plt.rcParams.update({'font.size':9})
    
    limit = 10000
    datetime_format = '%Y-%m-%d %I:%M-%p'
    date_string = '2021-01-01 12:10-am'
    
    #here I'm creating a dataframe, to simulate a company income over time
    df = pd.DataFrame({"DATE":pd.to_datetime([],format=datetime_format),"INCOME":np.array([],dtype=np.int64)})
    start_date = datetime.strptime(date_string,datetime_format)
    
    #This will add +1 month for each frame of the graph animation.
    incrementing = relativedelta(years=+0,months=+1,days=+0,hours=+0,minutes=+0)
    
    #This function is responsible for the animation
    def animating_graph(i):
    
        #dataframe related code
        global df
        global limit
        global start_date
        global incrementing
        datetime_quantity = len(df['DATE'])
    
        start, stop = slider.val
        INDEX = np.arange(int(start*datetime_quantity), int(stop*datetime_quantity))
    
        #Graph related code
        ax.cla()
        ax.plot(INDEX,df.loc[INDEX, 'INCOME'],linestyle='-',linewidth=2,marker='o',label='Income')
    
       #it will mark an area<=limit, which represents lost of money
        ax.fill_between(INDEX,df.loc[INDEX, 'INCOME'],limit,where=(df.loc[INDEX, 'INCOME']<=limit),interpolate=True,alpha=0.5,label='Loss of Income')
        ax.set_title('INCOME OVER TIME')
        ax.set_xlabel('Income over each month')
        ax.set_ylabel('Income(USD)')
    
        #here I am appending to df each month that passed, and the income(random number)
        df = df.append({'DATE':start_date,'INCOME':random.randint(5000,30000)},ignore_index=True,sort=False)
        start_date = start_date + incrementing
    
    fig, ax = plt.subplots()
    plt.subplots_adjust(left = 0.1, top = 0.75, bottom = 0.1, right = 0.9)
    ax_slider = plt.axes([0.1, 0.85, 0.8, 0.1])
    slider = RangeSlider(ax = ax_slider, label = 'Period', valmin = 0, valmax = 1, valinit = (0, 1))
    
    animating = funca(fig,animating_graph,interval=1000)
    ax.grid(False)
    
    plt.show()
    

    【讨论】:

    • 您好,很抱歉,我一直在测试您的解决方案并寻找新的解决方案,但最后我会给您应得的荣誉。于是我找到了一种“解决方案”,展示了我想要创建的效果,因为上面的解决方案的效果并不是我想要的。在这个新试验中,它给出了我正在寻找的效果,但我无法滚动查看过去的结果。我会努力的,但我希望你也能看到。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-28
    • 2015-08-19
    • 2021-09-10
    相关资源
    最近更新 更多