Log Scale with Multiple Plots/ Multiple Axis

80 views
Skip to first unread message

Syed Fahad Iqbal

unread,
Nov 14, 2020, 5:59:50 AM11/14/20
to pyqtgraph
I'm trying to plot 2 different plots in a single graph window (Sample Code given below). I'm using ViewBox to bring an independent 2nd y axis but I'm unable to apply log scale on both x and the y axis for the 2nd plot being plotted through View Box. Also please let me know if I can add a legend for the 2ns plot in vb as well !!! Can I have any solution please ?  
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import pandas as pd

pg.mkQApp()

pw = pg.PlotWidget()
legend = pw.plotItem.legend
pw.plotItem.addLegend(offset=(50, 10))
pw.plotItem.addLegend()
pw.setLogMode(x=True, y=True)


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.setLogMode(x=True, y=True)
p1.scene().addItem(p2)

p1.getAxis('right').linkToView(p2)

p1.getAxis('right').setLogMode(True)
p1.getAxis('left').setLogMode(False)
p1.getAxis('bottom').setLogMode(True)
#p1.getAxis('bottom').linkToView(p2)
#p2.setXLink(p1)
p1.getAxis('right').setTicks(False)  #.setLabel('axis2', color='#0000ff')


## Handle view resizing
def updateViews():
    ## view has resized; update auxiliary views to match
    global p1, p2
    p2.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)



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

x = [0.00189308411214953, 0.0586856074766355, 0.113585046728972, 0.172270654205607, 0.229063177570093, 0.751554392523364, 0.804560747663551, 0.863246355140187]

y1 = [20.4519856776661, 2.36731824818362, 1.37059599828579, 0.893434884015263, 0.654398170298103, 0.16233537659946, 0.136188319914979, 0.12445839587513]

y2 = [100.206092906091, 10.7767052017243, 6.7863521253753, 5.06574658107859, 4.13195800053371, 3.54173495603609, 3.12977548096585, 0.109898121408567]


p1.plot(x,y1, name = 'First Curve')


p2.addItem(pg.PlotCurveItem(x,y2, name = 'SecondCurve', pen='b'))


## 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_()

Patrick

unread,
Nov 16, 2020, 12:47:17 AM11/16/20
to pyqtgraph
Hi,

I don't exactly understand what you're trying to do, but I implemented dual-scale axes using a custom AxisItem class which applies a conversion function to produce the labels for the second axis. It works something like this:

from PyQt5 import QtCore, QtWidgets
import pyqtgraph as pg
import numpy as np
import warnings


class AxisItemScaleFunction(pg.AxisItem):
    """Extension of pyqtgraph AxisItem which allows the axis tick labels to be
    transformed using a user-specified function.

    Typical use would be to display the same axis data in two different unit
    systems. For example, temperature in C and F, or light wavelength as well as
    photon energy.

    Note that the axis range and scale is not changed, only the calculation of
    the axis tick labels."""

    def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLength=-5, showValues=True, scalefunc=lambda x: x):
        self.scalefunc = scalefunc
        pg.AxisItem.__init__(self, orientation, pen=pen, linkView=linkView, parent=parent, maxTickLength=maxTickLength, showValues=showValues)

    def tickStrings(self, values, scale, spacing):
        """Generates the strings to use for tick labels."""
        if self.logMode:
            return self.logTickStrings(values, scale, spacing)

        warnings.simplefilter("ignore")
        try:
            places = np.nanmax([0,
                np.ceil(-np.log10((np.abs(self.scalefunc(values[0] + spacing) - self.scalefunc(values[0])))*scale)),
                np.ceil(-np.log10((np.abs(self.scalefunc(values[-1]) - self.scalefunc(values[-1] - spacing)))*scale))])
        except IndexError:
            places = 0
        warnings.simplefilter("default")
        strings = []
        for v in values:
            vs = self.scalefunc(v) * scale
            if abs(vs) < .001 or abs(vs) >= 10000:
                vstr = "%g" % vs
            else:
                vstr = ("%%0.%df" % places) % vs
            strings.append(vstr)
        return strings

    def updateAutoSIPrefix(self):
        """Update the SI prefix for units, if displayed."""
        if self.label.isVisible():
            (scale, prefix) = pg.siScale(max(abs(self.scalefunc(self.range[0])), abs(self.scalefunc(self.range[1]))))
            if self.labelUnits == '' and prefix in ['k', 'm']:  ## If we are not showing units, wait until 1e6 before scaling.
                scale = 1.0
                prefix = ''
            self.setLabel(unitPrefix=prefix)
        else:
            scale = 1.0

        self.autoSIPrefixScale = scale
        self.picture = None
        self.update()

    def logTickStrings(self, values, scale, spacing):
        """Return tick strings for a logarithmic axis."""
        # This one-line abomination isn't tested very extensively but seems to work...
        return ["%0.1g"%x for x in np.array([self.scalefunc(v) for v in 10 ** np.array(values).astype(float)])]


class TwinScales(pg.GraphicsLayoutWidget):

    def __init__(self):
        super().__init__()
        rax = AxisItemScaleFunction(orientation='right', scalefunc=lambda x: 2.998e5/x if not x == 0 else np.inf)
        self.spectrum = self.addPlot(row=0, col=0, axisItems={'right': rax}, enableMenu=False)
        self.spectrum.showGrid(x=True, y=True)
        self.spectrum.addLegend(offset=(-10, 5))
        self.spectrum.setLabels(left="Frequency (THz)", bottom="Intensity (a.u.)", right="Wavelength (nm)")

        y = np.linspace(400, 800, num=200)
        self.p1 = self.spectrum.plot(56.7*np.sin(y/20), y, pen=(255, 255, 0), name="Spectral slice t = 123.4 fs")
        self.p2 = self.spectrum.plot(45.6*np.cos(y/30), y, pen=(255, 0, 0), name="Other slice t = 567.8 fs")

        self.spectrum.setLogMode(x=False, y=True)


def main():
    import sys
    app = QtWidgets.QApplication(sys.argv)
    mainwindow = TwinScales()
    mainwindow.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()


Screenshot from 2020-11-16 16-15-35.png

Hopefully that might help you.

Patrick

Fahad I. Syed

unread,
Nov 16, 2020, 1:59:27 AM11/16/20
to pyqt...@googlegroups.com

Dear Patrik,

Thanks for your input, half of my problem (dual Y axis) is solved with your provided example. Other thing that I need to fix my graph is; I would like one of the plot (lets say the yellow one in your example) is to be kept fixed (non-moveable) while the plot (red) is flexible (moveable) on both axis. The objective is to match the performance of two plots by means of over lapping each other. I tried through GraphicsView and ViewBox and its working there but I’m unable to introduce Log scale in the Y axis liked to the View/Graphics View.

 

What I want to see in brief:

A single graph window with multiple axis (all in Log scale), where one plot remain fixed while the other plot remain flexible for overlapping/ tracing the two plots performance.

 

I’ll be glad if I can have some solution accordingly.

Regards,

Fahad.

 

Sent from Mail for Windows 10

--
You received this message because you are subscribed to a topic in the Google Groups "pyqtgraph" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/pyqtgraph/pjogNHybJAE/unsubscribe.
To unsubscribe from this group and all its topics, send an email to pyqtgraph+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pyqtgraph/317cbddd-ce69-42aa-83fc-6bb91949ae69n%40googlegroups.com.

 

Reply all
Reply to author
Forward
0 new messages