Skip to content

Commit

Permalink
Merge pull request #395 from jeremiahnlin/log_scale
Browse files Browse the repository at this point in the history
Added 20*log10(mag) for Pyqtgraph
  • Loading branch information
marcosfrenkel authored Jun 14, 2023
2 parents dad8bc2 + d5c1ed2 commit ed6c541
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 23 deletions.
38 changes: 38 additions & 0 deletions plottr/plot/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ class PlotDataType(Enum):
#: grid data with 2 dependents
grid2d = auto()

#: logarithmic scatter-type data with 1 dependent (data is not on a grid)
log10_scatter1d = auto()

#: logarithmic line data with 1 dependent (data is on a grid)
log10_line1d = auto()


class ComplexRepresentation(LabeledOptions):
"""Options for plotting complex-valued data."""
Expand All @@ -264,6 +270,9 @@ class ComplexRepresentation(LabeledOptions):
#: magnitude and phase
magAndPhase = "Mag/Phase"

#: Natural Logarithmic magnitude and phase
log_MagAndPhase = "logMag/Phase"


def determinePlotDataType(data: Optional[DataDictBase]) -> PlotDataType:
"""
Expand Down Expand Up @@ -463,6 +472,35 @@ def _splitComplexData(self, plotItem: PlotItem) -> List[PlotItem]:

return [re_plotItem, im_plotItem]

elif self.complexRepresentation == ComplexRepresentation.log_MagAndPhase:
data = plotItem.data[-1]

# this check avoids a numpy ComplexWarning when we're working with MaskedArray (almost always)
mag_data = np.ma.abs(data).real if isinstance(data, np.ma.MaskedArray) else np.abs(data)
phase_data = np.angle(data)

if label == '':
mag_label, phase_label = '20*log10(Mag)', 'Phase'
else:
mag_label, phase_label = label + ' 20*log10(Mag)', label + ' (Phase)'

mag_plotItem = plotItem
phase_plotItem = deepcopy(mag_plotItem)

mag_plotItem.data[-1] = mag_data
phase_plotItem.data[-1] = phase_data
phase_plotItem.id = mag_plotItem.id + 1
phase_plotItem.subPlot = mag_plotItem.subPlot + 1

# this is a bit of a silly check (see top of the function -- should certainly be True!).
# but it keeps mypy happy.
assert isinstance(mag_plotItem.labels, list)
mag_plotItem.labels[-1] = mag_label
assert isinstance(phase_plotItem.labels, list)
phase_plotItem.labels[-1] = phase_label

return [mag_plotItem, phase_plotItem]

else: # means that self.complexRepresentation is ComplexRepresentation.magAndPhase:
data = plotItem.data[-1]

Expand Down
118 changes: 95 additions & 23 deletions plottr/plot/pyqtgraph/autoplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,16 @@ def plot(self, plotItem: PlotItem) -> None:
plotItem.plotDataType = PlotDataType.scatter1d
elif len(plotItem.data) == 3:
plotItem.plotDataType = PlotDataType.scatter2d

if plotItem.plotDataType in [PlotDataType.scatter1d, PlotDataType.line1d]:

#If the Complex Representation is LogMag
if self.complexRepresentation == ComplexRepresentation.log_MagAndPhase:

#Switch the 1d plots to the logarithmic variation if the plot is the Magnitude plot (not the Phase Plot)
if plotItem.subPlot == 0:
if plotItem.plotDataType == PlotDataType.scatter1d: plotItem.plotDataType = PlotDataType.log10_scatter1d
if plotItem.plotDataType == PlotDataType.line1d: plotItem.plotDataType = PlotDataType.log10_line1d

if plotItem.plotDataType in [PlotDataType.scatter1d, PlotDataType.line1d,PlotDataType.log10_line1d,PlotDataType.log10_scatter1d]:
self._1dPlot(plotItem)
elif plotItem.plotDataType == PlotDataType.grid2d:
self._colorPlot(plotItem)
Expand All @@ -204,17 +212,28 @@ def _1dPlot(self, plotItem: PlotItem) -> None:
assert len(plotItem.data) == 2
x, y = plotItem.data


color = colors[self.findPlotIndexInSubPlot(plotItem.id) % len(colors)]
symbol = symbols[self.findPlotIndexInSubPlot(plotItem.id) % len(symbols)]
if isinstance(plotItem.labels, list):
name = plotItem.labels[-1]
else:
name = ''

if plotItem.plotDataType == PlotDataType.line1d:
name = plotItem.labels[-1] if isinstance(plotItem.labels, list) else ''
return subPlot.plot.plot(x.flatten(), y.flatten(), name=name,
#flatten and apply data transformations (if applicable)
x = x.flatten()
if plotItem.plotDataType in [PlotDataType.log10_line1d, PlotDataType.log10_scatter1d]:
y = 20*np.log(y.flatten())
else:
y = y.flatten()

#plot either line or scatter depending on what graph is being requested
if plotItem.plotDataType in [PlotDataType.line1d, PlotDataType.log10_line1d]:
return subPlot.plot.plot(x, y, name=name,
pen=mkPen(color, width=1), symbol=symbol, symbolBrush=color,
symbolPen=None, symbolSize=symbolSize)
else:
name = plotItem.labels[-1] if isinstance(plotItem.labels, list) else ''
return subPlot.plot.plot(x.flatten(), y.flatten(), name=name,
else: #plotItem.plotDataType is either PlotDataType.scatter1d or PlotDataType.log10_scatter1d
return subPlot.plot.plot(x, y, name=name,
pen=None, symbol=symbol, symbolBrush=color,
symbolPen=None, symbolSize=symbolSize)

Expand All @@ -226,6 +245,7 @@ def _colorPlot(self, plotItem: PlotItem) -> None:
def _scatterPlot2d(self, plotItem: PlotItem) -> None:
subPlot = self.subPlotFromId(plotItem.subPlot)
assert isinstance(subPlot, PlotWithColorbar) and len(plotItem.data) == 3
assert not self.complexRepresentation == ComplexRepresentation.log_MagAndPhase
subPlot.setScatter2d(*plotItem.data)


Expand Down Expand Up @@ -312,6 +332,26 @@ def _plotData(self, **kwargs: Any) -> None:
self.fmWidget.setTitle(self.data.meta_val('title'))
self.title = self.data.meta_val('title')

#update FigOptions numAxes and imagData
self.figOptions.numAxes = len(inds)

#define imagData for single and multiple value data
for val in dvals:
try:
if not all(val.imag == 0):
self.figOptions.imagData = True
break
except:
if not val.imag == 0:
self.figOptions.imagData = True
break

#Assertions to make mypy happy
assert self.figConfig is not None
assert self.figConfig.updateComplexButton() is not None

self.figConfig.updateComplexButton()

@Slot()
def _refreshPlot(self) -> None:
self._plotData()
Expand Down Expand Up @@ -346,7 +386,7 @@ def onfigSaved(self) -> None:
screenshot.save(str(path.parent)+'/'+filename, format='PNG')
return

logger.error("Could not find the path of the figuer. Figure has not been saved")
logger.error("Could not find the path of the figure. Figure has not been saved")

# TODO: Allow for the option to choose filetypes and the name/directory

Expand All @@ -361,6 +401,12 @@ class FigureOptions:
#: how to represent complex data
complexRepresentation: ComplexRepresentation = ComplexRepresentation.realAndImag

#: The number of independent axes that are passed
numAxes: int = 0

#: whether the dependent data contains any instance of imaginary data
imagData: bool = False


class FigureConfigToolBar(QtWidgets.QToolBar):
"""Simple toolbar to configure the figure."""
Expand Down Expand Up @@ -394,11 +440,39 @@ def __init__(self, options: FigureOptions,
lambda: self._setOption('combineLinePlots',
combineLinePlots.isChecked())
)
complexOptions = QtWidgets.QMenu(parent=self)
complexGroup = QtWidgets.QActionGroup(complexOptions)
complexGroup.setExclusive(True)
self._createComplexRepresentation()

# Adding functionality to copy and save the graph
self.copyFig = self.addAction('Copy Figure', self._copyFig)
self.saveFig = self.addAction('Save Figure', self._saveFig)


def _setOption(self, option: str, value: Any) -> None:
setattr(self.options, option, value)
self.optionsChanged.emit()

def _copyFig(self) -> None:
self.figCopied.emit()

def _saveFig(self) -> None:
self.figSaved.emit()

def _createComplexRepresentation(self) -> bool:
#constructs/reconstructs the Complex Button with different viewing options based upon input data

complexOptions = QtWidgets.QMenu(parent=self)
complexGroup = QtWidgets.QActionGroup(complexOptions)
complexGroup.setExclusive(True)

for k in ComplexRepresentation:

#Checks instance of non-imaginary data (to only enable real view) and 2 independent variables (to disable logMag view)
if not self.options.imagData and not k == ComplexRepresentation.real: continue
if self.options.numAxes == 2 and k == ComplexRepresentation.log_MagAndPhase: continue

a = QtWidgets.QAction(k.label, complexOptions)
a.setCheckable(True)
complexGroup.addAction(a)
Expand All @@ -413,18 +487,16 @@ def __init__(self, options: FigureOptions,
complexButton.setText('Complex')
complexButton.setPopupMode(QtWidgets.QToolButton.InstantPopup)
complexButton.setMenu(complexOptions)
self.addWidget(complexButton)

# Adding functionality to copy and save the graph
self.copyFig = self.addAction('Copy Figure', self._copyFig)
self.saveFig = self.addAction('Save Figure', self._saveFig)

def _setOption(self, option: str, value: Any) -> None:
setattr(self.options, option, value)
self.optionsChanged.emit()

def _copyFig(self) -> None:
self.figCopied.emit()

def _saveFig(self) -> None:
self.figSaved.emit()
#stylistic edit to ensure that complexButton is the second button, also to ensure that the updateComplexButton removes the correct button
if len(self.actions()) == 1:
self.addWidget(complexButton)
else:
self.insertAction(self.actions()[1],self.addWidget(complexButton))
return True

def updateComplexButton(self) -> bool:
#remove the second action in the list (currently corresponding to the complexRepresentation button)
self.removeAction(self.actions()[1])
self._createComplexRepresentation()
return True

0 comments on commit ed6c541

Please sign in to comment.