Mr RC
Mr RC

Reputation: 189

How can I have multiple left axisItems with the same alignment/position using pyqtgraph?

I wonder if someone can help me out. I am trying to convert the MultiplePlotAxes.py example in pyqtgraph so that the all the axes are on the right side and aligned. Here is my code which I have added an extra plot:

"""
Demonstrates a way to put multiple axes around a single plot.
(This will eventually become a built-in feature of PlotItem)
"""
#import initExample  ## Add path to library (just for examples; you do not need this)

import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np

pg.mkQApp()

pw = pg.PlotWidget()
pw.show()
pw.setWindowTitle('pyqtgraph example: MultiplePlotAxes')
p1 = pw.plotItem
p1.setLabels(left='axis 1')

## create a new ViewBox, link the right axis to its coordinate system
p2 = pg.ViewBox()
p1.showAxis('right')
p1.scene().addItem(p2)
p1.getAxis('right').linkToView(p2)
p2.setXLink(p1)
p1.getAxis('right').setLabel('axis2', color='#0000ff')

## create third ViewBox.
## this time we need to create a new axis as well.
p3 = pg.ViewBox()
ax3 = pg.AxisItem('right')
p1.layout.addItem(ax3, 2, 3)
p1.scene().addItem(p3)
ax3.linkToView(p3)
p3.setXLink(p1)
ax3.setZValue(-10000)
ax3.setLabel('axis 3', color='#ff0000')

## create forth ViewBox.
## this time we need to create a new axis as well.
p4 = pg.ViewBox()
ax4 = pg.AxisItem('right')
p1.layout.addItem(ax4, 2, 4)
p1.scene().addItem(p4)
ax4.linkToView(p4)
p4.setXLink(p1)
ax4.setZValue(-10000)
ax4.setLabel('axis 4', color='#2EFEF7')

## Handle view resizing
def updateViews():
    ## view has resized; update auxiliary views to match
    global p1, p2, p3, p4
    p2.setGeometry(p1.vb.sceneBoundingRect())
    p3.setGeometry(p1.vb.sceneBoundingRect())
    p4.setGeometry(p1.vb.sceneBoundingRect())

    ## need to re-update linked axes since this was called
    ## incorrectly while views had different shapes.
    ## (probably this should be handled in ViewBox.resizeEvent)
    p2.linkedViewChanged(p1.vb, p2.XAxis)
    p3.linkedViewChanged(p1.vb, p3.XAxis)
    p4.linkedViewChanged(p1.vb, p4.XAxis)

updateViews()
p1.vb.sigResized.connect(updateViews)

p1.plot([1, 2, 4, 8, 16, 32])
p2.addItem(pg.PlotCurveItem([1, 2, 4, 9, 16, 32], pen='b'))
p3.addItem(pg.PlotCurveItem([1, 2, 4, 7, 16, 32], pen='r'))
p4.addItem(pg.PlotCurveItem([1, 3, 5, 7, 17, 32], pen='c'))

## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
    import sys

    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

This produces the following picture which I have added a red arrow showing what I want to do. enter image description here

Based on what I have been able to figure out I can do this using pg.GraphicsLayout(). However, the axisItem directly associated with plotItem and the created axisItems don't match because the one attached to the plotitem is smaller.

Here is a picture of what I believe is happening:

enter image description here

This is my problem as I want them to all align. In the attached code you can addItem(axes,2,1) to the right but it doesn't work on the left. Anyone know how I can do this?

Thanks in advance for any help you can provide.

Upvotes: 3

Views: 2598

Answers (3)

Igor Petrov
Igor Petrov

Reputation: 21

While the solution posted by @mr-rc may look OK at the first glance, it does not actually solve the problem. If you draw the line through all 0-s, it will not intersect with 0 point on the curves.

After spending some time researching this issue, I don't think there's a way to fix axis alignment and scale if it is outside the QGraphicsGridLayout that's created inside the PlotItem (which is internally created in the PlotWidget). Therefore, the proper solution to this issue would be implementation of an alternative to PlotItem that can host as many Y axes as you want.

However, if you don't want to re-implement the entire PlotItem class, here's the trick that I used in my application. The idea is simple, after PlotItem is created, I remove all the items from its internal layout, and insert them back, but to column indexes shifted by the amount of extra Y axes I want to add. And all the extra axes are inserted into the PlotItem internal layout. As result, all the axes are perfectly aligned with the curves in the view boxes. Here is the example how it looks like

from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg

pg.mkQApp()


x = [1, 2, 3, 4, 5, 6]
y = [
    ('axis 1','#FFFFFF',[0, 4, 6, 8, 10, 4]),
    ('axis 2','#2E2EFE',[0, 5, 7, 9, 11, 3]),
    ('axis 3','#2EFEF7',[0, 1, 2, 3, 4, 12]),
    ('axis 4','#2EFE2E',[0, 8, 0.3, 0.4, 2, 5]),
    ('axis 5','#FFFF00',[0, 1, 6, 4, 2, 1]),
    ('axis 6','#FE2E64',[0, 0.2, 0.3, 0.4, 0.5, 0.6]),
]

# main view
pw = pg.GraphicsView()
pw.setWindowTitle('pyqtgraph example: multiple y-axis')
pw.show()

# layout
layout = pg.GraphicsLayout()
pw.setCentralWidget(layout)

# utility variables
secondary_viewboxes = []
plot_item = None
main_viewbox = None
previous_viewbox = None
main_layout = None

for i, (name, color, y_data) in enumerate(y):
    pen = pg.mkPen(width=1,  color=color)
    column = len(y) - i

    curve = pg.PlotDataItem(x, y_data, pen=pen, name=name, autoDownsample=True)

    if i == 0: # first, main plot
        plot_item = pg.PlotItem()

        main_y_axis = plot_item.getAxis("left") # get Y axis
        main_y_axis.setTextPen(pen)
        main_y_axis.setLabel(name)
        main_x_axis = plot_item.getAxis("bottom") # get x axis
        main_viewbox = plot_item.vb # get main viewbox
        main_viewbox.setMouseMode(pg.ViewBox.RectMode)

        # trick
        main_layout = plot_item.layout # get reference to QGraphicsGridLayout from plot_item
        main_layout.removeItem(main_y_axis) # remove items created in PlotItem from its layout
        main_layout.removeItem(main_x_axis)
        main_layout.removeItem(main_viewbox)

        main_layout.addItem(main_y_axis, 2, column)  # shift them to the right, making space for secondary axes
        main_layout.addItem(main_viewbox, 2, column + 1)
        main_layout.addItem(main_x_axis,  3, column + 1)

        main_layout.setColumnStretchFactor(column + 1, 100) # fix scaling factor, in original layout col 1 contains
        main_layout.setColumnStretchFactor(1, 0)            # the view_box and it's stretched
        #  /trick
        layout.addItem(plot_item, row=0, col=column + 1)
        viewbox = previous_viewbox = main_viewbox
    else: # Secondary "sub" plots
        axis = pg.AxisItem("left")  # create axis
        axis.setTextPen(pen)
        axis.setLabel(name)
        main_layout.addItem(axis, 2, column)  # trick, add axis it into original plot_item layout
        viewbox = pg.ViewBox()  # create ViewBox
        viewbox.setXLink(previous_viewbox)  # link to previous
        previous_viewbox = viewbox
        axis.linkToView(viewbox)  # link axis with viewbox
        layout.scene().addItem(viewbox)  # add viewbox to layout
        viewbox.enableAutoRange(axis=pg.ViewBox.XYAxes, enable=True)  # autorange once to fit views at start

        secondary_viewboxes.append(viewbox)

    viewbox.addItem(curve)

# slot: update view when resized
def updateViews():
    for vb in secondary_viewboxes:
        vb.setGeometry(main_viewbox.sceneBoundingRect())


main_viewbox.sigResized.connect(updateViews)
updateViews()

if __name__ == '__main__':
    import sys

    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QGuiApplication.instance().exec_()

Upvotes: 2

Mr RC
Mr RC

Reputation: 189

I have figured something out that is not perfect but is pretty close. Using the suggestion above and adding an extra axis item to the bottom in the appropriate space and blanking it out I have come up with this. All lines line up but not perfectly with the y-axis but close enough to get away with it. Heres the difference

Original enter image description here New enter image description here Heres the code:

from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg

pg.mkQApp()

# Axis
a2 = pg.AxisItem("left")
a3 = pg.AxisItem("left")
a4 = pg.AxisItem("left")  
a5 = pg.AxisItem("left")
a6 = pg.AxisItem("left")

# ViewBoxes
v2 = pg.ViewBox()
v3 = pg.ViewBox()
v4 = pg.ViewBox()
v5 = pg.ViewBox()
v6 = pg.ViewBox()

# main view
pw = pg.GraphicsView()
pw.setWindowTitle('pyqtgraph example: multiple y-axis')
pw.show()

# layout
l = pg.GraphicsLayout()
pw.setCentralWidget(l)

# add axis to layout
## watch the col parameter here for the position
l.addItem(a2, row=1, col=5, rowspan=1, colspan=1)
l.addItem(a3, row=1, col=4, rowspan=1, colspan=1)
l.addItem(a4, row=1, col=3, rowspan=1, colspan=1)
l.addItem(a5, row=1, col=2, rowspan=1, colspan=1)
l.addItem(a6, row=1, col=1, rowspan=1, colspan=1)

# Blank axis used for aligning things
ax = pg.AxisItem(orientation='bottom')
ax.setPen('#000000')
pos = (2,2)
l.addItem(ax, *pos)

# plotitem and viewbox
## at least one plotitem is used whioch holds its own viewbox and left axis
pI = pg.PlotItem()
v1 = pI.vb  # reference to viewbox of the plotitem
l.addItem(pI, row=1, col=8, rowspan=2, colspan=1)  # add plotitem to layout

# split off 1st axis and put to side
pI.axis_left = pI.getAxis('left')
pos = (1,7)
l.addItem(pI.axis_left, *pos)

# add viewboxes to layout
l.scene().addItem(v2)
l.scene().addItem(v3)
l.scene().addItem(v4)
l.scene().addItem(v5)
l.scene().addItem(v6)

# link axis with viewboxes
a2.linkToView(v2)
a3.linkToView(v3)
a4.linkToView(v4)
a5.linkToView(v5)
a6.linkToView(v6)

# link viewboxes
v2.setXLink(v1)
v3.setXLink(v2)
v4.setXLink(v3)
v5.setXLink(v4)
v6.setXLink(v5)

# axes labels
pI.getAxis("left").setLabel('axis 1 in ViewBox of PlotItem', color='#FFFFFF')
a2.setLabel('axis 2 in Viewbox 2', color='#2E2EFE')
a3.setLabel('axis 3 in Viewbox 3', color='#2EFEF7')
a4.setLabel('axis 4 in Viewbox 4', color='#2EFE2E')
a5.setLabel('axis 5 in Viewbox 5', color='#FFFF00')
a6.setLabel('axis 6 in Viewbox 6', color='#FE2E64')


# slot: update view when resized
def updateViews():
    v2.setGeometry(v1.sceneBoundingRect())
    v3.setGeometry(v1.sceneBoundingRect())
    v4.setGeometry(v1.sceneBoundingRect())
    v5.setGeometry(v1.sceneBoundingRect())
    v6.setGeometry(v1.sceneBoundingRect())

# data
x = [1, 2, 3, 4, 5, 6]
y1 = [0, 4, 6, 8, 10, 4]
y2 = [0, 5, 7, 9, 11, 3]
y3 = [0, 1, 2, 3, 4, 12]
y4 = [0, 8, 0.3, 0.4, 2, 5]
y5 = [0, 1, 6, 4, 2, 1]
y6 = [0, 0.2, 0.3, 0.4, 0.5, 0.6]

# plot
v1.addItem(pg.PlotCurveItem(x, y1, pen='#FFFFFF'))
v2.addItem(pg.PlotCurveItem(x, y2, pen='#2E2EFE'))
v3.addItem(pg.PlotCurveItem(x, y3, pen='#2EFEF7'))
v4.addItem(pg.PlotCurveItem(x, y4, pen='#2EFE2E'))
v5.addItem(pg.PlotCurveItem(x, y5, pen='#FFFF00'))
v6.addItem(pg.PlotCurveItem(x, y6, pen='#FE2E64'))

# updates when resized
v1.sigResized.connect(updateViews)

# autorange once to fit views at start
v2.enableAutoRange(axis=pg.ViewBox.XYAxes, enable=True)
v3.enableAutoRange(axis=pg.ViewBox.XYAxes, enable=True)
v4.enableAutoRange(axis=pg.ViewBox.XYAxes, enable=True)
v5.enableAutoRange(axis=pg.ViewBox.XYAxes, enable=True)
v6.enableAutoRange(axis=pg.ViewBox.XYAxes, enable=True)

updateViews()

if __name__ == '__main__':
    import sys

    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtGui.QApplication.instance().exec_()

Hope it helps anyone who has the same problem in the future

Upvotes: 2

Ogi Moore
Ogi Moore

Reputation: 131

I had this same issue, saw this post, was disappointed with the lack of an answer, but I finally did manage a fix so figured I would post here.

If you could post the code you have for the axis on the right, I could patch that up, but basically what I did was attach the bottom axis to a a row of its own.

self.main_pI = pg.PlotItem()
self.axis_bottom = self.main_pI.getAxis('bottom')
self.axis_bottom.setLabel('Time (seconds)', color='#ffffff')
self.layout.addItem(self.main_pI, row=1, col=2, rowspan=2)
self.layout.addItem(self.axis_bottom, row=3, col=2, rowspan=2)

I'm a noob at pyqtgraph so I'm sure I'm going to butcher this terminology, but I detach the 'bottom' axis, and reattach it to the layout, where it's on a row of its own.

Hope that helps.

Upvotes: 2

Related Questions