Reputation: 189
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.
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:
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
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
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
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
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