Source code for src.pynanovna.vis

"""
Plotting functions
"""

import logging
import matplotlib.pyplot as plt
import numpy as np


[docs] def plot( stream: object, axis_mode: str = "first", fixed_limits: list[float] = None, log: bool = True, ): """ Show a magnitude plot from the data. Args: stream: The data stream to plot. axis_mode (str): 'dynamic', 'fixed', or 'first'. Default is 'dynamic'. 'dynamic' - Axes limits adjust to data. 'fixed' - Axes limits are fixed as per 'fixed_limits'. 'first' - Axes limits are set based on the first data batch and kept constant. fixed_limits (list): A dictionary containing axis limits if axis_mode is 'fixed'. Example: [min_s11, max_s11, min_s21, max_s21] log (bool): If the magnitude should be log or not. """ plt.ion() fig, ax = plt.subplots(2, 1, figsize=(10, 8)) fig.tight_layout(pad=4.0) ax[0].set(xlabel="Frequency (Hz)", ylabel="dB", title="S11") ax[1].set(xlabel="Frequency (Hz)", ylabel="dB", title="S21") (line1,) = ax[0].plot([], [], label="S11") (line2,) = ax[1].plot([], [], label="S21") ax[0].legend() ax[1].legend() if log: ax[0].set_yscale("log") ax[1].set_yscale("log") plt.show() first_data = True try: for new_data in stream: s11 = np.abs(new_data[0]) s21 = np.abs(new_data[1]) x = new_data[2] line1.set_data(x, s11) line2.set_data(x, s21) if first_data: if axis_mode == "first" or axis_mode == "fixed": fixed_xlim = (min(x), max(x)) fixed_ylim_s11 = ( (fixed_limits[0], fixed_limits[1]) if axis_mode == "fixed" else (min(s11), max(s11)) ) fixed_ylim_s21 = ( (fixed_limits[2], fixed_limits[3]) if axis_mode == "fixed" else (min(s21), max(s21)) ) ax[0].set_xlim(*fixed_xlim) ax[0].set_ylim(*fixed_ylim_s11) ax[1].set_xlim(*fixed_xlim) ax[1].set_ylim(*fixed_ylim_s21) first_data = False if axis_mode == "dynamic": ax[0].relim() ax[0].autoscale_view() ax[1].relim() ax[1].autoscale_view() fig.canvas.draw() fig.canvas.flush_events() plt.pause(0.001) except KeyboardInterrupt: logging.info("Killing csv stream because of keyboard interrupt.") return except Exception as e: logging.critical("Error in the magnitude plot function.", exc_info=e) return plt.ioff() plt.show()
[docs] def polar(stream: object, normalize: bool = False): """ Create polar plots for S11 and S21 data. Args: stream: The data stream to plot. normalize (bool): If True, normalize the data based on the first value. """ plt.ion() fig, ax = plt.subplots(1, 2, subplot_kw=dict(polar=True), figsize=(12, 6)) fig.tight_layout(pad=4.0) (line1,) = ax[0].plot([], [], label="S11") (line2,) = ax[1].plot([], [], label="S21") ax[0].legend() ax[1].legend() ax[0].set_title("S11 Polar Plot") ax[1].set_title("S21 Polar Plot") plt.show() first_data = True ref_s11, ref_s21, ref_theta1, ref_theta2 = None, None, None, None for new_data in stream: try: s11 = np.abs(new_data[0]) s21 = np.abs(new_data[1]) theta1 = np.angle(new_data[0]) theta2 = np.angle(new_data[1]) if first_data: if normalize: ref_s11, ref_s21 = s11[0], s21[0] ref_theta1, ref_theta2 = theta1[0], theta2[0] s11 = s11 - ref_s11 s21 = s21 - ref_s21 theta1 = (theta1 - ref_theta1) % (2 * np.pi) theta2 = (theta2 - ref_theta2) % (2 * np.pi) ax[0].set_ylim(0, max(s11) * 1.1) ax[1].set_ylim(0, max(s21) * 1.1) first_data = False elif normalize: s11 = s11 - ref_s11 s21 = s21 - ref_s21 theta1 = (theta1 - ref_theta1) % (2 * np.pi) theta2 = (theta2 - ref_theta2) % (2 * np.pi) line1.set_data(theta1, s11) line2.set_data(theta2, s21) fig.canvas.draw() fig.canvas.flush_events() plt.pause(0.01) except KeyboardInterrupt: logging.info("Killing csv stream because of keyboard interrupt.") return except Exception as e: logging.critical("Error in the polar plot function.", exc_info=e) return plt.ioff() plt.show()