diff --git a/NanoVNASaver/Chart.py b/NanoVNASaver/Chart.py index c6da7b3..84b0a73 100644 --- a/NanoVNASaver/Chart.py +++ b/NanoVNASaver/Chart.py @@ -1345,6 +1345,261 @@ class LogMagChart(FrequencyChart): 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 + 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) + + def getYPosition(self, d: Datapoint) -> int: + logMag = self.logMag(d) + return self.topMargin + round((self.maxValue - logMag) / self.span * self.chartHeight) + + def logMag(self, p: Datapoint) -> float: + if self.isInverted: + return -RFTools.gain(p) + else: + return RFTools.gain(p) + + 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) diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index 10ec884..25d11c2 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -31,7 +31,7 @@ from serial.tools import list_ports from .Hardware import VNA, InvalidVNA, Version from .RFTools import RFTools, Datapoint from .Chart import Chart, PhaseChart, VSWRChart, PolarChart, SmithChart, LogMagChart, QualityFactorChart, TDRChart, \ - RealImaginaryChart, MagnitudeChart, MagnitudeZChart + RealImaginaryChart, MagnitudeChart, MagnitudeZChart, CombinedLogMagChart from .Calibration import CalibrationWindow, Calibration from .Marker import Marker from .SweepWorker import SweepWorker @@ -131,6 +131,7 @@ class NanoVNASaver(QtWidgets.QWidget): self.s11RealImaginary = RealImaginaryChart("S11 R+jX") self.tdr_chart = TDRChart("TDR") self.tdr_mainwindow_chart = TDRChart("TDR") + self.combinedLogMag = CombinedLogMagChart("S11 & S21 LogMag") # List of all the S11 charts, for selecting self.s11charts: List[Chart] = [] @@ -150,8 +151,12 @@ class NanoVNASaver(QtWidgets.QWidget): self.s21charts.append(self.s21Mag) self.s21charts.append(self.s21Phase) + # List of all charts that use both S11 and S21 + self.combinedCharts: List[Chart] = [] + self.combinedCharts.append(self.combinedLogMag) + # List of all charts that can be selected for display - self.selectable_charts = self.s11charts + self.s21charts + self.selectable_charts = self.s11charts + self.s21charts + self.combinedCharts self.selectable_charts.append(self.tdr_mainwindow_chart) # List of all charts that subscribe to updates (including duplicates!) @@ -685,6 +690,9 @@ class NanoVNASaver(QtWidgets.QWidget): for c in self.s21charts: c.setData(self.data21) + for c in self.combinedCharts: + c.setCombinedData(self.data, self.data21) + self.sweepProgressBar.setValue(self.worker.percentage) self.tdr_window.updateTDR() @@ -782,6 +790,10 @@ class NanoVNASaver(QtWidgets.QWidget): self.referenceS21data = s21data for c in self.s21charts: c.setReference(s21data) + + for c in self.combinedCharts: + c.setCombinedReference(s11data, s21data) + self.btnResetReference.setDisabled(False) if source is not None: @@ -893,6 +905,8 @@ class NanoVNASaver(QtWidgets.QWidget): self.s11charts.append(new_chart) if chart in self.s21charts: self.s21charts.append(new_chart) + if chart in self.combinedCharts: + self.combinedCharts.append(new_chart) new_chart.popoutRequested.connect(self.popoutChart) return new_chart