nanovna-saver/NanoVNASaver/Chart.py

3969 wiersze
162 KiB
Python

# NanoVNASaver - a python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019. Rune B. Broberg
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import collections
import math
from typing import List, Set
import numpy as np
import logging
from scipy import signal
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtCore import pyqtSignal
from NanoVNASaver.RFTools import Datapoint, RFTools
from NanoVNASaver.SITools import Format, Value
from .Marker import Marker
logger = logging.getLogger(__name__)
class Chart(QtWidgets.QWidget):
sweepColor = QtCore.Qt.darkYellow
secondarySweepColor = QtCore.Qt.darkMagenta
referenceColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.blue)
referenceColor.setAlpha(64)
secondaryReferenceColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.blue)
secondaryReferenceColor.setAlpha(64)
backgroundColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.white)
foregroundColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.lightGray)
textColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.black)
swrColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.red)
swrColor.setAlpha(128)
data: List[Datapoint] = []
reference: List[Datapoint] = []
markers: List[Marker] = []
swrMarkers: Set[float] = set()
bands = None
draggedMarker: Marker = None
name = ""
drawLines = False
minChartHeight = 200
minChartWidth = 200
chartWidth = minChartWidth
chartHeight = minChartHeight
lineThickness = 1
pointSize = 2
markerSize = 3
drawMarkerNumbers = False
markerAtTip = False
filledMarkers = False
draggedBox = False
draggedBoxStart = (0, 0)
draggedBoxCurrent = (-1, -1)
moveStartX = -1
moveStartY = -1
isPopout = False
popoutRequested = pyqtSignal(object)
def __init__(self, name):
super().__init__()
self.name = name
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
self.action_save_screenshot = QtWidgets.QAction("Save image")
self.action_save_screenshot.triggered.connect(self.saveScreenshot)
self.addAction(self.action_save_screenshot)
self.action_popout = QtWidgets.QAction("Popout chart")
self.action_popout.triggered.connect(lambda: self.popoutRequested.emit(self))
self.addAction(self.action_popout)
self.swrMarkers = set()
def setSweepColor(self, color : QtGui.QColor):
self.sweepColor = color
self.update()
def setSecondarySweepColor(self, color : QtGui.QColor):
self.secondarySweepColor = color
self.update()
def setReferenceColor(self, color : QtGui.QColor):
self.referenceColor = color
self.update()
def setSecondaryReferenceColor(self, color : QtGui.QColor):
self.secondaryReferenceColor = color
self.update()
def setBackgroundColor(self, color: QtGui.QColor):
self.backgroundColor = color
pal = self.palette()
pal.setColor(QtGui.QPalette.Background, color)
self.setPalette(pal)
self.update()
def setForegroundColor(self, color: QtGui.QColor):
self.foregroundColor = color
self.update()
def setTextColor(self, color: QtGui.QColor):
self.textColor = color
self.update()
def setReference(self, data):
self.reference = data
self.update()
def resetReference(self):
self.reference = []
self.update()
def setData(self, data):
self.data = data
self.update()
def setMarkers(self, markers):
self.markers = markers
def setBands(self, bands):
self.bands = bands
def setLineThickness(self, thickness):
self.lineThickness = thickness
self.update()
def setPointSize(self, size):
self.pointSize = size
self.update()
def setMarkerSize(self, size):
self.markerSize = size
self.update()
def getActiveMarker(self) -> Marker:
if self.draggedMarker is not None:
return self.draggedMarker
for m in self.markers:
if m.isMouseControlledRadioButton.isChecked():
return m
return None
def getNearestMarker(self, x, y) -> Marker:
if len(self.data) == 0:
return None
shortest = 10**6
nearest = None
for m in self.markers:
mx, my = self.getPosition(self.data[m.location])
dx = abs(x - mx)
dy = abs(y - my)
distance = math.sqrt(dx**2 + dy**2)
if distance < shortest:
shortest = distance
nearest = m
return nearest
def getYPosition(self, d: Datapoint) -> int:
return 0
def getXPosition(self, d: Datapoint) -> int:
return 0
def getPosition(self, d: Datapoint) -> (int, int):
return self.getXPosition(d), self.getYPosition(d)
def setDrawLines(self, draw_lines):
self.drawLines = draw_lines
self.update()
def setDrawMarkerNumbers(self, draw_marker_numbers):
self.drawMarkerNumbers = draw_marker_numbers
self.update()
def setMarkerAtTip(self, marker_at_tip):
self.markerAtTip = marker_at_tip
self.update()
def setFilledMarkers(self, filled_markers):
self.filledMarkers = filled_markers
self.update()
@staticmethod
def shortenFrequency(frequency: int) -> str:
if frequency < 50000:
return str(frequency)
elif frequency < 5000000:
return str(round(frequency / 1000)) + "k"
elif frequency < 50000000:
return str(round(frequency / 1000000, 2)) + "M"
else:
return str(round(frequency / 1000000, 1)) + "M"
def mousePressEvent(self, event: QtGui.QMouseEvent) -> None:
if event.buttons() == QtCore.Qt.RightButton:
event.ignore()
return
elif event.buttons() == QtCore.Qt.MiddleButton:
# Drag event
event.accept()
self.moveStartX = event.x()
self.moveStartY = event.y()
return
if event.modifiers() == QtCore.Qt.ShiftModifier:
self.draggedMarker = self.getNearestMarker(event.x(), event.y())
elif event.modifiers() == QtCore.Qt.ControlModifier:
event.accept()
self.draggedBox = True
self.draggedBoxStart = (event.x(), event.y())
return
self.mouseMoveEvent(event)
def mouseReleaseEvent(self, a0: QtGui.QMouseEvent) -> None:
self.draggedMarker = None
if self.draggedBox:
self.zoomTo(self.draggedBoxStart[0], self.draggedBoxStart[1], a0.x(), a0.y())
self.draggedBox = False
self.draggedBoxCurrent = (-1, -1)
self.draggedBoxStart = (0, 0)
self.update()
def zoomTo(self, x1, y1, x2, y2):
pass
def saveScreenshot(self):
logger.info("Saving %s to file...", self.name)
filename, _ = QtWidgets.QFileDialog.getSaveFileName(parent=self, caption="Save image",
filter="PNG (*.png);;All files (*.*)")
logger.debug("Filename: %s", filename)
if filename != "":
self.grab().save(filename)
def copy(self):
new_chart = self.__class__(self.name)
new_chart.data = self.data
new_chart.reference = self.reference
new_chart.sweepColor = self.sweepColor
new_chart.secondarySweepColor = self.secondarySweepColor
new_chart.referenceColor = self.referenceColor
new_chart.secondaryReferenceColor = self.secondaryReferenceColor
new_chart.setBackgroundColor(self.backgroundColor)
new_chart.textColor = self.textColor
new_chart.foregroundColor = self.foregroundColor
new_chart.swrColor = self.swrColor
new_chart.markers = self.markers
new_chart.swrMarkers = self.swrMarkers
new_chart.bands = self.bands
new_chart.drawLines = self.drawLines
new_chart.markerSize = self.markerSize
new_chart.drawMarkerNumbers = self.drawMarkerNumbers
new_chart.filledMarkers = self.filledMarkers
new_chart.markerAtTip = self.markerAtTip
new_chart.resize(self.width(), self.height())
new_chart.setPointSize(self.pointSize)
new_chart.setLineThickness(self.lineThickness)
return new_chart
def addSWRMarker(self, swr: float):
self.swrMarkers.add(swr)
self.update()
def removeSWRMarker(self, swr: float):
try:
self.swrMarkers.remove(swr)
except KeyError:
logger.debug("KeyError from %s", self.name)
return
finally:
self.update()
def clearSWRMarkers(self):
self.swrMarkers.clear()
self.update()
def setSWRColor(self, color: QtGui.QColor):
self.swrColor = color
self.update()
def drawMarker(self, x, y, qp: QtGui.QPainter, color: QtGui.QColor, number=0):
if self.markerAtTip:
y -= self.markerSize
pen = QtGui.QPen(color)
qp.setPen(pen)
qpp = QtGui.QPainterPath()
qpp.moveTo(x, y + self.markerSize)
qpp.lineTo(x - self.markerSize, y - self.markerSize)
qpp.lineTo(x + self.markerSize, y - self.markerSize)
qpp.lineTo(x, y + self.markerSize)
if self.filledMarkers:
qp.fillPath(qpp, color)
else:
qp.drawPath(qpp)
if self.drawMarkerNumbers:
number_x = x - 3
number_y = y - self.markerSize - 3
qp.drawText(number_x, number_y, str(number))
class FrequencyChart(Chart):
fstart = 0
fstop = 0
maxFrequency = 100000000
minFrequency = 1000000
minDisplayValue = -1
maxDisplayValue = 1
fixedSpan = False
fixedValues = False
logarithmicX = False
leftMargin = 30
rightMargin = 20
bottomMargin = 20
topMargin = 30
def __init__(self, name):
super().__init__(name)
self.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
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.x_menu.addSeparator()
frequency_mode_group = QtWidgets.QActionGroup(self.x_menu)
self.action_set_linear_x = QtWidgets.QAction("Linear")
self.action_set_linear_x.setCheckable(True)
self.action_set_logarithmic_x = QtWidgets.QAction("Logarithmic")
self.action_set_logarithmic_x.setCheckable(True)
frequency_mode_group.addAction(self.action_set_linear_x)
frequency_mode_group.addAction(self.action_set_logarithmic_x)
self.action_set_linear_x.triggered.connect(lambda: self.setLogarithmicX(False))
self.action_set_logarithmic_x.triggered.connect(lambda: self.setLogarithmicX(True))
self.action_set_linear_x.setChecked(True)
self.x_menu.addAction(self.action_set_linear_x)
self.x_menu.addAction(self.action_set_logarithmic_x)
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)
self.menu.addSeparator()
self.menu.addAction(self.action_save_screenshot)
self.action_popout = QtWidgets.QAction("Popout chart")
self.action_popout.triggered.connect(lambda: self.popoutRequested.emit(self))
self.menu.addAction(self.action_popout)
self.setFocusPolicy(QtCore.Qt.ClickFocus)
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
if fixed_span and self.minFrequency >= self.maxFrequency:
self.fixedSpan = False
self.action_automatic.setChecked(True)
self.action_fixed_span.setChecked(False)
self.update()
def setFixedValues(self, fixed_values: bool):
self.fixedValues = fixed_values
if fixed_values and self.minDisplayValue >= self.maxDisplayValue:
self.fixedValues = False
self.y_action_automatic.setChecked(True)
self.y_action_fixed_span.setChecked(False)
self.update()
def setLogarithmicX(self, logarithmic: bool):
self.logarithmicX = logarithmic
self.update()
def setMinimumFrequency(self):
min_freq_str, selected = QtWidgets.QInputDialog.getText(self, "Start frequency",
"Set start frequency", text=str(self.minFrequency))
if not selected:
return
min_freq = RFTools.parseFrequency(min_freq_str)
if min_freq > 0 and not (self.fixedSpan and min_freq >= self.maxFrequency):
self.minFrequency = min_freq
if self.fixedSpan:
self.update()
def setMaximumFrequency(self):
max_freq_str, selected = QtWidgets.QInputDialog.getText(self, "Stop frequency",
"Set stop frequency", text=str(self.maxFrequency))
if not selected:
return
max_freq = RFTools.parseFrequency(max_freq_str)
if max_freq > 0 and not (self.fixedSpan and max_freq <= self.minFrequency):
self.maxFrequency = max_freq
if self.fixedSpan:
self.update()
def setMinimumValue(self):
min_val, selected = QtWidgets.QInputDialog.getDouble(self, "Minimum value",
"Set minimum value", value=self.minDisplayValue,
decimals=3)
if not selected:
return
if not (self.fixedValues and min_val >= self.maxDisplayValue):
self.minDisplayValue = min_val
if self.fixedValues:
self.update()
def setMaximumValue(self):
max_val, selected = QtWidgets.QInputDialog.getDouble(self, "Maximum value",
"Set maximum value", value=self.maxDisplayValue,
decimals=3)
if not selected:
return
if not (self.fixedValues and max_val <= self.minDisplayValue):
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.logarithmicX = False
self.update()
def getXPosition(self, d: Datapoint) -> int:
span = self.fstop - self.fstart
if span > 0:
if self.logarithmicX:
span = math.log(self.fstop) - math.log(self.fstart)
return self.leftMargin +\
round(self.chartWidth * (math.log(d.freq) - math.log(self.fstart)) / span)
else:
return self.leftMargin + round(self.chartWidth * (d.freq - self.fstart) / span)
else:
return math.floor(self.width()/2)
def frequencyAtPosition(self, x, limit = True) -> int:
"""
Calculates the frequency at a given X-position
:param limit: Determines whether frequencies outside the currently displayed span can be returned.
:param x: The X position to calculate for.
:return: The frequency at the given position, if one exists, or -1 otherwise. If limit is True, and the value
is before or after the chart, returns minimum or maximum frequencies.
"""
if self.fstop - self.fstart > 0:
absx = x - self.leftMargin
if limit and absx < 0:
return self.fstart
elif limit and absx > self.chartWidth:
return self.fstop
elif self.logarithmicX:
span = math.log(self.fstop) - math.log(self.fstart)
step = span/self.chartWidth
return round(math.exp(math.log(self.fstart) + absx * step))
else:
span = self.fstop - self.fstart
step = span/self.chartWidth
return round(self.fstart + absx * step)
else:
return -1
def valueAtPosition(self, y) -> List[float]:
"""
Returns the chart-specific value(s) at the specified Y-position
:param y: The Y position to calculate for.
:return: A list of the values at the Y-position, either containing a single value, or the two values for the
chart from left to right Y-axis. If no value can be found, returns the empty list. If the frequency
is above or below the chart, returns maximum or minimum values.
"""
return []
def wheelEvent(self, a0: QtGui.QWheelEvent) -> None:
if len(self.data) == 0 and len(self.reference) == 0:
a0.ignore()
return
if a0.angleDelta().y() > 0:
# Zoom in
a0.accept()
# Center of zoom = a0.x(), a0.y()
# We zoom in by 1/10 of the width/height.
rate = a0.angleDelta().y() / 120
zoomx = rate * self.chartWidth / 10
zoomy = rate * self.chartHeight / 10
absx = max(0, a0.x() - self.leftMargin)
absy = max(0, a0.y() - self.topMargin)
ratiox = absx/self.chartWidth
ratioy = absy/self.chartHeight
p1x = int(self.leftMargin + ratiox * zoomx)
p1y = int(self.topMargin + ratioy * zoomy)
p2x = int(self.leftMargin + self.chartWidth - (1 - ratiox) * zoomx)
p2y = int(self.topMargin + self.chartHeight - (1 - ratioy) * zoomy)
self.zoomTo(p1x, p1y, p2x, p2y)
elif a0.angleDelta().y() < 0:
# Zoom out
a0.accept()
# Center of zoom = a0.x(), a0.y()
# We zoom out by 1/9 of the width/height, to match zoom in.
rate = -a0.angleDelta().y() / 120
zoomx = rate * self.chartWidth / 9
zoomy = rate * self.chartHeight / 9
absx = max(0, a0.x() - self.leftMargin)
absy = max(0, a0.y() - self.topMargin)
ratiox = absx/self.chartWidth
ratioy = absy/self.chartHeight
p1x = int(self.leftMargin - ratiox * zoomx)
p1y = int(self.topMargin - ratioy * zoomy)
p2x = int(self.leftMargin + self.chartWidth + (1 - ratiox) * zoomx)
p2y = int(self.topMargin + self.chartHeight + (1 - ratioy) * zoomy)
self.zoomTo(p1x, p1y, p2x, p2y)
else:
a0.ignore()
def zoomTo(self, x1, y1, x2, y2):
val1 = self.valueAtPosition(y1)
val2 = self.valueAtPosition(y2)
if len(val1) == len(val2) == 1 and val1[0] != val2[0]:
self.minDisplayValue = round(min(val1[0], val2[0]), 3)
self.maxDisplayValue = round(max(val1[0], val2[0]), 3)
self.setFixedValues(True)
freq1 = max(1, self.frequencyAtPosition(x1, limit=False))
freq2 = max(1, self.frequencyAtPosition(x2, limit=False))
if freq1 > 0 and freq2 > 0 and freq1 != freq2:
self.minFrequency = min(freq1, freq2)
self.maxFrequency = max(freq1, freq2)
self.setFixedSpan(True)
self.update()
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
if a0.buttons() == QtCore.Qt.RightButton:
a0.ignore()
return
if a0.buttons() == QtCore.Qt.MiddleButton:
# Drag the display
a0.accept()
if self.moveStartX != -1 and self.moveStartY != -1:
dx = self.moveStartX - a0.x()
dy = self.moveStartY - a0.y()
self.zoomTo(self.leftMargin + dx, self.topMargin + dy,
self.leftMargin + self.chartWidth + dx, self.topMargin + self.chartHeight + dy)
self.moveStartX = a0.x()
self.moveStartY = a0.y()
return
if a0.modifiers() == QtCore.Qt.ControlModifier:
# Dragging a box
if not self.draggedBox:
self.draggedBoxStart = (a0.x(), a0.y())
self.draggedBoxCurrent = (a0.x(), a0.y())
self.update()
a0.accept()
return
x = a0.x()
f = self.frequencyAtPosition(x)
if x == -1:
a0.ignore()
return
else:
a0.accept()
m = self.getActiveMarker()
if m is not None:
m.setFrequency(str(f))
m.frequencyInput.setText(str(f))
return
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
self.chartWidth = a0.size().width()-self.rightMargin-self.leftMargin
self.chartHeight = a0.size().height() - self.bottomMargin - self.topMargin
self.update()
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
qp = QtGui.QPainter(self)
self.drawChart(qp)
self.drawValues(qp)
if len(self.data) > 0\
and (self.data[0].freq > self.fstop or self.data[len(self.data)-1].freq < self.fstart) \
and (len(self.reference) == 0 or self.reference[0].freq > self.fstop or self.reference[len(self.reference)-1].freq < self.fstart):
# Data outside frequency range
qp.setBackgroundMode(QtCore.Qt.OpaqueMode)
qp.setBackground(self.backgroundColor)
qp.setPen(self.textColor)
qp.drawText(self.leftMargin + self.chartWidth/2 - 70, self.topMargin + self.chartHeight/2 - 20,
"Data outside frequency span")
if self.draggedBox and self.draggedBoxCurrent[0] != -1:
dashed_pen = QtGui.QPen(self.foregroundColor, 1, QtCore.Qt.DashLine)
qp.setPen(dashed_pen)
top_left = QtCore.QPoint(self.draggedBoxStart[0], self.draggedBoxStart[1])
bottom_right = QtCore.QPoint(self.draggedBoxCurrent[0], self.draggedBoxCurrent[1])
rect = QtCore.QRect(top_left, bottom_right)
qp.drawRect(rect)
qp.end()
def drawFrequencyTicks(self, qp):
fspan = self.fstop - self.fstart
qp.setPen(self.textColor)
qp.drawText(self.leftMargin - 20, self.topMargin + 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)
if self.logarithmicX:
fspan = math.log(self.fstop) - math.log(self.fstart)
freq = round(math.exp(((i + 1) * fspan / ticks) + math.log(self.fstart)))
else:
freq = round(fspan / ticks * (i + 1) + self.fstart)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(x, self.topMargin, x, self.topMargin + self.chartHeight + 5)
qp.setPen(self.textColor)
qp.drawText(x - 20, self.topMargin + self.chartHeight + 15, Chart.shortenFrequency(freq))
def drawBands(self, qp, fstart, fstop):
qp.setBrush(self.bands.color)
qp.setPen(QtGui.QColor(128, 128, 128, 0)) # Don't outline the bands
for (name, start, end) in self.bands.bands:
if fstart < start < fstop and fstart < end < fstop:
# The band is entirely within the chart
x_start = self.getXPosition(Datapoint(start, 0, 0))
x_end = self.getXPosition(Datapoint(end, 0, 0))
qp.drawRect(x_start, self.topMargin, x_end - x_start, self.chartHeight)
elif fstart < start < fstop:
# Only the start of the band is within the chart
x_start = self.getXPosition(Datapoint(start, 0, 0))
qp.drawRect(x_start, self.topMargin, self.leftMargin + self.chartWidth - x_start, self.chartHeight)
elif fstart < end < fstop:
# Only the end of the band is within the chart
x_end = self.getXPosition(Datapoint(end, 0, 0))
qp.drawRect(self.leftMargin + 1, self.topMargin, x_end - (self.leftMargin + 1), self.chartHeight)
elif start < fstart < fstop < end:
# All the chart is in a band, we won't show it(?)
pass
def drawData(self, qp: QtGui.QPainter, data: List[Datapoint], color: QtGui.QColor, y_function=None):
if y_function is None:
y_function = self.getYPosition
pen = QtGui.QPen(color)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(color)
line_pen.setWidth(self.lineThickness)
qp.setPen(pen)
for i in range(len(data)):
x = self.getXPosition(data[i])
y = y_function(data[i])
if self.isPlotable(x, y):
qp.drawPoint(int(x), int(y))
if self.drawLines and i > 0:
prevx = self.getXPosition(data[i - 1])
prevy = y_function(data[i - 1])
qp.setPen(line_pen)
if self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
qp.drawLine(x, y, prevx, prevy)
elif self.isPlotable(x, y) and not self.isPlotable(prevx, prevy):
new_x, new_y = self.getPlotable(x, y, prevx, prevy)
qp.drawLine(x, y, new_x, new_y)
elif not self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
new_x, new_y = self.getPlotable(prevx, prevy, x, y)
qp.drawLine(prevx, prevy, new_x, new_y)
qp.setPen(pen)
def drawMarkers(self, qp, data=None, y_function=None):
if data is None:
data = self.data
if y_function is None:
y_function = self.getYPosition
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
for m in self.markers:
if m.location != -1 and m.location < len(data):
x = self.getXPosition(data[m.location])
y = y_function(data[m.location])
if self.isPlotable(x, y):
self.drawMarker(x, y, qp, m.color, self.markers.index(m)+1)
def isPlotable(self, x, y):
return self.leftMargin <= x <= self.leftMargin + self.chartWidth and \
self.topMargin <= y <= self.topMargin + self.chartHeight
def getPlotable(self, x, y, distantx, distanty):
p1 = np.array([x, y])
p2 = np.array([distantx, distanty])
# First check the top line
if distanty < self.topMargin:
p3 = np.array([self.leftMargin, self.topMargin])
p4 = np.array([self.leftMargin + self.chartWidth, self.topMargin])
elif distanty > self.topMargin + self.chartHeight:
p3 = np.array([self.leftMargin, self.topMargin + self.chartHeight])
p4 = np.array([self.leftMargin + self.chartWidth, self.topMargin + self.chartHeight])
else:
return x, y
da = p2 - p1
db = p4 - p3
dp = p1 - p3
dap = np.array([-da[1], da[0]])
denom = np.dot(dap, db)
if denom != 0:
num = np.dot(dap, dp)
result = (num / denom.astype(float)) * db + p3
return result[0], result[1]
else:
return x, y
def copy(self):
new_chart: FrequencyChart = super().copy()
new_chart.fstart = self.fstart
new_chart.fstop = self.fstop
new_chart.maxFrequency = self.maxFrequency
new_chart.minFrequency = self.minFrequency
new_chart.minDisplayValue = self.minDisplayValue
new_chart.maxDisplayValue = self.maxDisplayValue
new_chart.pointSize = self.pointSize
new_chart.lineThickness = self.lineThickness
new_chart.setFixedSpan(self.fixedSpan)
new_chart.action_automatic.setChecked(not self.fixedSpan)
new_chart.action_fixed_span.setChecked(self.fixedSpan)
new_chart.setFixedValues(self.fixedValues)
new_chart.y_action_automatic.setChecked(not self.fixedValues)
new_chart.y_action_fixed_span.setChecked(self.fixedValues)
new_chart.setLogarithmicX(self.logarithmicX)
new_chart.action_set_logarithmic_x.setChecked(self.logarithmicX)
new_chart.action_set_linear_x.setChecked(not self.logarithmicX)
return new_chart
def keyPressEvent(self, a0: QtGui.QKeyEvent) -> None:
m = self.getActiveMarker()
if m is not None and a0.modifiers() == QtCore.Qt.NoModifier:
if a0.key() == QtCore.Qt.Key_Down or a0.key() == QtCore.Qt.Key_Left:
m.frequencyInput.keyPressEvent(QtGui.QKeyEvent(a0.type(), QtCore.Qt.Key_Down, a0.modifiers()))
elif a0.key() == QtCore.Qt.Key_Up or a0.key() == QtCore.Qt.Key_Right:
m.frequencyInput.keyPressEvent(QtGui.QKeyEvent(a0.type(), QtCore.Qt.Key_Up, a0.modifiers()))
else:
super().keyPressEvent(a0)
class SquareChart(Chart):
def __init__(self, name):
super().__init__(name)
sizepolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.MinimumExpanding)
self.setSizePolicy(sizepolicy)
self.chartWidth = self.width()-40
self.chartHeight = self.height()-40
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
if not self.isPopout:
self.setFixedWidth(a0.size().height())
self.chartWidth = a0.size().height()-40
self.chartHeight = a0.size().height()-40
else:
min_dimension = min(a0.size().height(), a0.size().width())
self.chartWidth = self.chartHeight = min_dimension - 40
self.update()
class PhaseChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 40
self.chartWidth = 250
self.chartHeight = 250
self.fstart = 0
self.fstop = 0
self.minAngle = 0
self.maxAngle = 0
self.span = 0
self.unwrap = False
self.unwrappedData = []
self.unwrappedReference = []
self.minDisplayValue = -180
self.maxDisplayValue = 180
self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.bottomMargin)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
self.y_menu.addSeparator()
self.action_unwrap = QtWidgets.QAction("Unwrap")
self.action_unwrap.setCheckable(True)
self.action_unwrap.triggered.connect(lambda: self.setUnwrap(self.action_unwrap.isChecked()))
self.y_menu.addAction(self.action_unwrap)
def copy(self):
new_chart: PhaseChart = super().copy()
new_chart.setUnwrap(self.unwrap)
new_chart.action_unwrap.setChecked(self.unwrap)
return new_chart
def setUnwrap(self, unwrap: bool):
self.unwrap = unwrap
self.update()
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
if self.unwrap:
rawData = []
for d in self.data:
rawData.append(d.phase)
rawReference = []
for d in self.reference:
rawReference.append(d.phase)
self.unwrappedData = np.degrees(np.unwrap(rawData))
self.unwrappedReference = np.degrees(np.unwrap(rawReference))
if self.fixedValues:
minAngle = self.minDisplayValue
maxAngle = self.maxDisplayValue
elif self.unwrap and self.data:
minAngle = math.floor(np.min(self.unwrappedData))
maxAngle = math.ceil(np.max(self.unwrappedData))
elif self.unwrap and self.reference:
minAngle = math.floor(np.min(self.unwrappedReference))
maxAngle = math.ceil(np.max(self.unwrappedReference))
else:
minAngle = -180
maxAngle = 180
span = maxAngle - minAngle
if span == 0:
span = 0.01
self.minAngle = minAngle
self.maxAngle = maxAngle
self.span = span
tickcount = math.floor(self.chartHeight / 60)
for i in range(tickcount):
angle = minAngle + span * i / tickcount
y = self.topMargin + round((self.maxAngle - angle) / self.span * self.chartHeight)
if angle != minAngle and angle != maxAngle:
qp.setPen(QtGui.QPen(self.textColor))
if angle != 0:
digits = max(0, min(2, math.floor(3 - math.log10(abs(angle)))))
if digits == 0:
anglestr = str(round(angle))
else:
anglestr = str(round(angle, digits))
else:
anglestr = "0"
qp.drawText(3, y + 3, anglestr + "°")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y)
qp.drawLine(self.leftMargin - 5, self.topMargin, self.leftMargin + self.chartWidth, self.topMargin)
qp.setPen(self.textColor)
qp.drawText(3, self.topMargin + 5, str(maxAngle) + "°")
qp.drawText(3, self.chartHeight + self.topMargin, str(minAngle) + "°")
if self.fixedSpan:
fstart = self.minFrequency
fstop = self.maxFrequency
else:
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 = self.fstop-self.fstart
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
self.drawFrequencyTicks(qp)
self.drawData(qp, self.data, self.sweepColor)
self.drawData(qp, self.reference, self.referenceColor)
self.drawMarkers(qp)
def getYPosition(self, d: Datapoint) -> int:
if self.unwrap:
if d in self.data:
angle = self.unwrappedData[self.data.index(d)]
elif d in self.reference:
angle = self.unwrappedReference[self.reference.index(d)]
else:
angle = math.degrees(d.phase)
else:
angle = math.degrees(d.phase)
return self.topMargin + round((self.maxAngle - angle) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
val = -1 * ((absy / self.chartHeight * self.span) - self.maxAngle)
return [val]
class VSWRChart(FrequencyChart):
logarithmicY = False
maxVSWR = 3
span = 2
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 30
self.chartWidth = 250
self.chartHeight = 250
self.fstart = 0
self.fstop = 0
self.maxDisplayValue = 25
self.minDisplayValue = 1
self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin,
self.chartHeight + self.topMargin + self.bottomMargin)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
self.y_menu.addSeparator()
self.y_log_lin_group = QtWidgets.QActionGroup(self.y_menu)
self.y_action_linear = QtWidgets.QAction("Linear")
self.y_action_linear.setCheckable(True)
self.y_action_linear.setChecked(True)
self.y_action_logarithmic = QtWidgets.QAction("Logarithmic")
self.y_action_logarithmic.setCheckable(True)
self.y_action_linear.triggered.connect(lambda: self.setLogarithmicY(False))
self.y_action_logarithmic.triggered.connect(lambda: self.setLogarithmicY(True))
self.y_log_lin_group.addAction(self.y_action_linear)
self.y_log_lin_group.addAction(self.y_action_logarithmic)
self.y_menu.addAction(self.y_action_linear)
self.y_menu.addAction(self.y_action_logarithmic)
def setLogarithmicY(self, logarithmic: bool):
self.logarithmicY = logarithmic
self.update()
def copy(self):
new_chart: VSWRChart = super().copy()
new_chart.logarithmicY = self.logarithmicY
return new_chart
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, self.topMargin - 5,
self.leftMargin, self.topMargin + self.chartHeight + 5)
qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
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:
fstart = self.reference[0].freq
fstop = self.reference[len(self.reference) - 1].freq
self.fstart = fstart
self.fstop = fstop
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
# Find scaling
if self.fixedValues:
minVSWR = max(1, self.minDisplayValue)
maxVSWR = self.maxDisplayValue
else:
minVSWR = 1
maxVSWR = 3
for d in self.data:
vswr = d.vswr
if vswr > maxVSWR:
maxVSWR = vswr
maxVSWR = min(self.maxDisplayValue, math.ceil(maxVSWR))
self.maxVSWR = maxVSWR
span = maxVSWR-minVSWR
if span == 0:
span = 0.01
self.span = span
target_ticks = math.floor(self.chartHeight / 60)
if self.logarithmicY:
for i in range(target_ticks):
y = int(self.topMargin + (i / target_ticks) * self.chartHeight)
vswr = self.valueAtPosition(y)[0]
qp.setPen(self.textColor)
if vswr != 0:
digits = max(0, min(2, math.floor(3 - math.log10(abs(vswr)))))
if digits == 0:
vswrstr = str(round(vswr))
else:
vswrstr = str(round(vswr, digits))
qp.drawText(3, y+3, vswrstr)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y)
qp.drawLine(self.leftMargin - 5, self.topMargin + self.chartHeight,
self.leftMargin + self.chartWidth, self.topMargin + self.chartHeight)
qp.setPen(self.textColor)
digits = max(0, min(2, math.floor(3 - math.log10(abs(minVSWR)))))
if digits == 0:
vswrstr = str(round(minVSWR))
else:
vswrstr = str(round(minVSWR, digits))
qp.drawText(3, self.topMargin + self.chartHeight, vswrstr)
else:
for i in range(target_ticks):
vswr = minVSWR + i * self.span/target_ticks
y = self.getYPositionFromValue(vswr)
qp.setPen(self.textColor)
if vswr != 0:
digits = max(0, min(2, math.floor(3 - math.log10(abs(vswr)))))
if digits == 0:
vswrstr = str(round(vswr))
else:
vswrstr = str(round(vswr, digits))
qp.drawText(3, y+3, vswrstr)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y)
qp.drawLine(self.leftMargin - 5, self.topMargin, self.leftMargin + self.chartWidth, self.topMargin)
qp.setPen(self.textColor)
digits = max(0, min(2, math.floor(3 - math.log10(abs(maxVSWR)))))
if digits == 0:
vswrstr = str(round(maxVSWR))
else:
vswrstr = str(round(maxVSWR, digits))
qp.drawText(3, 35, vswrstr)
self.drawFrequencyTicks(qp)
qp.setPen(self.swrColor)
for vswr in self.swrMarkers:
y = self.getYPositionFromValue(vswr)
qp.drawLine(self.leftMargin, y, self.leftMargin + self.chartWidth, y)
qp.drawText(self.leftMargin + 3, y - 1, str(vswr))
self.drawData(qp, self.data, self.sweepColor)
self.drawData(qp, self.reference, self.referenceColor)
self.drawMarkers(qp)
def getYPositionFromValue(self, vswr) -> int:
if self.logarithmicY:
min_val = self.maxVSWR - self.span
if self.maxVSWR > 0 and min_val > 0 and vswr > 0:
span = math.log(self.maxVSWR) - math.log(min_val)
else:
return -1
return self.topMargin + round((math.log(self.maxVSWR) - math.log(vswr)) / span * self.chartHeight)
else:
return self.topMargin + round((self.maxVSWR - vswr) / self.span * self.chartHeight)
def getYPosition(self, d: Datapoint) -> int:
return self.getYPositionFromValue(d.vswr)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
if self.logarithmicY:
min_val = self.maxVSWR - self.span
if self.maxVSWR > 0 and min_val > 0:
span = math.log(self.maxVSWR) - math.log(min_val)
step = span / self.chartHeight
val = math.exp(math.log(self.maxVSWR) - absy * step)
else:
val = -1
else:
val = -1 * ((absy / self.chartHeight * self.span) - self.maxVSWR)
return [val]
def resetDisplayLimits(self):
self.maxDisplayValue = 25
self.logarithmicY = False
super().resetDisplayLimits()
class PolarChart(SquareChart):
def __init__(self, name=""):
super().__init__(name)
self.chartWidth = 250
self.chartHeight = 250
self.setMinimumSize(self.chartWidth + 40, self.chartHeight + 40)
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
qp = QtGui.QPainter(self)
self.drawChart(qp)
self.drawValues(qp)
qp.end()
def drawChart(self, qp: QtGui.QPainter):
centerX = int(self.width()/2)
centerY = int(self.height()/2)
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawEllipse(QtCore.QPoint(centerX, centerY), int(self.chartWidth/2), int(self.chartHeight/2))
qp.drawEllipse(QtCore.QPoint(centerX, centerY), int(self.chartWidth/4), int(self.chartHeight/4))
qp.drawLine(centerX - int(self.chartWidth/2), centerY, centerX + int(self.chartWidth/2), centerY)
qp.drawLine(centerX, centerY - int(self.chartHeight/2), centerX, centerY + int(self.chartHeight/2))
qp.drawLine(centerX + int(self.chartHeight / 2 * math.sin(math.pi / 4)),
centerY + int(self.chartHeight / 2 * math.sin(math.pi / 4)),
centerX - int(self.chartHeight / 2 * math.sin(math.pi / 4)),
centerY - int(self.chartHeight / 2 * math.sin(math.pi / 4)))
qp.drawLine(centerX + int(self.chartHeight / 2 * math.sin(math.pi / 4)),
centerY - int(self.chartHeight / 2 * math.sin(math.pi / 4)),
centerX - int(self.chartHeight / 2 * math.sin(math.pi / 4)),
centerY + int(self.chartHeight / 2 * math.sin(math.pi / 4)))
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
qp.setPen(pen)
for i in range(len(self.data)):
x = self.getXPosition(self.data[i])
y = self.height()/2 + self.data[i].im * -1 * self.chartHeight/2
qp.drawPoint(int(x), int(y))
if self.drawLines and i > 0:
prevx = self.getXPosition(self.data[i-1])
prevy = self.height() / 2 + self.data[i-1].im * -1 * self.chartHeight / 2
qp.setPen(line_pen)
qp.drawLine(x, y, prevx, prevy)
qp.setPen(pen)
pen.setColor(self.referenceColor)
line_pen.setColor(self.referenceColor)
qp.setPen(pen)
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
for i in range(len(self.reference)):
data = self.reference[i]
if data.freq < fstart or data.freq > fstop:
continue
x = self.getXPosition(self.reference[i])
y = self.height()/2 + data.im * -1 * self.chartHeight/2
qp.drawPoint(int(x), int(y))
if self.drawLines and i > 0:
prevx = self.getXPosition(self.reference[i-1])
prevy = self.height() / 2 + self.reference[i-1].im * -1 * self.chartHeight / 2
qp.setPen(line_pen)
qp.drawLine(x, y, prevx, prevy)
qp.setPen(pen)
# Now draw the markers
for m in self.markers:
if m.location != -1 and m.location < len(self.data):
x = self.getXPosition(self.data[m.location])
y = self.height() / 2 + self.data[m.location].im * -1 * self.chartHeight / 2
self.drawMarker(x, y, qp, m.color, self.markers.index(m)+1)
def getXPosition(self, d: Datapoint) -> int:
return self.width()/2 + d.re * self.chartWidth/2
def getYPosition(self, d: Datapoint) -> int:
return self.height()/2 + d.im * -1 * self.chartHeight/2
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
if a0.buttons() == QtCore.Qt.RightButton:
a0.ignore()
return
x = a0.x()
y = a0.y()
absx = x - (self.width() - self.chartWidth) / 2
absy = y - (self.height() - self.chartHeight) / 2
if absx < 0 or absx > self.chartWidth or absy < 0 or absy > self.chartHeight \
or len(self.data) == len(self.reference) == 0:
a0.ignore()
return
a0.accept()
if len(self.data) > 0:
target = self.data
else:
target = self.reference
positions = []
for d in target:
thisx = self.width() / 2 + d.re * self.chartWidth / 2
thisy = self.height() / 2 + d.im * -1 * self.chartHeight / 2
positions.append(math.sqrt((x - thisx)**2 + (y - thisy)**2))
minimum_position = positions.index(min(positions))
m = self.getActiveMarker()
if m is not None:
m.setFrequency(str(round(target[minimum_position].freq)))
m.frequencyInput.setText(str(round(target[minimum_position].freq)))
return
class SmithChart(SquareChart):
def __init__(self, name=""):
super().__init__(name)
self.chartWidth = 250
self.chartHeight = 250
self.setMinimumSize(self.chartWidth + 40, self.chartHeight + 40)
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
qp = QtGui.QPainter(self)
#qp.begin(self) # Apparently not needed?
self.drawSmithChart(qp)
self.drawValues(qp)
qp.end()
def drawSmithChart(self, qp: QtGui.QPainter):
centerX = int(self.width()/2)
centerY = int(self.height()/2)
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawEllipse(QtCore.QPoint(centerX, centerY), int(self.chartWidth/2), int(self.chartHeight/2))
qp.drawLine(centerX - int(self.chartWidth/2), centerY, centerX + int(self.chartWidth/2), centerY)
qp.drawEllipse(QtCore.QPoint(centerX + int(self.chartWidth/4), centerY), int(self.chartWidth/4), int(self.chartHeight/4)) # Re(Z) = 1
qp.drawEllipse(QtCore.QPoint(centerX + int(2/3*self.chartWidth/2), centerY), int(self.chartWidth/6), int(self.chartHeight/6)) # Re(Z) = 2
qp.drawEllipse(QtCore.QPoint(centerX + int(3 / 4 * self.chartWidth / 2), centerY), int(self.chartWidth / 8), int(self.chartHeight / 8)) # Re(Z) = 3
qp.drawEllipse(QtCore.QPoint(centerX + int(5 / 6 * self.chartWidth / 2), centerY), int(self.chartWidth / 12), int(self.chartHeight / 12)) # Re(Z) = 5
qp.drawEllipse(QtCore.QPoint(centerX + int(1 / 3 * self.chartWidth / 2), centerY), int(self.chartWidth / 3), int(self.chartHeight / 3)) # Re(Z) = 0.5
qp.drawEllipse(QtCore.QPoint(centerX + int(1 / 6 * self.chartWidth / 2), centerY), int(self.chartWidth / 2.4), int(self.chartHeight / 2.4)) # Re(Z) = 0.2
qp.drawArc(centerX + int(3/8*self.chartWidth), centerY, int(self.chartWidth/4), int(self.chartWidth/4), 90*16, 152*16) # Im(Z) = -5
qp.drawArc(centerX + int(3/8*self.chartWidth), centerY, int(self.chartWidth/4), -int(self.chartWidth/4), -90 * 16, -152 * 16) # Im(Z) = 5
qp.drawArc(centerX + int(self.chartWidth/4), centerY, int(self.chartWidth/2), int(self.chartHeight/2), 90*16, 127*16) # Im(Z) = -2
qp.drawArc(centerX + int(self.chartWidth/4), centerY, int(self.chartWidth/2), -int(self.chartHeight/2), -90*16, -127*16) # Im(Z) = 2
qp.drawArc(centerX, centerY, self.chartWidth, self.chartHeight, 90*16, 90*16) # Im(Z) = -1
qp.drawArc(centerX, centerY, self.chartWidth, -self.chartHeight, -90 * 16, -90 * 16) # Im(Z) = 1
qp.drawArc(centerX - int(self.chartWidth/2), centerY, self.chartWidth*2, self.chartHeight*2, int(99.5*16), int(43.5*16)) # Im(Z) = -0.5
qp.drawArc(centerX - int(self.chartWidth/2), centerY, self.chartWidth*2, -self.chartHeight*2, int(-99.5 * 16), int(-43.5 * 16)) # Im(Z) = 0.5
qp.drawArc(centerX - self.chartWidth*2, centerY, self.chartWidth*5, self.chartHeight*5, int(93.85*16), int(18.85*16)) # Im(Z) = -0.2
qp.drawArc(centerX - self.chartWidth*2, centerY, self.chartWidth*5, -self.chartHeight*5, int(-93.85 * 16), int(-18.85 * 16)) # Im(Z) = 0.2
qp.setPen(self.swrColor)
for swr in self.swrMarkers:
if swr <= 1:
continue
gamma = (swr - 1)/(swr + 1)
r = round(gamma * self.chartWidth/2)
qp.drawEllipse(QtCore.QPoint(centerX, centerY), r, r)
qp.drawText(QtCore.QRect(centerX - 50, centerY - 4 + r, 100, 20), QtCore.Qt.AlignCenter, str(swr))
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
qp.setPen(pen)
for i in range(len(self.data)):
x = self.getXPosition(self.data[i])
y = self.height()/2 + self.data[i].im * -1 * self.chartHeight/2
qp.drawPoint(int(x), int(y))
if self.drawLines and i > 0:
prevx = self.getXPosition(self.data[i-1])
prevy = self.height() / 2 + self.data[i-1].im * -1 * self.chartHeight / 2
qp.setPen(line_pen)
qp.drawLine(x, y, prevx, prevy)
qp.setPen(pen)
pen.setColor(self.referenceColor)
line_pen.setColor(self.referenceColor)
qp.setPen(pen)
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
for i in range(len(self.reference)):
data = self.reference[i]
if data.freq < fstart or data.freq > fstop:
continue
x = self.getXPosition(data)
y = self.height()/2 + data.im * -1 * self.chartHeight/2
qp.drawPoint(int(x), int(y))
if self.drawLines and i > 0:
prevx = self.getXPosition(self.reference[i-1])
prevy = self.height() / 2 + self.reference[i-1].im * -1 * self.chartHeight / 2
qp.setPen(line_pen)
qp.drawLine(x, y, prevx, prevy)
qp.setPen(pen)
# Now draw the markers
for m in self.markers:
if m.location != -1:
x = self.getXPosition(self.data[m.location])
y = self.height() / 2 + self.data[m.location].im * -1 * self.chartHeight / 2
self.drawMarker(x, y, qp, m.color, self.markers.index(m)+1)
def getXPosition(self, d: Datapoint) -> int:
return self.width()/2 + d.re * self.chartWidth/2
def getYPosition(self, d: Datapoint) -> int:
return self.height()/2 + d.im * -1 * self.chartHeight/2
def heightForWidth(self, a0: int) -> int:
return a0
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
if a0.buttons() == QtCore.Qt.RightButton:
a0.ignore()
return
x = a0.x()
y = a0.y()
absx = x - (self.width() - self.chartWidth) / 2
absy = y - (self.height() - self.chartHeight) / 2
if absx < 0 or absx > self.chartWidth or absy < 0 or absy > self.chartHeight \
or len(self.data) == len(self.reference) == 0:
a0.ignore()
return
a0.accept()
if len(self.data) > 0:
target = self.data
else:
target = self.reference
positions = []
for d in target:
thisx = self.width() / 2 + d.re * self.chartWidth / 2
thisy = self.height() / 2 + d.im * -1 * self.chartHeight / 2
positions.append(math.sqrt((x - thisx)**2 + (y - thisy)**2))
minimum_position = positions.index(min(positions))
m = self.getActiveMarker()
if m is not None:
m.setFrequency(str(round(target[minimum_position].freq)))
m.frequencyInput.setText(str(round(target[minimum_position].freq)))
return
class LogMagChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 30
self.chartWidth = 250
self.chartHeight = 250
self.minDisplayValue = -80
self.maxDisplayValue = 10
self.minValue = 0
self.maxValue = 1
self.span = 1
self.isInverted = False
self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.bottomMargin)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name + " (dB)")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
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.fstart = self.minFrequency
fstop = self.fstop = self.maxFrequency
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
if self.fixedValues:
maxValue = self.maxDisplayValue
minValue = self.minDisplayValue
self.maxValue = maxValue
self.minValue = minValue
else:
# Find scaling
minValue = 100
maxValue = -100
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
span = maxValue-minValue
if span == 0:
span = 0.01
self.span = span
if self.span >= 50:
# Ticks per 10dB step
tick_count = math.floor(self.span/10)
first_tick = math.ceil(self.minValue/10) * 10
tick_step = 10
if first_tick == minValue:
first_tick += 10
elif self.span >= 20:
# 5 dB ticks
tick_count = math.floor(self.span/5)
first_tick = math.ceil(self.minValue/5) * 5
tick_step = 5
if first_tick == minValue:
first_tick += 5
elif self.span >= 10:
# 2 dB ticks
tick_count = math.floor(self.span/2)
first_tick = math.ceil(self.minValue/2) * 2
tick_step = 2
if first_tick == minValue:
first_tick += 2
elif self.span >= 5:
# 1dB ticks
tick_count = math.floor(self.span)
first_tick = math.ceil(minValue)
tick_step = 1
if first_tick == minValue:
first_tick += 1
elif self.span >= 2:
# .5 dB ticks
tick_count = math.floor(self.span*2)
first_tick = math.ceil(minValue*2) / 2
tick_step = .5
if first_tick == minValue:
first_tick += .5
else:
# .1 dB ticks
tick_count = math.floor(self.span*10)
first_tick = math.ceil(minValue*10) / 10
tick_step = .1
if first_tick == minValue:
first_tick += .1
for i in range(tick_count):
db = first_tick + i * tick_step
y = self.topMargin + round((maxValue - db)/span*self.chartHeight)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y)
if db > minValue and db != maxValue:
qp.setPen(QtGui.QPen(self.textColor))
if tick_step < 1:
dbstr = str(round(db, 1))
else:
dbstr = str(db)
qp.drawText(3, y + 4, dbstr)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, self.topMargin,
self.leftMargin + self.chartWidth, self.topMargin)
qp.setPen(self.textColor)
qp.drawText(3, self.topMargin + 4, str(maxValue))
qp.drawText(3, self.chartHeight+self.topMargin, str(minValue))
self.drawFrequencyTicks(qp)
qp.setPen(self.swrColor)
for vswr in self.swrMarkers:
if vswr <= 1:
continue
logMag = 20 * math.log10((vswr-1)/(vswr+1))
if self.isInverted:
logMag = logMag * -1
y = self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight)
qp.drawLine(self.leftMargin, y, self.leftMargin + self.chartWidth, y)
qp.drawText(self.leftMargin + 3, y - 1, "VSWR: " + str(vswr))
self.drawData(qp, self.data, self.sweepColor)
self.drawData(qp, self.reference, self.referenceColor)
self.drawMarkers(qp)
def getYPosition(self, d: Datapoint) -> int:
logMag = self.logMag(d)
return self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue)
return [val]
def logMag(self, p: Datapoint) -> float:
if self.isInverted:
return -p.gain
else:
return p.gain
def copy(self):
new_chart: LogMagChart = super().copy()
new_chart.isInverted = self.isInverted
new_chart.span = self.span
return new_chart
class SParameterChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 30
self.chartWidth = 250
self.chartHeight = 250
self.minDisplayValue = -1
self.maxDisplayValue = 1
self.fixedValues = True
self.y_action_automatic.setChecked(False)
self.y_action_fixed_span.setChecked(True)
self.minValue = 0
self.maxValue = 1
self.span = 1
self.isInverted = False
self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.bottomMargin)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(int(round(self.chartWidth / 2)) - 20, 15, self.name + "")
qp.drawText(10, 15, "Real")
qp.drawText(self.leftMargin + self.chartWidth - 15, 15, "Imag")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
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.fstart = self.minFrequency
fstop = self.fstop = self.maxFrequency
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
if self.fixedValues:
maxValue = self.maxDisplayValue
minValue = self.minDisplayValue
self.maxValue = maxValue
self.minValue = minValue
else:
# Find scaling
minValue = -1
maxValue = 1
self.maxValue = maxValue
self.minValue = minValue
# for d in self.data:
# val = d.re
# if val > maxValue:
# maxValue = val
# if val < minValue:
# minValue = val
# 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
span = maxValue-minValue
if span == 0:
span = 0.01
self.span = span
tick_count = math.floor(self.chartHeight / 60)
tick_step = self.span / tick_count
for i in range(tick_count):
val = minValue + i * tick_step
y = self.topMargin + round((maxValue - val)/span*self.chartHeight)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y)
if val > minValue and val != maxValue:
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, y + 4, str(round(val, 2)))
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, self.topMargin,
self.leftMargin + self.chartWidth, self.topMargin)
qp.setPen(self.textColor)
qp.drawText(3, self.topMargin + 4, str(maxValue))
qp.drawText(3, self.chartHeight+self.topMargin, str(minValue))
self.drawFrequencyTicks(qp)
self.drawData(qp, self.data, self.sweepColor, self.getReYPosition)
self.drawData(qp, self.reference, self.referenceColor, self.getReYPosition)
self.drawData(qp, self.data, self.secondarySweepColor, self.getImYPosition)
self.drawData(qp, self.reference, self.secondaryReferenceColor, self.getImYPosition)
self.drawMarkers(qp, y_function=self.getReYPosition)
self.drawMarkers(qp, y_function=self.getImYPosition)
def getYPosition(self, d: Datapoint) -> int:
return self.topMargin + round((self.maxValue - d.re) / self.span * self.chartHeight)
def getReYPosition(self, d: Datapoint) -> int:
return self.topMargin + round((self.maxValue - d.re) / self.span * self.chartHeight)
def getImYPosition(self, d: Datapoint) -> int:
return self.topMargin + round((self.maxValue - d.im) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue)
return [val]
def logMag(self, p: Datapoint) -> float:
if self.isInverted:
return -p.gain
else:
return p.gain
def copy(self):
new_chart: LogMagChart = super().copy()
new_chart.isInverted = self.isInverted
new_chart.span = self.span
return new_chart
class CombinedLogMagChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 30
self.chartWidth = 250
self.chartHeight = 250
self.minDisplayValue = -80
self.maxDisplayValue = 10
self.data11: List[Datapoint] = []
self.data21: List[Datapoint] = []
self.reference11: List[Datapoint] = []
self.reference21: List[Datapoint] = []
self.minValue = 0
self.maxValue = 1
self.span = 1
self.isInverted = False
self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.bottomMargin)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def setCombinedData(self, data11, data21):
self.data11 = data11
self.data21 = data21
self.update()
def setCombinedReference(self, data11, data21):
self.reference11 = data11
self.reference21 = data21
self.update()
def resetDisplayLimits(self):
self.reference11 = []
self.reference21 = []
self.update()
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(int(round(self.chartWidth / 2)) - 20, 15, self.name + " (dB)")
qp.drawText(10, 15, "S11")
qp.drawText(self.leftMargin + self.chartWidth - 8, 15, "S21")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data11) == 0 and len(self.reference11) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
if not self.fixedSpan:
if len(self.data11) > 0:
fstart = self.data11[0].freq
fstop = self.data11[len(self.data11)-1].freq
else:
fstart = self.reference11[0].freq
fstop = self.reference11[len(self.reference11) - 1].freq
self.fstart = fstart
self.fstop = fstop
else:
fstart = self.fstart = self.minFrequency
fstop = self.fstop = self.maxFrequency
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
if self.fixedValues:
maxValue = self.maxDisplayValue
minValue = self.minDisplayValue
self.maxValue = maxValue
self.minValue = minValue
else:
# Find scaling
minValue = 100
maxValue = 0
for d in self.data11:
logmag = self.logMag(d)
if logmag > maxValue:
maxValue = logmag
if logmag < minValue:
minValue = logmag
for d in self.data21:
logmag = self.logMag(d)
if logmag > maxValue:
maxValue = logmag
if logmag < minValue:
minValue = logmag
for d in self.reference11: # 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
for d in self.reference21: # 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
span = maxValue-minValue
if span == 0:
span = 0.01
self.span = span
if self.span >= 50:
# Ticks per 10dB step
tick_count = math.floor(self.span/10)
first_tick = math.ceil(self.minValue/10) * 10
tick_step = 10
if first_tick == minValue:
first_tick += 10
elif self.span >= 20:
# 5 dB ticks
tick_count = math.floor(self.span/5)
first_tick = math.ceil(self.minValue/5) * 5
tick_step = 5
if first_tick == minValue:
first_tick += 5
elif self.span >= 10:
# 2 dB ticks
tick_count = math.floor(self.span/2)
first_tick = math.ceil(self.minValue/2) * 2
tick_step = 2
if first_tick == minValue:
first_tick += 2
elif self.span >= 5:
# 1dB ticks
tick_count = math.floor(self.span)
first_tick = math.ceil(minValue)
tick_step = 1
if first_tick == minValue:
first_tick += 1
elif self.span >= 2:
# .5 dB ticks
tick_count = math.floor(self.span*2)
first_tick = math.ceil(minValue*2) / 2
tick_step = .5
if first_tick == minValue:
first_tick += .5
else:
# .1 dB ticks
tick_count = math.floor(self.span*10)
first_tick = math.ceil(minValue*10) / 10
tick_step = .1
if first_tick == minValue:
first_tick += .1
for i in range(tick_count):
db = first_tick + i * tick_step
y = self.topMargin + round((maxValue - db)/span*self.chartHeight)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin-5, y, self.leftMargin+self.chartWidth, y)
if db > minValue and db != maxValue:
qp.setPen(QtGui.QPen(self.textColor))
if tick_step < 1:
dbstr = str(round(db, 1))
else:
dbstr = str(db)
qp.drawText(3, y + 4, dbstr)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, self.topMargin,
self.leftMargin + self.chartWidth, self.topMargin)
qp.setPen(self.textColor)
qp.drawText(3, self.topMargin + 4, str(maxValue))
qp.drawText(3, self.chartHeight+self.topMargin, str(minValue))
self.drawFrequencyTicks(qp)
qp.setPen(self.swrColor)
for vswr in self.swrMarkers:
if vswr <= 1:
continue
logMag = 20 * math.log10((vswr-1)/(vswr+1))
if self.isInverted:
logMag = logMag * -1
y = self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight)
qp.drawLine(self.leftMargin, y, self.leftMargin + self.chartWidth, y)
qp.drawText(self.leftMargin + 3, y - 1, "VSWR: " + str(vswr))
if len(self.data11) > 0:
c = QtGui.QColor(self.sweepColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(33, 9, 38, 9)
c = QtGui.QColor(self.secondarySweepColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(self.leftMargin + self.chartWidth - 20, 9, self.leftMargin + self.chartWidth - 15, 9)
if len(self.reference11) > 0:
c = QtGui.QColor(self.referenceColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(33, 14, 38, 14)
c = QtGui.QColor(self.secondaryReferenceColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(self.leftMargin + self.chartWidth - 20, 14, self.leftMargin + self.chartWidth - 15, 14)
self.drawData(qp, self.data11, self.sweepColor)
self.drawData(qp, self.data21, self.secondarySweepColor)
self.drawData(qp, self.reference11, self.referenceColor)
self.drawData(qp, self.reference21, self.secondaryReferenceColor)
self.drawMarkers(qp, data=self.data11)
self.drawMarkers(qp, data=self.data21)
def getYPosition(self, d: Datapoint) -> int:
logMag = self.logMag(d)
return self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue)
return [val]
def logMag(self, p: Datapoint) -> float:
if self.isInverted:
return -p.gain
else:
return p.gain
def copy(self):
new_chart: LogMagChart = super().copy()
new_chart.isInverted = self.isInverted
new_chart.span = self.span
new_chart.data11 = self.data11
new_chart.data21 = self.data21
new_chart.reference11 = self.reference11
new_chart.reference21 = self.reference21
return new_chart
class QualityFactorChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 35
self.chartWidth = 250
self.chartHeight = 250
self.fstart = 0
self.fstop = 0
self.minQ = 0
self.maxQ = 0
self.span = 0
self.minDisplayValue = 0
self.maxDisplayValue = 100
self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin,
self.chartHeight + self.topMargin + self.bottomMargin)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, self.topMargin - 5, self.leftMargin, self.topMargin + self.chartHeight + 5)
qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
# Make up some sensible scaling here
if self.fixedValues:
maxQ = self.maxDisplayValue
minQ = self.minDisplayValue
else:
minQ = 0
maxQ = 0
for d in self.data:
Q = d.qFactor()
if Q > maxQ:
maxQ = Q
scale = 0
if maxQ > 0:
scale = max(scale, math.floor(math.log10(maxQ)))
maxQ = math.ceil(maxQ / 10 ** scale) * 10 ** scale
self.minQ = minQ
self.maxQ = maxQ
self.span = self.maxQ - self.minQ
if self.span == 0:
return # No data to draw the graph from
tickcount = math.floor(self.chartHeight / 60)
for i in range(tickcount):
q = self.minQ + i * self.span / tickcount
y = self.topMargin + round((self.maxQ - q) / self.span * self.chartHeight)
if q < 10:
q = round(q, 2)
elif q < 20:
q = round(q, 1)
else:
q = round(q)
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, y+3, str(q))
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin-5, y, self.leftMargin + self.chartWidth, y)
qp.drawLine(self.leftMargin - 5, self.topMargin, self.leftMargin + self.chartWidth, self.topMargin)
qp.setPen(self.textColor)
if maxQ < 10:
qstr = str(round(maxQ, 2))
elif maxQ < 20:
qstr = str(round(maxQ, 1))
else:
qstr = str(round(maxQ))
qp.drawText(3, 35, qstr)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
if self.span == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
if self.fixedSpan:
fstart = self.minFrequency
fstop = self.maxFrequency
else:
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
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
self.drawFrequencyTicks(qp)
self.drawData(qp, self.data, self.sweepColor)
self.drawData(qp, self.reference, self.referenceColor)
self.drawMarkers(qp)
def getYPosition(self, d: Datapoint) -> int:
Q = d.qFactor()
return self.topMargin + round((self.maxQ - Q) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
val = -1 * ((absy / self.chartHeight * self.span) - self.maxQ)
return [val]
class TDRChart(Chart):
maxDisplayLength = 50
minDisplayLength = 0
fixedSpan = False
minImpedance = 0
maxImpedance = 1000
fixedValues = False
markerLocation = -1
def __init__(self, name):
super().__init__(name)
self.tdrWindow = None
self.leftMargin = 30
self.rightMargin = 20
self.bottomMargin = 25
self.topMargin = 20
self.setMinimumSize(300, 300)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
self.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
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("Length axis")
self.mode_group = QtWidgets.QActionGroup(self.x_menu)
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()))
self.mode_group.addAction(self.action_automatic)
self.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 (" + str(self.minDisplayLength) + ")")
self.action_set_fixed_start.triggered.connect(self.setMinimumLength)
self.action_set_fixed_stop = QtWidgets.QAction("Stop (" + str(self.maxDisplayLength) + ")")
self.action_set_fixed_stop.triggered.connect(self.setMaximumLength)
self.x_menu.addAction(self.action_set_fixed_start)
self.x_menu.addAction(self.action_set_fixed_stop)
self.y_menu = QtWidgets.QMenu("Impedance axis")
self.y_mode_group = QtWidgets.QActionGroup(self.y_menu)
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.isChecked()))
self.y_action_fixed = QtWidgets.QAction("Fixed")
self.y_action_fixed.setCheckable(True)
self.y_action_fixed.changed.connect(lambda: self.setFixedValues(self.y_action_fixed.isChecked()))
self.y_mode_group.addAction(self.y_action_automatic)
self.y_mode_group.addAction(self.y_action_fixed)
self.y_menu.addAction(self.y_action_automatic)
self.y_menu.addAction(self.y_action_fixed)
self.y_menu.addSeparator()
self.y_action_set_fixed_maximum = QtWidgets.QAction("Maximum (" + str(self.maxImpedance) + ")")
self.y_action_set_fixed_maximum.triggered.connect(self.setMaximumImpedance)
self.y_action_set_fixed_minimum = QtWidgets.QAction("Minimum (" + str(self.minImpedance) + ")")
self.y_action_set_fixed_minimum.triggered.connect(self.setMinimumImpedance)
self.y_menu.addAction(self.y_action_set_fixed_maximum)
self.y_menu.addAction(self.y_action_set_fixed_minimum)
self.menu.addMenu(self.x_menu)
self.menu.addMenu(self.y_menu)
self.menu.addSeparator()
self.menu.addAction(self.action_save_screenshot)
self.action_popout = QtWidgets.QAction("Popout chart")
self.action_popout.triggered.connect(lambda: self.popoutRequested.emit(self))
self.menu.addAction(self.action_popout)
def contextMenuEvent(self, event):
self.action_set_fixed_start.setText("Start (" + str(self.minDisplayLength) + ")")
self.action_set_fixed_stop.setText("Stop (" + str(self.maxDisplayLength) + ")")
self.y_action_set_fixed_minimum.setText("Minimum (" + str(self.minImpedance) + ")")
self.y_action_set_fixed_maximum.setText("Maximum (" + str(self.maxImpedance) + ")")
self.menu.exec_(event.globalPos())
def isPlotable(self, x, y):
return self.leftMargin <= x <= self.width() - self.rightMargin and \
self.topMargin <= y <= self.height() - self.bottomMargin
def resetDisplayLimits(self):
self.fixedSpan = False
self.minDisplayLength = 0
self.maxDisplayLength = 100
self.fixedValues = False
self.minImpedance = 0
self.maxImpedance = 1000
self.update()
def setFixedSpan(self, fixed_span):
self.fixedSpan = fixed_span
self.update()
def setMinimumLength(self):
min_val, selected = QtWidgets.QInputDialog.getDouble(self, "Start length (m)",
"Set start length (m)", value=self.minDisplayLength,
min=0, decimals=1)
if not selected:
return
if not (self.fixedSpan and min_val >= self.maxDisplayLength):
self.minDisplayLength = min_val
if self.fixedSpan:
self.update()
def setMaximumLength(self):
max_val, selected = QtWidgets.QInputDialog.getDouble(self, "Stop length (m)",
"Set stop length (m)", value=self.minDisplayLength,
min=0, decimals=1)
if not selected:
return
if not (self.fixedSpan and max_val <= self.minDisplayLength):
self.maxDisplayLength = max_val
if self.fixedSpan:
self.update()
def setFixedValues(self, fixed_values):
self.fixedValues = fixed_values
self.update()
def setMinimumImpedance(self):
min_val, selected = QtWidgets.QInputDialog.getDouble(self, "Minimum impedance (\N{OHM SIGN})",
"Set minimum impedance (\N{OHM SIGN})", value=self.minDisplayLength,
min=0, decimals=1)
if not selected:
return
if not (self.fixedValues and min_val >= self.maxImpedance):
self.minImpedance = min_val
if self.fixedSpan:
self.update()
def setMaximumImpedance(self):
max_val, selected = QtWidgets.QInputDialog.getDouble(self, "Maximum impedance (\N{OHM SIGN})",
"Set maximum impedance (\N{OHM SIGN})", value=self.minDisplayLength,
min=0, decimals=1)
if not selected:
return
if not (self.fixedValues and max_val <= self.minImpedance):
self.maxImpedance = max_val
if self.fixedSpan:
self.update()
def copy(self):
new_chart: TDRChart = super().copy()
new_chart.tdrWindow = self.tdrWindow
new_chart.minDisplayLength = self.minDisplayLength
new_chart.maxDisplayLength = self.maxDisplayLength
new_chart.fixedSpan = self.fixedSpan
new_chart.minImpedance = self.minImpedance
new_chart.maxImpedance = self.maxImpedance
new_chart.fixedValues = self.fixedValues
self.tdrWindow.updated.connect(new_chart.update)
return new_chart
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.width() - self.rightMargin:
a0.ignore()
return
a0.accept()
width = self.width() - self.leftMargin - self.rightMargin
if len(self.tdrWindow.td) > 0:
if self.fixedSpan:
max_index = np.searchsorted(self.tdrWindow.distance_axis, self.maxDisplayLength * 2)
min_index = np.searchsorted(self.tdrWindow.distance_axis, self.minDisplayLength * 2)
x_step = (max_index - min_index) / width
else:
max_index = math.ceil(len(self.tdrWindow.distance_axis) / 2)
x_step = max_index / width
self.markerLocation = int(round(absx * x_step))
self.update()
return
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
qp = QtGui.QPainter(self)
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
width = self.width() - self.leftMargin - self.rightMargin
height = self.height() - self.bottomMargin - self.topMargin
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, self.height() - self.bottomMargin, self.width() - self.rightMargin,
self.height() - self.bottomMargin)
qp.drawLine(self.leftMargin, self.topMargin - 5, self.leftMargin, self.height() - self.bottomMargin + 5)
ticks = math.floor((self.width() - self.leftMargin)/100) # Number of ticks does not include the origin
if len(self.tdrWindow.td) > 0:
if self.fixedSpan:
max_index = np.searchsorted(self.tdrWindow.distance_axis, self.maxDisplayLength * 2)
min_index = np.searchsorted(self.tdrWindow.distance_axis, self.minDisplayLength * 2)
x_step = (max_index - min_index) / width
else:
min_index = 0
max_index = math.ceil(len(self.tdrWindow.distance_axis) / 2)
x_step = max_index / width
if self.fixedValues:
min_impedance = self.minImpedance
max_impedance = self.maxImpedance
else:
# TODO: Limit the search to the selected span?
min_impedance = max(0, np.min(self.tdrWindow.step_response_Z) / 1.05)
max_impedance = min(1000, np.max(self.tdrWindow.step_response_Z) * 1.05)
y_step = np.max(self.tdrWindow.td) * 1.1 / height
y_impedance_step = (max_impedance - min_impedance) / height
for i in range(ticks):
x = self.leftMargin + round((i + 1) * width / ticks)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(x, self.topMargin, x, self.topMargin + height)
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(x - 15, self.topMargin + height + 15,
str(round(self.tdrWindow.distance_axis[min_index + int((x - self.leftMargin) * x_step) - 1]/2, 1)) + "m")
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(self.leftMargin - 10, self.topMargin + height + 15,
str(round(self.tdrWindow.distance_axis[min_index]/2, 1)) + "m")
y_ticks = math.floor(height / 60)
y_tick_step = height/y_ticks
for i in range(y_ticks):
y = self.bottomMargin + int(i * y_tick_step)
qp.setPen(self.foregroundColor)
qp.drawLine(self.leftMargin, y, self.leftMargin + width, y)
y_val = max_impedance - y_impedance_step * i * y_tick_step
qp.setPen(self.textColor)
qp.drawText(3, y + 3, str(round(y_val, 1)))
qp.drawText(3, self.topMargin + height + 3, str(round(min_impedance, 1)))
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
qp.setPen(pen)
for i in range(min_index, max_index):
if i < min_index or i > max_index:
continue
x = self.leftMargin + int((i - min_index) / x_step)
y = (self.topMargin + height) - int(self.tdrWindow.td[i] / y_step)
if self.isPlotable(x, y):
pen.setColor(self.sweepColor)
qp.setPen(pen)
qp.drawPoint(x, y)
x = self.leftMargin + int((i - min_index) / x_step)
y = (self.topMargin + height) - int((self.tdrWindow.step_response_Z[i]-min_impedance) / y_impedance_step)
if self.isPlotable(x, y):
pen.setColor(self.secondarySweepColor)
qp.setPen(pen)
qp.drawPoint(x, y)
id_max = np.argmax(self.tdrWindow.td)
max_point = QtCore.QPoint(self.leftMargin + int((id_max - min_index) / x_step),
(self.topMargin + height) - int(self.tdrWindow.td[id_max] / y_step))
qp.setPen(self.markers[0].color)
qp.drawEllipse(max_point, 2, 2)
qp.setPen(self.textColor)
qp.drawText(max_point.x() - 10, max_point.y() - 5,
str(round(self.tdrWindow.distance_axis[id_max]/2, 2)) + "m")
if self.markerLocation != -1:
marker_point = QtCore.QPoint(self.leftMargin + int((self.markerLocation - min_index) / x_step),
(self.topMargin + height) - int(self.tdrWindow.td[self.markerLocation] / y_step))
qp.setPen(self.textColor)
qp.drawEllipse(marker_point, 2, 2)
qp.drawText(marker_point.x() - 10, marker_point.y() - 5,
str(round(self.tdrWindow.distance_axis[self.markerLocation] / 2, 2)) + "m")
qp.end()
class RealImaginaryChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 45
self.rightMargin = 45
self.chartWidth = 230
self.chartHeight = 250
self.fstart = 0
self.fstop = 0
self.span_real = 0.01
self.span_imag = 0.01
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()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def copy(self):
new_chart: RealImaginaryChart = super().copy()
new_chart.maxDisplayReal = self.maxDisplayReal
new_chart.maxDisplayImag = self.maxDisplayImag
new_chart.minDisplayReal = self.minDisplayReal
new_chart.minDisplayImag = self.minDisplayImag
return new_chart
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(self.leftMargin + 5, 15, self.name + " (\N{OHM SIGN})")
qp.drawText(10, 15, "R")
qp.drawText(self.leftMargin + self.chartWidth + 10, 15, "X")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, self.topMargin - 5, self.leftMargin, self.topMargin + self.chartHeight + 5)
qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight,
self.leftMargin + self.chartWidth + 5, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
if self.fixedSpan:
fstart = self.minFrequency
fstop = self.maxFrequency
else:
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
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
# Find scaling
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:
imp = d.impedance()
re, im = imp.real, imp.imag
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
imp = d.impedance()
re, im = imp.real, imp.imag
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)
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
span_real = max_real - min_real
if span_real == 0:
span_real = 0.01
self.span_real = span_real
span_imag = max_imag - min_imag
if span_imag == 0:
span_imag = 0.01
self.span_imag = span_imag
# We want one horizontal tick per 50 pixels, at most
horizontal_ticks = math.floor(self.chartHeight/50)
for i in range(horizontal_ticks):
y = self.topMargin + round(i * self.chartHeight / horizontal_ticks)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth + 5, y)
qp.setPen(QtGui.QPen(self.textColor))
re = max_real - i * span_real / horizontal_ticks
im = max_imag - i * span_imag / horizontal_ticks
qp.drawText(3, y + 4, str(round(re, 1)))
qp.drawText(self.leftMargin + self.chartWidth + 8, y + 4, str(round(im, 1)))
qp.drawText(3, self.chartHeight + self.topMargin, str(round(min_real, 1)))
qp.drawText(self.leftMargin + self.chartWidth + 8, self.chartHeight + self.topMargin, str(round(min_imag, 1)))
self.drawFrequencyTicks(qp)
primary_pen = pen
secondary_pen = QtGui.QPen(self.secondarySweepColor)
if len(self.data) > 0:
c = QtGui.QColor(self.sweepColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(20, 9, 25, 9)
c = QtGui.QColor(self.secondarySweepColor)
c.setAlpha(255)
pen.setColor(c)
qp.setPen(pen)
qp.drawLine(self.leftMargin + self.chartWidth, 9, self.leftMargin + self.chartWidth + 5, 9)
primary_pen.setWidth(self.pointSize)
secondary_pen.setWidth(self.pointSize)
line_pen.setWidth(self.lineThickness)
for i in range(len(self.data)):
x = self.getXPosition(self.data[i])
y_re = self.getReYPosition(self.data[i])
y_im = self.getImYPosition(self.data[i])
qp.setPen(primary_pen)
if self.isPlotable(x, y_re):
qp.drawPoint(x, y_re)
qp.setPen(secondary_pen)
if self.isPlotable(x, y_im):
qp.drawPoint(x, y_im)
if self.drawLines and i > 0:
prev_x = self.getXPosition(self.data[i - 1])
prev_y_re = self.getReYPosition(self.data[i-1])
prev_y_im = self.getImYPosition(self.data[i-1])
# Real part first
line_pen.setColor(self.sweepColor)
qp.setPen(line_pen)
if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re):
qp.drawLine(x, y_re, prev_x, prev_y_re)
elif self.isPlotable(x, y_re) and not self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(x, y_re, prev_x, prev_y_re)
qp.drawLine(x, y_re, new_x, new_y)
elif not self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re)
qp.drawLine(prev_x, prev_y_re, new_x, new_y)
# Imag part second
line_pen.setColor(self.secondarySweepColor)
qp.setPen(line_pen)
if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im):
qp.drawLine(x, y_im, prev_x, prev_y_im)
elif self.isPlotable(x, y_im) and not self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(x, y_im, prev_x, prev_y_im)
qp.drawLine(x, y_im, new_x, new_y)
elif not self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im)
qp.drawLine(prev_x, prev_y_im, new_x, new_y)
primary_pen.setColor(self.referenceColor)
line_pen.setColor(self.referenceColor)
secondary_pen.setColor(self.secondaryReferenceColor)
qp.setPen(primary_pen)
if len(self.reference) > 0:
c = QtGui.QColor(self.referenceColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(20, 14, 25, 14)
c = QtGui.QColor(self.secondaryReferenceColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(self.leftMargin + self.chartWidth, 14, self.leftMargin + self.chartWidth + 5, 14)
for i in range(len(self.reference)):
if self.reference[i].freq < fstart or self.reference[i].freq > fstop:
continue
x = self.getXPosition(self.reference[i])
y_re = self.getReYPosition(self.reference[i])
y_im = self.getImYPosition(self.reference[i])
qp.setPen(primary_pen)
if self.isPlotable(x, y_re):
qp.drawPoint(x, y_re)
qp.setPen(secondary_pen)
if self.isPlotable(x, y_im):
qp.drawPoint(x, y_im)
if self.drawLines and i > 0:
prev_x = self.getXPosition(self.reference[i - 1])
prev_y_re = self.getReYPosition(self.reference[i-1])
prev_y_im = self.getImYPosition(self.reference[i-1])
line_pen.setColor(self.referenceColor)
qp.setPen(line_pen)
# Real part first
if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re):
qp.drawLine(x, y_re, prev_x, prev_y_re)
elif self.isPlotable(x, y_re) and not self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(x, y_re, prev_x, prev_y_re)
qp.drawLine(x, y_re, new_x, new_y)
elif not self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re)
qp.drawLine(prev_x, prev_y_re, new_x, new_y)
line_pen.setColor(self.secondaryReferenceColor)
qp.setPen(line_pen)
# Imag part second
if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im):
qp.drawLine(x, y_im, prev_x, prev_y_im)
elif self.isPlotable(x, y_im) and not self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(x, y_im, prev_x, prev_y_im)
qp.drawLine(x, y_im, new_x, new_y)
elif not self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im)
qp.drawLine(prev_x, prev_y_im, new_x, new_y)
# Now draw the markers
for m in self.markers:
if m.location != -1:
x = self.getXPosition(self.data[m.location])
y_re = self.getReYPosition(self.data[m.location])
y_im = self.getImYPosition(self.data[m.location])
self.drawMarker(x, y_re, qp, m.color, self.markers.index(m)+1)
self.drawMarker(x, y_im, qp, m.color, self.markers.index(m)+1)
def getImYPosition(self, d: Datapoint) -> int:
im = d.impedance().imag
return self.topMargin + round((self.max_imag - im) / self.span_imag * self.chartHeight)
def getReYPosition(self, d: Datapoint) -> int:
re = d.impedance().real
return self.topMargin + round((self.max_real - re) / self.span_real * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
valRe = -1 * ((absy / self.chartHeight * self.span_real) - self.max_real)
valIm = -1 * ((absy / self.chartHeight * self.span_imag) - self.max_imag)
return [valRe, valIm]
def zoomTo(self, x1, y1, x2, y2):
val1 = self.valueAtPosition(y1)
val2 = self.valueAtPosition(y2)
if len(val1) == len(val2) == 2 and val1[0] != val2[0]:
self.minDisplayReal = round(min(val1[0], val2[0]), 2)
self.maxDisplayReal = round(max(val1[0], val2[0]), 2)
self.minDisplayImag = round(min(val1[1], val2[1]), 2)
self.maxDisplayImag = round(max(val1[1], val2[1]), 2)
self.setFixedValues(True)
freq1 = max(1, self.frequencyAtPosition(x1, limit=False))
freq2 = max(1, self.frequencyAtPosition(x2, limit=False))
if freq1 > 0 and freq2 > 0 and freq1 != freq2:
self.minFrequency = min(freq1, freq2)
self.maxFrequency = max(freq1, freq2)
self.setFixedSpan(True)
self.update()
def getNearestMarker(self, x, y) -> Marker:
if len(self.data) == 0:
return None
shortest = 10**6
nearest = None
for m in self.markers:
mx, _ = self.getPosition(self.data[m.location])
myr = self.getReYPosition(self.data[m.location])
myi = self.getImYPosition(self.data[m.location])
dx = abs(x - mx)
dy = min(abs(y - myr), abs(y-myi))
distance = math.sqrt(dx**2 + dy**2)
if distance < shortest:
shortest = distance
nearest = m
return nearest
def setMinimumRealValue(self):
min_val, selected = QtWidgets.QInputDialog.getDouble(self, "Minimum real value",
"Set minimum real value", value=self.minDisplayReal,
decimals=2)
if not selected:
return
if not (self.fixedValues and min_val >= self.maxDisplayReal):
self.minDisplayReal = min_val
if self.fixedValues:
self.update()
def setMaximumRealValue(self):
max_val, selected = QtWidgets.QInputDialog.getDouble(self, "Maximum real value",
"Set maximum real value", value=self.maxDisplayReal,
decimals=2)
if not selected:
return
if not (self.fixedValues and max_val <= self.minDisplayReal):
self.maxDisplayReal = max_val
if self.fixedValues:
self.update()
def setMinimumImagValue(self):
min_val, selected = QtWidgets.QInputDialog.getDouble(self, "Minimum imaginary value",
"Set minimum imaginary value", value=self.minDisplayImag,
decimals=2)
if not selected:
return
if not (self.fixedValues and min_val >= self.maxDisplayImag):
self.minDisplayImag = min_val
if self.fixedValues:
self.update()
def setMaximumImagValue(self):
max_val, selected = QtWidgets.QInputDialog.getDouble(self, "Maximum imaginary value",
"Set maximum imaginary value", value=self.maxDisplayImag,
decimals=2)
if not selected:
return
if not (self.fixedValues and max_val <= self.minDisplayImag):
self.maxDisplayImag = max_val
if self.fixedValues:
self.update()
def setFixedValues(self, fixed_values: bool):
self.fixedValues = fixed_values
if fixed_values and (self.minDisplayReal >= self.maxDisplayReal or self.minDisplayImag > self.maxDisplayImag):
self.fixedValues = False
self.y_action_automatic.setChecked(True)
self.y_action_fixed_span.setChecked(False)
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())
class MagnitudeChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 30
self.chartWidth = 250
self.chartHeight = 250
self.minDisplayValue = 0
self.maxDisplayValue = 1
self.fixedValues = True
self.y_action_fixed_span.setChecked(True)
self.y_action_automatic.setChecked(False)
self.minValue = 0
self.maxValue = 1
self.span = 1
self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.bottomMargin)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
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.fstart = self.minFrequency
fstop = self.fstop = self.maxFrequency
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
if self.fixedValues:
maxValue = self.maxDisplayValue
minValue = self.minDisplayValue
self.maxValue = maxValue
self.minValue = minValue
else:
# Find scaling
minValue = 100
maxValue = 0
for d in self.data:
mag = self.magnitude(d)
if mag > maxValue:
maxValue = mag
if mag < minValue:
minValue = mag
for d in self.reference: # Also check min/max for the reference sweep
if d.freq < self.fstart or d.freq > self.fstop:
continue
mag = self.magnitude(d)
if mag > maxValue:
maxValue = mag
if mag < minValue:
minValue = mag
minValue = 10*math.floor(minValue/10)
self.minValue = minValue
maxValue = 10*math.ceil(maxValue/10)
self.maxValue = maxValue
span = maxValue-minValue
if span == 0:
span = 0.01
self.span = span
target_ticks = math.floor(self.chartHeight / 60)
for i in range(target_ticks):
val = minValue + i / target_ticks * span
y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight)
qp.setPen(self.textColor)
if val != minValue:
digits = max(0, min(2, math.floor(3 - math.log10(abs(val)))))
if digits == 0:
vswrstr = str(round(val))
else:
vswrstr = str(round(val, digits))
qp.drawText(3, y + 3, vswrstr)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, self.topMargin,
self.leftMargin + self.chartWidth, self.topMargin)
qp.setPen(self.textColor)
qp.drawText(3, self.topMargin + 4, str(maxValue))
qp.drawText(3, self.chartHeight+self.topMargin, str(minValue))
self.drawFrequencyTicks(qp)
qp.setPen(self.swrColor)
for vswr in self.swrMarkers:
if vswr <= 1:
continue
mag = (vswr-1)/(vswr+1)
y = self.topMargin + round((self.maxValue - mag) / self.span * self.chartHeight)
qp.drawLine(self.leftMargin, y, self.leftMargin + self.chartWidth, y)
qp.drawText(self.leftMargin + 3, y - 1, "VSWR: " + str(vswr))
self.drawData(qp, self.data, self.sweepColor)
self.drawData(qp, self.reference, self.referenceColor)
self.drawMarkers(qp)
def getYPosition(self, d: Datapoint) -> int:
mag = self.magnitude(d)
return self.topMargin + round((self.maxValue - mag) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue)
return [val]
@staticmethod
def magnitude(p: Datapoint) -> float:
return math.sqrt(p.re**2 + p.im**2)
def copy(self):
new_chart: LogMagChart = super().copy()
new_chart.span = self.span
return new_chart
class MagnitudeZChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 30
self.chartWidth = 250
self.chartHeight = 250
self.minDisplayValue = 0
self.maxDisplayValue = 100
self.minValue = 0
self.maxValue = 1
self.span = 1
self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.bottomMargin)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
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.fstart = self.minFrequency
fstop = self.fstop = self.maxFrequency
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
if self.fixedValues:
maxValue = self.maxDisplayValue
minValue = self.minDisplayValue
self.maxValue = maxValue
self.minValue = minValue
else:
# Find scaling
minValue = 100
maxValue = 0
for d in self.data:
mag = self.magnitude(d)
if mag > maxValue:
maxValue = mag
if mag < minValue:
minValue = mag
for d in self.reference: # Also check min/max for the reference sweep
if d.freq < self.fstart or d.freq > self.fstop:
continue
mag = self.magnitude(d)
if mag > maxValue:
maxValue = mag
if mag < minValue:
minValue = mag
minValue = 10*math.floor(minValue/10)
self.minValue = minValue
maxValue = 10*math.ceil(maxValue/10)
self.maxValue = maxValue
span = maxValue-minValue
if span == 0:
span = 0.01
self.span = span
target_ticks = math.floor(self.chartHeight / 60)
for i in range(target_ticks):
val = minValue + (i / target_ticks) * span
y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight)
qp.setPen(self.textColor)
if val != minValue:
digits = max(0, min(2, math.floor(3 - math.log10(abs(val)))))
if digits == 0:
vswrstr = str(round(val))
else:
vswrstr = str(round(val, digits))
qp.drawText(3, y + 3, vswrstr)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, self.topMargin,
self.leftMargin + self.chartWidth, self.topMargin)
qp.setPen(self.textColor)
qp.drawText(3, self.topMargin + 4, str(maxValue))
qp.drawText(3, self.chartHeight+self.topMargin, str(minValue))
self.drawFrequencyTicks(qp)
self.drawData(qp, self.data, self.sweepColor)
self.drawData(qp, self.reference, self.referenceColor)
self.drawMarkers(qp)
def getYPosition(self, d: Datapoint) -> int:
mag = self.magnitude(d)
return self.topMargin + round((self.maxValue - mag) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue)
return [val]
@staticmethod
def magnitude(p: Datapoint) -> float:
return abs(p.impedance())
def copy(self):
new_chart: LogMagChart = super().copy()
new_chart.span = self.span
return new_chart
class PermeabilityChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 40
self.rightMargin = 30
self.chartWidth = 230
self.chartHeight = 250
self.fstart = 0
self.fstop = 0
self.span = 0.01
self.max = 0
self.logarithmicY = True
self.maxDisplayValue = 100
self.minDisplayValue = -100
#
# 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()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
self.y_menu.addSeparator()
self.y_log_lin_group = QtWidgets.QActionGroup(self.y_menu)
self.y_action_linear = QtWidgets.QAction("Linear")
self.y_action_linear.setCheckable(True)
self.y_action_logarithmic = QtWidgets.QAction("Logarithmic")
self.y_action_logarithmic.setCheckable(True)
self.y_action_logarithmic.setChecked(True)
self.y_action_linear.triggered.connect(lambda: self.setLogarithmicY(False))
self.y_action_logarithmic.triggered.connect(lambda: self.setLogarithmicY(True))
self.y_log_lin_group.addAction(self.y_action_linear)
self.y_log_lin_group.addAction(self.y_action_logarithmic)
self.y_menu.addAction(self.y_action_linear)
self.y_menu.addAction(self.y_action_logarithmic)
def setLogarithmicY(self, logarithmic: bool):
self.logarithmicY = logarithmic
self.update()
def copy(self):
new_chart: PermeabilityChart = super().copy()
new_chart.logarithmicY = self.logarithmicY
return new_chart
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(self.leftMargin + 5, 15, self.name + " (\N{MICRO SIGN}\N{OHM SIGN} / Hz)")
qp.drawText(10, 15, "R")
qp.drawText(self.leftMargin + self.chartWidth + 10, 15, "X")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, self.topMargin - 5, self.leftMargin, self.topMargin + self.chartHeight + 5)
qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight,
self.leftMargin + self.chartWidth + 5, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
if self.fixedSpan:
fstart = self.minFrequency
fstop = self.maxFrequency
else:
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
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
# Find scaling
if self.fixedValues:
min_val = self.minDisplayValue
max_val = self.maxDisplayValue
else:
min_val = 1000
max_val = -1000
for d in self.data:
imp = d.impedance()
re, im = imp.real, imp.imag
re = re * 10e6 / d.freq
im = im * 10e6 / d.freq
if re > max_val:
max_val = re
if re < min_val:
min_val = re
if im > max_val:
max_val = im
if im < min_val:
min_val = im
for d in self.reference: # Also check min/max for the reference sweep
if d.freq < fstart or d.freq > fstop:
continue
imp = d.impedance()
re, im = imp.real, imp.imag
re = re * 10e6 / d.freq
im = im * 10e6 / d.freq
if re > max_val:
max_val = re
if re < min_val:
min_val = re
if im > max_val:
max_val = im
if im < min_val:
min_val = im
if self.logarithmicY:
min_val = max(0.01, min_val)
self.max = max_val
span = max_val - min_val
if span == 0:
span = 0.01
self.span = span
# We want one horizontal tick per 50 pixels, at most
horizontal_ticks = math.floor(self.chartHeight/50)
fmt = Format(max_nr_digits=4)
for i in range(horizontal_ticks):
y = self.topMargin + round(i * self.chartHeight / horizontal_ticks)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth + 5, y)
qp.setPen(QtGui.QPen(self.textColor))
val = Value(self.valueAtPosition(y)[0], fmt=fmt)
qp.drawText(3, y + 4, str(val))
qp.drawText(3, self.chartHeight + self.topMargin, str(Value(min_val, fmt=fmt)))
self.drawFrequencyTicks(qp)
primary_pen = pen
secondary_pen = QtGui.QPen(self.secondarySweepColor)
if len(self.data) > 0:
c = QtGui.QColor(self.sweepColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(20, 9, 25, 9)
c = QtGui.QColor(self.secondarySweepColor)
c.setAlpha(255)
pen.setColor(c)
qp.setPen(pen)
qp.drawLine(self.leftMargin + self.chartWidth, 9, self.leftMargin + self.chartWidth + 5, 9)
primary_pen.setWidth(self.pointSize)
secondary_pen.setWidth(self.pointSize)
line_pen.setWidth(self.lineThickness)
for i in range(len(self.data)):
x = self.getXPosition(self.data[i])
y_re = self.getReYPosition(self.data[i])
y_im = self.getImYPosition(self.data[i])
qp.setPen(primary_pen)
if self.isPlotable(x, y_re):
qp.drawPoint(x, y_re)
qp.setPen(secondary_pen)
if self.isPlotable(x, y_im):
qp.drawPoint(x, y_im)
if self.drawLines and i > 0:
prev_x = self.getXPosition(self.data[i - 1])
prev_y_re = self.getReYPosition(self.data[i-1])
prev_y_im = self.getImYPosition(self.data[i-1])
# Real part first
line_pen.setColor(self.sweepColor)
qp.setPen(line_pen)
if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re):
qp.drawLine(x, y_re, prev_x, prev_y_re)
elif self.isPlotable(x, y_re) and not self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(x, y_re, prev_x, prev_y_re)
qp.drawLine(x, y_re, new_x, new_y)
elif not self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re)
qp.drawLine(prev_x, prev_y_re, new_x, new_y)
# Imag part second
line_pen.setColor(self.secondarySweepColor)
qp.setPen(line_pen)
if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im):
qp.drawLine(x, y_im, prev_x, prev_y_im)
elif self.isPlotable(x, y_im) and not self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(x, y_im, prev_x, prev_y_im)
qp.drawLine(x, y_im, new_x, new_y)
elif not self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im)
qp.drawLine(prev_x, prev_y_im, new_x, new_y)
primary_pen.setColor(self.referenceColor)
line_pen.setColor(self.referenceColor)
secondary_pen.setColor(self.secondaryReferenceColor)
qp.setPen(primary_pen)
if len(self.reference) > 0:
c = QtGui.QColor(self.referenceColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(20, 14, 25, 14)
c = QtGui.QColor(self.secondaryReferenceColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(self.leftMargin + self.chartWidth, 14, self.leftMargin + self.chartWidth + 5, 14)
for i in range(len(self.reference)):
if self.reference[i].freq < fstart or self.reference[i].freq > fstop:
continue
x = self.getXPosition(self.reference[i])
y_re = self.getReYPosition(self.reference[i])
y_im = self.getImYPosition(self.reference[i])
qp.setPen(primary_pen)
if self.isPlotable(x, y_re):
qp.drawPoint(x, y_re)
qp.setPen(secondary_pen)
if self.isPlotable(x, y_im):
qp.drawPoint(x, y_im)
if self.drawLines and i > 0:
prev_x = self.getXPosition(self.reference[i - 1])
prev_y_re = self.getReYPosition(self.reference[i-1])
prev_y_im = self.getImYPosition(self.reference[i-1])
line_pen.setColor(self.referenceColor)
qp.setPen(line_pen)
# Real part first
if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re):
qp.drawLine(x, y_re, prev_x, prev_y_re)
elif self.isPlotable(x, y_re) and not self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(x, y_re, prev_x, prev_y_re)
qp.drawLine(x, y_re, new_x, new_y)
elif not self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re):
new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re)
qp.drawLine(prev_x, prev_y_re, new_x, new_y)
line_pen.setColor(self.secondaryReferenceColor)
qp.setPen(line_pen)
# Imag part second
if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im):
qp.drawLine(x, y_im, prev_x, prev_y_im)
elif self.isPlotable(x, y_im) and not self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(x, y_im, prev_x, prev_y_im)
qp.drawLine(x, y_im, new_x, new_y)
elif not self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im):
new_x, new_y = self.getPlotable(prev_x, prev_y_im, x, y_im)
qp.drawLine(prev_x, prev_y_im, new_x, new_y)
# Now draw the markers
for m in self.markers:
if m.location != -1:
x = self.getXPosition(self.data[m.location])
y_re = self.getReYPosition(self.data[m.location])
y_im = self.getImYPosition(self.data[m.location])
self.drawMarker(x, y_re, qp, m.color, self.markers.index(m)+1)
self.drawMarker(x, y_im, qp, m.color, self.markers.index(m)+1)
def getImYPosition(self, d: Datapoint) -> int:
im = d.impedance().imag
im = im * 10e6 / d.freq
if self.logarithmicY:
min_val = self.max - self.span
if self.max > 0 and min_val > 0 and im > 0:
span = math.log(self.max) - math.log(min_val)
else:
return -1
return self.topMargin + round((math.log(self.max) - math.log(im)) / span * self.chartHeight)
else:
return self.topMargin + round((self.max - im) / self.span * self.chartHeight)
def getReYPosition(self, d: Datapoint) -> int:
re = d.impedance().real
re = re * 10e6 / d.freq
if self.logarithmicY:
min_val = self.max - self.span
if self.max > 0 and min_val > 0 and re > 0:
span = math.log(self.max) - math.log(min_val)
else:
return -1
return self.topMargin + round((math.log(self.max) - math.log(re)) / span * self.chartHeight)
else:
return self.topMargin + round((self.max - re) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
if self.logarithmicY:
min_val = self.max - self.span
if self.max > 0 and min_val > 0:
span = math.log(self.max) - math.log(min_val)
step = span / self.chartHeight
val = math.exp(math.log(self.max) - absy * step)
else:
val = -1
else:
val = -1 * ((absy / self.chartHeight * self.span) - self.max)
return [val]
def getNearestMarker(self, x, y) -> Marker:
if len(self.data) == 0:
return None
shortest = 10**6
nearest = None
for m in self.markers:
mx, _ = self.getPosition(self.data[m.location])
myr = self.getReYPosition(self.data[m.location])
myi = self.getImYPosition(self.data[m.location])
dx = abs(x - mx)
dy = min(abs(y - myr), abs(y-myi))
distance = math.sqrt(dx**2 + dy**2)
if distance < shortest:
shortest = distance
nearest = m
return nearest
class GroupDelayChart(FrequencyChart):
def __init__(self, name="", reflective=True):
super().__init__(name)
self.leftMargin = 40
self.chartWidth = 250
self.chartHeight = 250
self.fstart = 0
self.fstop = 0
self.minDelay = 0
self.maxDelay = 0
self.span = 0
self.reflective = reflective
self.unwrappedData = []
self.unwrappedReference = []
self.groupDelay = []
self.groupDelayReference = []
self.minDisplayValue = -180
self.maxDisplayValue = 180
self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin,
self.chartHeight + self.topMargin + self.bottomMargin)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def copy(self):
new_chart: GroupDelayChart = super().copy()
new_chart.reflective = self.reflective
return new_chart
def setReference(self, data):
self.reference = data
self.calculateGroupDelay()
def setData(self, data):
self.data = data
self.calculateGroupDelay()
def calculateGroupDelay(self):
rawData = []
for d in self.data:
rawData.append(d.phase)
rawReference = []
for d in self.reference:
rawReference.append(d.phase)
if len(self.data) > 0:
self.unwrappedData = np.degrees(np.unwrap(rawData))
self.groupDelay = []
for i in range(len(self.data)):
if i == 0:
phase_change = self.unwrappedData[1] - self.unwrappedData[0]
freq_change = self.data[1].freq - self.data[0].freq
elif i == len(self.data)-1:
idx = len(self.data)-1
phase_change = self.unwrappedData[idx] - self.unwrappedData[idx-1]
freq_change = self.data[idx].freq - self.data[idx-1].freq
else:
phase_change = self.unwrappedData[i+1] - self.unwrappedData[i-1]
freq_change = self.data[i+1].freq - self.data[i-1].freq
delay = (-phase_change / (freq_change * 360)) * 10e8
if not self.reflective:
delay /= 2
self.groupDelay.append(delay)
if len(self.reference) > 0:
self.unwrappedReference = np.degrees(np.unwrap(rawReference))
self.groupDelayReference = []
for i in range(len(self.reference)):
if i == 0:
phase_change = self.unwrappedReference[1] - self.unwrappedReference[0]
freq_change = self.reference[1].freq - self.reference[0].freq
elif i == len(self.reference)-1:
idx = len(self.reference)-1
phase_change = self.unwrappedReference[idx] - self.unwrappedReference[idx-1]
freq_change = self.reference[idx].freq - self.reference[idx-1].freq
else:
phase_change = self.unwrappedReference[i+1] - self.unwrappedReference[i-1]
freq_change = self.reference[i+1].freq - self.reference[i-1].freq
delay = (-phase_change / (freq_change * 360)) * 10e8
if not self.reflective:
delay /= 2
self.groupDelayReference.append(delay)
self.update()
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name + " (ns)")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
if self.fixedValues:
min_delay = self.minDisplayValue
max_delay = self.maxDisplayValue
elif self.data:
min_delay = math.floor(np.min(self.groupDelay))
max_delay = math.ceil(np.max(self.groupDelay))
elif self.reference:
min_delay = math.floor(np.min(self.groupDelayReference))
max_delay = math.ceil(np.max(self.groupDelayReference))
span = max_delay - min_delay
if span == 0:
span = 0.01
self.minDelay = min_delay
self.maxDelay = max_delay
self.span = span
tickcount = math.floor(self.chartHeight / 60)
for i in range(tickcount):
delay = min_delay + span * i / tickcount
y = self.topMargin + round((self.maxDelay - delay) / self.span * self.chartHeight)
if delay != min_delay and delay != max_delay:
qp.setPen(QtGui.QPen(self.textColor))
if delay != 0:
digits = max(0, min(2, math.floor(3 - math.log10(abs(delay)))))
if digits == 0:
delaystr = str(round(delay))
else:
delaystr = str(round(delay, digits))
else:
delaystr = "0"
qp.drawText(3, y + 3, delaystr)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y)
qp.drawLine(self.leftMargin - 5, self.topMargin, self.leftMargin + self.chartWidth, self.topMargin)
qp.setPen(self.textColor)
qp.drawText(3, self.topMargin + 5, str(max_delay))
qp.drawText(3, self.chartHeight + self.topMargin, str(min_delay))
if self.fixedSpan:
fstart = self.minFrequency
fstop = self.maxFrequency
else:
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
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
self.drawFrequencyTicks(qp)
self.drawData(qp, self.data, self.sweepColor)
self.drawData(qp, self.reference, self.referenceColor)
self.drawMarkers(qp)
def getYPosition(self, d: Datapoint) -> int:
# TODO: Find a faster way than these expensive "d in self.data" lookups
if d in self.data:
delay = self.groupDelay[self.data.index(d)]
elif d in self.reference:
delay = self.groupDelayReference[self.reference.index(d)]
else:
delay = 0
return self.topMargin + round((self.maxDelay - delay) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
val = -1 * ((absy / self.chartHeight * self.span) - self.maxDelay)
return [val]
class CapacitanceChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 30
self.chartWidth = 250
self.chartHeight = 250
self.minDisplayValue = 0
self.maxDisplayValue = 100
self.minValue = -1
self.maxValue = 1
self.span = 1
self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.bottomMargin)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name + " (F)")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
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.fstart = self.minFrequency
fstop = self.fstop = self.maxFrequency
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
if self.fixedValues:
maxValue = self.maxDisplayValue / 10e11
minValue = self.minDisplayValue / 10e11
self.maxValue = maxValue
self.minValue = minValue
else:
# Find scaling
minValue = 1
maxValue = -1
for d in self.data:
val = d.capacitiveEquivalent()
if val > maxValue:
maxValue = val
if val < minValue:
minValue = val
for d in self.reference: # Also check min/max for the reference sweep
if d.freq < self.fstart or d.freq > self.fstop:
continue
val = d.capacitiveEquivalent()
if val > maxValue:
maxValue = val
if val < minValue:
minValue = val
self.maxValue = maxValue
self.minValue = minValue
span = maxValue - minValue
if span == 0:
logger.info("Span is zero for CapacitanceChart, setting to a small value.")
span = 1e-15
self.span = span
target_ticks = math.floor(self.chartHeight / 60)
fmt = Format(max_nr_digits=3)
for i in range(target_ticks):
val = minValue + (i / target_ticks) * span
y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight)
qp.setPen(self.textColor)
if val != minValue:
valstr = str(Value(val, fmt=fmt))
qp.drawText(3, y + 3, valstr)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, self.topMargin,
self.leftMargin + self.chartWidth, self.topMargin)
qp.setPen(self.textColor)
qp.drawText(3, self.topMargin + 4, str(Value(maxValue, fmt=fmt)))
qp.drawText(3, self.chartHeight+self.topMargin, str(Value(minValue, fmt=fmt)))
self.drawFrequencyTicks(qp)
self.drawData(qp, self.data, self.sweepColor)
self.drawData(qp, self.reference, self.referenceColor)
self.drawMarkers(qp)
def getYPosition(self, d: Datapoint) -> int:
return self.topMargin + round((self.maxValue - d.capacitiveEquivalent()) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue)
return [val * 10e11]
def copy(self):
new_chart: CapacitanceChart = super().copy()
new_chart.span = self.span
return new_chart
class InductanceChart(FrequencyChart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 30
self.chartWidth = 250
self.chartHeight = 250
self.minDisplayValue = 0
self.maxDisplayValue = 100
self.minValue = -1
self.maxValue = 1
self.span = 1
self.setMinimumSize(self.chartWidth + self.rightMargin + self.leftMargin, self.chartHeight + self.topMargin + self.bottomMargin)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name + " (H)")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight, self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(self.lineThickness)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
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.fstart = self.minFrequency
fstop = self.fstop = self.maxFrequency
# Draw bands if required
if self.bands.enabled:
self.drawBands(qp, fstart, fstop)
if self.fixedValues:
maxValue = self.maxDisplayValue / 10e11
minValue = self.minDisplayValue / 10e11
self.maxValue = maxValue
self.minValue = minValue
else:
# Find scaling
minValue = 1
maxValue = -1
for d in self.data:
val = d.inductiveEquivalent()
if val > maxValue:
maxValue = val
if val < minValue:
minValue = val
for d in self.reference: # Also check min/max for the reference sweep
if d.freq < self.fstart or d.freq > self.fstop:
continue
val = d.inductiveEquivalent()
if val > maxValue:
maxValue = val
if val < minValue:
minValue = val
self.maxValue = maxValue
self.minValue = minValue
span = maxValue - minValue
if span == 0:
logger.info("Span is zero for CapacitanceChart, setting to a small value.")
span = 1e-15
self.span = span
target_ticks = math.floor(self.chartHeight / 60)
fmt = Format(max_nr_digits=3)
for i in range(target_ticks):
val = minValue + (i / target_ticks) * span
y = self.topMargin + round((self.maxValue - val) / self.span * self.chartHeight)
qp.setPen(self.textColor)
if val != minValue:
valstr = str(Value(val, fmt=fmt))
qp.drawText(3, y + 3, valstr)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth, y)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, self.topMargin,
self.leftMargin + self.chartWidth, self.topMargin)
qp.setPen(self.textColor)
qp.drawText(3, self.topMargin + 4, str(Value(maxValue, fmt=fmt)))
qp.drawText(3, self.chartHeight+self.topMargin, str(Value(minValue, fmt=fmt)))
self.drawFrequencyTicks(qp)
self.drawData(qp, self.data, self.sweepColor)
self.drawData(qp, self.reference, self.referenceColor)
self.drawMarkers(qp)
def getYPosition(self, d: Datapoint) -> int:
return self.topMargin + round((self.maxValue - d.inductiveEquivalent()) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
absy = y - self.topMargin
val = -1 * ((absy / self.chartHeight * self.span) - self.maxValue)
return [val * 10e11]
def copy(self):
new_chart: InductanceChart = super().copy()
new_chart.span = self.span
return new_chart