fix graph and white background added
This commit is contained in:
@@ -6,10 +6,10 @@ from typing import Protocol
|
||||
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QPen
|
||||
from PyQt6.QtWidgets import QVBoxLayout, QWidget
|
||||
|
||||
from python_app.gui.controllers.renderers.common import create_plot
|
||||
from python_app.gui.controllers.renderers.common import CAPTURED_TRACE_PEN, OVERLAY_FILL, SERIES_COLORS, create_plot
|
||||
from python_app.models.dataset_model import ComboKey, ResultCollection, TraceData
|
||||
from python_app.models.gui_profile_schema import GuiPassThroughStateModel
|
||||
from python_app.models.run_config_validation import parse_combos_from_text
|
||||
@@ -21,9 +21,7 @@ class WarningLogger(Protocol):
|
||||
|
||||
|
||||
CurveKey = tuple[ComboKey, int, str]
|
||||
Series = tuple[CurveKey, np.ndarray, np.ndarray, str]
|
||||
_PALETTE = ("#4cc9f0", "#f72585", "#b8f2e6", "#ffd166",
|
||||
"#90be6d", "#ff595e", "#6a4c93", "#1982c4")
|
||||
Series = tuple[CurveKey, np.ndarray, np.ndarray, QPen]
|
||||
|
||||
|
||||
class _TracePanel:
|
||||
@@ -35,57 +33,49 @@ class _TracePanel:
|
||||
assert item is not None
|
||||
self.item = item
|
||||
self.view = item.getViewBox()
|
||||
self.item.showGrid(x=True, y=True, alpha=0.2)
|
||||
self.item.setLabel("left", label, units=units)
|
||||
self.item.setLabel("bottom", "Frequency", units="Hz")
|
||||
self.item.setDownsampling(mode="peak")
|
||||
self.item.setClipToView(True)
|
||||
self.curves: dict[CurveKey, pg.PlotCurveItem] = {}
|
||||
self.legend: pg.LegendItem | None = None
|
||||
self._legend_sources: dict[ComboKey, pg.PlotCurveItem] = {}
|
||||
# Sweeps denser than the screen are drawn as per-bin min/max, so narrow peaks
|
||||
# and notches stay visible; ordinary sweeps keep every point.
|
||||
self.item.setDownsampling(auto=True, mode="peak")
|
||||
self.curves: dict[CurveKey, pg.PlotDataItem] = {}
|
||||
self.legend = self.item.addLegend(offset=(8, 8), brush=pg.mkBrush(*OVERLAY_FILL), colCount=2)
|
||||
self.legend.hide()
|
||||
self._legend_sources: dict[ComboKey, pg.PlotDataItem] = {}
|
||||
|
||||
def clear(self) -> None:
|
||||
self.item.clear()
|
||||
self.curves.clear()
|
||||
self.sync_legend({})
|
||||
|
||||
def update_curve(self, key: CurveKey, x: np.ndarray, y: np.ndarray,
|
||||
color: str, *, width: float, dashed: bool = False) -> pg.PlotCurveItem:
|
||||
pen = pg.mkPen(color, width=width,
|
||||
style=Qt.PenStyle.DashLine if dashed else Qt.PenStyle.SolidLine)
|
||||
def update_curve(self, key: CurveKey, x: np.ndarray, y: np.ndarray, pen: QPen) -> pg.PlotDataItem:
|
||||
curve = self.curves.get(key)
|
||||
if curve is None:
|
||||
curve = pg.PlotCurveItem(pen=pen, antialias=False)
|
||||
curve = pg.PlotDataItem(antialias=False)
|
||||
self.curves[key] = curve
|
||||
self.item.addItem(curve)
|
||||
else:
|
||||
curve.setPen(pen)
|
||||
curve.setData(x, y)
|
||||
curve.setData(x, y, pen=pen) # one update; a separate setPen would redraw the old data first
|
||||
return curve
|
||||
|
||||
def retain_curves(self, active_keys: set[CurveKey]) -> None:
|
||||
for key in self.curves.keys() - active_keys:
|
||||
self.item.removeItem(self.curves.pop(key))
|
||||
|
||||
def sync_legend(self, sources: dict[ComboKey, pg.PlotCurveItem]) -> None:
|
||||
def sync_legend(self, sources: dict[ComboKey, pg.PlotDataItem]) -> None:
|
||||
if self._legend_sources == sources:
|
||||
return
|
||||
if self.legend is None and sources:
|
||||
self.legend = self.item.addLegend(offset=(8, 8))
|
||||
if self.legend is not None:
|
||||
self.legend.clear()
|
||||
for combo in sorted(sources, key=lambda key: (key.input, key.output)):
|
||||
self.legend.addItem(sources[combo], f"in{combo.input}/out{combo.output}")
|
||||
self.legend.setVisible(bool(sources))
|
||||
self.legend.clear()
|
||||
for combo in sorted(sources, key=lambda key: (key.input, key.output)):
|
||||
self.legend.addItem(sources[combo], f"in{combo.input}/out{combo.output}")
|
||||
self.legend.setVisible(bool(sources))
|
||||
self._legend_sources = sources
|
||||
|
||||
|
||||
class TraceRenderer:
|
||||
"""Render result traces or a captured trace using explicit display settings."""
|
||||
|
||||
def __init__(self, log_warning: WarningLogger, max_points: int = 800) -> None:
|
||||
def __init__(self, log_warning: WarningLogger) -> None:
|
||||
self._log_warning = log_warning
|
||||
self.max_points = max_points
|
||||
self.widget = QWidget()
|
||||
layout = QVBoxLayout(self.widget)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -102,23 +92,24 @@ class TraceRenderer:
|
||||
|
||||
def draw(self, collection: ResultCollection, settings: GuiPassThroughStateModel) -> bool:
|
||||
combo_filter = self._combo_filter(settings.combo_filter)
|
||||
colors: dict[ComboKey, str] = {}
|
||||
series: list[Series] = []
|
||||
for block in collection.blocks:
|
||||
# The color follows the pair's place in the run, so filtering never repaints shown pairs.
|
||||
for position, block in enumerate(collection.blocks):
|
||||
if combo_filter is not None and block.combo not in combo_filter:
|
||||
continue
|
||||
color = colors.setdefault(block.combo, _PALETTE[len(colors) % len(_PALETTE)])
|
||||
pen = pg.mkPen(SERIES_COLORS[position % len(SERIES_COLORS)], width=2)
|
||||
for index, payload in enumerate(block.payloads):
|
||||
if payload.kind != 1 or not payload.trace.size or payload.frequency_hz.size != payload.trace.size:
|
||||
# Another mode's result (a B-scan range profile) is not S21 over frequency.
|
||||
if payload.processing_name != "pass_through" or payload.kind != 1:
|
||||
continue
|
||||
series.append(((block.combo, index, payload.processing_name),
|
||||
payload.frequency_hz, payload.trace, color))
|
||||
payload.frequency_hz, payload.trace, pen))
|
||||
return self._render(series, settings, "Pass-Through S21")
|
||||
|
||||
def draw_trace(self, trace: TraceData, title: str, settings: GuiPassThroughStateModel) -> None:
|
||||
self.clear()
|
||||
self._render([((trace.combo, 0, "__single_trace__"), trace.frequency_hz,
|
||||
trace.s21, "#ffd166")], settings, title, single=True)
|
||||
trace.s21, CAPTURED_TRACE_PEN)], settings, title, single=True)
|
||||
|
||||
def _combo_filter(self, text: str) -> set[ComboKey] | None:
|
||||
text = text.strip()
|
||||
@@ -142,27 +133,23 @@ class TraceRenderer:
|
||||
return False
|
||||
mag.item.showAxis("bottom", show=not settings.show_phase)
|
||||
active: set[CurveKey] = set()
|
||||
mag_sources: dict[ComboKey, pg.PlotCurveItem] = {}
|
||||
phase_sources: dict[ComboKey, pg.PlotCurveItem] = {}
|
||||
mag_sources: dict[ComboKey, pg.PlotDataItem] = {}
|
||||
phase_sources: dict[ComboKey, pg.PlotDataItem] = {}
|
||||
x_min, x_max = np.inf, -np.inf
|
||||
for key, frequency, samples, color in series:
|
||||
for key, frequency, samples, pen in series:
|
||||
if not samples.size or frequency.size != samples.size:
|
||||
continue
|
||||
active.add(key)
|
||||
x_min, x_max = min(x_min, float(np.min(frequency))), max(x_max, float(np.max(frequency)))
|
||||
step = max(1, int(np.ceil(samples.size / self.max_points))) if self.max_points > 0 else 1
|
||||
if settings.show_magnitude:
|
||||
magnitude = 20.0 * np.log10(np.maximum(np.abs(samples[::step]), 1e-12))
|
||||
curve = mag.update_curve(key, frequency[::step], magnitude, color,
|
||||
width=1.8 if single else 1.4)
|
||||
magnitude = 20.0 * np.log10(np.maximum(np.abs(samples), 1e-12))
|
||||
curve = mag.update_curve(key, frequency, magnitude, pen)
|
||||
mag_sources.setdefault(key[0], curve)
|
||||
if settings.show_phase:
|
||||
angles = np.angle(samples)
|
||||
if settings.unwrap_phase:
|
||||
angles = np.unwrap(angles)
|
||||
curve = phase.update_curve(key, frequency[::step], np.degrees(angles[::step]),
|
||||
"#80ed99" if single else color,
|
||||
width=1.4 if single else 1.2, dashed=single)
|
||||
curve = phase.update_curve(key, frequency, np.degrees(angles), pen)
|
||||
phase_sources.setdefault(key[0], curve)
|
||||
|
||||
for panel, visible, sources in ((mag, settings.show_magnitude, mag_sources),
|
||||
@@ -171,7 +158,6 @@ class TraceRenderer:
|
||||
panel.sync_legend(sources if not single else {})
|
||||
if visible:
|
||||
panel.item.setTitle(title)
|
||||
panel.view.invertY(False)
|
||||
if active and np.isfinite(x_min) and np.isfinite(x_max):
|
||||
panel.view.setXRange(x_min, x_max, padding=0.02)
|
||||
if settings.show_magnitude:
|
||||
|
||||
Reference in New Issue
Block a user