Rewrite to make it more modular

added veiws for area and single waveforms
added settings dialog
added MenuBar and populated with "View" "Settings" and "Help"
Help menu contains "About" currently

** ** WILL NOT RUN due to rewrite being incomplete ** **
This commit is contained in:
bryce
2025-08-30 19:41:31 +12:00
parent 9d5ba2d7c2
commit 613f017f41
22 changed files with 670 additions and 114 deletions

BIN
assets/icons/app_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -64,3 +64,11 @@ QWidget#waveformChartContainer {
border-radius: 8px;
padding: 16px;
}
QLabel#chartTitleLabel {
font-size: 14px;
font-weight: bold;
color: #CBD5E0;
padding-bottom: 2px;
padding-left: 5px;
}

2
config/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
from .areas_config import AREAS_OF_INTEREST
from .stations_config import ALL_STATIONS_CONFIG, SENSOR_TYPE_CHANNELS, STATION_LOOKUP

Binary file not shown.

Binary file not shown.

Binary file not shown.

173
config/areas_config.py Normal file
View File

@@ -0,0 +1,173 @@
# DICTIONARY mapping area names to a list of station codes belonging to that area.
#
# Each station config in the list is a dictionary:
# {
# code: StationCode
# preferred_channel: channelCode
# preferred_location: locationCode
# }
AREAS_OF_INTEREST = {
"Northland & Auckland": [
],
"Waikato": [
],
"Hikaurangi Subduction Zone": [
{
"code": "RIZ",
"preferred_channel": "HHE",
"preferred_location": "10"
},
{
"code": "GLKZ",
"preferred_channel": "HHE",
"preferred_location": "10"
},
{
"code": "MXZ",
"preferred_channel": "HHE",
"preferred_location": "10"
},
{
"code": "WMGZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "PUZ",
"preferred_channel": "HHE",
"preferred_location": "10"
},
{
"code": "CNGZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "RIGZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "PRGZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "KNZ",
"preferred_channel": "HHE",
"preferred_location": "10"
},
{
"code": "WHHZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "ARHZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "CKHZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "KAHZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "PXZ",
"preferred_channel": "HHE",
"preferred_location": "10"
},
{
"code": "PRHZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "BFZ",
"preferred_channel": "HHE",
"preferred_location": "10"
},
{
"code": "CPWZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "TMWZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "TRWZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "WRRZ",
"preferred_channel": "HHE",
"preferred_location": "10"
},
{
"code": "CMWZ",
"preferred_channel": "EHE",
"preferred_location": "10"
},
{
"code": "BSWZ",
"preferred_channel": "HHE",
"preferred_location": "10"
},
{
"code": "CTZ",
"preferred_channel": "HHE",
"preferred_location": "10"
},
],
"Rotorua & East Cape": [
],
"White Island": [
],
"Taupo & Greater Area": [
],
"Hawkes Bay": [
],
"Mount Taranaki": [
],
"Palmerston North & Whanganui": [
],
"Mount Ruapehu": [
],
"Wellington (Ashurst & South)": [
],
"Christchurch & Kaikoura incl. Banks Peninsula": [
],
"Southland (Otago, Invicargil Milford Sound etc)": [
],
"Stewart Island, Snares Island & Antartica": [
],
"Southern Alps / Alpine Fault": [
],
"Queenstown & Wanaka": [
],
}

251
config/stations_config.py Normal file
View File

@@ -0,0 +1,251 @@
"""
THIS IS A DICTIONARY that maps a sensot type to a list of available channels for that type
"""
SENSOR_TYPE_CHANNELS = {
"Broadband": ["BHZ", "BHN", "BHE", "HHZ", "HHN", "HHE", "LHZ", "LHN", "LHE"],
"Short Period": ["EHZ", "EHN", "EHE", "SHZ", "SHN", "SHE"],
"Strong Motion": ["HNZ", "HNN", "HNE"],
"All Channels": []
}
#List of all known stations with their details
# Each Station is a DICTIONARY
ALL_STATIONS_CONFIG = [
{
"code": "RIZ",
"name": "Bells Beach, Raoul Is.",
"location_codes": {
"HH": "10",
"LH": "10",
"default": "10"
},
"types": ["Broadband"],
"area_tags": ["Raoul Island", "Bells Beach" "Hikaurangi Trench"]
},
{
"code": "GLKZ",
"name": "Green Lake, Raoul Is.",
"location_codes": {
"HH": "10",
"LH": "10",
"default": "10"
},
"types": ["Broadband"],
"area_tags": ["Raoul Island", "Green Lake", "Hikaurangi Trench"]
},
{
"code": "MXZ",
"name": "Te Araroa",
"location_codes": {
"HH": "10",
"LH": "10",
"default": "10"
},
"types": ["Broadband"],
"area_tags": ["East Coast", "Te Araroa", "North Island", "Hikaurangi Trench"]
},
{
"code": "WMGZ",
"name": "Ruatoria",
"location_codes": {
"EH": "10",
"default": "10"
},
"types": ["Short Period"],
"area_tags": ["East Coast", "Ruatoria", "North Island", "Hikaurangi Trench"]
},
{
"code": "PUZ",
"name": "Tokomaru Bay",
"location_codes": {
"HH": "10",
"LH": "10",
"default": "10"
},
"types": ["Broadband"],
"area_tags": ["East Coast", "Tokomaru Bay", "North Island", "Hikaurangi Trench"]
},
{
"code": "CNGZ",
"name": "Tologa Bay",
"location_codes": {
"EH": "10",
"default": "10"
},
"types": ["Shot Period"],
"area_tags": ["East Coast", "Tologa Bay", "Hikaurangi Trench", "North Island"]
},
{
"code": "RIGZ",
"name": "Gisborne",
"location_codes": {
"EH": "10",
"default": "10"
},
"types": ["Short Period"],
"area_tags": ["Gisborne", "East Coast", "Hikaurangi Trench", "North Island"]
},
{
"code": "PRGZ",
"name": "Tikiwhata",
"location_codes": {
"EH": "10",
"default": "10"
},
"types": ["Short Period"],
"area_tags": ["East Coast", "Tikiwhata", "Hikaurangi Trench", "North Island"]
},
{
"code": "KNZ",
"name": "Wairoa",
"location_codes": {
"HH": "10",
"LH": "10",
"default": "10"
},
"types": ["Broadband"],
"area_tags": ["Wairoa", "East Coast", "Hikaurangi Trench", "North Island"]
},
{
"code": "WHHZ",
"name": "Waihua",
"location_codes": {
"EH": "10",
"default": "10"
},
"types": ["Short Period"],
"area_tags": ["Waihua", "East Coast", "Hikaurangi Trench", "North Island"]
},
{
"code": "ARHZ",
"name": "Aropaonui",
"location_codes": {
"EH": "10",
"default": "10"
},
"types": ["Short Period"],
"area_tags": ["East Coast", "Aropaonui", "Hikaurangi Trench", "North Island"]
},
{
"code": "CKHZ",
"name": "Cape Kidnappers",
"location_codes": {
"EH": "10",
"default": "10"
},
"types": ["Short Period"],
"area_tags": ["Cape Kidnappers", "East Coast", "Hikaurangi Trench", "North Island"]
},
{
"code": "KAHZ",
"name": "Tukituki",
"location_codes": {
"EH": "10",
"default": "10"
},
"types": ["Short Period"],
"area_tags": ["Tukituki", "East Coast", "Hikaurangi Trench", "North Island"]
},
{
"code": "PXZ",
"name": "Pawanui",
"location_codes": {
"HH": "10",
"LH": "10",
"default": "10"
},
"types": ["Broadband"],
"area_tags": ["Pawanui", "East Coast", "Hikaurangi Trench", "North Island"]
},
{
"code": "PRHZ",
"name": "Porangahau",
"location_codes": {
"EH": "10",
"default": "10"
},
"types": ["Short Period"],
"area_tags": ["Porangahau", "East Coast", "Hikaurangi Trench", "North Island"]
},
{
"code": "BFZ",
"name": "Birch Farm",
"location_codes": {
"HH": "10",
"LH": "10",
"default": "10"
},
"types": ["Broadband"],
"area_tags": ["Birch Farm", "Hikaurangi Trench", "North Island"]
},
{
"code": "CPWZ",
"name": "Castlepoint",
"location_codes": {
"EH": "10",
"default": "10"
},
"types": ["Short Period"],
"area_tags": ["Castlepoint", "Hikaurangi Trench", "North Island"]
},
{
"code": "TMWZ",
"name": "Traveller",
"location_codes": {
"EH": "10",
"default": "10"
},
"types": ["Short Period"],
"area_tags": ["Traveller", "Hikaurangi Trench", "North Island"]
},
{
"code": "WRRZ",
"name": "White Rock",
"location_codes": {
"HH": "10",
"LH": "10",
"default": "10"
},
"types": ["Broadband"],
"area_tags": ["White Rock", "Hikaurangi Trench", "North Island"]
},
{
"code": "CMWZ",
"name": "Cape Campbell",
"location_codes": {
"EH": "10",
"default": "10"
},
"types": ["Short Period"],
"area_tags": ["Cape Campbell", "North Island" "Hikaurangi Trench"]
},
{
"code": "BSWZ",
"name": "Black Birch Station",
"location_codes": {
"HH": "10",
"LH": "10",
"default": "10"
},
"types": ["Broadband"],
"area_tags": ["Black Birch Station", "South Island" "Hikaurangi Trench"]
},
{
"code": "CTZ",
"name": "Chatham Islands",
"location_codes": {
"HH": "10",
"LH": "10",
"default": "10"
},
"types": ["Broadband"],
"area_tags": ["Chatham Islands", "Hikaurangi Trench"]
},
]
# Post processing for convenience
ALL_UNIQUE_CHANNELS = sorted(list(set(sum(SENSOR_TYPE_CHANNELS.values(), []))))
SENSOR_TYPE_CHANNELS["All Channels"] = ALL_UNIQUE_CHANNELS
# Pre built dictionary for quick lookup by station code
STATION_LOOKUP = {station['code']: station for station in ALL_STATIONS_CONFIG}

Binary file not shown.

37
src/data/persistence.py Normal file
View File

@@ -0,0 +1,37 @@
import json
import os
APP_SETTINGS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__))), 'config', 'app_settings.json')
DEFAULT_SETTINGS = {
'theme': 'dark_blue.xml',
'default_duration_minutes': 30,
'last_area_selected': ' --- Select Area ---',
'last_sensor_type': 'Broadband',
'last_staion_selected': 'WEL',
'last_station_selected': 'HHZ',
'last_location_selected': '10',
# Add More Settings here
}
def load_app_settings():
"""Loads application setings from app_settings.json"""
settings = DEFAULT_SETTINGS.copy()
if os.path.exists(APP_SETTINGS_FILE):
try:
with open(APP_SETTINGS_FILE, 'r') as f:
user_settings = json.load(f)
settings.update(user_settings)
except json.JSONDecodeError as e:
print(f"Warning: Could not decode app_settings.json. Using default settings. Error: {e}")
except Exception as e:
print(f"Warning: Error loading app_settings.json. Using default settings. Error: {e}")
return settings
def save_app_settings(settings):
"""Saves application settings to app_settings.json."""
try:
os.makedirs(os.path.dirname(APP_SETTINGS_FILE), exist_ok=True)
with open(APP_SETTINGS_FILE, 'w') as f:
json.dump(seettings, f, indent=4)
except Exception as e:
print(f"ERROR: could not save app_settings.json. Error: {e}")

View File

@@ -4,6 +4,24 @@ from PyQt6.QtWidgets import QApplication
from src.ui.main_window import MainWindow
from qt_material import apply_stylesheet
# --- Debug Imports ---
print("\n--- sys.path DEBUG ---")
print("Current Working Directory:", os.getcwd())
print("Script Directory:", os.path.dirname(os.path.abspath(__file__)))
print("Project Root (calculated):", os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
print("Python sys.path:")
for p in sys.path:
print(f" - {p}")
print("--- END sys.path DEBUG ---\n")
#test direct import
try:
import config.areas_config
print(f"DEBUG: AREAS_OF_INTEREST keys: {config.areas_config.AREAS_OF_INTEREST.keys()}")
except Exception as e:
print(f"DEBUG: Failed to directly import config.areas_config: {e}")
# --- END DEBUGGING INPORTS ---
def main():
app = QApplication(sys.argv)

View File

@@ -1,138 +1,159 @@
from PyQt6.QtWidgets import QMainWindow, QLabel, QVBoxLayout, QWidget, QPushButton, QComboBox, QHBoxLayout
from PyQt6.QtCore import Qt, QTimer
from src.ui.waveform_chart import WaveformChart
from src.data.geonet_api import fetch_waveform_data
from PyQt6.QtWidgets import ( QMainWindow, QLabel, QVBoxLayout, QWidget, QStackedWidget, QMenuBar, QMenu, QStatusBar, QApplication )
from PyQt6.QtCore import Qt, QTimer, Qsize
from src.ui.views.single_waveform_view import SingleWaveformView
from src.ui.views.area_waveforms_view import AreaWavefromsView
from src.ui.dialogs.settings_dialog import SettingsDialog
import config
import sys
# Theme Modes
THEME_MODE_DARK = "dark_blue.xml"
THEME_MODE_LIGHT = "light_blue.xml"
THEME_MODE_SYSTEM = "system" # Not directly supported but Qt-Material
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("NZ EarthQuake Viewer (POC)")
self.setGeometry(100, 100, 1200, 800) # x, y, width, height
self.setMinimumSize(800, 600)
self.setWindowTitle("NZ Earthquake Viewer V0.2 (POC)")
self.setGeometry(100, 100, 1200, 800)
self.seMinimumSize(800, 600)
# Load Pesistent Settings
self.app_settings = load_app_settings()
self._apply_theme(self.app_settings.get('theme', THEME_MODE_DARK))
self.resizeEvent = self._on_resize_event
# --- Central Widget & Layout ---
central_widget = QWidget()
self.setCentralWidget(central_widget)
self.main_layout = QVBoxLayout(central_widget)
self.main_layout.setContentsMargins(10, 10, 10, 10)
self.main_layout.setSpacing(8)
main_layout = QVBoxLayout(central_widget)
main_layout.setContentsMargins(20, 20, 20, 20)
main_layout.setSpacing(15)
controls_layout = QHBoxLayout()
# App Title Label
self.app_title_label = QLabel("NZ Seismic Viewer", self)
self.app_title_label.setObjectName("headerLabel")
main_layout.addWidget(self.app_title_label)
self.main_layout.addWidget(self.app_title_label)
#Station & channel selection
station_label = QLabel("Station:", self)
station_label.setProperty("controlLabel", True)
self.station_selector = QComboBox(self)
self.station_selector.addItems(["WEL", "MAZ", "LTZ", "WRRZ"])
# Manage different views
self.stacked_widget = QStackedWidget(self)
self.main_layout.addWidget(self.stackedWidget)
channel_label = QLabel("Channel:", self)
channel_label.setProperty("controlLabel", True)
self.channel_selector = QComboBox(self)
self.channel_selector.addItems([
"BHZ", "BHN", "BHE", # Common Broadband
"HHZ", "HHN", "HHE", # High-Gain Broadband
"LHZ", "LHN", "LHE", # Low-Gain Broadband, Long Period
# Instantiate views
self.area_waveforms_view = AreaWaveformsView(self)
self.single_waveform_view = SingleWaveformView(self)
"EHZ", "EHN", "EHE", # Short-Period (Popular)
self.stacked_widget.addWidget(self.area_waveforms_view) # Index 0
self.stacked_widget.addWidget(self.single_waveform_view) # Index 1
"HNZ", "HNN", "HNE", # High-Gain, Strong Motion
# Menu Bar
self._create_menu_bar()
# OTHER COMMON Channels that could be encountered in the future
# ONLY UNCOMMENT THE LINE WHEN NEEDED!
# "BMZ", "BNN", "BNE", # Broadband Variants
# "ENZ", "ENN", "ENE", # Short-Period Variants
# "SHZ", "SHN", "SHE", # other Short-Period variants (Less Common in NZ)
])
self.channel_selector.setCurrentText("HHE") # Default
#Duration Selector
duration_label = QLabel("Duration (min):", self)
duration_label.setProperty("controlLabel", True)
self.duration_selector = QComboBox(self)
self.duration_selector.addItems(["5", "15", "20", "30", "60", "90", "120"]) # 60 =1hr | 90 = 1.5hrs | 120 = 2hrs
self.duration_selector.setCurrentText("30") # Default to 30 mins
#Location Code Selector (GeoNet requires this to not be '--')
location_label = QLabel("Location:", self)
location_label.setProperty("controlLabel", True)
self.location_selector = QComboBox(self)
self.location_selector.addItems(["--", "00", "01", "02", "03", "10", "20", "22", "30"]) # DOUBLE CHECK AS GET INTO THE MULTIPLE GRAPHS
self.location_selector.setCurrentText("10") # most responsive I've found on swarm - BC
# Fetch Button
self.fetch_button = QPushButton("Fetch waveform", self)
self.fetch_button.clicked.connect(self.fetch_and_plot_waveform)
# LAYOUT
controls_layout.addWidget(station_label)
controls_layout.addWidget(self.station_selector)
controls_layout.addSpacing(15)
controls_layout.addWidget(channel_label)
controls_layout.addWidget(self.channel_selector)
controls_layout.addSpacing(15)
controls_layout.addWidget(location_label)
controls_layout.addWidget(self.location_selector)
controls_layout.addSpacing(15)
controls_layout.addWidget(duration_label)
controls_layout.addWidget(self.duration_selector)
controls_layout.addStretch(1)
controls_layout.addWidget(self.fetch_button)
main_layout.addLayout(controls_layout)
self.status_label = QLabel("Ready.", self)
# Status Bar
self.status_bar = QStatusBar(self)
self.setStatusBar(self.status_bar)
self.status_label = QLabel("Ready.")
self.status_label.setObjectName("statusLabel")
self.status_abr.addWidget(self.status_label, 1)
main_layout.addWidget(self.status_label)
self.waveform_chart = WaveformChart(self)
self.waveform_chart.setObjectName("waveformChartContainer")
main_layout.addWidget(self.waveform_chart)
# Intiial View and Data Load
self.show_area_waveforms_view()
# Polling Timer
self.polling_timer = QTimer(self)
self.polling_timer.setInterval(15 * 1000) # 15 seconds
self.polling_timer.timeout.connect(self.fetch_and_plot_waveform)
self.polling_timer.setInterval(15 * 1000)
self.polling_timer.timeout.connect(self._refresh_current_view)
self.polling_timer.start()
self.fetch_and_plot_waveform()
# Menu bar creation
def _create_menu_bar(self):
menu_bar = self.menuBar()
def set_status(self, message, is_error=False):
self.status_label.setText(message)
# View Menu
view_menu = menu_bar.addMenu("&View")
area_view_action = view_menu.addAction("Area Waveforms")
single_view_action = view_menu.addAction("Single Waveform")
if is_error:
self.status_label.setStyleSheet("color: #EF4444; font-weight: bold;")
else:
self.status_label.setStyleSheet("color: #6B7280; font-weight: normal;")
area_view_action.triggered.connect(self.show_area_waveforms_view)
single_view_action.triggered.connect(self.show_single_waveform_view)
def fetch_and_plot_waveform(self):
station = self.station_selector.currentText()
channel = self.channel_selector.currentText()
location_code = self.location_selector.currentText()
self.set_status(f"Fetching data for {station}-{channel}...", is_error=False)
self.fetch_button.setEnabled(False)
# Options Menu
options_menu = menu_bar.addMenu("&Options")
settings_action = options_menu.addAction("Settings")
settings_action.triggered.connect(self.show_settings_dialog)
try:
traces = fetch_waveform_data(station, channel, location=location_code, duration_seconds=1800)
# Help Menu
help_menu = menu_bar.addMenu("&Help")
about_action = help_menu.addAction("About")
about_action.triggered.connect(self.show_about_dialog)
if traces:
selected_trace = next((t for t in traces if t['channel'] == channel), None)
if selected_trace and selected_trace['points']:
self.waveform_chart.plot_waveform(selected_trace['points'], f"Live Waveform: {station}-{channel}")
self.set_status(f"Data for {station}-{channel} updated. Last sample at {selected_trace['points'][-1]['x'].strftime('%H:%M:%S')}", is_error=False)
else:
self.set_status(f"No waveform points found for {station}-{channel} in this window", is_error=True)
self.waveform_chart.plot_waveform([], f"No data: {station}-{channel}")
# view Management Methods
def show_area_waveforms_view(self):
self.stacked_widget.setCurrentWidget(self.area_waveforms_view)
self.area_waveforms_view.load_selected_area_waveforms()
self.area_waveforms_view.start_polling() # may need self._refresh_current_view() if start_polling() not work
self.set_status("Showing Area Waveforms.")
self.current_view_mode = "area"
def show_single_waveform_view(self):
self.stacked_widget.setCurrentWidget(self.single_waveform_view)
self.single_waveform_view.start_polling()
self.set_status("Showing Single Waveform. Select Parameters and click 'fetch' button")
self.current_view_mode = "single"
def show_settigns_dialog(self):
settings_dialog = sSettignsDialog(self.app_settings, self)
settings_dialog.settings_changed.connect(self._handle_settings_changed)
settings_dialog.exec()
def _handle_settings_changed(self, new_settings):
self.app_settigns.update(new_settings)
save_app_settings(self.app_settigns)
# Apply new theme if it changed
if 'theme' in new_settings:
self._apply_theme(new_settings['theme'])
def _apply_theme(self, theme_choice):
from qt_material import apply_stylesheet
current_app = QApplication.instance()
if not current_app: return
if theme_choic == THEME_MODE_SYSTEM:
# qt-material doesn't have a 'system' theme
# Try to detect the system light / dark mode and set the theme acordingly.
# default to dark mode if system is not detected.
print("System theme detection not fully implemented, defaulting to Dark Theme.")
apply_stylesheet(current_app, theme=THEME_MODE_DARK)
elif theme_choice == THEME_MODE_LIGHT:
apply_stylesheet(current_app, theme=THEME_MODE_LIGHT)
else: # Defaults to dark mode if no specific match
apply_stylesheet(current_app, theme=THEME_MODE_DARK)
print(f"Applied theme: {theme_choice}")
def _refresh_current_view(self)
""" Refreshes the currently active view's data."""
if self.current_view_mode == "area":
self.area_waveforms_view.load_area_data()
elif self.current_view_mode == "single":
self.single_waveform_view.load_single_data()
# other views added here following the above format
def _on_resize_event(self, event):
"""Overrides the resize event to trigger re layout in active view"""
if self.current_view_mode == "area":
self.area_waveforms_view.hanndle_resize()
elif self.current_view_mode == "single":
self.single_waveform_view.handle_resize()
QMainWindow.resizeEvent(self, event)
# Status forwarding for "sub" views
def update_main_status(self, message, is_error=False):
self.status_label.setText(message)
if is_error:
self.status_label.setStlyeSheet("color: #EF4444; font-weight: bold;")
else:
self.set_status(f"No Data returned from GeoNet for {station}-{channel}.", is_error=True)
self.waveform_chart.plot_waveform([], f"No data: {station}-{channel}")
except Exception as e:
self.set_status(f"Error fetching waveform: {e}", is_error=True)
print(f"Detailed error in main_window.py: {e}", file=sys.stderr)
finally:
self.fetch_button.setEnabled(True)
self.status_label.setStyleSheet("color: #A0AEC0; font-weight: normal;")

View File

@@ -0,0 +1,39 @@
from PyQt6.QtWidgets import ( QWidget, QLabel, QVBoxLayout, QPushButon, QComboBox, QHBoxLayout, QGridLayout )
from PyQt6.QtCore import Qt
from src.ui.waveform_chart import WaveformChart
from src.ui.geonet_api import fetch_waveform_data
import config
import sys
import math
class AreaWaveformsView(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.parent_window = parent
self.main_layout = QVBoxLayout(self)
self.main_layout.setContentsMargins(0, 0, 0, 0)
self.main_layout.setSpacing(8)
# Area Selection Controls Row 1
area_controls_layout = QHBoxLayout()
area_controls_layout.setSpacing(5)
area_label = QLabel("Area:", self)
area_label.setProperty("controlLabel", True)
area_controls_layout.addWidget(area_label)
self.area_selector = QComboBox(self)
self.area_selector.addItems(["-- Select Area --"] + list(config.AREAS_OF_INTEREST.keys()))
controls_controls_layout.addWidget(self.area_selector)
self.load_area_button = QPushButton("Fetch Waveforms", self)
self.load_area_button.setObjectName("loadAreaButton")
self.load_area_button.clicked.connect(self.load_area_data)
area_controls_layout.addWidget(self.load_area_button)
area_controls_layout.addStretch(1)
self.main_layout.addLayout(area_controls_layout)
# sensor Type Selector - Row 2

View File

View File

View File

View File

@@ -6,7 +6,7 @@ import matplotlib.dates as mdates
from src.utils.mpl_styles import apply_mpl_styles
class WaveformChart(QWidget):
def __init__(self, parent=None):
def __init__(self, parent=None, figsize=(10, 4)):
super().__init__(parent)
self.layout = QVBoxLayout(self)
self.layout.setContentsMargins(0,0,0,0)
@@ -16,11 +16,12 @@ class WaveformChart(QWidget):
apply_mpl_styles()
self.title_label = QLabel("Waveform Chart", self)
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.title_label.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignLeft)
self.title_label.setObjectName("chartTitleLabel")
self.layout.addWidget(self.title_label)
# Matplot
self.fig = Figure(figsize=(10,4), dpi=100)
self.fig = Figure(figsize=figsize, dpi=100)
self.canvas = FigureCanvas(self.fig)
self.layout.addWidget(self.canvas)
@@ -36,6 +37,12 @@ class WaveformChart(QWidget):
def plot_waveform(self, points, title=""):
self.title_label.setText(title)
self.ax.clear()
self.ax.set_xlabel("Time")
self.ax.set_ylabel("Amplitude")
self.ax.tick_params(axis='x')
self.ax.tick_params(axis='y')
self.ax.xaxis.set_major_formatter(self.formatter)
self.ax.tick_params(axis='x', rotation=45)
if points:
x_data = [p['x'] for p in points]

View File

View File