Plotting with Two Y-Axes in PyQtGraph

How to display two data series with independent Y-axes on a single plot
Heads up! You've already completed this tutorial.

How can I plot two data series with completely different scales on the same PyQtGraph plot, using a left Y-axis for one and a right Y-axis for the other?

When you're working with data that has two different scales — say temperature and humidity, or voltage and current — plotting both series on a single Y-axis can make one of them look flat and unreadable. The solution is to use two Y-axes: one on the left for one data series, and one on the right for the other.

PyQtGraph supports this through its ViewBox system, which lets you overlay a second axis with its own independent scale. In this tutorial, we'll walk through how to set this up from scratch.

A simple single-axis plot

Let's start with a basic PyQtGraph plot showing a single data series. This gives us a foundation to build on.

python
import sys

import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets

app = QtWidgets.QApplication(sys.argv)

win = pg.GraphicsLayoutWidget(show=True, title="Two Y-Axes Example")
win.resize(800, 400)

plot = win.addPlot()
plot.setLabel("left", "Series 1")
plot.setLabel("bottom", "Time")

x = np.linspace(0, 10, 100)
y1 = np.sin(x) * 10  # Values range roughly -10 to 10

plot.plot(x, y1, pen="r")

sys.exit(app.exec())

This creates a window with a single plot and a red sine wave. The left Y-axis is labeled "Series 1" and the bottom axis shows "Time".

Now let's add a second data series — but one that lives on a completely different scale.

Adding a second Y-axis

To add a second Y-axis on the right side of the plot, we need to do a few things:

  • Create a new ViewBox to hold the second data series.
  • Link that ViewBox to the right-hand axis of the existing plot.
  • Make sure both views stay synchronized when the plot is resized or the X-axis is panned.

Here's the full working example:

python
import sys

import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets

app = QtWidgets.QApplication(sys.argv)

win = pg.GraphicsLayoutWidget(show=True, title="Two Y-Axes Example")
win.resize(800, 400)

# Create the main plot with the left Y-axis.
plot = win.addPlot()
plot.setLabel("left", "Series 1 (sin)", color="r")
plot.setLabel("bottom", "Time")

# Generate some sample data.
x = np.linspace(0, 10, 100)
y1 = np.sin(x) * 10       # Range: roughly -10 to 10
y2 = np.cos(x) * 1000     # Range: roughly -1000 to 1000

# Plot the first series on the main plot (left Y-axis).
plot.plot(x, y1, pen="r")

# Show the right axis and give it a label.
plot.showAxis("right")
plot.setLabel("right", "Series 2 (cos)", color="b")

# Create a new ViewBox for the second series.
viewbox2 = pg.ViewBox()
plot.scene().addItem(viewbox2)
plot.getAxis("right").linkToView(viewbox2)
viewbox2.setXLink(plot)

# Create a PlotCurveItem for the second series and add it to the new ViewBox.
curve2 = pg.PlotCurveItem(x, y2, pen="b")
viewbox2.addItem(curve2)


# Keep the second ViewBox geometry in sync with the main plot.
def update_views():
    viewbox2.setGeometry(plot.vb.sceneBoundingRect())
    viewbox2.linkedViewChanged(plot.vb, viewbox2.XAxis)


plot.vb.sigResized.connect(update_views)
update_views()

sys.exit(app.exec())

Run this and you'll see a red sine wave scaled to the left Y-axis (roughly ±10) and a blue cosine wave scaled to the right Y-axis (roughly ±1000). Both share the same X-axis, but each has its own independent vertical scale.

Let's walk through what each part is doing.

How it works

Showing the right axis

By default, PyQtGraph plots only display the left and bottom axes. We need to explicitly show the right axis:

python
plot.showAxis("right")
plot.setLabel("right", "Series 2 (cos)", color="b")

This makes the right axis visible and gives it a label. The color parameter tints the label text, which is a nice way to visually link each axis to its corresponding data series.

Creating a second ViewBox

A ViewBox in PyQtGraph is the area that manages the coordinate system and handles panning and scaling for a set of items. The main plot already has its own ViewBox (accessible as plot.vb). To get an independent Y-axis, we need a second one:

python
viewbox2 = pg.ViewBox()
plot.scene().addItem(viewbox2)

We add the new ViewBox directly to the plot's scene so it renders in the same graphical space.

Linking the axis and the X-axis

Next, we connect the right axis to our new ViewBox, and we link the X-axis so both views scroll and zoom together horizontally:

python
plot.getAxis("right").linkToView(viewbox2)
viewbox2.setXLink(plot)

linkToView tells the right axis to display tick values based on the coordinate system of viewbox2. setXLink ensures that when you pan or zoom along the X-axis on the main plot, the second ViewBox follows along.

Keeping the geometry in sync

When the window is resized, the main plot's ViewBox changes size — but our second ViewBox won't automatically follow. We need to update it manually:

python
def update_views():
    viewbox2.setGeometry(plot.vb.sceneBoundingRect())
    viewbox2.linkedViewChanged(plot.vb, viewbox2.XAxis)

plot.vb.sigResized.connect(update_views)
update_views()

The sigResized signal fires whenever the main ViewBox changes size. Our update_views function then sets the second ViewBox to the same geometry. The call to linkedViewChanged ensures the X-axis link is properly refreshed.

We also call update_views() once immediately to set the correct initial geometry. Without this, the second series may not appear in the right position until the first resize event occurs.

Adding data to the second ViewBox

We can't use plot.plot() for the second series because that would add it to the main ViewBox (and the left Y-axis). Instead, we create a PlotCurveItem and add it to viewbox2 directly:

python
curve2 = pg.PlotCurveItem(x, y2, pen="b")
viewbox2.addItem(curve2)

PlotCurveItem is the same type of item that plot.plot() creates under the hood. By adding it to viewbox2, the data is scaled according to the right Y-axis.

Color-coding the axes

To make it immediately clear which axis belongs to which series, it helps to color the axis tick marks to match the pen color of the corresponding line. We already set the label color above, but we can also style the axis ticks themselves:

python
plot.getAxis("left").setPen("r")
plot.getAxis("right").setPen("b")

Add these lines after the axis setup and both axes will be tinted to match their data series. This small visual detail makes dual-axis plots much easier to read at a glance.

Complete working example

Here's the full example with color-coded axes and everything wired up:

python
import sys

import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets

app = QtWidgets.QApplication(sys.argv)

win = pg.GraphicsLayoutWidget(show=True, title="Two Y-Axes Example")
win.resize(800, 400)

# Create the main plot.
plot = win.addPlot()
plot.setLabel("left", "Series 1 (sin)", color="r")
plot.setLabel("right", "Series 2 (cos)", color="b")
plot.setLabel("bottom", "Time")

# Show the right axis.
plot.showAxis("right")

# Color-code the axes to match the data series.
plot.getAxis("left").setPen("r")
plot.getAxis("right").setPen("b")

# Generate sample data with very different scales.
x = np.linspace(0, 10, 100)
y1 = np.sin(x) * 10       # Left axis: range ~ -10 to 10
y2 = np.cos(x) * 1000     # Right axis: range ~ -1000 to 1000

# Plot series 1 on the main (left) Y-axis.
plot.plot(x, y1, pen="r")

# Create a second ViewBox for series 2.
viewbox2 = pg.ViewBox()
plot.scene().addItem(viewbox2)
plot.getAxis("right").linkToView(viewbox2)
viewbox2.setXLink(plot)

# Add series 2 to the second ViewBox.
curve2 = pg.PlotCurveItem(x, y2, pen="b")
viewbox2.addItem(curve2)


# Keep the ViewBox geometries synchronized.
def update_views():
    viewbox2.setGeometry(plot.vb.sceneBoundingRect())
    viewbox2.linkedViewChanged(plot.vb, viewbox2.XAxis)


plot.vb.sigResized.connect(update_views)
update_views()

sys.exit(app.exec())

You can pan and zoom the plot, and both series will stay aligned along the X-axis while maintaining their own independent Y-axis scales. Try changing the data to use your own values — the technique works the same way regardless of what you're plotting.

This approach extends naturally if you need to add more axes or overlay additional data series. Each additional series just needs its own ViewBox, linked to an axis and kept in sync through the same update_views pattern.

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

Create GUI Applications with Python & Qt6 by Martin Fitzpatrick

(PyQt6 Edition) The hands-on guide to making apps with Python — Over 15,000 copies sold!

More info Get the book

Martin Fitzpatrick

Plotting with Two Y-Axes in PyQtGraph was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.