kopia lustrzana https://github.com/NanoVNA-Saver/nanovna-saver
Merge pull request #24 from mihtjel/ContextMenus
User selectable scaling, rescan button for COM ports, bugfixes and tweaks.pull/42/head
commit
799fa07854
|
@ -126,14 +126,17 @@ class Chart(QtWidgets.QWidget):
|
|||
self.update()
|
||||
|
||||
@staticmethod
|
||||
def shortenFrequency(frequency):
|
||||
def shortenFrequency(frequency: int) -> str:
|
||||
if frequency < 50000:
|
||||
return frequency
|
||||
return str(frequency)
|
||||
if frequency < 5000000:
|
||||
return str(round(frequency / 1000)) + "k"
|
||||
return str(round(frequency / 1000000, 1)) + "M"
|
||||
|
||||
def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
|
||||
if event.buttons() == QtCore.Qt.RightButton:
|
||||
event.ignore()
|
||||
return
|
||||
if event.modifiers() == QtCore.Qt.ShiftModifier:
|
||||
self.draggedMarker = self.getNearestMarker(event.x(), event.y())
|
||||
self.mouseMoveEvent(event)
|
||||
|
@ -142,6 +145,174 @@ class Chart(QtWidgets.QWidget):
|
|||
self.draggedMarker = None
|
||||
|
||||
|
||||
class FrequencyChart(Chart):
|
||||
fstart = 0
|
||||
fstop = 0
|
||||
|
||||
maxFrequency = 100000000
|
||||
minFrequency = 1000000
|
||||
|
||||
minDisplayValue = -1
|
||||
maxDisplayValue = 1
|
||||
|
||||
fixedSpan = False
|
||||
fixedValues = False
|
||||
|
||||
linear = True
|
||||
logarithmic = False
|
||||
|
||||
def __init__(self, name):
|
||||
super().__init__(name)
|
||||
|
||||
mode_group = QtWidgets.QActionGroup(self)
|
||||
self.menu = QtWidgets.QMenu()
|
||||
|
||||
self.reset = QtWidgets.QAction("Reset")
|
||||
self.reset.triggered.connect(self.resetDisplayLimits)
|
||||
self.menu.addAction(self.reset)
|
||||
|
||||
self.x_menu = QtWidgets.QMenu("Frequency axis")
|
||||
self.action_automatic = QtWidgets.QAction("Automatic")
|
||||
self.action_automatic.setCheckable(True)
|
||||
self.action_automatic.setChecked(True)
|
||||
self.action_automatic.changed.connect(lambda: self.setFixedSpan(self.action_fixed_span.isChecked()))
|
||||
self.action_fixed_span = QtWidgets.QAction("Fixed span")
|
||||
self.action_fixed_span.setCheckable(True)
|
||||
self.action_fixed_span.changed.connect(lambda: self.setFixedSpan(self.action_fixed_span.isChecked()))
|
||||
mode_group.addAction(self.action_automatic)
|
||||
mode_group.addAction(self.action_fixed_span)
|
||||
self.x_menu.addAction(self.action_automatic)
|
||||
self.x_menu.addAction(self.action_fixed_span)
|
||||
self.x_menu.addSeparator()
|
||||
|
||||
self.action_set_fixed_start = QtWidgets.QAction("Start (" + Chart.shortenFrequency(self.minFrequency) + ")")
|
||||
self.action_set_fixed_start.triggered.connect(self.setMinimumFrequency)
|
||||
|
||||
self.action_set_fixed_stop = QtWidgets.QAction("Stop (" + Chart.shortenFrequency(self.maxFrequency) + ")")
|
||||
self.action_set_fixed_stop.triggered.connect(self.setMaximumFrequency)
|
||||
|
||||
self.x_menu.addAction(self.action_set_fixed_start)
|
||||
self.x_menu.addAction(self.action_set_fixed_stop)
|
||||
|
||||
self.y_menu = QtWidgets.QMenu("Data axis")
|
||||
self.y_action_automatic = QtWidgets.QAction("Automatic")
|
||||
self.y_action_automatic.setCheckable(True)
|
||||
self.y_action_automatic.setChecked(True)
|
||||
self.y_action_automatic.changed.connect(lambda: self.setFixedValues(self.y_action_fixed_span.isChecked()))
|
||||
self.y_action_fixed_span = QtWidgets.QAction("Fixed span")
|
||||
self.y_action_fixed_span.setCheckable(True)
|
||||
self.y_action_fixed_span.changed.connect(lambda: self.setFixedValues(self.y_action_fixed_span.isChecked()))
|
||||
mode_group = QtWidgets.QActionGroup(self)
|
||||
mode_group.addAction(self.y_action_automatic)
|
||||
mode_group.addAction(self.y_action_fixed_span)
|
||||
self.y_menu.addAction(self.y_action_automatic)
|
||||
self.y_menu.addAction(self.y_action_fixed_span)
|
||||
self.y_menu.addSeparator()
|
||||
|
||||
self.action_set_fixed_maximum = QtWidgets.QAction("Maximum (" + str(self.maxDisplayValue) + ")")
|
||||
self.action_set_fixed_maximum.triggered.connect(self.setMaximumValue)
|
||||
|
||||
self.action_set_fixed_minimum = QtWidgets.QAction("Minimum (" + str(self.minDisplayValue) + ")")
|
||||
self.action_set_fixed_minimum.triggered.connect(self.setMinimumValue)
|
||||
|
||||
self.y_menu.addAction(self.action_set_fixed_maximum)
|
||||
self.y_menu.addAction(self.action_set_fixed_minimum)
|
||||
|
||||
self.menu.addMenu(self.x_menu)
|
||||
self.menu.addMenu(self.y_menu)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
self.action_set_fixed_start.setText("Start (" + Chart.shortenFrequency(self.minFrequency) + ")")
|
||||
self.action_set_fixed_stop.setText("Stop (" + Chart.shortenFrequency(self.maxFrequency) + ")")
|
||||
self.action_set_fixed_minimum.setText("Minimum (" + str(self.minDisplayValue) + ")")
|
||||
self.action_set_fixed_maximum.setText("Maximum (" + str(self.maxDisplayValue) + ")")
|
||||
|
||||
self.menu.exec_(event.globalPos())
|
||||
|
||||
def setFixedSpan(self, fixed_span: bool):
|
||||
self.fixedSpan = fixed_span
|
||||
self.update()
|
||||
|
||||
def setFixedValues(self, fixed_values: bool):
|
||||
self.fixedValues = fixed_values
|
||||
self.update()
|
||||
|
||||
def setMinimumFrequency(self):
|
||||
from NanoVNASaver.NanoVNASaver import NanoVNASaver
|
||||
min_freq_str, selected = QtWidgets.QInputDialog.getText(self, "Start frequency",
|
||||
"Set start frequency", text=str(self.minFrequency))
|
||||
if not selected:
|
||||
return
|
||||
min_freq = NanoVNASaver.parseFrequency(min_freq_str)
|
||||
if min_freq > 0:
|
||||
self.minFrequency = min_freq
|
||||
if self.fixedSpan:
|
||||
self.update()
|
||||
|
||||
def setMaximumFrequency(self):
|
||||
from NanoVNASaver.NanoVNASaver import NanoVNASaver
|
||||
max_freq_str, selected = QtWidgets.QInputDialog.getText(self, "Stop frequency",
|
||||
"Set stop frequency", text=str(self.maxFrequency))
|
||||
if not selected:
|
||||
return
|
||||
max_freq = NanoVNASaver.parseFrequency(max_freq_str)
|
||||
if max_freq > 0:
|
||||
self.maxFrequency = max_freq
|
||||
if self.fixedSpan:
|
||||
self.update()
|
||||
|
||||
def setMinimumValue(self):
|
||||
min_val, selected = QtWidgets.QInputDialog.getInt(self, "Minimum value",
|
||||
"Set minimum value", value=self.minDisplayValue)
|
||||
if not selected:
|
||||
return
|
||||
self.minDisplayValue = min_val
|
||||
if self.fixedValues:
|
||||
self.update()
|
||||
|
||||
def setMaximumValue(self):
|
||||
max_val, selected = QtWidgets.QInputDialog.getInt(self, "Maximum value",
|
||||
"Set maximum value", value=self.maxDisplayValue)
|
||||
if not selected:
|
||||
return
|
||||
self.maxDisplayValue = max_val
|
||||
if self.fixedValues:
|
||||
self.update()
|
||||
|
||||
def resetDisplayLimits(self):
|
||||
self.fixedValues = False
|
||||
self.y_action_automatic.setChecked(True)
|
||||
self.fixedSpan = False
|
||||
self.action_automatic.setChecked(True)
|
||||
self.update()
|
||||
|
||||
def getXPosition(self, d: Datapoint) -> int:
|
||||
span = self.fstop - self.fstart
|
||||
if span > 0:
|
||||
return self.leftMargin + 1 + round(self.chartWidth * (d.freq - self.fstart) / span)
|
||||
else:
|
||||
return math.floor(self.width()/2)
|
||||
|
||||
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
|
||||
if a0.buttons() == QtCore.Qt.RightButton:
|
||||
a0.ignore()
|
||||
return
|
||||
x = a0.x()
|
||||
absx = x - self.leftMargin
|
||||
if absx < 0 or absx > self.chartWidth:
|
||||
a0.ignore()
|
||||
return
|
||||
a0.accept()
|
||||
if self.fstop - self.fstart > 0:
|
||||
m = self.getActiveMarker(a0)
|
||||
span = self.fstop - self.fstart
|
||||
step = span/self.chartWidth
|
||||
f = self.fstart + absx * step
|
||||
m.setFrequency(str(round(f)))
|
||||
m.frequencyInput.setText(str(round(f)))
|
||||
return
|
||||
|
||||
|
||||
class SquareChart(Chart):
|
||||
def __init__(self, name):
|
||||
super().__init__(name)
|
||||
|
@ -155,7 +326,7 @@ class SquareChart(Chart):
|
|||
self.update()
|
||||
|
||||
|
||||
class PhaseChart(Chart):
|
||||
class PhaseChart(FrequencyChart):
|
||||
def __init__(self, name=""):
|
||||
super().__init__(name)
|
||||
self.leftMargin = 35
|
||||
|
@ -166,6 +337,8 @@ class PhaseChart(Chart):
|
|||
self.minAngle = 0
|
||||
self.span = 0
|
||||
|
||||
self.y_menu.setDisabled(True)
|
||||
|
||||
self.setMinimumSize(self.chartWidth + 20 + self.leftMargin, self.chartHeight + 40)
|
||||
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
|
||||
pal = QtGui.QPalette()
|
||||
|
@ -215,26 +388,30 @@ class PhaseChart(Chart):
|
|||
line_pen.setWidth(1)
|
||||
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
|
||||
highlighter.setWidth(1)
|
||||
if len(self.data) > 0:
|
||||
fstart = self.data[0].freq
|
||||
fstop = self.data[len(self.data)-1].freq
|
||||
if self.fixedSpan:
|
||||
fstart = self.minFrequency
|
||||
fstop = self.maxFrequency
|
||||
else:
|
||||
fstart = self.reference[0].freq
|
||||
fstop = self.reference[len(self.reference) - 1].freq
|
||||
if len(self.data) > 0:
|
||||
fstart = self.data[0].freq
|
||||
fstop = self.data[len(self.data)-1].freq
|
||||
else:
|
||||
fstart = self.reference[0].freq
|
||||
fstop = self.reference[len(self.reference) - 1].freq
|
||||
self.fstart = fstart
|
||||
self.fstop = fstop
|
||||
fspan = fstop-fstart
|
||||
fspan = self.fstop-self.fstart
|
||||
minAngle = -180
|
||||
maxAngle = 180
|
||||
span = maxAngle-minAngle
|
||||
qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, Chart.shortenFrequency(fstart))
|
||||
qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, Chart.shortenFrequency(self.fstart))
|
||||
ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin
|
||||
for i in range(ticks):
|
||||
x = self.leftMargin + round((i+1)*self.chartWidth/ticks)
|
||||
qp.setPen(QtGui.QPen(self.foregroundColor))
|
||||
qp.drawLine(x, 20, x, 20+self.chartHeight+5)
|
||||
qp.setPen(self.textColor)
|
||||
qp.drawText(x-20, 20+self.chartHeight+15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + fstart)))
|
||||
qp.drawText(x-20, 20+self.chartHeight+15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + self.fstart)))
|
||||
|
||||
qp.setPen(pen)
|
||||
for i in range(len(self.data)):
|
||||
|
@ -260,7 +437,7 @@ class PhaseChart(Chart):
|
|||
qp.drawPoint(int(x), int(y))
|
||||
if self.drawLines and i > 0:
|
||||
angle = self.angle(self.reference[i-1])
|
||||
prevx = x = self.leftMargin + 1 + round(self.chartWidth*(self.reference[i-1].freq - fstart)/fspan)
|
||||
prevx = self.leftMargin + 1 + round(self.chartWidth*(self.reference[i-1].freq - fstart)/fspan)
|
||||
prevy = 30 + round((angle - minAngle) / span * (self.chartHeight - 10))
|
||||
qp.setPen(line_pen)
|
||||
qp.drawLine(x, y, prevx, prevy)
|
||||
|
@ -285,22 +462,6 @@ class PhaseChart(Chart):
|
|||
angle = self.angle(d)
|
||||
return 30 + round((angle - self.minAngle) / self.span * (self.chartHeight - 10))
|
||||
|
||||
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
|
||||
x = a0.x()
|
||||
absx = x - self.leftMargin
|
||||
if absx < 0 or absx > self.chartWidth:
|
||||
a0.ignore()
|
||||
return
|
||||
a0.accept()
|
||||
if self.fstop - self.fstart > 0:
|
||||
m = self.getActiveMarker(a0)
|
||||
span = self.fstop - self.fstart
|
||||
step = span/self.chartWidth
|
||||
f = self.fstart + absx * step
|
||||
m.setFrequency(str(round(f)))
|
||||
m.frequencyInput.setText(str(round(f)))
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def angle(d: Datapoint) -> float:
|
||||
re = d.re
|
||||
|
@ -308,7 +469,7 @@ class PhaseChart(Chart):
|
|||
return -math.degrees(math.atan2(im, re))
|
||||
|
||||
|
||||
class VSWRChart(Chart):
|
||||
class VSWRChart(FrequencyChart):
|
||||
def __init__(self, name=""):
|
||||
super().__init__(name)
|
||||
self.leftMargin = 30
|
||||
|
@ -316,6 +477,8 @@ class VSWRChart(Chart):
|
|||
self.chartHeight = 250
|
||||
self.fstart = 0
|
||||
self.fstop = 0
|
||||
self.maxDisplayValue = 25
|
||||
self.minDisplayValue = 1
|
||||
|
||||
self.setMinimumSize(self.chartWidth + 20 + self.leftMargin, self.chartHeight + 40)
|
||||
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
|
||||
|
@ -352,7 +515,10 @@ class VSWRChart(Chart):
|
|||
line_pen.setWidth(1)
|
||||
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
|
||||
highlighter.setWidth(1)
|
||||
if len(self.data) > 0:
|
||||
if self.fixedSpan:
|
||||
fstart = self.minFrequency
|
||||
fstop = self.maxFrequency
|
||||
elif len(self.data) > 0:
|
||||
fstart = self.data[0].freq
|
||||
fstop = self.data[len(self.data)-1].freq
|
||||
else:
|
||||
|
@ -362,18 +528,24 @@ class VSWRChart(Chart):
|
|||
self.fstop = fstop
|
||||
fspan = fstop-fstart
|
||||
# Find scaling
|
||||
minVSWR = 1
|
||||
maxVSWR = 3
|
||||
for d in self.data:
|
||||
_, _, vswr = NanoVNASaver.vswr(d)
|
||||
if vswr > maxVSWR:
|
||||
maxVSWR = vswr
|
||||
maxVSWR = min(25, math.ceil(maxVSWR))
|
||||
if self.fixedValues:
|
||||
minVSWR = max(1, self.minDisplayValue)
|
||||
maxVSWR = self.maxDisplayValue
|
||||
else:
|
||||
minVSWR = 1
|
||||
maxVSWR = 3
|
||||
for d in self.data:
|
||||
_, _, vswr = NanoVNASaver.vswr(d)
|
||||
if vswr > maxVSWR:
|
||||
maxVSWR = vswr
|
||||
maxVSWR = min(self.maxDisplayValue, math.ceil(maxVSWR))
|
||||
self.maxVSWR = maxVSWR
|
||||
span = maxVSWR-minVSWR
|
||||
self.span = span
|
||||
ticksize = 1
|
||||
if span > 10 and span % 5 == 0:
|
||||
if span > 15 and span % 7 == 0:
|
||||
ticksize = 7
|
||||
elif span > 10 and span % 5 == 0:
|
||||
ticksize = 5
|
||||
elif span > 12 and span % 4 == 0:
|
||||
ticksize = 4
|
||||
|
@ -464,21 +636,9 @@ class VSWRChart(Chart):
|
|||
_, _, vswr = NanoVNASaver.vswr(d)
|
||||
return 30 + round((self.maxVSWR - vswr) / self.span * (self.chartHeight - 10))
|
||||
|
||||
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
|
||||
x = a0.x()
|
||||
absx = x - self.leftMargin
|
||||
if absx < 0 or absx > self.chartWidth:
|
||||
a0.ignore()
|
||||
return
|
||||
a0.accept()
|
||||
if self.fstop - self.fstart > 0:
|
||||
m = self.getActiveMarker(a0)
|
||||
span = self.fstop - self.fstart
|
||||
step = span/self.chartWidth
|
||||
f = self.fstart + absx * step
|
||||
m.setFrequency(str(round(f)))
|
||||
m.frequencyInput.setText(str(round(f)))
|
||||
return
|
||||
def resetDisplayLimits(self):
|
||||
self.maxDisplayValue = 25
|
||||
super().resetDisplayLimits()
|
||||
|
||||
|
||||
class PolarChart(SquareChart):
|
||||
|
@ -746,14 +906,14 @@ class SmithChart(SquareChart):
|
|||
return
|
||||
|
||||
|
||||
class LogMagChart(Chart):
|
||||
class LogMagChart(FrequencyChart):
|
||||
def __init__(self, name=""):
|
||||
super().__init__(name)
|
||||
self.leftMargin = 30
|
||||
self.chartWidth = 250
|
||||
self.chartHeight = 250
|
||||
self.fstart = 0
|
||||
self.fstop = 0
|
||||
self.minDisplayValue = -80
|
||||
self.maxDisplayValue = 10
|
||||
|
||||
self.setMinimumSize(self.chartWidth + 20 + self.leftMargin, self.chartHeight + 40)
|
||||
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
|
||||
|
@ -790,36 +950,46 @@ class LogMagChart(Chart):
|
|||
line_pen.setWidth(1)
|
||||
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
|
||||
highlighter.setWidth(1)
|
||||
if len(self.data) > 0:
|
||||
fstart = self.data[0].freq
|
||||
fstop = self.data[len(self.data)-1].freq
|
||||
if not self.fixedSpan:
|
||||
if len(self.data) > 0:
|
||||
fstart = self.data[0].freq
|
||||
fstop = self.data[len(self.data)-1].freq
|
||||
else:
|
||||
fstart = self.reference[0].freq
|
||||
fstop = self.reference[len(self.reference) - 1].freq
|
||||
self.fstart = fstart
|
||||
self.fstop = fstop
|
||||
else:
|
||||
fstart = self.reference[0].freq
|
||||
fstop = self.reference[len(self.reference) - 1].freq
|
||||
self.fstart = fstart
|
||||
self.fstop = fstop
|
||||
fspan = fstop-fstart
|
||||
# Find scaling
|
||||
minValue = 100
|
||||
maxValue = 0
|
||||
for d in self.data:
|
||||
logmag = self.logMag(d)
|
||||
if logmag > maxValue:
|
||||
maxValue = logmag
|
||||
if logmag < minValue:
|
||||
minValue = logmag
|
||||
for d in self.reference: # Also check min/max for the reference sweep
|
||||
if d.freq < fstart or d.freq > fstop:
|
||||
continue
|
||||
logmag = self.logMag(d)
|
||||
if logmag > maxValue:
|
||||
maxValue = logmag
|
||||
if logmag < minValue:
|
||||
minValue = logmag
|
||||
self.fstart = self.minFrequency
|
||||
self.fstop = self.maxFrequency
|
||||
fspan = self.fstop - self.fstart
|
||||
if self.fixedValues:
|
||||
maxValue = -self.minDisplayValue # These are negative, because the entire logmag chart is
|
||||
minValue = -self.maxDisplayValue # upside down.
|
||||
else:
|
||||
# Find scaling
|
||||
minValue = 100
|
||||
maxValue = 0
|
||||
for d in self.data:
|
||||
logmag = self.logMag(d)
|
||||
if logmag > maxValue:
|
||||
maxValue = logmag
|
||||
if logmag < minValue:
|
||||
minValue = logmag
|
||||
for d in self.reference: # Also check min/max for the reference sweep
|
||||
if d.freq < self.fstart or d.freq > self.fstop:
|
||||
continue
|
||||
logmag = self.logMag(d)
|
||||
if logmag > maxValue:
|
||||
maxValue = logmag
|
||||
if logmag < minValue:
|
||||
minValue = logmag
|
||||
|
||||
minValue = 10*math.floor(minValue/10)
|
||||
self.minValue = minValue
|
||||
maxValue = 10*math.ceil(maxValue/10)
|
||||
self.maxValue = maxValue
|
||||
|
||||
minValue = 10*math.floor(minValue/10)
|
||||
self.minValue = minValue
|
||||
maxValue = 10*math.ceil(maxValue/10)
|
||||
span = maxValue-minValue
|
||||
self.span = span
|
||||
for i in range(minValue, maxValue, 10):
|
||||
|
@ -832,25 +1002,28 @@ class LogMagChart(Chart):
|
|||
qp.setPen(self.textColor)
|
||||
qp.drawText(3, 35, str(-minValue))
|
||||
qp.drawText(3, self.chartHeight+20, str(-maxValue))
|
||||
# At least 100 px between ticks
|
||||
qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, LogMagChart.shortenFrequency(fstart))
|
||||
|
||||
# Frequency ticks
|
||||
qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, Chart.shortenFrequency(self.fstart))
|
||||
ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin
|
||||
for i in range(ticks):
|
||||
x = self.leftMargin + round((i+1)*self.chartWidth/ticks)
|
||||
qp.setPen(QtGui.QPen(self.foregroundColor))
|
||||
qp.drawLine(x, 20, x, 20+self.chartHeight+5)
|
||||
qp.setPen(self.textColor)
|
||||
qp.drawText(x-20, 20+self.chartHeight+15, LogMagChart.shortenFrequency(round(fspan/ticks*(i+1) + fstart)))
|
||||
qp.drawText(x-20, 20+self.chartHeight+15, LogMagChart.shortenFrequency(round(fspan/ticks*(i+1) + self.fstart)))
|
||||
|
||||
qp.setPen(pen)
|
||||
for i in range(len(self.data)):
|
||||
if self.data[i].freq < self.fstart or self.data[i].freq > self.fstop:
|
||||
continue
|
||||
logmag = self.logMag(self.data[i])
|
||||
x = self.leftMargin + 1 + round(self.chartWidth/len(self.data) * i)
|
||||
x = self.getXPosition(self.data[i])
|
||||
y = 30 + round((logmag-minValue)/span*(self.chartHeight-10))
|
||||
qp.drawPoint(int(x), int(y))
|
||||
if self.drawLines and i > 0:
|
||||
logmag = self.logMag(self.data[i-1])
|
||||
prevx = self.leftMargin + 1 + round(self.chartWidth / len(self.data) * (i-1))
|
||||
prevx = self.getXPosition(self.data[i-1])
|
||||
prevy = 30 + round((logmag - minValue) / span * (self.chartHeight - 10))
|
||||
qp.setPen(line_pen)
|
||||
qp.drawLine(x, y, prevx, prevy)
|
||||
|
@ -859,15 +1032,15 @@ class LogMagChart(Chart):
|
|||
line_pen.setColor(self.referenceColor)
|
||||
qp.setPen(pen)
|
||||
for i in range(len(self.reference)):
|
||||
if self.reference[i].freq < fstart or self.reference[i].freq > fstop:
|
||||
if self.reference[i].freq < self.fstart or self.reference[i].freq > self.fstop:
|
||||
continue
|
||||
logmag = self.logMag(self.reference[i])
|
||||
x = self.leftMargin + 1 + round(self.chartWidth*(self.reference[i].freq - fstart)/fspan)
|
||||
x = self.getXPosition(self.reference[i])
|
||||
y = 30 + round((logmag-minValue)/span*(self.chartHeight-10))
|
||||
qp.drawPoint(int(x), int(y))
|
||||
if self.drawLines and i > 0:
|
||||
logmag = self.logMag(self.reference[i-1])
|
||||
prevx = self.leftMargin + 1 + round(self.chartWidth*(self.reference[i-1].freq - fstart)/fspan)
|
||||
prevx = self.getXPosition(self.reference[i-1])
|
||||
prevy = 30 + round((logmag - minValue) / span * (self.chartHeight - 10))
|
||||
qp.setPen(line_pen)
|
||||
qp.drawLine(x, y, prevx, prevy)
|
||||
|
@ -875,39 +1048,21 @@ class LogMagChart(Chart):
|
|||
# Now draw the markers
|
||||
for m in self.markers:
|
||||
if m.location != -1:
|
||||
if self.data[m.location].freq > self.fstop or self.data[m.location].freq < self.fstart:
|
||||
continue
|
||||
highlighter.setColor(m.color)
|
||||
qp.setPen(highlighter)
|
||||
logmag = self.logMag(self.data[m.location])
|
||||
x = self.leftMargin + 1 + round(self.chartWidth/len(self.data) * m.location)
|
||||
x = self.getXPosition(self.data[m.location])
|
||||
y = 30 + round((logmag - minValue) / span * (self.chartHeight - 10))
|
||||
qp.drawLine(int(x), int(y) + 3, int(x) - 3, int(y) - 3)
|
||||
qp.drawLine(int(x), int(y) + 3, int(x) + 3, int(y) - 3)
|
||||
qp.drawLine(int(x) - 3, int(y) - 3, int(x) + 3, int(y) - 3)
|
||||
|
||||
def getXPosition(self, d: Datapoint) -> int:
|
||||
span = self.fstop - self.fstart
|
||||
return self.leftMargin + 1 + round(self.chartWidth * (d.freq - self.fstart) / span)
|
||||
|
||||
def getYPosition(self, d: Datapoint) -> int:
|
||||
logMag = self.logMag(d)
|
||||
return 30 + round((logMag - self.minValue) / self.span * (self.chartHeight - 10))
|
||||
|
||||
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
|
||||
x = a0.x()
|
||||
absx = x - self.leftMargin
|
||||
if absx < 0 or absx > self.chartWidth:
|
||||
a0.ignore()
|
||||
return
|
||||
a0.accept()
|
||||
if self.fstop - self.fstart > 0:
|
||||
m = self.getActiveMarker(a0)
|
||||
span = self.fstop - self.fstart
|
||||
step = span/self.chartWidth
|
||||
f = self.fstart + absx * step
|
||||
m.setFrequency(str(round(f)))
|
||||
m.frequencyInput.setText(str(round(f)))
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def logMag(p: Datapoint) -> float:
|
||||
re = p.re
|
||||
|
@ -919,7 +1074,7 @@ class LogMagChart(Chart):
|
|||
return -20 * math.log10(mag)
|
||||
|
||||
|
||||
class QualityFactorChart(Chart):
|
||||
class QualityFactorChart(FrequencyChart):
|
||||
def __init__(self, name=""):
|
||||
super().__init__(name)
|
||||
self.leftMargin = 35
|
||||
|
@ -930,6 +1085,8 @@ class QualityFactorChart(Chart):
|
|||
self.minQ = 0
|
||||
self.maxQ = 0
|
||||
self.span = 0
|
||||
self.minDisplayValue = 0
|
||||
self.maxDisplayValue = 100
|
||||
|
||||
self.setMinimumSize(self.chartWidth + 20 + self.leftMargin, self.chartHeight + 40)
|
||||
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
|
||||
|
@ -959,14 +1116,20 @@ class QualityFactorChart(Chart):
|
|||
maxQ = 0
|
||||
|
||||
# Make up some sensible scaling here
|
||||
for d in self.data:
|
||||
Q = NanoVNASaver.qualifyFactor(d)
|
||||
if Q > maxQ:
|
||||
maxQ = Q
|
||||
if self.fixedValues:
|
||||
maxQ = self.maxDisplayValue
|
||||
minQ = self.minDisplayValue
|
||||
else:
|
||||
minQ = 0
|
||||
for d in self.data:
|
||||
Q = NanoVNASaver.qualifyFactor(d)
|
||||
if Q > maxQ:
|
||||
maxQ = Q
|
||||
scale = 0
|
||||
if maxQ > 0:
|
||||
scale = max(scale, math.floor(math.log10(maxQ)))
|
||||
|
||||
self.minQ = minQ
|
||||
self.maxQ = math.ceil(maxQ/10**scale) * 10**scale
|
||||
self.span = self.maxQ - self.minQ
|
||||
step = math.floor(self.span / 10)
|
||||
|
@ -996,12 +1159,16 @@ class QualityFactorChart(Chart):
|
|||
line_pen.setWidth(1)
|
||||
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
|
||||
highlighter.setWidth(1)
|
||||
if len(self.data) > 0:
|
||||
fstart = self.data[0].freq
|
||||
fstop = self.data[len(self.data)-1].freq
|
||||
if self.fixedSpan:
|
||||
fstart = self.minFrequency
|
||||
fstop = self.maxFrequency
|
||||
else:
|
||||
fstart = self.reference[0].freq
|
||||
fstop = self.reference[len(self.reference) - 1].freq
|
||||
if len(self.data) > 0:
|
||||
fstart = self.data[0].freq
|
||||
fstop = self.data[len(self.data)-1].freq
|
||||
else:
|
||||
fstart = self.reference[0].freq
|
||||
fstop = self.reference[len(self.reference) - 1].freq
|
||||
self.fstart = fstart
|
||||
self.fstop = fstop
|
||||
fspan = fstop-fstart
|
||||
|
@ -1039,7 +1206,7 @@ class QualityFactorChart(Chart):
|
|||
qp.drawPoint(int(x), int(y))
|
||||
if self.drawLines and i > 0:
|
||||
Q = NanoVNASaver.qualifyFactor(self.reference[i-1])
|
||||
prevx = x = self.leftMargin + 1 + round(self.chartWidth*(self.reference[i-1].freq - fstart)/fspan)
|
||||
prevx = self.leftMargin + 1 + round(self.chartWidth*(self.reference[i-1].freq - fstart)/fspan)
|
||||
prevy = 30 + round((self.maxQ - Q) / self.span * (self.chartHeight - 10))
|
||||
qp.setPen(line_pen)
|
||||
qp.drawLine(x, y, prevx, prevy)
|
||||
|
@ -1065,22 +1232,6 @@ class QualityFactorChart(Chart):
|
|||
Q = NanoVNASaver.qualifyFactor(d)
|
||||
return 30 + round((self.maxQ - Q) / self.span * (self.chartHeight - 10))
|
||||
|
||||
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
|
||||
x = a0.x()
|
||||
absx = x - self.leftMargin
|
||||
if absx < 0 or absx > self.chartWidth:
|
||||
a0.ignore()
|
||||
return
|
||||
a0.accept()
|
||||
if self.fstop - self.fstart > 0:
|
||||
m = self.getActiveMarker(a0)
|
||||
span = self.fstop - self.fstart
|
||||
step = span/self.chartWidth
|
||||
f = self.fstart + absx * step
|
||||
m.setFrequency(str(round(f)))
|
||||
m.frequencyInput.setText(str(round(f)))
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def angle(d: Datapoint) -> float:
|
||||
re = d.re
|
||||
|
@ -1142,7 +1293,7 @@ class TDRChart(Chart):
|
|||
qp.end()
|
||||
|
||||
|
||||
class RealImaginaryChart(Chart):
|
||||
class RealImaginaryChart(FrequencyChart):
|
||||
def __init__(self, name=""):
|
||||
super().__init__(name)
|
||||
self.leftMargin = 45
|
||||
|
@ -1156,6 +1307,53 @@ class RealImaginaryChart(Chart):
|
|||
self.max_real = 0
|
||||
self.max_imag = 0
|
||||
|
||||
self.maxDisplayReal = 100
|
||||
self.maxDisplayImag = 100
|
||||
self.minDisplayReal = 0
|
||||
self.minDisplayImag = -100
|
||||
|
||||
#
|
||||
# Build the context menu
|
||||
#
|
||||
|
||||
self.y_menu.clear()
|
||||
|
||||
self.y_action_automatic = QtWidgets.QAction("Automatic")
|
||||
self.y_action_automatic.setCheckable(True)
|
||||
self.y_action_automatic.setChecked(True)
|
||||
self.y_action_automatic.changed.connect(lambda: self.setFixedValues(self.y_action_fixed_span.isChecked()))
|
||||
self.y_action_fixed_span = QtWidgets.QAction("Fixed span")
|
||||
self.y_action_fixed_span.setCheckable(True)
|
||||
self.y_action_fixed_span.changed.connect(lambda: self.setFixedValues(self.y_action_fixed_span.isChecked()))
|
||||
mode_group = QtWidgets.QActionGroup(self)
|
||||
mode_group.addAction(self.y_action_automatic)
|
||||
mode_group.addAction(self.y_action_fixed_span)
|
||||
self.y_menu.addAction(self.y_action_automatic)
|
||||
self.y_menu.addAction(self.y_action_fixed_span)
|
||||
self.y_menu.addSeparator()
|
||||
|
||||
self.action_set_fixed_maximum_real = QtWidgets.QAction("Maximum R (" + str(self.maxDisplayReal) + ")")
|
||||
self.action_set_fixed_maximum_real.triggered.connect(self.setMaximumRealValue)
|
||||
|
||||
self.action_set_fixed_minimum_real = QtWidgets.QAction("Minimum R (" + str(self.minDisplayReal) + ")")
|
||||
self.action_set_fixed_minimum_real.triggered.connect(self.setMinimumRealValue)
|
||||
|
||||
self.action_set_fixed_maximum_imag = QtWidgets.QAction("Maximum jX (" + str(self.maxDisplayImag) + ")")
|
||||
self.action_set_fixed_maximum_imag.triggered.connect(self.setMaximumImagValue)
|
||||
|
||||
self.action_set_fixed_minimum_imag = QtWidgets.QAction("Minimum jX (" + str(self.minDisplayImag) + ")")
|
||||
self.action_set_fixed_minimum_imag.triggered.connect(self.setMinimumImagValue)
|
||||
|
||||
self.y_menu.addAction(self.action_set_fixed_maximum_real)
|
||||
self.y_menu.addAction(self.action_set_fixed_minimum_real)
|
||||
self.y_menu.addSeparator()
|
||||
self.y_menu.addAction(self.action_set_fixed_maximum_imag)
|
||||
self.y_menu.addAction(self.action_set_fixed_minimum_imag)
|
||||
|
||||
#
|
||||
# Set up size policy and palette
|
||||
#
|
||||
|
||||
self.setMinimumSize(self.chartWidth + self.leftMargin + self.rightMargin, self.chartHeight + 40)
|
||||
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
|
||||
pal = QtGui.QPalette()
|
||||
|
@ -1170,7 +1368,6 @@ class RealImaginaryChart(Chart):
|
|||
|
||||
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
|
||||
qp = QtGui.QPainter(self)
|
||||
#qp.begin(self) # Apparently not needed?
|
||||
self.drawChart(qp)
|
||||
self.drawValues(qp)
|
||||
qp.end()
|
||||
|
@ -1194,52 +1391,83 @@ class RealImaginaryChart(Chart):
|
|||
line_pen.setWidth(1)
|
||||
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
|
||||
highlighter.setWidth(1)
|
||||
if len(self.data) > 0:
|
||||
fstart = self.data[0].freq
|
||||
fstop = self.data[len(self.data)-1].freq
|
||||
if self.fixedSpan:
|
||||
fstart = self.minFrequency
|
||||
fstop = self.maxFrequency
|
||||
else:
|
||||
fstart = self.reference[0].freq
|
||||
fstop = self.reference[len(self.reference) - 1].freq
|
||||
if len(self.data) > 0:
|
||||
fstart = self.data[0].freq
|
||||
fstop = self.data[len(self.data)-1].freq
|
||||
else:
|
||||
fstart = self.reference[0].freq
|
||||
fstop = self.reference[len(self.reference) - 1].freq
|
||||
self.fstart = fstart
|
||||
self.fstop = fstop
|
||||
fspan = fstop-fstart
|
||||
# Find scaling
|
||||
min_real = 1000
|
||||
min_imag = 1000
|
||||
max_real = 0
|
||||
max_imag = -1000
|
||||
for d in self.data:
|
||||
re, im = NanoVNASaver.normalize50(d)
|
||||
if re > max_real:
|
||||
max_real = re
|
||||
if re < min_real:
|
||||
min_real = re
|
||||
if im > max_imag:
|
||||
max_imag = im
|
||||
if im < min_imag:
|
||||
min_imag = im
|
||||
for d in self.reference: # Also check min/max for the reference sweep
|
||||
if d.freq < fstart or d.freq > fstop:
|
||||
continue
|
||||
re, im = NanoVNASaver.normalize50(d)
|
||||
if re > max_real:
|
||||
max_real = re
|
||||
if re < min_real:
|
||||
min_real = re
|
||||
if im > max_imag:
|
||||
max_imag = im
|
||||
if im < min_imag:
|
||||
min_imag = im
|
||||
if self.fixedValues:
|
||||
min_real = self.minDisplayReal
|
||||
max_real = self.maxDisplayReal
|
||||
min_imag = self.minDisplayImag
|
||||
max_imag = self.maxDisplayImag
|
||||
else:
|
||||
min_real = 1000
|
||||
min_imag = 1000
|
||||
max_real = 0
|
||||
max_imag = -1000
|
||||
for d in self.data:
|
||||
re, im = NanoVNASaver.normalize50(d)
|
||||
if re > max_real:
|
||||
max_real = re
|
||||
if re < min_real:
|
||||
min_real = re
|
||||
if im > max_imag:
|
||||
max_imag = im
|
||||
if im < min_imag:
|
||||
min_imag = im
|
||||
for d in self.reference: # Also check min/max for the reference sweep
|
||||
if d.freq < fstart or d.freq > fstop:
|
||||
continue
|
||||
re, im = NanoVNASaver.normalize50(d)
|
||||
if re > max_real:
|
||||
max_real = re
|
||||
if re < min_real:
|
||||
min_real = re
|
||||
if im > max_imag:
|
||||
max_imag = im
|
||||
if im < min_imag:
|
||||
min_imag = im
|
||||
|
||||
max_real = max(8, math.ceil(max_real)) # Always have at least 8 numbered horizontal lines
|
||||
min_real = max(0, math.floor(min_real)) # Negative real resistance? No.
|
||||
max_imag = math.ceil(max_imag)
|
||||
min_imag = math.floor(min_imag)
|
||||
max_real = max(8, math.ceil(max_real)) # Always have at least 8 numbered horizontal lines
|
||||
min_real = max(0, math.floor(min_real)) # Negative real resistance? No.
|
||||
max_imag = math.ceil(max_imag)
|
||||
min_imag = math.floor(min_imag)
|
||||
|
||||
if max_imag - min_imag < 8:
|
||||
missing = 8 - (max_imag - min_imag)
|
||||
max_imag += math.ceil(missing/2)
|
||||
min_imag -= math.floor(missing/2)
|
||||
if max_imag - min_imag < 8:
|
||||
missing = 8 - (max_imag - min_imag)
|
||||
max_imag += math.ceil(missing/2)
|
||||
min_imag -= math.floor(missing/2)
|
||||
|
||||
if 0 > max_imag > -2:
|
||||
max_imag = 0
|
||||
if 0 < min_imag < 2:
|
||||
min_imag = 0
|
||||
|
||||
if (max_imag - min_imag) > 8 and min_imag < 0 < max_imag:
|
||||
# We should show a "0" line for the reactive part
|
||||
span = max_imag - min_imag
|
||||
step_size = span / 8
|
||||
if max_imag < step_size:
|
||||
# The 0 line is the first step after the top. Scale accordingly.
|
||||
max_imag = -min_imag/7
|
||||
elif -min_imag < step_size:
|
||||
# The 0 line is the last step before the bottom. Scale accordingly.
|
||||
min_imag = -max_imag/7
|
||||
else:
|
||||
# Scale max_imag to be a whole factor of min_imag
|
||||
num_min = math.floor(min_imag/step_size * -1)
|
||||
num_max = 8 - num_min
|
||||
max_imag = num_max * (min_imag / num_min) * -1
|
||||
|
||||
self.max_real = max_real
|
||||
self.max_imag = max_imag
|
||||
|
@ -1267,14 +1495,14 @@ class RealImaginaryChart(Chart):
|
|||
qp.drawText(3, self.chartHeight + 20, str(round(min_real, 1)))
|
||||
qp.drawText(self.leftMargin + self.chartWidth + 8, self.chartHeight + 20, str(round(min_imag, 1)))
|
||||
|
||||
qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, LogMagChart.shortenFrequency(fstart))
|
||||
qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, Chart.shortenFrequency(fstart))
|
||||
ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin
|
||||
for i in range(ticks):
|
||||
x = self.leftMargin + round((i+1)*self.chartWidth/ticks)
|
||||
qp.setPen(QtGui.QPen(self.foregroundColor))
|
||||
qp.drawLine(x, 20, x, 20+self.chartHeight+5)
|
||||
qp.setPen(self.textColor)
|
||||
qp.drawText(x-20, 20+self.chartHeight+15, LogMagChart.shortenFrequency(round(fspan/ticks*(i+1) + fstart)))
|
||||
qp.drawText(x-20, 20+self.chartHeight+15, Chart.shortenFrequency(round(fspan/ticks*(i+1) + fstart)))
|
||||
|
||||
primary_pen = pen
|
||||
secondary_pen = QtGui.QPen(self.secondarySweepColor)
|
||||
|
@ -1343,13 +1571,19 @@ class RealImaginaryChart(Chart):
|
|||
qp.setPen(secondary_pen)
|
||||
qp.drawPoint(int(x), int(y_im))
|
||||
|
||||
# if self.drawLines and i > 0:
|
||||
# logmag = self.logMag(self.reference[i-1])
|
||||
# prevx = self.leftMargin + 1 + round(self.chartWidth*(self.reference[i-1].freq - fstart)/fspan)
|
||||
# prevy = 30 + round((logmag - minValue) / span * (self.chartHeight - 10))
|
||||
# qp.setPen(line_pen)
|
||||
# qp.drawLine(x, y, prevx, prevy)
|
||||
# qp.setPen(pen)
|
||||
if self.drawLines and i > 0:
|
||||
new_re, new_im = NanoVNASaver.normalize50(self.reference[i-1])
|
||||
prev_x = self.getXPosition(self.reference[i-1])
|
||||
prev_y_re = 30 + round((max_real - new_re) / span_real * (self.chartHeight - 10))
|
||||
prev_y_im = 30 + round((max_imag - new_im) / span_imag * (self.chartHeight - 10))
|
||||
|
||||
if re > 0 and new_re > 0:
|
||||
line_pen.setColor(self.referenceColor)
|
||||
qp.setPen(line_pen)
|
||||
qp.drawLine(x, y_re, prev_x, prev_y_re)
|
||||
|
||||
qp.drawLine(x, y_im, prev_x, prev_y_im)
|
||||
|
||||
# Now draw the markers
|
||||
for m in self.markers:
|
||||
if m.location != -1:
|
||||
|
@ -1359,7 +1593,7 @@ class RealImaginaryChart(Chart):
|
|||
x = self.getXPosition(self.data[m.location])
|
||||
y_re = 30 + round((max_real - re) / span_real * (self.chartHeight - 10))
|
||||
y_im = 30 + round((max_imag - im) / span_imag * (self.chartHeight - 10))
|
||||
|
||||
|
||||
qp.drawLine(int(x), int(y_re) + 3, int(x) - 3, int(y_re) - 3)
|
||||
qp.drawLine(int(x), int(y_re) + 3, int(x) + 3, int(y_re) - 3)
|
||||
qp.drawLine(int(x) - 3, int(y_re) - 3, int(x) + 3, int(y_re) - 3)
|
||||
|
@ -1399,18 +1633,48 @@ class RealImaginaryChart(Chart):
|
|||
nearest = m
|
||||
return nearest
|
||||
|
||||
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
|
||||
x = a0.x()
|
||||
absx = x - self.leftMargin
|
||||
if absx < 0 or absx > self.chartWidth:
|
||||
a0.ignore()
|
||||
def setMinimumRealValue(self):
|
||||
min_val, selected = QtWidgets.QInputDialog.getInt(self, "Minimum real value",
|
||||
"Set minimum real value", value=self.minDisplayReal)
|
||||
if not selected:
|
||||
return
|
||||
a0.accept()
|
||||
if self.fstop - self.fstart > 0:
|
||||
m = self.getActiveMarker(a0)
|
||||
span = self.fstop - self.fstart
|
||||
step = span/self.chartWidth
|
||||
f = self.fstart + absx * step
|
||||
m.setFrequency(str(round(f)))
|
||||
m.frequencyInput.setText(str(round(f)))
|
||||
return
|
||||
self.minDisplayValue = min_val
|
||||
if self.fixedValues:
|
||||
self.update()
|
||||
|
||||
def setMaximumRealValue(self):
|
||||
max_val, selected = QtWidgets.QInputDialog.getInt(self, "Maximum real value",
|
||||
"Set maximum real value", value=self.maxDisplayReal)
|
||||
if not selected:
|
||||
return
|
||||
self.maxDisplayValue = max_val
|
||||
if self.fixedValues:
|
||||
self.update()
|
||||
|
||||
def setMinimumImagValue(self):
|
||||
min_val, selected = QtWidgets.QInputDialog.getInt(self, "Minimum imaginary value",
|
||||
"Set minimum imaginary value", value=self.minDisplayImag)
|
||||
if not selected:
|
||||
return
|
||||
self.minDisplayValue = min_val
|
||||
if self.fixedValues:
|
||||
self.update()
|
||||
|
||||
def setMaximumImagValue(self):
|
||||
max_val, selected = QtWidgets.QInputDialog.getInt(self, "Maximum imaginary value",
|
||||
"Set maximum imaginary value", value=self.maxDisplayImag)
|
||||
if not selected:
|
||||
return
|
||||
self.maxDisplayValue = max_val
|
||||
if self.fixedValues:
|
||||
self.update()
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
self.action_set_fixed_start.setText("Start (" + Chart.shortenFrequency(self.minFrequency) + ")")
|
||||
self.action_set_fixed_stop.setText("Stop (" + Chart.shortenFrequency(self.maxFrequency) + ")")
|
||||
self.action_set_fixed_minimum_real.setText("Minimum R (" + str(self.minDisplayReal) + ")")
|
||||
self.action_set_fixed_maximum_real.setText("Maximum R (" + str(self.maxDisplayReal) + ")")
|
||||
self.action_set_fixed_minimum_imag.setText("Minimum jX (" + str(self.minDisplayImag) + ")")
|
||||
self.action_set_fixed_maximum_imag.setText("Maximum jX (" + str(self.maxDisplayImag) + ")")
|
||||
|
||||
self.menu.exec_(event.globalPos())
|
||||
|
|
|
@ -75,7 +75,6 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
self.markers = []
|
||||
|
||||
self.serialPort = self.getPort()
|
||||
# self.serialSpeed = "115200"
|
||||
|
||||
logger.debug("Building user interface")
|
||||
|
||||
|
@ -214,13 +213,13 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
self.btnStopSweep = QtWidgets.QPushButton("Stop")
|
||||
self.btnStopSweep.clicked.connect(self.stopSweep)
|
||||
self.btnStopSweep.setDisabled(True)
|
||||
btnLayout = QtWidgets.QHBoxLayout()
|
||||
btnLayout.addWidget(self.btnSweep)
|
||||
btnLayout.addWidget(self.btnStopSweep)
|
||||
btnLayout.setContentsMargins(0, 0, 0, 0)
|
||||
btnLayoutWidget = QtWidgets.QWidget()
|
||||
btnLayoutWidget.setLayout(btnLayout)
|
||||
sweep_control_layout.addRow(btnLayoutWidget)
|
||||
btn_layout = QtWidgets.QHBoxLayout()
|
||||
btn_layout.addWidget(self.btnSweep)
|
||||
btn_layout.addWidget(self.btnStopSweep)
|
||||
btn_layout.setContentsMargins(0, 0, 0, 0)
|
||||
btn_layout_widget = QtWidgets.QWidget()
|
||||
btn_layout_widget.setLayout(btn_layout)
|
||||
sweep_control_layout.addRow(btn_layout_widget)
|
||||
|
||||
left_column.addWidget(sweep_control_box)
|
||||
|
||||
|
@ -266,7 +265,6 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
|
||||
for c in self.charts:
|
||||
c.setMarkers(self.markers)
|
||||
#marker3.frequencyInput.setDisabled(True)
|
||||
left_column.addWidget(marker_control_box)
|
||||
|
||||
marker_column.addWidget(self.markers[0].getGroupBox())
|
||||
|
@ -363,11 +361,13 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
serial_control_layout = QtWidgets.QFormLayout(serial_control_box)
|
||||
self.serialPortInput = QtWidgets.QLineEdit(self.serialPort)
|
||||
self.serialPortInput.setAlignment(QtCore.Qt.AlignRight)
|
||||
# self.serialSpeedInput = QtWidgets.QLineEdit(str(self.serialSpeed))
|
||||
# self.serialSpeedInput.setValidator(QtGui.QIntValidator())
|
||||
# self.serialSpeedInput.setAlignment(QtCore.Qt.AlignRight)
|
||||
serial_control_layout.addRow(QtWidgets.QLabel("Serial port"), self.serialPortInput)
|
||||
# serial_control_layout.addRow(QtWidgets.QLabel("Speed"), self.serialSpeedInput)
|
||||
btn_rescan_serial_port = QtWidgets.QPushButton("Rescan")
|
||||
btn_rescan_serial_port.setFixedWidth(60)
|
||||
btn_rescan_serial_port.clicked.connect(self.rescanSerialPort)
|
||||
serial_port_input_layout = QtWidgets.QHBoxLayout()
|
||||
serial_port_input_layout.addWidget(self.serialPortInput)
|
||||
serial_port_input_layout.addWidget(btn_rescan_serial_port)
|
||||
serial_control_layout.addRow(QtWidgets.QLabel("Serial port"), serial_port_input_layout)
|
||||
|
||||
self.btnSerialToggle = QtWidgets.QPushButton("Connect to NanoVNA")
|
||||
self.btnSerialToggle.clicked.connect(self.serialButtonClick)
|
||||
|
@ -388,36 +388,36 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
reference_file_control_box = QtWidgets.QGroupBox("Import file")
|
||||
reference_file_control_layout = QtWidgets.QFormLayout(reference_file_control_box)
|
||||
self.referenceFileNameInput = QtWidgets.QLineEdit("")
|
||||
btnReferenceFilePicker = QtWidgets.QPushButton("...")
|
||||
btnReferenceFilePicker.setMaximumWidth(25)
|
||||
btnReferenceFilePicker.clicked.connect(self.pickReferenceFile)
|
||||
referenceFileNameLayout = QtWidgets.QHBoxLayout()
|
||||
referenceFileNameLayout.addWidget(self.referenceFileNameInput)
|
||||
referenceFileNameLayout.addWidget(btnReferenceFilePicker)
|
||||
btn_reference_file_picker = QtWidgets.QPushButton("...")
|
||||
btn_reference_file_picker.setMaximumWidth(25)
|
||||
btn_reference_file_picker.clicked.connect(self.pickReferenceFile)
|
||||
reference_file_name_layout = QtWidgets.QHBoxLayout()
|
||||
reference_file_name_layout.addWidget(self.referenceFileNameInput)
|
||||
reference_file_name_layout.addWidget(btn_reference_file_picker)
|
||||
|
||||
reference_file_control_layout.addRow(QtWidgets.QLabel("Filename"), referenceFileNameLayout)
|
||||
reference_file_control_layout.addRow(QtWidgets.QLabel("Filename"), reference_file_name_layout)
|
||||
file_window_layout.addWidget(reference_file_control_box)
|
||||
|
||||
btnLoadReference = QtWidgets.QPushButton("Load reference")
|
||||
btnLoadReference.clicked.connect(self.loadReferenceFile)
|
||||
btnLoadSweep = QtWidgets.QPushButton("Load as sweep")
|
||||
btnLoadSweep.clicked.connect(self.loadSweepFile)
|
||||
reference_file_control_layout.addRow(btnLoadReference)
|
||||
reference_file_control_layout.addRow(btnLoadSweep)
|
||||
btn_load_reference = QtWidgets.QPushButton("Load reference")
|
||||
btn_load_reference.clicked.connect(self.loadReferenceFile)
|
||||
btn_load_sweep = QtWidgets.QPushButton("Load as sweep")
|
||||
btn_load_sweep.clicked.connect(self.loadSweepFile)
|
||||
reference_file_control_layout.addRow(btn_load_reference)
|
||||
reference_file_control_layout.addRow(btn_load_sweep)
|
||||
|
||||
file_control_box = QtWidgets.QGroupBox()
|
||||
file_control_box.setTitle("Export file")
|
||||
file_control_box.setMaximumWidth(300)
|
||||
file_control_layout = QtWidgets.QFormLayout(file_control_box)
|
||||
self.fileNameInput = QtWidgets.QLineEdit("")
|
||||
btnFilePicker = QtWidgets.QPushButton("...")
|
||||
btnFilePicker.setMaximumWidth(25)
|
||||
btnFilePicker.clicked.connect(self.pickFile)
|
||||
fileNameLayout = QtWidgets.QHBoxLayout()
|
||||
fileNameLayout.addWidget(self.fileNameInput)
|
||||
fileNameLayout.addWidget(btnFilePicker)
|
||||
btn_file_picker = QtWidgets.QPushButton("...")
|
||||
btn_file_picker.setMaximumWidth(25)
|
||||
btn_file_picker.clicked.connect(self.pickFile)
|
||||
file_name_layout = QtWidgets.QHBoxLayout()
|
||||
file_name_layout.addWidget(self.fileNameInput)
|
||||
file_name_layout.addWidget(btn_file_picker)
|
||||
|
||||
file_control_layout.addRow(QtWidgets.QLabel("Filename"), fileNameLayout)
|
||||
file_control_layout.addRow(QtWidgets.QLabel("Filename"), file_name_layout)
|
||||
|
||||
self.btnExportFile = QtWidgets.QPushButton("Export data S1P")
|
||||
self.btnExportFile.clicked.connect(self.exportFileS1P)
|
||||
|
@ -429,8 +429,8 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
|
||||
file_window_layout.addWidget(file_control_box)
|
||||
|
||||
btnOpenFileWindow = QtWidgets.QPushButton("Files ...")
|
||||
btnOpenFileWindow.clicked.connect(self.displayFileWindow)
|
||||
btn_open_file_window = QtWidgets.QPushButton("Files ...")
|
||||
btn_open_file_window.clicked.connect(self.displayFileWindow)
|
||||
|
||||
################################################################################################################
|
||||
# Calibration
|
||||
|
@ -457,34 +457,30 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
"\n\n\N{COPYRIGHT SIGN} Copyright 2019 Rune B. Broberg\n" +
|
||||
"This program comes with ABSOLUTELY NO WARRANTY\n" +
|
||||
"This program is licensed under the GNU General Public License version 3\n\n" +
|
||||
"See https://github.com/mihtjel/nanovna-saver for further details"))
|
||||
"See https://mihtjel.github.io/nanovna-saver/ for further details"))
|
||||
|
||||
button_grid = QtWidgets.QGridLayout()
|
||||
button_grid.addWidget(btnOpenFileWindow, 0, 0)
|
||||
button_grid.addWidget(btn_open_file_window, 0, 0)
|
||||
button_grid.addWidget(btnOpenCalibrationWindow, 0, 1)
|
||||
button_grid.addWidget(btn_display_setup, 1, 0)
|
||||
button_grid.addWidget(btn_about, 1, 1)
|
||||
left_column.addLayout(button_grid)
|
||||
#left_column.addWidget(btnOpenFileWindow)
|
||||
#left_column.addWidget(btnOpenCalibrationWindow)
|
||||
#left_column.addWidget(btn_display_setup)
|
||||
#left_column.addWidget(btn_about)
|
||||
|
||||
################################################################################################################
|
||||
# Right side
|
||||
################################################################################################################
|
||||
|
||||
self.lister = QtWidgets.QPlainTextEdit()
|
||||
self.lister.setFixedHeight(200)
|
||||
self.lister.setMaximumWidth(350)
|
||||
# The lister doesn't really need to be visible for now. Maybe put it in a separate window?
|
||||
#marker_column.addWidget(self.lister)
|
||||
|
||||
self.worker.signals.updated.connect(self.dataUpdated)
|
||||
self.worker.signals.finished.connect(self.sweepFinished)
|
||||
self.worker.signals.sweepError.connect(self.showSweepError)
|
||||
|
||||
logger.debug("Finished building interface")
|
||||
|
||||
def rescanSerialPort(self):
|
||||
serial_port = self.getPort()
|
||||
self.serialPort = serial_port
|
||||
self.serialPortInput.setText(serial_port)
|
||||
|
||||
# Get that windows port
|
||||
@staticmethod
|
||||
def getPort() -> str:
|
||||
|
@ -495,6 +491,7 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
port = d.device
|
||||
logger.info("Found NanoVNA (%04x %04x) on port %s", d.vid, d.pid, d.device)
|
||||
return port
|
||||
return ""
|
||||
|
||||
def pickReferenceFile(self):
|
||||
filename, _ = QtWidgets.QFileDialog.getOpenFileName(directory=self.referenceFileNameInput.text(),
|
||||
|
@ -788,11 +785,15 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
|
||||
@staticmethod
|
||||
def capacitanceEquivalent(im50, freq) -> str:
|
||||
if im50 == 0 or freq == 0:
|
||||
return "- pF"
|
||||
capacitance = 10**12/(freq * 2 * math.pi * im50)
|
||||
return str(round(-capacitance, 3)) + " pF"
|
||||
|
||||
@staticmethod
|
||||
def inductanceEquivalent(im50, freq) -> str:
|
||||
if freq == 0:
|
||||
return "- nH"
|
||||
inductance = im50 / (freq * 2 * math.pi)
|
||||
return str(round(inductance * 1000000000, 3)) + " nH"
|
||||
|
||||
|
@ -855,10 +856,12 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
if self.sweepCountInput.text().isdigit():
|
||||
segments = int(self.sweepCountInput.text())
|
||||
fstep = fspan / (segments * 101)
|
||||
self.sweepStepLabel.setText(self.formatFrequency(fstep) + "/step")
|
||||
self.sweepStepLabel.setText(self.formatShortFrequency(fstep) + "/step")
|
||||
|
||||
@staticmethod
|
||||
def formatFrequency(freq):
|
||||
if freq < 1:
|
||||
return "- Hz"
|
||||
if math.log10(freq) < 3:
|
||||
return str(round(freq)) + " Hz"
|
||||
elif math.log10(freq) < 7:
|
||||
|
@ -868,6 +871,25 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
else:
|
||||
return "{:.3f}".format(freq/1000000) + " MHz"
|
||||
|
||||
@staticmethod
|
||||
def formatShortFrequency(freq):
|
||||
if freq < 1:
|
||||
return "- Hz"
|
||||
if math.log10(freq) < 3:
|
||||
return str(round(freq)) + " Hz"
|
||||
elif math.log10(freq) < 5:
|
||||
return "{:.3f}".format(freq/1000) + " kHz"
|
||||
elif math.log10(freq) < 6:
|
||||
return "{:.2f}".format(freq/1000) + " kHz"
|
||||
elif math.log10(freq) < 7:
|
||||
return "{:.1f}".format(freq/1000) + " kHz"
|
||||
elif math.log10(freq) < 8:
|
||||
return "{:.3f}".format(freq/1000000) + " MHz"
|
||||
elif math.log10(freq) < 9:
|
||||
return "{:.2f}".format(freq/1000000) + " MHz"
|
||||
else:
|
||||
return "{:.1f}".format(freq/1000000) + " MHz"
|
||||
|
||||
@staticmethod
|
||||
def parseFrequency(freq: str):
|
||||
freq = freq.replace(" ", "") # People put all sorts of weird whitespace in.
|
||||
|
@ -989,7 +1011,11 @@ class NanoVNASaver(QtWidgets.QWidget):
|
|||
QtWidgets.QApplication.setActiveWindow(self.tdr_window)
|
||||
|
||||
def showError(self, text):
|
||||
QtWidgets.QErrorMessage.showMessage(text)
|
||||
error_message = QtWidgets.QErrorMessage(self)
|
||||
error_message.showMessage(text)
|
||||
|
||||
def showSweepError(self):
|
||||
self.showError(self.worker.error_message)
|
||||
|
||||
def closeEvent(self, a0: QtGui.QCloseEvent) -> None:
|
||||
self.worker.stopped = True
|
||||
|
@ -1345,7 +1371,7 @@ class TDRWindow(QtWidgets.QWidget):
|
|||
self.tdr_velocity_dropdown.addItem("Semi-solid PE (SSPE) (0.84)", 0.84)
|
||||
self.tdr_velocity_dropdown.addItem("Air (Helical spacers) (0.94)", 0.94)
|
||||
self.tdr_velocity_dropdown.insertSeparator(self.tdr_velocity_dropdown.count())
|
||||
# Lots of table types added by Larry Goga, AE5CZ
|
||||
# Lots of cable types added by Larry Goga, AE5CZ
|
||||
self.tdr_velocity_dropdown.addItem("RG-6/U PE 75\N{OHM SIGN} (Belden 8215) (0.66)", 0.66)
|
||||
self.tdr_velocity_dropdown.addItem("RG-6/U Foam 75\N{OHM SIGN} (Belden 9290) (0.81)", 0.81)
|
||||
self.tdr_velocity_dropdown.addItem("RG-8/U PE 50\N{OHM SIGN} (Belden 8237) (0.66)", 0.66)
|
||||
|
|
|
@ -32,6 +32,7 @@ Datapoint = collections.namedtuple('Datapoint', 'freq re im')
|
|||
class WorkerSignals(QtCore.QObject):
|
||||
updated = pyqtSignal()
|
||||
finished = pyqtSignal()
|
||||
sweepError = pyqtSignal()
|
||||
|
||||
|
||||
class SweepWorker(QtCore.QRunnable):
|
||||
|
@ -52,6 +53,7 @@ class SweepWorker(QtCore.QRunnable):
|
|||
self.averaging = False
|
||||
self.averages = 3
|
||||
self.truncates = 0
|
||||
self.error_message = ""
|
||||
|
||||
@pyqtSlot()
|
||||
def run(self):
|
||||
|
@ -118,15 +120,20 @@ class SweepWorker(QtCore.QRunnable):
|
|||
logger.debug("Stopping sweeping as signalled")
|
||||
break
|
||||
start = sweep_from + i*101*stepsize
|
||||
freq, val11, val21 = self.readSegment(start, start+100*stepsize)
|
||||
try:
|
||||
freq, val11, val21 = self.readSegment(start, start+100*stepsize)
|
||||
|
||||
frequencies += freq
|
||||
values += val11
|
||||
values21 += val21
|
||||
frequencies += freq
|
||||
values += val11
|
||||
values21 += val21
|
||||
|
||||
self.percentage = (i+1)*100/self.noSweeps
|
||||
logger.debug("Saving acquired data")
|
||||
self.saveData(frequencies, values, values21)
|
||||
self.percentage = (i+1)*100/self.noSweeps
|
||||
logger.debug("Saving acquired data")
|
||||
self.saveData(frequencies, values, values21)
|
||||
except NanoVNAValueException as e:
|
||||
self.error_message = str(e)
|
||||
self.stopped = True
|
||||
self.signals.sweepError.emit()
|
||||
|
||||
while self.continuousSweep and not self.stopped:
|
||||
logger.debug("Continuous sweeping")
|
||||
|
@ -136,9 +143,14 @@ class SweepWorker(QtCore.QRunnable):
|
|||
logger.debug("Stopping sweeping as signalled")
|
||||
break
|
||||
start = sweep_from + i * 101 * stepsize
|
||||
_, values, values21 = self.readSegment(start, start + 100 * stepsize)
|
||||
logger.debug("Updating acquired data")
|
||||
self.updateData(values, values21, i)
|
||||
try:
|
||||
_, values, values21 = self.readSegment(start, start + 100 * stepsize)
|
||||
logger.debug("Updating acquired data")
|
||||
self.updateData(values, values21, i)
|
||||
except NanoVNAValueException as e:
|
||||
self.error_message = str(e)
|
||||
self.stopped = True
|
||||
self.signals.sweepError.emit()
|
||||
|
||||
# Reset the device to show the full range
|
||||
logger.debug("Resetting NanoVNA sweep to full range: %d to %d",
|
||||
|
@ -307,8 +319,9 @@ class SweepWorker(QtCore.QRunnable):
|
|||
if count == 10:
|
||||
logger.error("Tried and failed to read %s %d times.", data, count)
|
||||
if count >= 20:
|
||||
logger.error("Tried and failed to read %s %d times. Giving up.", data, count)
|
||||
return None # Put a proper exception in here
|
||||
logger.critical("Tried and failed to read %s %d times. Giving up.", data, count)
|
||||
raise NanoVNAValueException("Failed reading " + str(data) + " " + str(count) + " times.\n" +
|
||||
"Data outside expected valid ranges, or in an unexpected format.")
|
||||
return returndata
|
||||
|
||||
def readFreq(self):
|
||||
|
@ -330,19 +343,23 @@ class SweepWorker(QtCore.QRunnable):
|
|||
if count == 10:
|
||||
logger.error("Tried and failed %d times to read frequencies.", count)
|
||||
if count >= 20:
|
||||
logger.critical("Tried and failed to read frequencies from the NanoVNA more than %d times.", count)
|
||||
return None # Put a proper exception in here
|
||||
logger.critical("Tried and failed to read frequencies from the NanoVNA %d times.", count)
|
||||
raise NanoVNAValueException("Failed reading frequencies " + str(count) + " times.")
|
||||
else:
|
||||
returnfreq.append(int(f))
|
||||
return returnfreq
|
||||
|
||||
def setContinuousSweep(self, continuousSweep):
|
||||
def setContinuousSweep(self, continuousSweep: bool):
|
||||
self.continuousSweep = continuousSweep
|
||||
|
||||
def setAveraging(self, averaging, averages, truncates):
|
||||
def setAveraging(self, averaging: bool, averages: str, truncates: str):
|
||||
self.averaging = averaging
|
||||
try:
|
||||
self.averages = int(averages)
|
||||
self.truncates = int(truncates)
|
||||
except:
|
||||
return
|
||||
|
||||
|
||||
class NanoVNAValueException(Exception):
|
||||
pass
|
||||
|
|
Ładowanie…
Reference in New Issue