Skip to content

Commit

Permalink
Zooming and panning in TDR window and choosing fft window type (#804)
Browse files Browse the repository at this point in the history
* enable user to set velocity factor before any data arrives
* fix wrong limits, experiments with kaiser window
* amplitude correction aligned for all windowing functions

Co-authored-by: André Lison <andre.lison@general-bytes.com>
  • Loading branch information
arrlee and lisona authored Feb 20, 2025
1 parent cc7c91d commit 314b91d
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 74 deletions.
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,4 @@ below:
.. image:: https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif
:target: https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=T8KTGVDQF5K6E&item_name=NanoVNASaver+Development&currency_code=EUR&source=url
:alt: Paypal

14 changes: 14 additions & 0 deletions src/NanoVNASaver/Charts/Chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,10 @@ def mouseReleaseEvent(self, a0: QtGui.QMouseEvent) -> None:

def wheelEvent(self, a0: QtGui.QWheelEvent) -> None:
delta = a0.angleDelta().y()
logger.debug(f"wheelEvent {delta}, {self.data}, {self.reference}")
if not delta or (not self.data and not self.reference):
a0.ignore()
logger.debug("nothing to do, returning")
return
modifiers = a0.modifiers()

Expand Down Expand Up @@ -356,3 +358,15 @@ def update(self, a=None, b=None, c=None, d=None) -> None: # pylint: disable=unu
pal.setColor(QtGui.QPalette.ColorRole.Window, Chart.color.background)
self.setPalette(pal)
super().update()

def drawDragbog(self, qp: QtGui.QPainter):
dashed_pen = QtGui.QPen(Chart.color.foreground, 1, Qt.PenStyle.DashLine)
qp.setPen(dashed_pen)
top_left = QtCore.QPoint(
self.dragbox.pos_start[0], self.dragbox.pos_start[1]
)
bottom_right = QtCore.QPoint(self.dragbox.pos[0], self.dragbox.pos[1])
rect = QtCore.QRect(top_left, bottom_right)
qp.drawRect(rect)


10 changes: 0 additions & 10 deletions src/NanoVNASaver/Charts/Frequency.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,16 +513,6 @@ def _check_frequency_boundaries(self, qp: QtGui.QPainter):
"Data outside frequency span",
)

def drawDragbog(self, qp: QtGui.QPainter):
dashed_pen = QtGui.QPen(Chart.color.foreground, 1, Qt.PenStyle.DashLine)
qp.setPen(dashed_pen)
top_left = QtCore.QPoint(
self.dragbox.pos_start[0], self.dragbox.pos_start[1]
)
bottom_right = QtCore.QPoint(self.dragbox.pos[0], self.dragbox.pos[1])
rect = QtCore.QRect(top_left, bottom_right)
qp.drawRect(rect)

def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(Chart.color.text))
headline = self.name
Expand Down
126 changes: 87 additions & 39 deletions src/NanoVNASaver/Charts/TDR.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
QPalette,
QPen,
QResizeEvent,
QWheelEvent,
QShortcut,
)
from PySide6.QtWidgets import QDialog, QInputDialog, QMenu, QSizePolicy

Expand Down Expand Up @@ -160,6 +162,22 @@ def __init__(self, name) -> None:
self.dim.width = self.width() - self.leftMargin - self.rightMargin
self.dim.height = self.height() - self.bottomMargin - self.topMargin

QShortcut(Qt.Key.Key_Up, self, lambda: self.pan_graph(0, 1))
QShortcut(Qt.Key.Key_Down, self, lambda: self.pan_graph(0, -1))
QShortcut(Qt.Key.Key_Left, self, lambda: self.pan_graph(1, 0))
QShortcut(Qt.Key.Key_Right, self, lambda: self.pan_graph(-1, 0))

def pan_graph(self, x, y):
logger.debug(f"Moving graph {x}, {y}")
dx = self.dim.width / 10 * x
dy = self.dim.height / 10 * y
self.zoomTo(
self.leftMargin + dx,
self.topMargin + dy,
self.leftMargin + self.dim.width + dx,
self.topMargin + self.dim.height + dy,
)

def contextMenuEvent(self, event) -> None:
self.action_set_fixed_start.setText(
f"Start ({self.min_display_length})"
Expand All @@ -183,8 +201,8 @@ def _configureGraphFromFormat(self) -> None:
"impedance (\N{OHM SIGN})",
1,
),
"S11 (lowpass)": (MIN_S11, MIN_S11, "S11 (dB)", 1),
"VSWR (lowpass)": (MIN_VSWR, MIN_VSWR, "VSWR", 2),
"S11 (lowpass)": (MIN_S11, MAX_S11, "S11 (dB)", 1),
"VSWR (lowpass)": (MIN_VSWR, MAX_VSWR, "VSWR", 2),
"Refl (lowpass)": (-1, 1, "U", 2),
"Refl (bandpass)": (0, 1, "U", 2),
}
Expand Down Expand Up @@ -282,6 +300,11 @@ def copy(self) -> "TDRChart":
self.tdrWindow.updated.connect(new_chart.update)
return new_chart

def wheelEvent(self, a0: QWheelEvent) -> None:
a0.accept()
self.data = [0] # A bit of cheating otherwise the super().wheelEvent() exits without doing anything.
super().wheelEvent(a0)

def mouseMoveEvent(self, a0: QMouseEvent) -> None:
if not hasattr(self.tdrWindow, "td"):
return
Expand All @@ -305,15 +328,16 @@ def mouseMoveEvent(self, a0: QMouseEvent) -> None:
return
if a0.modifiers() == Qt.KeyboardModifier.ControlModifier:
# Dragging a box
a0.accept()
if not self.dragbox.state:
self.dragbox.state = True
self.dragbox.pos_start = ChartPosition(
a0.position().x(), a0.position().y()
)
self.dragbox.pos = ChartPosition(
a0.position().x(), a0.position().y()
)
self.update()
a0.accept()
return

x = a0.position().x()
Expand All @@ -337,9 +361,9 @@ def mouseMoveEvent(self, a0: QMouseEvent) -> None:
self.marker_location = int(round(absx * x_step))
self.update()

def _draw_ticks(self, height, width, x_step, min_index) -> None:
def _draw_ticks(self, height, width, x_step, min_index, qp: QPainter) -> None:
ticks = (self.width() - self.leftMargin) // 100
qp = QPainter(self)
# qp = QPainter(self)
for i in range(ticks):
x = self.leftMargin + round((i + 1) * width / ticks)
qp.setPen(QPen(Chart.color.foreground))
Expand All @@ -366,9 +390,8 @@ def _draw_ticks(self, height, width, x_step, min_index) -> None:
)

def _draw_y_ticks(
self, height, width, min_impedance, max_impedance
self, height, width, min_impedance, max_impedance, qp: QPainter
) -> None:
qp = QPainter(self)
y_step = (max_impedance - min_impedance) / height
y_ticks = math.floor(height / 60)
y_tick_step = height / y_ticks
Expand All @@ -386,8 +409,7 @@ def _draw_y_ticks(
f"{round(min_impedance, self.decimals)}",
)

def _draw_max_point(self, height, x_step, y_step, min_index) -> None:
qp = QPainter(self)
def _draw_max_point(self, height, x_step, y_step, min_index, qp: QPainter) -> None:
id_max = np.argmax(self.tdrWindow.td)

max_point = QPoint(
Expand All @@ -405,8 +427,7 @@ def _draw_max_point(self, height, x_step, y_step, min_index) -> None:
f"{round(self.tdrWindow.distance_axis[id_max] / 2, 2)}m",
)

def _draw_marker(self, height, x_step, y_step, min_index):
qp = QPainter(self)
def _draw_marker(self, height, x_step, y_step, min_index, qp: QPainter):
marker_point = QPoint(
self.leftMargin + int((self.marker_location - min_index) / x_step),
(self.topMargin + height)
Expand All @@ -422,7 +443,7 @@ def _draw_marker(self, height, x_step, y_step, min_index):
}m""",
)

def _draw_graph(self, height, width):
def _draw_graph(self, height, width, qp: QPainter) -> None:
min_index = 0
max_index = math.ceil(len(self.tdrWindow.distance_axis) / 2)

Expand All @@ -444,24 +465,23 @@ def _draw_graph(self, height, width):
# TODO: Limit the search to the selected span?
min_Z = np.min(self.tdrWindow.step_response_Z)
max_Z = np.max(self.tdrWindow.step_response_Z)

# Ensure that everything works even if limits are negative
min_impedance = max(self.min_y_lim, min_Z - 0.05 * np.abs(min_Z))
max_impedance = min(self.max_y_lim, max_Z + 0.05 * np.abs(max_Z))
if self.fixed_values:
min_impedance = self.min_y_lim
max_impedance = self.max_y_lim

y_step = max(self.tdrWindow.td) * 1.1 / height or 1.0e-30
self._draw_ticks(height, width, x_step, min_index, qp)
self._draw_y_ticks(height, width, min_impedance, max_impedance, qp)

self._draw_ticks(height, width, x_step, min_index)
self._draw_y_ticks(height, width, min_impedance, max_impedance)

qp = QPainter(self)
pen = QPen(Chart.color.sweep)
pen.setWidth(self.dim.point)
pen.setWidth(self.dim.line if self.flag.draw_lines else self.dim.point)
qp.setPen(pen)

y_step = (max_impedance - min_impedance) / height
last_x_primary, last_y_primary = None, None
last_x_secondary, last_y_secondary = None, None
for i in range(min_index, max_index):
x = self.leftMargin + int((i - min_index) / x_step)
y = (self.topMargin + height) - int(
Expand All @@ -470,7 +490,12 @@ def _draw_graph(self, height, width):
if self.isPlotable(x, y):
pen.setColor(Chart.color.sweep)
qp.setPen(pen)
qp.drawPoint(x, y)
if self.flag.draw_lines and last_x_primary is not None:
qp.drawLine(last_x_primary, last_y_primary, x, y)
else:
qp.drawPoint(x, y)
last_x_primary = x
last_y_primary = y

x = self.leftMargin + int((i - min_index) / x_step)
y = (self.topMargin + height) - int(
Expand All @@ -479,12 +504,17 @@ def _draw_graph(self, height, width):
if self.isPlotable(x, y):
pen.setColor(Chart.color.sweep_secondary)
qp.setPen(pen)
qp.drawPoint(x, y)
if self.flag.draw_lines and last_x_secondary is not None:
qp.drawLine(last_x_secondary, last_y_secondary, x, y)
else:
qp.drawPoint(x, y)
last_x_secondary = x
last_y_secondary = y

self._draw_max_point(height, x_step, y_step, min_index)
self._draw_max_point(height, x_step, y_step, min_index, qp)

if self.marker_location != -1:
self._draw_marker(height, x_step, y_step, min_index)
self._draw_marker(height, x_step, y_step, min_index, qp)

def paintEvent(self, _: QPaintEvent) -> None:
qp = QPainter(self)
Expand All @@ -511,17 +541,10 @@ def paintEvent(self, _: QPaintEvent) -> None:
self.drawTitle(qp)

if hasattr(self.tdrWindow, "td"):
self._draw_graph(height, width)
self._draw_graph(height, width, qp)

if self.dragbox.state and self.dragbox.pos[0] != -1:
dashed_pen = QPen(Chart.color.foreground, 1, Qt.PenStyle.DashLine)
qp.setPen(dashed_pen)
qp.drawRect(
QRect(
QPoint(*self.dragbox.pos_start),
QPoint(*self.dragbox.pos),
)
)
self.drawDragbog(qp)

qp.end()

Expand All @@ -546,11 +569,12 @@ def valueAtPosition(self, y: int) -> float:
return y_step * absy + min_impedance
return 0.0

def lengthAtPosition(self, x: int, limit=True):
if not hasattr(self.tdrWindow, "td"):
return 0
#
# Get the currently displayed
# start end length in meter and the step size in m/pixel
#
def _get_chart_parameters(self):
width = self.width() - self.leftMargin - self.rightMargin
absx = x - self.leftMargin
min_length = self.min_display_length if self.fixed_span else 0
max_length = (
self.max_display_length
Expand All @@ -562,18 +586,33 @@ def lengthAtPosition(self, x: int, limit=True):
/ 2
)
)

x_step = float(max_length - min_length) / width
return min_length, max_length, x_step

def lengthAtPosition(self, x: int, limit=True):
if not hasattr(self.tdrWindow, "td"):
return 0
min_length, max_length, x_step = self._get_chart_parameters()
absx = x - self.leftMargin
if limit and absx < 0:
return float(min_length)
return float(
max_length if limit and absx > width else absx * x_step + min_length
)

def positionAtLength(self, length, limit=True):
if not hasattr(self.tdrWindow, "td"):
return 0
min_length, max_length, x_step = self._get_chart_parameters()
if limit:
return self.leftMargin # really? not sure how to handle this
return ((length - min_length) / x_step) + self.leftMargin

def zoomTo(self, x1, y1, x2, y2) -> None:
logger.debug(
"Zoom to (x,y) by (x,y): (%d, %d) by (%d, %d)", x1, y1, x2, y2
)

val1 = self.valueAtPosition(y1)
val2 = self.valueAtPosition(y2)

Expand All @@ -582,8 +621,17 @@ def zoomTo(self, x1, y1, x2, y2) -> None:
self.max_y_lim = round(max(val1, val2), 3)
self.setFixedValues(True)

len1 = max(0, self.lengthAtPosition(x1, limit=False))
len2 = max(0, self.lengthAtPosition(x2, limit=False))
x_min = min(x1, x2)
x_max = max(x1, x2)

# test if we reach the negative length range -> adjust to zero
if self.lengthAtPosition(x_min, limit=False) < 0:
at_zero = self.positionAtLength(0, limit=False)
x_max = x_max + (at_zero - x_min)
x_min = at_zero

len1 = max(0, self.lengthAtPosition(x_min, limit=False))
len2 = max(0, self.lengthAtPosition(x_max, limit=False))

if len1 >= 0 and len2 >= 0 and len1 != len2:
self.min_display_length = min(len1, len2)
Expand Down
Loading

0 comments on commit 314b91d

Please sign in to comment.