<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Python GUIs - real-time</title><link href="https://www.pythonguis.com/" rel="alternate"/><link href="https://www.pythonguis.com/feeds/real-time.tag.atom.xml" rel="self"/><id>https://www.pythonguis.com/</id><updated>2020-05-07T09:00:00+00:00</updated><subtitle>Create GUI applications with Python and Qt</subtitle><entry><title>PyQtGraph plotting over time — Updating plots in real time with PyQtGraph and PyQt6</title><link href="https://www.pythonguis.com/faq/pyqtgraph-plotting-over-time/" rel="alternate"/><published>2020-05-07T09:00:00+00:00</published><updated>2020-05-07T09:00:00+00:00</updated><author><name>Martin Fitzpatrick</name></author><id>tag:www.pythonguis.com,2020-05-07:/faq/pyqtgraph-plotting-over-time/</id><summary type="html">One of the most common things people want to do with &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-plotting-pyqtgraph/"&gt;PyQtGraph&lt;/a&gt; is display data that updates over time &amp;mdash; like an oscilloscope trace, a live sensor feed, or a streaming signal. The plot scrolls continuously as new data arrives, with old data dropping off the left side.</summary><content type="html">
            &lt;p&gt;One of the most common things people want to do with &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-plotting-pyqtgraph/"&gt;PyQtGraph&lt;/a&gt; is display data that updates over time &amp;mdash; like an oscilloscope trace, a live sensor feed, or a streaming signal. The plot scrolls continuously as new data arrives, with old data dropping off the left side.&lt;/p&gt;
&lt;p&gt;In this tutorial, you'll learn how to create a real-time scrolling plot using PyQtGraph and PyQt6. We'll start with a static plot, then animate it with a &lt;code&gt;QTimer&lt;/code&gt; so it updates on a fixed interval. By the end, you'll have a working template you can adapt for your own live data sources.&lt;/p&gt;
&lt;h2 id="setting-up-a-basic-plot"&gt;Setting up a basic plot&lt;/h2&gt;
&lt;p&gt;Before we animate anything, let's get a simple PyQtGraph plot on screen. If you haven't already installed PyQtGraph, you can do so with pip:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-bash"&gt;bash&lt;/span&gt;
&lt;pre&gt;&lt;code class="bash"&gt;pip install pyqtgraph PyQt6
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Here's a minimal example that creates a window with a plot widget and draws a static line:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6 import QtWidgets
import pyqtgraph as pg
import sys


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.graphWidget = pg.PlotWidget()
        self.setCentralWidget(self.graphWidget)

        x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        y = [30, 32, 34, 32, 33, 31, 29, 32, 35, 45]

        self.graphWidget.plot(x, y)


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this and you'll see a window with a single line plotted from your data. That's your starting point &amp;mdash; now let's make it move.&lt;/p&gt;
&lt;h2 id="updating-the-plot-with-a-qtimer"&gt;Updating the plot with a QTimer&lt;/h2&gt;
&lt;p&gt;To create a scrolling, real-time effect, we need two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;A reference to the plotted line&lt;/strong&gt;, so we can update its data later.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A timer&lt;/strong&gt; that fires at regular intervals and triggers a data update.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;When you call &lt;code&gt;self.graphWidget.plot(...)&lt;/code&gt;, it returns a &lt;code&gt;PlotDataItem&lt;/code&gt; object &amp;mdash; a reference to the line that was just drawn. By holding onto that reference, you can call &lt;code&gt;.setData()&lt;/code&gt; on it later to replace the data with new values. This redraws the line without creating a new one, which is both clean and efficient.&lt;/p&gt;
&lt;p&gt;For the timer, Qt provides &lt;code&gt;QTimer&lt;/code&gt;. You connect its &lt;code&gt;timeout&lt;/code&gt; &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-signals-slots-events/"&gt;signal&lt;/a&gt; to a function, set an interval in milliseconds, and start it. Every time the interval elapses, your function runs.&lt;/p&gt;
&lt;p&gt;Here's how it looks in practice:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6 import QtWidgets, QtCore
import pyqtgraph as pg
import sys
from random import randint


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.graphWidget = pg.PlotWidget()
        self.setCentralWidget(self.graphWidget)

        self.x = list(range(100))  # 100 time points
        self.y = [randint(0, 100) for _ in range(100)]  # 100 data points

        self.graphWidget.setBackground("w")

        pen = pg.mkPen(color=(255, 0, 0))
        self.data_line = self.graphWidget.plot(self.x, self.y, pen=pen)

        self.timer = QtCore.QTimer()
        self.timer.setInterval(50)
        self.timer.timeout.connect(self.update_plot_data)
        self.timer.start()

    def update_plot_data(self):
        self.x = self.x[1:]  # Remove the first x element.
        self.x.append(self.x[-1] + 1)  # Add a new value 1 higher than the last.

        self.y = self.y[1:]  # Remove the first y element.
        self.y.append(randint(0, 100))  # Add a new random value.

        self.data_line.setData(self.x, self.y)  # Update the data.


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Run this and you'll see a red line scrolling across the screen, updating every 50 milliseconds with new random values. It already looks like a live data feed.&lt;/p&gt;
&lt;p&gt;Let's walk through what's happening in &lt;code&gt;update_plot_data&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;self.x = self.x[1:]&lt;/code&gt; &amp;mdash; removes the oldest time point from the front of the list.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;self.x.append(self.x[-1] + 1)&lt;/code&gt; &amp;mdash; adds a new time point at the end, one step beyond the previous last value. This keeps the x-axis advancing forward.&lt;/li&gt;
&lt;li&gt;The same pattern applies to &lt;code&gt;self.y&lt;/code&gt; &amp;mdash; drop the oldest value, add a new one.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;self.data_line.setData(self.x, self.y)&lt;/code&gt; &amp;mdash; redraws the line with the updated data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The lists always stay the same length (100 elements), so the plot shows a fixed-width window of data that slides forward over time. This is the scrolling effect.&lt;/p&gt;
&lt;h2 id="adjusting-the-speed-and-window-size"&gt;Adjusting the speed and window size&lt;/h2&gt;
&lt;p&gt;You can easily tune this to match your needs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Change the update speed&lt;/strong&gt; by adjusting &lt;code&gt;self.timer.setInterval()&lt;/code&gt;. The value is in milliseconds, so &lt;code&gt;50&lt;/code&gt; means 20 updates per second. Use &lt;code&gt;100&lt;/code&gt; for 10 updates/second, or &lt;code&gt;1000&lt;/code&gt; for once per second.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change the visible window size&lt;/strong&gt; by adjusting the initial length of &lt;code&gt;self.x&lt;/code&gt; and &lt;code&gt;self.y&lt;/code&gt;. Using &lt;code&gt;list(range(500))&lt;/code&gt; and 500 data points gives you a wider view of the data history.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, to show 200 data points updating once every 100ms:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;self.x = list(range(200))
self.y = [randint(0, 100) for _ in range(200)]
# ...
self.timer.setInterval(100)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h2 id="using-numpy-for-better-performance"&gt;Using numpy for better performance&lt;/h2&gt;
&lt;p&gt;The example above uses plain Python lists, which works well for moderate data sizes. If you're working with large amounts of data or need fast update rates, using NumPy arrays and &lt;code&gt;np.roll()&lt;/code&gt; can give you a significant performance boost.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;np.roll()&lt;/code&gt; function shifts all elements in an array by a given number of positions, wrapping values from one end to the other. For a scrolling plot, you roll the array by -1 (shifting everything one position to the left) and then overwrite the last element with the new value:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;import numpy as np

data = np.array([10, 20, 30, 40, 50])
data = np.roll(data, -1)
# data is now [20, 30, 40, 50, 10]
data[-1] = 60
# data is now [20, 30, 40, 50, 60]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Here's the full example rewritten with NumPy:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6 import QtWidgets, QtCore
import pyqtgraph as pg
import numpy as np
import sys


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.graphWidget = pg.PlotWidget()
        self.setCentralWidget(self.graphWidget)

        self.x = np.arange(100)  # 100 time points
        self.y = np.random.randint(0, 100, size=100)  # 100 random data points

        self.graphWidget.setBackground("w")

        pen = pg.mkPen(color=(255, 0, 0))
        self.data_line = self.graphWidget.plot(self.x, self.y, pen=pen)

        self.timer = QtCore.QTimer()
        self.timer.setInterval(50)
        self.timer.timeout.connect(self.update_plot_data)
        self.timer.start()

    def update_plot_data(self):
        self.x = np.roll(self.x, -1)
        self.x[-1] = self.x[-2] + 1

        self.y = np.roll(self.y, -1)
        self.y[-1] = np.random.randint(0, 100)

        self.data_line.setData(self.x, self.y)


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The behavior is identical &amp;mdash; a scrolling red line with random data &amp;mdash; but the array operations are faster under the hood. For 100 points, you won't notice a difference. For thousands of points at high update rates, NumPy makes a real difference.&lt;/p&gt;
&lt;h2 id="connecting-to-an-external-data-source"&gt;Connecting to an external data source&lt;/h2&gt;
&lt;p&gt;In the examples so far, we've generated random data inside &lt;code&gt;update_plot_data&lt;/code&gt;. In a real application, your data will come from somewhere else &amp;mdash; a sensor, a file, a network socket, or another part of your program.&lt;/p&gt;
&lt;p&gt;The pattern stays the same: in your timer callback, you fetch the latest value from wherever your data lives, and feed it into the plot. Here's an example that reads from an external data source (simulated here with a simple class):&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6 import QtWidgets, QtCore
import pyqtgraph as pg
import numpy as np
import sys


class DataSource:
    """Simulates an external data source that provides readings."""

    def __init__(self):
        self.offset = 0

    def get_reading(self):
        """Return a simulated sensor reading."""
        self.offset += 0.1
        return 50 + 30 * np.sin(self.offset) + np.random.normal(0, 3)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, data_source):
        super().__init__()

        self.data_source = data_source

        self.graphWidget = pg.PlotWidget()
        self.setCentralWidget(self.graphWidget)

        self.x = np.arange(200)
        self.y = np.zeros(200)

        self.graphWidget.setBackground("w")
        self.graphWidget.setTitle("Live Sensor Data")
        self.graphWidget.setLabel("left", "Amplitude")
        self.graphWidget.setLabel("bottom", "Time")

        pen = pg.mkPen(color=(255, 0, 0), width=2)
        self.data_line = self.graphWidget.plot(self.x, self.y, pen=pen)

        self.timer = QtCore.QTimer()
        self.timer.setInterval(50)
        self.timer.timeout.connect(self.update_plot_data)
        self.timer.start()

    def update_plot_data(self):
        self.x = np.roll(self.x, -1)
        self.x[-1] = self.x[-2] + 1

        self.y = np.roll(self.y, -1)
        self.y[-1] = self.data_source.get_reading()

        self.data_line.setData(self.x, self.y)


app = QtWidgets.QApplication(sys.argv)
source = DataSource()
w = MainWindow(source)
w.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;DataSource&lt;/code&gt; class here is a stand-in. In your own project, you'd replace &lt;code&gt;get_reading()&lt;/code&gt; with whatever code fetches data from your actual source &amp;mdash; reading from a serial port, polling a web API, or pulling from a shared queue.&lt;/p&gt;
&lt;p&gt;The plot window receives the &lt;code&gt;DataSource&lt;/code&gt; as a dependency, keeping the data logic separate from the display logic. This makes it straightforward to swap in a different data source later without changing the plotting code.&lt;/p&gt;
&lt;p&gt;If your data source is slow or blocking (for example, a network request), you should consider running it in a separate thread to avoid freezing the UI. See our guide on &lt;a href="https://www.pythonguis.com/tutorials/multithreading-pyqt6-applications-qthreadpool/"&gt;multithreading PyQt6 applications with QThreadPool&lt;/a&gt; for how to handle this.&lt;/p&gt;
&lt;h2 id="displaying-different-time-windows"&gt;Displaying different time windows&lt;/h2&gt;
&lt;p&gt;If you want to show the same data stream at different zoom levels &amp;mdash; say, the last 5 seconds &lt;em&gt;and&lt;/em&gt; the last 30 seconds &amp;mdash; you can use multiple &lt;code&gt;PlotWidget&lt;/code&gt; instances, each with its own data buffer. To arrange them vertically, we use a &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-layouts/"&gt;QVBoxLayout&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here's an example with two plots, each showing a different time window from the same data source:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6 import QtWidgets, QtCore
import pyqtgraph as pg
import numpy as np
import sys


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        layout = QtWidgets.QVBoxLayout()

        # Short window: 100 points (5 seconds at 50ms interval)
        self.graph_short = pg.PlotWidget()
        self.graph_short.setBackground("w")
        self.graph_short.setTitle("Last 5 seconds")
        layout.addWidget(self.graph_short)

        # Long window: 600 points (30 seconds at 50ms interval)
        self.graph_long = pg.PlotWidget()
        self.graph_long.setBackground("w")
        self.graph_long.setTitle("Last 30 seconds")
        layout.addWidget(self.graph_long)

        container = QtWidgets.QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        self.x_short = np.arange(100)
        self.y_short = np.zeros(100)

        self.x_long = np.arange(600)
        self.y_long = np.zeros(600)

        pen = pg.mkPen(color=(255, 0, 0), width=2)
        self.line_short = self.graph_short.plot(
            self.x_short, self.y_short, pen=pen
        )
        self.line_long = self.graph_long.plot(
            self.x_long, self.y_long, pen=pen
        )

        self.timer = QtCore.QTimer()
        self.timer.setInterval(50)
        self.timer.timeout.connect(self.update_plot_data)
        self.timer.start()

    def update_plot_data(self):
        new_value = np.random.randint(0, 100)

        # Update short window.
        self.x_short = np.roll(self.x_short, -1)
        self.x_short[-1] = self.x_short[-2] + 1
        self.y_short = np.roll(self.y_short, -1)
        self.y_short[-1] = new_value
        self.line_short.setData(self.x_short, self.y_short)

        # Update long window.
        self.x_long = np.roll(self.x_long, -1)
        self.x_long[-1] = self.x_long[-2] + 1
        self.y_long = np.roll(self.y_long, -1)
        self.y_long[-1] = new_value
        self.line_long.setData(self.x_long, self.y_long)


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Both plots receive the same data point on each tick. The short plot shows a zoomed-in view (the last 5 seconds), while the long plot shows a wider history (the last 30 seconds). You could extend this pattern to show even longer windows &amp;mdash; minutes or hours &amp;mdash; by increasing the buffer size and adjusting the timer interval or down-sampling the data for the longer views.&lt;/p&gt;
&lt;h2 id="complete-working-example"&gt;Complete working example&lt;/h2&gt;
&lt;p&gt;Here's the full, polished version with a white background, labeled axes, and a clean structure you can use as a starting point for your own projects:&lt;/p&gt;
&lt;div class="code-block"&gt;
&lt;span class="code-block-language code-block-python"&gt;python&lt;/span&gt;
&lt;pre&gt;&lt;code class="python"&gt;from PyQt6 import QtWidgets, QtCore
import pyqtgraph as pg
import numpy as np
import sys


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Real-Time PyQtGraph Plot")

        self.graphWidget = pg.PlotWidget()
        self.setCentralWidget(self.graphWidget)

        # Configure the plot appearance.
        self.graphWidget.setBackground("w")
        self.graphWidget.setTitle(
            "Live Data", color="k", size="14pt"
        )
        self.graphWidget.setLabel("left", "Amplitude")
        self.graphWidget.setLabel("bottom", "Time (samples)")
        self.graphWidget.showGrid(x=True, y=True)

        # Initialize data arrays.
        self.buffer_size = 200
        self.x = np.arange(self.buffer_size)
        self.y = np.zeros(self.buffer_size)

        # Create the plot line and keep a reference.
        pen = pg.mkPen(color=(255, 0, 0), width=2)
        self.data_line = self.graphWidget.plot(self.x, self.y, pen=pen)

        # Set up a timer to update the plot.
        self.timer = QtCore.QTimer()
        self.timer.setInterval(50)  # 50ms = 20 updates per second
        self.timer.timeout.connect(self.update_plot_data)
        self.timer.start()

    def update_plot_data(self):
        # Scroll the x-axis forward.
        self.x = np.roll(self.x, -1)
        self.x[-1] = self.x[-2] + 1

        # Add a new data point (replace with your data source).
        self.y = np.roll(self.y, -1)
        self.y[-1] = np.random.randint(0, 100)

        # Redraw the line with updated data.
        self.data_line.setData(self.x, self.y)


app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec())
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;To adapt this for your own data, replace the &lt;code&gt;np.random.randint(0, 100)&lt;/code&gt; line in &lt;code&gt;update_plot_data&lt;/code&gt; with a call to whatever provides your actual data. Everything else &amp;mdash; the scrolling, the display, the timer &amp;mdash; stays the same.&lt;/p&gt;
&lt;p&gt;If you want to embed PyQtGraph plots as &lt;a href="https://www.pythonguis.com/tutorials/pyqt6-embed-pyqtgraph-custom-widgets-qt-app/"&gt;custom widgets within a larger Qt application&lt;/a&gt;, you can integrate them alongside other UI elements using Qt Designer or code-based layouts.&lt;/p&gt;
            &lt;p&gt;For an in-depth guide to building Python GUIs with PyQt6 see my book, &lt;a href="https://www.mfitzp.com/pyqt6-book/"&gt;Create GUI Applications with Python &amp; Qt6.&lt;/a&gt;&lt;/p&gt;
            </content><category term="pyqt6"/><category term="pyqt"/><category term="pyqtgraph"/><category term="python"/><category term="real-time"/><category term="qt"/><category term="qt6"/><category term="data-science"/><category term="pyqt6-data-science"/></entry></feed>