Set X range values at second X axis which corresponds with the first one

65 views
Skip to first unread message

roman.grz...@gmail.com

unread,
Feb 7, 2019, 5:44:34 AM2/7/19
to pyqtgraph
Hello. I have a program which gathers measurements of voltage, current and time. I'd like to create a plot Voltage vs time and second X-axis at top with Capacity on it. I've done simple window like this:

import pyqtgraph as pg

win
=pg.GraphicsWindow("plot")
win
.resize(1000,600)
plt
=win.addPlot(row = 1, col = 0,title="Plot",labels={'left': 'Voltage [V]','bottom': 'time [s]','top': 'Capacity [mAh]'})
curve
=plt.plot(pen='b')
label_cords
= pg.LabelItem(justify = "right")
So it looks like on screen attached.

In my measurements I also get current so Capacity is calculated as: current*10*time/36 (It's the same time I use to plot bottom x axis).

How can I set values of top axis to show capacity corresponded to the time on bottom axis? 

I've tried to work with one of 

plt.getAxis('top').setTickSpacing()

plt
.getAxis('top').setTicks()

plt
.getAxis('top').tickValues()



but I don't really know how to set it properly. I want to keep autorange function active (so when time axis going wider I want my Capacity axis to expand aswell)


Capture.JPG

roman.grz...@gmail.com

unread,
Feb 7, 2019, 6:11:24 AM2/7/19
to pyqtgraph
BTW I forgot to mention I don't want to plot new curve, I just want one curve and 2 x-axes whose values are corresponds to each other

Patrick

unread,
Feb 7, 2019, 9:19:40 PM2/7/19
to pyqtgraph
Hi,

Is the current is constant over time? If not this becomes a harder problem but not impossible -- you'd need to integrate the current curve to compute the capacity labels.

I'm not sure if this is the best way to do it, but I made a dual axis for a project (left/right wavelength and frequency axes) by copying/extending AxisItem like this:

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):
       
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):
       
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 ["%0.1g"%x for x in 10 ** np.array(self.scalefunc(values)).astype(float)]

In its init method it takes a reference to a function which is used to compute the alternate tick labels. You then manually assign this custom axis item to the plot when you create it, something like:

rax = AxisItemScaleFunction(orientation='right', scalefunc=lambda x: 1e-3*constants.c/x if not x == 0 else np.inf)
self.spectrum = self.overview_graphicsLayoutWidget.addPlot(axisItems={'right': rax})
self.spectrum.setLabels(left="Frequency (THz)", bottom="Intensity (a.u.)", right="Wavelength (nm)")
self.spectrum_plot = self.spectrum.plot(pen=(255, 255, 0), name="Spectrum")

So if your current is constant, just sub your formula into the lambda function above. If not, then you will probably want to create a full function that integrates the current given a time t, and use scalefunc=compute_capacity instead.

Hope that helps,
Patrick
Reply all
Reply to author
Forward
0 new messages