You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1505 lines
49 KiB
1505 lines
49 KiB
from __future__ import print_function |
|
|
|
# encoding: utf-8 |
|
""" |
|
pyqtgraphPlotHelpers.py |
|
|
|
Routines to help use pyqtgraph and make cleaner plots |
|
as well as get plots read for publication. |
|
|
|
Intially copied from PlotHelpers.py for matplotlib. |
|
Modified to allow us to use a list of axes, and operate on all of those, |
|
or to use just one axis if that's all that is passed. |
|
Therefore, the first argument to these calls can either be a pyqtgraph axis object, |
|
or a list of axes objects. 2/10/2012 pbm. |
|
|
|
Created by Paul Manis on 2010-03-09. |
|
""" |
|
|
|
|
|
import string |
|
|
|
stdFont = "Arial" |
|
|
|
import scipy.stats |
|
import numpy as np |
|
import pyqtgraph as pg |
|
from .talbotetalTicks import Extended # logical tick formatting... |
|
|
|
""" |
|
Basic functions: |
|
""" |
|
|
|
|
|
def nice_plot( |
|
plotlist, spines=["left", "bottom"], position=10, direction="inward", axesoff=False |
|
): |
|
""" Adjust a plot so that it looks nicer than the default matplotlib plot |
|
Also allow quickaccess to things we like to do for publication plots, including: |
|
using a calbar instead of an axes: calbar = [x0, y0, xs, ys] |
|
inserting a reference line (grey, 3pt dashed, 0.5pt, at refline = y position) |
|
|
|
Paramaetrs |
|
---------- |
|
plotlist : list |
|
a plot handle or list of plot handles to which the "niceplot" will be applied |
|
spines : list |
|
a list of which axes should have spines. Not relevant for pyqtgraph |
|
position : int |
|
not relevant for pyqtgraph |
|
direction : string |
|
need to implement for pyqtgraph |
|
axesoff : boolean |
|
flag that forces plots to turn axes off |
|
|
|
""" |
|
if type(plotlist) is not list: |
|
plotlist = [plotlist] |
|
for pl in plotlist: |
|
if axesoff is True: |
|
pl.hideAxis("bottom") |
|
pl.hideAxis("left") |
|
|
|
|
|
def noaxes(plotlist, whichaxes="xy"): |
|
""" take away all the axis ticks and the lines |
|
|
|
Parameters |
|
---------- |
|
plotlist : list |
|
list of plot handles |
|
whichaxes : string |
|
string describing which axes to remove: 'x', 'y', or 'xy' for both |
|
|
|
""" |
|
if type(plotlist) is not list: |
|
plotlist = [plotlist] |
|
for pl in plotlist: |
|
if "x" in whichaxes: |
|
pl.hideAxis("bottom") |
|
if "y" in whichaxes: |
|
pl.hideAxis("left") |
|
|
|
|
|
def setY(ax1, ax2): |
|
""" |
|
Set the Y axis of all the plots in ax2 to be like ax1 |
|
|
|
Parameters |
|
---------- |
|
ax1 : pyqtgraph plot instance |
|
ax2 : list |
|
list of target plots that will have the axes properties of ax1 |
|
""" |
|
if type(ax1) is list: |
|
print("PlotHelpers: cannot use list as source to set Y axis") |
|
return |
|
if type(ax2) is not list: |
|
ax2 = [ax2] |
|
y = ax1.getAxis("left") |
|
refy = y.range # return the current range |
|
for ax in ax2: |
|
ax.setRange(refy) |
|
|
|
|
|
def setX(ax1, ax2): |
|
""" |
|
Set the X axis of all the plots in ax2 to be like ax1 |
|
|
|
Parameters |
|
---------- |
|
ax1 : pyqtgraph plot instance |
|
ax2 : list |
|
list of target plots that will have the axes properties of ax1 |
|
""" |
|
if type(ax1) is list: |
|
print("PlotHelpers: cannot use list as source to set X axis") |
|
return |
|
if type(ax2) is not list: |
|
ax2 = [ax2] |
|
x = ax1.getAxis("bottom") |
|
refx = x.range |
|
for ax in ax2: |
|
ax.setrange(refx) |
|
|
|
|
|
def labelPanels(axl, axlist=None, font="Arial", fontsize=18, weight="normal"): |
|
""" |
|
Label the panels like a specific panel |
|
|
|
Parameters |
|
---------- |
|
axl : dict or list |
|
axlist : list, optional |
|
list of labels to use for the axes, defaults to None |
|
font : str, optional |
|
Font to use for the labels, defaults to Arial |
|
fontsize : int, optional |
|
Font size in points for the labels, defaults to 18 |
|
weight : str, optional |
|
Font weight to use, defaults to 'normal' |
|
|
|
""" |
|
if type(axl) is dict: |
|
axt = [axl[x] for x in axl] |
|
axlist = axl.keys() |
|
axl = axt |
|
if type(axl) is not list: |
|
axl = [axl] |
|
if axlist is None: |
|
axlist = string.uppercase(1, len(axl)) # assume we wish to go in sequence |
|
|
|
for i, ax in enumerate(axl): |
|
labelText = pg.TextItem(axlist[i]) |
|
y = ax.getAxis("left").range |
|
x = ax.getAxis("bottom").range |
|
ax.addItem(labelText) |
|
labelText.setPos(x[0], y[1]) |
|
|
|
|
|
def listAxes(axd): |
|
""" |
|
make a list of the axes from the dictionary of axes |
|
|
|
Parameters |
|
---------- |
|
axd : dict |
|
a dict of axes, whose values are returned in a list |
|
|
|
Returns |
|
------- |
|
list : a list of the axes |
|
|
|
""" |
|
if type(axd) is not dict: |
|
if type(axd) is list: |
|
return axd |
|
else: |
|
print("listAxes expects dictionary or list; type not known (fix the code)") |
|
raise |
|
axl = [axd[x] for x in axd] |
|
return axl |
|
|
|
|
|
def cleanAxes(axl): |
|
""" |
|
""" |
|
if type(axl) is not list: |
|
axl = [axl] |
|
# does nothing at the moment, as axes are already "clean" |
|
# for ax in axl: |
|
# |
|
# update_font(ax) |
|
|
|
|
|
def formatTicks(axl, axis="xy", fmt="%d", font="Arial"): |
|
""" |
|
Convert tick labels to intergers |
|
to do just one axis, set axis = 'x' or 'y' |
|
control the format with the formatting string |
|
""" |
|
if type(axl) is not list: |
|
axl = [axl] |
|
|
|
|
|
def autoFormatTicks(axl, axis="xy", font="Arial"): |
|
if type(axl) is not list: |
|
axl = [axl] |
|
for ax in axl: |
|
if "x" in axis: |
|
b = ax.getAxis("bottom") |
|
x0 = b.range |
|
# setFormatter(ax, x0, x1, axis = 'x') |
|
if "y" in axis: |
|
l = ax.getAxis("left") |
|
y0 = l.range |
|
|
|
|
|
# setFormatter(ax, y0, y1, axis = 'y') |
|
|
|
|
|
def setFormatter(ax, x0, x1, axis="x"): |
|
datarange = np.abs(x0 - x1) |
|
mdata = np.ceil(np.log10(datarange)) |
|
# if mdata > 0 and mdata <= 4: |
|
# majorFormatter = FormatStrFormatter('%d') |
|
# elif mdata > 4: |
|
# majorFormatter = FormatStrFormatter('%e') |
|
# elif mdata <= 0 and mdata > -1: |
|
# majorFormatter = FormatStrFormatter('%5.1f') |
|
# elif mdata < -1 and mdata > -3: |
|
# majorFormatatter = FormatStrFormatter('%6.3f') |
|
# else: |
|
# majorFormatter = FormatStrFormatter('%e') |
|
# if axis == 'x': |
|
# ax.xaxis.set_major_formatter(majorFormatter) |
|
# else: |
|
# ax.yaxis.set_major_formatter(majorFormatter) |
|
|
|
|
|
def update_font(axl, size=6, font=stdFont): |
|
pass |
|
# if type(axl) is not list: |
|
# axl = [axl] |
|
# fontProperties = {'family':'sans-serif','sans-serif':[font], |
|
# 'weight' : 'normal', 'size' : size} |
|
# for ax in axl: |
|
# for tick in ax.xaxis.get_major_ticks(): |
|
# tick.label1.set_family('sans-serif') |
|
# tick.label1.set_fontname(stdFont) |
|
# tick.label1.set_size(size) |
|
# |
|
# for tick in ax.yaxis.get_major_ticks(): |
|
# tick.label1.set_family('sans-serif') |
|
# tick.label1.set_fontname(stdFont) |
|
# tick.label1.set_size(size) |
|
# ax.set_xticklabels(ax.get_xticks(), fontProperties) |
|
# ax.set_yticklabels(ax.get_yticks(), fontProperties) |
|
# ax.xaxis.set_smart_bounds(True) |
|
# ax.yaxis.set_smart_bounds(True) |
|
# ax.tick_params(axis = 'both', labelsize = 9) |
|
|
|
|
|
def lockPlot(axl, lims, ticks=None): |
|
""" |
|
This routine forces the plot of invisible data to force the axes to take certain |
|
limits and to force the tick marks to appear. |
|
call with the axis and lims = [x0, x1, y0, y1] |
|
""" |
|
if type(axl) is not list: |
|
axl = [axl] |
|
plist = [] |
|
for ax in axl: |
|
y = ax.getAxis("left") |
|
x = ax.getAxis("bottom") |
|
x.setRange(lims[0], lims[1]) |
|
y.setRange(lims[2], lims[3]) |
|
|
|
|
|
def adjust_spines( |
|
axl, spines=("left", "bottom"), direction="outward", distance=5, smart=True |
|
): |
|
pass |
|
# if type(axl) is not list: |
|
# axl = [axl] |
|
# for ax in axl: |
|
# # turn off ticks where there is no spine |
|
# if 'left' in spines: |
|
# ax.yaxis.set_ticks_position('left') |
|
# else: |
|
# # no yaxis ticks |
|
# ax.yaxis.set_ticks([]) |
|
# |
|
# if 'bottom' in spines: |
|
# ax.xaxis.set_ticks_position('bottom') |
|
# else: |
|
# # no xaxis ticks |
|
# ax.xaxis.set_ticks([]) |
|
# for loc, spine in ax.spines.iteritems(): |
|
# if loc in spines: |
|
# spine.set_position((direction,distance)) # outward by 10 points |
|
# if smart is True: |
|
# spine.set_smart_bounds(True) |
|
# else: |
|
# spine.set_smart_bounds(False) |
|
# else: |
|
# spine.set_color('none') # don't draw spine |
|
# return |
|
# |
|
|
|
|
|
def calbar(plotlist, calbar=None, axesoff=True, orient="left", unitNames=None): |
|
""" draw a calibration bar and label it up. The calibration bar is defined as: |
|
[x0, y0, xlen, ylen] |
|
|
|
Parameters |
|
---------- |
|
plotlist : list |
|
a plot item or a list of plot items for which a calbar will be applied |
|
calbar : list, optional |
|
a list with 4 elements, describing the calibration bar |
|
[xposition, yposition, xlength, ylength] in units of the data inthe plot |
|
defaults to None |
|
axesoff : boolean, optional |
|
Set true to turn off the standard axes, defaults to True |
|
orient : text, optional |
|
'left': put vertical part of the bar on the left |
|
'right': put the vertical part of the bar on the right |
|
defaults to 'left' |
|
unitNames: str, optional |
|
a dictionary with the names of the units to append to the calibration bar |
|
lengths. Example: {'x': 'ms', 'y': 'nA'} |
|
defaults to None |
|
Returns |
|
------- |
|
Nothing |
|
""" |
|
|
|
if type(plotlist) is not list: |
|
plotlist = [plotlist] |
|
for pl in plotlist: |
|
if axesoff is True: |
|
noaxes(pl) |
|
Vfmt = "%.0f" |
|
if calbar[2] < 1.0: |
|
Vfmt = "%.1f" |
|
Hfmt = "%.0f" |
|
if calbar[3] < 1.0: |
|
Hfmt = "%.1f" |
|
if unitNames is not None: |
|
Vfmt = Vfmt + " " + unitNames["x"] |
|
Hfmt = Hfmt + " " + unitNames["y"] |
|
Vtxt = pg.TextItem(Vfmt % calbar[2], anchor=(0.5, 0.5), color=pg.mkColor("k")) |
|
Htxt = pg.TextItem(Hfmt % calbar[3], anchor=(0.5, 0.5), color=pg.mkColor("k")) |
|
# print pl |
|
if calbar is not None: |
|
if orient == "left": # vertical part is on the left |
|
pl.plot( |
|
[calbar[0], calbar[0], calbar[0] + calbar[2]], |
|
[calbar[1] + calbar[3], calbar[1], calbar[1]], |
|
pen=pg.mkPen("k"), |
|
linestyle="-", |
|
linewidth=1.5, |
|
) |
|
ht = Htxt.setPos( |
|
calbar[0] + 0.05 * calbar[2], calbar[1] + 0.5 * calbar[3] |
|
) |
|
elif orient == "right": # vertical part goes on the right |
|
pl.plot( |
|
[calbar[0] + calbar[2], calbar[0] + calbar[2], calbar[0]], |
|
[calbar[1] + calbar[3], calbar[1], calbar[1]], |
|
pen=pg.mkPen("k"), |
|
linestyle="-", |
|
linewidth=1.5, |
|
) |
|
ht = Htxt.setPos( |
|
calbar[0] + calbar[2] - 0.05 * calbar[2], |
|
calbar[1] + 0.5 * calbar[3], |
|
) |
|
else: |
|
print("PlotHelpers.py: I did not understand orientation: %s" % (orient)) |
|
print("plotting as if set to left... ") |
|
pl.plot( |
|
[calbar[0], calbar[0], calbar[0] + calbar[2]], |
|
[calbar[1] + calbar[3], calbar[1], calbar[1]], |
|
pen=pg.mkPen("k"), |
|
linestyle="-", |
|
linewidth=1.5, |
|
) |
|
ht = Htxt.setPos( |
|
calbar[0] + 0.05 * calbar[2], calbar[1] + 0.5 * calbar[3] |
|
) |
|
Htxt.setText(Hfmt % calbar[3]) |
|
xc = float(calbar[0] + calbar[2] * 0.5) # always centered, below the line |
|
yc = float(calbar[1] - 0.1 * calbar[3]) |
|
vt = Vtxt.setPos(xc, yc) |
|
Vtxt.setText(Vfmt % calbar[2]) |
|
pl.addItem(Htxt) |
|
pl.addItem(Vtxt) |
|
|
|
|
|
def refline( |
|
axl, |
|
refline=None, |
|
color=[64, 64, 64], |
|
linestyle="--", |
|
linewidth=0.5, |
|
orient="horizontal", |
|
): |
|
""" |
|
Draw a reference line at a particular level of the data on the y axis |
|
|
|
Parameters |
|
---------- |
|
axl : list |
|
axis handle or list of axis handles |
|
refline : float, optional |
|
the position of the reference line, defaults to None |
|
color : list, optional |
|
the RGB color list for the line, in format [r,g,b], defaults to [64, 64, 64] (faint grey line) |
|
linestyle : str, optional |
|
defines the linestyle to be used: -- for dash, . for doted, - for solid, -. for dash-dot, |
|
-.. for -.., etc. |
|
defaults to '--' (dashed) |
|
linewidth : float, optional |
|
width of the line, defaults to 0.5 |
|
""" |
|
if type(axl) is not list: |
|
axl = [axl] |
|
if linestyle == "--": |
|
style = pg.QtCore.Qt.DashLine |
|
elif linestyle == ".": |
|
style = pg.QtCore.Qt.DotLine |
|
elif linestyle == "-": |
|
style = pg.QtCore.Qt.SolidLine |
|
elif linestyle == "-.": |
|
style = pg.QtCore.Qt.DsahDotLine |
|
elif linestyle == "-..": |
|
style = pg.QtCore.Qt.DashDotDotLine |
|
else: |
|
style = pg.QtCore.Qt.SolidLine # default is solid |
|
if orient is "horizontal": |
|
for ax in axl: |
|
if refline is not None: |
|
x = ax.getAxis("bottom") |
|
xlims = x.range |
|
ax.plot( |
|
xlims, |
|
[refline, refline], |
|
pen=pg.mkPen(color, width=linewidth, style=style), |
|
) |
|
if orient is "vertical": |
|
for ax in axl: |
|
if refline is not None: |
|
y = ax.getAxis("left") |
|
ylims = y.range |
|
ax.plot( |
|
[refline, refline], |
|
[ylims[0] + 0.5, ylims[1] - 0.5], |
|
pen=pg.mkPen(color, width=linewidth, style=style), |
|
) |
|
|
|
|
|
def tickStrings(values, scale=1, spacing=None, tickPlacesAdd=1): |
|
"""Return the strings that should be placed next to ticks. This method is called |
|
when redrawing the axis and is a good method to override in subclasses. |
|
|
|
Parameters |
|
---------- |
|
values : array or list |
|
An array or list of tick values |
|
scale : float, optional |
|
a scaling factor (see below), defaults to 1 |
|
spacing : float, optional |
|
spaceing between ticks (this is required since, in some instances, there may be only |
|
one tick and thus no other way to determine the tick spacing). Defaults to None |
|
tickPlacesToAdd : int, optional |
|
the number of decimal places to add to the ticks, default is 1 |
|
|
|
Returns |
|
------- |
|
list : a list containing the tick strings |
|
|
|
The scale argument is used when the axis label is displaying units which may have an SI scaling prefix. |
|
When determining the text to display, use value*scale to correctly account for this prefix. |
|
For example, if the axis label's units are set to 'V', then a tick value of 0.001 might |
|
be accompanied by a scale value of 1000. This indicates that the label is displaying 'mV', and |
|
thus the tick should display 0.001 * 1000 = 1. |
|
Copied rom pyqtgraph; we needed it here. |
|
""" |
|
if spacing is None: |
|
spacing = np.mean(np.diff(values)) |
|
places = max(0, np.ceil(-np.log10(spacing * scale))) + tickPlacesAdd |
|
strings = [] |
|
for v in values: |
|
vs = v * scale |
|
if abs(vs) < 0.001 or abs(vs) >= 10000: |
|
vstr = "%g" % vs |
|
else: |
|
vstr = ("%%0.%df" % places) % vs |
|
strings.append(vstr) |
|
return strings |
|
|
|
|
|
def crossAxes(axl, xyzero=[0.0, 0.0], limits=[None, None, None, None], **kwds): |
|
""" |
|
Make the plot(s) have crossed axes at the data points set by xyzero, and optionally |
|
set axes limits |
|
|
|
Parameters |
|
---------- |
|
axl : pyqtgraph plot/axes instance or list |
|
the plot to modify |
|
xyzero : list |
|
A 2-element list for the placement of x=0 and y=0, defaults to [0., 0.] |
|
limits : list |
|
A 4-element list with the min and max limits of the axes, defaults to all None |
|
**kwds : keyword arguments to pass to make_crossedAxes |
|
|
|
""" |
|
if type(axl) is not list: |
|
axl = [axl] |
|
for ax in axl: |
|
make_crossedAxes(ax, xyzero, limits, **kwds) |
|
|
|
|
|
def make_crossedAxes( |
|
ax, |
|
xyzero=[0.0, 0.0], |
|
limits=[None, None, None, None], |
|
ndec=3, |
|
density=(1.0, 1.0), |
|
tickl=0.0125, |
|
insideMargin=0.05, |
|
pointSize=12, |
|
tickPlacesAdd=(0, 0), |
|
): |
|
""" |
|
Parameters |
|
---------- |
|
axl : pyqtgraph plot/axes instance or list |
|
the plot to modify |
|
xyzero : list |
|
A 2-element list for the placement of x=0 and y=0, defaults to [0., 0.] |
|
limits : list |
|
A 4-element list with the min and max limits of the axes, defaults to all None |
|
ndec : int |
|
Number of decimals (would be passed to talbotTicks if that was being called) |
|
density : tuple |
|
tick density (for talbotTicks), defaults to (1.0, 1.0) |
|
tickl : float |
|
Tick length, defaults to 0.0125 |
|
insideMargin : float |
|
Inside margin space for plot, defaults to 0.05 (5%) |
|
pointSize : int |
|
point size for tick text, defaults to 12 |
|
tickPlacesAdd : tuple |
|
number of decimal places to add in tickstrings for the ticks, pair for x and y axes, defaults to (0,0) |
|
|
|
Returns |
|
------- |
|
Nothing |
|
|
|
|
|
""" |
|
# get axis limits |
|
aleft = ax.getAxis("left") |
|
abottom = ax.getAxis("bottom") |
|
aleft.setPos(pg.Point(3.0, 0.0)) |
|
yRange = aleft.range |
|
xRange = abottom.range |
|
hl = pg.InfiniteLine(pos=xyzero[0], angle=90, pen=pg.mkPen("k")) |
|
ax.addItem(hl) |
|
vl = pg.InfiniteLine(pos=xyzero[1], angle=0, pen=pg.mkPen("k")) |
|
ax.addItem(vl) |
|
ax.hideAxis("bottom") |
|
ax.hideAxis("left") |
|
# now create substitue tick marks and labels, using Talbot et al algorithm |
|
xr = np.diff(xRange)[0] |
|
yr = np.diff(yRange)[0] |
|
xmin, xmax = ( |
|
np.min(xRange) - xr * insideMargin, |
|
np.max(xRange) + xr * insideMargin, |
|
) |
|
ymin, ymax = ( |
|
np.min(yRange) - yr * insideMargin, |
|
np.max(yRange) + yr * insideMargin, |
|
) |
|
xtick = ticks.Extended( |
|
density=density[0], figure=None, range=(xmin, xmax), axis="x" |
|
) |
|
ytick = ticks.Extended( |
|
density=density[1], figure=None, range=(ymin, ymax), axis="y" |
|
) |
|
xt = xtick() |
|
yt = ytick() |
|
ytk = yr * tickl |
|
xtk = xr * tickl |
|
y0 = xyzero[1] |
|
x0 = xyzero[0] |
|
tsx = tickStrings(xt, tickPlacesAdd=tickPlacesAdd[0]) |
|
tsy = tickStrings(yt, tickPlacesAdd=tickPlacesAdd[1]) |
|
for i, x in enumerate(xt): |
|
t = pg.PlotDataItem(x=x * np.ones(2), y=[y0 - ytk, y0 + ytk], pen=pg.mkPen("k")) |
|
ax.addItem(t) # tick mark |
|
# put text in only if it does not overlap the opposite line |
|
if x == y0: |
|
continue |
|
txt = pg.TextItem( |
|
tsx[i], anchor=(0.5, 0), color=pg.mkColor("k") |
|
) # , size='10pt') |
|
txt.setFont(pg.QtGui.QFont("Arial", pointSize=pointSize)) |
|
txt.setPos(pg.Point(x, y0 - ytk)) |
|
ax.addItem(txt) # , pos=pg.Point(x, y0-ytk)) |
|
for i, y in enumerate(yt): |
|
t = pg.PlotDataItem( |
|
x=np.array([x0 - xtk, x0 + xtk]), y=np.ones(2) * y, pen=pg.mkPen("k") |
|
) |
|
ax.addItem(t) |
|
if y == x0: |
|
continue |
|
txt = pg.TextItem( |
|
tsy[i], anchor=(1, 0.5), color=pg.mkColor("k") |
|
) # , size='10pt') |
|
txt.setFont(pg.QtGui.QFont("Arial", pointSize=pointSize)) |
|
txt.setPos(pg.Point(x0 - xtk, y)) |
|
ax.addItem(txt) # , pos=pg.Point(x, y0-ytk)) |
|
|
|
|
|
class polarPlot: |
|
""" |
|
Create a polar plot, as a PlotItem for pyqtgraph. |
|
|
|
|
|
""" |
|
|
|
def __init__(self, plot=None): |
|
""" |
|
Instantiate a plot as a polar plot |
|
|
|
Parmeters |
|
--------- |
|
plot : pyqtgraph plotItem |
|
the plot that will be converted to a polar plot, defaults to None |
|
if None, then a new PlotItem will be created, accessible |
|
as polarPlot.plotItem |
|
""" |
|
if plot is None: |
|
self.plotItem = pg.PlotItem() # create a plot item for the plot |
|
else: |
|
self.plotItem = plot |
|
self.plotItem.setAspectLocked() |
|
self.plotItem.hideAxis("bottom") |
|
self.plotItem.hideAxis("left") |
|
self.gridSet = False |
|
self.data = None |
|
self.rMax = None |
|
|
|
def setAxes(self, steps=4, rMax=None, makeGrid=True): |
|
""" |
|
Make the polar plot axes |
|
|
|
Parameters |
|
---------- |
|
steps : int, optional |
|
The number of radial steps for the grid, defaults to 4 |
|
rMax : float, optional |
|
The maximum radius of the plot, defaults to None (the rMax is 1) |
|
makeGrid : boolean, optional |
|
Whether the grid will actually be plotted or not, defaults to True |
|
|
|
""" |
|
if makeGrid is False or self.gridSet: |
|
return |
|
if rMax is None: |
|
if self.data is None: |
|
rMax = 1.0 |
|
else: |
|
rMax = np.max(self.data["y"]) |
|
self.rMax = rMax |
|
# Add radial grid lines (theta markers) |
|
gridPen = pg.mkPen(width=0.55, color="k", style=pg.QtCore.Qt.DotLine) |
|
ringPen = pg.mkPen(width=0.75, color="k", style=pg.QtCore.Qt.SolidLine) |
|
for th in np.linspace(0.0, np.pi * 2, 8, endpoint=False): |
|
rx = np.cos(th) * rMax |
|
ry = np.sin(th) * rMax |
|
self.plotItem.plot(x=[0, rx], y=[0.0, ry], pen=gridPen) |
|
ang = th * 360.0 / (np.pi * 2) |
|
# anchor is odd: 0,0 is upper left corner, 1,1 is lower right corner |
|
if ang < 90.0: |
|
x = 0.0 |
|
y = 0.5 |
|
elif ang == 90.0: |
|
x = 0.5 |
|
y = 1 |
|
elif ang < 180: |
|
x = 1.0 |
|
y = 0.5 |
|
elif ang == 180.0: |
|
x = 1 |
|
y = 0.5 |
|
elif ang < 270: |
|
x = 1 |
|
y = 0 |
|
elif ang == 270.0: |
|
x = 0.5 |
|
y = 0 |
|
elif ang < 360: |
|
x = 0 |
|
y = 0 |
|
ti = pg.TextItem("%d" % (int(ang)), color=pg.mkColor("k"), anchor=(x, y)) |
|
self.plotItem.addItem(ti) |
|
ti.setPos(rx, ry) |
|
# add polar grid lines (r) |
|
for gr in np.linspace(rMax / steps, rMax, steps): |
|
circle = pg.QtGui.QGraphicsEllipseItem(-gr, -gr, gr * 2, gr * 2) |
|
if gr < rMax: |
|
circle.setPen(gridPen) |
|
else: |
|
circle.setPen(ringPen) |
|
self.plotItem.addItem(circle) |
|
ti = pg.TextItem("%d" % (int(gr)), color=pg.mkColor("k"), anchor=(1, 1)) |
|
ti.setPos(gr, 0.0) |
|
self.plotItem.addItem(ti) |
|
self.gridSet = True |
|
|
|
def plot( |
|
self, |
|
r, |
|
theta, |
|
vectors=False, |
|
arrowhead=True, |
|
normalize=False, |
|
sort=False, |
|
**kwds |
|
): |
|
""" |
|
plot puts the data into a polar plot. |
|
the plot will be converted to a polar graph |
|
|
|
Parameters |
|
---------- |
|
r : list or numpy array |
|
a list or array of radii |
|
theta : list or numpy array |
|
a list or array of angles (in radians) corresponding to the values in r |
|
vectors : boolean, optional |
|
vectors True means that plot is composed of vectors to each point radiating from the origin, defaults to False |
|
arrowhead : boolean, optional |
|
arrowhead True plots arrowheads at the end of the vectors, defaults to True |
|
normalize : boolean, optional |
|
normalize forces the plot to be scaled to the max values in r, defaults to False |
|
sort : boolean, optional |
|
causes data r, theta to be sorted by theta, defaults to False |
|
**kwds are passed to the data plot call. |
|
|
|
""" |
|
|
|
# sort r, theta by r |
|
|
|
rs = np.array(r) |
|
thetas = np.array(theta) |
|
|
|
if sort: |
|
indx = np.argsort(thetas) |
|
theta = thetas |
|
if not isinstance(indx, np.int64): |
|
for i, j in enumerate(indx): |
|
rs[i] = r[j] |
|
thetas[i] = theta[j] |
|
|
|
# Transform to cartesian and plot |
|
if normalize: |
|
rs = rs / np.max(rs) |
|
x = rs * np.cos(thetas) |
|
y = rs * np.sin(thetas) |
|
try: |
|
len(x) |
|
except: |
|
x = [x] |
|
y = [y] |
|
if vectors: # plot r,theta as lines from origin |
|
for i, xi in enumerate(x): |
|
# print x[i], y[i] |
|
if arrowhead: |
|
arrowAngle = -( |
|
thetas[i] * 360 / (2 * np.pi) + 180 |
|
) # convert to degrees, and correct orientation |
|
arrow = pg.ArrowItem( |
|
angle=arrowAngle, tailLen=0, tailWidth=1.5, **kwds |
|
) |
|
arrow.setPos(x[i], y[i]) |
|
self.plotItem.addItem(arrow) |
|
self.plotItem.plot([0.0, x[i]], [0.0, y[i]], **kwds) |
|
|
|
else: |
|
self.plotItem.plot(x, y, **kwds) |
|
self.rMax = np.max(y) |
|
self.data = {"x": x, "y": y} |
|
|
|
def hist( |
|
self, |
|
r, |
|
theta, |
|
binwidth=np.pi / 6.0, |
|
normalize=False, |
|
density=False, |
|
mode="straight", |
|
**kwds |
|
): |
|
""" |
|
plot puts the data into a polar plot as a histogram of the number of observations |
|
within a wedge |
|
the plot will be converted to a polar graph |
|
|
|
Parameters |
|
---------- |
|
r : list or numpy array |
|
a list or array of radii |
|
theta : list or numpy array |
|
a list or array of angles (in radians) corresponding to the values in r |
|
binwidth : bin width, in radians optional |
|
vectors True means that plot is composed of vectors to each point radiating from the origin, defaults to 30 degrees (np.pi/6) |
|
normalize : boolean, optional |
|
normalize forces the plot to be scaled to the max values in r, defaults to False |
|
density : boolean, optional |
|
plot a count histogram, or a density histogram weighted by r values, defaults to False |
|
mode : str, optional |
|
'straight' selects straight line between bars. 'arc' makes the end of the bar an arc (truer representation), defaults to 'straight' |
|
**kwds are passed to the data plot call. |
|
|
|
Returns |
|
------- |
|
tuple : (list of rHist, list of bins) |
|
The histogram that was plotted (use for statistical comparisions) |
|
""" |
|
|
|
rs = np.array(r) |
|
thetas = np.array(theta) |
|
twopi = np.pi * 2.0 |
|
for i, t in enumerate(thetas): # restrict to positive half plane [0....2*pi] |
|
while t < 0.0: |
|
t += twopi |
|
while t > twopi: |
|
t -= twopi |
|
thetas[i] = t |
|
bins = np.arange(0, np.pi * 2 + 1e-12, binwidth) |
|
# compute histogram |
|
(rhist, rbins) = np.histogram(thetas, bins=bins, weights=rs, density=density) |
|
# Transform to cartesian and plot |
|
if normalize: |
|
rhist = rhist / np.max(rhist) |
|
xo = rhist * np.cos(bins[:-1]) # get cartesian form |
|
xp = rhist * np.cos(bins[:-1] + binwidth) |
|
yo = rhist * np.sin(bins[:-1]) |
|
yp = rhist * np.sin(bins[:-1] + binwidth) |
|
arcinc = np.pi / 100.0 # arc increments |
|
for i in range(len(xp)): |
|
if mode is "arc": |
|
self.plotItem.plot( |
|
[xo[i], 0.0, xp[i]], [yo[i], 0.0, yp[i]], **kwds |
|
) # "v" segement |
|
arcseg = np.arange(bins[i], bins[i + 1], arcinc) |
|
x = np.array(rhist[i] * np.cos(arcseg)) |
|
y = np.array(rhist[i] * np.sin(arcseg)) |
|
self.plotItem.plot(x, y, **kwds) |
|
|
|
else: |
|
self.plotItem.plot( |
|
[0.0, xo[i], xp[i], 0.0], [0.0, yo[i], yp[i], 0.0], **kwds |
|
) |
|
self.data = {"x": xo, "y": yo} |
|
self.rMax = np.max(yo) |
|
return (rhist, rbins) |
|
|
|
def circmean(self, alpha, axis=None): |
|
""" |
|
Compute the circular mean of a set of angles along the axis |
|
|
|
Parameters |
|
---------- |
|
alpha : numpy array |
|
the angles to compute the circular mean of |
|
axis : int |
|
The axis of alpha for the computatoin, defaults to None |
|
|
|
Returns |
|
------- |
|
float : the mean angle |
|
""" |
|
mean_angle = np.arctan2( |
|
np.mean(np.sin(alpha), axis), np.mean(np.cos(alpha), axis) |
|
) |
|
return mean_angle |
|
|
|
|
|
def talbotTicks(axl, **kwds): |
|
""" |
|
Adjust the tick marks using the talbot et al algorithm, on an existing plot. |
|
""" |
|
if type(axl) is not list: |
|
axl = [axl] |
|
for ax in axl: |
|
do_talbotTicks(ax, **kwds) |
|
|
|
|
|
def do_talbotTicks( |
|
ax, |
|
ndec=3, |
|
density=(1.0, 1.0), |
|
insideMargin=0.05, |
|
pointSize=None, |
|
tickPlacesAdd=(0, 0), |
|
): |
|
""" |
|
Change the axis ticks to use the talbot algorithm for ONE axis |
|
Paramerters control the ticks |
|
|
|
Parameters |
|
---------- |
|
ax : pyqtgraph axis instance |
|
the axis to change the ticks on |
|
ndec : int |
|
Number of decimals (would be passed to talbotTicks if that was being called) |
|
density : tuple |
|
tick density (for talbotTicks), defaults to (1.0, 1.0) |
|
insideMargin : float |
|
Inside margin space for plot, defaults to 0.05 (5%) |
|
pointSize : int |
|
point size for tick text, defaults to 12 |
|
tickPlacesAdd : tuple |
|
number of decimal places to add in tickstrings for the ticks, pair for x and y axes, defaults to (0,0) |
|
|
|
""" |
|
# get axis limits |
|
aleft = ax.getAxis("left") |
|
abottom = ax.getAxis("bottom") |
|
yRange = aleft.range |
|
xRange = abottom.range |
|
# now create substitue tick marks and labels, using Talbot et al algorithm |
|
xr = np.diff(xRange)[0] |
|
yr = np.diff(yRange)[0] |
|
xmin, xmax = ( |
|
np.min(xRange) - xr * insideMargin, |
|
np.max(xRange) + xr * insideMargin, |
|
) |
|
ymin, ymax = ( |
|
np.min(yRange) - yr * insideMargin, |
|
np.max(yRange) + yr * insideMargin, |
|
) |
|
xtick = ticks.Extended( |
|
density=density[0], figure=None, range=(xmin, xmax), axis="x" |
|
) |
|
ytick = ticks.Extended( |
|
density=density[1], figure=None, range=(ymin, ymax), axis="y" |
|
) |
|
xt = xtick() |
|
yt = ytick() |
|
xts = tickStrings(xt, scale=1, spacing=None, tickPlacesAdd=tickPlacesAdd[0]) |
|
yts = tickStrings(yt, scale=1, spacing=None, tickPlacesAdd=tickPlacesAdd[1]) |
|
xtickl = [[(x, xts[i]) for i, x in enumerate(xt)], []] # no minor ticks here |
|
ytickl = [[(y, yts[i]) for i, y in enumerate(yt)], []] # no minor ticks here |
|
|
|
# ticks format: [ (majorTickValue1, majorTickString1), (majorTickValue2, majorTickString2), ... ], |
|
aleft.setTicks(ytickl) |
|
abottom.setTicks(xtickl) |
|
# now set the point size (this may affect spacing from axis, and that would have to be adjusted - see the pyqtgraph google groups) |
|
if pointSize is not None: |
|
b = pg.QtGui.QFont() |
|
b.setPixelSize(pointSize) |
|
aleft.tickFont = b |
|
abottom.tickFont = b |
|
|
|
|
|
def violinPlotScatter(ax, data, symbolColor="k", symbolSize=4, symbol="o"): |
|
""" |
|
Plot data as violin plot with scatter and error bar |
|
|
|
Parameters |
|
---------- |
|
ax : pyqtgraph plot instance |
|
is the axs to plot into |
|
data : dict |
|
dictionary containing {pos1: data1, pos2: data2}, where pos is the x position for the data in data. Each data |
|
set iis plotted as a separate column |
|
symcolor : string, optional |
|
color of the symbols, defaults to 'k' (black) |
|
symbolSize : int, optional |
|
Size of the symbols in the scatter plot, points, defaults to 4 |
|
symbol : string, optoinal |
|
The symbol to use, defaults to 'o' (circle) |
|
""" |
|
|
|
y = [] |
|
x = [] |
|
xb = np.arange(0, len(data.keys()), 1) |
|
ybm = [0] * len(data.keys()) # np.zeros(len(sdat.keys())) |
|
ybs = [0] * len(data.keys()) # np.zeros(len(sdat.keys())) |
|
for i, k in enumerate(data.keys()): |
|
yvals = np.array(data[k]) |
|
xvals = pg.pseudoScatter(yvals, spacing=0.4, bidir=True) * 0.2 |
|
ax.plot( |
|
x=xvals + i, |
|
y=yvals, |
|
pen=None, |
|
symbol=symbol, |
|
symbolSize=symbolSize, |
|
symbolBrush=pg.mkBrush(symbolColor), |
|
) |
|
y.append(yvals) |
|
x.append([i] * len(yvals)) |
|
ybm[i] = np.nanmean(yvals) |
|
ybs[i] = np.nanstd(yvals) |
|
mbar = pg.PlotDataItem( |
|
x=np.array([xb[i] - 0.2, xb[i] + 0.2]), |
|
y=np.array([ybm[i], ybm[i]]), |
|
pen={"color": "k", "width": 0.75}, |
|
) |
|
ax.addItem(mbar) |
|
bar = pg.ErrorBarItem( |
|
x=xb, |
|
y=np.array(ybm), |
|
height=np.array(ybs), |
|
beam=0.2, |
|
pen={"color": "k", "width": 0.75}, |
|
) |
|
violin_plot(ax, y, xb, bp=False) |
|
ax.addItem(bar) |
|
ticks = [[(v, k) for v, k in enumerate(data.keys())], []] |
|
ax.getAxis("bottom").setTicks(ticks) |
|
|
|
|
|
def violin_plot(ax, data, pos, dist=0.0, bp=False): |
|
""" |
|
create violin plots on an axis |
|
""" |
|
|
|
if data is None or len(data) == 0: |
|
return # skip trying to do the plot |
|
|
|
dist = max(pos) - min(pos) |
|
w = min(0.15 * max(dist, 1.0), 0.5) |
|
for i, d in enumerate(data): |
|
if d == [] or len(d) == 0: |
|
continue |
|
k = scipy.stats.gaussian_kde(d) # calculates the kernel density |
|
m = k.dataset.min() # lower bound of violin |
|
M = k.dataset.max() # upper bound of violin |
|
y = np.arange(m, M, (M - m) / 100.0) # support for violin |
|
v = k.evaluate(y) # violin profile (density curve) |
|
v = v / v.max() * w # scaling the violin to the available space |
|
c1 = pg.PlotDataItem(y=y, x=pos[i] + v, pen=pg.mkPen("k", width=0.5)) |
|
c2 = pg.PlotDataItem(y=y, x=pos[i] - v, pen=pg.mkPen("k", width=0.5)) |
|
# mean = k.dataset.mean() |
|
# vm = k.evaluate(mean) |
|
# vm = vm * w |
|
# ax.plot(x=np.array([pos[i]-vm[0], pos[i]+vm[0]]), y=np.array([mean, mean]), pen=pg.mkPen('k', width=1.0)) |
|
ax.addItem(c1) |
|
ax.addItem(c2) |
|
# ax.addItem(hbar) |
|
f = pg.FillBetweenItem( |
|
curve1=c1, curve2=c2, brush=pg.mkBrush((255, 255, 0, 96)) |
|
) |
|
ax.addItem(f) |
|
|
|
if bp: |
|
pass |
|
# bpf = ax.boxplot(data, notch=0, positions=pos, vert=1) |
|
# pylab.setp(bpf['boxes'], color='black') |
|
# pylab.setp(bpf['whiskers'], color='black', linestyle='-') |
|
|
|
|
|
def labelAxes(plot, xtext, ytext, **kwargs): |
|
""" |
|
helper to label up the plot |
|
|
|
Parameters |
|
----------- |
|
plot : plot item |
|
xtext : string |
|
text for x axis |
|
ytext : string |
|
text for y axis |
|
**kwargs : keywords |
|
additional arguments to pass to pyqtgraph setLabel |
|
""" |
|
|
|
plot.setLabel("bottom", xtext, **kwargs) |
|
plot.setLabel("left", ytext, **kwargs) |
|
|
|
|
|
def labelPanels(plot, label=None, **kwargs): |
|
""" |
|
helper to label up the plot |
|
Inputs: plot item |
|
text for x axis |
|
text for yaxis |
|
plot title (on top) OR |
|
plot panel label (for example, "A", "A1") |
|
""" |
|
|
|
if label is not None: |
|
setPlotLabel(plot, plotlabel="%s" % label, **kwargs) |
|
else: |
|
setPlotLabel(plot, plotlabel="") |
|
|
|
|
|
def labelTitles(plot, title=None, **kwargs): |
|
""" |
|
Set the title of a plotitem. Basic HTML formatting is allowed, along |
|
with "size", "bold", "italic", etc.. |
|
If the title is not defined, then a blank label is used |
|
A title is a text label that appears centered above the plot, in |
|
QGridLayout (position 0,2) of the plotitem. |
|
params |
|
------- |
|
:param plotitem: The plot item to label |
|
:param title: The text string to use for the label |
|
:kwargs: keywords to pass to the pg.LabelItem |
|
:return: None |
|
|
|
""" |
|
|
|
if title is not None: |
|
plot.setTitle(title="<b><large>%s</large></b>" % title, visible=True, **kwargs) |
|
else: # clear the plot title |
|
plot.setTitle(title=" ") |
|
|
|
|
|
def setPlotLabel(plotitem, plotlabel="", **kwargs): |
|
""" |
|
Set the plotlabel of a plotitem. Basic HTML formatting is allowed, along |
|
with "size", "bold", "italic", etc.. |
|
If plotlabel is not defined, then a blank label is used |
|
A plotlabel is a text label that appears the upper left corner of the |
|
QGridLayout (position 0,0) of the plotitem. |
|
params |
|
------- |
|
:param plotitem: The plot item to label |
|
:param plotlabel: The text string to use for the label |
|
:kwargs: keywords to pass to the pg.LabelItem |
|
:return: None |
|
|
|
""" |
|
|
|
plotitem.LabelItem = pg.LabelItem(plotlabel, **kwargs) |
|
plotitem.LabelItem.setMaximumHeight(30) |
|
plotitem.layout.setRowFixedHeight(0, 30) |
|
plotitem.layout.addItem(plotitem.LabelItem, 0, 0) |
|
plotitem.LabelItem.setVisible(True) |
|
|
|
|
|
class LayoutMaker: |
|
def __init__( |
|
self, |
|
win=None, |
|
cols=1, |
|
rows=1, |
|
letters=True, |
|
titles=False, |
|
labelEdges=True, |
|
margins=4, |
|
spacing=4, |
|
ticks="default", |
|
): |
|
self.sequential_letters = string.ascii_uppercase |
|
self.cols = cols |
|
self.rows = rows |
|
self.letters = letters |
|
self.titles = titles |
|
self.edges = labelEdges |
|
self.margins = margins |
|
self.spacing = spacing |
|
self.rcmap = [None] * cols * rows |
|
self.plots = None |
|
self.win = win |
|
self.ticks = ticks |
|
self._makeLayout( |
|
letters=letters, titles=titles, margins=margins, spacing=spacing |
|
) |
|
# self.addLayout(win) |
|
|
|
# def addLayout(self, win=None): |
|
# if win is not None: |
|
# win.setLayout(self.gridLayout) |
|
|
|
def getCols(self): |
|
return self.cols |
|
|
|
def getRows(self): |
|
return self.rows |
|
|
|
def mapFromIndex(self, index): |
|
""" |
|
for a given index, return the row, col tuple associated with the index |
|
""" |
|
return self.rcmap[index] |
|
|
|
def getPlot(self, index): |
|
""" |
|
return the plot item in the list corresponding to the index n |
|
""" |
|
if isinstance(index, tuple): |
|
r, c = index |
|
elif isinstance(index, int): |
|
r, c = self.rcmap[index] |
|
else: |
|
raise ValueError( |
|
"pyqtgraphPlotHelpers, LayoutMaker plot: index must be int or tuple(r,c)" |
|
) |
|
return self.plots[r][c] |
|
|
|
def plot(self, index, *args, **kwargs): |
|
p = self.getPlot(index).plot(*args, **kwargs) |
|
if self.ticks == "talbot": |
|
talbotTicks(self.getPlot(index)) |
|
return p |
|
|
|
def _makeLayout(self, letters=True, titles=True, margins=4, spacing=4): |
|
""" |
|
Create a multipanel plot. |
|
The pyptgraph elements (widget, gridlayout, plots) are stored as class variables. |
|
The layout is always a rectangular grid with shape (cols, rows) |
|
if letters is true, then the plot is labeled "A, B, C..." Indices move horizontally first, then vertically |
|
margins sets the margins around the outside of the plot |
|
spacing sets the spacing between the elements of the grid |
|
If a window was specified (self.win is not None) then the grid layout will derive from that window's central |
|
item; otherwise we just make a gridLayout that can be put into another container somewhere. |
|
""" |
|
import string |
|
|
|
if self.win is not None: |
|
self.gridLayout = ( |
|
self.win.ci.layout |
|
) # the window's 'central item' is the main gridlayout. |
|
else: |
|
self.gridLayout = ( |
|
pg.QtGui.QGridLayout() |
|
) # just create the grid layout to add to another item |
|
self.gridLayout.setContentsMargins(margins, margins, margins, margins) |
|
self.gridLayout.setSpacing(spacing) |
|
self.plots = [[0 for x in xrange(self.cols)] for x in xrange(self.rows)] |
|
i = 0 |
|
for r in range(self.rows): |
|
for c in range(self.cols): |
|
self.plots[r][c] = self.win.addPlot(row=r, col=c) # pg.PlotWidget() |
|
if letters: |
|
labelPanels( |
|
self.plots[r][c], |
|
label=self.sequential_letters[i], |
|
size="14pt", |
|
bold=True, |
|
) |
|
if titles: |
|
labelTitles( |
|
self.plots[r][c], |
|
title=self.sequential_letters[i], |
|
size="14pt", |
|
bold=False, |
|
) |
|
|
|
self.rcmap[i] = (r, c) |
|
i += 1 |
|
if i > 25: |
|
i = 0 |
|
self.labelEdges("T(s)", "Y", edgeOnly=self.edges) |
|
|
|
def labelEdges(self, xlabel="T(s)", ylabel="Y", edgeOnly=True, **kwargs): |
|
""" |
|
label the axes on the outer edges of the gridlayout, leaving the interior axes clean |
|
""" |
|
(lastrow, lastcol) = self.rcmap[-1] |
|
i = 0 |
|
for (r, c) in self.rcmap: |
|
if c == 0: |
|
ylab = ylabel |
|
elif edgeOnly: |
|
ylab = "" |
|
else: |
|
ylab = ylabel |
|
if r == self.rows - 1: # only the last row |
|
xlab = xlabel |
|
elif edgeOnly: # but not other rows |
|
xlab = "" |
|
else: |
|
xlab = xlabel # otherwise, label it |
|
labelAxes(self.plots[r][c], xlab, ylab, **kwargs) |
|
i += 1 |
|
|
|
def axesEdges(self, edgeOnly=True): |
|
""" |
|
text labesls only on the axes on the outer edges of the gridlayout, |
|
leaving the interior axes clean |
|
""" |
|
(lastrow, lastcol) = self.rcmap[-1] |
|
i = 0 |
|
for (r, c) in self.rcmap: |
|
xshow = True |
|
yshow = True |
|
if edgeOnly and c > 0: |
|
yshow = False |
|
if edgeOnly and r < self.rows: # only the last row |
|
yshow = False |
|
ax = self.getPlot((r, c)) |
|
leftaxis = ax.getAxis("left") |
|
bottomaxis = ax.getAxis("bottom") |
|
# print dir(self.plots[r][c]) |
|
leftaxis.showValues = yshow |
|
bottomaxis.showValues = xshow |
|
i += 1 |
|
|
|
def columnAutoScale(self, col, axis="left"): |
|
""" |
|
autoscale the columns according to the max value in the column. |
|
Finds outside range of column data, then sets the scale of all plots |
|
in the column to that range |
|
""" |
|
atmax = None |
|
atmin = None |
|
for (r, c) in self.rcmap: |
|
if c != col: |
|
continue |
|
ax = self.getPlot((r, c)) |
|
thisaxis = ax.getAxis(axis) |
|
amin, amax = thisaxis.range |
|
if atmax is None: |
|
atmax = amax |
|
else: |
|
if amax > atmax: |
|
atmax = amax |
|
if atmin is None: |
|
atmin = amin |
|
else: |
|
if amin > atmin: |
|
atmin = amin |
|
|
|
self.columnSetScale(col, axis=axis, range=(atmin, atmax)) |
|
return (atmin, atmax) |
|
|
|
def columnSetScale(self, col, axis="left", range=(0.0, 1.0)): |
|
""" |
|
Set the column scale |
|
""" |
|
for (r, c) in self.rcmap: |
|
if c != col: |
|
continue |
|
ax = self.getPlot((r, c)) |
|
if axis == "left": |
|
ax.setYRange(range[0], range[1]) |
|
elif axis == "bottom": |
|
ax.setXRange(range[0], range[1]) |
|
|
|
if self.ticks == "talbot": |
|
talbotTicks(ax) |
|
|
|
def title(self, index, title="", **kwargs): |
|
""" |
|
add a title to a specific plot (specified by index) in the layout |
|
""" |
|
labelTitles(self.getPlot(index), title=title, **kwargs) |
|
|
|
|
|
def figure(title=None, background="w"): |
|
if background == "w": |
|
pg.setConfigOption("background", "w") # set background to white |
|
pg.setConfigOption("foreground", "k") |
|
pg.mkQApp() |
|
win = pg.GraphicsWindow(title=title) |
|
return win |
|
|
|
|
|
def show(): |
|
pg.QApplication.instance().exec_() |
|
|
|
|
|
def test_layout(win): |
|
""" |
|
Test the various plot types and modifications provided by the helpers above, |
|
in the context of a layout with various kinds of plots. |
|
""" |
|
layout = LayoutMaker(cols=4, rows=2, win=win, labelEdges=True, ticks="talbot") |
|
x = np.arange(0, 10.0, 0.1) |
|
y = np.sin(x * 3.0) # make an interesting signal |
|
r = np.random.random(10) # and a random signal |
|
theta = np.linspace(0, 2.0 * np.pi, 10, endpoint=False) # r, theta for polar plots |
|
for n in range(4 * 2): |
|
if n not in [1, 2, 3, 4]: |
|
layout.plot(n, x, y) |
|
p = layout.getPlot(n) |
|
if n == 0: # crossed axes plot |
|
crossAxes( |
|
p, |
|
xyzero=[5.0, 0.0], |
|
density=(0.75, 1.5), |
|
tickPlacesAdd=(1, 0), |
|
pointSize=12, |
|
) |
|
layout.title(n, "Crossed Axes") |
|
if n in [1, 2, 3]: # two differnt forms of polar plots |
|
if n == 1: |
|
po = polarPlot(p) |
|
po.setAxes(rMax=np.max(r)) |
|
po.plot(r, theta, pen=pg.mkPen("r")) |
|
layout.title(n, "Polar Path") |
|
|
|
if n == 2: |
|
po = polarPlot(p) |
|
po.plot(r, theta, vectors=True, pen=pg.mkPen("k", width=2.0)) |
|
po.setAxes(rMax=np.max(r)) |
|
po.plot( |
|
[np.mean(r)], |
|
[po.circmean(theta)], |
|
vectors=True, |
|
pen=pg.mkPen("r", width=2.0), |
|
) |
|
layout.title(n, "Polar Arrows") |
|
if n == 3: |
|
po = polarPlot(p) |
|
po.hist( |
|
r, |
|
theta, |
|
binwidth=np.pi / 6.0, |
|
normalize=False, |
|
density=False, |
|
pen="r", |
|
) |
|
po.hist( |
|
r, |
|
theta, |
|
binwidth=np.pi / 6.0, |
|
normalize=False, |
|
density=False, |
|
mode="arc", |
|
pen="b", |
|
) |
|
po.setAxes(rMax=None) |
|
layout.title(n, "Polar Histogram") |
|
|
|
if n == 4: # violin plot with scatter plot data |
|
data = { |
|
2: [3, 5, 7, 9, 2, 4, 6, 8, 7, 2, 3, 1, 2.5], |
|
3: [5, 6, 7, 9, 2, 8, 10, 9.5, 11], |
|
} |
|
violinPlotScatter(p, data, symbolColor="r") |
|
p.setYRange(0, 12) |
|
layout.title(n, "Violin Plots with PseudoScatter") |
|
|
|
if ( |
|
n == 5 |
|
): # clean plot for physiology with baseline reference and a calibration bar |
|
calbar( |
|
p, |
|
calbar=[7.0, -1.5, 2.0, 0.5], |
|
axesoff=True, |
|
orient="left", |
|
unitNames={"x": "ms", "y": "nA"}, |
|
) |
|
refline(p, refline=0.0, color=[64, 64, 64], linestyle="--", linewidth=0.5) |
|
layout.title(n, "Calbar and Refline") |
|
|
|
# talbotTicks(layout.getPlot(1)) |
|
layout.columnAutoScale(col=3, axis="left") |
|
show() |
|
|
|
|
|
def test_crossAxes(win): |
|
layout = LayoutMaker(cols=1, rows=1, win=win, labelEdges=True) |
|
x = np.arange(-1, 1.0, 0.01) |
|
y = np.sin(x * 10.0) |
|
layout.plot(0, x, y) |
|
p = layout.getPlot(0) |
|
crossAxes( |
|
p, |
|
xyzero=[0.0, 0.0], |
|
limits=[None, None, None, None], |
|
density=1.5, |
|
tickPlacesAdd=1, |
|
pointSize=12, |
|
) |
|
show() |
|
|
|
|
|
def test_polarPlot(win): |
|
layout = LayoutMaker(cols=1, rows=1, win=win, labelEdges=True) |
|
po = polarPlot(layout.getPlot((0, 0))) # convert rectangular plot to polar |
|
po.setAxes(steps=4, rMax=100, makeGrid=True) # build the axes |
|
nvecs = 50 |
|
# th = np.linspace(-np.pi*2, np.pi*2-np.pi*2/nvecs, nvecs) |
|
th = np.linspace(-np.pi * 4, 0, nvecs) |
|
r = np.linspace(10, 100, nvecs) |
|
po.plot( |
|
r, th, vectors=True, arrowhead=True, symbols="o", pen=pg.mkPen("k", width=1.5) |
|
) # plot with arrowheads |
|
nvecs = 8 |
|
th = np.linspace(-np.pi * 2, np.pi * 2 - np.pi * 2 / nvecs, nvecs) |
|
r = np.linspace(10, 100, nvecs) |
|
# po.plot(r, th, vectors=True, arrowhead=False, symbols='o', pen=pg.mkPen('r', width=1.5)) # plot with just lines |
|
|
|
show() |
|
|
|
|
|
if __name__ == "__main__": |
|
win = figure(title="testing") |
|
test_layout(win) |
|
# test_crossAxes(win) |
|
# test_polarPlot(win)
|
|
|