From 4c25b55ad31e7f7271e60dc9557a14c2de3d6fa4 Mon Sep 17 00:00:00 2001 From: Prerna2024-cyber Date: Thu, 21 Aug 2025 15:42:49 +0530 Subject: [PATCH 1/2] Add desktop app UI with new widgets --- src/osbridgelcca/desktop_app/ui/.gitignore | 84 +++ src/osbridgelcca/desktop_app/ui/app.py | 21 + src/osbridgelcca/desktop_app/ui/input_form.py | 1 - .../desktop_app/ui/main_template.py | 440 ++++++++++++++ .../desktop_app/ui/main_window.py | 1 - .../desktop_app/ui/resources/AlataRegular.ttf | Bin 0 -> 348308 bytes .../desktop_app/ui/resources/Group 4.svg | 80 +++ .../desktop_app/ui/resources/arrow_down.png | Bin 0 -> 3992 bytes .../ui/resources/arrow_down_selected.png | Bin 0 -> 4267 bytes .../desktop_app/ui/resources/close.png | Bin 0 -> 6536 bytes .../desktop_app/ui/resources/contact.svg | 6 + .../ui/resources/country_arrow.png | Bin 0 -> 216 bytes .../desktop_app/ui/resources/create_copy.svg | 4 + .../desktop_app/ui/resources/edit_button.png | Bin 0 -> 11726 bytes .../desktop_app/ui/resources/export.svg | 11 + .../desktop_app/ui/resources/feedback.svg | 3 + .../desktop_app/ui/resources/file_button.png | Bin 0 -> 1402 bytes .../desktop_app/ui/resources/info.svg | 3 + .../ui/resources/join_community.svg | 8 + .../desktop_app/ui/resources/new.svg | 3 + .../desktop_app/ui/resources/open.svg | 4 + .../ui/resources/play-button-arrowhead.png | Bin 0 -> 6074 bytes .../ui/resources/play-button-selected.png | Bin 0 -> 4537 bytes .../desktop_app/ui/resources/print.svg | 3 + .../desktop_app/ui/resources/rename.svg | 4 + .../desktop_app/ui/resources/save.svg | 3 + .../desktop_app/ui/resources/save_as.svg | 3 + .../desktop_app/ui/resources/save_button.png | Bin 0 -> 8849 bytes .../desktop_app/ui/resources/tab_close.png | Bin 0 -> 9732 bytes .../ui/resources/version_history.svg | 3 + .../ui/resources/video_tutorial.svg | 4 + .../desktop_app/ui/resources/window_close.svg | 1 + .../ui/resources/window_maximize.svg | 4 + .../ui/resources/window_minimize.svg | 1 + .../ui/resources/window_restore.svg | 3 + .../desktop_app/ui/results_view.py | 1 - .../ui/widgets/bridge_and_traffic_data.py | 422 ++++++++++++++ .../carbon_emission_cost_data.py | 299 ++++++++++ .../carbon_emission_data.py | 521 +++++++++++++++++ .../widgets/demolition_and_recycling_data.py | 308 ++++++++++ .../desktop_app/ui/widgets/financial_data.py | 322 +++++++++++ .../ui/widgets/maintenance_repair_data.py | 352 ++++++++++++ .../ui/widgets/project_details_left_widget.py | 375 ++++++++++++ .../widgets/project_details_right_widget.py | 507 +++++++++++++++++ .../auxiliary_works_widget.py | 534 +++++++++++++++++ .../structure_works_data/foundation_widget.py | 534 +++++++++++++++++ .../sub_structure_widget.py | 535 ++++++++++++++++++ .../super_structure_widget.py | 534 +++++++++++++++++ .../desktop_app/ui/widgets/title_bar.py | 135 +++++ .../ui/widgets/tutorial_widget_left.py | 282 +++++++++ 50 files changed, 6356 insertions(+), 3 deletions(-) create mode 100644 src/osbridgelcca/desktop_app/ui/.gitignore create mode 100644 src/osbridgelcca/desktop_app/ui/app.py delete mode 100644 src/osbridgelcca/desktop_app/ui/input_form.py create mode 100644 src/osbridgelcca/desktop_app/ui/main_template.py delete mode 100644 src/osbridgelcca/desktop_app/ui/main_window.py create mode 100644 src/osbridgelcca/desktop_app/ui/resources/AlataRegular.ttf create mode 100644 src/osbridgelcca/desktop_app/ui/resources/Group 4.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/arrow_down.png create mode 100644 src/osbridgelcca/desktop_app/ui/resources/arrow_down_selected.png create mode 100644 src/osbridgelcca/desktop_app/ui/resources/close.png create mode 100644 src/osbridgelcca/desktop_app/ui/resources/contact.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/country_arrow.png create mode 100644 src/osbridgelcca/desktop_app/ui/resources/create_copy.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/edit_button.png create mode 100644 src/osbridgelcca/desktop_app/ui/resources/export.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/feedback.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/file_button.png create mode 100644 src/osbridgelcca/desktop_app/ui/resources/info.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/join_community.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/new.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/open.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/play-button-arrowhead.png create mode 100644 src/osbridgelcca/desktop_app/ui/resources/play-button-selected.png create mode 100644 src/osbridgelcca/desktop_app/ui/resources/print.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/rename.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/save.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/save_as.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/save_button.png create mode 100644 src/osbridgelcca/desktop_app/ui/resources/tab_close.png create mode 100644 src/osbridgelcca/desktop_app/ui/resources/version_history.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/video_tutorial.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/window_close.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/window_maximize.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/window_minimize.svg create mode 100644 src/osbridgelcca/desktop_app/ui/resources/window_restore.svg delete mode 100644 src/osbridgelcca/desktop_app/ui/results_view.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/bridge_and_traffic_data.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/carbon_emission_data/carbon_emission_cost_data.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/carbon_emission_data/carbon_emission_data.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/demolition_and_recycling_data.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/financial_data.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/maintenance_repair_data.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/project_details_left_widget.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/project_details_right_widget.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/auxiliary_works_widget.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/foundation_widget.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/sub_structure_widget.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/super_structure_widget.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/title_bar.py create mode 100644 src/osbridgelcca/desktop_app/ui/widgets/tutorial_widget_left.py diff --git a/src/osbridgelcca/desktop_app/ui/.gitignore b/src/osbridgelcca/desktop_app/ui/.gitignore new file mode 100644 index 0000000..9006474 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/.gitignore @@ -0,0 +1,84 @@ +# Python bytecode and cache +__pycache__/ +*.py[cod] +*$py.class + +# Virtual environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Sphinx documentation +docs/_build/ + +# Jupyter Notebook +.ipynb_checkpoints + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Qt Creator & QML +*.user +*.pro.user +*.qbs.user +*.qmlproject.user +*.autosave + +# MacOS +.DS_Store + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini + +# Misc +*.log diff --git a/src/osbridgelcca/desktop_app/ui/app.py b/src/osbridgelcca/desktop_app/ui/app.py new file mode 100644 index 0000000..3a3fa13 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/app.py @@ -0,0 +1,21 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt +from main_template import UiMainWindow +import sys + +class MyMainWindow(QMainWindow): + """ + The main application window that uses a custom title bar. + """ + def __init__(self): + super().__init__() + self.ui = UiMainWindow() + self.ui.setupUi(self) + +if __name__ == "__main__": + # Enable mnemonics (underlined shortcuts) for menu items + QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) + app = QApplication(sys.argv) + window = MyMainWindow() + window.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/input_form.py b/src/osbridgelcca/desktop_app/ui/input_form.py deleted file mode 100644 index 5516c75..0000000 --- a/src/osbridgelcca/desktop_app/ui/input_form.py +++ /dev/null @@ -1 +0,0 @@ -# Placeholder for input form UI diff --git a/src/osbridgelcca/desktop_app/ui/main_template.py b/src/osbridgelcca/desktop_app/ui/main_template.py new file mode 100644 index 0000000..8c20d02 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/main_template.py @@ -0,0 +1,440 @@ +""" +: Prerna Praveen Vidyarthi +: FOSSEE Summer Fellowship 2025 +: https://github.com/prerna2024-cyber +: vidyarthiprerna637@gmail.com +""" +from PySide6.QtCore import (QSize, Qt, QPropertyAnimation, QEasingCurve) +from PySide6.QtGui import (QAction,QFont, QFontDatabase, QIcon) +from PySide6.QtWidgets import (QApplication, QHBoxLayout, QTextEdit, QScrollArea, QSpacerItem, QSizePolicy, + QMenu, QMenuBar, QPushButton, QWidget, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QComboBox) + +from widgets.title_bar import CustomTitleBar +from widgets.project_details_right_widget import ProjectDetailsWidget +from widgets.tutorial_widget_left import TutorialWidget +from widgets.structure_works_data.foundation_widget import Foundation +from widgets.structure_works_data.super_structure_widget import SuperStructure +from widgets.structure_works_data.sub_structure_widget import SubStructure +from widgets.structure_works_data.auxiliary_works_widget import AuxiliaryWorks +from widgets.financial_data import FinancialData +from widgets.carbon_emission_data.carbon_emission_data import CarbonEmissionData +from widgets.carbon_emission_data.carbon_emission_cost_data import CarbonEmissionCostData +from widgets.bridge_and_traffic_data import BridgeAndTrafficData +from widgets.maintenance_repair_data import MaintenanceRepairData +from widgets.demolition_and_recycling_data import DemolitionAndRecyclingData +from widgets.project_details_left_widget import ProjectDetailsLeft + +from PySide6.QtWidgets import QStackedWidget + +class UiMainWindow(object): + def setupUi(self, MainWindow): + if not MainWindow.objectName(): + MainWindow.setObjectName(u"MainWindow") + + # Load and set the Alata Regular font + font_id = QFontDatabase.addApplicationFont("resources/AlataRegular.ttf") + if font_id != -1: + font_family = QFontDatabase.applicationFontFamilies(font_id)[0] + app_font = QFont(font_family, 10) + QApplication.setFont(app_font) + else: + print("Failed to load Alata font") + + MainWindow.setWindowTitle("Custom Title Bar App") + MainWindow.setWindowFlags(Qt.WindowType.FramelessWindowHint) + screen = QApplication.primaryScreen().geometry() + x = (screen.width()*1//8) + y = (screen.height()*1//8) + MainWindow.setGeometry(x, y, screen.width()*3//4, screen.height()*3//4) + + # Set window stylesheet + MainWindow.setStyleSheet(""" + QMainWindow { + border: none; + } + QMenuBar { + background-color: #FAFAFA; + border-bottom: 1px solid #d0d0d0; + border-left: 1px solid #285A23; + border-right: 1px solid #285A23; + } + QMenuBar::item { + padding: 4px 10px; + background-color: transparent; + border-bottom: 2px solid #FAFAFA; + margin: 2px; + } + QMenuBar::item:selected { + border-bottom: 2px solid #806C6C; + } + QMenu { + background-color: #f0f0f0; + border: 1px solid #d0d0d0; + } + QMenu::item { + padding: 4px 4px 4px 12px; + text-align: left; + color: #514E4E; + } + QMenu::item:selected { + background-color: #e0e0e0; + } + QMenu::separator { + height: 1px; + background-color: #d0d0d0; + margin: 4px 0px; + } + QMenu::icon { + padding-left: 5px; + width: 16px; + height: 16px; + } + QMenu::indicator { + width: 16px; + height: 16px; + } + QMenu::right-arrow { + margin-right: 5px; + } + """) + + # Create a central widget and main layout for the window + self.central_widget = QWidget() + self.central_widget.setStyleSheet("border: none;") + self.central_widget.setObjectName("central_widget") + MainWindow.setCentralWidget(self.central_widget) + main_layout = QVBoxLayout(self.central_widget) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + # Add the custom title bar to the top of the main layout + self.title_bar = CustomTitleBar(MainWindow) + main_layout.addWidget(self.title_bar) + + # Create and add menu bar directly to the central widget's main_layout + # DO NOT use MainWindow.setMenuBar() when using FramelessWindowHint + self.menubar = QMenuBar() + self.menubar.setObjectName(u"menubar") + main_layout.addWidget(self.menubar) # This is the crucial line for custom frame + + # Create menus + self.menuFile = QMenu("&File", self.menubar) + self.menuHome = QMenu("&Home", self.menubar) + self.menuReport = QMenu("&Report", self.menubar) + self.menuHelp = QMenu("&Help", self.menubar) + + # Add menus to menubar + self.menubar.addMenu(self.menuFile) + self.menubar.addMenu(self.menuHome) + self.menubar.addMenu(self.menuReport) + self.menubar.addMenu(self.menuHelp) + + # Create and add actions to File menu with icons + self.actionNew = QAction(QIcon("resources/new.svg"), "New", MainWindow) + self.actionOpen = QAction(QIcon("resources/open.svg"), "Open", MainWindow) + self.actionSave = QAction(QIcon("resources/save.svg"), "Save", MainWindow) + self.actionSaveAs = QAction(QIcon("resources/save_as.svg"), "Save As...", MainWindow) + self.actionCreateCopy = QAction(QIcon("resources/create_copy.svg"), "Create a Copy", MainWindow) + self.actionPrint = QAction(QIcon("resources/print.svg"), "Print", MainWindow) + self.actionRename = QAction(QIcon("resources/rename.svg"), "Rename", MainWindow) + self.actionExport = QAction(QIcon("resources/export.svg"), "Export", MainWindow) + self.actionVersionHistory = QAction(QIcon("resources/version_history.svg"), "Version History", MainWindow) + self.actionInfo = QAction(QIcon("resources/info.svg"), "Info", MainWindow) + + # Add actions to File menu + self.menuFile.addAction(self.actionNew) + self.menuFile.addAction(self.actionOpen) + self.menuFile.addSeparator() + self.menuFile.addAction(self.actionSave) + self.menuFile.addAction(self.actionSaveAs) + self.menuFile.addAction(self.actionCreateCopy) + self.menuFile.addAction(self.actionPrint) + self.menuFile.addSeparator() + self.menuFile.addAction(self.actionRename) + self.menuFile.addAction(self.actionExport) + self.menuFile.addAction(self.actionVersionHistory) + self.menuFile.addAction(self.actionInfo) + + # Create and add actions to Help menu with icons + self.actionDocumentation = QAction(QIcon("resources/contact.svg"), "Contact us", MainWindow) + self.actionFeedback = QAction(QIcon("resources/feedback.svg"), "Feedback", MainWindow) + self.actionVideoTutorial = QAction(QIcon("resources/video_tutorial.svg"), "Video Tutorials", MainWindow) + self.actionJoinCommunity = QAction(QIcon("resources/join_community.svg"), "Join our Community", MainWindow) + + # Add actions to Help menu + self.menuHelp.addAction(self.actionDocumentation) + self.menuHelp.addAction(self.actionFeedback) + self.menuHelp.addSeparator() + + self.menuHelp.addAction(self.actionVideoTutorial) + self.menuHelp.addAction(self.actionJoinCommunity) + + # Main content area below the menubar + self.main_content_area = QWidget() + # This widget needs to stretch to fill the remaining space + main_layout.addWidget(self.main_content_area, 1) # Add stretch factor of 1 + self.main_content_area.setObjectName("main_content_area") + self.main_content_area.setStyleSheet(""" + #main_content_area { + background-color: #FAFAFA; + border: 1px solid #285A23; + border-top: 1px solid #BBBBBB; + + } + QLabel { + color: #9F8888; + font-size: 14px; + border: none; + padding: 5px; + } + QPushButton { + background-color: #EDEDED; + border: 1px solid #d0d0d0; + padding: 6px 16px; + color: #514E4E; + } + QPushButton:hover { + background: qlineargradient( + x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #BBBBBB, + stop: 0.26 #E8E8E8, + stop: 1 #EDEDED + ); + border-color: #806C6C; + } + QPushButton:pressed { + background-color: #d0d0d0; + } + """) + + # Main vertical layout for content inside main_content_area + content_layout = QVBoxLayout(self.main_content_area) + content_layout.setContentsMargins(0,0,0,0) + content_layout.setSpacing(0) + + # Create horizontal layout for buttons + button_layout = QHBoxLayout() + button_layout.setSpacing(0) # Add spacing between all buttons + button_layout.setContentsMargins(5, 5, 5, 5) # Add some margin around the layout + + # ------------------------------------------------------------ + + # Add buttons to left container + self.edit_button = QPushButton() + self.edit_button.setObjectName(u"edit_button") + self.edit_button.setIcon(QIcon("resources/edit_button.png")) + self.edit_button.setFixedSize(60, 30) + self.edit_button.setIconSize(QSize(25, 25)) + self.edit_button.setStyleSheet(""" + QPushButton { + border-radius: 5px; + background-color: #EDEDED; + border: 1px solid #BBBBBB; + margin-right: 20px; + margin-left: 10px; + } + QPushButton:hover { + background-color: #FAFAFA; + border: 1px solid #888888; + } + """) + button_layout.addWidget(self.edit_button) + + self.file_button= QPushButton() + self.file_button.setObjectName(u"file_button") + self.file_button.setFixedSize(50, 30) + self.file_button.setIcon(QIcon("resources/file_button.png")) + self.file_button.setIconSize(QSize(30, 30)) + self.file_button.setStyleSheet(""" + QPushButton { + border-radius: 5px; + background-color: #EDEDED; + border: 1px solid #BBBBBB; + margin-right: 20px; + } + QPushButton:hover { + border: 1px solid #888888; + background-color: #FAFAFA; + } + """) + button_layout.addWidget(self.file_button) + + # Create save menu button + self.save_button = QPushButton() + self.save_button.setObjectName(u"save_button") + self.save_button.setFixedSize(40, 30) + self.save_button.setIcon(QIcon("resources/save_button.png")) + self.save_button.setIconSize(QSize(25, 25)) + self.save_button.setStyleSheet(""" + QPushButton { + border-radius: 5px; + background-color: #EDEDED; + border: 1px solid #BBBBBB; + } + QPushButton:hover { + background-color: #FAFAFA; + border: 1px solid #888888; + } + QPushButton::menu-indicator { + image: url(resources/arrow_down.png); + width: 8px; + height: 30px; + subcontrol-position: right center; + subcontrol-origin: padding; + margin-right: 2px; + margin-left: 2px; + border-left: 1px solid #BBBBBB; + } + """) + + # Create save menu + self.save_menu = QMenu(self.save_button) + self.save_menu.setStyleSheet(""" + QMenu { + background-color: #EDEDED; + border: 1px solid #BBBBBB; + padding: 2px; + color: #9F8888; + } + + QMenu::item { + padding: 2px 2px; + margin: 0px; + border: none; + min-height: 18px; + font-size: 11px; + } + + QMenu::item:selected { + background-color: #FAFAFA; + } + + QMenu::icon { + width: 0px; + } + """) + self.save_action = QAction("Save", self.save_menu) + self.save_as_action = QAction("Save As", self.save_menu) + self.save_menu.addAction(self.save_action) + self.save_menu.addAction(self.save_as_action) + self.save_button.setMenu(self.save_menu) + + button_layout.addWidget(self.save_button) + + # ------------------------------------------------------------ + + button_layout.addStretch() + + # Add label + self.windows = QLabel("Windows:") + button_layout.addWidget(self.windows) + + # add buttons to the right + self.tutorial_tab = QPushButton("Tutorials") + self.project_details_tab = QPushButton("Project Details") + self.results_tab = QPushButton("Results") + self.compare = QPushButton("Compare") + + # Add buttons to layout + button_layout.addWidget(self.tutorial_tab) + button_layout.addWidget(self.project_details_tab) + button_layout.addWidget(self.results_tab) + button_layout.addWidget(self.compare) + + button_layout.addStretch() + + # Add the button layout to the main content layout + content_layout.addLayout(button_layout) + + # ------------------------------------------------------------ + body_widget = QWidget() + body_widget.setObjectName("body_widget") + body_widget.setStyleSheet(""" + #body_widget { + border-top: 1px solid #d0d0d0; + } + """) + # ------------------------------------------------------------ + body_layout = QHBoxLayout(body_widget) + body_layout.setSpacing(40) + + # Placeholders for dynamic widgets + self.left_panel_placeholder = QWidget() + self.left_panel_placeholder.setLayout(QVBoxLayout()) + self.right_panel_placeholder = QWidget() + self.right_panel_placeholder.setLayout(QVBoxLayout()) + body_layout.addWidget(self.left_panel_placeholder, 1) + body_layout.addWidget(self.right_panel_placeholder, 4) + content_layout.addWidget(body_widget) + + # Store references to current widgets + self.current_left_widget = None + self.current_right_widget = None + + # Button click handlers + def show_tutorial_widget(): + if self.current_left_widget: + self.left_panel_placeholder.layout().removeWidget(self.current_left_widget) + self.current_left_widget.setParent(None) + self.current_left_widget = TutorialWidget() + self.left_panel_placeholder.layout().addWidget(self.current_left_widget) + self.current_left_widget.closed.connect(lambda: self.remove_left_widget()) + + def show_project_details_widget(widget_name=None): + if self.current_right_widget: + self.right_panel_placeholder.layout().removeWidget(self.current_right_widget) + self.current_right_widget.setParent(None) + self.widget_map = { + "Structure Works Data": Foundation, + "Foundation": Foundation, + "Super-Structure": SuperStructure, + "Sub-Structure": SubStructure, + "Miscellaneous": AuxiliaryWorks, + "Financial Data": FinancialData, + "Carbon Emission Data": CarbonEmissionData, + "Carbon Emission Cost Data": CarbonEmissionCostData, + "Bridge and Traffic Data": BridgeAndTrafficData, + "Maintenance and Repair": MaintenanceRepairData, + "Demolition and Recycling": DemolitionAndRecyclingData + } + if widget_name and widget_name in self.widget_map: + self.current_right_widget = self.widget_map[widget_name]() + self.remove_left_widget() + self.detail_stack = QStackedWidget() + self.current_left_widget = ProjectDetailsLeft(self.widget_map, parent=self) + self.current_left_widget.handle_button_selection(button_name=widget_name) + self.left_panel_placeholder.layout().addWidget(self.current_left_widget) + self.current_left_widget.closed.connect(lambda: self.remove_left_widget()) + else: + self.current_right_widget = ProjectDetailsWidget() + self.right_panel_placeholder.layout().addWidget(self.current_right_widget) + self.current_right_widget.closed.connect(lambda: self.remove_right_widget()) + # Connect param_buttons if present + if hasattr(self.current_right_widget, 'param_buttons'): + for btn in self.current_right_widget.param_buttons: + btn.clicked.connect(lambda checked, b=btn: show_project_details_widget(b.text().strip())) + def remove_right_widget(): + if self.current_right_widget: + self.right_panel_placeholder.layout().removeWidget(self.current_right_widget) + self.current_right_widget.setParent(None) + self.current_right_widget = None + self.remove_right_widget = remove_right_widget + + def remove_left_widget(): + if self.current_left_widget: + self.left_panel_placeholder.layout().removeWidget(self.current_left_widget) + self.current_left_widget.setParent(None) + self.current_left_widget = None + self.remove_left_widget = remove_left_widget + + self.tutorial_tab.clicked.connect(show_tutorial_widget) + self.project_details_tab.clicked.connect(lambda: show_project_details_widget()) + + def show_project_detail_widgets(self, widget_name): + if self.current_right_widget: + self.right_panel_placeholder.layout().removeWidget(self.current_right_widget) + self.current_right_widget.setParent(None) + if widget_name and widget_name in self.widget_map: + self.current_right_widget = self.widget_map[widget_name]() + self.right_panel_placeholder.layout().addWidget(self.current_right_widget) + diff --git a/src/osbridgelcca/desktop_app/ui/main_window.py b/src/osbridgelcca/desktop_app/ui/main_window.py deleted file mode 100644 index 27ae599..0000000 --- a/src/osbridgelcca/desktop_app/ui/main_window.py +++ /dev/null @@ -1 +0,0 @@ -# Placeholder for main window UI diff --git a/src/osbridgelcca/desktop_app/ui/resources/AlataRegular.ttf b/src/osbridgelcca/desktop_app/ui/resources/AlataRegular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8533e4deb1bea99927c2b98564d70a7fe5fee1e4 GIT binary patch literal 348308 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 literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/ui/resources/Group 4.svg b/src/osbridgelcca/desktop_app/ui/resources/Group 4.svg new file mode 100644 index 0000000..cf7290c --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/Group 4.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/arrow_down.png b/src/osbridgelcca/desktop_app/ui/resources/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..e4d726673d77e74c768a594ec717ae65f7c5b92c GIT binary patch literal 3992 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 LzpH!){rrCc2U1_u literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/ui/resources/arrow_down_selected.png b/src/osbridgelcca/desktop_app/ui/resources/arrow_down_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..9a4ad609dd82e9b974cc7131e2b05f79f958535b GIT binary patch literal 4267 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 literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/ui/resources/contact.svg b/src/osbridgelcca/desktop_app/ui/resources/contact.svg new file mode 100644 index 0000000..5b860be --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/contact.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/country_arrow.png b/src/osbridgelcca/desktop_app/ui/resources/country_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..f43b06bf05465d1f96cfbccc1151afef45bc42bf GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|CV9FzhIn|t z4RYjbP~b@p|N2Jwq3;Km^RG(N+=P-6W~&@Iy+dHqrFCx)@Y)&7xIeRD#s=3b@_{{t zObutN_ZR4BE|nF$E^sLFu&H&0-8qJWEiNV!+fw=*oc;Qa=&lmXJQ>QwqQ&{LwQa3G literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/ui/resources/create_copy.svg b/src/osbridgelcca/desktop_app/ui/resources/create_copy.svg new file mode 100644 index 0000000..7a1c424 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/create_copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/edit_button.png b/src/osbridgelcca/desktop_app/ui/resources/edit_button.png new file mode 100644 index 0000000000000000000000000000000000000000..6c30c3f6c47bd739ee53753e8ca8b1746e0736f0 GIT binary patch literal 11726 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 + + + + + + + + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/feedback.svg b/src/osbridgelcca/desktop_app/ui/resources/feedback.svg new file mode 100644 index 0000000..d164afe --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/feedback.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/file_button.png b/src/osbridgelcca/desktop_app/ui/resources/file_button.png new file mode 100644 index 0000000000000000000000000000000000000000..a6b579f2f748ebc18b4f1b8a4e4d6ed0dba70722 GIT binary patch literal 1402 zcmV-=1%>*FP)U43UqJg8s4Hw;bXc*F zh7yfi%FYc*_a<^#y0O!swX~gkyr)T3Na@R+nKREY-%mo)uz)Up^PJ~-PH(RW00000 z000000000000000000000000006-%n%0wfR1?gNYNDai_eH9C8N=0Cz&&SW0z+5$A zhtlt;CSh4)#muJvZftCFud*yNafuLBqoIL`zPLFY1i_^HQhYI{lsv7B5u9uq7MO#V zC$5UI-$-M`iOBVM;>&5lwuI6E(UtTFGE$PpDWsp23&oSCW$xjR4+LB0JuuP8%_s=X z?exo<_A1%f;o`|t^K%b>`bDrMfp`{lgg3-z&$79%T`wir60bds8<~UL(wyj1PL`4r zG{T$nT4BO)X;^eBm|S>wbq9nEc@2yWlOa*c?SRticSEotzdejK+CRb94qo}5&}_hO zg)z1XN`5j*XkN_wl++wtXvQD8eUac9ZGsnx(ofUfgO|t4?mkNDEWJycO>FW&`}W!E zSbgBi&61JeqF^I&TKG%9OV zkX%>K>Yr+~wY6V7`O*2);ud=*iIWw3Z|eL`?^|I4Q$t}}yA19>BD?k-max5DYAn6D zUA?@G(py{!LNju9>W^3|$=DC)c9z5wj-(nH+;>FY{NQ8Redu^S!m>V_h9?Ln&rQvF zo1zFzm0VRhH1xjgJ@&T0pghM~J1U-VBvm2T*(0x?cu%@|TjwV`sW2J2%F=3KaR^LC zN8cH4xv)3{CWF_G4z*-h90IdVuO50scI`hbt%|^GgA0orn_C%E(iXX6sFf?r8UnKk zx8mQOR+ir4fS?Hj)0N)MxQ4T%vs>z^Bfa~{_YKzl|7kW&5tusP(O?_Y2gcpec)g#& zhExZJsYD;OV>MyXNo8RAcSnRpDV2e7V=Q!17Z_Jva$StFTImbSo=BjVy1=ke)lF4k zs{P4&)St4z(7R}-Dli?LR2c23FxVDVf$7c{1-hvUOqk<>Q9E5n5Wy zrYp~TA*xUoPt*j4{v|srQukjxy+%!7*ui9HtjvoiY68O!CcDJgSUgb^7?jQI98#4k3*n4-8=}jt2~+Yp zQafIcr0GwJ<)?oy+!EBp^0Ih;Rk8OeDaiMXisw=AWn`@#oBpgAi)Y>g + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/join_community.svg b/src/osbridgelcca/desktop_app/ui/resources/join_community.svg new file mode 100644 index 0000000..79f1447 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/join_community.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/new.svg b/src/osbridgelcca/desktop_app/ui/resources/new.svg new file mode 100644 index 0000000..97b5791 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/new.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/open.svg b/src/osbridgelcca/desktop_app/ui/resources/open.svg new file mode 100644 index 0000000..4c57e2b --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/open.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/play-button-arrowhead.png b/src/osbridgelcca/desktop_app/ui/resources/play-button-arrowhead.png new file mode 100644 index 0000000000000000000000000000000000000000..70e877065d054343b6218548c8dff6510d4ca500 GIT binary patch literal 6074 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 P`F+^#sBPf^%8ma68G@5X literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/ui/resources/play-button-selected.png b/src/osbridgelcca/desktop_app/ui/resources/play-button-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..d27a4821e6fd3d22f7175481d1166a901eec581e GIT binary patch literal 4537 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 W{ov!T=FDTfhs+N9=2D<^_x}M+^Qh+l literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/ui/resources/print.svg b/src/osbridgelcca/desktop_app/ui/resources/print.svg new file mode 100644 index 0000000..89a50fc --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/print.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/rename.svg b/src/osbridgelcca/desktop_app/ui/resources/rename.svg new file mode 100644 index 0000000..43a0af7 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/rename.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/save.svg b/src/osbridgelcca/desktop_app/ui/resources/save.svg new file mode 100644 index 0000000..4f1ac42 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/save.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/save_as.svg b/src/osbridgelcca/desktop_app/ui/resources/save_as.svg new file mode 100644 index 0000000..b51e900 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/save_as.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/save_button.png b/src/osbridgelcca/desktop_app/ui/resources/save_button.png new file mode 100644 index 0000000000000000000000000000000000000000..f035e0bc244de07dc4939029a87aa8c8669f0352 GIT binary patch literal 8849 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 AGynhq literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/ui/resources/tab_close.png b/src/osbridgelcca/desktop_app/ui/resources/tab_close.png new file mode 100644 index 0000000000000000000000000000000000000000..394d5b1afe7012f3e13014fff50471123bb16022 GIT binary patch literal 9732 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 + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/video_tutorial.svg b/src/osbridgelcca/desktop_app/ui/resources/video_tutorial.svg new file mode 100644 index 0000000..c90e743 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/video_tutorial.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/window_close.svg b/src/osbridgelcca/desktop_app/ui/resources/window_close.svg new file mode 100644 index 0000000..eeafc65 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/window_close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/resources/window_maximize.svg b/src/osbridgelcca/desktop_app/ui/resources/window_maximize.svg new file mode 100644 index 0000000..6adddd1 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/window_maximize.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osbridgelcca/desktop_app/ui/resources/window_minimize.svg b/src/osbridgelcca/desktop_app/ui/resources/window_minimize.svg new file mode 100644 index 0000000..9c2d49e --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/window_minimize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/resources/window_restore.svg b/src/osbridgelcca/desktop_app/ui/resources/window_restore.svg new file mode 100644 index 0000000..07a15be --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/resources/window_restore.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridgelcca/desktop_app/ui/results_view.py b/src/osbridgelcca/desktop_app/ui/results_view.py deleted file mode 100644 index 444c46e..0000000 --- a/src/osbridgelcca/desktop_app/ui/results_view.py +++ /dev/null @@ -1 +0,0 @@ -# Placeholder for results view UI diff --git a/src/osbridgelcca/desktop_app/ui/widgets/bridge_and_traffic_data.py b/src/osbridgelcca/desktop_app/ui/widgets/bridge_and_traffic_data.py new file mode 100644 index 0000000..756589b --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/bridge_and_traffic_data.py @@ -0,0 +1,422 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +import sys +import os + +class BridgeAndTrafficData(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__() + self.text_box_width = 200 + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + + /* QComboBox global style with country_arrow.png as dropdown arrow */ + QComboBox { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 18px; + height: 18px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + """) + + self.setObjectName("central_panel_widget") + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + top_h_layout_left_panel = QHBoxLayout() + top_button_left_panel = QPushButton("Bridge and Traffic Data ") + top_h_layout_left_panel.addWidget(top_button_left_panel) + top_button_left_panel.setIcon(QIcon("resources/close.png")) + top_button_left_panel.setIconSize(QSize(13, 13)) + top_button_left_panel.setObjectName("top_button_left_panel") + top_button_left_panel.clicked.connect(self.close_widget) + top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + top_h_layout_left_panel.addWidget(top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Preferred)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # --- Add General Info Form at the top of the scroll area --- + self.general_widget = QWidget() + self.general_layout = QVBoxLayout(self.general_widget) + self.general_layout.setContentsMargins(10, 20, 10, 10) + self.general_layout.setSpacing(10) + + grid_layout = QGridLayout() + grid_layout.setHorizontalSpacing(10) + grid_layout.setVerticalSpacing(20) + + # Number of Lanes + label = QLabel("Number of Lanes") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 0, 0, 1, 1) + valuer_combo = QComboBox(self.general_widget) + valuer_combo.setFixedWidth(self.text_box_width) + valuer_combo.setPlaceholderText("Select") + valuer_combo.addItem("1") + valuer_combo.addItem("2") + valuer_combo.addItem("3") + valuer_combo.addItem("4") + valuer_combo.addItem("5+") + grid_layout.addWidget(valuer_combo, 0, 1, 1, 1) + + info_icon = QLabel(" ") + info_icon.setStyleSheet("color: grey; font-size: 14px;") + info_icon.setAlignment(Qt.AlignmentFlag.AlignLeft) + grid_layout.addWidget(info_icon, 0, 2, 1, 1) + + # Additional Re-Route Distance + label = QLabel("Additional Re-Route Distance") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 1, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setFixedWidth(self.text_box_width) + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 1, 1, 1, 1) + info_icon = QLabel("(km)") + info_icon.setStyleSheet("color: grey; font-size: 14px;") + info_icon.setAlignment(Qt.AlignmentFlag.AlignLeft) + grid_layout.addWidget(info_icon, 1, 2, 1, 1) + + # Road Roughness + label = QLabel("Road Roughness") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 3, 0, 1, 1) + valuer_combo = QComboBox(self.general_widget) + valuer_combo.setFixedWidth(self.text_box_width) + valuer_combo.setPlaceholderText("Select") + valuer_combo.addItem("option a") + valuer_combo.addItem("option b") + valuer_combo.addItem("option c") + valuer_combo.addItem("option d") + grid_layout.addWidget(valuer_combo, 3, 1, 1, 1) + info_icon = QLabel("(mm/km)") + info_icon.setStyleSheet("color: grey; font-size: 14px;") + info_icon.setAlignment(Qt.AlignmentFlag.AlignLeft) + grid_layout.addWidget(info_icon, 3, 2, 1, 1) + + # Road Rise and Fall (RF) + label = QLabel("Road Rise and Fall (RF)") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 4, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setFixedWidth(self.text_box_width) + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 4, 1, 1, 1) + + info_icon = QLabel("(m/km)") + info_icon.setStyleSheet("color: grey; font-size: 14px;") + info_icon.setAlignment(Qt.AlignmentFlag.AlignLeft) + grid_layout.addWidget(info_icon, 4, 2, 1, 1) + + # Type of Road + label = QLabel("Type of Road") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 5, 0, 1, 1) + valuer_combo = QComboBox(self.general_widget) + valuer_combo.setFixedWidth(self.text_box_width) + valuer_combo.setPlaceholderText("Select") + valuer_combo.addItem("option a") + valuer_combo.addItem("option b") + valuer_combo.addItem("option c") + valuer_combo.addItem("option d") + grid_layout.addWidget(valuer_combo, 5, 1, 1, 1) + + + info_icon = QLabel(" ") + info_icon.setStyleSheet("color: grey; font-size: 14px;") + info_icon.setAlignment(Qt.AlignmentFlag.AlignLeft) + grid_layout.addWidget(info_icon, 5, 2, 1, 1) + + # Annual Increase in Traffic + label = QLabel("Annual Increaase in Traffic if Re-Routing duration increases more than a year ") + label.setFixedWidth(200) + label.setWordWrap(True) + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 6, 0, 1, 1) + valuer_combo = QComboBox(self.general_widget) + valuer_combo.setFixedWidth(self.text_box_width) + valuer_combo.setPlaceholderText("Select") + valuer_combo.addItem("option a") + valuer_combo.addItem("option b") + valuer_combo.addItem("option c") + valuer_combo.addItem("option d") + grid_layout.addWidget(valuer_combo, 6, 1, 1, 1) + + info_icon = QLabel("(%)") + info_icon.setStyleSheet("color: grey; font-size: 14px; padding-top: 14px;") + info_icon.setAlignment(Qt.AlignmentFlag.AlignLeft) + grid_layout.addWidget(info_icon, 6, 2, 1, 1) + + # Composition of Various Vehicles + # Remove the old label and vehicle_widget from the grid + # Instead, create a horizontal layout for this row + composition_row_widget = QWidget(self.general_widget) + composition_row_layout = QHBoxLayout(composition_row_widget) + composition_row_layout.setContentsMargins(0, 0, 0, 0) + composition_row_layout.setSpacing(20) # Space between label and box + + # The label + composition_label = QLabel("Composition of Various Vehicles") + composition_label.setAlignment(Qt.AlignLeft | Qt.AlignTop) + composition_row_layout.addWidget(composition_label, alignment=Qt.AlignTop) + + # The white box (vehicle_widget) as before + vehicle_widget = QWidget(self.general_widget) + vehicle_widget.setStyleSheet("background-color: #FFFFFF; border-radius: 10px; border: 1px solid #DDDCE0") + vehicle_widget.setFixedWidth(400) + vehicle_widget.setFixedHeight(250) + vehicle_layout = QGridLayout(vehicle_widget) + vehicle_layout.setContentsMargins(0, 0, 0, 0) + # vehicle_layout.setHorizontalSpacing(7) + # vehicle_layout.setVerticalSpacing(8) + + vehicles = ["Cars", "Buses", "HCV", "MCV", "LCV"] + for i, vehicle in enumerate(vehicles): + v_label = QLabel(f"{vehicle}:") + v_label.setFixedHeight(40) + v_label.setFixedWidth(50) + v_label.setStyleSheet("background-color: #FFFFFF; border: 1px solid #FFFFFF; border-radius: 10px; padding: 10px 10px 10px 1px;") + v_input = QLineEdit() + v_input.setFixedWidth(self.text_box_width) + v_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background: #FFFFFF; + } + """) + vehicle_layout.addWidget(v_label, i, 0) + vehicle_layout.addWidget(v_input, i, 1) + + v_label1 = QLabel("(PCU/D)") + v_label1.setStyleSheet(" padding: 10px 10px 10px 1px;") + + composition_row_layout.addWidget(vehicle_widget, alignment=Qt.AlignTop) + + # Add the composition_row_widget to the main grid, spanning columns 0-2 + grid_layout.addWidget(v_label1, 7, 3, 1, 3) + grid_layout.addWidget(composition_row_widget, 7, 0, 1, 3) + + self.general_layout.addLayout(grid_layout) + self.general_layout.addStretch(1) + self.scroll_content_layout.addWidget(self.general_widget, alignment=Qt.AlignLeft) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + self.button_h_layout.addWidget(next_button) + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + + # --- Add a corner spacer to the scroll_content_layout --- + self.button_h_layout.addSpacerItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.scroll_content_layout.addSpacerItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) + + left_panel_vlayout.addWidget(self.scroll_area) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(BridgeAndTrafficData(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/widgets/carbon_emission_data/carbon_emission_cost_data.py b/src/osbridgelcca/desktop_app/ui/widgets/carbon_emission_data/carbon_emission_cost_data.py new file mode 100644 index 0000000..c1e64d5 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/carbon_emission_data/carbon_emission_cost_data.py @@ -0,0 +1,299 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +import sys + +class CarbonEmissionCostData(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__(parent) + + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* (Removed: QComboBox and material grid element CSS) */ + """) + + self.setObjectName("central_panel_widget") + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + top_h_layout_left_panel = QHBoxLayout() + top_button_left_panel = QPushButton("Carbon Emission Cost Data ") + top_h_layout_left_panel.addWidget(top_button_left_panel) + top_button_left_panel.setIcon(QIcon("resources/close.png")) + top_button_left_panel.setIconSize(QSize(13, 13)) + top_button_left_panel.setObjectName("top_button_left_panel") + top_button_left_panel.clicked.connect(self.close_widget) + top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + top_h_layout_left_panel.addWidget(top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Preferred)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # --- Add General Info Form at the top of the scroll area --- + self.general_widget = QWidget() + self.general_layout = QVBoxLayout(self.general_widget) + self.general_layout.setContentsMargins(10, 20, 10, 10) + self.general_layout.setSpacing(10) + + grid_layout = QGridLayout() + grid_layout.setHorizontalSpacing(10) + grid_layout.setVerticalSpacing(20) + + field_width = 200 # More compact width for input fields + + # Shared Socioeconomic Pathway Type (first row, word wrap enabled) + label = QLabel("Shared Socioeconomic Pathway\nType") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 0, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) + input_widget.setFixedWidth(field_width) + input_widget.setText("SSP2") # Set default text + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 0, 1, 1, 1, alignment=Qt.AlignLeft) + + # Representative Concentration Pathway Type + label = QLabel("Representative Concentration Pathway\nType") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 1, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) + input_widget.setFixedWidth(field_width) + input_widget.setText("RCP60") # Set default text + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 1, 1, 1, 1, alignment=Qt.AlignLeft) + + # Social Cost of Carbon (SCC) + label = QLabel("Social Cost of Carbon") # Removed "\n(SCC)" + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 2, 0, 1, 1) + + # Create a QWidget with a horizontal layout for the SCC input and label + scc_widget = QWidget(self.general_widget) + scc_h_layout = QHBoxLayout(scc_widget) + scc_h_layout.setContentsMargins(0,0,0,0) + scc_h_layout.setSpacing(10) + + input_widget = QLineEdit() + input_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) + input_widget.setFixedWidth(field_width) # Adjusted width for SCC input to match the image + input_widget.setText("6.3936") # Set default text + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + scc_h_layout.addWidget(input_widget) + + scc_suggested_label = QLabel("(INR/kgCO₂e)") + scc_suggested_label.setStyleSheet("color: #707070; font-size: 11px;") + scc_h_layout.addWidget(scc_suggested_label) + + scc_suggested_label = QLabel(" Suggested") + scc_suggested_label.setStyleSheet("color: #B3AEAE; font-size: 10px;") + scc_h_layout.addWidget(scc_suggested_label) + scc_h_layout.addStretch(1) # Add stretch to push content to the left + + grid_layout.addWidget(scc_widget, 2, 1, 1, 1, alignment=Qt.AlignLeft) + + self.general_layout.addLayout(grid_layout) + self.general_layout.addStretch(1) + self.scroll_content_layout.addWidget(self.general_widget, alignment=Qt.AlignLeft) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + self.button_h_layout.addWidget(next_button) + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + + # --- Add a corner spacer to the scroll_content_layout --- + self.button_h_layout.addSpacerItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.scroll_content_layout.addSpacerItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) + + left_panel_vlayout.addWidget(self.scroll_area) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(CarbonEmissionCostData(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) diff --git a/src/osbridgelcca/desktop_app/ui/widgets/carbon_emission_data/carbon_emission_data.py b/src/osbridgelcca/desktop_app/ui/widgets/carbon_emission_data/carbon_emission_data.py new file mode 100644 index 0000000..4650192 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/carbon_emission_data/carbon_emission_data.py @@ -0,0 +1,521 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +import sys + +class ComponentWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + + self.material_rows = [] # To store references to widgets in each material row + self.current_material_row_idx = 1 # Start index for material rows (0 is header) + + self.init_ui() + + def init_ui(self): + self.component_first_scroll_content_layout = QVBoxLayout(self) # Set QVBoxLayout directly on self + self.component_first_scroll_content_layout.setContentsMargins(10, 10, 10, 10) + self.component_first_scroll_content_layout.setSpacing(10) + + # --- Material Details Grid Layout --- + self.material_grid_layout = QGridLayout() + self.material_grid_layout.setHorizontalSpacing(10) + self.material_grid_layout.setVerticalSpacing(5) + + # Header Row - Updated headers for clarity (no specific unit in header now) + headers = ["Type of Material and Grade", "Quantity", "Unit", "Embodied Carbon Energy", "Carbon Emission Factor"] + for col, header_text in enumerate(headers): + label = QLabel(header_text) + label.setAlignment(Qt.AlignCenter) + label.setObjectName("MaterialGridLabel") + if header_text=="Embodied Carbon Energy": + self.material_grid_layout.addWidget(label, 0, col, alignment=Qt.AlignmentFlag.AlignRight) + else: + + self.material_grid_layout.addWidget(label, 0, col, alignment=Qt.AlignmentFlag.AlignCenter) + + self.component_first_scroll_content_layout.addLayout(self.material_grid_layout) + + # Add initial two material rows + self.add_material_row() + self.add_material_row() + + # --- Add Material Button --- + self.add_material_button = QPushButton("+ Add Material") + self.add_material_button.setObjectName("add_material_button") + self.add_material_button.clicked.connect(self.add_material_row) + self.component_first_scroll_content_layout.addWidget(self.add_material_button, alignment=Qt.AlignCenter) + + + def add_material_row(self): + row_widgets = {} + row_idx = self.current_material_row_idx + + # Set fixed width for input widgets. + fixed_input_width_combo = 80 # Width for individual combo boxes + fixed_input_width_line_edit = 80 # Width for individual line edits + + type_material_combo = QComboBox() + type_material_combo.addItems(["Sand", "Gravel", "Cement", "Water", "Admixture", "Rebar", "Other"]) + type_material_combo.setObjectName("MaterialGridInput") + type_material_combo.setFixedWidth(fixed_input_width_combo) + self.material_grid_layout.addWidget(type_material_combo, row_idx, 0, alignment=Qt.AlignmentFlag.AlignHCenter) + row_widgets['type'] = type_material_combo + + quantity_edit = QLineEdit() + quantity_edit.setPlaceholderText("0") + quantity_edit.setObjectName("MaterialGridInput") + quantity_edit.setFixedWidth(fixed_input_width_line_edit) + self.material_grid_layout.addWidget(quantity_edit, row_idx, 1, alignment=Qt.AlignmentFlag.AlignHCenter) + row_widgets['quantity'] = quantity_edit + + unit_combo_m3 = QComboBox() + unit_combo_m3.addItems(["m³", "ft³", "kg", "ton", "litre"]) + unit_combo_m3.setObjectName("MaterialGridInput") + unit_combo_m3.setFixedWidth(fixed_input_width_combo) + self.material_grid_layout.addWidget(unit_combo_m3, row_idx, 2, alignment=Qt.AlignmentFlag.AlignHCenter) + row_widgets['unit_m3'] = unit_combo_m3 + + + # --- Embodied Carbon Energy (LineEdit + QLabel for unit text) - Column 3 --- + embodied_carbon_layout = QHBoxLayout() + embodied_carbon_layout.addStretch() + embodied_carbon_layout.setContentsMargins(0, 0, 0, 0) + embodied_carbon_layout.setSpacing(5) # Small spacing between line edit and label + + embodied_carbon_edit = QLineEdit() + embodied_carbon_edit.setPlaceholderText("0.00") + embodied_carbon_edit.setObjectName("MaterialGridInput") + embodied_carbon_edit.setFixedWidth(fixed_input_width_combo) # Changed to fixed_input_width_combo + embodied_carbon_layout.addWidget(embodied_carbon_edit) + + # Replaced QComboBox with QLabel for static text + embodied_carbon_unit_label = QLabel("(MJ/kg)") + embodied_carbon_unit_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + embodied_carbon_unit_label.setStyleSheet("color: #3F3E5E; font-size: 11px;") # Style to match other text + embodied_carbon_layout.addWidget(embodied_carbon_unit_label) + + + self.material_grid_layout.addLayout(embodied_carbon_layout, row_idx, 3, alignment=Qt.AlignmentFlag.AlignHCenter) + row_widgets['embodied_carbon_edit'] = embodied_carbon_edit + row_widgets['embodied_carbon_unit_label'] = embodied_carbon_unit_label + + + # --- Carbon Emission Factor (LineEdit + QLabel for unit text) - Column 4 --- + carbon_emission_layout = QHBoxLayout() + carbon_emission_layout.setContentsMargins(0, 0, 0, 0) + carbon_emission_layout.setSpacing(5) # Small spacing between line edit and label + + carbon_emission_layout.addStretch() + + carbon_emission_edit = QLineEdit() + carbon_emission_edit.setPlaceholderText("0.00") + carbon_emission_edit.setObjectName("MaterialGridInput") + carbon_emission_edit.setFixedWidth(fixed_input_width_combo) # Changed to fixed_input_width_combo + carbon_emission_layout.addWidget(carbon_emission_edit) + + # Replaced QComboBox with QLabel for static text + carbon_emission_unit_label = QLabel("kg CO2e/kg") + carbon_emission_unit_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + carbon_emission_unit_label.setStyleSheet("color: #3F3E5E; font-size: 11px;") # Style to match other text + carbon_emission_layout.addWidget(carbon_emission_unit_label) + + carbon_emission_layout.addStretch() + + self.material_grid_layout.addLayout(carbon_emission_layout, row_idx, 4) + row_widgets['carbon_emission_edit'] = carbon_emission_edit + row_widgets['carbon_emission_unit_label'] = carbon_emission_unit_label + + + self.material_rows.append(row_widgets) + self.current_material_row_idx += 1 + self.updateGeometry() + self.adjustSize() + + + def remove_material_row_by_widgets(self, row_widgets_to_remove): + if row_widgets_to_remove not in self.material_rows: + return + + row_idx_in_grid = -1 + for i, row_dict in enumerate(self.material_rows): + if row_dict == row_widgets_to_remove: + row_idx_in_grid = i + 1 # +1 because row 0 is header + break + + if row_idx_in_grid == -1: + return + + for col in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(row_idx_in_grid, col) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + widget.deleteLater() + elif item.layout(): + layout = item.layout() + # Iterate and delete widgets within the layout + while layout.count(): + sub_item = layout.takeAt(0) + if sub_item.widget(): + sub_item.widget().deleteLater() + self.material_grid_layout.removeItem(layout) # Remove the layout itself + + self.material_rows.remove(row_widgets_to_remove) + self.current_material_row_idx -= 1 + + # Re-arrange remaining rows + for r_idx in range(row_idx_in_grid, self.current_material_row_idx + 1): + for c_idx in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(r_idx + 1, c_idx) # Look at the row below + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + self.material_grid_layout.addWidget(widget, r_idx, c_idx, alignment=Qt.AlignmentFlag.AlignHCenter) # Re-add with original alignment + elif item.layout(): + layout = item.layout() + self.material_grid_layout.removeItem(layout) + self.material_grid_layout.addLayout(layout, r_idx, c_idx, alignment=Qt.AlignmentFlag.AlignHCenter) # Re-add with original alignment + + self.updateGeometry() + self.update() + self.material_grid_layout.invalidate() + self.adjustSize() + +class CarbonEmissionData(QWidget): + closed = Signal() + def __init__(self): + super().__init__() + + self.component_widgets = [] # To store references to each ComponentWidget instance + + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Styling for component_first_widget (the container for the nested scroll area) */ + #component_first_widget { + background-color: transparent; + margin-top: 10px; /* Add some space above each component block */ + } + + #component_first_scroll_content_widget { /* This now applies directly to ComponentWidget itself */ + background-color: #FFFFFF; + padding: 10px; + + border-radius: 8px; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* Styling for QComboBox */ + QComboBox { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 30px; + height: 30px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + + /* Styling for material grid elements */ + #MaterialGridLabel { + font-weight: bold; + color: #3F3E5E; + padding: 5px; + text-align: center; + } + #MaterialGridInput { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background-color: #FFFFFF; + } + #MaterialGridInput:focus { + border: 1px solid #DDDCE0; + background-color: #FFFFFF; + } + /* IMPROVED CSS FOR ADD MATERIAL/COMPONENT BUTTONS */ + QPushButton#add_material_button, QPushButton#add_component_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + } + QPushButton#add_material_button:hover, QPushButton#add_component_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#add_material_button:pressed, QPushButton#add_component_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* END IMPROVED CSS */ + """) + + self.setObjectName("central_panel_widget") + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + top_h_layout_left_panel = QHBoxLayout() + top_button_left_panel = QPushButton("Carbon Emission Data") + top_h_layout_left_panel.addWidget(top_button_left_panel) + top_button_left_panel.setIcon(QIcon("resources/close.png")) + top_button_left_panel.setIconSize(QSize(13, 13)) + top_button_left_panel.setObjectName("top_button_left_panel") + top_button_left_panel.clicked.connect(self.close_widget) + top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + top_h_layout_left_panel.addWidget(top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Preferred)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + self.button_h_layout.addWidget(next_button) + + + # Add the initial component layout + self.add_component_layout() + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + + # --- Add a corner spacer to the scroll_content_layout --- + self.button_h_layout.addSpacerItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.scroll_content_layout.addSpacerItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) + + left_panel_vlayout.addWidget(self.scroll_area) + + + def add_component_layout(self): + new_component = ComponentWidget(self) + self.component_widgets.append(new_component) + + # Temporarily remove button_h_layout and the vertical spacer for insertion + # Find the vertical spacer and remove it if it exists + vertical_spacer_item = None + for i in range(self.scroll_content_layout.count()): + item = self.scroll_content_layout.itemAt(i) + if isinstance(item, QSpacerItem) and item.sizeHint().width() == 0: # This identifies the vertical spacer + vertical_spacer_item = self.scroll_content_layout.takeAt(i) + break + + if self.scroll_content_layout.indexOf(self.button_h_layout) != -1: + self.scroll_content_layout.removeItem(self.button_h_layout) + + # Insert the new component + self.scroll_content_layout.addWidget(new_component) + + # Re-add the navigation buttons layout + self.scroll_content_layout.addLayout(self.button_h_layout) + + # Re-add the vertical spacer if it was found + if vertical_spacer_item: + self.scroll_content_layout.addItem(vertical_spacer_item) + + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + + def remove_component_layout(self, component_to_remove): + if component_to_remove in self.component_widgets: + self.scroll_content_layout.removeWidget(component_to_remove) + self.component_widgets.remove(component_to_remove) + component_to_remove.deleteLater() + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + + def expand_scroll_area(self): + self.central_widget.layout().invalidate() + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(CarbonEmissionData(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/widgets/demolition_and_recycling_data.py b/src/osbridgelcca/desktop_app/ui/widgets/demolition_and_recycling_data.py new file mode 100644 index 0000000..18b6a5f --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/demolition_and_recycling_data.py @@ -0,0 +1,308 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +import sys +import os + +class DemolitionAndRecyclingData(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__() + + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* (Removed: QComboBox and material grid element CSS) */ + """) + + self.setObjectName("central_panel_widget") + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + top_h_layout_left_panel = QHBoxLayout() + top_button_left_panel = QPushButton("Demolition and Recycling Data ") + top_h_layout_left_panel.addWidget(top_button_left_panel) + top_button_left_panel.setIcon(QIcon("resources/close.png")) + top_button_left_panel.setIconSize(QSize(13, 13)) + top_button_left_panel.setObjectName("top_button_left_panel") + top_button_left_panel.clicked.connect(self.close_widget) + top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + top_h_layout_left_panel.addWidget(top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Preferred)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # --- Add General Info Form at the top of the scroll area --- + self.general_widget = QWidget() + self.general_layout = QVBoxLayout(self.general_widget) + self.general_layout.setContentsMargins(10, 20, 10, 10) + self.general_layout.setSpacing(10) + + grid_layout = QGridLayout() + grid_layout.setHorizontalSpacing(10) + grid_layout.setVerticalSpacing(20) + + field_width = 200 # More compact width for input fields + + # 1. Demolition Cost rate as percentage to total construction cost + label = QLabel("Demolition Cost rate as\npercentage to total construction cost") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 0, 0, 1, 1) + demolition_widget = QWidget(self.general_widget) + demolition_layout = QHBoxLayout(demolition_widget) + demolition_layout.setContentsMargins(0,0,0,0) + demolition_layout.setSpacing(10) + demolition_input = QLineEdit() + demolition_input.setAlignment(Qt.AlignmentFlag.AlignCenter) + demolition_input.setFixedWidth(field_width) + demolition_input.setText("10") + demolition_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + demolition_layout.addWidget(demolition_input) + demolition_layout.addWidget(QLabel("(%)")) + suggested_label = QLabel("Suggested") + suggested_label.setStyleSheet("color: #B3AEAE; font-size: 10px;") + demolition_layout.addWidget(suggested_label) + demolition_layout.addStretch(1) + grid_layout.addWidget(demolition_widget, 0, 1, 1, 1, alignment=Qt.AlignLeft) + + # 2. Scrap Value of Structural Steel + label = QLabel("Scrap Value of Structural Steel") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 1, 0, 1, 1) + scrap_value_widget = QWidget(self.general_widget) + scrap_value_layout = QHBoxLayout(scrap_value_widget) + scrap_value_layout.setContentsMargins(0,0,0,0) + scrap_value_layout.setSpacing(10) + scrap_value_input = QLineEdit() + scrap_value_input.setAlignment(Qt.AlignmentFlag.AlignCenter) + scrap_value_input.setFixedWidth(field_width) + scrap_value_input.setText("50000") + scrap_value_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + scrap_value_layout.addWidget(scrap_value_input) + scrap_value_layout.addWidget(QLabel("(INR/MT)")) + suggested_label2 = QLabel("Suggested") + suggested_label2.setStyleSheet("color: #B3AEAE; font-size: 10px;") + scrap_value_layout.addWidget(suggested_label2) + scrap_value_layout.addStretch(1) + grid_layout.addWidget(scrap_value_widget, 1, 1, 1, 1, alignment=Qt.AlignLeft) + + # 3. Structural Steel Scrap + label = QLabel("Structural Steel Scrap") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 2, 0, 1, 1) + steel_scrap_widget = QWidget(self.general_widget) + steel_scrap_layout = QHBoxLayout(steel_scrap_widget) + steel_scrap_layout.setContentsMargins(0,0,0,0) + steel_scrap_layout.setSpacing(10) + steel_scrap_input = QLineEdit() + steel_scrap_input.setAlignment(Qt.AlignmentFlag.AlignCenter) + steel_scrap_input.setFixedWidth(field_width) + steel_scrap_input.setText("98") + steel_scrap_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + steel_scrap_layout.addWidget(steel_scrap_input) + steel_scrap_layout.addWidget(QLabel("(%)")) + suggested_label3 = QLabel("Suggested") + suggested_label3.setStyleSheet("color: #B3AEAE; font-size: 10px;") + steel_scrap_layout.addWidget(suggested_label3) + steel_scrap_layout.addStretch(1) + grid_layout.addWidget(steel_scrap_widget, 2, 1, 1, 1, alignment=Qt.AlignLeft) + + self.general_layout.addLayout(grid_layout) + self.general_layout.addStretch(1) + self.scroll_content_layout.addWidget(self.general_widget, alignment=Qt.AlignLeft) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + self.button_h_layout.addWidget(back_button) + + calculate_button = QPushButton("Calculate") + calculate_button.setObjectName("nav_button") + self.button_h_layout.addWidget(calculate_button) + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + + # --- Add a corner spacer to the scroll_content_layout --- + self.button_h_layout.addSpacerItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.scroll_content_layout.addSpacerItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) + + left_panel_vlayout.addWidget(self.scroll_area) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +class MyMainWindow(QMainWindow): + def __init__(self): + super().__init__() + + self.setStyleSheet("border: none") + + self.central_widget = QWidget() + self.central_widget.setObjectName("central_widget") + self.setCentralWidget(self.central_widget) + + self.main_h_layout = QHBoxLayout(self.central_widget) + self.main_h_layout.addStretch(1) + + self.main_h_layout.addWidget(DemolitionAndRecyclingData(), 2) + + self.setWindowState(Qt.WindowMaximized) + + def close_widget(self): + self.closed.emit() + self.setParent(None) diff --git a/src/osbridgelcca/desktop_app/ui/widgets/financial_data.py b/src/osbridgelcca/desktop_app/ui/widgets/financial_data.py new file mode 100644 index 0000000..08f9fd5 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/financial_data.py @@ -0,0 +1,322 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +import sys +import os + +class FinancialData(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__() + + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* (Removed: QComboBox and material grid element CSS) */ + """) + + self.setObjectName("central_panel_widget") + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + top_h_layout_left_panel = QHBoxLayout() + top_button_left_panel = QPushButton("Financial Data ") + top_h_layout_left_panel.addWidget(top_button_left_panel) + top_button_left_panel.setIcon(QIcon("resources/close.png")) + top_button_left_panel.setIconSize(QSize(13, 13)) + top_button_left_panel.setObjectName("top_button_left_panel") + top_button_left_panel.clicked.connect(self.close_widget) + top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + top_h_layout_left_panel.addWidget(top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Preferred)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # --- Add General Info Form at the top of the scroll area --- + self.general_widget = QWidget() + self.general_layout = QVBoxLayout(self.general_widget) + self.general_layout.setContentsMargins(10, 20, 10, 10) + self.general_layout.setSpacing(10) + + # Change section title + top_button_left_panel.setText("Financial Data ") + + # --- Financial Data Form --- + grid_layout = QGridLayout() + grid_layout.setHorizontalSpacing(10) + grid_layout.setVerticalSpacing(20) + field_width = 200 + + # 1. Real Discount Rate (with info icon) + label1_widget = QWidget() + label1_layout = QHBoxLayout(label1_widget) + label1_layout.setContentsMargins(0, 0, 0, 0) + label1_layout.setSpacing(4) + label1 = QLabel("Real Discount Rate") + label1.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + label1_layout.addWidget(label1) + info_icon = QLabel() + info_icon.setPixmap(QIcon("resources/info.svg").pixmap(16, 16)) + label1_layout.addWidget(info_icon) + label1_layout.addStretch(1) + input1 = QLineEdit() + input1.setAlignment(Qt.AlignmentFlag.AlignLeft) + input1.setFixedWidth(field_width) + input1.setText("4.2500") + input1.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + unit1 = QLabel("(%)") + suggested1 = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label1_widget, 0, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(input1, 0, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(unit1, 0, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(suggested1, 0, 3, alignment=Qt.AlignVCenter) + + # 2. Interest Rate + label2 = QLabel("Interest Rate") + label2.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + input2 = QLineEdit() + input2.setAlignment(Qt.AlignmentFlag.AlignLeft) + input2.setFixedWidth(field_width) + input2.setText("10") + input2.setStyleSheet(input1.styleSheet()) + unit2 = QLabel("(%)") + suggested2 = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label2, 1, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(input2, 1, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(unit2, 1, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(suggested2, 1, 3, alignment=Qt.AlignVCenter) + + # 3. Investment Ratio + label3 = QLabel("Investment Ratio") + label3.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + input3 = QLineEdit() + input3.setAlignment(Qt.AlignmentFlag.AlignLeft) + input3.setFixedWidth(field_width) + input3.setText("0.5000") + input3.setStyleSheet(input1.styleSheet()) + suggested3 = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label3, 2, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(input3, 2, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(QWidget(), 2, 2) # Empty cell for unit + grid_layout.addWidget(suggested3, 2, 3, alignment=Qt.AlignVCenter) + + # 4. Duration of Study + label4 = QLabel("Duration of Study") + label4.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + input4 = QLineEdit() + input4.setAlignment(Qt.AlignmentFlag.AlignLeft) + input4.setFixedWidth(field_width) + input4.setText("50 & 100") + input4.setStyleSheet(input1.styleSheet()) + unit4 = QLabel("(years)") + suggested4 = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label4, 3, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(input4, 3, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(unit4, 3, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(suggested4, 3, 3, alignment=Qt.AlignVCenter) + + # 5. Time for construction of Base Project + label5 = QLabel("Time for construction of Base Project") + label5.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + input5 = QLineEdit() + input5.setAlignment(Qt.AlignmentFlag.AlignLeft) + input5.setFixedWidth(field_width) + input5.setText("") + input5.setStyleSheet(input1.styleSheet()) + unit5 = QLabel("(years)") + grid_layout.addWidget(label5, 4, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(input5, 4, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(unit5, 4, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(QWidget(), 4, 3) # Empty cell for suggested + + self.general_layout.addLayout(grid_layout) + self.general_layout.addStretch(1) + self.scroll_content_layout.addWidget(self.general_widget, alignment=Qt.AlignLeft) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + self.button_h_layout.addWidget(next_button) + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + + # --- Add a corner spacer to the scroll_content_layout --- + self.button_h_layout.addSpacerItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.scroll_content_layout.addSpacerItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) + + left_panel_vlayout.addWidget(self.scroll_area) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(FinancialData(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/widgets/maintenance_repair_data.py b/src/osbridgelcca/desktop_app/ui/widgets/maintenance_repair_data.py new file mode 100644 index 0000000..68ff967 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/maintenance_repair_data.py @@ -0,0 +1,352 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +import sys +import os + +class ComponentWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + + self.init_ui() + + def init_ui(self): + self.component_first_scroll_content_layout = QVBoxLayout(self) # Set QVBoxLayout directly on self + self.component_first_scroll_content_layout.setContentsMargins(10, 10, 10, 10) + self.component_first_scroll_content_layout.setSpacing(10) + + + +class MaintenanceRepairData(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__() + + self.component_widgets = [] + + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* (Removed: QComboBox and material grid element CSS) */ + """) + + self.setObjectName("central_panel_widget") + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + top_h_layout_left_panel = QHBoxLayout() + top_button_left_panel = QPushButton("Maintenance and Repair Data ") + top_h_layout_left_panel.addWidget(top_button_left_panel) + top_button_left_panel.setIcon(QIcon("resources/close.png")) + top_button_left_panel.setIconSize(QSize(13, 13)) + top_button_left_panel.setObjectName("top_button_left_panel") + top_button_left_panel.clicked.connect(self.close_widget) + top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + top_h_layout_left_panel.addWidget(top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Preferred)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # --- Add General Info Form at the top of the scroll area --- + self.general_widget = QWidget() + self.general_layout = QVBoxLayout(self.general_widget) + self.general_layout.setContentsMargins(10, 20, 10, 10) + self.general_layout.setSpacing(10) + + grid_layout = QGridLayout() + grid_layout.setHorizontalSpacing(10) + grid_layout.setVerticalSpacing(20) + + field_width = 200 + + # 1. Periodic Maintenance Cost rate as percentage to total construction cost + label1 = QLabel("Periodic Maintenance Cost rate as\npercentage to total construction\ncost") + label1.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + pmc_input = QLineEdit() + pmc_input.setAlignment(Qt.AlignmentFlag.AlignTop) + pmc_input.setFixedWidth(field_width) + pmc_input.setText("0.555") + pmc_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + pmc_unit = QLabel("(%)") + pmc_suggested = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label1, 0, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(pmc_input, 0, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(pmc_unit, 0, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(pmc_suggested, 0, 3, alignment=Qt.AlignVCenter) + + # 2. Annual Routine Inspection cost rate as percentage of total construction cost + label2 = QLabel("Annual Routine Inspection cost rate\nas percentage of total construction\ncost") + label2.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + ari_input = QLineEdit() + ari_input.setAlignment(Qt.AlignmentFlag.AlignTop) + ari_input.setFixedWidth(field_width) + ari_input.setText("1") + ari_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + ari_unit = QLabel("(%)") + ari_suggested = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label2, 1, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(ari_input, 1, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(ari_unit, 1, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(ari_suggested, 1, 3, alignment=Qt.AlignVCenter) + + # 3. Repair and Rehabilitation cost rate as percentage of total construction cost + label3 = QLabel("Repair and Rehabilitation cost rate as\npercentage of total construction cost") + label3.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + rr_input = QLineEdit() + rr_input.setAlignment(Qt.AlignmentFlag.AlignTop) + rr_input.setFixedWidth(field_width) + rr_input.setText("10") + rr_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + rr_unit = QLabel("(%)") + rr_suggested = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label3, 2, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(rr_input, 2, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(rr_unit, 2, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(rr_suggested, 2, 3, alignment=Qt.AlignVCenter) + + # 4. Frequency of Periodic Maintenance + label4 = QLabel("Frequency of Periodic Maintenance") + label4.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + fpm_input = QLineEdit() + fpm_input.setAlignment(Qt.AlignmentFlag.AlignTop) + fpm_input.setFixedWidth(field_width) + fpm_input.setText("5") + fpm_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + fpm_unit = QLabel("(years)") + fpm_suggested = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label4, 3, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(fpm_input, 3, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(fpm_unit, 3, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(fpm_suggested, 3, 3, alignment=Qt.AlignVCenter) + + # 5. Frequency of Routine Inspection + label5 = QLabel("Frequency of Routine Inspection") + label5.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + fri_input = QLineEdit() + fri_input.setAlignment(Qt.AlignmentFlag.AlignTop) + fri_input.setFixedWidth(field_width) + fri_input.setText("1") + fri_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + fri_unit = QLabel("(years)") + fri_suggested = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label5, 4, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(fri_input, 4, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(fri_unit, 4, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(fri_suggested, 4, 3, alignment=Qt.AlignVCenter) + + self.general_layout.addLayout(grid_layout) + self.general_layout.addStretch(1) + self.scroll_content_layout.addWidget(self.general_widget, alignment=Qt.AlignLeft) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + self.button_h_layout.addWidget(next_button) + + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + + # --- Add a corner spacer to the scroll_content_layout --- + self.button_h_layout.addSpacerItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.scroll_content_layout.addSpacerItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) + + left_panel_vlayout.addWidget(self.scroll_area) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(MaintenanceRepairData(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/widgets/project_details_left_widget.py b/src/osbridgelcca/desktop_app/ui/widgets/project_details_left_widget.py new file mode 100644 index 0000000..07c3c8e --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/project_details_left_widget.py @@ -0,0 +1,375 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, QPropertyAnimation, QEasingCurve, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QTextEdit, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy) +from PySide6.QtGui import QIcon, QTextDocument +import sys +import os +from widgets.structure_works_data.foundation_widget import Foundation +from widgets.structure_works_data.super_structure_widget import SuperStructure +from widgets.structure_works_data.sub_structure_widget import SubStructure +from widgets.structure_works_data.auxiliary_works_widget import AuxiliaryWorks +from PySide6.QtWidgets import QStackedWidget + +class ProjectDetailsLeft(QWidget): + closed = Signal() + """ + The main application window that uses a custom title bar. + """ + def __init__(self, widget_map, parent=None): + super().__init__() + self.widget_map = widget_map + self.parent = parent + + self.current_selected_button = None + self.all_param_buttons = [] + + self.setObjectName("left_panel_widget") + self.setStyleSheet(""" + #left_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #left_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #left_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + QScrollArea { + border: 1px solid #000000; + background-color: transparent; + outline: none; + } + QScrollArea > QWidget { + background-color: transparent; + } + QScrollBar:vertical { + width: 12px; + margin: 0px; + border-radius: 6px; + } + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + QPushButton#top_button_left_panel { + background-color: #F0E6E6; + border: 1px solid #000000; + border-bottom: none; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #FDEFEF; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + } + #input_output_header { + background-color: #F0E6E6; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; + } + #input_output_header QLabel { + font-size: 15px; + font-weight: bold; + color: #333; + letter-spacing: 1px; + padding: 8px 0 8px 0; + background: transparent; + } + #input_output_separator { + background-color: #F0E6E6; + height: 10px; + border-bottom: 1px solid black; + } + QPushButton.category_button { + background-color: transparent; + border: none; + text-align: left; + padding: 6px 0px 6px 15px; + color: #000; + font-size: 13px; + } + QPushButton.category_button:hover { + background-color: #2A3F54; + color: #FFFFFF; + } + QPushButton.subcategory_button { + background-color: transparent; + border: none; + text-align: left; + padding: 4px 0px 4px 30px; + color: #000; + font-size: 12px; + } + QPushButton.subcategory_button:hover { + background-color: #2A3F54; + color: #FFFFFF; + } + QPushButton.category_button[selected="true"], + QPushButton.subcategory_button[selected="true"] { + background-color: #2A3F54; + color: #FFFFFF; + } + QLabel.output_label { + background-color: transparent; + color: #808080; + font-size: 12px; + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + top_h_layout_left_panel = QHBoxLayout() + top_button_left_panel = QPushButton("Project Details Window") + top_button_left_panel.setIcon(QIcon("resources/close.png")) + top_button_left_panel.setIconSize(QSize(13, 13)) + top_button_left_panel.setObjectName("top_button_left_panel") + top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + top_button_left_panel.clicked.connect(self.close_widget) + top_h_layout_left_panel.addWidget(top_button_left_panel, 2) + top_h_layout_left_panel.addStretch(1) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + bordered_spacer_widget = QWidget() + bordered_spacer_widget.setObjectName("bordered_spacer_widget") + bordered_spacer_widget.setFixedHeight(50) + bordered_spacer_widget.setStyleSheet(""" + #bordered_spacer_widget { + background-color: #F0E6E6; + border: 1px solid black; + border-bottom: none; + } + """) + left_panel_vlayout.addWidget(bordered_spacer_widget) + + scroll_area = QScrollArea() + scroll_area.setWidgetResizable(True) + scroll_content_widget = QWidget() + scroll_area.setWidget(scroll_content_widget) + scroll_content_layout = QVBoxLayout(scroll_content_widget) + scroll_content_layout.setContentsMargins(0, 0, 0, 0) + scroll_content_layout.setSpacing(0) + + input_param_header = QWidget() + input_param_header.setObjectName("input_param_header") + input_param_header.setStyleSheet(""" + #input_param_header { + background-color: #F0E6E6; + } + """) + input_param_header_layout = QVBoxLayout(input_param_header) + input_param_header_layout.setContentsMargins(0, 0, 0, 0) + input_param_header_layout.setSpacing(0) + input_param_label = QLabel("Input Parameters") + input_param_label.setAlignment(Qt.AlignCenter) + input_param_label.setStyleSheet("font-size: 15px; font-weight: bold; color: #333; padding: 8px 0 8px 0; background: transparent;border:none;") + input_param_header_layout.addWidget(input_param_label) + scroll_content_layout.addWidget(input_param_header) + + header_separator = QWidget() + header_separator.setObjectName("input_output_separator") + header_separator.setFixedHeight(2) + scroll_content_layout.addWidget(header_separator) + + # Define icons for the four states of category buttons + unselected_unexpanded_icon = QIcon("resources/play-button-arrowhead.png") + unselected_sub_icon = QIcon("resources/play-button-arrowhead.png") + + self.current_selected_button = None + self.all_param_buttons = [] + button_data = { + "Structure Works Data": ["Foundation", "Super-Structure", "Sub-Structure", "Miscellaneous"], + "Financial Data": [], + "Carbon Emission Data": ["Carbon Emission Cost Data"], + "Bridge and Traffic Data": [], + "Maintenance and Repair": [], + "Demolition and Recycling": [] + } + for label, sublabels in button_data.items(): + btn = QPushButton(label) + btn.setProperty("class", "category_button") + btn.setProperty("selected", False) + btn.setProperty("expanded", False) + btn.setCursor(Qt.CursorShape.PointingHandCursor) + btn.setLayoutDirection(Qt.LeftToRight) + btn.setIcon(unselected_unexpanded_icon) + btn.setIconSize(QSize(10, 10)) + btn.clicked.connect(lambda checked ,b=btn ,name=label: self.show_structure_widget(name, b)) + scroll_content_layout.addWidget(btn) + self.all_param_buttons.append(btn) + if sublabels: + sub_widgets = [] + for sublabel in sublabels: + sub_btn = QPushButton(sublabel) + sub_btn.setProperty("class", "subcategory_button") + sub_btn.setProperty("selected", False) + sub_btn.setIcon(unselected_sub_icon) + sub_btn.setIconSize(QSize(10, 10)) + sub_btn.setCursor(Qt.CursorShape.PointingHandCursor) + sub_btn.setLayoutDirection(Qt.LeftToRight) + sub_btn.setVisible(False) + sub_btn.clicked.connect(lambda checked ,b=sub_btn, name=sublabel: self.show_structure_widget(name,b)) + scroll_content_layout.addWidget(sub_btn) + sub_widgets.append(sub_btn) + self.all_param_buttons.append(sub_btn) + + def make_toggle(button, sub_widgets): + def toggle(): + is_expanded = button.property("expanded") + for widget in sub_widgets: + widget.setVisible(not widget.isVisible()) + button.setProperty("expanded", not is_expanded) + self.update_button_icon(button) + self.handle_button_selection(button) + return toggle + btn.clicked.connect(lambda checked, b=btn, sw=sub_widgets: make_toggle(b, sw)()) + + input_output_middle_separator = QWidget() + input_output_middle_separator.setObjectName("input_output_separator") + input_output_middle_separator.setFixedHeight(2) + scroll_content_layout.addWidget(input_output_middle_separator) + + output_header = QWidget() + output_header.setObjectName("input_param_header") + output_header.setStyleSheet(""" + #input_param_header { + background-color: #F0E6E6; + } + """) + output_header_layout = QVBoxLayout(output_header) + output_header_layout.setContentsMargins(0, 0, 0, 0) + output_header_layout.setSpacing(0) + output_label = QLabel("Output") + output_label.setAlignment(Qt.AlignCenter) + output_label.setStyleSheet("font-size: 15px; font-weight: bold; color: #333; letter-spacing: 1px; padding: 8px 0 8px 0; background: transparent;") + output_header_layout.addWidget(output_label) + scroll_content_layout.addWidget(output_header) + + output_header_separator = QWidget() + output_header_separator.setObjectName("input_output_separator") + output_header_separator.setFixedHeight(2) + scroll_content_layout.addWidget(output_header_separator) + + output_labels_data = [ + "Initial Construction Cost", "Initial Carbon Emission Cost", "Time Cost", + "Road User Cost", "Carbon Emission due to Re-Routing", "Periodic Maintenance Costs", + "Maintenance Emission Costs", "Routine Inspection Costs", "Repair & Rehabilitation Costs", + "Reconstruction Costs", "Demolition & Disposal Cost", "Recycling Cost", "Total Life-Cycle Cost" + ] + output_layout = QVBoxLayout() + output_layout.setContentsMargins(10, 10, 10, 10) + for text in output_labels_data: + output_item = QLabel(text) + output_item.setProperty("class", "output_label") + output_layout.addWidget(output_item) + scroll_content_layout.addLayout(output_layout) + scroll_content_layout.addStretch(1) + + scroll_content_widget.setStyleSheet(""" + QPushButton[selected="true"] { + background-color: #2A3F54; + color: #FFFFFF; + } + QPushButton[selected="false"], QPushButton { + background-color: transparent; + color: #000000; + } + """) + left_panel_vlayout.addWidget(scroll_area) + + def handle_button_selection(self, button_clicked=None, button_name=None): + """ + Handles the visual selection state of buttons in the side panel. + """ + for b in self.all_param_buttons: + b.setProperty("selected", False) + b.style().unpolish(b) + b.style().polish(b) + if b.property("class") == "subcategory_button": + b.setIcon(QIcon("resources/play-button-arrowhead.png")) + else: + self.update_button_icon(b) + + if button_name: + if b.text() == button_name: + button_clicked = b + button_clicked.click() + + button_clicked.setProperty("selected", True) + button_clicked.style().unpolish(button_clicked) + button_clicked.style().polish(button_clicked) + if button_clicked.property("class") == "subcategory_button": + button_clicked.setIcon(QIcon("resources/play-button-selected.png")) + else: + self.update_button_icon(button_clicked) + self.current_selected_button = button_clicked + + def update_button_icon(self, button): + """ + Updates the icon of a category button based on its selected and expanded state. + """ + if button.property("class") != "category_button": + return + is_selected = button.property("selected") + is_expanded = button.property("expanded") + if is_selected and is_expanded: + button.setIcon(QIcon("resources/arrow_down_selected.png")) + elif is_selected: + button.setIcon(QIcon("resources/play-button-selected.png")) + elif is_expanded: + button.setIcon(QIcon("resources/arrow_down.png")) + else: + button.setIcon(QIcon("resources/play-button-arrowhead.png")) + button.setIconSize(QSize(10, 10)) + + def show_structure_widget(self, name, btn): + self.parent.show_project_detail_widgets(name) + self.handle_button_selection(btn) + + def close_widget(self): + self.closed.emit() + self.setParent(None) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/widgets/project_details_right_widget.py b/src/osbridgelcca/desktop_app/ui/widgets/project_details_right_widget.py new file mode 100644 index 0000000..ec9e20e --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/project_details_right_widget.py @@ -0,0 +1,507 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, QSize, Qt, QPropertyAnimation, QEasingCurve, Signal +from PySide6.QtGui import (QIcon) +from PySide6.QtWidgets import (QHBoxLayout, QTextEdit, QScrollArea, QSpacerItem, QSizePolicy, + QPushButton, QWidget, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QComboBox) +import sys + +class ProjectDetailsWidget(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("central_panel_widget") + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + QScrollArea { + border: 1px solid #000000; + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + } + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + QPushButton#general_info, QPushButton#parameter_button, QPushButton#output_button { + background-color: #FDEFEF; + border: 1px solid #000000; + text-align: left; + padding: 3px 10px; + color: #000000; + font-size: 14px; + } + QPushButton#general_info:hover, QPushButton#parameter_button:hover, QPushButton#output_button:hover { + background-color: #F0E6E6; + border: 1px solid #000000; + } + QPushButton#general_info:pressed, QPushButton#parameter_button:pressed, QPushButton#output_button:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + QPushButton#top_button_right_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_right_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_right_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + QPushButton#top_button_right_panel:hover QIcon { + color: red; + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + # --- Top Section --- + top_h_layout_left_panel = QHBoxLayout() + self.top_button_right_panel = QPushButton("Project Details Window ") + self.top_button_right_panel.setObjectName("top_button_right_panel") + self.top_button_right_panel.setIcon(QIcon("resources/close.png")) + self.top_button_right_panel.setIconSize(QSize(13, 13)) + self.top_button_right_panel.setLayoutDirection(Qt.RightToLeft) + self.top_button_right_panel.clicked.connect(self.close_widget) + top_h_layout_left_panel.addWidget(self.top_button_right_panel) + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + # --- Scroll Area Setup --- + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + scroll_content_widget = QWidget() + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + scroll_content_layout = QVBoxLayout(scroll_content_widget) + scroll_content_layout.addStretch(1) + + # --- General Information Section Button --- + self.general_info_button = QPushButton(" General Information") + self.general_info_button.setObjectName("general_info") + self.general_info_button.setCursor(Qt.PointingHandCursor) + self.unactive_arrow_icon = QIcon("resources/play-button-arrowhead.png") + self.active_arrow_icon = QIcon("resources/arrow_down.png") + self.general_info_button.setIcon(self.unactive_arrow_icon) + self.general_info_button.setIconSize(QSize(10, 10)) + self.general_info_button.setLayoutDirection(Qt.LeftToRight) + scroll_content_layout.insertWidget(0, self.general_info_button) + + # --- General Information Form Widget --- + self.general_button_active = False + self.general_widget = QWidget() + self.general_widget.setObjectName("general_info_form_widget") + self.general_widget.setStyleSheet(f""" + #general_info_form_widget QLineEdit, + #general_info_form_widget QTextEdit, + #general_info_form_widget QComboBox {{ + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background-color: #FFFFFF; + }} + #general_info_form_widget QTextEdit::viewport {{ + background-color: #FFFFFF; + }} + #general_info_form_widget QLineEdit:focus, + #general_info_form_widget QTextEdit:focus, + #general_info_form_widget QComboBox:focus {{ + border: 1px solid #DDDCE0; + background-color: #FFFFFF; + }} + #general_info_form_widget QTextEdit:focus::viewport {{ + background-color: #FFFFFF; + }} + #left_label {{ + margin-right: 20px; + }} + #info_button {{ + margin-right: {self.general_info_button.width()//2}px; + }} + """) + grid_layout = QGridLayout(self.general_widget) + grid_layout.setContentsMargins(30, 20, 30, 20) + grid_layout.setHorizontalSpacing(10) + grid_layout.setVerticalSpacing(10) + # Company Name + label = QLabel("Company Name") + label.setObjectName("left_label") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 0, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + grid_layout.addWidget(input_widget, 0, 1, 1, 2) + # Project Title + label = QLabel("Project Title") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 1, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + grid_layout.addWidget(input_widget, 1, 1, 1, 2) + # Project Description + label = QLabel("Project Description") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 2, 0, 1, 1) + input_widget = QTextEdit(self.general_widget) + input_widget.setPlaceholderText("Enter project description here...") + grid_layout.addWidget(input_widget, 2, 1, 1, 2) + # Name of Valuer + label = QLabel("Name of Valuer") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 3, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 3, 1, 1, 1) + # Job Number + label = QLabel("Job Number") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 4, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 4, 1, 1, 1) + # Client + label = QLabel("Client") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 5, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 5, 1, 1, 1) + # Country ComboBox + label = QLabel("Country") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 6, 0, 1, 1) + valuer_combo = QComboBox(self.general_widget) + valuer_combo.addItem("India") + valuer_combo.addItem("USA") + valuer_combo.addItem("UK") + valuer_combo.setStyleSheet(""" + QComboBox{ + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 18px; + height: 18px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + """) + grid_layout.addWidget(valuer_combo, 6, 1, 1, 1) + info_icon = QLabel("ⓘ") + info_icon.setStyleSheet("color: grey; font-size: 14px;") + info_icon.setObjectName("info_button") + info_icon.setToolTip("Social Cost of Carbon varies as per selected region") + info_icon.setCursor(Qt.PointingHandCursor) + info_icon.setAlignment(Qt.AlignRight) + grid_layout.addWidget(info_icon, 6, 2, 1, 1) + # Base Year + label = QLabel("Base Year") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 7, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 7, 1, 1, 1) + scroll_content_layout.insertWidget(1, self.general_widget) + self.general_widget.show() + self.general_widget.adjustSize() + self.original_general_widget_height = self.general_widget.sizeHint().height() + self.general_widget.hide() + self.general_widget_animation = QPropertyAnimation(self.general_widget, b"maximumHeight") + self.general_widget_animation.setDuration(300) + self.general_widget_animation.setEasingCurve(QEasingCurve.InOutQuad) + self.general_widget.setMaximumHeight(0) + self.general_widget.hide() + # Input Parameters Section + self.input_param_unactive_icon = QIcon("resources/play-button-arrowhead.png") + self.input_param_active_icon = QIcon("resources/arrow_down.png") + self.input_param_button_css_inactive = """ + QPushButton#parameter_button { + background-color: transparent; + border: 1px solid #000000; + text-align: left; + padding: 3px 10px; + color: #000000; + font-size: 16px; + } + QPushButton#parameter_button:hover { + background-color: #F0E6E6; + } + QPushButton#parameter_button:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + """ + self.input_param_button_css_active = """ + QPushButton#parameter_button { + background-color: transparent; + text-align: left; + padding: 3px 10px; + color: #000000; + font-size: 16px; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + } + QPushButton#parameter_button:hover { + background-color: #F0E6E6; + } + QPushButton#parameter_button:pressed { + background-color: #FFF3F3; + } + """ + self.input_param_widget = QWidget() + self.input_param_widget.setObjectName("input_param_widget") + self.input_param_widget.setStyleSheet(""" + #input_param_widget{ + background-color: #FDEFEF; + border: 1px solid #000000; + } + """) + self.input_param_layout = QVBoxLayout(self.input_param_widget) + self.input_param_layout.setContentsMargins(0, 0, 0, 0) + self.input_param_layout.setSpacing(0) + self.input_param_button = QPushButton(" Input Parameters") + self.input_param_button.setStyleSheet(self.input_param_button_css_inactive) + self.input_param_button.setObjectName("parameter_button") + self.input_param_button.setCursor(Qt.PointingHandCursor) + self.input_param_button.clicked.connect(self.input_button_toggle) + self.input_param_button.setIcon(self.input_param_unactive_icon) + self.input_param_button.setIconSize(QSize(10, 10)) + self.input_param_button.setLayoutDirection(Qt.LeftToRight) + self.input_param_layout.addWidget(self.input_param_button) + self.input_param_option_widget = QWidget() + self.input_param_option_widget.hide() + self.input_option_active = False + self.input_param_option_widget.setObjectName("input_param_option_widget") + self.input_param_option_layout = QVBoxLayout(self.input_param_option_widget) + self.input_param_option_layout.setContentsMargins(8, 8, 8, 8) + self.input_param_option_layout.setSpacing(2) + button_labels = [ + "Structure Works Data", + "Financial Data", + "Carbon Emission Data", + "Bridge and Traffic Data", + "Maintenance and Repair", + "Demolition and Recycling" + ] + self.selected_icon = QIcon("resources/selected_icon.png") + self.param_buttons = [] + for label in button_labels: + btn = QPushButton(f" {label}") + btn.setObjectName("parameter_button") + btn.setIcon(QIcon("resources/play-button-arrowhead.png")) + btn.setIconSize(QSize(10, 10)) + btn.setCursor(Qt.PointingHandCursor) + btn.setLayoutDirection(Qt.LeftToRight) + btn.setStyleSheet(""" + QPushButton#parameter_button { + background-color: none; + border: none; + text-align: left; + padding: 2px 10px; + color: #000; + font-size: 12px; + margin-left: 40px + } + QPushButton#parameter_button:hover { + background-color: #F0E6E6; + } + """) + btn.clicked.connect(lambda checked, b=btn: self.select_param_button(b)) + self.input_param_option_layout.addWidget(btn) + self.param_buttons.append(btn) + self.input_param_layout.addWidget(self.input_param_option_widget) + scroll_content_layout.insertWidget(2, self.input_param_widget) + self.output_button = QPushButton(" Outputs") + self.output_button.setObjectName("output_button") + self.output_button.setIcon(QIcon("resources/play-button-arrowhead.png")) + self.output_button.setIconSize(QSize(10, 10)) + self.output_button.setLayoutDirection(Qt.LeftToRight) + scroll_content_layout.insertWidget(3, self.output_button) + left_panel_vlayout.addWidget(self.scroll_area) + self.bottom_widget = QWidget() + self.bottom_widget.setObjectName("bottom_widget") + self.bottom_widget.setStyleSheet(""" + #bottom_widget { + background-color: #F0E6E6; + border-left: 1px solid #000000; + border-bottom: 1px solid #000000; + border-right: 1px solid #000000; + } + """) + left_panel_vlayout.addWidget(self.bottom_widget) + self.general_info_button.clicked.connect(self.expand_general_area) + + def expand_general_area(self): + try: + self.general_widget_animation.finished.disconnect() + except RuntimeError: + pass + if self.general_button_active: + self.general_info_button.setIcon(self.unactive_arrow_icon) + self.general_widget_animation.setStartValue(self.general_widget.height()) + self.general_widget_animation.setEndValue(0) + self.general_widget_animation.finished.connect(self.general_widget.hide) + self.general_button_active = False + else: + self.general_info_button.setIcon(self.active_arrow_icon) + self.general_widget.show() + self.general_widget_animation.setStartValue(0) + self.general_widget_animation.setEndValue(self.original_general_widget_height) + self.general_widget_animation.finished.connect(lambda: self.general_widget.setMaximumHeight(16777215)) + self.general_button_active = True + self.general_widget_animation.start() + + def input_button_toggle(self): + if self.input_option_active: + self.input_param_option_widget.hide() + self.input_param_button.setStyleSheet(self.input_param_button_css_inactive) + self.input_param_button.setIcon(self.input_param_unactive_icon) + self.input_option_active = False + else: + self.input_param_option_widget.show() + self.input_option_active = True + self.input_param_button.setStyleSheet(self.input_param_button_css_active) + self.input_param_button.setIcon(self.input_param_active_icon) + + def select_param_button(self, selected_btn): + for btn in self.param_buttons: + if btn == selected_btn: + btn.setIcon(self.selected_icon) + else: + btn.setIcon(QIcon("resources/play-button-arrowhead.png")) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(ProjectDetailsWidget(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/auxiliary_works_widget.py b/src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/auxiliary_works_widget.py new file mode 100644 index 0000000..b1e6481 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/auxiliary_works_widget.py @@ -0,0 +1,534 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +import sys + +class ComponentWidget(QWidget): + def __init__(self, parent): + super().__init__(parent) + + self.material_rows = [] # To store references to widgets in each material row + self.current_material_row_idx = 1 # Start index for material rows (0 is header) + + self.init_ui() + + def init_ui(self): + self.component_first_scroll_content_layout = QVBoxLayout(self) # Set QVBoxLayout directly on self + self.component_first_scroll_content_layout.setContentsMargins(10, 10, 10, 10) + self.component_first_scroll_content_layout.setSpacing(10) + + # Component Label and Dropdown with a remove button + component_header_layout = QHBoxLayout() + component_label = QLabel("Component:") + component_label.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(component_label) + + self.component_combobox = QComboBox() + self.component_combobox.addItems(["Earthwork", "Concrete", "Steel", "Wood"]) + self.component_combobox.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(self.component_combobox) + + # Add a remove button for the component + self.remove_component_button = QPushButton("x") + self.remove_component_button.setFixedSize(24, 24) + self.remove_component_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + line-height:12px; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + component_header_layout.addWidget(self.remove_component_button) + component_header_layout.addStretch(1) + + self.component_first_scroll_content_layout.addLayout(component_header_layout) + + # --- Material Details Grid Layout --- + self.material_grid_layout = QGridLayout() + self.material_grid_layout.setHorizontalSpacing(10) + self.material_grid_layout.setVerticalSpacing(5) + + # Header Row + headers = ["Type of Material", "Grade", "Quantity", "Unit", "Rate", "Rate Data Source"] + for col, header_text in enumerate(headers): + label = QLabel(header_text) + label.setAlignment(Qt.AlignCenter) + label.setObjectName("MaterialGridLabel") + self.material_grid_layout.addWidget(label, 0, col) + + # Column stretch factors are removed as all input widgets will now have fixed widths. + # This allows the grid to size columns based on the fixed widget sizes and spacing. + + self.component_first_scroll_content_layout.addLayout(self.material_grid_layout) + + # Add initial two material rows + self.add_material_row() + self.add_material_row() + + # --- Add Material Button --- + self.add_material_button = QPushButton("+ Add Material") + self.add_material_button.setObjectName("add_material_button") + self.add_material_button.clicked.connect(self.add_material_row) + self.component_first_scroll_content_layout.addWidget(self.add_material_button, alignment=Qt.AlignCenter) + + + def add_material_row(self): + row_widgets = {} + row_idx = self.current_material_row_idx + + # Set fixed width for all input widgets to 80px, as requested. + # Note: This might make wider text truncate or appear cramped depending on content. + fixed_input_width = 80 + + type_material_combo = QComboBox() + type_material_combo.addItems(["Sand", "Gravel", "Cement", "Water", "Admixture", "Rebar", "Other"]) + type_material_combo.setObjectName("MaterialGridInput") + type_material_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(type_material_combo, row_idx, 0) + row_widgets['type'] = type_material_combo + + grade_combo = QComboBox() + grade_combo.addItems(["A", "B", "C", "X", "Y", "Z", "N/A"]) + grade_combo.setObjectName("MaterialGridInput") + grade_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(grade_combo, row_idx, 1) + row_widgets['grade'] = grade_combo + + quantity_edit = QLineEdit() + quantity_edit.setPlaceholderText("0") + quantity_edit.setObjectName("MaterialGridInput") + quantity_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(quantity_edit, row_idx, 2) + row_widgets['quantity'] = quantity_edit + + unit_combo_m3 = QComboBox() + unit_combo_m3.addItems(["m³", "ft³", "kg", "ton", "litre"]) + unit_combo_m3.setObjectName("MaterialGridInput") + unit_combo_m3.setFixedWidth(fixed_input_width) # Previously 80, now consistent with others + self.material_grid_layout.addWidget(unit_combo_m3, row_idx, 3) # Directly add, no extra layout + row_widgets['unit_m3'] = unit_combo_m3 + + rate_edit = QLineEdit() + rate_edit.setPlaceholderText("0.00") + rate_edit.setObjectName("MaterialGridInput") + rate_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_edit, row_idx, 4) + row_widgets['rate'] = rate_edit + + rate_data_source_edit = QLineEdit() + rate_data_source_edit.setObjectName("MaterialGridInput") + rate_data_source_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_data_source_edit, row_idx, 5) + row_widgets['rate_data_source'] = rate_data_source_edit + + remove_button = QPushButton("x") + remove_button.setFixedSize(24, 24) + remove_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + line-height:12px; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + remove_button.clicked.connect(lambda: self.remove_material_row_by_widgets(row_widgets)) + self.material_grid_layout.addWidget(remove_button, row_idx, 6) + row_widgets['remove_button'] = remove_button + + self.material_rows.append(row_widgets) + self.current_material_row_idx += 1 + self.updateGeometry() # Call updateGeometry on self, not on the scroll content widget + self.adjustSize() # Adjust the size of the component widget + + + def remove_material_row_by_widgets(self, row_widgets_to_remove): + if row_widgets_to_remove not in self.material_rows: + return + + row_idx_in_grid = -1 + for i, row_dict in enumerate(self.material_rows): + if row_dict == row_widgets_to_remove: + row_idx_in_grid = i + 1 # +1 because row 0 is header + break + + if row_idx_in_grid == -1: + return + + for col in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(row_idx_in_grid, col) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + widget.deleteLater() + elif item.layout(): + layout = item.layout() + while layout.count(): + sub_item = layout.takeAt(0) + if sub_item.widget(): + sub_item.widget().deleteLater() + self.material_grid_layout.removeItem(layout) + + self.material_rows.remove(row_widgets_to_remove) + self.current_material_row_idx -= 1 + + for r_idx in range(row_idx_in_grid, self.current_material_row_idx + 1): + for c_idx in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(r_idx + 1, c_idx) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + self.material_grid_layout.addWidget(widget, r_idx, c_idx) + elif item.layout(): + layout = item.layout() + self.material_grid_layout.removeItem(layout) + self.material_grid_layout.addLayout(layout, r_idx, c_idx) + + self.updateGeometry() # Call updateGeometry on self + self.update() # Call update on self + self.material_grid_layout.invalidate() + self.adjustSize() # Adjust the size of the component widget + +class AuxiliaryWorks(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("central_panel_widget") + self.component_widgets = [] # To store references to each ComponentWidget instance + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Styling for component_first_widget (the container for the nested scroll area) */ + #component_first_widget { + background-color: transparent; + margin-top: 10px; /* Add some space above each component block */ + } + + #component_first_scroll_content_widget { /* This now applies directly to ComponentWidget itself */ + background-color: #FFFFFF; + padding: 10px; + + border-radius: 8px; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* Styling for QComboBox */ + QComboBox { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 30px; + height: 30px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + + /* Styling for material grid elements */ + #MaterialGridLabel { + font-weight: bold; + color: #3F3E5E; + padding: 5px; + text-align: center; + } + #MaterialGridInput { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background-color: #FFFFFF; + } + #MaterialGridInput:focus { + border: 1px solid #DDDCE0; + background-color: #FFFFFF; + } + /* IMPROVED CSS FOR ADD MATERIAL/COMPONENT BUTTONS */ + QPushButton#add_material_button, QPushButton#add_component_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + } + QPushButton#add_material_button:hover, QPushButton#add_component_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#add_material_button:pressed, QPushButton#add_component_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + top_h_layout_left_panel = QHBoxLayout() + top_button_left_panel = QPushButton("Auxiliary Works") + top_h_layout_left_panel.addWidget(top_button_left_panel) + top_button_left_panel.setIcon(QIcon("resources/close.png")) + top_button_left_panel.setIconSize(QSize(13, 13)) + top_button_left_panel.setObjectName("top_button_left_panel") + top_button_left_panel.clicked.connect(self.close_widget) + top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + top_h_layout_left_panel.addWidget(top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Preferred)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # Create the Add Component button and connect it + self.add_component_button = QPushButton("+ Add Component") + self.add_component_button.setObjectName("add_component_button") + self.add_component_button.clicked.connect(self.add_component_layout) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + self.button_h_layout.addWidget(next_button) + + # Add the initial component layout + self.add_component_layout() + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + left_panel_vlayout.addWidget(self.scroll_area) + + def add_component_layout(self): + new_component = ComponentWidget(self) + self.component_widgets.append(new_component) + new_component.remove_component_button.clicked.connect(lambda: self.remove_component_layout(new_component)) + + # Temporarily remove button_h_layout and add_component_button for insertion + if self.scroll_content_layout.indexOf(self.add_component_button) != -1: + self.scroll_content_layout.removeWidget(self.add_component_button) + if self.scroll_content_layout.indexOf(self.button_h_layout) != -1: + self.scroll_content_layout.removeItem(self.button_h_layout) + + # Insert the new component + self.scroll_content_layout.addWidget(new_component) + + # Re-add the 'Add Component' button + self.scroll_content_layout.addWidget(self.add_component_button, alignment=Qt.AlignCenter) + + # Re-add the navigation buttons layout + self.scroll_content_layout.addLayout(self.button_h_layout) + + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def remove_component_layout(self, component_to_remove): + if component_to_remove in self.component_widgets: + self.scroll_content_layout.removeWidget(component_to_remove) + self.component_widgets.remove(component_to_remove) + component_to_remove.deleteLater() + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def expand_scroll_area(self): + self.central_widget.layout().invalidate() + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(AuxiliaryWorks(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/foundation_widget.py b/src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/foundation_widget.py new file mode 100644 index 0000000..cdf193e --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/foundation_widget.py @@ -0,0 +1,534 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +import sys + +class ComponentWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + + self.material_rows = [] # To store references to widgets in each material row + self.current_material_row_idx = 1 # Start index for material rows (0 is header) + + self.init_ui() + + def init_ui(self): + self.component_first_scroll_content_layout = QVBoxLayout(self) # Set QVBoxLayout directly on self + self.component_first_scroll_content_layout.setContentsMargins(10, 10, 10, 10) + self.component_first_scroll_content_layout.setSpacing(10) + + # Component Label and Dropdown with a remove button + component_header_layout = QHBoxLayout() + component_label = QLabel("Component:") + component_label.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(component_label) + + self.component_combobox = QComboBox() + self.component_combobox.addItems(["Earthwork", "Concrete", "Steel", "Wood"]) + self.component_combobox.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(self.component_combobox) + + # Add a remove button for the component + self.remove_component_button = QPushButton("x") + self.remove_component_button.setFixedSize(24, 24) + self.remove_component_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + line-height:12px; + color: #CC0000; + padding: 0px; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + component_header_layout.addWidget(self.remove_component_button) + component_header_layout.addStretch(1) + + self.component_first_scroll_content_layout.addLayout(component_header_layout) + + # --- Material Details Grid Layout --- + self.material_grid_layout = QGridLayout() + self.material_grid_layout.setHorizontalSpacing(10) + self.material_grid_layout.setVerticalSpacing(5) + + # Header Row + headers = ["Type of Material", "Grade", "Quantity", "Unit", "Rate", "Rate Data Source"] + for col, header_text in enumerate(headers): + label = QLabel(header_text) + label.setAlignment(Qt.AlignCenter) + label.setObjectName("MaterialGridLabel") + self.material_grid_layout.addWidget(label, 0, col) + + # Column stretch factors are removed as all input widgets will now have fixed widths. + # This allows the grid to size columns based on the fixed widget sizes and spacing. + + self.component_first_scroll_content_layout.addLayout(self.material_grid_layout) + + # Add initial two material rows + self.add_material_row() + self.add_material_row() + + # --- Add Material Button --- + self.add_material_button = QPushButton("+ Add Material") + self.add_material_button.setObjectName("add_material_button") + self.add_material_button.clicked.connect(self.add_material_row) + self.component_first_scroll_content_layout.addWidget(self.add_material_button, alignment=Qt.AlignCenter) + + + def add_material_row(self): + row_widgets = {} + row_idx = self.current_material_row_idx + + # Set fixed width for all input widgets to 80px, as requested. + # Note: This might make wider text truncate or appear cramped depending on content. + fixed_input_width = 80 + + type_material_combo = QComboBox() + type_material_combo.addItems(["Sand", "Gravel", "Cement", "Water", "Admixture", "Rebar", "Other"]) + type_material_combo.setObjectName("MaterialGridInput") + type_material_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(type_material_combo, row_idx, 0) + row_widgets['type'] = type_material_combo + + grade_combo = QComboBox() + grade_combo.addItems(["A", "B", "C", "X", "Y", "Z", "N/A"]) + grade_combo.setObjectName("MaterialGridInput") + grade_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(grade_combo, row_idx, 1) + row_widgets['grade'] = grade_combo + + quantity_edit = QLineEdit() + quantity_edit.setPlaceholderText("0") + quantity_edit.setObjectName("MaterialGridInput") + quantity_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(quantity_edit, row_idx, 2) + row_widgets['quantity'] = quantity_edit + + unit_combo_m3 = QComboBox() + unit_combo_m3.addItems(["m³", "ft³", "kg", "ton", "litre"]) + unit_combo_m3.setObjectName("MaterialGridInput") + unit_combo_m3.setFixedWidth(fixed_input_width) # Previously 80, now consistent with others + self.material_grid_layout.addWidget(unit_combo_m3, row_idx, 3) # Directly add, no extra layout + row_widgets['unit_m3'] = unit_combo_m3 + + rate_edit = QLineEdit() + rate_edit.setPlaceholderText("0.00") + rate_edit.setObjectName("MaterialGridInput") + rate_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_edit, row_idx, 4) + row_widgets['rate'] = rate_edit + + rate_data_source_edit = QLineEdit() + rate_data_source_edit.setObjectName("MaterialGridInput") + rate_data_source_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_data_source_edit, row_idx, 5) + row_widgets['rate_data_source'] = rate_data_source_edit + + remove_button = QPushButton("x") + remove_button.setFixedSize(24, 24) + remove_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + line-height:12px; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + remove_button.clicked.connect(lambda: self.remove_material_row_by_widgets(row_widgets)) + self.material_grid_layout.addWidget(remove_button, row_idx, 6) + row_widgets['remove_button'] = remove_button + + self.material_rows.append(row_widgets) + self.current_material_row_idx += 1 + self.updateGeometry() # Call updateGeometry on self, not on the scroll content widget + self.adjustSize() # Adjust the size of the component widget + + + def remove_material_row_by_widgets(self, row_widgets_to_remove): + if row_widgets_to_remove not in self.material_rows: + return + + row_idx_in_grid = -1 + for i, row_dict in enumerate(self.material_rows): + if row_dict == row_widgets_to_remove: + row_idx_in_grid = i + 1 # +1 because row 0 is header + break + + if row_idx_in_grid == -1: + return + + for col in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(row_idx_in_grid, col) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + widget.deleteLater() + elif item.layout(): + layout = item.layout() + while layout.count(): + sub_item = layout.takeAt(0) + if sub_item.widget(): + sub_item.widget().deleteLater() + self.material_grid_layout.removeItem(layout) + + self.material_rows.remove(row_widgets_to_remove) + self.current_material_row_idx -= 1 + + for r_idx in range(row_idx_in_grid, self.current_material_row_idx + 1): + for c_idx in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(r_idx + 1, c_idx) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + self.material_grid_layout.addWidget(widget, r_idx, c_idx) + elif item.layout(): + layout = item.layout() + self.material_grid_layout.removeItem(layout) + self.material_grid_layout.addLayout(layout, r_idx, c_idx) + + self.updateGeometry() # Call updateGeometry on self + self.update() # Call update on self + self.material_grid_layout.invalidate() + self.adjustSize() # Adjust the size of the component widget + +class Foundation(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("central_panel_widget") + self.component_widgets = [] # To store references to each ComponentWidget instance + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Styling for component_first_widget (the container for the nested scroll area) */ + #component_first_widget { + background-color: transparent; + margin-top: 10px; /* Add some space above each component block */ + } + + #component_first_scroll_content_widget { /* This now applies directly to ComponentWidget itself */ + background-color: #FFFFFF; + padding: 10px; + + border-radius: 8px; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* Styling for QComboBox */ + QComboBox { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 30px; + height: 30px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + + /* Styling for material grid elements */ + #MaterialGridLabel { + font-weight: bold; + color: #3F3E5E; + padding: 5px; + text-align: center; + } + #MaterialGridInput { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background-color: #FFFFFF; + } + #MaterialGridInput:focus { + border: 1px solid #DDDCE0; + background-color: #FFFFFF; + } + /* IMPROVED CSS FOR ADD MATERIAL/COMPONENT BUTTONS */ + QPushButton#add_material_button, QPushButton#add_component_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + } + QPushButton#add_material_button:hover, QPushButton#add_component_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#add_material_button:pressed, QPushButton#add_component_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + top_h_layout_left_panel = QHBoxLayout() + top_button_left_panel = QPushButton("Foundation") + top_h_layout_left_panel.addWidget(top_button_left_panel) + top_button_left_panel.setIcon(QIcon("resources/close.png")) + top_button_left_panel.setIconSize(QSize(13, 13)) + top_button_left_panel.setObjectName("top_button_left_panel") + top_button_left_panel.clicked.connect(self.close_widget) + top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + top_h_layout_left_panel.addWidget(top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Preferred)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # Create the Add Component button and connect it + self.add_component_button = QPushButton("+ Add Component") + self.add_component_button.setObjectName("add_component_button") + self.add_component_button.clicked.connect(self.add_component_layout) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + self.button_h_layout.addWidget(next_button) + + # Add the initial component layout + self.add_component_layout() + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + left_panel_vlayout.addWidget(self.scroll_area) + + def add_component_layout(self): + new_component = ComponentWidget(self) + self.component_widgets.append(new_component) + new_component.remove_component_button.clicked.connect(lambda: self.remove_component_layout(new_component)) + + # Temporarily remove button_h_layout and add_component_button for insertion + if self.scroll_content_layout.indexOf(self.add_component_button) != -1: + self.scroll_content_layout.removeWidget(self.add_component_button) + if self.scroll_content_layout.indexOf(self.button_h_layout) != -1: + self.scroll_content_layout.removeItem(self.button_h_layout) + + # Insert the new component + self.scroll_content_layout.addWidget(new_component) + + # Re-add the 'Add Component' button + self.scroll_content_layout.addWidget(self.add_component_button, alignment=Qt.AlignCenter) + + # Re-add the navigation buttons layout + self.scroll_content_layout.addLayout(self.button_h_layout) + + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def remove_component_layout(self, component_to_remove): + if component_to_remove in self.component_widgets: + self.scroll_content_layout.removeWidget(component_to_remove) + self.component_widgets.remove(component_to_remove) + component_to_remove.deleteLater() + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def expand_scroll_area(self): + self.central_widget.layout().invalidate() + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(Foundation(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/sub_structure_widget.py b/src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/sub_structure_widget.py new file mode 100644 index 0000000..adfc183 --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/sub_structure_widget.py @@ -0,0 +1,535 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +import sys + +class ComponentWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + + self.material_rows = [] # To store references to widgets in each material row + self.current_material_row_idx = 1 # Start index for material rows (0 is header) + + self.init_ui() + + def init_ui(self): + self.component_first_scroll_content_layout = QVBoxLayout(self) # Set QVBoxLayout directly on self + self.component_first_scroll_content_layout.setContentsMargins(10, 10, 10, 10) + self.component_first_scroll_content_layout.setSpacing(10) + + # Component Label and Dropdown with a remove button + component_header_layout = QHBoxLayout() + component_label = QLabel("Component:") + component_label.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(component_label) + + self.component_combobox = QComboBox() + self.component_combobox.addItems(["Earthwork", "Concrete", "Steel", "Wood"]) + self.component_combobox.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(self.component_combobox) + + # Add a remove button for the component + self.remove_component_button = QPushButton("x") + self.remove_component_button.setFixedSize(24, 24) + self.remove_component_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + component_header_layout.addWidget(self.remove_component_button) + component_header_layout.addStretch(1) + + self.component_first_scroll_content_layout.addLayout(component_header_layout) + + # --- Material Details Grid Layout --- + self.material_grid_layout = QGridLayout() + self.material_grid_layout.setHorizontalSpacing(10) + self.material_grid_layout.setVerticalSpacing(5) + + # Header Row + headers = ["Type of Material", "Grade", "Quantity", "Unit", "Rate", "Rate Data Source"] + for col, header_text in enumerate(headers): + label = QLabel(header_text) + label.setAlignment(Qt.AlignCenter) + label.setObjectName("MaterialGridLabel") + self.material_grid_layout.addWidget(label, 0, col) + + # Column stretch factors are removed as all input widgets will now have fixed widths. + # This allows the grid to size columns based on the fixed widget sizes and spacing. + + self.component_first_scroll_content_layout.addLayout(self.material_grid_layout) + + # Add initial two material rows + self.add_material_row() + self.add_material_row() + + # --- Add Material Button --- + self.add_material_button = QPushButton("+ Add Material") + self.add_material_button.setObjectName("add_material_button") + self.add_material_button.clicked.connect(self.add_material_row) + self.component_first_scroll_content_layout.addWidget(self.add_material_button, alignment=Qt.AlignCenter) + + + def add_material_row(self): + row_widgets = {} + row_idx = self.current_material_row_idx + + # Set fixed width for all input widgets to 80px, as requested. + # Note: This might make wider text truncate or appear cramped depending on content. + fixed_input_width = 80 + + type_material_combo = QComboBox() + type_material_combo.addItems(["Sand", "Gravel", "Cement", "Water", "Admixture", "Rebar", "Other"]) + type_material_combo.setObjectName("MaterialGridInput") + type_material_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(type_material_combo, row_idx, 0) + row_widgets['type'] = type_material_combo + + grade_combo = QComboBox() + grade_combo.addItems(["A", "B", "C", "X", "Y", "Z", "N/A"]) + grade_combo.setObjectName("MaterialGridInput") + grade_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(grade_combo, row_idx, 1) + row_widgets['grade'] = grade_combo + + quantity_edit = QLineEdit() + quantity_edit.setPlaceholderText("0") + quantity_edit.setObjectName("MaterialGridInput") + quantity_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(quantity_edit, row_idx, 2) + row_widgets['quantity'] = quantity_edit + + unit_combo_m3 = QComboBox() + unit_combo_m3.addItems(["m³", "ft³", "kg", "ton", "litre"]) + unit_combo_m3.setObjectName("MaterialGridInput") + unit_combo_m3.setFixedWidth(fixed_input_width) # Previously 80, now consistent with others + self.material_grid_layout.addWidget(unit_combo_m3, row_idx, 3) # Directly add, no extra layout + row_widgets['unit_m3'] = unit_combo_m3 + + rate_edit = QLineEdit() + rate_edit.setPlaceholderText("0.00") + rate_edit.setObjectName("MaterialGridInput") + rate_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_edit, row_idx, 4) + row_widgets['rate'] = rate_edit + + rate_data_source_edit = QLineEdit() + rate_data_source_edit.setObjectName("MaterialGridInput") + rate_data_source_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_data_source_edit, row_idx, 5) + row_widgets['rate_data_source'] = rate_data_source_edit + + remove_button = QPushButton("x") + remove_button.setFixedSize(24, 24) + remove_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + color: white; + } + """) + remove_button.clicked.connect(lambda: self.remove_material_row_by_widgets(row_widgets)) + self.material_grid_layout.addWidget(remove_button, row_idx, 6) + row_widgets['remove_button'] = remove_button + + self.material_rows.append(row_widgets) + self.current_material_row_idx += 1 + self.updateGeometry() # Call updateGeometry on self, not on the scroll content widget + self.adjustSize() # Adjust the size of the component widget + + + def remove_material_row_by_widgets(self, row_widgets_to_remove): + if row_widgets_to_remove not in self.material_rows: + return + + row_idx_in_grid = -1 + for i, row_dict in enumerate(self.material_rows): + if row_dict == row_widgets_to_remove: + row_idx_in_grid = i + 1 # +1 because row 0 is header + break + + if row_idx_in_grid == -1: + return + + for col in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(row_idx_in_grid, col) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + widget.deleteLater() + elif item.layout(): + layout = item.layout() + while layout.count(): + sub_item = layout.takeAt(0) + if sub_item.widget(): + sub_item.widget().deleteLater() + self.material_grid_layout.removeItem(layout) + + self.material_rows.remove(row_widgets_to_remove) + self.current_material_row_idx -= 1 + + for r_idx in range(row_idx_in_grid, self.current_material_row_idx + 1): + for c_idx in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(r_idx + 1, c_idx) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + self.material_grid_layout.addWidget(widget, r_idx, c_idx) + elif item.layout(): + layout = item.layout() + self.material_grid_layout.removeItem(layout) + self.material_grid_layout.addLayout(layout, r_idx, c_idx) + + self.updateGeometry() # Call updateGeometry on self + self.update() # Call update on self + self.material_grid_layout.invalidate() + self.adjustSize() # Adjust the size of the component widget + +class SubStructure(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__(parent) + self.parent = parent + self.setObjectName("central_panel_widget") + self.component_widgets = [] # To store references to each ComponentWidget instance + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Styling for component_first_widget (the container for the nested scroll area) */ + #component_first_widget { + background-color: transparent; + margin-top: 10px; /* Add some space above each component block */ + } + + #component_first_scroll_content_widget { /* This now applies directly to ComponentWidget itself */ + background-color: #FFFFFF; + padding: 10px; + + border-radius: 8px; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* Styling for QComboBox */ + QComboBox { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 30px; + height: 30px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + + /* Styling for material grid elements */ + #MaterialGridLabel { + font-weight: bold; + color: #3F3E5E; + padding: 5px; + text-align: center; + } + #MaterialGridInput { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background-color: #FFFFFF; + } + #MaterialGridInput:focus { + border: 1px solid #DDDCE0; + background-color: #FFFFFF; + } + /* IMPROVED CSS FOR ADD MATERIAL/COMPONENT BUTTONS */ + QPushButton#add_material_button, QPushButton#add_component_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + } + QPushButton#add_material_button:hover, QPushButton#add_component_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#add_material_button:pressed, QPushButton#add_component_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + top_h_layout_left_panel = QHBoxLayout() + top_button_left_panel = QPushButton("Sub-Structure") + top_h_layout_left_panel.addWidget(top_button_left_panel) + top_button_left_panel.setIcon(QIcon("resources/close.png")) + top_button_left_panel.setIconSize(QSize(13, 13)) + top_button_left_panel.setObjectName("top_button_left_panel") + top_button_left_panel.clicked.connect(self.close_widget) + top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + top_button_left_panel.setCursor(Qt.CursorShape.PointingHandCursor) + top_h_layout_left_panel.addWidget(top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Preferred)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # Create the Add Component button and connect it + self.add_component_button = QPushButton("+ Add Component") + self.add_component_button.setObjectName("add_component_button") + self.add_component_button.clicked.connect(self.add_component_layout) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + self.button_h_layout.addWidget(next_button) + + # Add the initial component layout + self.add_component_layout() + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + left_panel_vlayout.addWidget(self.scroll_area) + + def add_component_layout(self): + new_component = ComponentWidget(self) + self.component_widgets.append(new_component) + new_component.remove_component_button.clicked.connect(lambda: self.remove_component_layout(new_component)) + + # Temporarily remove button_h_layout and add_component_button for insertion + if self.scroll_content_layout.indexOf(self.add_component_button) != -1: + self.scroll_content_layout.removeWidget(self.add_component_button) + if self.scroll_content_layout.indexOf(self.button_h_layout) != -1: + self.scroll_content_layout.removeItem(self.button_h_layout) + + # Insert the new component + self.scroll_content_layout.addWidget(new_component) + + # Re-add the 'Add Component' button + self.scroll_content_layout.addWidget(self.add_component_button, alignment=Qt.AlignCenter) + + # Re-add the navigation buttons layout + self.scroll_content_layout.addLayout(self.button_h_layout) + + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def remove_component_layout(self, component_to_remove): + if component_to_remove in self.component_widgets: + self.scroll_content_layout.removeWidget(component_to_remove) + self.component_widgets.remove(component_to_remove) + component_to_remove.deleteLater() + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def expand_scroll_area(self): + self.central_widget.layout().invalidate() + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(SubStructure(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/super_structure_widget.py b/src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/super_structure_widget.py new file mode 100644 index 0000000..c62490f --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/structure_works_data/super_structure_widget.py @@ -0,0 +1,534 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +import sys + +class ComponentWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + + self.material_rows = [] # To store references to widgets in each material row + self.current_material_row_idx = 1 # Start index for material rows (0 is header) + + self.init_ui() + + def init_ui(self): + self.component_first_scroll_content_layout = QVBoxLayout(self) # Set QVBoxLayout directly on self + self.component_first_scroll_content_layout.setContentsMargins(10, 10, 10, 10) + self.component_first_scroll_content_layout.setSpacing(10) + + # Component Label and Dropdown with a remove button + component_header_layout = QHBoxLayout() + component_label = QLabel("Component:") + component_label.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(component_label) + + self.component_combobox = QComboBox() + self.component_combobox.addItems(["Earthwork", "Concrete", "Steel", "Wood"]) + self.component_combobox.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(self.component_combobox) + + # Add a remove button for the component + self.remove_component_button = QPushButton("x") + self.remove_component_button.setFixedSize(24, 24) + self.remove_component_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + line-height:12px; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + component_header_layout.addWidget(self.remove_component_button) + component_header_layout.addStretch(1) + + self.component_first_scroll_content_layout.addLayout(component_header_layout) + + # --- Material Details Grid Layout --- + self.material_grid_layout = QGridLayout() + self.material_grid_layout.setHorizontalSpacing(10) + self.material_grid_layout.setVerticalSpacing(5) + + # Header Row + headers = ["Type of Material", "Grade", "Quantity", "Unit", "Rate", "Rate Data Source"] + for col, header_text in enumerate(headers): + label = QLabel(header_text) + label.setAlignment(Qt.AlignCenter) + label.setObjectName("MaterialGridLabel") + self.material_grid_layout.addWidget(label, 0, col) + + # Column stretch factors are removed as all input widgets will now have fixed widths. + # This allows the grid to size columns based on the fixed widget sizes and spacing. + + self.component_first_scroll_content_layout.addLayout(self.material_grid_layout) + + # Add initial two material rows + self.add_material_row() + self.add_material_row() + + # --- Add Material Button --- + self.add_material_button = QPushButton("+ Add Material") + self.add_material_button.setObjectName("add_material_button") + self.add_material_button.clicked.connect(self.add_material_row) + self.component_first_scroll_content_layout.addWidget(self.add_material_button, alignment=Qt.AlignCenter) + + + def add_material_row(self): + row_widgets = {} + row_idx = self.current_material_row_idx + + # Set fixed width for all input widgets to 80px, as requested. + # Note: This might make wider text truncate or appear cramped depending on content. + fixed_input_width = 80 + + type_material_combo = QComboBox() + type_material_combo.addItems(["Sand", "Gravel", "Cement", "Water", "Admixture", "Rebar", "Other"]) + type_material_combo.setObjectName("MaterialGridInput") + type_material_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(type_material_combo, row_idx, 0) + row_widgets['type'] = type_material_combo + + grade_combo = QComboBox() + grade_combo.addItems(["A", "B", "C", "X", "Y", "Z", "N/A"]) + grade_combo.setObjectName("MaterialGridInput") + grade_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(grade_combo, row_idx, 1) + row_widgets['grade'] = grade_combo + + quantity_edit = QLineEdit() + quantity_edit.setPlaceholderText("0") + quantity_edit.setObjectName("MaterialGridInput") + quantity_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(quantity_edit, row_idx, 2) + row_widgets['quantity'] = quantity_edit + + unit_combo_m3 = QComboBox() + unit_combo_m3.addItems(["m³", "ft³", "kg", "ton", "litre"]) + unit_combo_m3.setObjectName("MaterialGridInput") + unit_combo_m3.setFixedWidth(fixed_input_width) # Previously 80, now consistent with others + self.material_grid_layout.addWidget(unit_combo_m3, row_idx, 3) # Directly add, no extra layout + row_widgets['unit_m3'] = unit_combo_m3 + + rate_edit = QLineEdit() + rate_edit.setPlaceholderText("0.00") + rate_edit.setObjectName("MaterialGridInput") + rate_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_edit, row_idx, 4) + row_widgets['rate'] = rate_edit + + rate_data_source_edit = QLineEdit() + rate_data_source_edit.setObjectName("MaterialGridInput") + rate_data_source_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_data_source_edit, row_idx, 5) + row_widgets['rate_data_source'] = rate_data_source_edit + + remove_button = QPushButton("x") + remove_button.setFixedSize(24, 24) + remove_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + line-height:12px; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + remove_button.clicked.connect(lambda: self.remove_material_row_by_widgets(row_widgets)) + self.material_grid_layout.addWidget(remove_button, row_idx, 6) + row_widgets['remove_button'] = remove_button + + self.material_rows.append(row_widgets) + self.current_material_row_idx += 1 + self.updateGeometry() # Call updateGeometry on self, not on the scroll content widget + self.adjustSize() # Adjust the size of the component widget + + + def remove_material_row_by_widgets(self, row_widgets_to_remove): + if row_widgets_to_remove not in self.material_rows: + return + + row_idx_in_grid = -1 + for i, row_dict in enumerate(self.material_rows): + if row_dict == row_widgets_to_remove: + row_idx_in_grid = i + 1 # +1 because row 0 is header + break + + if row_idx_in_grid == -1: + return + + for col in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(row_idx_in_grid, col) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + widget.deleteLater() + elif item.layout(): + layout = item.layout() + while layout.count(): + sub_item = layout.takeAt(0) + if sub_item.widget(): + sub_item.widget().deleteLater() + self.material_grid_layout.removeItem(layout) + + self.material_rows.remove(row_widgets_to_remove) + self.current_material_row_idx -= 1 + + for r_idx in range(row_idx_in_grid, self.current_material_row_idx + 1): + for c_idx in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(r_idx + 1, c_idx) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + self.material_grid_layout.addWidget(widget, r_idx, c_idx) + elif item.layout(): + layout = item.layout() + self.material_grid_layout.removeItem(layout) + self.material_grid_layout.addLayout(layout, r_idx, c_idx) + + self.updateGeometry() # Call updateGeometry on self + self.update() # Call update on self + self.material_grid_layout.invalidate() + self.adjustSize() # Adjust the size of the component widget + +class SuperStructure(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("central_panel_widget") + self.component_widgets = [] # To store references to each ComponentWidget instance + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Styling for component_first_widget (the container for the nested scroll area) */ + #component_first_widget { + background-color: transparent; + margin-top: 10px; /* Add some space above each component block */ + } + + #component_first_scroll_content_widget { /* This now applies directly to ComponentWidget itself */ + background-color: #FFFFFF; + padding: 10px; + + border-radius: 8px; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* Styling for QComboBox */ + QComboBox { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 30px; + height: 30px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + + /* Styling for material grid elements */ + #MaterialGridLabel { + font-weight: bold; + color: #3F3E5E; + padding: 5px; + text-align: center; + } + #MaterialGridInput { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background-color: #FFFFFF; + } + #MaterialGridInput:focus { + border: 1px solid #DDDCE0; + background-color: #FFFFFF; + } + /* IMPROVED CSS FOR ADD MATERIAL/COMPONENT BUTTONS */ + QPushButton#add_material_button, QPushButton#add_component_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + } + QPushButton#add_material_button:hover, QPushButton#add_component_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#add_material_button:pressed, QPushButton#add_component_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + top_h_layout_left_panel = QHBoxLayout() + top_button_left_panel = QPushButton("Super-Structure") + top_h_layout_left_panel.addWidget(top_button_left_panel) + top_button_left_panel.setIcon(QIcon("resources/close.png")) + top_button_left_panel.setIconSize(QSize(13, 13)) + top_button_left_panel.setObjectName("top_button_left_panel") + top_button_left_panel.clicked.connect(self.close_widget) + top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + top_h_layout_left_panel.addWidget(top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Preferred)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # Create the Add Component button and connect it + self.add_component_button = QPushButton("+ Add Component") + self.add_component_button.setObjectName("add_component_button") + self.add_component_button.clicked.connect(self.add_component_layout) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + self.button_h_layout.addWidget(next_button) + + # Add the initial component layout + self.add_component_layout() + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + left_panel_vlayout.addWidget(self.scroll_area) + + def add_component_layout(self): + new_component = ComponentWidget(self) + self.component_widgets.append(new_component) + new_component.remove_component_button.clicked.connect(lambda: self.remove_component_layout(new_component)) + + # Temporarily remove button_h_layout and add_component_button for insertion + if self.scroll_content_layout.indexOf(self.add_component_button) != -1: + self.scroll_content_layout.removeWidget(self.add_component_button) + if self.scroll_content_layout.indexOf(self.button_h_layout) != -1: + self.scroll_content_layout.removeItem(self.button_h_layout) + + # Insert the new component + self.scroll_content_layout.addWidget(new_component) + + # Re-add the 'Add Component' button + self.scroll_content_layout.addWidget(self.add_component_button, alignment=Qt.AlignCenter) + + # Re-add the navigation buttons layout + self.scroll_content_layout.addLayout(self.button_h_layout) + + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def remove_component_layout(self, component_to_remove): + if component_to_remove in self.component_widgets: + self.scroll_content_layout.removeWidget(component_to_remove) + self.component_widgets.remove(component_to_remove) + component_to_remove.deleteLater() + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def expand_scroll_area(self): + self.central_widget.layout().invalidate() + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(SuperStructure(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/ui/widgets/title_bar.py b/src/osbridgelcca/desktop_app/ui/widgets/title_bar.py new file mode 100644 index 0000000..a19aefe --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/title_bar.py @@ -0,0 +1,135 @@ +from PySide6.QtCore import (QSize, Qt) +from PySide6.QtGui import (QIcon, QPixmap) +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QWidget, QLabel) +from PySide6.QtGui import QMouseEvent + +class CustomTitleBar(QWidget): + """ + A custom title bar widget for a frameless QMainWindow. + It provides window dragging, minimize, maximize/restore, and close buttons + with custom styling and SVG icons. + """ + def __init__(self, parent=None): + super().__init__(parent) + self.parent_window = parent + self.setObjectName("custom_title_bar") + self.setFixedHeight(30) + # Set a single stylesheet for the entire title bar and all its children + self.setStyleSheet(""" + #custom_title_bar { + background-color: #45913E; + } + #custom_title_bar QLabel { + background-color: #45913E; + color: white; + } + #custom_title_bar QPushButton { + background-color: #45913E; + color: white; + border: none; + padding: 0px; + } + #custom_title_bar QPushButton:hover { + background-color: #55a04c; + } + #custom_title_bar QPushButton:pressed { + background-color: #3d7936; + } + #custom_title_bar QPushButton#close_button:hover { + background-color: #E81123; + } + #custom_title_bar QPushButton#close_button:pressed { + background-color: #F1707A; + } + """) + + # Main layout + self.layout = QHBoxLayout() + self.layout.setContentsMargins(0, 0, 0, 0) + self.layout.setSpacing(0) + self.setLayout(self.layout) + + # Left: Icon + self.icon_label = QLabel() + self.icon_label.setFixedSize(30, 30) + self.icon_label.setStyleSheet("padding: 5px;") + # self.icon_label.setPixmap(QPixmap("resources/osdag_logo.svg").scaled(20, 20, Qt.KeepAspectRatio, Qt.SmoothTransformation)) + self.layout.addWidget(self.icon_label) + + # Middle: Title (centered) + self.title_label = QLabel("My Custom App") + self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.title_label.setStyleSheet("font-weight: bold;") + self.layout.addWidget(self.title_label, 1) # Add stretch factor of 1 to center the title + + # Right: Control Buttons + self.btn_size = QSize(46, 30) + + # Helper function to create a styled button + def create_button(icon_svg, is_close=False): + btn = QPushButton() + btn.setFixedSize(self.btn_size) + btn.setIcon(QIcon(QPixmap.fromImage(QPixmap(icon_svg).toImage()))) + btn.setIconSize(QSize(14, 14)) + if is_close: + btn.setObjectName("close_button") + return btn + + # Control buttons + self.minimize_button = create_button("resources/window_minimize.svg") + self.minimize_button.clicked.connect(self.parent_window.showMinimized) + self.layout.addWidget(self.minimize_button) + + self.maximize_button = create_button("resources/window_maximize.svg") + self.maximize_button.clicked.connect(self.toggle_maximize_restore) + self.layout.addWidget(self.maximize_button) + + self.close_button = create_button("resources/window_close.svg", is_close=True) + self.close_button.clicked.connect(self.parent_window.close) + self.layout.addWidget(self.close_button) + + self.start_pos = None + self.start_geometry = None + + def set_maximize_icon(self): + self.maximize_button.setIcon(QIcon(QPixmap.fromImage(QPixmap("resources/window_maximize.svg").toImage()))) + + def set_restore_icon(self): + self.maximize_button.setIcon(QIcon(QPixmap.fromImage(QPixmap("resources/window_restore.svg").toImage()))) + + def toggle_maximize_restore(self): + """Toggles between maximized and normal window states and updates the icon.""" + if self.parent_window.isMaximized(): + self.parent_window.showNormal() + self.set_maximize_icon() + else: + self.parent_window.showMaximized() + self.set_restore_icon() + + # --- Window Dragging Functionality --- + def mousePressEvent(self, event: QMouseEvent): + """Records initial position for dragging.""" + if event.button() == Qt.LeftButton and not self.parent_window.isMaximized(): + self.start_pos = event.globalPosition().toPoint() + self.start_geometry = self.parent_window.geometry() + event.accept() + else: + event.ignore() + + def mouseMoveEvent(self, event: QMouseEvent): + """Moves the window based on mouse movement.""" + if event.buttons() == Qt.LeftButton and self.start_pos and not self.parent_window.isMaximized(): + delta = event.globalPosition().toPoint() - self.start_pos + new_x = self.start_geometry.x() + delta.x() + new_y = self.start_geometry.y() + delta.y() + self.parent_window.move(new_x, new_y) + event.accept() + else: + event.ignore() + + def mouseReleaseEvent(self, event: QMouseEvent): + """Resets dragging state.""" + if not self.parent_window.isMaximized(): + self.start_pos = None + self.start_geometry = None + event.accept() diff --git a/src/osbridgelcca/desktop_app/ui/widgets/tutorial_widget_left.py b/src/osbridgelcca/desktop_app/ui/widgets/tutorial_widget_left.py new file mode 100644 index 0000000..8beaa5b --- /dev/null +++ b/src/osbridgelcca/desktop_app/ui/widgets/tutorial_widget_left.py @@ -0,0 +1,282 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, QSize, Qt, Signal +from PySide6.QtGui import (QIcon) +from PySide6.QtWidgets import (QHBoxLayout, QTextEdit, QScrollArea, QSpacerItem, QSizePolicy, + QPushButton, QWidget, QLabel, QVBoxLayout) +import sys + +class TutorialWidget(QWidget): + closed = Signal() + def __init__(self): + super().__init__() + self.setObjectName("left_panel_widget") + self.setStyleSheet(""" + #left_panel_widget { + background-color: #F8F8F8; /* Light gray/off-white background */ + border-radius: 8px; /* Slightly rounded corners for the entire body_widget */ + border: none; + } + #left_panel_widget QLabel { + color: #333333; /* Darker text for content */ + font-size: 12px; + /* No border or padding here, managed by layout margins */ + } + #left_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + /* Styling for the scroll area */ + QScrollArea { + border: 1px solid #000000; /* No border for the scroll area itself */ + background-color: transparent; /* Make background transparent to show widget's background */ + outline: none; + } + QScrollArea > QWidget { /* This targets the widget *inside* the scroll area */ + background-color: transparent; /* Inherit parent background */ + } + + /* Scrollbar styling */ + QScrollBar:vertical { + border: 1px solid #E0E0E0; /* Lighter border for scrollbar area */ + background: #F0F0F0; /* Light gray track */ + width: 12px; /* Width of the vertical scrollbar */ + margin: 18px 0px 18px 0px; /* Space for arrows */ + border-radius: 6px; /* Rounded track */ + } + + QScrollBar::handle:vertical { + background: #C0C0C0; /* Medium gray handle */ + border: 1px solid #A0A0A0; /* Darker border for handle */ + min-height: 20px; + border-radius: 5px; /* Rounded handle */ + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); /* You might need to provide these icons */ + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + #left_panel_widget QPushButton#back_button { + background-color: #FFFFFF; + border: 1px solid #D0D0D0; + padding: 6px 8px; + margin: 8px; + color: #000000; + font-size: 13px; + border-radius: 8px; + } + #left_panel_widget QPushButton#back_button:hover { + background: qlineargradient( + x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #BBBBBB, + stop: 0.26 #E8E8E8, + stop: 1 #EDEDED + ); + border-color: #806C6C; + } + #left_panel_widget QPushButton#back_button:pressed { + background-color: #FFFFFF; + border-color: #606060; + } + + #left_panel_widget QPushButton#next_button { + background-color: #FFFFFF; + border: 1px solid #D0D0D0; + padding: 6px 8px; + color: #000000; + margin: 8px; + font-size: 13px; + border-radius: 8px; + } + #left_panel_widget QPushButton#next_button:hover { + background: qlineargradient( + x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #BBBBBB, + stop: 0.26 #E8E8E8, + stop: 1 #EDEDED + ); + border-color: #806C6C; + } + #left_panel_widget QPushButton#next_button:pressed { + background-color: #FFFFFF; + border-color: #606060; + } + + QPushButton#top_button_left_panel { + background-color: #F0E6E6; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #FDEFEF; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + QPushButton#top_button_left_panel:hover QIcon { + color: red; + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) # Add some padding inside the red area + left_panel_vlayout.setSpacing(0) # Spacing between major sections + + top_h_layout_left_panel = QHBoxLayout() + self.top_button_left_panel = QPushButton("Tutorials ") + self.top_button_left_panel.setIcon(QIcon("resources/close.png")) + self.top_button_left_panel.setIconSize(QSize(13, 13)) + self.top_button_left_panel.setObjectName("top_button_left_panel") + self.top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + self.top_button_left_panel.clicked.connect(self.close_widget) + top_h_layout_left_panel.addWidget(self.top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + bordered_spacer_widget = QWidget() + bordered_spacer_widget.setObjectName("bordered_spacer_widget") # Give it an object name for CSS + bordered_spacer_widget.setFixedHeight(50) + bordered_spacer_widget.setStyleSheet(""" + #bordered_spacer_widget { + background-color: #F0E6E6; + border-left: 1px solid #000000; + border-top: 1px solid #000000; + border-right: 1px solid #000000; + } + """) + left_panel_vlayout.addWidget(bordered_spacer_widget) + + scroll_area = QScrollArea() + scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_area.setWidget(scroll_content_widget) + + scroll_content_layout = QVBoxLayout(scroll_content_widget) + + middle_header_label = QLabel(""" +

+ 1/4
+ Welcome to
+ BLCCA Studio
+
+ """) + middle_header_label.setStyleSheet(""" + QLabel{ + font-size: 17px; + } + """) + middle_header_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + scroll_content_layout.addWidget(middle_header_label) + + + middle_body_label = QTextEdit() + middle_body_label.setReadOnly(True) + tutorial_text = """ + BLCCA Studio has a lot of features to offer. In the next few minutes, you will learn how to use BLCCA Studio efficiently, from setting up and managing projects to navigating the user interface. This tutorial will guide you through essential features, including customization options shortcuts, and export capabilities, ensuring a seamless workflow. Whether you're a beginner or an advanced user, this guide will help you unlock the full potential of BLCCA Studio and enhance your productivity. + """ + + middle_body_label.setHtml(tutorial_text) + middle_body_label.setAlignment(Qt.AlignmentFlag.AlignJustify) + middle_body_label.setStyleSheet(""" + QTextEdit { + font-size: 14px; + padding-top: 20px; + border-top: 2px solid #806C6C; + } + """) + scroll_content_layout.addWidget(middle_body_label) + scroll_content_widget.setStyleSheet("background-color: #FFF9F9;") + left_panel_vlayout.addWidget(scroll_area) + + bottom_widget = QWidget() + bottom_widget.setObjectName("bottom_widget") + bottom_widget.setStyleSheet(""" + #bottom_widget { + background-color: #F0E6E6; + border-left: 1px solid #000000; + border-bottom: 1px solid #000000; + border-right: 1px solid #000000; + } + """) + bottom_h_layout = QHBoxLayout(bottom_widget) + back_button = QPushButton("Back") + back_button.setObjectName("back_button") + next_button = QPushButton("Next") + next_button.setObjectName("next_button") + bottom_h_layout.addWidget(back_button) + bottom_h_layout.addWidget(next_button) + left_panel_vlayout.addWidget(bottom_widget) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addWidget(TutorialWidget(), 1) + +# self.main_h_layout.addStretch(4) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file From ddc935a238609058d3c8b97309fb1aea609f77b4 Mon Sep 17 00:00:00 2001 From: Prerna Date: Tue, 28 Oct 2025 15:14:54 -0700 Subject: [PATCH 2/2] Add desktop app with dependencies and UI components --- .gitignore | 1 + src/osbridgelcca/desktop_app/.gitignore | 84 + .../desktop_app/dependencies/d3js.js | 2 + .../desktop_app/graphs/bar_graph.py | 436 ++++ .../desktop_app/graphs/bubble_graph.py | 231 ++ src/osbridgelcca/desktop_app/graphs/data.csv | 29 + src/osbridgelcca/desktop_app/graphs/data1.csv | 29 + .../graphs/horizontal_bar_graph.py | 345 +++ .../desktop_app/graphs/pie_chart.py | 569 +++++ .../desktop_app/graphs/radial_bar_graph.py | 205 ++ src/osbridgelcca/desktop_app/main_template.py | 518 ++++ .../desktop_app/resources/AlataRegular.ttf | Bin 0 -> 348308 bytes .../desktop_app/resources/Group 4.svg | 80 + .../desktop_app/resources/arrow_down.png | Bin 0 -> 3992 bytes .../resources/arrow_down_selected.png | Bin 0 -> 4267 bytes .../desktop_app/resources/close.png | Bin 0 -> 6536 bytes .../desktop_app/resources/contact.svg | 6 + .../desktop_app/resources/country_arrow.png | Bin 0 -> 216 bytes .../desktop_app/resources/create_copy.svg | 4 + .../desktop_app/resources/edit_button.png | Bin 0 -> 11726 bytes .../desktop_app/resources/export.svg | 11 + .../desktop_app/resources/feedback.svg | 3 + .../desktop_app/resources/file_button.png | Bin 0 -> 1402 bytes .../desktop_app/resources/info.svg | 3 + .../desktop_app/resources/join_community.svg | 8 + .../desktop_app/resources/new.svg | 3 + .../desktop_app/resources/open.svg | 4 + .../resources/play-button-arrowhead.png | Bin 0 -> 6074 bytes .../resources/play-button-selected.png | Bin 0 -> 4537 bytes .../desktop_app/resources/print.svg | 3 + .../desktop_app/resources/rename.svg | 4 + .../desktop_app/resources/save.svg | 3 + .../desktop_app/resources/save_as.svg | 3 + .../desktop_app/resources/save_button.png | Bin 0 -> 8849 bytes .../desktop_app/resources/tab_close.png | Bin 0 -> 9732 bytes .../desktop_app/resources/version_history.svg | 3 + .../desktop_app/resources/video_tutorial.svg | 4 + .../desktop_app/resources/window_close.svg | 1 + .../desktop_app/resources/window_maximize.svg | 4 + .../desktop_app/resources/window_minimize.svg | 1 + .../desktop_app/resources/window_restore.svg | 3 + .../widgets/bridge_and_traffic_data.py | 429 ++++ .../carbon_emission_cost_data.py | 291 +++ .../carbon_emission_data.py | 513 ++++ .../desktop_app/widgets/comparison_widget.py | 873 +++++++ .../widgets/demolition_and_recycling_data.py | 317 +++ .../desktop_app/widgets/financial_data.py | 331 +++ .../widgets/maintenance_repair_data.py | 363 +++ .../widgets/project_details_left_widget.py | 378 +++ .../widgets/project_details_right_widget.py | 508 ++++ .../desktop_app/widgets/results_widget.py | 2117 +++++++++++++++++ .../auxiliary_works_widget.py | 568 +++++ .../structure_works_data/foundation_widget.py | 588 +++++ .../sub_structure_widget.py | 541 +++++ .../super_structure_widget.py | 542 +++++ .../desktop_app/widgets/tab_widget.py | 214 ++ .../desktop_app/widgets/tab_widget_backup.py | 205 ++ .../desktop_app/widgets/title_bar.py | 135 ++ .../widgets/tutorial_widget_left.py | 282 +++ .../desktop_app/widgets/utils/data.py | 299 +++ .../desktop_app/widgets/utils/database.py | 358 +++ 61 files changed, 12452 insertions(+) create mode 100644 src/osbridgelcca/desktop_app/.gitignore create mode 100644 src/osbridgelcca/desktop_app/dependencies/d3js.js create mode 100644 src/osbridgelcca/desktop_app/graphs/bar_graph.py create mode 100644 src/osbridgelcca/desktop_app/graphs/bubble_graph.py create mode 100644 src/osbridgelcca/desktop_app/graphs/data.csv create mode 100644 src/osbridgelcca/desktop_app/graphs/data1.csv create mode 100644 src/osbridgelcca/desktop_app/graphs/horizontal_bar_graph.py create mode 100644 src/osbridgelcca/desktop_app/graphs/pie_chart.py create mode 100644 src/osbridgelcca/desktop_app/graphs/radial_bar_graph.py create mode 100644 src/osbridgelcca/desktop_app/main_template.py create mode 100644 src/osbridgelcca/desktop_app/resources/AlataRegular.ttf create mode 100644 src/osbridgelcca/desktop_app/resources/Group 4.svg create mode 100644 src/osbridgelcca/desktop_app/resources/arrow_down.png create mode 100644 src/osbridgelcca/desktop_app/resources/arrow_down_selected.png create mode 100644 src/osbridgelcca/desktop_app/resources/close.png create mode 100644 src/osbridgelcca/desktop_app/resources/contact.svg create mode 100644 src/osbridgelcca/desktop_app/resources/country_arrow.png create mode 100644 src/osbridgelcca/desktop_app/resources/create_copy.svg create mode 100644 src/osbridgelcca/desktop_app/resources/edit_button.png create mode 100644 src/osbridgelcca/desktop_app/resources/export.svg create mode 100644 src/osbridgelcca/desktop_app/resources/feedback.svg create mode 100644 src/osbridgelcca/desktop_app/resources/file_button.png create mode 100644 src/osbridgelcca/desktop_app/resources/info.svg create mode 100644 src/osbridgelcca/desktop_app/resources/join_community.svg create mode 100644 src/osbridgelcca/desktop_app/resources/new.svg create mode 100644 src/osbridgelcca/desktop_app/resources/open.svg create mode 100644 src/osbridgelcca/desktop_app/resources/play-button-arrowhead.png create mode 100644 src/osbridgelcca/desktop_app/resources/play-button-selected.png create mode 100644 src/osbridgelcca/desktop_app/resources/print.svg create mode 100644 src/osbridgelcca/desktop_app/resources/rename.svg create mode 100644 src/osbridgelcca/desktop_app/resources/save.svg create mode 100644 src/osbridgelcca/desktop_app/resources/save_as.svg create mode 100644 src/osbridgelcca/desktop_app/resources/save_button.png create mode 100644 src/osbridgelcca/desktop_app/resources/tab_close.png create mode 100644 src/osbridgelcca/desktop_app/resources/version_history.svg create mode 100644 src/osbridgelcca/desktop_app/resources/video_tutorial.svg create mode 100644 src/osbridgelcca/desktop_app/resources/window_close.svg create mode 100644 src/osbridgelcca/desktop_app/resources/window_maximize.svg create mode 100644 src/osbridgelcca/desktop_app/resources/window_minimize.svg create mode 100644 src/osbridgelcca/desktop_app/resources/window_restore.svg create mode 100644 src/osbridgelcca/desktop_app/widgets/bridge_and_traffic_data.py create mode 100644 src/osbridgelcca/desktop_app/widgets/carbon_emission_data/carbon_emission_cost_data.py create mode 100644 src/osbridgelcca/desktop_app/widgets/carbon_emission_data/carbon_emission_data.py create mode 100644 src/osbridgelcca/desktop_app/widgets/comparison_widget.py create mode 100644 src/osbridgelcca/desktop_app/widgets/demolition_and_recycling_data.py create mode 100644 src/osbridgelcca/desktop_app/widgets/financial_data.py create mode 100644 src/osbridgelcca/desktop_app/widgets/maintenance_repair_data.py create mode 100644 src/osbridgelcca/desktop_app/widgets/project_details_left_widget.py create mode 100644 src/osbridgelcca/desktop_app/widgets/project_details_right_widget.py create mode 100644 src/osbridgelcca/desktop_app/widgets/results_widget.py create mode 100644 src/osbridgelcca/desktop_app/widgets/structure_works_data/auxiliary_works_widget.py create mode 100644 src/osbridgelcca/desktop_app/widgets/structure_works_data/foundation_widget.py create mode 100644 src/osbridgelcca/desktop_app/widgets/structure_works_data/sub_structure_widget.py create mode 100644 src/osbridgelcca/desktop_app/widgets/structure_works_data/super_structure_widget.py create mode 100644 src/osbridgelcca/desktop_app/widgets/tab_widget.py create mode 100644 src/osbridgelcca/desktop_app/widgets/tab_widget_backup.py create mode 100644 src/osbridgelcca/desktop_app/widgets/title_bar.py create mode 100644 src/osbridgelcca/desktop_app/widgets/tutorial_widget_left.py create mode 100644 src/osbridgelcca/desktop_app/widgets/utils/data.py create mode 100644 src/osbridgelcca/desktop_app/widgets/utils/database.py diff --git a/.gitignore b/.gitignore index 6dcf019..fc5027e 100644 --- a/.gitignore +++ b/.gitignore @@ -85,3 +85,4 @@ cicd/github_actions/*.log # Misc .DS_Store Thumbs.db +node_modules/ diff --git a/src/osbridgelcca/desktop_app/.gitignore b/src/osbridgelcca/desktop_app/.gitignore new file mode 100644 index 0000000..9006474 --- /dev/null +++ b/src/osbridgelcca/desktop_app/.gitignore @@ -0,0 +1,84 @@ +# Python bytecode and cache +__pycache__/ +*.py[cod] +*$py.class + +# Virtual environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Sphinx documentation +docs/_build/ + +# Jupyter Notebook +.ipynb_checkpoints + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Qt Creator & QML +*.user +*.pro.user +*.qbs.user +*.qmlproject.user +*.autosave + +# MacOS +.DS_Store + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini + +# Misc +*.log diff --git a/src/osbridgelcca/desktop_app/dependencies/d3js.js b/src/osbridgelcca/desktop_app/dependencies/d3js.js new file mode 100644 index 0000000..33bb880 --- /dev/null +++ b/src/osbridgelcca/desktop_app/dependencies/d3js.js @@ -0,0 +1,2 @@ +// https://d3js.org v7.9.0 Copyright 2010-2023 Mike Bostock +!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports):"function"==typeof define&&define.amd?define(["exports"],n):n((t="undefined"!=typeof globalThis?globalThis:t||self).d3=t.d3||{})}(this,(function(t){"use strict";function n(t,n){return null==t||null==n?NaN:tn?1:t>=n?0:NaN}function e(t,n){return null==t||null==n?NaN:nt?1:n>=t?0:NaN}function r(t){let r,o,a;function u(t,n,e=0,i=t.length){if(e>>1;o(t[r],n)<0?e=r+1:i=r}while(en(t(e),r),a=(n,e)=>t(n)-e):(r=t===n||t===e?t:i,o=t,a=t),{left:u,center:function(t,n,e=0,r=t.length){const i=u(t,n,e,r-1);return i>e&&a(t[i-1],n)>-a(t[i],n)?i-1:i},right:function(t,n,e=0,i=t.length){if(e>>1;o(t[r],n)<=0?e=r+1:i=r}while(e{n(t,e,(r<<=2)+0,(i<<=2)+0,o<<=2),n(t,e,r+1,i+1,o),n(t,e,r+2,i+2,o),n(t,e,r+3,i+3,o)}}));function d(t){return function(n,e,r=e){if(!((e=+e)>=0))throw new RangeError("invalid rx");if(!((r=+r)>=0))throw new RangeError("invalid ry");let{data:i,width:o,height:a}=n;if(!((o=Math.floor(o))>=0))throw new RangeError("invalid width");if(!((a=Math.floor(void 0!==a?a:i.length/o))>=0))throw new RangeError("invalid height");if(!o||!a||!e&&!r)return n;const u=e&&t(e),c=r&&t(r),f=i.slice();return u&&c?(p(u,f,i,o,a),p(u,i,f,o,a),p(u,f,i,o,a),g(c,i,f,o,a),g(c,f,i,o,a),g(c,i,f,o,a)):u?(p(u,i,f,o,a),p(u,f,i,o,a),p(u,i,f,o,a)):c&&(g(c,i,f,o,a),g(c,f,i,o,a),g(c,i,f,o,a)),n}}function p(t,n,e,r,i){for(let o=0,a=r*i;o{if(!((o-=a)>=i))return;let u=t*r[i];const c=a*t;for(let t=i,n=i+c;t{if(!((a-=u)>=o))return;let c=n*i[o];const f=u*n,s=f+u;for(let t=o,n=o+f;t=n&&++e;else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&(i=+i)>=i&&++e}return e}function _(t){return 0|t.length}function b(t){return!(t>0)}function m(t){return"object"!=typeof t||"length"in t?t:Array.from(t)}function x(t,n){let e,r=0,i=0,o=0;if(void 0===n)for(let n of t)null!=n&&(n=+n)>=n&&(e=n-i,i+=e/++r,o+=e*(n-i));else{let a=-1;for(let u of t)null!=(u=n(u,++a,t))&&(u=+u)>=u&&(e=u-i,i+=e/++r,o+=e*(u-i))}if(r>1)return o/(r-1)}function w(t,n){const e=x(t,n);return e?Math.sqrt(e):e}function M(t,n){let e,r;if(void 0===n)for(const n of t)null!=n&&(void 0===e?n>=n&&(e=r=n):(e>n&&(e=n),r=o&&(e=r=o):(e>o&&(e=o),r0){for(o=t[--i];i>0&&(n=o,e=t[--i],o=n+e,r=e-(o-n),!r););i>0&&(r<0&&t[i-1]<0||r>0&&t[i-1]>0)&&(e=2*r,n=o+e,e==n-o&&(o=n))}return o}}class InternMap extends Map{constructor(t,n=N){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:n}}),null!=t)for(const[n,e]of t)this.set(n,e)}get(t){return super.get(A(this,t))}has(t){return super.has(A(this,t))}set(t,n){return super.set(S(this,t),n)}delete(t){return super.delete(E(this,t))}}class InternSet extends Set{constructor(t,n=N){if(super(),Object.defineProperties(this,{_intern:{value:new Map},_key:{value:n}}),null!=t)for(const n of t)this.add(n)}has(t){return super.has(A(this,t))}add(t){return super.add(S(this,t))}delete(t){return super.delete(E(this,t))}}function A({_intern:t,_key:n},e){const r=n(e);return t.has(r)?t.get(r):e}function S({_intern:t,_key:n},e){const r=n(e);return t.has(r)?t.get(r):(t.set(r,e),e)}function E({_intern:t,_key:n},e){const r=n(e);return t.has(r)&&(e=t.get(r),t.delete(r)),e}function N(t){return null!==t&&"object"==typeof t?t.valueOf():t}function k(t){return t}function C(t,...n){return F(t,k,k,n)}function P(t,...n){return F(t,Array.from,k,n)}function z(t,n){for(let e=1,r=n.length;et.pop().map((([n,e])=>[...t,n,e]))));return t}function $(t,n,...e){return F(t,k,n,e)}function D(t,n,...e){return F(t,Array.from,n,e)}function R(t){if(1!==t.length)throw new Error("duplicate key");return t[0]}function F(t,n,e,r){return function t(i,o){if(o>=r.length)return e(i);const a=new InternMap,u=r[o++];let c=-1;for(const t of i){const n=u(t,++c,i),e=a.get(n);e?e.push(t):a.set(n,[t])}for(const[n,e]of a)a.set(n,t(e,o));return n(a)}(t,0)}function q(t,n){return Array.from(n,(n=>t[n]))}function U(t,...n){if("function"!=typeof t[Symbol.iterator])throw new TypeError("values is not iterable");t=Array.from(t);let[e]=n;if(e&&2!==e.length||n.length>1){const r=Uint32Array.from(t,((t,n)=>n));return n.length>1?(n=n.map((n=>t.map(n))),r.sort(((t,e)=>{for(const r of n){const n=O(r[t],r[e]);if(n)return n}}))):(e=t.map(e),r.sort(((t,n)=>O(e[t],e[n])))),q(t,r)}return t.sort(I(e))}function I(t=n){if(t===n)return O;if("function"!=typeof t)throw new TypeError("compare is not a function");return(n,e)=>{const r=t(n,e);return r||0===r?r:(0===t(e,e))-(0===t(n,n))}}function O(t,n){return(null==t||!(t>=t))-(null==n||!(n>=n))||(tn?1:0)}var B=Array.prototype.slice;function Y(t){return()=>t}const L=Math.sqrt(50),j=Math.sqrt(10),H=Math.sqrt(2);function X(t,n,e){const r=(n-t)/Math.max(0,e),i=Math.floor(Math.log10(r)),o=r/Math.pow(10,i),a=o>=L?10:o>=j?5:o>=H?2:1;let u,c,f;return i<0?(f=Math.pow(10,-i)/a,u=Math.round(t*f),c=Math.round(n*f),u/fn&&--c,f=-f):(f=Math.pow(10,i)*a,u=Math.round(t/f),c=Math.round(n/f),u*fn&&--c),c0))return[];if((t=+t)===(n=+n))return[t];const r=n=i))return[];const u=o-i+1,c=new Array(u);if(r)if(a<0)for(let t=0;t0?(t=Math.floor(t/i)*i,n=Math.ceil(n/i)*i):i<0&&(t=Math.ceil(t*i)/i,n=Math.floor(n*i)/i),r=i}}function K(t){return Math.max(1,Math.ceil(Math.log(v(t))/Math.LN2)+1)}function Q(){var t=k,n=M,e=K;function r(r){Array.isArray(r)||(r=Array.from(r));var i,o,a,u=r.length,c=new Array(u);for(i=0;i=h)if(t>=h&&n===M){const t=V(l,h,e);isFinite(t)&&(t>0?h=(Math.floor(h/t)+1)*t:t<0&&(h=(Math.ceil(h*-t)+1)/-t))}else d.pop()}for(var p=d.length,g=0,y=p;d[g]<=l;)++g;for(;d[y-1]>h;)--y;(g||y0?d[i-1]:l,v.x1=i0)for(i=0;i=n)&&(e=n);else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&(e=i)&&(e=i)}return e}function tt(t,n){let e,r=-1,i=-1;if(void 0===n)for(const n of t)++i,null!=n&&(e=n)&&(e=n,r=i);else for(let o of t)null!=(o=n(o,++i,t))&&(e=o)&&(e=o,r=i);return r}function nt(t,n){let e;if(void 0===n)for(const n of t)null!=n&&(e>n||void 0===e&&n>=n)&&(e=n);else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&(e>i||void 0===e&&i>=i)&&(e=i)}return e}function et(t,n){let e,r=-1,i=-1;if(void 0===n)for(const n of t)++i,null!=n&&(e>n||void 0===e&&n>=n)&&(e=n,r=i);else for(let o of t)null!=(o=n(o,++i,t))&&(e>o||void 0===e&&o>=o)&&(e=o,r=i);return r}function rt(t,n,e=0,r=1/0,i){if(n=Math.floor(n),e=Math.floor(Math.max(0,e)),r=Math.floor(Math.min(t.length-1,r)),!(e<=n&&n<=r))return t;for(i=void 0===i?O:I(i);r>e;){if(r-e>600){const o=r-e+1,a=n-e+1,u=Math.log(o),c=.5*Math.exp(2*u/3),f=.5*Math.sqrt(u*c*(o-c)/o)*(a-o/2<0?-1:1);rt(t,n,Math.max(e,Math.floor(n-a*c/o+f)),Math.min(r,Math.floor(n+(o-a)*c/o+f)),i)}const o=t[n];let a=e,u=r;for(it(t,e,n),i(t[r],o)>0&&it(t,e,r);a0;)--u}0===i(t[e],o)?it(t,e,u):(++u,it(t,u,r)),u<=n&&(e=u+1),n<=u&&(r=u-1)}return t}function it(t,n,e){const r=t[n];t[n]=t[e],t[e]=r}function ot(t,e=n){let r,i=!1;if(1===e.length){let o;for(const a of t){const t=e(a);(i?n(t,o)>0:0===n(t,t))&&(r=a,o=t,i=!0)}}else for(const n of t)(i?e(n,r)>0:0===e(n,n))&&(r=n,i=!0);return r}function at(t,n,e){if(t=Float64Array.from(function*(t,n){if(void 0===n)for(let n of t)null!=n&&(n=+n)>=n&&(yield n);else{let e=-1;for(let r of t)null!=(r=n(r,++e,t))&&(r=+r)>=r&&(yield r)}}(t,e)),(r=t.length)&&!isNaN(n=+n)){if(n<=0||r<2)return nt(t);if(n>=1)return J(t);var r,i=(r-1)*n,o=Math.floor(i),a=J(rt(t,o).subarray(0,o+1));return a+(nt(t.subarray(o+1))-a)*(i-o)}}function ut(t,n,e=o){if((r=t.length)&&!isNaN(n=+n)){if(n<=0||r<2)return+e(t[0],0,t);if(n>=1)return+e(t[r-1],r-1,t);var r,i=(r-1)*n,a=Math.floor(i),u=+e(t[a],a,t);return u+(+e(t[a+1],a+1,t)-u)*(i-a)}}function ct(t,n,e=o){if(!isNaN(n=+n)){if(r=Float64Array.from(t,((n,r)=>o(e(t[r],r,t)))),n<=0)return et(r);if(n>=1)return tt(r);var r,i=Uint32Array.from(t,((t,n)=>n)),a=r.length-1,u=Math.floor(a*n);return rt(i,u,0,a,((t,n)=>O(r[t],r[n]))),(u=ot(i.subarray(0,u+1),(t=>r[t])))>=0?u:-1}}function ft(t){return Array.from(function*(t){for(const n of t)yield*n}(t))}function st(t,n){return[t,n]}function lt(t,n,e){t=+t,n=+n,e=(i=arguments.length)<2?(n=t,t=0,1):i<3?1:+e;for(var r=-1,i=0|Math.max(0,Math.ceil((n-t)/e)),o=new Array(i);++r+t(n)}function kt(t,n){return n=Math.max(0,t.bandwidth()-2*n)/2,t.round()&&(n=Math.round(n)),e=>+t(e)+n}function Ct(){return!this.__axis}function Pt(t,n){var e=[],r=null,i=null,o=6,a=6,u=3,c="undefined"!=typeof window&&window.devicePixelRatio>1?0:.5,f=t===xt||t===Tt?-1:1,s=t===Tt||t===wt?"x":"y",l=t===xt||t===Mt?St:Et;function h(h){var d=null==r?n.ticks?n.ticks.apply(n,e):n.domain():r,p=null==i?n.tickFormat?n.tickFormat.apply(n,e):mt:i,g=Math.max(o,0)+u,y=n.range(),v=+y[0]+c,_=+y[y.length-1]+c,b=(n.bandwidth?kt:Nt)(n.copy(),c),m=h.selection?h.selection():h,x=m.selectAll(".domain").data([null]),w=m.selectAll(".tick").data(d,n).order(),M=w.exit(),T=w.enter().append("g").attr("class","tick"),A=w.select("line"),S=w.select("text");x=x.merge(x.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),w=w.merge(T),A=A.merge(T.append("line").attr("stroke","currentColor").attr(s+"2",f*o)),S=S.merge(T.append("text").attr("fill","currentColor").attr(s,f*g).attr("dy",t===xt?"0em":t===Mt?"0.71em":"0.32em")),h!==m&&(x=x.transition(h),w=w.transition(h),A=A.transition(h),S=S.transition(h),M=M.transition(h).attr("opacity",At).attr("transform",(function(t){return isFinite(t=b(t))?l(t+c):this.getAttribute("transform")})),T.attr("opacity",At).attr("transform",(function(t){var n=this.parentNode.__axis;return l((n&&isFinite(n=n(t))?n:b(t))+c)}))),M.remove(),x.attr("d",t===Tt||t===wt?a?"M"+f*a+","+v+"H"+c+"V"+_+"H"+f*a:"M"+c+","+v+"V"+_:a?"M"+v+","+f*a+"V"+c+"H"+_+"V"+f*a:"M"+v+","+c+"H"+_),w.attr("opacity",1).attr("transform",(function(t){return l(b(t)+c)})),A.attr(s+"2",f*o),S.attr(s,f*g).text(p),m.filter(Ct).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",t===wt?"start":t===Tt?"end":"middle"),m.each((function(){this.__axis=b}))}return h.scale=function(t){return arguments.length?(n=t,h):n},h.ticks=function(){return e=Array.from(arguments),h},h.tickArguments=function(t){return arguments.length?(e=null==t?[]:Array.from(t),h):e.slice()},h.tickValues=function(t){return arguments.length?(r=null==t?null:Array.from(t),h):r&&r.slice()},h.tickFormat=function(t){return arguments.length?(i=t,h):i},h.tickSize=function(t){return arguments.length?(o=a=+t,h):o},h.tickSizeInner=function(t){return arguments.length?(o=+t,h):o},h.tickSizeOuter=function(t){return arguments.length?(a=+t,h):a},h.tickPadding=function(t){return arguments.length?(u=+t,h):u},h.offset=function(t){return arguments.length?(c=+t,h):c},h}var zt={value:()=>{}};function $t(){for(var t,n=0,e=arguments.length,r={};n=0&&(n=t.slice(e+1),t=t.slice(0,e)),t&&!r.hasOwnProperty(t))throw new Error("unknown type: "+t);return{type:t,name:n}}))),a=-1,u=o.length;if(!(arguments.length<2)){if(null!=n&&"function"!=typeof n)throw new Error("invalid callback: "+n);for(;++a0)for(var e,r,i=new Array(e),o=0;o=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),Ut.hasOwnProperty(n)?{space:Ut[n],local:t}:t}function Ot(t){return function(){var n=this.ownerDocument,e=this.namespaceURI;return e===qt&&n.documentElement.namespaceURI===qt?n.createElement(t):n.createElementNS(e,t)}}function Bt(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function Yt(t){var n=It(t);return(n.local?Bt:Ot)(n)}function Lt(){}function jt(t){return null==t?Lt:function(){return this.querySelector(t)}}function Ht(t){return null==t?[]:Array.isArray(t)?t:Array.from(t)}function Xt(){return[]}function Gt(t){return null==t?Xt:function(){return this.querySelectorAll(t)}}function Vt(t){return function(){return this.matches(t)}}function Wt(t){return function(n){return n.matches(t)}}var Zt=Array.prototype.find;function Kt(){return this.firstElementChild}var Qt=Array.prototype.filter;function Jt(){return Array.from(this.children)}function tn(t){return new Array(t.length)}function nn(t,n){this.ownerDocument=t.ownerDocument,this.namespaceURI=t.namespaceURI,this._next=null,this._parent=t,this.__data__=n}function en(t,n,e,r,i,o){for(var a,u=0,c=n.length,f=o.length;un?1:t>=n?0:NaN}function cn(t){return function(){this.removeAttribute(t)}}function fn(t){return function(){this.removeAttributeNS(t.space,t.local)}}function sn(t,n){return function(){this.setAttribute(t,n)}}function ln(t,n){return function(){this.setAttributeNS(t.space,t.local,n)}}function hn(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttribute(t):this.setAttribute(t,e)}}function dn(t,n){return function(){var e=n.apply(this,arguments);null==e?this.removeAttributeNS(t.space,t.local):this.setAttributeNS(t.space,t.local,e)}}function pn(t){return t.ownerDocument&&t.ownerDocument.defaultView||t.document&&t||t.defaultView}function gn(t){return function(){this.style.removeProperty(t)}}function yn(t,n,e){return function(){this.style.setProperty(t,n,e)}}function vn(t,n,e){return function(){var r=n.apply(this,arguments);null==r?this.style.removeProperty(t):this.style.setProperty(t,r,e)}}function _n(t,n){return t.style.getPropertyValue(n)||pn(t).getComputedStyle(t,null).getPropertyValue(n)}function bn(t){return function(){delete this[t]}}function mn(t,n){return function(){this[t]=n}}function xn(t,n){return function(){var e=n.apply(this,arguments);null==e?delete this[t]:this[t]=e}}function wn(t){return t.trim().split(/^|\s+/)}function Mn(t){return t.classList||new Tn(t)}function Tn(t){this._node=t,this._names=wn(t.getAttribute("class")||"")}function An(t,n){for(var e=Mn(t),r=-1,i=n.length;++r=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var Gn=[null];function Vn(t,n){this._groups=t,this._parents=n}function Wn(){return new Vn([[document.documentElement]],Gn)}function Zn(t){return"string"==typeof t?new Vn([[document.querySelector(t)]],[document.documentElement]):new Vn([[t]],Gn)}Vn.prototype=Wn.prototype={constructor:Vn,select:function(t){"function"!=typeof t&&(t=jt(t));for(var n=this._groups,e=n.length,r=new Array(e),i=0;i=m&&(m=b+1);!(_=y[m])&&++m=0;)(r=i[o])&&(a&&4^r.compareDocumentPosition(a)&&a.parentNode.insertBefore(r,a),a=r);return this},sort:function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=un);for(var e=this._groups,r=e.length,i=new Array(r),o=0;o1?this.each((null==n?gn:"function"==typeof n?vn:yn)(t,n,null==e?"":e)):_n(this.node(),t)},property:function(t,n){return arguments.length>1?this.each((null==n?bn:"function"==typeof n?xn:mn)(t,n)):this.node()[t]},classed:function(t,n){var e=wn(t+"");if(arguments.length<2){for(var r=Mn(this.node()),i=-1,o=e.length;++i=0&&(n=t.slice(e+1),t=t.slice(0,e)),{type:t,name:n}}))}(t+""),a=o.length;if(!(arguments.length<2)){for(u=n?Ln:Yn,r=0;r()=>t;function fe(t,{sourceEvent:n,subject:e,target:r,identifier:i,active:o,x:a,y:u,dx:c,dy:f,dispatch:s}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},subject:{value:e,enumerable:!0,configurable:!0},target:{value:r,enumerable:!0,configurable:!0},identifier:{value:i,enumerable:!0,configurable:!0},active:{value:o,enumerable:!0,configurable:!0},x:{value:a,enumerable:!0,configurable:!0},y:{value:u,enumerable:!0,configurable:!0},dx:{value:c,enumerable:!0,configurable:!0},dy:{value:f,enumerable:!0,configurable:!0},_:{value:s}})}function se(t){return!t.ctrlKey&&!t.button}function le(){return this.parentNode}function he(t,n){return null==n?{x:t.x,y:t.y}:n}function de(){return navigator.maxTouchPoints||"ontouchstart"in this}function pe(t,n,e){t.prototype=n.prototype=e,e.constructor=t}function ge(t,n){var e=Object.create(t.prototype);for(var r in n)e[r]=n[r];return e}function ye(){}fe.prototype.on=function(){var t=this._.on.apply(this._,arguments);return t===this._?this:t};var ve=.7,_e=1/ve,be="\\s*([+-]?\\d+)\\s*",me="\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)\\s*",xe="\\s*([+-]?(?:\\d*\\.)?\\d+(?:[eE][+-]?\\d+)?)%\\s*",we=/^#([0-9a-f]{3,8})$/,Me=new RegExp(`^rgb\\(${be},${be},${be}\\)$`),Te=new RegExp(`^rgb\\(${xe},${xe},${xe}\\)$`),Ae=new RegExp(`^rgba\\(${be},${be},${be},${me}\\)$`),Se=new RegExp(`^rgba\\(${xe},${xe},${xe},${me}\\)$`),Ee=new RegExp(`^hsl\\(${me},${xe},${xe}\\)$`),Ne=new RegExp(`^hsla\\(${me},${xe},${xe},${me}\\)$`),ke={aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};function Ce(){return this.rgb().formatHex()}function Pe(){return this.rgb().formatRgb()}function ze(t){var n,e;return t=(t+"").trim().toLowerCase(),(n=we.exec(t))?(e=n[1].length,n=parseInt(n[1],16),6===e?$e(n):3===e?new qe(n>>8&15|n>>4&240,n>>4&15|240&n,(15&n)<<4|15&n,1):8===e?De(n>>24&255,n>>16&255,n>>8&255,(255&n)/255):4===e?De(n>>12&15|n>>8&240,n>>8&15|n>>4&240,n>>4&15|240&n,((15&n)<<4|15&n)/255):null):(n=Me.exec(t))?new qe(n[1],n[2],n[3],1):(n=Te.exec(t))?new qe(255*n[1]/100,255*n[2]/100,255*n[3]/100,1):(n=Ae.exec(t))?De(n[1],n[2],n[3],n[4]):(n=Se.exec(t))?De(255*n[1]/100,255*n[2]/100,255*n[3]/100,n[4]):(n=Ee.exec(t))?Le(n[1],n[2]/100,n[3]/100,1):(n=Ne.exec(t))?Le(n[1],n[2]/100,n[3]/100,n[4]):ke.hasOwnProperty(t)?$e(ke[t]):"transparent"===t?new qe(NaN,NaN,NaN,0):null}function $e(t){return new qe(t>>16&255,t>>8&255,255&t,1)}function De(t,n,e,r){return r<=0&&(t=n=e=NaN),new qe(t,n,e,r)}function Re(t){return t instanceof ye||(t=ze(t)),t?new qe((t=t.rgb()).r,t.g,t.b,t.opacity):new qe}function Fe(t,n,e,r){return 1===arguments.length?Re(t):new qe(t,n,e,null==r?1:r)}function qe(t,n,e,r){this.r=+t,this.g=+n,this.b=+e,this.opacity=+r}function Ue(){return`#${Ye(this.r)}${Ye(this.g)}${Ye(this.b)}`}function Ie(){const t=Oe(this.opacity);return`${1===t?"rgb(":"rgba("}${Be(this.r)}, ${Be(this.g)}, ${Be(this.b)}${1===t?")":`, ${t})`}`}function Oe(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function Be(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function Ye(t){return((t=Be(t))<16?"0":"")+t.toString(16)}function Le(t,n,e,r){return r<=0?t=n=e=NaN:e<=0||e>=1?t=n=NaN:n<=0&&(t=NaN),new Xe(t,n,e,r)}function je(t){if(t instanceof Xe)return new Xe(t.h,t.s,t.l,t.opacity);if(t instanceof ye||(t=ze(t)),!t)return new Xe;if(t instanceof Xe)return t;var n=(t=t.rgb()).r/255,e=t.g/255,r=t.b/255,i=Math.min(n,e,r),o=Math.max(n,e,r),a=NaN,u=o-i,c=(o+i)/2;return u?(a=n===o?(e-r)/u+6*(e0&&c<1?0:a,new Xe(a,u,c,t.opacity)}function He(t,n,e,r){return 1===arguments.length?je(t):new Xe(t,n,e,null==r?1:r)}function Xe(t,n,e,r){this.h=+t,this.s=+n,this.l=+e,this.opacity=+r}function Ge(t){return(t=(t||0)%360)<0?t+360:t}function Ve(t){return Math.max(0,Math.min(1,t||0))}function We(t,n,e){return 255*(t<60?n+(e-n)*t/60:t<180?e:t<240?n+(e-n)*(240-t)/60:n)}pe(ye,ze,{copy(t){return Object.assign(new this.constructor,this,t)},displayable(){return this.rgb().displayable()},hex:Ce,formatHex:Ce,formatHex8:function(){return this.rgb().formatHex8()},formatHsl:function(){return je(this).formatHsl()},formatRgb:Pe,toString:Pe}),pe(qe,Fe,ge(ye,{brighter(t){return t=null==t?_e:Math.pow(_e,t),new qe(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=null==t?ve:Math.pow(ve,t),new qe(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new qe(Be(this.r),Be(this.g),Be(this.b),Oe(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:Ue,formatHex:Ue,formatHex8:function(){return`#${Ye(this.r)}${Ye(this.g)}${Ye(this.b)}${Ye(255*(isNaN(this.opacity)?1:this.opacity))}`},formatRgb:Ie,toString:Ie})),pe(Xe,He,ge(ye,{brighter(t){return t=null==t?_e:Math.pow(_e,t),new Xe(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=null==t?ve:Math.pow(ve,t),new Xe(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+360*(this.h<0),n=isNaN(t)||isNaN(this.s)?0:this.s,e=this.l,r=e+(e<.5?e:1-e)*n,i=2*e-r;return new qe(We(t>=240?t-240:t+120,i,r),We(t,i,r),We(t<120?t+240:t-120,i,r),this.opacity)},clamp(){return new Xe(Ge(this.h),Ve(this.s),Ve(this.l),Oe(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const t=Oe(this.opacity);return`${1===t?"hsl(":"hsla("}${Ge(this.h)}, ${100*Ve(this.s)}%, ${100*Ve(this.l)}%${1===t?")":`, ${t})`}`}}));const Ze=Math.PI/180,Ke=180/Math.PI,Qe=.96422,Je=1,tr=.82521,nr=4/29,er=6/29,rr=3*er*er,ir=er*er*er;function or(t){if(t instanceof ur)return new ur(t.l,t.a,t.b,t.opacity);if(t instanceof pr)return gr(t);t instanceof qe||(t=Re(t));var n,e,r=lr(t.r),i=lr(t.g),o=lr(t.b),a=cr((.2225045*r+.7168786*i+.0606169*o)/Je);return r===i&&i===o?n=e=a:(n=cr((.4360747*r+.3850649*i+.1430804*o)/Qe),e=cr((.0139322*r+.0971045*i+.7141733*o)/tr)),new ur(116*a-16,500*(n-a),200*(a-e),t.opacity)}function ar(t,n,e,r){return 1===arguments.length?or(t):new ur(t,n,e,null==r?1:r)}function ur(t,n,e,r){this.l=+t,this.a=+n,this.b=+e,this.opacity=+r}function cr(t){return t>ir?Math.pow(t,1/3):t/rr+nr}function fr(t){return t>er?t*t*t:rr*(t-nr)}function sr(t){return 255*(t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055)}function lr(t){return(t/=255)<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4)}function hr(t){if(t instanceof pr)return new pr(t.h,t.c,t.l,t.opacity);if(t instanceof ur||(t=or(t)),0===t.a&&0===t.b)return new pr(NaN,0=1?(e=1,n-1):Math.floor(e*n),i=t[r],o=t[r+1],a=r>0?t[r-1]:2*i-o,u=r()=>t;function Cr(t,n){return function(e){return t+e*n}}function Pr(t,n){var e=n-t;return e?Cr(t,e>180||e<-180?e-360*Math.round(e/360):e):kr(isNaN(t)?n:t)}function zr(t){return 1==(t=+t)?$r:function(n,e){return e-n?function(t,n,e){return t=Math.pow(t,e),n=Math.pow(n,e)-t,e=1/e,function(r){return Math.pow(t+r*n,e)}}(n,e,t):kr(isNaN(n)?e:n)}}function $r(t,n){var e=n-t;return e?Cr(t,e):kr(isNaN(t)?n:t)}var Dr=function t(n){var e=zr(n);function r(t,n){var r=e((t=Fe(t)).r,(n=Fe(n)).r),i=e(t.g,n.g),o=e(t.b,n.b),a=$r(t.opacity,n.opacity);return function(n){return t.r=r(n),t.g=i(n),t.b=o(n),t.opacity=a(n),t+""}}return r.gamma=t,r}(1);function Rr(t){return function(n){var e,r,i=n.length,o=new Array(i),a=new Array(i),u=new Array(i);for(e=0;eo&&(i=n.slice(o,i),u[a]?u[a]+=i:u[++a]=i),(e=e[0])===(r=r[0])?u[a]?u[a]+=r:u[++a]=r:(u[++a]=null,c.push({i:a,x:Yr(e,r)})),o=Hr.lastIndex;return o180?n+=360:n-t>180&&(t+=360),o.push({i:e.push(i(e)+"rotate(",null,r)-2,x:Yr(t,n)})):n&&e.push(i(e)+"rotate("+n+r)}(o.rotate,a.rotate,u,c),function(t,n,e,o){t!==n?o.push({i:e.push(i(e)+"skewX(",null,r)-2,x:Yr(t,n)}):n&&e.push(i(e)+"skewX("+n+r)}(o.skewX,a.skewX,u,c),function(t,n,e,r,o,a){if(t!==e||n!==r){var u=o.push(i(o)+"scale(",null,",",null,")");a.push({i:u-4,x:Yr(t,e)},{i:u-2,x:Yr(n,r)})}else 1===e&&1===r||o.push(i(o)+"scale("+e+","+r+")")}(o.scaleX,o.scaleY,a.scaleX,a.scaleY,u,c),o=a=null,function(t){for(var n,e=-1,r=c.length;++e=0&&n._call.call(void 0,t),n=n._next;--yi}function Ci(){xi=(mi=Mi.now())+wi,yi=vi=0;try{ki()}finally{yi=0,function(){var t,n,e=pi,r=1/0;for(;e;)e._call?(r>e._time&&(r=e._time),t=e,e=e._next):(n=e._next,e._next=null,e=t?t._next=n:pi=n);gi=t,zi(r)}(),xi=0}}function Pi(){var t=Mi.now(),n=t-mi;n>bi&&(wi-=n,mi=t)}function zi(t){yi||(vi&&(vi=clearTimeout(vi)),t-xi>24?(t<1/0&&(vi=setTimeout(Ci,t-Mi.now()-wi)),_i&&(_i=clearInterval(_i))):(_i||(mi=Mi.now(),_i=setInterval(Pi,bi)),yi=1,Ti(Ci)))}function $i(t,n,e){var r=new Ei;return n=null==n?0:+n,r.restart((e=>{r.stop(),t(e+n)}),n,e),r}Ei.prototype=Ni.prototype={constructor:Ei,restart:function(t,n,e){if("function"!=typeof t)throw new TypeError("callback is not a function");e=(null==e?Ai():+e)+(null==n?0:+n),this._next||gi===this||(gi?gi._next=this:pi=this,gi=this),this._call=t,this._time=e,zi()},stop:function(){this._call&&(this._call=null,this._time=1/0,zi())}};var Di=$t("start","end","cancel","interrupt"),Ri=[],Fi=0,qi=1,Ui=2,Ii=3,Oi=4,Bi=5,Yi=6;function Li(t,n,e,r,i,o){var a=t.__transition;if(a){if(e in a)return}else t.__transition={};!function(t,n,e){var r,i=t.__transition;function o(t){e.state=qi,e.timer.restart(a,e.delay,e.time),e.delay<=t&&a(t-e.delay)}function a(o){var f,s,l,h;if(e.state!==qi)return c();for(f in i)if((h=i[f]).name===e.name){if(h.state===Ii)return $i(a);h.state===Oi?(h.state=Yi,h.timer.stop(),h.on.call("interrupt",t,t.__data__,h.index,h.group),delete i[f]):+fFi)throw new Error("too late; already scheduled");return e}function Hi(t,n){var e=Xi(t,n);if(e.state>Ii)throw new Error("too late; already running");return e}function Xi(t,n){var e=t.__transition;if(!e||!(e=e[n]))throw new Error("transition not found");return e}function Gi(t,n){var e,r,i,o=t.__transition,a=!0;if(o){for(i in n=null==n?null:n+"",o)(e=o[i]).name===n?(r=e.state>Ui&&e.state=0&&(t=t.slice(0,n)),!t||"start"===t}))}(n)?ji:Hi;return function(){var a=o(this,t),u=a.on;u!==r&&(i=(r=u).copy()).on(n,e),a.on=i}}(e,t,n))},attr:function(t,n){var e=It(t),r="transform"===e?ni:Ki;return this.attrTween(t,"function"==typeof n?(e.local?ro:eo)(e,r,Zi(this,"attr."+t,n)):null==n?(e.local?Ji:Qi)(e):(e.local?no:to)(e,r,n))},attrTween:function(t,n){var e="attr."+t;if(arguments.length<2)return(e=this.tween(e))&&e._value;if(null==n)return this.tween(e,null);if("function"!=typeof n)throw new Error;var r=It(t);return this.tween(e,(r.local?io:oo)(r,n))},style:function(t,n,e){var r="transform"==(t+="")?ti:Ki;return null==n?this.styleTween(t,function(t,n){var e,r,i;return function(){var o=_n(this,t),a=(this.style.removeProperty(t),_n(this,t));return o===a?null:o===e&&a===r?i:i=n(e=o,r=a)}}(t,r)).on("end.style."+t,lo(t)):"function"==typeof n?this.styleTween(t,function(t,n,e){var r,i,o;return function(){var a=_n(this,t),u=e(this),c=u+"";return null==u&&(this.style.removeProperty(t),c=u=_n(this,t)),a===c?null:a===r&&c===i?o:(i=c,o=n(r=a,u))}}(t,r,Zi(this,"style."+t,n))).each(function(t,n){var e,r,i,o,a="style."+n,u="end."+a;return function(){var c=Hi(this,t),f=c.on,s=null==c.value[a]?o||(o=lo(n)):void 0;f===e&&i===s||(r=(e=f).copy()).on(u,i=s),c.on=r}}(this._id,t)):this.styleTween(t,function(t,n,e){var r,i,o=e+"";return function(){var a=_n(this,t);return a===o?null:a===r?i:i=n(r=a,e)}}(t,r,n),e).on("end.style."+t,null)},styleTween:function(t,n,e){var r="style."+(t+="");if(arguments.length<2)return(r=this.tween(r))&&r._value;if(null==n)return this.tween(r,null);if("function"!=typeof n)throw new Error;return this.tween(r,function(t,n,e){var r,i;function o(){var o=n.apply(this,arguments);return o!==i&&(r=(i=o)&&function(t,n,e){return function(r){this.style.setProperty(t,n.call(this,r),e)}}(t,o,e)),r}return o._value=n,o}(t,n,null==e?"":e))},text:function(t){return this.tween("text","function"==typeof t?function(t){return function(){var n=t(this);this.textContent=null==n?"":n}}(Zi(this,"text",t)):function(t){return function(){this.textContent=t}}(null==t?"":t+""))},textTween:function(t){var n="text";if(arguments.length<1)return(n=this.tween(n))&&n._value;if(null==t)return this.tween(n,null);if("function"!=typeof t)throw new Error;return this.tween(n,function(t){var n,e;function r(){var r=t.apply(this,arguments);return r!==e&&(n=(e=r)&&function(t){return function(n){this.textContent=t.call(this,n)}}(r)),n}return r._value=t,r}(t))},remove:function(){return this.on("end.remove",function(t){return function(){var n=this.parentNode;for(var e in this.__transition)if(+e!==t)return;n&&n.removeChild(this)}}(this._id))},tween:function(t,n){var e=this._id;if(t+="",arguments.length<2){for(var r,i=Xi(this.node(),e).tween,o=0,a=i.length;o()=>t;function Qo(t,{sourceEvent:n,target:e,selection:r,mode:i,dispatch:o}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},target:{value:e,enumerable:!0,configurable:!0},selection:{value:r,enumerable:!0,configurable:!0},mode:{value:i,enumerable:!0,configurable:!0},_:{value:o}})}function Jo(t){t.preventDefault(),t.stopImmediatePropagation()}var ta={name:"drag"},na={name:"space"},ea={name:"handle"},ra={name:"center"};const{abs:ia,max:oa,min:aa}=Math;function ua(t){return[+t[0],+t[1]]}function ca(t){return[ua(t[0]),ua(t[1])]}var fa={name:"x",handles:["w","e"].map(va),input:function(t,n){return null==t?null:[[+t[0],n[0][1]],[+t[1],n[1][1]]]},output:function(t){return t&&[t[0][0],t[1][0]]}},sa={name:"y",handles:["n","s"].map(va),input:function(t,n){return null==t?null:[[n[0][0],+t[0]],[n[1][0],+t[1]]]},output:function(t){return t&&[t[0][1],t[1][1]]}},la={name:"xy",handles:["n","w","e","s","nw","ne","sw","se"].map(va),input:function(t){return null==t?null:ca(t)},output:function(t){return t}},ha={overlay:"crosshair",selection:"move",n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},da={e:"w",w:"e",nw:"ne",ne:"nw",se:"sw",sw:"se"},pa={n:"s",s:"n",nw:"sw",ne:"se",se:"ne",sw:"nw"},ga={overlay:1,selection:1,n:null,e:1,s:null,w:-1,nw:-1,ne:1,se:1,sw:-1},ya={overlay:1,selection:1,n:-1,e:null,s:1,w:null,nw:-1,ne:-1,se:1,sw:1};function va(t){return{type:t}}function _a(t){return!t.ctrlKey&&!t.button}function ba(){var t=this.ownerSVGElement||this;return t.hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]}function ma(){return navigator.maxTouchPoints||"ontouchstart"in this}function xa(t){for(;!t.__brush;)if(!(t=t.parentNode))return;return t.__brush}function wa(t){var n,e=ba,r=_a,i=ma,o=!0,a=$t("start","brush","end"),u=6;function c(n){var e=n.property("__brush",g).selectAll(".overlay").data([va("overlay")]);e.enter().append("rect").attr("class","overlay").attr("pointer-events","all").attr("cursor",ha.overlay).merge(e).each((function(){var t=xa(this).extent;Zn(this).attr("x",t[0][0]).attr("y",t[0][1]).attr("width",t[1][0]-t[0][0]).attr("height",t[1][1]-t[0][1])})),n.selectAll(".selection").data([va("selection")]).enter().append("rect").attr("class","selection").attr("cursor",ha.selection).attr("fill","#777").attr("fill-opacity",.3).attr("stroke","#fff").attr("shape-rendering","crispEdges");var r=n.selectAll(".handle").data(t.handles,(function(t){return t.type}));r.exit().remove(),r.enter().append("rect").attr("class",(function(t){return"handle handle--"+t.type})).attr("cursor",(function(t){return ha[t.type]})),n.each(f).attr("fill","none").attr("pointer-events","all").on("mousedown.brush",h).filter(i).on("touchstart.brush",h).on("touchmove.brush",d).on("touchend.brush touchcancel.brush",p).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function f(){var t=Zn(this),n=xa(this).selection;n?(t.selectAll(".selection").style("display",null).attr("x",n[0][0]).attr("y",n[0][1]).attr("width",n[1][0]-n[0][0]).attr("height",n[1][1]-n[0][1]),t.selectAll(".handle").style("display",null).attr("x",(function(t){return"e"===t.type[t.type.length-1]?n[1][0]-u/2:n[0][0]-u/2})).attr("y",(function(t){return"s"===t.type[0]?n[1][1]-u/2:n[0][1]-u/2})).attr("width",(function(t){return"n"===t.type||"s"===t.type?n[1][0]-n[0][0]+u:u})).attr("height",(function(t){return"e"===t.type||"w"===t.type?n[1][1]-n[0][1]+u:u}))):t.selectAll(".selection,.handle").style("display","none").attr("x",null).attr("y",null).attr("width",null).attr("height",null)}function s(t,n,e){var r=t.__brush.emitter;return!r||e&&r.clean?new l(t,n,e):r}function l(t,n,e){this.that=t,this.args=n,this.state=t.__brush,this.active=0,this.clean=e}function h(e){if((!n||e.touches)&&r.apply(this,arguments)){var i,a,u,c,l,h,d,p,g,y,v,_=this,b=e.target.__data__.type,m="selection"===(o&&e.metaKey?b="overlay":b)?ta:o&&e.altKey?ra:ea,x=t===sa?null:ga[b],w=t===fa?null:ya[b],M=xa(_),T=M.extent,A=M.selection,S=T[0][0],E=T[0][1],N=T[1][0],k=T[1][1],C=0,P=0,z=x&&w&&o&&e.shiftKey,$=Array.from(e.touches||[e],(t=>{const n=t.identifier;return(t=ne(t,_)).point0=t.slice(),t.identifier=n,t}));Gi(_);var D=s(_,arguments,!0).beforestart();if("overlay"===b){A&&(g=!0);const n=[$[0],$[1]||$[0]];M.selection=A=[[i=t===sa?S:aa(n[0][0],n[1][0]),u=t===fa?E:aa(n[0][1],n[1][1])],[l=t===sa?N:oa(n[0][0],n[1][0]),d=t===fa?k:oa(n[0][1],n[1][1])]],$.length>1&&I(e)}else i=A[0][0],u=A[0][1],l=A[1][0],d=A[1][1];a=i,c=u,h=l,p=d;var R=Zn(_).attr("pointer-events","none"),F=R.selectAll(".overlay").attr("cursor",ha[b]);if(e.touches)D.moved=U,D.ended=O;else{var q=Zn(e.view).on("mousemove.brush",U,!0).on("mouseup.brush",O,!0);o&&q.on("keydown.brush",(function(t){switch(t.keyCode){case 16:z=x&&w;break;case 18:m===ea&&(x&&(l=h-C*x,i=a+C*x),w&&(d=p-P*w,u=c+P*w),m=ra,I(t));break;case 32:m!==ea&&m!==ra||(x<0?l=h-C:x>0&&(i=a-C),w<0?d=p-P:w>0&&(u=c-P),m=na,F.attr("cursor",ha.selection),I(t));break;default:return}Jo(t)}),!0).on("keyup.brush",(function(t){switch(t.keyCode){case 16:z&&(y=v=z=!1,I(t));break;case 18:m===ra&&(x<0?l=h:x>0&&(i=a),w<0?d=p:w>0&&(u=c),m=ea,I(t));break;case 32:m===na&&(t.altKey?(x&&(l=h-C*x,i=a+C*x),w&&(d=p-P*w,u=c+P*w),m=ra):(x<0?l=h:x>0&&(i=a),w<0?d=p:w>0&&(u=c),m=ea),F.attr("cursor",ha[b]),I(t));break;default:return}Jo(t)}),!0),ae(e.view)}f.call(_),D.start(e,m.name)}function U(t){for(const n of t.changedTouches||[t])for(const t of $)t.identifier===n.identifier&&(t.cur=ne(n,_));if(z&&!y&&!v&&1===$.length){const t=$[0];ia(t.cur[0]-t[0])>ia(t.cur[1]-t[1])?v=!0:y=!0}for(const t of $)t.cur&&(t[0]=t.cur[0],t[1]=t.cur[1]);g=!0,Jo(t),I(t)}function I(t){const n=$[0],e=n.point0;var r;switch(C=n[0]-e[0],P=n[1]-e[1],m){case na:case ta:x&&(C=oa(S-i,aa(N-l,C)),a=i+C,h=l+C),w&&(P=oa(E-u,aa(k-d,P)),c=u+P,p=d+P);break;case ea:$[1]?(x&&(a=oa(S,aa(N,$[0][0])),h=oa(S,aa(N,$[1][0])),x=1),w&&(c=oa(E,aa(k,$[0][1])),p=oa(E,aa(k,$[1][1])),w=1)):(x<0?(C=oa(S-i,aa(N-i,C)),a=i+C,h=l):x>0&&(C=oa(S-l,aa(N-l,C)),a=i,h=l+C),w<0?(P=oa(E-u,aa(k-u,P)),c=u+P,p=d):w>0&&(P=oa(E-d,aa(k-d,P)),c=u,p=d+P));break;case ra:x&&(a=oa(S,aa(N,i-C*x)),h=oa(S,aa(N,l+C*x))),w&&(c=oa(E,aa(k,u-P*w)),p=oa(E,aa(k,d+P*w)))}ht+e))}function za(t,n){var e=0,r=null,i=null,o=null;function a(a){var u,c=a.length,f=new Array(c),s=Pa(0,c),l=new Array(c*c),h=new Array(c),d=0;a=Float64Array.from({length:c*c},n?(t,n)=>a[n%c][n/c|0]:(t,n)=>a[n/c|0][n%c]);for(let n=0;nr(f[t],f[n])));for(const e of s){const r=n;if(t){const t=Pa(1+~c,c).filter((t=>t<0?a[~t*c+e]:a[e*c+t]));i&&t.sort(((t,n)=>i(t<0?-a[~t*c+e]:a[e*c+t],n<0?-a[~n*c+e]:a[e*c+n])));for(const r of t)if(r<0){(l[~r*c+e]||(l[~r*c+e]={source:null,target:null})).target={index:e,startAngle:n,endAngle:n+=a[~r*c+e]*d,value:a[~r*c+e]}}else{(l[e*c+r]||(l[e*c+r]={source:null,target:null})).source={index:e,startAngle:n,endAngle:n+=a[e*c+r]*d,value:a[e*c+r]}}h[e]={index:e,startAngle:r,endAngle:n,value:f[e]}}else{const t=Pa(0,c).filter((t=>a[e*c+t]||a[t*c+e]));i&&t.sort(((t,n)=>i(a[e*c+t],a[e*c+n])));for(const r of t){let t;if(e=0))throw new Error(`invalid digits: ${t}`);if(n>15)return qa;const e=10**n;return function(t){this._+=t[0];for(let n=1,r=t.length;nRa)if(Math.abs(s*u-c*f)>Ra&&i){let h=e-o,d=r-a,p=u*u+c*c,g=h*h+d*d,y=Math.sqrt(p),v=Math.sqrt(l),_=i*Math.tan(($a-Math.acos((p+l-g)/(2*y*v)))/2),b=_/v,m=_/y;Math.abs(b-1)>Ra&&this._append`L${t+b*f},${n+b*s}`,this._append`A${i},${i},0,0,${+(s*h>f*d)},${this._x1=t+m*u},${this._y1=n+m*c}`}else this._append`L${this._x1=t},${this._y1=n}`;else;}arc(t,n,e,r,i,o){if(t=+t,n=+n,o=!!o,(e=+e)<0)throw new Error(`negative radius: ${e}`);let a=e*Math.cos(r),u=e*Math.sin(r),c=t+a,f=n+u,s=1^o,l=o?r-i:i-r;null===this._x1?this._append`M${c},${f}`:(Math.abs(this._x1-c)>Ra||Math.abs(this._y1-f)>Ra)&&this._append`L${c},${f}`,e&&(l<0&&(l=l%Da+Da),l>Fa?this._append`A${e},${e},0,1,${s},${t-a},${n-u}A${e},${e},0,1,${s},${this._x1=c},${this._y1=f}`:l>Ra&&this._append`A${e},${e},0,${+(l>=$a)},${s},${this._x1=t+e*Math.cos(i)},${this._y1=n+e*Math.sin(i)}`)}rect(t,n,e,r){this._append`M${this._x0=this._x1=+t},${this._y0=this._y1=+n}h${e=+e}v${+r}h${-e}Z`}toString(){return this._}};function Ia(){return new Ua}Ia.prototype=Ua.prototype;var Oa=Array.prototype.slice;function Ba(t){return function(){return t}}function Ya(t){return t.source}function La(t){return t.target}function ja(t){return t.radius}function Ha(t){return t.startAngle}function Xa(t){return t.endAngle}function Ga(){return 0}function Va(){return 10}function Wa(t){var n=Ya,e=La,r=ja,i=ja,o=Ha,a=Xa,u=Ga,c=null;function f(){var f,s=n.apply(this,arguments),l=e.apply(this,arguments),h=u.apply(this,arguments)/2,d=Oa.call(arguments),p=+r.apply(this,(d[0]=s,d)),g=o.apply(this,d)-Ea,y=a.apply(this,d)-Ea,v=+i.apply(this,(d[0]=l,d)),_=o.apply(this,d)-Ea,b=a.apply(this,d)-Ea;if(c||(c=f=Ia()),h>Ca&&(Ma(y-g)>2*h+Ca?y>g?(g+=h,y-=h):(g-=h,y+=h):g=y=(g+y)/2,Ma(b-_)>2*h+Ca?b>_?(_+=h,b-=h):(_-=h,b+=h):_=b=(_+b)/2),c.moveTo(p*Ta(g),p*Aa(g)),c.arc(0,0,p,g,y),g!==_||y!==b)if(t){var m=v-+t.apply(this,arguments),x=(_+b)/2;c.quadraticCurveTo(0,0,m*Ta(_),m*Aa(_)),c.lineTo(v*Ta(x),v*Aa(x)),c.lineTo(m*Ta(b),m*Aa(b))}else c.quadraticCurveTo(0,0,v*Ta(_),v*Aa(_)),c.arc(0,0,v,_,b);if(c.quadraticCurveTo(0,0,p*Ta(g),p*Aa(g)),c.closePath(),f)return c=null,f+""||null}return t&&(f.headRadius=function(n){return arguments.length?(t="function"==typeof n?n:Ba(+n),f):t}),f.radius=function(t){return arguments.length?(r=i="function"==typeof t?t:Ba(+t),f):r},f.sourceRadius=function(t){return arguments.length?(r="function"==typeof t?t:Ba(+t),f):r},f.targetRadius=function(t){return arguments.length?(i="function"==typeof t?t:Ba(+t),f):i},f.startAngle=function(t){return arguments.length?(o="function"==typeof t?t:Ba(+t),f):o},f.endAngle=function(t){return arguments.length?(a="function"==typeof t?t:Ba(+t),f):a},f.padAngle=function(t){return arguments.length?(u="function"==typeof t?t:Ba(+t),f):u},f.source=function(t){return arguments.length?(n=t,f):n},f.target=function(t){return arguments.length?(e=t,f):e},f.context=function(t){return arguments.length?(c=null==t?null:t,f):c},f}var Za=Array.prototype.slice;function Ka(t,n){return t-n}var Qa=t=>()=>t;function Ja(t,n){for(var e,r=-1,i=n.length;++rr!=d>r&&e<(h-f)*(r-s)/(d-s)+f&&(i=-i)}return i}function nu(t,n,e){var r,i,o,a;return function(t,n,e){return(n[0]-t[0])*(e[1]-t[1])==(e[0]-t[0])*(n[1]-t[1])}(t,n,e)&&(i=t[r=+(t[0]===n[0])],o=e[r],a=n[r],i<=o&&o<=a||a<=o&&o<=i)}function eu(){}var ru=[[],[[[1,1.5],[.5,1]]],[[[1.5,1],[1,1.5]]],[[[1.5,1],[.5,1]]],[[[1,.5],[1.5,1]]],[[[1,1.5],[.5,1]],[[1,.5],[1.5,1]]],[[[1,.5],[1,1.5]]],[[[1,.5],[.5,1]]],[[[.5,1],[1,.5]]],[[[1,1.5],[1,.5]]],[[[.5,1],[1,.5]],[[1.5,1],[1,1.5]]],[[[1.5,1],[1,.5]]],[[[.5,1],[1.5,1]]],[[[1,1.5],[1.5,1]]],[[[.5,1],[1,1.5]]],[]];function iu(){var t=1,n=1,e=K,r=u;function i(t){var n=e(t);if(Array.isArray(n))n=n.slice().sort(Ka);else{const e=M(t,ou);for(n=G(...Z(e[0],e[1],n),n);n[n.length-1]>=e[1];)n.pop();for(;n[1]o(t,n)))}function o(e,i){const o=null==i?NaN:+i;if(isNaN(o))throw new Error(`invalid value: ${i}`);var u=[],c=[];return function(e,r,i){var o,u,c,f,s,l,h=new Array,d=new Array;o=u=-1,f=au(e[0],r),ru[f<<1].forEach(p);for(;++o=r,ru[s<<2].forEach(p);for(;++o0?u.push([t]):c.push(t)})),c.forEach((function(t){for(var n,e=0,r=u.length;e0&&o0&&a=0&&o>=0))throw new Error("invalid size");return t=r,n=o,i},i.thresholds=function(t){return arguments.length?(e="function"==typeof t?t:Array.isArray(t)?Qa(Za.call(t)):Qa(t),i):e},i.smooth=function(t){return arguments.length?(r=t?u:eu,i):r===u},i}function ou(t){return isFinite(t)?t:NaN}function au(t,n){return null!=t&&+t>=n}function uu(t){return null==t||isNaN(t=+t)?-1/0:t}function cu(t,n,e,r){const i=r-n,o=e-n,a=isFinite(i)||isFinite(o)?i/o:Math.sign(i)/Math.sign(o);return isNaN(a)?t:t+a-.5}function fu(t){return t[0]}function su(t){return t[1]}function lu(){return 1}const hu=134217729,du=33306690738754706e-32;function pu(t,n,e,r,i){let o,a,u,c,f=n[0],s=r[0],l=0,h=0;s>f==s>-f?(o=f,f=n[++l]):(o=s,s=r[++h]);let d=0;if(lf==s>-f?(a=f+o,u=o-(a-f),f=n[++l]):(a=s+o,u=o-(a-s),s=r[++h]),o=a,0!==u&&(i[d++]=u);lf==s>-f?(a=o+f,c=a-o,u=o-(a-c)+(f-c),f=n[++l]):(a=o+s,c=a-o,u=o-(a-c)+(s-c),s=r[++h]),o=a,0!==u&&(i[d++]=u);for(;l=33306690738754716e-32*f?c:-function(t,n,e,r,i,o,a){let u,c,f,s,l,h,d,p,g,y,v,_,b,m,x,w,M,T;const A=t-i,S=e-i,E=n-o,N=r-o;m=A*N,h=hu*A,d=h-(h-A),p=A-d,h=hu*N,g=h-(h-N),y=N-g,x=p*y-(m-d*g-p*g-d*y),w=E*S,h=hu*E,d=h-(h-E),p=E-d,h=hu*S,g=h-(h-S),y=S-g,M=p*y-(w-d*g-p*g-d*y),v=x-M,l=x-v,_u[0]=x-(v+l)+(l-M),_=m+v,l=_-m,b=m-(_-l)+(v-l),v=b-w,l=b-v,_u[1]=b-(v+l)+(l-w),T=_+v,l=T-_,_u[2]=_-(T-l)+(v-l),_u[3]=T;let k=function(t,n){let e=n[0];for(let r=1;r=C||-k>=C)return k;if(l=t-A,u=t-(A+l)+(l-i),l=e-S,f=e-(S+l)+(l-i),l=n-E,c=n-(E+l)+(l-o),l=r-N,s=r-(N+l)+(l-o),0===u&&0===c&&0===f&&0===s)return k;if(C=vu*a+du*Math.abs(k),k+=A*s+N*u-(E*f+S*c),k>=C||-k>=C)return k;m=u*N,h=hu*u,d=h-(h-u),p=u-d,h=hu*N,g=h-(h-N),y=N-g,x=p*y-(m-d*g-p*g-d*y),w=c*S,h=hu*c,d=h-(h-c),p=c-d,h=hu*S,g=h-(h-S),y=S-g,M=p*y-(w-d*g-p*g-d*y),v=x-M,l=x-v,wu[0]=x-(v+l)+(l-M),_=m+v,l=_-m,b=m-(_-l)+(v-l),v=b-w,l=b-v,wu[1]=b-(v+l)+(l-w),T=_+v,l=T-_,wu[2]=_-(T-l)+(v-l),wu[3]=T;const P=pu(4,_u,4,wu,bu);m=A*s,h=hu*A,d=h-(h-A),p=A-d,h=hu*s,g=h-(h-s),y=s-g,x=p*y-(m-d*g-p*g-d*y),w=E*f,h=hu*E,d=h-(h-E),p=E-d,h=hu*f,g=h-(h-f),y=f-g,M=p*y-(w-d*g-p*g-d*y),v=x-M,l=x-v,wu[0]=x-(v+l)+(l-M),_=m+v,l=_-m,b=m-(_-l)+(v-l),v=b-w,l=b-v,wu[1]=b-(v+l)+(l-w),T=_+v,l=T-_,wu[2]=_-(T-l)+(v-l),wu[3]=T;const z=pu(P,bu,4,wu,mu);m=u*s,h=hu*u,d=h-(h-u),p=u-d,h=hu*s,g=h-(h-s),y=s-g,x=p*y-(m-d*g-p*g-d*y),w=c*f,h=hu*c,d=h-(h-c),p=c-d,h=hu*f,g=h-(h-f),y=f-g,M=p*y-(w-d*g-p*g-d*y),v=x-M,l=x-v,wu[0]=x-(v+l)+(l-M),_=m+v,l=_-m,b=m-(_-l)+(v-l),v=b-w,l=b-v,wu[1]=b-(v+l)+(l-w),T=_+v,l=T-_,wu[2]=_-(T-l)+(v-l),wu[3]=T;const $=pu(z,mu,4,wu,xu);return xu[$-1]}(t,n,e,r,i,o,f)}const Tu=Math.pow(2,-52),Au=new Uint32Array(512);class Su{static from(t,n=zu,e=$u){const r=t.length,i=new Float64Array(2*r);for(let o=0;o>1;if(n>0&&"number"!=typeof t[0])throw new Error("Expected coords to contain numbers.");this.coords=t;const e=Math.max(2*n-5,0);this._triangles=new Uint32Array(3*e),this._halfedges=new Int32Array(3*e),this._hashSize=Math.ceil(Math.sqrt(n)),this._hullPrev=new Uint32Array(n),this._hullNext=new Uint32Array(n),this._hullTri=new Uint32Array(n),this._hullHash=new Int32Array(this._hashSize),this._ids=new Uint32Array(n),this._dists=new Float64Array(n),this.update()}update(){const{coords:t,_hullPrev:n,_hullNext:e,_hullTri:r,_hullHash:i}=this,o=t.length>>1;let a=1/0,u=1/0,c=-1/0,f=-1/0;for(let n=0;nc&&(c=e),r>f&&(f=r),this._ids[n]=n}const s=(a+c)/2,l=(u+f)/2;let h,d,p;for(let n=0,e=1/0;n0&&(d=n,e=r)}let v=t[2*d],_=t[2*d+1],b=1/0;for(let n=0;nr&&(n[e++]=i,r=o)}return this.hull=n.subarray(0,e),this.triangles=new Uint32Array(0),void(this.halfedges=new Uint32Array(0))}if(Mu(g,y,v,_,m,x)<0){const t=d,n=v,e=_;d=p,v=m,_=x,p=t,m=n,x=e}const w=function(t,n,e,r,i,o){const a=e-t,u=r-n,c=i-t,f=o-n,s=a*a+u*u,l=c*c+f*f,h=.5/(a*f-u*c),d=t+(f*s-u*l)*h,p=n+(a*l-c*s)*h;return{x:d,y:p}}(g,y,v,_,m,x);this._cx=w.x,this._cy=w.y;for(let n=0;n0&&Math.abs(f-o)<=Tu&&Math.abs(s-a)<=Tu)continue;if(o=f,a=s,c===h||c===d||c===p)continue;let l=0;for(let t=0,n=this._hashKey(f,s);t=0;)if(y=g,y===l){y=-1;break}if(-1===y)continue;let v=this._addTriangle(y,c,e[y],-1,-1,r[y]);r[c]=this._legalize(v+2),r[y]=v,M++;let _=e[y];for(;g=e[_],Mu(f,s,t[2*_],t[2*_+1],t[2*g],t[2*g+1])<0;)v=this._addTriangle(_,c,g,r[c],-1,r[_]),r[c]=this._legalize(v+2),e[_]=_,M--,_=g;if(y===l)for(;g=n[y],Mu(f,s,t[2*g],t[2*g+1],t[2*y],t[2*y+1])<0;)v=this._addTriangle(g,c,y,-1,r[y],r[g]),this._legalize(v+2),r[g]=v,e[y]=y,M--,y=g;this._hullStart=n[c]=y,e[y]=n[_]=c,e[c]=_,i[this._hashKey(f,s)]=c,i[this._hashKey(t[2*y],t[2*y+1])]=y}this.hull=new Uint32Array(M);for(let t=0,n=this._hullStart;t0?3-e:1+e)/4}(t-this._cx,n-this._cy)*this._hashSize)%this._hashSize}_legalize(t){const{_triangles:n,_halfedges:e,coords:r}=this;let i=0,o=0;for(;;){const a=e[t],u=t-t%3;if(o=u+(t+2)%3,-1===a){if(0===i)break;t=Au[--i];continue}const c=a-a%3,f=u+(t+1)%3,s=c+(a+2)%3,l=n[o],h=n[t],d=n[f],p=n[s];if(Nu(r[2*l],r[2*l+1],r[2*h],r[2*h+1],r[2*d],r[2*d+1],r[2*p],r[2*p+1])){n[t]=p,n[a]=l;const r=e[s];if(-1===r){let n=this._hullStart;do{if(this._hullTri[n]===s){this._hullTri[n]=t;break}n=this._hullPrev[n]}while(n!==this._hullStart)}this._link(t,r),this._link(a,e[o]),this._link(o,s);const u=c+(a+1)%3;i=e&&n[t[a]]>o;)t[a+1]=t[a--];t[a+1]=r}else{let i=e+1,o=r;Pu(t,e+r>>1,i),n[t[e]]>n[t[r]]&&Pu(t,e,r),n[t[i]]>n[t[r]]&&Pu(t,i,r),n[t[e]]>n[t[i]]&&Pu(t,e,i);const a=t[i],u=n[a];for(;;){do{i++}while(n[t[i]]u);if(o=o-e?(Cu(t,n,i,r),Cu(t,n,e,o-1)):(Cu(t,n,e,o-1),Cu(t,n,i,r))}}function Pu(t,n,e){const r=t[n];t[n]=t[e],t[e]=r}function zu(t){return t[0]}function $u(t){return t[1]}const Du=1e-6;class Ru{constructor(){this._x0=this._y0=this._x1=this._y1=null,this._=""}moveTo(t,n){this._+=`M${this._x0=this._x1=+t},${this._y0=this._y1=+n}`}closePath(){null!==this._x1&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")}lineTo(t,n){this._+=`L${this._x1=+t},${this._y1=+n}`}arc(t,n,e){const r=(t=+t)+(e=+e),i=n=+n;if(e<0)throw new Error("negative radius");null===this._x1?this._+=`M${r},${i}`:(Math.abs(this._x1-r)>Du||Math.abs(this._y1-i)>Du)&&(this._+="L"+r+","+i),e&&(this._+=`A${e},${e},0,1,1,${t-e},${n}A${e},${e},0,1,1,${this._x1=r},${this._y1=i}`)}rect(t,n,e,r){this._+=`M${this._x0=this._x1=+t},${this._y0=this._y1=+n}h${+e}v${+r}h${-e}Z`}value(){return this._||null}}class Fu{constructor(){this._=[]}moveTo(t,n){this._.push([t,n])}closePath(){this._.push(this._[0].slice())}lineTo(t,n){this._.push([t,n])}value(){return this._.length?this._:null}}class qu{constructor(t,[n,e,r,i]=[0,0,960,500]){if(!((r=+r)>=(n=+n)&&(i=+i)>=(e=+e)))throw new Error("invalid bounds");this.delaunay=t,this._circumcenters=new Float64Array(2*t.points.length),this.vectors=new Float64Array(2*t.points.length),this.xmax=r,this.xmin=n,this.ymax=i,this.ymin=e,this._init()}update(){return this.delaunay.update(),this._init(),this}_init(){const{delaunay:{points:t,hull:n,triangles:e},vectors:r}=this;let i,o;const a=this.circumcenters=this._circumcenters.subarray(0,e.length/3*2);for(let r,u,c=0,f=0,s=e.length;c1;)i-=2;for(let t=2;t0){if(n>=this.ymax)return null;(i=(this.ymax-n)/r)0){if(t>=this.xmax)return null;(i=(this.xmax-t)/e)this.xmax?2:0)|(nthis.ymax?8:0)}_simplify(t){if(t&&t.length>4){for(let n=0;n2&&function(t){const{triangles:n,coords:e}=t;for(let t=0;t1e-10)return!1}return!0}(t)){this.collinear=Int32Array.from({length:n.length/2},((t,n)=>n)).sort(((t,e)=>n[2*t]-n[2*e]||n[2*t+1]-n[2*e+1]));const t=this.collinear[0],e=this.collinear[this.collinear.length-1],r=[n[2*t],n[2*t+1],n[2*e],n[2*e+1]],i=1e-8*Math.hypot(r[3]-r[1],r[2]-r[0]);for(let t=0,e=n.length/2;t0&&(this.triangles=new Int32Array(3).fill(-1),this.halfedges=new Int32Array(3).fill(-1),this.triangles[0]=r[0],o[r[0]]=1,2===r.length&&(o[r[1]]=0,this.triangles[1]=r[1],this.triangles[2]=r[1]))}voronoi(t){return new qu(this,t)}*neighbors(t){const{inedges:n,hull:e,_hullIndex:r,halfedges:i,triangles:o,collinear:a}=this;if(a){const n=a.indexOf(t);return n>0&&(yield a[n-1]),void(n=0&&i!==e&&i!==r;)e=i;return i}_step(t,n,e){const{inedges:r,hull:i,_hullIndex:o,halfedges:a,triangles:u,points:c}=this;if(-1===r[t]||!c.length)return(t+1)%(c.length>>1);let f=t,s=Iu(n-c[2*t],2)+Iu(e-c[2*t+1],2);const l=r[t];let h=l;do{let r=u[h];const l=Iu(n-c[2*r],2)+Iu(e-c[2*r+1],2);if(l9999?"+"+Ku(n,6):Ku(n,4))+"-"+Ku(t.getUTCMonth()+1,2)+"-"+Ku(t.getUTCDate(),2)+(o?"T"+Ku(e,2)+":"+Ku(r,2)+":"+Ku(i,2)+"."+Ku(o,3)+"Z":i?"T"+Ku(e,2)+":"+Ku(r,2)+":"+Ku(i,2)+"Z":r||e?"T"+Ku(e,2)+":"+Ku(r,2)+"Z":"")}function Ju(t){var n=new RegExp('["'+t+"\n\r]"),e=t.charCodeAt(0);function r(t,n){var r,i=[],o=t.length,a=0,u=0,c=o<=0,f=!1;function s(){if(c)return Hu;if(f)return f=!1,ju;var n,r,i=a;if(t.charCodeAt(i)===Xu){for(;a++=o?c=!0:(r=t.charCodeAt(a++))===Gu?f=!0:r===Vu&&(f=!0,t.charCodeAt(a)===Gu&&++a),t.slice(i+1,n-1).replace(/""/g,'"')}for(;amc(n,e).then((n=>(new DOMParser).parseFromString(n,t)))}var Sc=Ac("application/xml"),Ec=Ac("text/html"),Nc=Ac("image/svg+xml");function kc(t,n,e,r){if(isNaN(n)||isNaN(e))return t;var i,o,a,u,c,f,s,l,h,d=t._root,p={data:r},g=t._x0,y=t._y0,v=t._x1,_=t._y1;if(!d)return t._root=p,t;for(;d.length;)if((f=n>=(o=(g+v)/2))?g=o:v=o,(s=e>=(a=(y+_)/2))?y=a:_=a,i=d,!(d=d[l=s<<1|f]))return i[l]=p,t;if(u=+t._x.call(null,d.data),c=+t._y.call(null,d.data),n===u&&e===c)return p.next=d,i?i[l]=p:t._root=p,t;do{i=i?i[l]=new Array(4):t._root=new Array(4),(f=n>=(o=(g+v)/2))?g=o:v=o,(s=e>=(a=(y+_)/2))?y=a:_=a}while((l=s<<1|f)==(h=(c>=a)<<1|u>=o));return i[h]=d,i[l]=p,t}function Cc(t,n,e,r,i){this.node=t,this.x0=n,this.y0=e,this.x1=r,this.y1=i}function Pc(t){return t[0]}function zc(t){return t[1]}function $c(t,n,e){var r=new Dc(null==n?Pc:n,null==e?zc:e,NaN,NaN,NaN,NaN);return null==t?r:r.addAll(t)}function Dc(t,n,e,r,i,o){this._x=t,this._y=n,this._x0=e,this._y0=r,this._x1=i,this._y1=o,this._root=void 0}function Rc(t){for(var n={data:t.data},e=n;t=t.next;)e=e.next={data:t.data};return n}var Fc=$c.prototype=Dc.prototype;function qc(t){return function(){return t}}function Uc(t){return 1e-6*(t()-.5)}function Ic(t){return t.x+t.vx}function Oc(t){return t.y+t.vy}function Bc(t){return t.index}function Yc(t,n){var e=t.get(n);if(!e)throw new Error("node not found: "+n);return e}Fc.copy=function(){var t,n,e=new Dc(this._x,this._y,this._x0,this._y0,this._x1,this._y1),r=this._root;if(!r)return e;if(!r.length)return e._root=Rc(r),e;for(t=[{source:r,target:e._root=new Array(4)}];r=t.pop();)for(var i=0;i<4;++i)(n=r.source[i])&&(n.length?t.push({source:n,target:r.target[i]=new Array(4)}):r.target[i]=Rc(n));return e},Fc.add=function(t){const n=+this._x.call(null,t),e=+this._y.call(null,t);return kc(this.cover(n,e),n,e,t)},Fc.addAll=function(t){var n,e,r,i,o=t.length,a=new Array(o),u=new Array(o),c=1/0,f=1/0,s=-1/0,l=-1/0;for(e=0;es&&(s=r),il&&(l=i));if(c>s||f>l)return this;for(this.cover(c,f).cover(s,l),e=0;et||t>=i||r>n||n>=o;)switch(u=(nh||(o=c.y0)>d||(a=c.x1)=v)<<1|t>=y)&&(c=p[p.length-1],p[p.length-1]=p[p.length-1-f],p[p.length-1-f]=c)}else{var _=t-+this._x.call(null,g.data),b=n-+this._y.call(null,g.data),m=_*_+b*b;if(m=(u=(p+y)/2))?p=u:y=u,(s=a>=(c=(g+v)/2))?g=c:v=c,n=d,!(d=d[l=s<<1|f]))return this;if(!d.length)break;(n[l+1&3]||n[l+2&3]||n[l+3&3])&&(e=n,h=l)}for(;d.data!==t;)if(r=d,!(d=d.next))return this;return(i=d.next)&&delete d.next,r?(i?r.next=i:delete r.next,this):n?(i?n[l]=i:delete n[l],(d=n[0]||n[1]||n[2]||n[3])&&d===(n[3]||n[2]||n[1]||n[0])&&!d.length&&(e?e[h]=d:this._root=d),this):(this._root=i,this)},Fc.removeAll=function(t){for(var n=0,e=t.length;n1?r[0]+r.slice(2):r,+t.slice(e+1)]}function Zc(t){return(t=Wc(Math.abs(t)))?t[1]:NaN}var Kc,Qc=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function Jc(t){if(!(n=Qc.exec(t)))throw new Error("invalid format: "+t);var n;return new tf({fill:n[1],align:n[2],sign:n[3],symbol:n[4],zero:n[5],width:n[6],comma:n[7],precision:n[8]&&n[8].slice(1),trim:n[9],type:n[10]})}function tf(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}function nf(t,n){var e=Wc(t,n);if(!e)return t+"";var r=e[0],i=e[1];return i<0?"0."+new Array(-i).join("0")+r:r.length>i+1?r.slice(0,i+1)+"."+r.slice(i+1):r+new Array(i-r.length+2).join("0")}Jc.prototype=tf.prototype,tf.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var ef={"%":(t,n)=>(100*t).toFixed(n),b:t=>Math.round(t).toString(2),c:t=>t+"",d:function(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)},e:(t,n)=>t.toExponential(n),f:(t,n)=>t.toFixed(n),g:(t,n)=>t.toPrecision(n),o:t=>Math.round(t).toString(8),p:(t,n)=>nf(100*t,n),r:nf,s:function(t,n){var e=Wc(t,n);if(!e)return t+"";var r=e[0],i=e[1],o=i-(Kc=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,a=r.length;return o===a?r:o>a?r+new Array(o-a+1).join("0"):o>0?r.slice(0,o)+"."+r.slice(o):"0."+new Array(1-o).join("0")+Wc(t,Math.max(0,n+o-1))[0]},X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function rf(t){return t}var of,af=Array.prototype.map,uf=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];function cf(t){var n,e,r=void 0===t.grouping||void 0===t.thousands?rf:(n=af.call(t.grouping,Number),e=t.thousands+"",function(t,r){for(var i=t.length,o=[],a=0,u=n[0],c=0;i>0&&u>0&&(c+u+1>r&&(u=Math.max(1,r-c)),o.push(t.substring(i-=u,i+u)),!((c+=u+1)>r));)u=n[a=(a+1)%n.length];return o.reverse().join(e)}),i=void 0===t.currency?"":t.currency[0]+"",o=void 0===t.currency?"":t.currency[1]+"",a=void 0===t.decimal?".":t.decimal+"",u=void 0===t.numerals?rf:function(t){return function(n){return n.replace(/[0-9]/g,(function(n){return t[+n]}))}}(af.call(t.numerals,String)),c=void 0===t.percent?"%":t.percent+"",f=void 0===t.minus?"−":t.minus+"",s=void 0===t.nan?"NaN":t.nan+"";function l(t){var n=(t=Jc(t)).fill,e=t.align,l=t.sign,h=t.symbol,d=t.zero,p=t.width,g=t.comma,y=t.precision,v=t.trim,_=t.type;"n"===_?(g=!0,_="g"):ef[_]||(void 0===y&&(y=12),v=!0,_="g"),(d||"0"===n&&"="===e)&&(d=!0,n="0",e="=");var b="$"===h?i:"#"===h&&/[boxX]/.test(_)?"0"+_.toLowerCase():"",m="$"===h?o:/[%p]/.test(_)?c:"",x=ef[_],w=/[defgprs%]/.test(_);function M(t){var i,o,c,h=b,M=m;if("c"===_)M=x(t)+M,t="";else{var T=(t=+t)<0||1/t<0;if(t=isNaN(t)?s:x(Math.abs(t),y),v&&(t=function(t){t:for(var n,e=t.length,r=1,i=-1;r0&&(i=0)}return i>0?t.slice(0,i)+t.slice(n+1):t}(t)),T&&0==+t&&"+"!==l&&(T=!1),h=(T?"("===l?l:f:"-"===l||"("===l?"":l)+h,M=("s"===_?uf[8+Kc/3]:"")+M+(T&&"("===l?")":""),w)for(i=-1,o=t.length;++i(c=t.charCodeAt(i))||c>57){M=(46===c?a+t.slice(i+1):t.slice(i))+M,t=t.slice(0,i);break}}g&&!d&&(t=r(t,1/0));var A=h.length+t.length+M.length,S=A>1)+h+t+M+S.slice(A);break;default:t=S+h+t+M}return u(t)}return y=void 0===y?6:/[gprs]/.test(_)?Math.max(1,Math.min(21,y)):Math.max(0,Math.min(20,y)),M.toString=function(){return t+""},M}return{format:l,formatPrefix:function(t,n){var e=l(((t=Jc(t)).type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(Zc(n)/3))),i=Math.pow(10,-r),o=uf[8+r/3];return function(t){return e(i*t)+o}}}}function ff(n){return of=cf(n),t.format=of.format,t.formatPrefix=of.formatPrefix,of}function sf(t){return Math.max(0,-Zc(Math.abs(t)))}function lf(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(Zc(n)/3)))-Zc(Math.abs(t)))}function hf(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,Zc(n)-Zc(t))+1}t.format=void 0,t.formatPrefix=void 0,ff({thousands:",",grouping:[3],currency:["$",""]});var df=1e-6,pf=1e-12,gf=Math.PI,yf=gf/2,vf=gf/4,_f=2*gf,bf=180/gf,mf=gf/180,xf=Math.abs,wf=Math.atan,Mf=Math.atan2,Tf=Math.cos,Af=Math.ceil,Sf=Math.exp,Ef=Math.hypot,Nf=Math.log,kf=Math.pow,Cf=Math.sin,Pf=Math.sign||function(t){return t>0?1:t<0?-1:0},zf=Math.sqrt,$f=Math.tan;function Df(t){return t>1?0:t<-1?gf:Math.acos(t)}function Rf(t){return t>1?yf:t<-1?-yf:Math.asin(t)}function Ff(t){return(t=Cf(t/2))*t}function qf(){}function Uf(t,n){t&&Of.hasOwnProperty(t.type)&&Of[t.type](t,n)}var If={Feature:function(t,n){Uf(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++r=0?1:-1,i=r*e,o=Tf(n=(n*=mf)/2+vf),a=Cf(n),u=Vf*a,c=Gf*o+u*Tf(i),f=u*r*Cf(i);as.add(Mf(f,c)),Xf=t,Gf=o,Vf=a}function ds(t){return[Mf(t[1],t[0]),Rf(t[2])]}function ps(t){var n=t[0],e=t[1],r=Tf(e);return[r*Tf(n),r*Cf(n),Cf(e)]}function gs(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]}function ys(t,n){return[t[1]*n[2]-t[2]*n[1],t[2]*n[0]-t[0]*n[2],t[0]*n[1]-t[1]*n[0]]}function vs(t,n){t[0]+=n[0],t[1]+=n[1],t[2]+=n[2]}function _s(t,n){return[t[0]*n,t[1]*n,t[2]*n]}function bs(t){var n=zf(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]);t[0]/=n,t[1]/=n,t[2]/=n}var ms,xs,ws,Ms,Ts,As,Ss,Es,Ns,ks,Cs,Ps,zs,$s,Ds,Rs,Fs={point:qs,lineStart:Is,lineEnd:Os,polygonStart:function(){Fs.point=Bs,Fs.lineStart=Ys,Fs.lineEnd=Ls,rs=new T,cs.polygonStart()},polygonEnd:function(){cs.polygonEnd(),Fs.point=qs,Fs.lineStart=Is,Fs.lineEnd=Os,as<0?(Wf=-(Kf=180),Zf=-(Qf=90)):rs>df?Qf=90:rs<-df&&(Zf=-90),os[0]=Wf,os[1]=Kf},sphere:function(){Wf=-(Kf=180),Zf=-(Qf=90)}};function qs(t,n){is.push(os=[Wf=t,Kf=t]),nQf&&(Qf=n)}function Us(t,n){var e=ps([t*mf,n*mf]);if(es){var r=ys(es,e),i=ys([r[1],-r[0],0],r);bs(i),i=ds(i);var o,a=t-Jf,u=a>0?1:-1,c=i[0]*bf*u,f=xf(a)>180;f^(u*JfQf&&(Qf=o):f^(u*Jf<(c=(c+360)%360-180)&&cQf&&(Qf=n)),f?tjs(Wf,Kf)&&(Kf=t):js(t,Kf)>js(Wf,Kf)&&(Wf=t):Kf>=Wf?(tKf&&(Kf=t)):t>Jf?js(Wf,t)>js(Wf,Kf)&&(Kf=t):js(t,Kf)>js(Wf,Kf)&&(Wf=t)}else is.push(os=[Wf=t,Kf=t]);nQf&&(Qf=n),es=e,Jf=t}function Is(){Fs.point=Us}function Os(){os[0]=Wf,os[1]=Kf,Fs.point=qs,es=null}function Bs(t,n){if(es){var e=t-Jf;rs.add(xf(e)>180?e+(e>0?360:-360):e)}else ts=t,ns=n;cs.point(t,n),Us(t,n)}function Ys(){cs.lineStart()}function Ls(){Bs(ts,ns),cs.lineEnd(),xf(rs)>df&&(Wf=-(Kf=180)),os[0]=Wf,os[1]=Kf,es=null}function js(t,n){return(n-=t)<0?n+360:n}function Hs(t,n){return t[0]-n[0]}function Xs(t,n){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:ngf&&(t-=Math.round(t/_f)*_f),[t,n]}function ul(t,n,e){return(t%=_f)?n||e?ol(fl(t),sl(n,e)):fl(t):n||e?sl(n,e):al}function cl(t){return function(n,e){return xf(n+=t)>gf&&(n-=Math.round(n/_f)*_f),[n,e]}}function fl(t){var n=cl(t);return n.invert=cl(-t),n}function sl(t,n){var e=Tf(t),r=Cf(t),i=Tf(n),o=Cf(n);function a(t,n){var a=Tf(n),u=Tf(t)*a,c=Cf(t)*a,f=Cf(n),s=f*e+u*r;return[Mf(c*i-s*o,u*e-f*r),Rf(s*i+c*o)]}return a.invert=function(t,n){var a=Tf(n),u=Tf(t)*a,c=Cf(t)*a,f=Cf(n),s=f*i-c*o;return[Mf(c*i+f*o,u*e+s*r),Rf(s*e-u*r)]},a}function ll(t){function n(n){return(n=t(n[0]*mf,n[1]*mf))[0]*=bf,n[1]*=bf,n}return t=ul(t[0]*mf,t[1]*mf,t.length>2?t[2]*mf:0),n.invert=function(n){return(n=t.invert(n[0]*mf,n[1]*mf))[0]*=bf,n[1]*=bf,n},n}function hl(t,n,e,r,i,o){if(e){var a=Tf(n),u=Cf(n),c=r*e;null==i?(i=n+r*_f,o=n-c/2):(i=dl(a,i),o=dl(a,o),(r>0?io)&&(i+=r*_f));for(var f,s=i;r>0?s>o:s1&&n.push(n.pop().concat(n.shift()))},result:function(){var e=n;return n=[],t=null,e}}}function gl(t,n){return xf(t[0]-n[0])=0;--o)i.point((s=f[o])[0],s[1]);else r(h.x,h.p.x,-1,i);h=h.p}f=(h=h.o).z,d=!d}while(!h.v);i.lineEnd()}}}function _l(t){if(n=t.length){for(var n,e,r=0,i=t[0];++r=0?1:-1,E=S*A,N=E>gf,k=y*w;if(c.add(Mf(k*S*Cf(E),v*M+k*Tf(E))),a+=N?A+S*_f:A,N^p>=e^m>=e){var C=ys(ps(d),ps(b));bs(C);var P=ys(o,C);bs(P);var z=(N^A>=0?-1:1)*Rf(P[2]);(r>z||r===z&&(C[0]||C[1]))&&(u+=N^A>=0?1:-1)}}return(a<-df||a0){for(l||(i.polygonStart(),l=!0),i.lineStart(),t=0;t1&&2&c&&h.push(h.pop().concat(h.shift())),a.push(h.filter(wl))}return h}}function wl(t){return t.length>1}function Ml(t,n){return((t=t.x)[0]<0?t[1]-yf-df:yf-t[1])-((n=n.x)[0]<0?n[1]-yf-df:yf-n[1])}al.invert=al;var Tl=xl((function(){return!0}),(function(t){var n,e=NaN,r=NaN,i=NaN;return{lineStart:function(){t.lineStart(),n=1},point:function(o,a){var u=o>0?gf:-gf,c=xf(o-e);xf(c-gf)0?yf:-yf),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(u,r),t.point(o,r),n=0):i!==u&&c>=gf&&(xf(e-i)df?wf((Cf(n)*(o=Tf(r))*Cf(e)-Cf(r)*(i=Tf(n))*Cf(t))/(i*o*a)):(n+r)/2}(e,r,o,a),t.point(i,r),t.lineEnd(),t.lineStart(),t.point(u,r),n=0),t.point(e=o,r=a),i=u},lineEnd:function(){t.lineEnd(),e=r=NaN},clean:function(){return 2-n}}}),(function(t,n,e,r){var i;if(null==t)i=e*yf,r.point(-gf,i),r.point(0,i),r.point(gf,i),r.point(gf,0),r.point(gf,-i),r.point(0,-i),r.point(-gf,-i),r.point(-gf,0),r.point(-gf,i);else if(xf(t[0]-n[0])>df){var o=t[0]0,i=xf(n)>df;function o(t,e){return Tf(t)*Tf(e)>n}function a(t,e,r){var i=[1,0,0],o=ys(ps(t),ps(e)),a=gs(o,o),u=o[0],c=a-u*u;if(!c)return!r&&t;var f=n*a/c,s=-n*u/c,l=ys(i,o),h=_s(i,f);vs(h,_s(o,s));var d=l,p=gs(h,d),g=gs(d,d),y=p*p-g*(gs(h,h)-1);if(!(y<0)){var v=zf(y),_=_s(d,(-p-v)/g);if(vs(_,h),_=ds(_),!r)return _;var b,m=t[0],x=e[0],w=t[1],M=e[1];x0^_[1]<(xf(_[0]-m)gf^(m<=_[0]&&_[0]<=x)){var S=_s(d,(-p+v)/g);return vs(S,h),[_,ds(S)]}}}function u(n,e){var i=r?t:gf-t,o=0;return n<-i?o|=1:n>i&&(o|=2),e<-i?o|=4:e>i&&(o|=8),o}return xl(o,(function(t){var n,e,c,f,s;return{lineStart:function(){f=c=!1,s=1},point:function(l,h){var d,p=[l,h],g=o(l,h),y=r?g?0:u(l,h):g?u(l+(l<0?gf:-gf),h):0;if(!n&&(f=c=g)&&t.lineStart(),g!==c&&(!(d=a(n,p))||gl(n,d)||gl(p,d))&&(p[2]=1),g!==c)s=0,g?(t.lineStart(),d=a(p,n),t.point(d[0],d[1])):(d=a(n,p),t.point(d[0],d[1],2),t.lineEnd()),n=d;else if(i&&n&&r^g){var v;y&e||!(v=a(p,n,!0))||(s=0,r?(t.lineStart(),t.point(v[0][0],v[0][1]),t.point(v[1][0],v[1][1]),t.lineEnd()):(t.point(v[1][0],v[1][1]),t.lineEnd(),t.lineStart(),t.point(v[0][0],v[0][1],3)))}!g||n&&gl(n,p)||t.point(p[0],p[1]),n=p,c=g,e=y},lineEnd:function(){c&&t.lineEnd(),n=null},clean:function(){return s|(f&&c)<<1}}}),(function(n,r,i,o){hl(o,t,e,i,n,r)}),r?[0,-t]:[-gf,t-gf])}var Sl,El,Nl,kl,Cl=1e9,Pl=-Cl;function zl(t,n,e,r){function i(i,o){return t<=i&&i<=e&&n<=o&&o<=r}function o(i,o,u,f){var s=0,l=0;if(null==i||(s=a(i,u))!==(l=a(o,u))||c(i,o)<0^u>0)do{f.point(0===s||3===s?t:e,s>1?r:n)}while((s=(s+u+4)%4)!==l);else f.point(o[0],o[1])}function a(r,i){return xf(r[0]-t)0?0:3:xf(r[0]-e)0?2:1:xf(r[1]-n)0?1:0:i>0?3:2}function u(t,n){return c(t.x,n.x)}function c(t,n){var e=a(t,1),r=a(n,1);return e!==r?e-r:0===e?n[1]-t[1]:1===e?t[0]-n[0]:2===e?t[1]-n[1]:n[0]-t[0]}return function(a){var c,f,s,l,h,d,p,g,y,v,_,b=a,m=pl(),x={point:w,lineStart:function(){x.point=M,f&&f.push(s=[]);v=!0,y=!1,p=g=NaN},lineEnd:function(){c&&(M(l,h),d&&y&&m.rejoin(),c.push(m.result()));x.point=w,y&&b.lineEnd()},polygonStart:function(){b=m,c=[],f=[],_=!0},polygonEnd:function(){var n=function(){for(var n=0,e=0,i=f.length;er&&(h-o)*(r-a)>(d-a)*(t-o)&&++n:d<=r&&(h-o)*(r-a)<(d-a)*(t-o)&&--n;return n}(),e=_&&n,i=(c=ft(c)).length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),o(null,null,1,a),a.lineEnd()),i&&vl(c,u,n,o,a),a.polygonEnd());b=a,c=f=s=null}};function w(t,n){i(t,n)&&b.point(t,n)}function M(o,a){var u=i(o,a);if(f&&s.push([o,a]),v)l=o,h=a,d=u,v=!1,u&&(b.lineStart(),b.point(o,a));else if(u&&y)b.point(o,a);else{var c=[p=Math.max(Pl,Math.min(Cl,p)),g=Math.max(Pl,Math.min(Cl,g))],m=[o=Math.max(Pl,Math.min(Cl,o)),a=Math.max(Pl,Math.min(Cl,a))];!function(t,n,e,r,i,o){var a,u=t[0],c=t[1],f=0,s=1,l=n[0]-u,h=n[1]-c;if(a=e-u,l||!(a>0)){if(a/=l,l<0){if(a0){if(a>s)return;a>f&&(f=a)}if(a=i-u,l||!(a<0)){if(a/=l,l<0){if(a>s)return;a>f&&(f=a)}else if(l>0){if(a0)){if(a/=h,h<0){if(a0){if(a>s)return;a>f&&(f=a)}if(a=o-c,h||!(a<0)){if(a/=h,h<0){if(a>s)return;a>f&&(f=a)}else if(h>0){if(a0&&(t[0]=u+f*l,t[1]=c+f*h),s<1&&(n[0]=u+s*l,n[1]=c+s*h),!0}}}}}(c,m,t,n,e,r)?u&&(b.lineStart(),b.point(o,a),_=!1):(y||(b.lineStart(),b.point(c[0],c[1])),b.point(m[0],m[1]),u||b.lineEnd(),_=!1)}p=o,g=a,y=u}return x}}var $l={sphere:qf,point:qf,lineStart:function(){$l.point=Rl,$l.lineEnd=Dl},lineEnd:qf,polygonStart:qf,polygonEnd:qf};function Dl(){$l.point=$l.lineEnd=qf}function Rl(t,n){El=t*=mf,Nl=Cf(n*=mf),kl=Tf(n),$l.point=Fl}function Fl(t,n){t*=mf;var e=Cf(n*=mf),r=Tf(n),i=xf(t-El),o=Tf(i),a=r*Cf(i),u=kl*e-Nl*r*o,c=Nl*e+kl*r*o;Sl.add(Mf(zf(a*a+u*u),c)),El=t,Nl=e,kl=r}function ql(t){return Sl=new T,Lf(t,$l),+Sl}var Ul=[null,null],Il={type:"LineString",coordinates:Ul};function Ol(t,n){return Ul[0]=t,Ul[1]=n,ql(Il)}var Bl={Feature:function(t,n){return Ll(t.geometry,n)},FeatureCollection:function(t,n){for(var e=t.features,r=-1,i=e.length;++r0&&(i=Ol(t[o],t[o-1]))>0&&e<=i&&r<=i&&(e+r-i)*(1-Math.pow((e-r)/i,2))df})).map(c)).concat(lt(Af(o/d)*d,i,d).filter((function(t){return xf(t%g)>df})).map(f))}return v.lines=function(){return _().map((function(t){return{type:"LineString",coordinates:t}}))},v.outline=function(){return{type:"Polygon",coordinates:[s(r).concat(l(a).slice(1),s(e).reverse().slice(1),l(u).reverse().slice(1))]}},v.extent=function(t){return arguments.length?v.extentMajor(t).extentMinor(t):v.extentMinor()},v.extentMajor=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],u=+t[0][1],a=+t[1][1],r>e&&(t=r,r=e,e=t),u>a&&(t=u,u=a,a=t),v.precision(y)):[[r,u],[e,a]]},v.extentMinor=function(e){return arguments.length?(n=+e[0][0],t=+e[1][0],o=+e[0][1],i=+e[1][1],n>t&&(e=n,n=t,t=e),o>i&&(e=o,o=i,i=e),v.precision(y)):[[n,o],[t,i]]},v.step=function(t){return arguments.length?v.stepMajor(t).stepMinor(t):v.stepMinor()},v.stepMajor=function(t){return arguments.length?(p=+t[0],g=+t[1],v):[p,g]},v.stepMinor=function(t){return arguments.length?(h=+t[0],d=+t[1],v):[h,d]},v.precision=function(h){return arguments.length?(y=+h,c=Wl(o,i,90),f=Zl(n,t,y),s=Wl(u,a,90),l=Zl(r,e,y),v):y},v.extentMajor([[-180,-90+df],[180,90-df]]).extentMinor([[-180,-80-df],[180,80+df]])}var Ql,Jl,th,nh,eh=t=>t,rh=new T,ih=new T,oh={point:qf,lineStart:qf,lineEnd:qf,polygonStart:function(){oh.lineStart=ah,oh.lineEnd=fh},polygonEnd:function(){oh.lineStart=oh.lineEnd=oh.point=qf,rh.add(xf(ih)),ih=new T},result:function(){var t=rh/2;return rh=new T,t}};function ah(){oh.point=uh}function uh(t,n){oh.point=ch,Ql=th=t,Jl=nh=n}function ch(t,n){ih.add(nh*t-th*n),th=t,nh=n}function fh(){ch(Ql,Jl)}var sh=oh,lh=1/0,hh=lh,dh=-lh,ph=dh,gh={point:function(t,n){tdh&&(dh=t);nph&&(ph=n)},lineStart:qf,lineEnd:qf,polygonStart:qf,polygonEnd:qf,result:function(){var t=[[lh,hh],[dh,ph]];return dh=ph=-(hh=lh=1/0),t}};var yh,vh,_h,bh,mh=gh,xh=0,wh=0,Mh=0,Th=0,Ah=0,Sh=0,Eh=0,Nh=0,kh=0,Ch={point:Ph,lineStart:zh,lineEnd:Rh,polygonStart:function(){Ch.lineStart=Fh,Ch.lineEnd=qh},polygonEnd:function(){Ch.point=Ph,Ch.lineStart=zh,Ch.lineEnd=Rh},result:function(){var t=kh?[Eh/kh,Nh/kh]:Sh?[Th/Sh,Ah/Sh]:Mh?[xh/Mh,wh/Mh]:[NaN,NaN];return xh=wh=Mh=Th=Ah=Sh=Eh=Nh=kh=0,t}};function Ph(t,n){xh+=t,wh+=n,++Mh}function zh(){Ch.point=$h}function $h(t,n){Ch.point=Dh,Ph(_h=t,bh=n)}function Dh(t,n){var e=t-_h,r=n-bh,i=zf(e*e+r*r);Th+=i*(_h+t)/2,Ah+=i*(bh+n)/2,Sh+=i,Ph(_h=t,bh=n)}function Rh(){Ch.point=Ph}function Fh(){Ch.point=Uh}function qh(){Ih(yh,vh)}function Uh(t,n){Ch.point=Ih,Ph(yh=_h=t,vh=bh=n)}function Ih(t,n){var e=t-_h,r=n-bh,i=zf(e*e+r*r);Th+=i*(_h+t)/2,Ah+=i*(bh+n)/2,Sh+=i,Eh+=(i=bh*t-_h*n)*(_h+t),Nh+=i*(bh+n),kh+=3*i,Ph(_h=t,bh=n)}var Oh=Ch;function Bh(t){this._context=t}Bh.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._context.moveTo(t,n),this._point=1;break;case 1:this._context.lineTo(t,n);break;default:this._context.moveTo(t+this._radius,n),this._context.arc(t,n,this._radius,0,_f)}},result:qf};var Yh,Lh,jh,Hh,Xh,Gh=new T,Vh={point:qf,lineStart:function(){Vh.point=Wh},lineEnd:function(){Yh&&Zh(Lh,jh),Vh.point=qf},polygonStart:function(){Yh=!0},polygonEnd:function(){Yh=null},result:function(){var t=+Gh;return Gh=new T,t}};function Wh(t,n){Vh.point=Zh,Lh=Hh=t,jh=Xh=n}function Zh(t,n){Hh-=t,Xh-=n,Gh.add(zf(Hh*Hh+Xh*Xh)),Hh=t,Xh=n}var Kh=Vh;let Qh,Jh,td,nd;class ed{constructor(t){this._append=null==t?rd:function(t){const n=Math.floor(t);if(!(n>=0))throw new RangeError(`invalid digits: ${t}`);if(n>15)return rd;if(n!==Qh){const t=10**n;Qh=n,Jh=function(n){let e=1;this._+=n[0];for(const r=n.length;e4*n&&g--){var m=a+h,x=u+d,w=c+p,M=zf(m*m+x*x+w*w),T=Rf(w/=M),A=xf(xf(w)-1)n||xf((v*k+_*C)/b-.5)>.3||a*h+u*d+c*p2?t[2]%360*mf:0,k()):[y*bf,v*bf,_*bf]},E.angle=function(t){return arguments.length?(b=t%360*mf,k()):b*bf},E.reflectX=function(t){return arguments.length?(m=t?-1:1,k()):m<0},E.reflectY=function(t){return arguments.length?(x=t?-1:1,k()):x<0},E.precision=function(t){return arguments.length?(a=dd(u,S=t*t),C()):zf(S)},E.fitExtent=function(t,n){return ud(E,t,n)},E.fitSize=function(t,n){return cd(E,t,n)},E.fitWidth=function(t,n){return fd(E,t,n)},E.fitHeight=function(t,n){return sd(E,t,n)},function(){return n=t.apply(this,arguments),E.invert=n.invert&&N,k()}}function _d(t){var n=0,e=gf/3,r=vd(t),i=r(n,e);return i.parallels=function(t){return arguments.length?r(n=t[0]*mf,e=t[1]*mf):[n*bf,e*bf]},i}function bd(t,n){var e=Cf(t),r=(e+Cf(n))/2;if(xf(r)0?n<-yf+df&&(n=-yf+df):n>yf-df&&(n=yf-df);var e=i/kf(Nd(n),r);return[e*Cf(r*t),i-e*Tf(r*t)]}return o.invert=function(t,n){var e=i-n,o=Pf(r)*zf(t*t+e*e),a=Mf(t,xf(e))*Pf(e);return e*r<0&&(a-=gf*Pf(t)*Pf(e)),[a/r,2*wf(kf(i/o,1/r))-yf]},o}function Cd(t,n){return[t,n]}function Pd(t,n){var e=Tf(t),r=t===n?Cf(t):(e-Tf(n))/(n-t),i=e/r+t;if(xf(r)=0;)n+=e[r].value;else n=1;t.value=n}function Gd(t,n){t instanceof Map?(t=[void 0,t],void 0===n&&(n=Wd)):void 0===n&&(n=Vd);for(var e,r,i,o,a,u=new Qd(t),c=[u];e=c.pop();)if((i=n(e.data))&&(a=(i=Array.from(i)).length))for(e.children=i,o=a-1;o>=0;--o)c.push(r=i[o]=new Qd(i[o])),r.parent=e,r.depth=e.depth+1;return u.eachBefore(Kd)}function Vd(t){return t.children}function Wd(t){return Array.isArray(t)?t[1]:null}function Zd(t){void 0!==t.data.value&&(t.value=t.data.value),t.data=t.data.data}function Kd(t){var n=0;do{t.height=n}while((t=t.parent)&&t.height<++n)}function Qd(t){this.data=t,this.depth=this.height=0,this.parent=null}function Jd(t){return null==t?null:tp(t)}function tp(t){if("function"!=typeof t)throw new Error;return t}function np(){return 0}function ep(t){return function(){return t}}qd.invert=function(t,n){for(var e,r=n,i=r*r,o=i*i*i,a=0;a<12&&(o=(i=(r-=e=(r*(zd+$d*i+o*(Dd+Rd*i))-n)/(zd+3*$d*i+o*(7*Dd+9*Rd*i)))*r)*i*i,!(xf(e)df&&--i>0);return[t/(.8707+(o=r*r)*(o*(o*o*o*(.003971-.001529*o)-.013791)-.131979)),r]},Od.invert=Md(Rf),Bd.invert=Md((function(t){return 2*wf(t)})),Yd.invert=function(t,n){return[-n,2*wf(Sf(t))-yf]},Qd.prototype=Gd.prototype={constructor:Qd,count:function(){return this.eachAfter(Xd)},each:function(t,n){let e=-1;for(const r of this)t.call(n,r,++e,this);return this},eachAfter:function(t,n){for(var e,r,i,o=this,a=[o],u=[],c=-1;o=a.pop();)if(u.push(o),e=o.children)for(r=0,i=e.length;r=0;--r)o.push(e[r]);return this},find:function(t,n){let e=-1;for(const r of this)if(t.call(n,r,++e,this))return r},sum:function(t){return this.eachAfter((function(n){for(var e=+t(n.data)||0,r=n.children,i=r&&r.length;--i>=0;)e+=r[i].value;n.value=e}))},sort:function(t){return this.eachBefore((function(n){n.children&&n.children.sort(t)}))},path:function(t){for(var n=this,e=function(t,n){if(t===n)return t;var e=t.ancestors(),r=n.ancestors(),i=null;t=e.pop(),n=r.pop();for(;t===n;)i=t,t=e.pop(),n=r.pop();return i}(n,t),r=[n];n!==e;)n=n.parent,r.push(n);for(var i=r.length;t!==e;)r.splice(i,0,t),t=t.parent;return r},ancestors:function(){for(var t=this,n=[t];t=t.parent;)n.push(t);return n},descendants:function(){return Array.from(this)},leaves:function(){var t=[];return this.eachBefore((function(n){n.children||t.push(n)})),t},links:function(){var t=this,n=[];return t.each((function(e){e!==t&&n.push({source:e.parent,target:e})})),n},copy:function(){return Gd(this).eachBefore(Zd)},[Symbol.iterator]:function*(){var t,n,e,r,i=this,o=[i];do{for(t=o.reverse(),o=[];i=t.pop();)if(yield i,n=i.children)for(e=0,r=n.length;e(t=(rp*t+ip)%op)/op}function up(t,n){for(var e,r,i=0,o=(t=function(t,n){let e,r,i=t.length;for(;i;)r=n()*i--|0,e=t[i],t[i]=t[r],t[r]=e;return t}(Array.from(t),n)).length,a=[];i0&&e*e>r*r+i*i}function lp(t,n){for(var e=0;e1e-6?(E+Math.sqrt(E*E-4*S*N))/(2*S):N/E);return{x:r+w+M*k,y:i+T+A*k,r:k}}function gp(t,n,e){var r,i,o,a,u=t.x-n.x,c=t.y-n.y,f=u*u+c*c;f?(i=n.r+e.r,i*=i,a=t.r+e.r,i>(a*=a)?(r=(f+a-i)/(2*f),o=Math.sqrt(Math.max(0,a/f-r*r)),e.x=t.x-r*u-o*c,e.y=t.y-r*c+o*u):(r=(f+i-a)/(2*f),o=Math.sqrt(Math.max(0,i/f-r*r)),e.x=n.x+r*u-o*c,e.y=n.y+r*c+o*u)):(e.x=n.x+e.r,e.y=n.y)}function yp(t,n){var e=t.r+n.r-1e-6,r=n.x-t.x,i=n.y-t.y;return e>0&&e*e>r*r+i*i}function vp(t){var n=t._,e=t.next._,r=n.r+e.r,i=(n.x*e.r+e.x*n.r)/r,o=(n.y*e.r+e.y*n.r)/r;return i*i+o*o}function _p(t){this._=t,this.next=null,this.previous=null}function bp(t,n){if(!(o=(t=function(t){return"object"==typeof t&&"length"in t?t:Array.from(t)}(t)).length))return 0;var e,r,i,o,a,u,c,f,s,l,h;if((e=t[0]).x=0,e.y=0,!(o>1))return e.r;if(r=t[1],e.x=-r.r,r.x=e.r,r.y=0,!(o>2))return e.r+r.r;gp(r,e,i=t[2]),e=new _p(e),r=new _p(r),i=new _p(i),e.next=i.previous=r,r.next=e.previous=i,i.next=r.previous=e;t:for(c=3;c1&&!zp(t,n););return t.slice(0,n)}function zp(t,n){if("/"===t[n]){let e=0;for(;n>0&&"\\"===t[--n];)++e;if(!(1&e))return!0}return!1}function $p(t,n){return t.parent===n.parent?1:2}function Dp(t){var n=t.children;return n?n[0]:t.t}function Rp(t){var n=t.children;return n?n[n.length-1]:t.t}function Fp(t,n,e){var r=e/(n.i-t.i);n.c-=r,n.s+=e,t.c+=r,n.z+=e,n.m+=e}function qp(t,n,e){return t.a.parent===n.parent?t.a:e}function Up(t,n){this._=t,this.parent=null,this.children=null,this.A=null,this.a=this,this.z=0,this.m=0,this.c=0,this.s=0,this.t=null,this.i=n}function Ip(t,n,e,r,i){for(var o,a=t.children,u=-1,c=a.length,f=t.value&&(i-e)/t.value;++uh&&(h=u),y=s*s*g,(d=Math.max(h/y,y/l))>p){s-=u;break}p=d}v.push(a={value:s,dice:c1?n:1)},e}(Op);var Lp=function t(n){function e(t,e,r,i,o){if((a=t._squarify)&&a.ratio===n)for(var a,u,c,f,s,l=-1,h=a.length,d=t.value;++l1?n:1)},e}(Op);function jp(t,n,e){return(n[0]-t[0])*(e[1]-t[1])-(n[1]-t[1])*(e[0]-t[0])}function Hp(t,n){return t[0]-n[0]||t[1]-n[1]}function Xp(t){const n=t.length,e=[0,1];let r,i=2;for(r=2;r1&&jp(t[e[i-2]],t[e[i-1]],t[r])<=0;)--i;e[i++]=r}return e.slice(0,i)}var Gp=Math.random,Vp=function t(n){function e(t,e){return t=null==t?0:+t,e=null==e?1:+e,1===arguments.length?(e=t,t=0):e-=t,function(){return n()*e+t}}return e.source=t,e}(Gp),Wp=function t(n){function e(t,e){return arguments.length<2&&(e=t,t=0),t=Math.floor(t),e=Math.floor(e)-t,function(){return Math.floor(n()*e+t)}}return e.source=t,e}(Gp),Zp=function t(n){function e(t,e){var r,i;return t=null==t?0:+t,e=null==e?1:+e,function(){var o;if(null!=r)o=r,r=null;else do{r=2*n()-1,o=2*n()-1,i=r*r+o*o}while(!i||i>1);return t+e*o*Math.sqrt(-2*Math.log(i)/i)}}return e.source=t,e}(Gp),Kp=function t(n){var e=Zp.source(n);function r(){var t=e.apply(this,arguments);return function(){return Math.exp(t())}}return r.source=t,r}(Gp),Qp=function t(n){function e(t){return(t=+t)<=0?()=>0:function(){for(var e=0,r=t;r>1;--r)e+=n();return e+r*n()}}return e.source=t,e}(Gp),Jp=function t(n){var e=Qp.source(n);function r(t){if(0==(t=+t))return n;var r=e(t);return function(){return r()/t}}return r.source=t,r}(Gp),tg=function t(n){function e(t){return function(){return-Math.log1p(-n())/t}}return e.source=t,e}(Gp),ng=function t(n){function e(t){if((t=+t)<0)throw new RangeError("invalid alpha");return t=1/-t,function(){return Math.pow(1-n(),t)}}return e.source=t,e}(Gp),eg=function t(n){function e(t){if((t=+t)<0||t>1)throw new RangeError("invalid p");return function(){return Math.floor(n()+t)}}return e.source=t,e}(Gp),rg=function t(n){function e(t){if((t=+t)<0||t>1)throw new RangeError("invalid p");return 0===t?()=>1/0:1===t?()=>1:(t=Math.log1p(-t),function(){return 1+Math.floor(Math.log1p(-n())/t)})}return e.source=t,e}(Gp),ig=function t(n){var e=Zp.source(n)();function r(t,r){if((t=+t)<0)throw new RangeError("invalid k");if(0===t)return()=>0;if(r=null==r?1:+r,1===t)return()=>-Math.log1p(-n())*r;var i=(t<1?t+1:t)-1/3,o=1/(3*Math.sqrt(i)),a=t<1?()=>Math.pow(n(),1/t):()=>1;return function(){do{do{var t=e(),u=1+o*t}while(u<=0);u*=u*u;var c=1-n()}while(c>=1-.0331*t*t*t*t&&Math.log(c)>=.5*t*t+i*(1-u+Math.log(u)));return i*u*a()*r}}return r.source=t,r}(Gp),og=function t(n){var e=ig.source(n);function r(t,n){var r=e(t),i=e(n);return function(){var t=r();return 0===t?0:t/(t+i())}}return r.source=t,r}(Gp),ag=function t(n){var e=rg.source(n),r=og.source(n);function i(t,n){return t=+t,(n=+n)>=1?()=>t:n<=0?()=>0:function(){for(var i=0,o=t,a=n;o*a>16&&o*(1-a)>16;){var u=Math.floor((o+1)*a),c=r(u,o-u+1)();c<=a?(i+=u,o-=u,a=(a-c)/(1-c)):(o=u-1,a/=c)}for(var f=a<.5,s=e(f?a:1-a),l=s(),h=0;l<=o;++h)l+=s();return i+(f?h:o-h)}}return i.source=t,i}(Gp),ug=function t(n){function e(t,e,r){var i;return 0==(t=+t)?i=t=>-Math.log(t):(t=1/t,i=n=>Math.pow(n,t)),e=null==e?0:+e,r=null==r?1:+r,function(){return e+r*i(-Math.log1p(-n()))}}return e.source=t,e}(Gp),cg=function t(n){function e(t,e){return t=null==t?0:+t,e=null==e?1:+e,function(){return t+e*Math.tan(Math.PI*n())}}return e.source=t,e}(Gp),fg=function t(n){function e(t,e){return t=null==t?0:+t,e=null==e?1:+e,function(){var r=n();return t+e*Math.log(r/(1-r))}}return e.source=t,e}(Gp),sg=function t(n){var e=ig.source(n),r=ag.source(n);function i(t){return function(){for(var i=0,o=t;o>16;){var a=Math.floor(.875*o),u=e(a)();if(u>o)return i+r(a-1,o/u)();i+=a,o-=u}for(var c=-Math.log1p(-n()),f=0;c<=o;++f)c-=Math.log1p(-n());return i+f}}return i.source=t,i}(Gp);const lg=1/4294967296;function hg(t,n){switch(arguments.length){case 0:break;case 1:this.range(t);break;default:this.range(n).domain(t)}return this}function dg(t,n){switch(arguments.length){case 0:break;case 1:"function"==typeof t?this.interpolator(t):this.range(t);break;default:this.domain(t),"function"==typeof n?this.interpolator(n):this.range(n)}return this}const pg=Symbol("implicit");function gg(){var t=new InternMap,n=[],e=[],r=pg;function i(i){let o=t.get(i);if(void 0===o){if(r!==pg)return r;t.set(i,o=n.push(i)-1)}return e[o%e.length]}return i.domain=function(e){if(!arguments.length)return n.slice();n=[],t=new InternMap;for(const r of e)t.has(r)||t.set(r,n.push(r)-1);return i},i.range=function(t){return arguments.length?(e=Array.from(t),i):e.slice()},i.unknown=function(t){return arguments.length?(r=t,i):r},i.copy=function(){return gg(n,e).unknown(r)},hg.apply(i,arguments),i}function yg(){var t,n,e=gg().unknown(void 0),r=e.domain,i=e.range,o=0,a=1,u=!1,c=0,f=0,s=.5;function l(){var e=r().length,l=an&&(e=t,t=n,n=e),function(e){return Math.max(t,Math.min(n,e))}}(a[0],a[t-1])),r=t>2?Mg:wg,i=o=null,l}function l(n){return null==n||isNaN(n=+n)?e:(i||(i=r(a.map(t),u,c)))(t(f(n)))}return l.invert=function(e){return f(n((o||(o=r(u,a.map(t),Yr)))(e)))},l.domain=function(t){return arguments.length?(a=Array.from(t,_g),s()):a.slice()},l.range=function(t){return arguments.length?(u=Array.from(t),s()):u.slice()},l.rangeRound=function(t){return u=Array.from(t),c=Vr,s()},l.clamp=function(t){return arguments.length?(f=!!t||mg,s()):f!==mg},l.interpolate=function(t){return arguments.length?(c=t,s()):c},l.unknown=function(t){return arguments.length?(e=t,l):e},function(e,r){return t=e,n=r,s()}}function Sg(){return Ag()(mg,mg)}function Eg(n,e,r,i){var o,a=W(n,e,r);switch((i=Jc(null==i?",f":i)).type){case"s":var u=Math.max(Math.abs(n),Math.abs(e));return null!=i.precision||isNaN(o=lf(a,u))||(i.precision=o),t.formatPrefix(i,u);case"":case"e":case"g":case"p":case"r":null!=i.precision||isNaN(o=hf(a,Math.max(Math.abs(n),Math.abs(e))))||(i.precision=o-("e"===i.type));break;case"f":case"%":null!=i.precision||isNaN(o=sf(a))||(i.precision=o-2*("%"===i.type))}return t.format(i)}function Ng(t){var n=t.domain;return t.ticks=function(t){var e=n();return G(e[0],e[e.length-1],null==t?10:t)},t.tickFormat=function(t,e){var r=n();return Eg(r[0],r[r.length-1],null==t?10:t,e)},t.nice=function(e){null==e&&(e=10);var r,i,o=n(),a=0,u=o.length-1,c=o[a],f=o[u],s=10;for(f0;){if((i=V(c,f,e))===r)return o[a]=c,o[u]=f,n(o);if(i>0)c=Math.floor(c/i)*i,f=Math.ceil(f/i)*i;else{if(!(i<0))break;c=Math.ceil(c*i)/i,f=Math.floor(f*i)/i}r=i}return t},t}function kg(t,n){var e,r=0,i=(t=t.slice()).length-1,o=t[r],a=t[i];return a-t(-n,e)}function Fg(n){const e=n(Cg,Pg),r=e.domain;let i,o,a=10;function u(){return i=function(t){return t===Math.E?Math.log:10===t&&Math.log10||2===t&&Math.log2||(t=Math.log(t),n=>Math.log(n)/t)}(a),o=function(t){return 10===t?Dg:t===Math.E?Math.exp:n=>Math.pow(t,n)}(a),r()[0]<0?(i=Rg(i),o=Rg(o),n(zg,$g)):n(Cg,Pg),e}return e.base=function(t){return arguments.length?(a=+t,u()):a},e.domain=function(t){return arguments.length?(r(t),u()):r()},e.ticks=t=>{const n=r();let e=n[0],u=n[n.length-1];const c=u0){for(;l<=h;++l)for(f=1;fu)break;p.push(s)}}else for(;l<=h;++l)for(f=a-1;f>=1;--f)if(s=l>0?f/o(-l):f*o(l),!(su)break;p.push(s)}2*p.length{if(null==n&&(n=10),null==r&&(r=10===a?"s":","),"function"!=typeof r&&(a%1||null!=(r=Jc(r)).precision||(r.trim=!0),r=t.format(r)),n===1/0)return r;const u=Math.max(1,a*n/e.ticks().length);return t=>{let n=t/o(Math.round(i(t)));return n*ar(kg(r(),{floor:t=>o(Math.floor(i(t))),ceil:t=>o(Math.ceil(i(t)))})),e}function qg(t){return function(n){return Math.sign(n)*Math.log1p(Math.abs(n/t))}}function Ug(t){return function(n){return Math.sign(n)*Math.expm1(Math.abs(n))*t}}function Ig(t){var n=1,e=t(qg(n),Ug(n));return e.constant=function(e){return arguments.length?t(qg(n=+e),Ug(n)):n},Ng(e)}function Og(t){return function(n){return n<0?-Math.pow(-n,t):Math.pow(n,t)}}function Bg(t){return t<0?-Math.sqrt(-t):Math.sqrt(t)}function Yg(t){return t<0?-t*t:t*t}function Lg(t){var n=t(mg,mg),e=1;return n.exponent=function(n){return arguments.length?1===(e=+n)?t(mg,mg):.5===e?t(Bg,Yg):t(Og(e),Og(1/e)):e},Ng(n)}function jg(){var t=Lg(Ag());return t.copy=function(){return Tg(t,jg()).exponent(t.exponent())},hg.apply(t,arguments),t}function Hg(t){return Math.sign(t)*t*t}const Xg=new Date,Gg=new Date;function Vg(t,n,e,r){function i(n){return t(n=0===arguments.length?new Date:new Date(+n)),n}return i.floor=n=>(t(n=new Date(+n)),n),i.ceil=e=>(t(e=new Date(e-1)),n(e,1),t(e),e),i.round=t=>{const n=i(t),e=i.ceil(t);return t-n(n(t=new Date(+t),null==e?1:Math.floor(e)),t),i.range=(e,r,o)=>{const a=[];if(e=i.ceil(e),o=null==o?1:Math.floor(o),!(e0))return a;let u;do{a.push(u=new Date(+e)),n(e,o),t(e)}while(uVg((n=>{if(n>=n)for(;t(n),!e(n);)n.setTime(n-1)}),((t,r)=>{if(t>=t)if(r<0)for(;++r<=0;)for(;n(t,-1),!e(t););else for(;--r>=0;)for(;n(t,1),!e(t););})),e&&(i.count=(n,r)=>(Xg.setTime(+n),Gg.setTime(+r),t(Xg),t(Gg),Math.floor(e(Xg,Gg))),i.every=t=>(t=Math.floor(t),isFinite(t)&&t>0?t>1?i.filter(r?n=>r(n)%t==0:n=>i.count(0,n)%t==0):i:null)),i}const Wg=Vg((()=>{}),((t,n)=>{t.setTime(+t+n)}),((t,n)=>n-t));Wg.every=t=>(t=Math.floor(t),isFinite(t)&&t>0?t>1?Vg((n=>{n.setTime(Math.floor(n/t)*t)}),((n,e)=>{n.setTime(+n+e*t)}),((n,e)=>(e-n)/t)):Wg:null);const Zg=Wg.range,Kg=1e3,Qg=6e4,Jg=36e5,ty=864e5,ny=6048e5,ey=2592e6,ry=31536e6,iy=Vg((t=>{t.setTime(t-t.getMilliseconds())}),((t,n)=>{t.setTime(+t+n*Kg)}),((t,n)=>(n-t)/Kg),(t=>t.getUTCSeconds())),oy=iy.range,ay=Vg((t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*Kg)}),((t,n)=>{t.setTime(+t+n*Qg)}),((t,n)=>(n-t)/Qg),(t=>t.getMinutes())),uy=ay.range,cy=Vg((t=>{t.setUTCSeconds(0,0)}),((t,n)=>{t.setTime(+t+n*Qg)}),((t,n)=>(n-t)/Qg),(t=>t.getUTCMinutes())),fy=cy.range,sy=Vg((t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*Kg-t.getMinutes()*Qg)}),((t,n)=>{t.setTime(+t+n*Jg)}),((t,n)=>(n-t)/Jg),(t=>t.getHours())),ly=sy.range,hy=Vg((t=>{t.setUTCMinutes(0,0,0)}),((t,n)=>{t.setTime(+t+n*Jg)}),((t,n)=>(n-t)/Jg),(t=>t.getUTCHours())),dy=hy.range,py=Vg((t=>t.setHours(0,0,0,0)),((t,n)=>t.setDate(t.getDate()+n)),((t,n)=>(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Qg)/ty),(t=>t.getDate()-1)),gy=py.range,yy=Vg((t=>{t.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCDate(t.getUTCDate()+n)}),((t,n)=>(n-t)/ty),(t=>t.getUTCDate()-1)),vy=yy.range,_y=Vg((t=>{t.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCDate(t.getUTCDate()+n)}),((t,n)=>(n-t)/ty),(t=>Math.floor(t/ty))),by=_y.range;function my(t){return Vg((n=>{n.setDate(n.getDate()-(n.getDay()+7-t)%7),n.setHours(0,0,0,0)}),((t,n)=>{t.setDate(t.getDate()+7*n)}),((t,n)=>(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Qg)/ny))}const xy=my(0),wy=my(1),My=my(2),Ty=my(3),Ay=my(4),Sy=my(5),Ey=my(6),Ny=xy.range,ky=wy.range,Cy=My.range,Py=Ty.range,zy=Ay.range,$y=Sy.range,Dy=Ey.range;function Ry(t){return Vg((n=>{n.setUTCDate(n.getUTCDate()-(n.getUTCDay()+7-t)%7),n.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCDate(t.getUTCDate()+7*n)}),((t,n)=>(n-t)/ny))}const Fy=Ry(0),qy=Ry(1),Uy=Ry(2),Iy=Ry(3),Oy=Ry(4),By=Ry(5),Yy=Ry(6),Ly=Fy.range,jy=qy.range,Hy=Uy.range,Xy=Iy.range,Gy=Oy.range,Vy=By.range,Wy=Yy.range,Zy=Vg((t=>{t.setDate(1),t.setHours(0,0,0,0)}),((t,n)=>{t.setMonth(t.getMonth()+n)}),((t,n)=>n.getMonth()-t.getMonth()+12*(n.getFullYear()-t.getFullYear())),(t=>t.getMonth())),Ky=Zy.range,Qy=Vg((t=>{t.setUTCDate(1),t.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCMonth(t.getUTCMonth()+n)}),((t,n)=>n.getUTCMonth()-t.getUTCMonth()+12*(n.getUTCFullYear()-t.getUTCFullYear())),(t=>t.getUTCMonth())),Jy=Qy.range,tv=Vg((t=>{t.setMonth(0,1),t.setHours(0,0,0,0)}),((t,n)=>{t.setFullYear(t.getFullYear()+n)}),((t,n)=>n.getFullYear()-t.getFullYear()),(t=>t.getFullYear()));tv.every=t=>isFinite(t=Math.floor(t))&&t>0?Vg((n=>{n.setFullYear(Math.floor(n.getFullYear()/t)*t),n.setMonth(0,1),n.setHours(0,0,0,0)}),((n,e)=>{n.setFullYear(n.getFullYear()+e*t)})):null;const nv=tv.range,ev=Vg((t=>{t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)}),((t,n)=>{t.setUTCFullYear(t.getUTCFullYear()+n)}),((t,n)=>n.getUTCFullYear()-t.getUTCFullYear()),(t=>t.getUTCFullYear()));ev.every=t=>isFinite(t=Math.floor(t))&&t>0?Vg((n=>{n.setUTCFullYear(Math.floor(n.getUTCFullYear()/t)*t),n.setUTCMonth(0,1),n.setUTCHours(0,0,0,0)}),((n,e)=>{n.setUTCFullYear(n.getUTCFullYear()+e*t)})):null;const rv=ev.range;function iv(t,n,e,i,o,a){const u=[[iy,1,Kg],[iy,5,5e3],[iy,15,15e3],[iy,30,3e4],[a,1,Qg],[a,5,3e5],[a,15,9e5],[a,30,18e5],[o,1,Jg],[o,3,108e5],[o,6,216e5],[o,12,432e5],[i,1,ty],[i,2,1728e5],[e,1,ny],[n,1,ey],[n,3,7776e6],[t,1,ry]];function c(n,e,i){const o=Math.abs(e-n)/i,a=r((([,,t])=>t)).right(u,o);if(a===u.length)return t.every(W(n/ry,e/ry,i));if(0===a)return Wg.every(Math.max(W(n,e,i),1));const[c,f]=u[o/u[a-1][2]=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:k_,s:C_,S:Zv,u:Kv,U:Qv,V:t_,w:n_,W:e_,x:null,X:null,y:r_,Y:o_,Z:u_,"%":N_},m={a:function(t){return a[t.getUTCDay()]},A:function(t){return o[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return u[t.getUTCMonth()]},c:null,d:c_,e:c_,f:d_,g:T_,G:S_,H:f_,I:s_,j:l_,L:h_,m:p_,M:g_,p:function(t){return i[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:k_,s:C_,S:y_,u:v_,U:__,V:m_,w:x_,W:w_,x:null,X:null,y:M_,Y:A_,Z:E_,"%":N_},x={a:function(t,n,e){var r=d.exec(n.slice(e));return r?(t.w=p.get(r[0].toLowerCase()),e+r[0].length):-1},A:function(t,n,e){var r=l.exec(n.slice(e));return r?(t.w=h.get(r[0].toLowerCase()),e+r[0].length):-1},b:function(t,n,e){var r=v.exec(n.slice(e));return r?(t.m=_.get(r[0].toLowerCase()),e+r[0].length):-1},B:function(t,n,e){var r=g.exec(n.slice(e));return r?(t.m=y.get(r[0].toLowerCase()),e+r[0].length):-1},c:function(t,e,r){return T(t,n,e,r)},d:zv,e:zv,f:Uv,g:Nv,G:Ev,H:Dv,I:Dv,j:$v,L:qv,m:Pv,M:Rv,p:function(t,n,e){var r=f.exec(n.slice(e));return r?(t.p=s.get(r[0].toLowerCase()),e+r[0].length):-1},q:Cv,Q:Ov,s:Bv,S:Fv,u:Mv,U:Tv,V:Av,w:wv,W:Sv,x:function(t,n,r){return T(t,e,n,r)},X:function(t,n,e){return T(t,r,n,e)},y:Nv,Y:Ev,Z:kv,"%":Iv};function w(t,n){return function(e){var r,i,o,a=[],u=-1,c=0,f=t.length;for(e instanceof Date||(e=new Date(+e));++u53)return null;"w"in o||(o.w=1),"Z"in o?(i=(r=sv(lv(o.y,0,1))).getUTCDay(),r=i>4||0===i?qy.ceil(r):qy(r),r=yy.offset(r,7*(o.V-1)),o.y=r.getUTCFullYear(),o.m=r.getUTCMonth(),o.d=r.getUTCDate()+(o.w+6)%7):(i=(r=fv(lv(o.y,0,1))).getDay(),r=i>4||0===i?wy.ceil(r):wy(r),r=py.offset(r,7*(o.V-1)),o.y=r.getFullYear(),o.m=r.getMonth(),o.d=r.getDate()+(o.w+6)%7)}else("W"in o||"U"in o)&&("w"in o||(o.w="u"in o?o.u%7:"W"in o?1:0),i="Z"in o?sv(lv(o.y,0,1)).getUTCDay():fv(lv(o.y,0,1)).getDay(),o.m=0,o.d="W"in o?(o.w+6)%7+7*o.W-(i+5)%7:o.w+7*o.U-(i+6)%7);return"Z"in o?(o.H+=o.Z/100|0,o.M+=o.Z%100,sv(o)):fv(o)}}function T(t,n,e,r){for(var i,o,a=0,u=n.length,c=e.length;a=c)return-1;if(37===(i=n.charCodeAt(a++))){if(i=n.charAt(a++),!(o=x[i in pv?n.charAt(a++):i])||(r=o(t,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}return b.x=w(e,b),b.X=w(r,b),b.c=w(n,b),m.x=w(e,m),m.X=w(r,m),m.c=w(n,m),{format:function(t){var n=w(t+="",b);return n.toString=function(){return t},n},parse:function(t){var n=M(t+="",!1);return n.toString=function(){return t},n},utcFormat:function(t){var n=w(t+="",m);return n.toString=function(){return t},n},utcParse:function(t){var n=M(t+="",!0);return n.toString=function(){return t},n}}}var dv,pv={"-":"",_:" ",0:"0"},gv=/^\s*\d+/,yv=/^%/,vv=/[\\^$*+?|[\]().{}]/g;function _v(t,n,e){var r=t<0?"-":"",i=(r?-t:t)+"",o=i.length;return r+(o[t.toLowerCase(),n])))}function wv(t,n,e){var r=gv.exec(n.slice(e,e+1));return r?(t.w=+r[0],e+r[0].length):-1}function Mv(t,n,e){var r=gv.exec(n.slice(e,e+1));return r?(t.u=+r[0],e+r[0].length):-1}function Tv(t,n,e){var r=gv.exec(n.slice(e,e+2));return r?(t.U=+r[0],e+r[0].length):-1}function Av(t,n,e){var r=gv.exec(n.slice(e,e+2));return r?(t.V=+r[0],e+r[0].length):-1}function Sv(t,n,e){var r=gv.exec(n.slice(e,e+2));return r?(t.W=+r[0],e+r[0].length):-1}function Ev(t,n,e){var r=gv.exec(n.slice(e,e+4));return r?(t.y=+r[0],e+r[0].length):-1}function Nv(t,n,e){var r=gv.exec(n.slice(e,e+2));return r?(t.y=+r[0]+(+r[0]>68?1900:2e3),e+r[0].length):-1}function kv(t,n,e){var r=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(n.slice(e,e+6));return r?(t.Z=r[1]?0:-(r[2]+(r[3]||"00")),e+r[0].length):-1}function Cv(t,n,e){var r=gv.exec(n.slice(e,e+1));return r?(t.q=3*r[0]-3,e+r[0].length):-1}function Pv(t,n,e){var r=gv.exec(n.slice(e,e+2));return r?(t.m=r[0]-1,e+r[0].length):-1}function zv(t,n,e){var r=gv.exec(n.slice(e,e+2));return r?(t.d=+r[0],e+r[0].length):-1}function $v(t,n,e){var r=gv.exec(n.slice(e,e+3));return r?(t.m=0,t.d=+r[0],e+r[0].length):-1}function Dv(t,n,e){var r=gv.exec(n.slice(e,e+2));return r?(t.H=+r[0],e+r[0].length):-1}function Rv(t,n,e){var r=gv.exec(n.slice(e,e+2));return r?(t.M=+r[0],e+r[0].length):-1}function Fv(t,n,e){var r=gv.exec(n.slice(e,e+2));return r?(t.S=+r[0],e+r[0].length):-1}function qv(t,n,e){var r=gv.exec(n.slice(e,e+3));return r?(t.L=+r[0],e+r[0].length):-1}function Uv(t,n,e){var r=gv.exec(n.slice(e,e+6));return r?(t.L=Math.floor(r[0]/1e3),e+r[0].length):-1}function Iv(t,n,e){var r=yv.exec(n.slice(e,e+1));return r?e+r[0].length:-1}function Ov(t,n,e){var r=gv.exec(n.slice(e));return r?(t.Q=+r[0],e+r[0].length):-1}function Bv(t,n,e){var r=gv.exec(n.slice(e));return r?(t.s=+r[0],e+r[0].length):-1}function Yv(t,n){return _v(t.getDate(),n,2)}function Lv(t,n){return _v(t.getHours(),n,2)}function jv(t,n){return _v(t.getHours()%12||12,n,2)}function Hv(t,n){return _v(1+py.count(tv(t),t),n,3)}function Xv(t,n){return _v(t.getMilliseconds(),n,3)}function Gv(t,n){return Xv(t,n)+"000"}function Vv(t,n){return _v(t.getMonth()+1,n,2)}function Wv(t,n){return _v(t.getMinutes(),n,2)}function Zv(t,n){return _v(t.getSeconds(),n,2)}function Kv(t){var n=t.getDay();return 0===n?7:n}function Qv(t,n){return _v(xy.count(tv(t)-1,t),n,2)}function Jv(t){var n=t.getDay();return n>=4||0===n?Ay(t):Ay.ceil(t)}function t_(t,n){return t=Jv(t),_v(Ay.count(tv(t),t)+(4===tv(t).getDay()),n,2)}function n_(t){return t.getDay()}function e_(t,n){return _v(wy.count(tv(t)-1,t),n,2)}function r_(t,n){return _v(t.getFullYear()%100,n,2)}function i_(t,n){return _v((t=Jv(t)).getFullYear()%100,n,2)}function o_(t,n){return _v(t.getFullYear()%1e4,n,4)}function a_(t,n){var e=t.getDay();return _v((t=e>=4||0===e?Ay(t):Ay.ceil(t)).getFullYear()%1e4,n,4)}function u_(t){var n=t.getTimezoneOffset();return(n>0?"-":(n*=-1,"+"))+_v(n/60|0,"0",2)+_v(n%60,"0",2)}function c_(t,n){return _v(t.getUTCDate(),n,2)}function f_(t,n){return _v(t.getUTCHours(),n,2)}function s_(t,n){return _v(t.getUTCHours()%12||12,n,2)}function l_(t,n){return _v(1+yy.count(ev(t),t),n,3)}function h_(t,n){return _v(t.getUTCMilliseconds(),n,3)}function d_(t,n){return h_(t,n)+"000"}function p_(t,n){return _v(t.getUTCMonth()+1,n,2)}function g_(t,n){return _v(t.getUTCMinutes(),n,2)}function y_(t,n){return _v(t.getUTCSeconds(),n,2)}function v_(t){var n=t.getUTCDay();return 0===n?7:n}function __(t,n){return _v(Fy.count(ev(t)-1,t),n,2)}function b_(t){var n=t.getUTCDay();return n>=4||0===n?Oy(t):Oy.ceil(t)}function m_(t,n){return t=b_(t),_v(Oy.count(ev(t),t)+(4===ev(t).getUTCDay()),n,2)}function x_(t){return t.getUTCDay()}function w_(t,n){return _v(qy.count(ev(t)-1,t),n,2)}function M_(t,n){return _v(t.getUTCFullYear()%100,n,2)}function T_(t,n){return _v((t=b_(t)).getUTCFullYear()%100,n,2)}function A_(t,n){return _v(t.getUTCFullYear()%1e4,n,4)}function S_(t,n){var e=t.getUTCDay();return _v((t=e>=4||0===e?Oy(t):Oy.ceil(t)).getUTCFullYear()%1e4,n,4)}function E_(){return"+0000"}function N_(){return"%"}function k_(t){return+t}function C_(t){return Math.floor(+t/1e3)}function P_(n){return dv=hv(n),t.timeFormat=dv.format,t.timeParse=dv.parse,t.utcFormat=dv.utcFormat,t.utcParse=dv.utcParse,dv}t.timeFormat=void 0,t.timeParse=void 0,t.utcFormat=void 0,t.utcParse=void 0,P_({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var z_="%Y-%m-%dT%H:%M:%S.%LZ";var $_=Date.prototype.toISOString?function(t){return t.toISOString()}:t.utcFormat(z_),D_=$_;var R_=+new Date("2000-01-01T00:00:00.000Z")?function(t){var n=new Date(t);return isNaN(n)?null:n}:t.utcParse(z_),F_=R_;function q_(t){return new Date(t)}function U_(t){return t instanceof Date?+t:+new Date(+t)}function I_(t,n,e,r,i,o,a,u,c,f){var s=Sg(),l=s.invert,h=s.domain,d=f(".%L"),p=f(":%S"),g=f("%I:%M"),y=f("%I %p"),v=f("%a %d"),_=f("%b %d"),b=f("%B"),m=f("%Y");function x(t){return(c(t)Fr(t[t.length-1]),ib=new Array(3).concat("d8b365f5f5f55ab4ac","a6611adfc27d80cdc1018571","a6611adfc27df5f5f580cdc1018571","8c510ad8b365f6e8c3c7eae55ab4ac01665e","8c510ad8b365f6e8c3f5f5f5c7eae55ab4ac01665e","8c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e","8c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e","5430058c510abf812ddfc27df6e8c3c7eae580cdc135978f01665e003c30","5430058c510abf812ddfc27df6e8c3f5f5f5c7eae580cdc135978f01665e003c30").map(H_),ob=rb(ib),ab=new Array(3).concat("af8dc3f7f7f77fbf7b","7b3294c2a5cfa6dba0008837","7b3294c2a5cff7f7f7a6dba0008837","762a83af8dc3e7d4e8d9f0d37fbf7b1b7837","762a83af8dc3e7d4e8f7f7f7d9f0d37fbf7b1b7837","762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b7837","762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b7837","40004b762a839970abc2a5cfe7d4e8d9f0d3a6dba05aae611b783700441b","40004b762a839970abc2a5cfe7d4e8f7f7f7d9f0d3a6dba05aae611b783700441b").map(H_),ub=rb(ab),cb=new Array(3).concat("e9a3c9f7f7f7a1d76a","d01c8bf1b6dab8e1864dac26","d01c8bf1b6daf7f7f7b8e1864dac26","c51b7de9a3c9fde0efe6f5d0a1d76a4d9221","c51b7de9a3c9fde0eff7f7f7e6f5d0a1d76a4d9221","c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221","c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221","8e0152c51b7dde77aef1b6dafde0efe6f5d0b8e1867fbc414d9221276419","8e0152c51b7dde77aef1b6dafde0eff7f7f7e6f5d0b8e1867fbc414d9221276419").map(H_),fb=rb(cb),sb=new Array(3).concat("998ec3f7f7f7f1a340","5e3c99b2abd2fdb863e66101","5e3c99b2abd2f7f7f7fdb863e66101","542788998ec3d8daebfee0b6f1a340b35806","542788998ec3d8daebf7f7f7fee0b6f1a340b35806","5427888073acb2abd2d8daebfee0b6fdb863e08214b35806","5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b35806","2d004b5427888073acb2abd2d8daebfee0b6fdb863e08214b358067f3b08","2d004b5427888073acb2abd2d8daebf7f7f7fee0b6fdb863e08214b358067f3b08").map(H_),lb=rb(sb),hb=new Array(3).concat("ef8a62f7f7f767a9cf","ca0020f4a58292c5de0571b0","ca0020f4a582f7f7f792c5de0571b0","b2182bef8a62fddbc7d1e5f067a9cf2166ac","b2182bef8a62fddbc7f7f7f7d1e5f067a9cf2166ac","b2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac","b2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac","67001fb2182bd6604df4a582fddbc7d1e5f092c5de4393c32166ac053061","67001fb2182bd6604df4a582fddbc7f7f7f7d1e5f092c5de4393c32166ac053061").map(H_),db=rb(hb),pb=new Array(3).concat("ef8a62ffffff999999","ca0020f4a582bababa404040","ca0020f4a582ffffffbababa404040","b2182bef8a62fddbc7e0e0e09999994d4d4d","b2182bef8a62fddbc7ffffffe0e0e09999994d4d4d","b2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d","b2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d","67001fb2182bd6604df4a582fddbc7e0e0e0bababa8787874d4d4d1a1a1a","67001fb2182bd6604df4a582fddbc7ffffffe0e0e0bababa8787874d4d4d1a1a1a").map(H_),gb=rb(pb),yb=new Array(3).concat("fc8d59ffffbf91bfdb","d7191cfdae61abd9e92c7bb6","d7191cfdae61ffffbfabd9e92c7bb6","d73027fc8d59fee090e0f3f891bfdb4575b4","d73027fc8d59fee090ffffbfe0f3f891bfdb4575b4","d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4","d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4","a50026d73027f46d43fdae61fee090e0f3f8abd9e974add14575b4313695","a50026d73027f46d43fdae61fee090ffffbfe0f3f8abd9e974add14575b4313695").map(H_),vb=rb(yb),_b=new Array(3).concat("fc8d59ffffbf91cf60","d7191cfdae61a6d96a1a9641","d7191cfdae61ffffbfa6d96a1a9641","d73027fc8d59fee08bd9ef8b91cf601a9850","d73027fc8d59fee08bffffbfd9ef8b91cf601a9850","d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850","d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850","a50026d73027f46d43fdae61fee08bd9ef8ba6d96a66bd631a9850006837","a50026d73027f46d43fdae61fee08bffffbfd9ef8ba6d96a66bd631a9850006837").map(H_),bb=rb(_b),mb=new Array(3).concat("fc8d59ffffbf99d594","d7191cfdae61abdda42b83ba","d7191cfdae61ffffbfabdda42b83ba","d53e4ffc8d59fee08be6f59899d5943288bd","d53e4ffc8d59fee08bffffbfe6f59899d5943288bd","d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd","d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd","9e0142d53e4ff46d43fdae61fee08be6f598abdda466c2a53288bd5e4fa2","9e0142d53e4ff46d43fdae61fee08bffffbfe6f598abdda466c2a53288bd5e4fa2").map(H_),xb=rb(mb),wb=new Array(3).concat("e5f5f999d8c92ca25f","edf8fbb2e2e266c2a4238b45","edf8fbb2e2e266c2a42ca25f006d2c","edf8fbccece699d8c966c2a42ca25f006d2c","edf8fbccece699d8c966c2a441ae76238b45005824","f7fcfde5f5f9ccece699d8c966c2a441ae76238b45005824","f7fcfde5f5f9ccece699d8c966c2a441ae76238b45006d2c00441b").map(H_),Mb=rb(wb),Tb=new Array(3).concat("e0ecf49ebcda8856a7","edf8fbb3cde38c96c688419d","edf8fbb3cde38c96c68856a7810f7c","edf8fbbfd3e69ebcda8c96c68856a7810f7c","edf8fbbfd3e69ebcda8c96c68c6bb188419d6e016b","f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d6e016b","f7fcfde0ecf4bfd3e69ebcda8c96c68c6bb188419d810f7c4d004b").map(H_),Ab=rb(Tb),Sb=new Array(3).concat("e0f3dba8ddb543a2ca","f0f9e8bae4bc7bccc42b8cbe","f0f9e8bae4bc7bccc443a2ca0868ac","f0f9e8ccebc5a8ddb57bccc443a2ca0868ac","f0f9e8ccebc5a8ddb57bccc44eb3d32b8cbe08589e","f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe08589e","f7fcf0e0f3dbccebc5a8ddb57bccc44eb3d32b8cbe0868ac084081").map(H_),Eb=rb(Sb),Nb=new Array(3).concat("fee8c8fdbb84e34a33","fef0d9fdcc8afc8d59d7301f","fef0d9fdcc8afc8d59e34a33b30000","fef0d9fdd49efdbb84fc8d59e34a33b30000","fef0d9fdd49efdbb84fc8d59ef6548d7301f990000","fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301f990000","fff7ecfee8c8fdd49efdbb84fc8d59ef6548d7301fb300007f0000").map(H_),kb=rb(Nb),Cb=new Array(3).concat("ece2f0a6bddb1c9099","f6eff7bdc9e167a9cf02818a","f6eff7bdc9e167a9cf1c9099016c59","f6eff7d0d1e6a6bddb67a9cf1c9099016c59","f6eff7d0d1e6a6bddb67a9cf3690c002818a016450","fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016450","fff7fbece2f0d0d1e6a6bddb67a9cf3690c002818a016c59014636").map(H_),Pb=rb(Cb),zb=new Array(3).concat("ece7f2a6bddb2b8cbe","f1eef6bdc9e174a9cf0570b0","f1eef6bdc9e174a9cf2b8cbe045a8d","f1eef6d0d1e6a6bddb74a9cf2b8cbe045a8d","f1eef6d0d1e6a6bddb74a9cf3690c00570b0034e7b","fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0034e7b","fff7fbece7f2d0d1e6a6bddb74a9cf3690c00570b0045a8d023858").map(H_),$b=rb(zb),Db=new Array(3).concat("e7e1efc994c7dd1c77","f1eef6d7b5d8df65b0ce1256","f1eef6d7b5d8df65b0dd1c77980043","f1eef6d4b9dac994c7df65b0dd1c77980043","f1eef6d4b9dac994c7df65b0e7298ace125691003f","f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125691003f","f7f4f9e7e1efd4b9dac994c7df65b0e7298ace125698004367001f").map(H_),Rb=rb(Db),Fb=new Array(3).concat("fde0ddfa9fb5c51b8a","feebe2fbb4b9f768a1ae017e","feebe2fbb4b9f768a1c51b8a7a0177","feebe2fcc5c0fa9fb5f768a1c51b8a7a0177","feebe2fcc5c0fa9fb5f768a1dd3497ae017e7a0177","fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a0177","fff7f3fde0ddfcc5c0fa9fb5f768a1dd3497ae017e7a017749006a").map(H_),qb=rb(Fb),Ub=new Array(3).concat("edf8b17fcdbb2c7fb8","ffffcca1dab441b6c4225ea8","ffffcca1dab441b6c42c7fb8253494","ffffccc7e9b47fcdbb41b6c42c7fb8253494","ffffccc7e9b47fcdbb41b6c41d91c0225ea80c2c84","ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea80c2c84","ffffd9edf8b1c7e9b47fcdbb41b6c41d91c0225ea8253494081d58").map(H_),Ib=rb(Ub),Ob=new Array(3).concat("f7fcb9addd8e31a354","ffffccc2e69978c679238443","ffffccc2e69978c67931a354006837","ffffccd9f0a3addd8e78c67931a354006837","ffffccd9f0a3addd8e78c67941ab5d238443005a32","ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443005a32","ffffe5f7fcb9d9f0a3addd8e78c67941ab5d238443006837004529").map(H_),Bb=rb(Ob),Yb=new Array(3).concat("fff7bcfec44fd95f0e","ffffd4fed98efe9929cc4c02","ffffd4fed98efe9929d95f0e993404","ffffd4fee391fec44ffe9929d95f0e993404","ffffd4fee391fec44ffe9929ec7014cc4c028c2d04","ffffe5fff7bcfee391fec44ffe9929ec7014cc4c028c2d04","ffffe5fff7bcfee391fec44ffe9929ec7014cc4c02993404662506").map(H_),Lb=rb(Yb),jb=new Array(3).concat("ffeda0feb24cf03b20","ffffb2fecc5cfd8d3ce31a1c","ffffb2fecc5cfd8d3cf03b20bd0026","ffffb2fed976feb24cfd8d3cf03b20bd0026","ffffb2fed976feb24cfd8d3cfc4e2ae31a1cb10026","ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cb10026","ffffccffeda0fed976feb24cfd8d3cfc4e2ae31a1cbd0026800026").map(H_),Hb=rb(jb),Xb=new Array(3).concat("deebf79ecae13182bd","eff3ffbdd7e76baed62171b5","eff3ffbdd7e76baed63182bd08519c","eff3ffc6dbef9ecae16baed63182bd08519c","eff3ffc6dbef9ecae16baed64292c62171b5084594","f7fbffdeebf7c6dbef9ecae16baed64292c62171b5084594","f7fbffdeebf7c6dbef9ecae16baed64292c62171b508519c08306b").map(H_),Gb=rb(Xb),Vb=new Array(3).concat("e5f5e0a1d99b31a354","edf8e9bae4b374c476238b45","edf8e9bae4b374c47631a354006d2c","edf8e9c7e9c0a1d99b74c47631a354006d2c","edf8e9c7e9c0a1d99b74c47641ab5d238b45005a32","f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45005a32","f7fcf5e5f5e0c7e9c0a1d99b74c47641ab5d238b45006d2c00441b").map(H_),Wb=rb(Vb),Zb=new Array(3).concat("f0f0f0bdbdbd636363","f7f7f7cccccc969696525252","f7f7f7cccccc969696636363252525","f7f7f7d9d9d9bdbdbd969696636363252525","f7f7f7d9d9d9bdbdbd969696737373525252252525","fffffff0f0f0d9d9d9bdbdbd969696737373525252252525","fffffff0f0f0d9d9d9bdbdbd969696737373525252252525000000").map(H_),Kb=rb(Zb),Qb=new Array(3).concat("efedf5bcbddc756bb1","f2f0f7cbc9e29e9ac86a51a3","f2f0f7cbc9e29e9ac8756bb154278f","f2f0f7dadaebbcbddc9e9ac8756bb154278f","f2f0f7dadaebbcbddc9e9ac8807dba6a51a34a1486","fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a34a1486","fcfbfdefedf5dadaebbcbddc9e9ac8807dba6a51a354278f3f007d").map(H_),Jb=rb(Qb),tm=new Array(3).concat("fee0d2fc9272de2d26","fee5d9fcae91fb6a4acb181d","fee5d9fcae91fb6a4ade2d26a50f15","fee5d9fcbba1fc9272fb6a4ade2d26a50f15","fee5d9fcbba1fc9272fb6a4aef3b2ccb181d99000d","fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181d99000d","fff5f0fee0d2fcbba1fc9272fb6a4aef3b2ccb181da50f1567000d").map(H_),nm=rb(tm),em=new Array(3).concat("fee6cefdae6be6550d","feeddefdbe85fd8d3cd94701","feeddefdbe85fd8d3ce6550da63603","feeddefdd0a2fdae6bfd8d3ce6550da63603","feeddefdd0a2fdae6bfd8d3cf16913d948018c2d04","fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d948018c2d04","fff5ebfee6cefdd0a2fdae6bfd8d3cf16913d94801a636037f2704").map(H_),rm=rb(em);var im=hi(Tr(300,.5,0),Tr(-240,.5,1)),om=hi(Tr(-100,.75,.35),Tr(80,1.5,.8)),am=hi(Tr(260,.75,.35),Tr(80,1.5,.8)),um=Tr();var cm=Fe(),fm=Math.PI/3,sm=2*Math.PI/3;function lm(t){var n=t.length;return function(e){return t[Math.max(0,Math.min(n-1,Math.floor(e*n)))]}}var hm=lm(H_("44015444025645045745055946075a46085c460a5d460b5e470d60470e6147106347116447136548146748166848176948186a481a6c481b6d481c6e481d6f481f70482071482173482374482475482576482677482878482979472a7a472c7a472d7b472e7c472f7d46307e46327e46337f463480453581453781453882443983443a83443b84433d84433e85423f854240864241864142874144874045884046883f47883f48893e49893e4a893e4c8a3d4d8a3d4e8a3c4f8a3c508b3b518b3b528b3a538b3a548c39558c39568c38588c38598c375a8c375b8d365c8d365d8d355e8d355f8d34608d34618d33628d33638d32648e32658e31668e31678e31688e30698e306a8e2f6b8e2f6c8e2e6d8e2e6e8e2e6f8e2d708e2d718e2c718e2c728e2c738e2b748e2b758e2a768e2a778e2a788e29798e297a8e297b8e287c8e287d8e277e8e277f8e27808e26818e26828e26828e25838e25848e25858e24868e24878e23888e23898e238a8d228b8d228c8d228d8d218e8d218f8d21908d21918c20928c20928c20938c1f948c1f958b1f968b1f978b1f988b1f998a1f9a8a1e9b8a1e9c891e9d891f9e891f9f881fa0881fa1881fa1871fa28720a38620a48621a58521a68522a78522a88423a98324aa8325ab8225ac8226ad8127ad8128ae8029af7f2ab07f2cb17e2db27d2eb37c2fb47c31b57b32b67a34b67935b77937b87838b9773aba763bbb753dbc743fbc7340bd7242be7144bf7046c06f48c16e4ac16d4cc26c4ec36b50c46a52c56954c56856c66758c7655ac8645cc8635ec96260ca6063cb5f65cb5e67cc5c69cd5b6ccd5a6ece5870cf5773d05675d05477d1537ad1517cd2507fd34e81d34d84d44b86d54989d5488bd6468ed64590d74393d74195d84098d83e9bd93c9dd93ba0da39a2da37a5db36a8db34aadc32addc30b0dd2fb2dd2db5de2bb8de29bade28bddf26c0df25c2df23c5e021c8e020cae11fcde11dd0e11cd2e21bd5e21ad8e219dae319dde318dfe318e2e418e5e419e7e419eae51aece51befe51cf1e51df4e61ef6e620f8e621fbe723fde725")),dm=lm(H_("00000401000501010601010802010902020b02020d03030f03031204041405041606051806051a07061c08071e0907200a08220b09240c09260d0a290e0b2b100b2d110c2f120d31130d34140e36150e38160f3b180f3d19103f1a10421c10441d11471e114920114b21114e22115024125325125527125829115a2a115c2c115f2d11612f116331116533106734106936106b38106c390f6e3b0f703d0f713f0f72400f74420f75440f764510774710784910784a10794c117a4e117b4f127b51127c52137c54137d56147d57157e59157e5a167e5c167f5d177f5f187f601880621980641a80651a80671b80681c816a1c816b1d816d1d816e1e81701f81721f817320817521817621817822817922827b23827c23827e24828025828125818326818426818627818827818928818b29818c29818e2a81902a81912b81932b80942c80962c80982d80992d809b2e7f9c2e7f9e2f7fa02f7fa1307ea3307ea5317ea6317da8327daa337dab337cad347cae347bb0357bb2357bb3367ab5367ab73779b83779ba3878bc3978bd3977bf3a77c03a76c23b75c43c75c53c74c73d73c83e73ca3e72cc3f71cd4071cf4070d0416fd2426fd3436ed5446dd6456cd8456cd9466bdb476adc4869de4968df4a68e04c67e24d66e34e65e44f64e55064e75263e85362e95462ea5661eb5760ec5860ed5a5fee5b5eef5d5ef05f5ef1605df2625df2645cf3655cf4675cf4695cf56b5cf66c5cf66e5cf7705cf7725cf8745cf8765cf9785df9795df97b5dfa7d5efa7f5efa815ffb835ffb8560fb8761fc8961fc8a62fc8c63fc8e64fc9065fd9266fd9467fd9668fd9869fd9a6afd9b6bfe9d6cfe9f6dfea16efea36ffea571fea772fea973feaa74feac76feae77feb078feb27afeb47bfeb67cfeb77efeb97ffebb81febd82febf84fec185fec287fec488fec68afec88cfeca8dfecc8ffecd90fecf92fed194fed395fed597fed799fed89afdda9cfddc9efddea0fde0a1fde2a3fde3a5fde5a7fde7a9fde9aafdebacfcecaefceeb0fcf0b2fcf2b4fcf4b6fcf6b8fcf7b9fcf9bbfcfbbdfcfdbf")),pm=lm(H_("00000401000501010601010802010a02020c02020e03021004031204031405041706041907051b08051d09061f0a07220b07240c08260d08290e092b10092d110a30120a32140b34150b37160b39180c3c190c3e1b0c411c0c431e0c451f0c48210c4a230c4c240c4f260c51280b53290b552b0b572d0b592f0a5b310a5c320a5e340a5f3609613809623909633b09643d09653e0966400a67420a68440a68450a69470b6a490b6a4a0c6b4c0c6b4d0d6c4f0d6c510e6c520e6d540f6d550f6d57106e59106e5a116e5c126e5d126e5f136e61136e62146e64156e65156e67166e69166e6a176e6c186e6d186e6f196e71196e721a6e741a6e751b6e771c6d781c6d7a1d6d7c1d6d7d1e6d7f1e6c801f6c82206c84206b85216b87216b88226a8a226a8c23698d23698f24699025689225689326679526679727669827669a28659b29649d29649f2a63a02a63a22b62a32c61a52c60a62d60a82e5fa92e5eab2f5ead305dae305cb0315bb1325ab3325ab43359b63458b73557b93556ba3655bc3754bd3853bf3952c03a51c13a50c33b4fc43c4ec63d4dc73e4cc83f4bca404acb4149cc4248ce4347cf4446d04545d24644d34743d44842d54a41d74b3fd84c3ed94d3dda4e3cdb503bdd513ade5238df5337e05536e15635e25734e35933e45a31e55c30e65d2fe75e2ee8602de9612bea632aeb6429eb6628ec6726ed6925ee6a24ef6c23ef6e21f06f20f1711ff1731df2741cf3761bf37819f47918f57b17f57d15f67e14f68013f78212f78410f8850ff8870ef8890cf98b0bf98c0af98e09fa9008fa9207fa9407fb9606fb9706fb9906fb9b06fb9d07fc9f07fca108fca309fca50afca60cfca80dfcaa0ffcac11fcae12fcb014fcb216fcb418fbb61afbb81dfbba1ffbbc21fbbe23fac026fac228fac42afac62df9c72ff9c932f9cb35f8cd37f8cf3af7d13df7d340f6d543f6d746f5d949f5db4cf4dd4ff4df53f4e156f3e35af3e55df2e661f2e865f2ea69f1ec6df1ed71f1ef75f1f179f2f27df2f482f3f586f3f68af4f88ef5f992f6fa96f8fb9af9fc9dfafda1fcffa4")),gm=lm(H_("0d088710078813078916078a19068c1b068d1d068e20068f2206902406912605912805922a05932c05942e05952f059631059733059735049837049938049a3a049a3c049b3e049c3f049c41049d43039e44039e46039f48039f4903a04b03a14c02a14e02a25002a25102a35302a35502a45601a45801a45901a55b01a55c01a65e01a66001a66100a76300a76400a76600a76700a86900a86a00a86c00a86e00a86f00a87100a87201a87401a87501a87701a87801a87a02a87b02a87d03a87e03a88004a88104a78305a78405a78606a68707a68808a68a09a58b0aa58d0ba58e0ca48f0da4910ea3920fa39410a29511a19613a19814a099159f9a169f9c179e9d189d9e199da01a9ca11b9ba21d9aa31e9aa51f99a62098a72197a82296aa2395ab2494ac2694ad2793ae2892b02991b12a90b22b8fb32c8eb42e8db52f8cb6308bb7318ab83289ba3388bb3488bc3587bd3786be3885bf3984c03a83c13b82c23c81c33d80c43e7fc5407ec6417dc7427cc8437bc9447aca457acb4679cc4778cc4977cd4a76ce4b75cf4c74d04d73d14e72d24f71d35171d45270d5536fd5546ed6556dd7566cd8576bd9586ada5a6ada5b69db5c68dc5d67dd5e66de5f65de6164df6263e06363e16462e26561e26660e3685fe4695ee56a5de56b5de66c5ce76e5be76f5ae87059e97158e97257ea7457eb7556eb7655ec7754ed7953ed7a52ee7b51ef7c51ef7e50f07f4ff0804ef1814df1834cf2844bf3854bf3874af48849f48948f58b47f58c46f68d45f68f44f79044f79143f79342f89441f89540f9973ff9983ef99a3efa9b3dfa9c3cfa9e3bfb9f3afba139fba238fca338fca537fca636fca835fca934fdab33fdac33fdae32fdaf31fdb130fdb22ffdb42ffdb52efeb72dfeb82cfeba2cfebb2bfebd2afebe2afec029fdc229fdc328fdc527fdc627fdc827fdca26fdcb26fccd25fcce25fcd025fcd225fbd324fbd524fbd724fad824fada24f9dc24f9dd25f8df25f8e125f7e225f7e425f6e626f6e826f5e926f5eb27f4ed27f3ee27f3f027f2f227f1f426f1f525f0f724f0f921"));function ym(t){return function(){return t}}const vm=Math.abs,_m=Math.atan2,bm=Math.cos,mm=Math.max,xm=Math.min,wm=Math.sin,Mm=Math.sqrt,Tm=1e-12,Am=Math.PI,Sm=Am/2,Em=2*Am;function Nm(t){return t>=1?Sm:t<=-1?-Sm:Math.asin(t)}function km(t){let n=3;return t.digits=function(e){if(!arguments.length)return n;if(null==e)n=null;else{const t=Math.floor(e);if(!(t>=0))throw new RangeError(`invalid digits: ${e}`);n=t}return t},()=>new Ua(n)}function Cm(t){return t.innerRadius}function Pm(t){return t.outerRadius}function zm(t){return t.startAngle}function $m(t){return t.endAngle}function Dm(t){return t&&t.padAngle}function Rm(t,n,e,r,i,o,a){var u=t-e,c=n-r,f=(a?o:-o)/Mm(u*u+c*c),s=f*c,l=-f*u,h=t+s,d=n+l,p=e+s,g=r+l,y=(h+p)/2,v=(d+g)/2,_=p-h,b=g-d,m=_*_+b*b,x=i-o,w=h*g-p*d,M=(b<0?-1:1)*Mm(mm(0,x*x*m-w*w)),T=(w*b-_*M)/m,A=(-w*_-b*M)/m,S=(w*b+_*M)/m,E=(-w*_+b*M)/m,N=T-y,k=A-v,C=S-y,P=E-v;return N*N+k*k>C*C+P*P&&(T=S,A=E),{cx:T,cy:A,x01:-s,y01:-l,x11:T*(i/x-1),y11:A*(i/x-1)}}var Fm=Array.prototype.slice;function qm(t){return"object"==typeof t&&"length"in t?t:Array.from(t)}function Um(t){this._context=t}function Im(t){return new Um(t)}function Om(t){return t[0]}function Bm(t){return t[1]}function Ym(t,n){var e=ym(!0),r=null,i=Im,o=null,a=km(u);function u(u){var c,f,s,l=(u=qm(u)).length,h=!1;for(null==r&&(o=i(s=a())),c=0;c<=l;++c)!(c=l;--h)u.point(v[h],_[h]);u.lineEnd(),u.areaEnd()}y&&(v[s]=+t(d,s,f),_[s]=+n(d,s,f),u.point(r?+r(d,s,f):v[s],e?+e(d,s,f):_[s]))}if(p)return u=null,p+""||null}function s(){return Ym().defined(i).curve(a).context(o)}return t="function"==typeof t?t:void 0===t?Om:ym(+t),n="function"==typeof n?n:ym(void 0===n?0:+n),e="function"==typeof e?e:void 0===e?Bm:ym(+e),f.x=function(n){return arguments.length?(t="function"==typeof n?n:ym(+n),r=null,f):t},f.x0=function(n){return arguments.length?(t="function"==typeof n?n:ym(+n),f):t},f.x1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:ym(+t),f):r},f.y=function(t){return arguments.length?(n="function"==typeof t?t:ym(+t),e=null,f):n},f.y0=function(t){return arguments.length?(n="function"==typeof t?t:ym(+t),f):n},f.y1=function(t){return arguments.length?(e=null==t?null:"function"==typeof t?t:ym(+t),f):e},f.lineX0=f.lineY0=function(){return s().x(t).y(n)},f.lineY1=function(){return s().x(t).y(e)},f.lineX1=function(){return s().x(r).y(n)},f.defined=function(t){return arguments.length?(i="function"==typeof t?t:ym(!!t),f):i},f.curve=function(t){return arguments.length?(a=t,null!=o&&(u=a(o)),f):a},f.context=function(t){return arguments.length?(null==t?o=u=null:u=a(o=t),f):o},f}function jm(t,n){return nt?1:n>=t?0:NaN}function Hm(t){return t}Um.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:this._context.lineTo(t,n)}}};var Xm=Vm(Im);function Gm(t){this._curve=t}function Vm(t){function n(n){return new Gm(t(n))}return n._curve=t,n}function Wm(t){var n=t.curve;return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t.curve=function(t){return arguments.length?n(Vm(t)):n()._curve},t}function Zm(){return Wm(Ym().curve(Xm))}function Km(){var t=Lm().curve(Xm),n=t.curve,e=t.lineX0,r=t.lineX1,i=t.lineY0,o=t.lineY1;return t.angle=t.x,delete t.x,t.startAngle=t.x0,delete t.x0,t.endAngle=t.x1,delete t.x1,t.radius=t.y,delete t.y,t.innerRadius=t.y0,delete t.y0,t.outerRadius=t.y1,delete t.y1,t.lineStartAngle=function(){return Wm(e())},delete t.lineX0,t.lineEndAngle=function(){return Wm(r())},delete t.lineX1,t.lineInnerRadius=function(){return Wm(i())},delete t.lineY0,t.lineOuterRadius=function(){return Wm(o())},delete t.lineY1,t.curve=function(t){return arguments.length?n(Vm(t)):n()._curve},t}function Qm(t,n){return[(n=+n)*Math.cos(t-=Math.PI/2),n*Math.sin(t)]}Gm.prototype={areaStart:function(){this._curve.areaStart()},areaEnd:function(){this._curve.areaEnd()},lineStart:function(){this._curve.lineStart()},lineEnd:function(){this._curve.lineEnd()},point:function(t,n){this._curve.point(n*Math.sin(t),n*-Math.cos(t))}};class Jm{constructor(t,n){this._context=t,this._x=n}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line}point(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:this._x?this._context.bezierCurveTo(this._x0=(this._x0+t)/2,this._y0,this._x0,n,t,n):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+n)/2,t,this._y0,t,n)}this._x0=t,this._y0=n}}class tx{constructor(t){this._context=t}lineStart(){this._point=0}lineEnd(){}point(t,n){if(t=+t,n=+n,0===this._point)this._point=1;else{const e=Qm(this._x0,this._y0),r=Qm(this._x0,this._y0=(this._y0+n)/2),i=Qm(t,this._y0),o=Qm(t,n);this._context.moveTo(...e),this._context.bezierCurveTo(...r,...i,...o)}this._x0=t,this._y0=n}}function nx(t){return new Jm(t,!0)}function ex(t){return new Jm(t,!1)}function rx(t){return new tx(t)}function ix(t){return t.source}function ox(t){return t.target}function ax(t){let n=ix,e=ox,r=Om,i=Bm,o=null,a=null,u=km(c);function c(){let c;const f=Fm.call(arguments),s=n.apply(this,f),l=e.apply(this,f);if(null==o&&(a=t(c=u())),a.lineStart(),f[0]=s,a.point(+r.apply(this,f),+i.apply(this,f)),f[0]=l,a.point(+r.apply(this,f),+i.apply(this,f)),a.lineEnd(),c)return a=null,c+""||null}return c.source=function(t){return arguments.length?(n=t,c):n},c.target=function(t){return arguments.length?(e=t,c):e},c.x=function(t){return arguments.length?(r="function"==typeof t?t:ym(+t),c):r},c.y=function(t){return arguments.length?(i="function"==typeof t?t:ym(+t),c):i},c.context=function(n){return arguments.length?(null==n?o=a=null:a=t(o=n),c):o},c}const ux=Mm(3);var cx={draw(t,n){const e=.59436*Mm(n+xm(n/28,.75)),r=e/2,i=r*ux;t.moveTo(0,e),t.lineTo(0,-e),t.moveTo(-i,-r),t.lineTo(i,r),t.moveTo(-i,r),t.lineTo(i,-r)}},fx={draw(t,n){const e=Mm(n/Am);t.moveTo(e,0),t.arc(0,0,e,0,Em)}},sx={draw(t,n){const e=Mm(n/5)/2;t.moveTo(-3*e,-e),t.lineTo(-e,-e),t.lineTo(-e,-3*e),t.lineTo(e,-3*e),t.lineTo(e,-e),t.lineTo(3*e,-e),t.lineTo(3*e,e),t.lineTo(e,e),t.lineTo(e,3*e),t.lineTo(-e,3*e),t.lineTo(-e,e),t.lineTo(-3*e,e),t.closePath()}};const lx=Mm(1/3),hx=2*lx;var dx={draw(t,n){const e=Mm(n/hx),r=e*lx;t.moveTo(0,-e),t.lineTo(r,0),t.lineTo(0,e),t.lineTo(-r,0),t.closePath()}},px={draw(t,n){const e=.62625*Mm(n);t.moveTo(0,-e),t.lineTo(e,0),t.lineTo(0,e),t.lineTo(-e,0),t.closePath()}},gx={draw(t,n){const e=.87559*Mm(n-xm(n/7,2));t.moveTo(-e,0),t.lineTo(e,0),t.moveTo(0,e),t.lineTo(0,-e)}},yx={draw(t,n){const e=Mm(n),r=-e/2;t.rect(r,r,e,e)}},vx={draw(t,n){const e=.4431*Mm(n);t.moveTo(e,e),t.lineTo(e,-e),t.lineTo(-e,-e),t.lineTo(-e,e),t.closePath()}};const _x=wm(Am/10)/wm(7*Am/10),bx=wm(Em/10)*_x,mx=-bm(Em/10)*_x;var xx={draw(t,n){const e=Mm(.8908130915292852*n),r=bx*e,i=mx*e;t.moveTo(0,-e),t.lineTo(r,i);for(let n=1;n<5;++n){const o=Em*n/5,a=bm(o),u=wm(o);t.lineTo(u*e,-a*e),t.lineTo(a*r-u*i,u*r+a*i)}t.closePath()}};const wx=Mm(3);var Mx={draw(t,n){const e=-Mm(n/(3*wx));t.moveTo(0,2*e),t.lineTo(-wx*e,-e),t.lineTo(wx*e,-e),t.closePath()}};const Tx=Mm(3);var Ax={draw(t,n){const e=.6824*Mm(n),r=e/2,i=e*Tx/2;t.moveTo(0,-e),t.lineTo(i,r),t.lineTo(-i,r),t.closePath()}};const Sx=-.5,Ex=Mm(3)/2,Nx=1/Mm(12),kx=3*(Nx/2+1);var Cx={draw(t,n){const e=Mm(n/kx),r=e/2,i=e*Nx,o=r,a=e*Nx+e,u=-o,c=a;t.moveTo(r,i),t.lineTo(o,a),t.lineTo(u,c),t.lineTo(Sx*r-Ex*i,Ex*r+Sx*i),t.lineTo(Sx*o-Ex*a,Ex*o+Sx*a),t.lineTo(Sx*u-Ex*c,Ex*u+Sx*c),t.lineTo(Sx*r+Ex*i,Sx*i-Ex*r),t.lineTo(Sx*o+Ex*a,Sx*a-Ex*o),t.lineTo(Sx*u+Ex*c,Sx*c-Ex*u),t.closePath()}},Px={draw(t,n){const e=.6189*Mm(n-xm(n/6,1.7));t.moveTo(-e,-e),t.lineTo(e,e),t.moveTo(-e,e),t.lineTo(e,-e)}};const zx=[fx,sx,dx,yx,xx,Mx,Cx],$x=[fx,gx,Px,Ax,cx,vx,px];function Dx(){}function Rx(t,n,e){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+n)/6,(t._y0+4*t._y1+e)/6)}function Fx(t){this._context=t}function qx(t){this._context=t}function Ux(t){this._context=t}function Ix(t,n){this._basis=new Fx(t),this._beta=n}Fx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:Rx(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:Rx(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},qx.prototype={areaStart:Dx,areaEnd:Dx,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x2=t,this._y2=n;break;case 1:this._point=2,this._x3=t,this._y3=n;break;case 2:this._point=3,this._x4=t,this._y4=n,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+n)/6);break;default:Rx(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},Ux.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var e=(this._x0+4*this._x1+t)/6,r=(this._y0+4*this._y1+n)/6;this._line?this._context.lineTo(e,r):this._context.moveTo(e,r);break;case 3:this._point=4;default:Rx(this,t,n)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n}},Ix.prototype={lineStart:function(){this._x=[],this._y=[],this._basis.lineStart()},lineEnd:function(){var t=this._x,n=this._y,e=t.length-1;if(e>0)for(var r,i=t[0],o=n[0],a=t[e]-i,u=n[e]-o,c=-1;++c<=e;)r=c/e,this._basis.point(this._beta*t[c]+(1-this._beta)*(i+r*a),this._beta*n[c]+(1-this._beta)*(o+r*u));this._x=this._y=null,this._basis.lineEnd()},point:function(t,n){this._x.push(+t),this._y.push(+n)}};var Ox=function t(n){function e(t){return 1===n?new Fx(t):new Ix(t,n)}return e.beta=function(n){return t(+n)},e}(.85);function Bx(t,n,e){t._context.bezierCurveTo(t._x1+t._k*(t._x2-t._x0),t._y1+t._k*(t._y2-t._y0),t._x2+t._k*(t._x1-n),t._y2+t._k*(t._y1-e),t._x2,t._y2)}function Yx(t,n){this._context=t,this._k=(1-n)/6}Yx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:Bx(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2,this._x1=t,this._y1=n;break;case 2:this._point=3;default:Bx(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Lx=function t(n){function e(t){return new Yx(t,n)}return e.tension=function(n){return t(+n)},e}(0);function jx(t,n){this._context=t,this._k=(1-n)/6}jx.prototype={areaStart:Dx,areaEnd:Dx,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:Bx(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Hx=function t(n){function e(t){return new jx(t,n)}return e.tension=function(n){return t(+n)},e}(0);function Xx(t,n){this._context=t,this._k=(1-n)/6}Xx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Bx(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Gx=function t(n){function e(t){return new Xx(t,n)}return e.tension=function(n){return t(+n)},e}(0);function Vx(t,n,e){var r=t._x1,i=t._y1,o=t._x2,a=t._y2;if(t._l01_a>Tm){var u=2*t._l01_2a+3*t._l01_a*t._l12_a+t._l12_2a,c=3*t._l01_a*(t._l01_a+t._l12_a);r=(r*u-t._x0*t._l12_2a+t._x2*t._l01_2a)/c,i=(i*u-t._y0*t._l12_2a+t._y2*t._l01_2a)/c}if(t._l23_a>Tm){var f=2*t._l23_2a+3*t._l23_a*t._l12_a+t._l12_2a,s=3*t._l23_a*(t._l23_a+t._l12_a);o=(o*f+t._x1*t._l23_2a-n*t._l12_2a)/s,a=(a*f+t._y1*t._l23_2a-e*t._l12_2a)/s}t._context.bezierCurveTo(r,i,o,a,t._x2,t._y2)}function Wx(t,n){this._context=t,this._alpha=n}Wx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3;default:Vx(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Zx=function t(n){function e(t){return n?new Wx(t,n):new Yx(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function Kx(t,n){this._context=t,this._alpha=n}Kx.prototype={areaStart:Dx,areaEnd:Dx,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:Vx(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var Qx=function t(n){function e(t){return n?new Kx(t,n):new jx(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function Jx(t,n){this._context=t,this._alpha=n}Jx.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,r=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+r*r,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:Vx(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}};var tw=function t(n){function e(t){return n?new Jx(t,n):new Xx(t,0)}return e.alpha=function(n){return t(+n)},e}(.5);function nw(t){this._context=t}function ew(t){return t<0?-1:1}function rw(t,n,e){var r=t._x1-t._x0,i=n-t._x1,o=(t._y1-t._y0)/(r||i<0&&-0),a=(e-t._y1)/(i||r<0&&-0),u=(o*i+a*r)/(r+i);return(ew(o)+ew(a))*Math.min(Math.abs(o),Math.abs(a),.5*Math.abs(u))||0}function iw(t,n){var e=t._x1-t._x0;return e?(3*(t._y1-t._y0)/e-n)/2:n}function ow(t,n,e){var r=t._x0,i=t._y0,o=t._x1,a=t._y1,u=(o-r)/3;t._context.bezierCurveTo(r+u,i+u*n,o-u,a-u*e,o,a)}function aw(t){this._context=t}function uw(t){this._context=new cw(t)}function cw(t){this._context=t}function fw(t){this._context=t}function sw(t){var n,e,r=t.length-1,i=new Array(r),o=new Array(r),a=new Array(r);for(i[0]=0,o[0]=2,a[0]=t[0]+2*t[1],n=1;n=0;--n)i[n]=(a[n]-i[n+1])/o[n];for(o[r-1]=(t[r]+i[r-1])/2,n=0;n1)for(var e,r,i,o=1,a=t[n[0]],u=a.length;o=0;)e[n]=n;return e}function pw(t,n){return t[n]}function gw(t){const n=[];return n.key=t,n}function yw(t){var n=t.map(vw);return dw(t).sort((function(t,e){return n[t]-n[e]}))}function vw(t){for(var n,e=-1,r=0,i=t.length,o=-1/0;++eo&&(o=n,r=e);return r}function _w(t){var n=t.map(bw);return dw(t).sort((function(t,e){return n[t]-n[e]}))}function bw(t){for(var n,e=0,r=-1,i=t.length;++r=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,n),this._context.lineTo(t,n);else{var e=this._x*(1-this._t)+t*this._t;this._context.lineTo(e,this._y),this._context.lineTo(e,n)}}this._x=t,this._y=n}};var mw=t=>()=>t;function xw(t,{sourceEvent:n,target:e,transform:r,dispatch:i}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:n,enumerable:!0,configurable:!0},target:{value:e,enumerable:!0,configurable:!0},transform:{value:r,enumerable:!0,configurable:!0},_:{value:i}})}function ww(t,n,e){this.k=t,this.x=n,this.y=e}ww.prototype={constructor:ww,scale:function(t){return 1===t?this:new ww(this.k*t,this.x,this.y)},translate:function(t,n){return 0===t&0===n?this:new ww(this.k,this.x+this.k*t,this.y+this.k*n)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var Mw=new ww(1,0,0);function Tw(t){for(;!t.__zoom;)if(!(t=t.parentNode))return Mw;return t.__zoom}function Aw(t){t.stopImmediatePropagation()}function Sw(t){t.preventDefault(),t.stopImmediatePropagation()}function Ew(t){return!(t.ctrlKey&&"wheel"!==t.type||t.button)}function Nw(){var t=this;return t instanceof SVGElement?(t=t.ownerSVGElement||t).hasAttribute("viewBox")?[[(t=t.viewBox.baseVal).x,t.y],[t.x+t.width,t.y+t.height]]:[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]:[[0,0],[t.clientWidth,t.clientHeight]]}function kw(){return this.__zoom||Mw}function Cw(t){return-t.deltaY*(1===t.deltaMode?.05:t.deltaMode?1:.002)*(t.ctrlKey?10:1)}function Pw(){return navigator.maxTouchPoints||"ontouchstart"in this}function zw(t,n,e){var r=t.invertX(n[0][0])-e[0][0],i=t.invertX(n[1][0])-e[1][0],o=t.invertY(n[0][1])-e[0][1],a=t.invertY(n[1][1])-e[1][1];return t.translate(i>r?(r+i)/2:Math.min(0,r)||Math.max(0,i),a>o?(o+a)/2:Math.min(0,o)||Math.max(0,a))}Tw.prototype=ww.prototype,t.Adder=T,t.Delaunay=Lu,t.FormatSpecifier=tf,t.InternMap=InternMap,t.InternSet=InternSet,t.Node=Qd,t.Path=Ua,t.Voronoi=qu,t.ZoomTransform=ww,t.active=function(t,n){var e,r,i=t.__transition;if(i)for(r in n=null==n?null:n+"",i)if((e=i[r]).state>qi&&e.name===n)return new po([[t]],Zo,n,+r);return null},t.arc=function(){var t=Cm,n=Pm,e=ym(0),r=null,i=zm,o=$m,a=Dm,u=null,c=km(f);function f(){var f,s,l=+t.apply(this,arguments),h=+n.apply(this,arguments),d=i.apply(this,arguments)-Sm,p=o.apply(this,arguments)-Sm,g=vm(p-d),y=p>d;if(u||(u=f=c()),hTm)if(g>Em-Tm)u.moveTo(h*bm(d),h*wm(d)),u.arc(0,0,h,d,p,!y),l>Tm&&(u.moveTo(l*bm(p),l*wm(p)),u.arc(0,0,l,p,d,y));else{var v,_,b=d,m=p,x=d,w=p,M=g,T=g,A=a.apply(this,arguments)/2,S=A>Tm&&(r?+r.apply(this,arguments):Mm(l*l+h*h)),E=xm(vm(h-l)/2,+e.apply(this,arguments)),N=E,k=E;if(S>Tm){var C=Nm(S/l*wm(A)),P=Nm(S/h*wm(A));(M-=2*C)>Tm?(x+=C*=y?1:-1,w-=C):(M=0,x=w=(d+p)/2),(T-=2*P)>Tm?(b+=P*=y?1:-1,m-=P):(T=0,b=m=(d+p)/2)}var z=h*bm(b),$=h*wm(b),D=l*bm(w),R=l*wm(w);if(E>Tm){var F,q=h*bm(m),U=h*wm(m),I=l*bm(x),O=l*wm(x);if(g1?0:t<-1?Am:Math.acos(t)}((B*L+Y*j)/(Mm(B*B+Y*Y)*Mm(L*L+j*j)))/2),X=Mm(F[0]*F[0]+F[1]*F[1]);N=xm(E,(l-X)/(H-1)),k=xm(E,(h-X)/(H+1))}else N=k=0}T>Tm?k>Tm?(v=Rm(I,O,z,$,h,k,y),_=Rm(q,U,D,R,h,k,y),u.moveTo(v.cx+v.x01,v.cy+v.y01),kTm&&M>Tm?N>Tm?(v=Rm(D,R,q,U,l,-N,y),_=Rm(z,$,I,O,l,-N,y),u.lineTo(v.cx+v.x01,v.cy+v.y01),N=0))throw new RangeError("invalid r");let e=t.length;if(!((e=Math.floor(e))>=0))throw new RangeError("invalid length");if(!e||!n)return t;const r=y(n),i=t.slice();return r(t,i,0,e,1),r(i,t,0,e,1),r(t,i,0,e,1),t},t.blur2=l,t.blurImage=h,t.brush=function(){return wa(la)},t.brushSelection=function(t){var n=t.__brush;return n?n.dim.output(n.selection):null},t.brushX=function(){return wa(fa)},t.brushY=function(){return wa(sa)},t.buffer=function(t,n){return fetch(t,n).then(_c)},t.chord=function(){return za(!1,!1)},t.chordDirected=function(){return za(!0,!1)},t.chordTranspose=function(){return za(!1,!0)},t.cluster=function(){var t=Ld,n=1,e=1,r=!1;function i(i){var o,a=0;i.eachAfter((function(n){var e=n.children;e?(n.x=function(t){return t.reduce(jd,0)/t.length}(e),n.y=function(t){return 1+t.reduce(Hd,0)}(e)):(n.x=o?a+=t(n,o):0,n.y=0,o=n)}));var u=function(t){for(var n;n=t.children;)t=n[0];return t}(i),c=function(t){for(var n;n=t.children;)t=n[n.length-1];return t}(i),f=u.x-t(u,c)/2,s=c.x+t(c,u)/2;return i.eachAfter(r?function(t){t.x=(t.x-i.x)*n,t.y=(i.y-t.y)*e}:function(t){t.x=(t.x-f)/(s-f)*n,t.y=(1-(i.y?t.y/i.y:1))*e})}return i.separation=function(n){return arguments.length?(t=n,i):t},i.size=function(t){return arguments.length?(r=!1,n=+t[0],e=+t[1],i):r?null:[n,e]},i.nodeSize=function(t){return arguments.length?(r=!0,n=+t[0],e=+t[1],i):r?[n,e]:null},i},t.color=ze,t.contourDensity=function(){var t=fu,n=su,e=lu,r=960,i=500,o=20,a=2,u=3*o,c=r+2*u>>a,f=i+2*u>>a,s=Qa(20);function h(r){var i=new Float32Array(c*f),s=Math.pow(2,-a),h=-1;for(const o of r){var d=(t(o,++h,r)+u)*s,p=(n(o,h,r)+u)*s,g=+e(o,h,r);if(g&&d>=0&&d=0&&pt*r)))(n).map(((t,n)=>(t.value=+e[n],p(t))))}function p(t){return t.coordinates.forEach(g),t}function g(t){t.forEach(y)}function y(t){t.forEach(v)}function v(t){t[0]=t[0]*Math.pow(2,a)-u,t[1]=t[1]*Math.pow(2,a)-u}function _(){return c=r+2*(u=3*o)>>a,f=i+2*u>>a,d}return d.contours=function(t){var n=h(t),e=iu().size([c,f]),r=Math.pow(2,2*a),i=t=>{t=+t;var i=p(e.contour(n,t*r));return i.value=t,i};return Object.defineProperty(i,"max",{get:()=>J(n)/r}),i},d.x=function(n){return arguments.length?(t="function"==typeof n?n:Qa(+n),d):t},d.y=function(t){return arguments.length?(n="function"==typeof t?t:Qa(+t),d):n},d.weight=function(t){return arguments.length?(e="function"==typeof t?t:Qa(+t),d):e},d.size=function(t){if(!arguments.length)return[r,i];var n=+t[0],e=+t[1];if(!(n>=0&&e>=0))throw new Error("invalid size");return r=n,i=e,_()},d.cellSize=function(t){if(!arguments.length)return 1<=1))throw new Error("invalid cell size");return a=Math.floor(Math.log(t)/Math.LN2),_()},d.thresholds=function(t){return arguments.length?(s="function"==typeof t?t:Array.isArray(t)?Qa(Za.call(t)):Qa(t),d):s},d.bandwidth=function(t){if(!arguments.length)return Math.sqrt(o*(o+1));if(!((t=+t)>=0))throw new Error("invalid bandwidth");return o=(Math.sqrt(4*t*t+1)-1)/2,_()},d},t.contours=iu,t.count=v,t.create=function(t){return Zn(Yt(t).call(document.documentElement))},t.creator=Yt,t.cross=function(...t){const n="function"==typeof t[t.length-1]&&function(t){return n=>t(...n)}(t.pop()),e=(t=t.map(m)).map(_),r=t.length-1,i=new Array(r+1).fill(0),o=[];if(r<0||e.some(b))return o;for(;;){o.push(i.map(((n,e)=>t[e][n])));let a=r;for(;++i[a]===e[a];){if(0===a)return n?o.map(n):o;i[a--]=0}}},t.csv=wc,t.csvFormat=rc,t.csvFormatBody=ic,t.csvFormatRow=ac,t.csvFormatRows=oc,t.csvFormatValue=uc,t.csvParse=nc,t.csvParseRows=ec,t.cubehelix=Tr,t.cumsum=function(t,n){var e=0,r=0;return Float64Array.from(t,void 0===n?t=>e+=+t||0:i=>e+=+n(i,r++,t)||0)},t.curveBasis=function(t){return new Fx(t)},t.curveBasisClosed=function(t){return new qx(t)},t.curveBasisOpen=function(t){return new Ux(t)},t.curveBumpX=nx,t.curveBumpY=ex,t.curveBundle=Ox,t.curveCardinal=Lx,t.curveCardinalClosed=Hx,t.curveCardinalOpen=Gx,t.curveCatmullRom=Zx,t.curveCatmullRomClosed=Qx,t.curveCatmullRomOpen=tw,t.curveLinear=Im,t.curveLinearClosed=function(t){return new nw(t)},t.curveMonotoneX=function(t){return new aw(t)},t.curveMonotoneY=function(t){return new uw(t)},t.curveNatural=function(t){return new fw(t)},t.curveStep=function(t){return new lw(t,.5)},t.curveStepAfter=function(t){return new lw(t,1)},t.curveStepBefore=function(t){return new lw(t,0)},t.descending=e,t.deviation=w,t.difference=function(t,...n){t=new InternSet(t);for(const e of n)for(const n of e)t.delete(n);return t},t.disjoint=function(t,n){const e=n[Symbol.iterator](),r=new InternSet;for(const n of t){if(r.has(n))return!1;let t,i;for(;({value:t,done:i}=e.next())&&!i;){if(Object.is(n,t))return!1;r.add(t)}}return!0},t.dispatch=$t,t.drag=function(){var t,n,e,r,i=se,o=le,a=he,u=de,c={},f=$t("start","drag","end"),s=0,l=0;function h(t){t.on("mousedown.drag",d).filter(u).on("touchstart.drag",y).on("touchmove.drag",v,ee).on("touchend.drag touchcancel.drag",_).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function d(a,u){if(!r&&i.call(this,a,u)){var c=b(this,o.call(this,a,u),a,u,"mouse");c&&(Zn(a.view).on("mousemove.drag",p,re).on("mouseup.drag",g,re),ae(a.view),ie(a),e=!1,t=a.clientX,n=a.clientY,c("start",a))}}function p(r){if(oe(r),!e){var i=r.clientX-t,o=r.clientY-n;e=i*i+o*o>l}c.mouse("drag",r)}function g(t){Zn(t.view).on("mousemove.drag mouseup.drag",null),ue(t.view,e),oe(t),c.mouse("end",t)}function y(t,n){if(i.call(this,t,n)){var e,r,a=t.changedTouches,u=o.call(this,t,n),c=a.length;for(e=0;e+t,t.easePoly=wo,t.easePolyIn=mo,t.easePolyInOut=wo,t.easePolyOut=xo,t.easeQuad=_o,t.easeQuadIn=function(t){return t*t},t.easeQuadInOut=_o,t.easeQuadOut=function(t){return t*(2-t)},t.easeSin=Ao,t.easeSinIn=function(t){return 1==+t?1:1-Math.cos(t*To)},t.easeSinInOut=Ao,t.easeSinOut=function(t){return Math.sin(t*To)},t.every=function(t,n){if("function"!=typeof n)throw new TypeError("test is not a function");let e=-1;for(const r of t)if(!n(r,++e,t))return!1;return!0},t.extent=M,t.fcumsum=function(t,n){const e=new T;let r=-1;return Float64Array.from(t,void 0===n?t=>e.add(+t||0):i=>e.add(+n(i,++r,t)||0))},t.filter=function(t,n){if("function"!=typeof n)throw new TypeError("test is not a function");const e=[];let r=-1;for(const i of t)n(i,++r,t)&&e.push(i);return e},t.flatGroup=function(t,...n){return z(P(t,...n),n)},t.flatRollup=function(t,n,...e){return z(D(t,n,...e),e)},t.forceCenter=function(t,n){var e,r=1;function i(){var i,o,a=e.length,u=0,c=0;for(i=0;if+p||os+p||ac.index){var g=f-u.x-u.vx,y=s-u.y-u.vy,v=g*g+y*y;vt.r&&(t.r=t[n].r)}function c(){if(n){var r,i,o=n.length;for(e=new Array(o),r=0;r[u(t,n,r),t])));for(a=0,i=new Array(f);a=u)){(t.data!==n||t.next)&&(0===l&&(p+=(l=Uc(e))*l),0===h&&(p+=(h=Uc(e))*h),p(t=(Lc*t+jc)%Hc)/Hc}();function l(){h(),f.call("tick",n),e1?(null==e?u.delete(t):u.set(t,p(e)),n):u.get(t)},find:function(n,e,r){var i,o,a,u,c,f=0,s=t.length;for(null==r?r=1/0:r*=r,f=0;f1?(f.on(t,e),n):f.on(t)}}},t.forceX=function(t){var n,e,r,i=qc(.1);function o(t){for(var i,o=0,a=n.length;o=.12&&i<.234&&r>=-.425&&r<-.214?u:i>=.166&&i<.234&&r>=-.214&&r<-.115?c:a).invert(t)},s.stream=function(e){return t&&n===e?t:(r=[a.stream(n=e),u.stream(e),c.stream(e)],i=r.length,t={point:function(t,n){for(var e=-1;++ejs(r[0],r[1])&&(r[1]=i[1]),js(i[0],r[1])>js(r[0],r[1])&&(r[0]=i[0])):o.push(r=i);for(a=-1/0,n=0,r=o[e=o.length-1];n<=e;r=i,++n)i=o[n],(u=js(r[1],i[0]))>a&&(a=u,Wf=i[0],Kf=r[1])}return is=os=null,Wf===1/0||Zf===1/0?[[NaN,NaN],[NaN,NaN]]:[[Wf,Zf],[Kf,Qf]]},t.geoCentroid=function(t){ms=xs=ws=Ms=Ts=As=Ss=Es=0,Ns=new T,ks=new T,Cs=new T,Lf(t,Gs);var n=+Ns,e=+ks,r=+Cs,i=Ef(n,e,r);return i=0))throw new RangeError(`invalid digits: ${t}`);i=n}return null===n&&(r=new ed(i)),a},a.projection(t).digits(i).context(n)},t.geoProjection=yd,t.geoProjectionMutator=vd,t.geoRotation=ll,t.geoStereographic=function(){return yd(Bd).scale(250).clipAngle(142)},t.geoStereographicRaw=Bd,t.geoStream=Lf,t.geoTransform=function(t){return{stream:id(t)}},t.geoTransverseMercator=function(){var t=Ed(Yd),n=t.center,e=t.rotate;return t.center=function(t){return arguments.length?n([-t[1],t[0]]):[(t=n())[1],-t[0]]},t.rotate=function(t){return arguments.length?e([t[0],t[1],t.length>2?t[2]+90:90]):[(t=e())[0],t[1],t[2]-90]},e([0,0,90]).scale(159.155)},t.geoTransverseMercatorRaw=Yd,t.gray=function(t,n){return new ur(t,0,0,null==n?1:n)},t.greatest=ot,t.greatestIndex=function(t,e=n){if(1===e.length)return tt(t,e);let r,i=-1,o=-1;for(const n of t)++o,(i<0?0===e(n,n):e(n,r)>0)&&(r=n,i=o);return i},t.group=C,t.groupSort=function(t,e,r){return(2!==e.length?U($(t,e,r),(([t,e],[r,i])=>n(e,i)||n(t,r))):U(C(t,r),(([t,r],[i,o])=>e(r,o)||n(t,i)))).map((([t])=>t))},t.groups=P,t.hcl=dr,t.hierarchy=Gd,t.histogram=Q,t.hsl=He,t.html=Ec,t.image=function(t,n){return new Promise((function(e,r){var i=new Image;for(var o in n)i[o]=n[o];i.onerror=r,i.onload=function(){e(i)},i.src=t}))},t.index=function(t,...n){return F(t,k,R,n)},t.indexes=function(t,...n){return F(t,Array.from,R,n)},t.interpolate=Gr,t.interpolateArray=function(t,n){return(Ir(n)?Ur:Or)(t,n)},t.interpolateBasis=Er,t.interpolateBasisClosed=Nr,t.interpolateBlues=Gb,t.interpolateBrBG=ob,t.interpolateBuGn=Mb,t.interpolateBuPu=Ab,t.interpolateCividis=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(-4.54-t*(35.34-t*(2381.73-t*(6402.7-t*(7024.72-2710.57*t)))))))+", "+Math.max(0,Math.min(255,Math.round(32.49+t*(170.73+t*(52.82-t*(131.46-t*(176.58-67.37*t)))))))+", "+Math.max(0,Math.min(255,Math.round(81.24+t*(442.36-t*(2482.43-t*(6167.24-t*(6614.94-2475.67*t)))))))+")"},t.interpolateCool=am,t.interpolateCubehelix=li,t.interpolateCubehelixDefault=im,t.interpolateCubehelixLong=hi,t.interpolateDate=Br,t.interpolateDiscrete=function(t){var n=t.length;return function(e){return t[Math.max(0,Math.min(n-1,Math.floor(e*n)))]}},t.interpolateGnBu=Eb,t.interpolateGreens=Wb,t.interpolateGreys=Kb,t.interpolateHcl=ci,t.interpolateHclLong=fi,t.interpolateHsl=oi,t.interpolateHslLong=ai,t.interpolateHue=function(t,n){var e=Pr(+t,+n);return function(t){var n=e(t);return n-360*Math.floor(n/360)}},t.interpolateInferno=pm,t.interpolateLab=function(t,n){var e=$r((t=ar(t)).l,(n=ar(n)).l),r=$r(t.a,n.a),i=$r(t.b,n.b),o=$r(t.opacity,n.opacity);return function(n){return t.l=e(n),t.a=r(n),t.b=i(n),t.opacity=o(n),t+""}},t.interpolateMagma=dm,t.interpolateNumber=Yr,t.interpolateNumberArray=Ur,t.interpolateObject=Lr,t.interpolateOrRd=kb,t.interpolateOranges=rm,t.interpolatePRGn=ub,t.interpolatePiYG=fb,t.interpolatePlasma=gm,t.interpolatePuBu=$b,t.interpolatePuBuGn=Pb,t.interpolatePuOr=lb,t.interpolatePuRd=Rb,t.interpolatePurples=Jb,t.interpolateRainbow=function(t){(t<0||t>1)&&(t-=Math.floor(t));var n=Math.abs(t-.5);return um.h=360*t-100,um.s=1.5-1.5*n,um.l=.8-.9*n,um+""},t.interpolateRdBu=db,t.interpolateRdGy=gb,t.interpolateRdPu=qb,t.interpolateRdYlBu=vb,t.interpolateRdYlGn=bb,t.interpolateReds=nm,t.interpolateRgb=Dr,t.interpolateRgbBasis=Fr,t.interpolateRgbBasisClosed=qr,t.interpolateRound=Vr,t.interpolateSinebow=function(t){var n;return t=(.5-t)*Math.PI,cm.r=255*(n=Math.sin(t))*n,cm.g=255*(n=Math.sin(t+fm))*n,cm.b=255*(n=Math.sin(t+sm))*n,cm+""},t.interpolateSpectral=xb,t.interpolateString=Xr,t.interpolateTransformCss=ti,t.interpolateTransformSvg=ni,t.interpolateTurbo=function(t){return t=Math.max(0,Math.min(1,t)),"rgb("+Math.max(0,Math.min(255,Math.round(34.61+t*(1172.33-t*(10793.56-t*(33300.12-t*(38394.49-14825.05*t)))))))+", "+Math.max(0,Math.min(255,Math.round(23.31+t*(557.33+t*(1225.33-t*(3574.96-t*(1073.77+707.56*t)))))))+", "+Math.max(0,Math.min(255,Math.round(27.2+t*(3211.1-t*(15327.97-t*(27814-t*(22569.18-6838.66*t)))))))+")"},t.interpolateViridis=hm,t.interpolateWarm=om,t.interpolateYlGn=Bb,t.interpolateYlGnBu=Ib,t.interpolateYlOrBr=Lb,t.interpolateYlOrRd=Hb,t.interpolateZoom=ri,t.interrupt=Gi,t.intersection=function(t,...n){t=new InternSet(t),n=n.map(vt);t:for(const e of t)for(const r of n)if(!r.has(e)){t.delete(e);continue t}return t},t.interval=function(t,n,e){var r=new Ei,i=n;return null==n?(r.restart(t,n,e),r):(r._restart=r.restart,r.restart=function(t,n,e){n=+n,e=null==e?Ai():+e,r._restart((function o(a){a+=i,r._restart(o,i+=n,e),t(a)}),n,e)},r.restart(t,n,e),r)},t.isoFormat=D_,t.isoParse=F_,t.json=function(t,n){return fetch(t,n).then(Tc)},t.lab=ar,t.lch=function(t,n,e,r){return 1===arguments.length?hr(t):new pr(e,n,t,null==r?1:r)},t.least=function(t,e=n){let r,i=!1;if(1===e.length){let o;for(const a of t){const t=e(a);(i?n(t,o)<0:0===n(t,t))&&(r=a,o=t,i=!0)}}else for(const n of t)(i?e(n,r)<0:0===e(n,n))&&(r=n,i=!0);return r},t.leastIndex=ht,t.line=Ym,t.lineRadial=Zm,t.link=ax,t.linkHorizontal=function(){return ax(nx)},t.linkRadial=function(){const t=ax(rx);return t.angle=t.x,delete t.x,t.radius=t.y,delete t.y,t},t.linkVertical=function(){return ax(ex)},t.local=Qn,t.map=function(t,n){if("function"!=typeof t[Symbol.iterator])throw new TypeError("values is not iterable");if("function"!=typeof n)throw new TypeError("mapper is not a function");return Array.from(t,((e,r)=>n(e,r,t)))},t.matcher=Vt,t.max=J,t.maxIndex=tt,t.mean=function(t,n){let e=0,r=0;if(void 0===n)for(let n of t)null!=n&&(n=+n)>=n&&(++e,r+=n);else{let i=-1;for(let o of t)null!=(o=n(o,++i,t))&&(o=+o)>=o&&(++e,r+=o)}if(e)return r/e},t.median=function(t,n){return at(t,.5,n)},t.medianIndex=function(t,n){return ct(t,.5,n)},t.merge=ft,t.min=nt,t.minIndex=et,t.mode=function(t,n){const e=new InternMap;if(void 0===n)for(let n of t)null!=n&&n>=n&&e.set(n,(e.get(n)||0)+1);else{let r=-1;for(let i of t)null!=(i=n(i,++r,t))&&i>=i&&e.set(i,(e.get(i)||0)+1)}let r,i=0;for(const[t,n]of e)n>i&&(i=n,r=t);return r},t.namespace=It,t.namespaces=Ut,t.nice=Z,t.now=Ai,t.pack=function(){var t=null,n=1,e=1,r=np;function i(i){const o=ap();return i.x=n/2,i.y=e/2,t?i.eachBefore(xp(t)).eachAfter(wp(r,.5,o)).eachBefore(Mp(1)):i.eachBefore(xp(mp)).eachAfter(wp(np,1,o)).eachAfter(wp(r,i.r/Math.min(n,e),o)).eachBefore(Mp(Math.min(n,e)/(2*i.r))),i}return i.radius=function(n){return arguments.length?(t=Jd(n),i):t},i.size=function(t){return arguments.length?(n=+t[0],e=+t[1],i):[n,e]},i.padding=function(t){return arguments.length?(r="function"==typeof t?t:ep(+t),i):r},i},t.packEnclose=function(t){return up(t,ap())},t.packSiblings=function(t){return bp(t,ap()),t},t.pairs=function(t,n=st){const e=[];let r,i=!1;for(const o of t)i&&e.push(n(r,o)),r=o,i=!0;return e},t.partition=function(){var t=1,n=1,e=0,r=!1;function i(i){var o=i.height+1;return i.x0=i.y0=e,i.x1=t,i.y1=n/o,i.eachBefore(function(t,n){return function(r){r.children&&Ap(r,r.x0,t*(r.depth+1)/n,r.x1,t*(r.depth+2)/n);var i=r.x0,o=r.y0,a=r.x1-e,u=r.y1-e;a0&&(d+=l);for(null!=n?p.sort((function(t,e){return n(g[t],g[e])})):null!=e&&p.sort((function(t,n){return e(a[t],a[n])})),u=0,f=d?(v-h*b)/d:0;u0?l*f:0)+b,g[c]={data:a[c],index:u,value:l,startAngle:y,endAngle:s,padAngle:_};return g}return a.value=function(n){return arguments.length?(t="function"==typeof n?n:ym(+n),a):t},a.sortValues=function(t){return arguments.length?(n=t,e=null,a):n},a.sort=function(t){return arguments.length?(e=t,n=null,a):e},a.startAngle=function(t){return arguments.length?(r="function"==typeof t?t:ym(+t),a):r},a.endAngle=function(t){return arguments.length?(i="function"==typeof t?t:ym(+t),a):i},a.padAngle=function(t){return arguments.length?(o="function"==typeof t?t:ym(+t),a):o},a},t.piecewise=di,t.pointRadial=Qm,t.pointer=ne,t.pointers=function(t,n){return t.target&&(t=te(t),void 0===n&&(n=t.currentTarget),t=t.touches||[t]),Array.from(t,(t=>ne(t,n)))},t.polygonArea=function(t){for(var n,e=-1,r=t.length,i=t[r-1],o=0;++eu!=f>u&&a<(c-e)*(u-r)/(f-r)+e&&(s=!s),c=e,f=r;return s},t.polygonHull=function(t){if((e=t.length)<3)return null;var n,e,r=new Array(e),i=new Array(e);for(n=0;n=0;--n)f.push(t[r[o[n]][2]]);for(n=+u;n(n=1664525*n+1013904223|0,lg*(n>>>0))},t.randomLogNormal=Kp,t.randomLogistic=fg,t.randomNormal=Zp,t.randomPareto=ng,t.randomPoisson=sg,t.randomUniform=Vp,t.randomWeibull=ug,t.range=lt,t.rank=function(t,e=n){if("function"!=typeof t[Symbol.iterator])throw new TypeError("values is not iterable");let r=Array.from(t);const i=new Float64Array(r.length);2!==e.length&&(r=r.map(e),e=n);const o=(t,n)=>e(r[t],r[n]);let a,u;return(t=Uint32Array.from(r,((t,n)=>n))).sort(e===n?(t,n)=>O(r[t],r[n]):I(o)),t.forEach(((t,n)=>{const e=o(t,void 0===a?t:a);e>=0?((void 0===a||e>0)&&(a=t,u=n),i[t]=u):i[t]=NaN})),i},t.reduce=function(t,n,e){if("function"!=typeof n)throw new TypeError("reducer is not a function");const r=t[Symbol.iterator]();let i,o,a=-1;if(arguments.length<3){if(({done:i,value:e}=r.next()),i)return;++a}for(;({done:i,value:o}=r.next()),!i;)e=n(e,o,++a,t);return e},t.reverse=function(t){if("function"!=typeof t[Symbol.iterator])throw new TypeError("values is not iterable");return Array.from(t).reverse()},t.rgb=Fe,t.ribbon=function(){return Wa()},t.ribbonArrow=function(){return Wa(Va)},t.rollup=$,t.rollups=D,t.scaleBand=yg,t.scaleDiverging=function t(){var n=Ng(L_()(mg));return n.copy=function(){return B_(n,t())},dg.apply(n,arguments)},t.scaleDivergingLog=function t(){var n=Fg(L_()).domain([.1,1,10]);return n.copy=function(){return B_(n,t()).base(n.base())},dg.apply(n,arguments)},t.scaleDivergingPow=j_,t.scaleDivergingSqrt=function(){return j_.apply(null,arguments).exponent(.5)},t.scaleDivergingSymlog=function t(){var n=Ig(L_());return n.copy=function(){return B_(n,t()).constant(n.constant())},dg.apply(n,arguments)},t.scaleIdentity=function t(n){var e;function r(t){return null==t||isNaN(t=+t)?e:t}return r.invert=r,r.domain=r.range=function(t){return arguments.length?(n=Array.from(t,_g),r):n.slice()},r.unknown=function(t){return arguments.length?(e=t,r):e},r.copy=function(){return t(n).unknown(e)},n=arguments.length?Array.from(n,_g):[0,1],Ng(r)},t.scaleImplicit=pg,t.scaleLinear=function t(){var n=Sg();return n.copy=function(){return Tg(n,t())},hg.apply(n,arguments),Ng(n)},t.scaleLog=function t(){const n=Fg(Ag()).domain([1,10]);return n.copy=()=>Tg(n,t()).base(n.base()),hg.apply(n,arguments),n},t.scaleOrdinal=gg,t.scalePoint=function(){return vg(yg.apply(null,arguments).paddingInner(1))},t.scalePow=jg,t.scaleQuantile=function t(){var e,r=[],i=[],o=[];function a(){var t=0,n=Math.max(1,i.length);for(o=new Array(n-1);++t0?o[n-1]:r[0],n=i?[o[i-1],r]:[o[n-1],o[n]]},u.unknown=function(t){return arguments.length?(n=t,u):u},u.thresholds=function(){return o.slice()},u.copy=function(){return t().domain([e,r]).range(a).unknown(n)},hg.apply(Ng(u),arguments)},t.scaleRadial=function t(){var n,e=Sg(),r=[0,1],i=!1;function o(t){var r=function(t){return Math.sign(t)*Math.sqrt(Math.abs(t))}(e(t));return isNaN(r)?n:i?Math.round(r):r}return o.invert=function(t){return e.invert(Hg(t))},o.domain=function(t){return arguments.length?(e.domain(t),o):e.domain()},o.range=function(t){return arguments.length?(e.range((r=Array.from(t,_g)).map(Hg)),o):r.slice()},o.rangeRound=function(t){return o.range(t).round(!0)},o.round=function(t){return arguments.length?(i=!!t,o):i},o.clamp=function(t){return arguments.length?(e.clamp(t),o):e.clamp()},o.unknown=function(t){return arguments.length?(n=t,o):n},o.copy=function(){return t(e.domain(),r).round(i).clamp(e.clamp()).unknown(n)},hg.apply(o,arguments),Ng(o)},t.scaleSequential=function t(){var n=Ng(O_()(mg));return n.copy=function(){return B_(n,t())},dg.apply(n,arguments)},t.scaleSequentialLog=function t(){var n=Fg(O_()).domain([1,10]);return n.copy=function(){return B_(n,t()).base(n.base())},dg.apply(n,arguments)},t.scaleSequentialPow=Y_,t.scaleSequentialQuantile=function t(){var e=[],r=mg;function i(t){if(null!=t&&!isNaN(t=+t))return r((s(e,t,1)-1)/(e.length-1))}return i.domain=function(t){if(!arguments.length)return e.slice();e=[];for(let n of t)null==n||isNaN(n=+n)||e.push(n);return e.sort(n),i},i.interpolator=function(t){return arguments.length?(r=t,i):r},i.range=function(){return e.map(((t,n)=>r(n/(e.length-1))))},i.quantiles=function(t){return Array.from({length:t+1},((n,r)=>at(e,r/t)))},i.copy=function(){return t(r).domain(e)},dg.apply(i,arguments)},t.scaleSequentialSqrt=function(){return Y_.apply(null,arguments).exponent(.5)},t.scaleSequentialSymlog=function t(){var n=Ig(O_());return n.copy=function(){return B_(n,t()).constant(n.constant())},dg.apply(n,arguments)},t.scaleSqrt=function(){return jg.apply(null,arguments).exponent(.5)},t.scaleSymlog=function t(){var n=Ig(Ag());return n.copy=function(){return Tg(n,t()).constant(n.constant())},hg.apply(n,arguments)},t.scaleThreshold=function t(){var n,e=[.5],r=[0,1],i=1;function o(t){return null!=t&&t<=t?r[s(e,t,0,i)]:n}return o.domain=function(t){return arguments.length?(e=Array.from(t),i=Math.min(e.length,r.length-1),o):e.slice()},o.range=function(t){return arguments.length?(r=Array.from(t),i=Math.min(e.length,r.length-1),o):r.slice()},o.invertExtent=function(t){var n=r.indexOf(t);return[e[n-1],e[n]]},o.unknown=function(t){return arguments.length?(n=t,o):n},o.copy=function(){return t().domain(e).range(r).unknown(n)},hg.apply(o,arguments)},t.scaleTime=function(){return hg.apply(I_(uv,cv,tv,Zy,xy,py,sy,ay,iy,t.timeFormat).domain([new Date(2e3,0,1),new Date(2e3,0,2)]),arguments)},t.scaleUtc=function(){return hg.apply(I_(ov,av,ev,Qy,Fy,yy,hy,cy,iy,t.utcFormat).domain([Date.UTC(2e3,0,1),Date.UTC(2e3,0,2)]),arguments)},t.scan=function(t,n){const e=ht(t,n);return e<0?void 0:e},t.schemeAccent=G_,t.schemeBlues=Xb,t.schemeBrBG=ib,t.schemeBuGn=wb,t.schemeBuPu=Tb,t.schemeCategory10=X_,t.schemeDark2=V_,t.schemeGnBu=Sb,t.schemeGreens=Vb,t.schemeGreys=Zb,t.schemeObservable10=W_,t.schemeOrRd=Nb,t.schemeOranges=em,t.schemePRGn=ab,t.schemePaired=Z_,t.schemePastel1=K_,t.schemePastel2=Q_,t.schemePiYG=cb,t.schemePuBu=zb,t.schemePuBuGn=Cb,t.schemePuOr=sb,t.schemePuRd=Db,t.schemePurples=Qb,t.schemeRdBu=hb,t.schemeRdGy=pb,t.schemeRdPu=Fb,t.schemeRdYlBu=yb,t.schemeRdYlGn=_b,t.schemeReds=tm,t.schemeSet1=J_,t.schemeSet2=tb,t.schemeSet3=nb,t.schemeSpectral=mb,t.schemeTableau10=eb,t.schemeYlGn=Ob,t.schemeYlGnBu=Ub,t.schemeYlOrBr=Yb,t.schemeYlOrRd=jb,t.select=Zn,t.selectAll=function(t){return"string"==typeof t?new Vn([document.querySelectorAll(t)],[document.documentElement]):new Vn([Ht(t)],Gn)},t.selection=Wn,t.selector=jt,t.selectorAll=Gt,t.shuffle=dt,t.shuffler=pt,t.some=function(t,n){if("function"!=typeof n)throw new TypeError("test is not a function");let e=-1;for(const r of t)if(n(r,++e,t))return!0;return!1},t.sort=U,t.stack=function(){var t=ym([]),n=dw,e=hw,r=pw;function i(i){var o,a,u=Array.from(t.apply(this,arguments),gw),c=u.length,f=-1;for(const t of i)for(o=0,++f;o0)for(var e,r,i,o,a,u,c=0,f=t[n[0]].length;c0?(r[0]=o,r[1]=o+=i):i<0?(r[1]=a,r[0]=a+=i):(r[0]=0,r[1]=i)},t.stackOffsetExpand=function(t,n){if((r=t.length)>0){for(var e,r,i,o=0,a=t[0].length;o0){for(var e,r=0,i=t[n[0]],o=i.length;r0&&(r=(e=t[n[0]]).length)>0){for(var e,r,i,o=0,a=1;afunction(t){t=`${t}`;let n=t.length;zp(t,n-1)&&!zp(t,n-2)&&(t=t.slice(0,-1));return"/"===t[0]?t:`/${t}`}(t(n,e,r)))),e=n.map(Pp),i=new Set(n).add("");for(const t of e)i.has(t)||(i.add(t),n.push(t),e.push(Pp(t)),h.push(Np));d=(t,e)=>n[e],p=(t,n)=>e[n]}for(a=0,i=h.length;a=0&&(f=h[t]).data===Np;--t)f.data=null}if(u.parent=Sp,u.eachBefore((function(t){t.depth=t.parent.depth+1,--i})).eachBefore(Kd),u.parent=null,i>0)throw new Error("cycle");return u}return r.id=function(t){return arguments.length?(n=Jd(t),r):n},r.parentId=function(t){return arguments.length?(e=Jd(t),r):e},r.path=function(n){return arguments.length?(t=Jd(n),r):t},r},t.style=_n,t.subset=function(t,n){return _t(n,t)},t.sum=function(t,n){let e=0;if(void 0===n)for(let n of t)(n=+n)&&(e+=n);else{let r=-1;for(let i of t)(i=+n(i,++r,t))&&(e+=i)}return e},t.superset=_t,t.svg=Nc,t.symbol=function(t,n){let e=null,r=km(i);function i(){let i;if(e||(e=i=r()),t.apply(this,arguments).draw(e,+n.apply(this,arguments)),i)return e=null,i+""||null}return t="function"==typeof t?t:ym(t||fx),n="function"==typeof n?n:ym(void 0===n?64:+n),i.type=function(n){return arguments.length?(t="function"==typeof n?n:ym(n),i):t},i.size=function(t){return arguments.length?(n="function"==typeof t?t:ym(+t),i):n},i.context=function(t){return arguments.length?(e=null==t?null:t,i):e},i},t.symbolAsterisk=cx,t.symbolCircle=fx,t.symbolCross=sx,t.symbolDiamond=dx,t.symbolDiamond2=px,t.symbolPlus=gx,t.symbolSquare=yx,t.symbolSquare2=vx,t.symbolStar=xx,t.symbolTimes=Px,t.symbolTriangle=Mx,t.symbolTriangle2=Ax,t.symbolWye=Cx,t.symbolX=Px,t.symbols=zx,t.symbolsFill=zx,t.symbolsStroke=$x,t.text=mc,t.thresholdFreedmanDiaconis=function(t,n,e){const r=v(t),i=at(t,.75)-at(t,.25);return r&&i?Math.ceil((e-n)/(2*i*Math.pow(r,-1/3))):1},t.thresholdScott=function(t,n,e){const r=v(t),i=w(t);return r&&i?Math.ceil((e-n)*Math.cbrt(r)/(3.49*i)):1},t.thresholdSturges=K,t.tickFormat=Eg,t.tickIncrement=V,t.tickStep=W,t.ticks=G,t.timeDay=py,t.timeDays=gy,t.timeFormatDefaultLocale=P_,t.timeFormatLocale=hv,t.timeFriday=Sy,t.timeFridays=$y,t.timeHour=sy,t.timeHours=ly,t.timeInterval=Vg,t.timeMillisecond=Wg,t.timeMilliseconds=Zg,t.timeMinute=ay,t.timeMinutes=uy,t.timeMonday=wy,t.timeMondays=ky,t.timeMonth=Zy,t.timeMonths=Ky,t.timeSaturday=Ey,t.timeSaturdays=Dy,t.timeSecond=iy,t.timeSeconds=oy,t.timeSunday=xy,t.timeSundays=Ny,t.timeThursday=Ay,t.timeThursdays=zy,t.timeTickInterval=cv,t.timeTicks=uv,t.timeTuesday=My,t.timeTuesdays=Cy,t.timeWednesday=Ty,t.timeWednesdays=Py,t.timeWeek=xy,t.timeWeeks=Ny,t.timeYear=tv,t.timeYears=nv,t.timeout=$i,t.timer=Ni,t.timerFlush=ki,t.transition=go,t.transpose=gt,t.tree=function(){var t=$p,n=1,e=1,r=null;function i(i){var c=function(t){for(var n,e,r,i,o,a=new Up(t,0),u=[a];n=u.pop();)if(r=n._.children)for(n.children=new Array(o=r.length),i=o-1;i>=0;--i)u.push(e=n.children[i]=new Up(r[i],i)),e.parent=n;return(a.parent=new Up(null,0)).children=[a],a}(i);if(c.eachAfter(o),c.parent.m=-c.z,c.eachBefore(a),r)i.eachBefore(u);else{var f=i,s=i,l=i;i.eachBefore((function(t){t.xs.x&&(s=t),t.depth>l.depth&&(l=t)}));var h=f===s?1:t(f,s)/2,d=h-f.x,p=n/(s.x+h+d),g=e/(l.depth||1);i.eachBefore((function(t){t.x=(t.x+d)*p,t.y=t.depth*g}))}return i}function o(n){var e=n.children,r=n.parent.children,i=n.i?r[n.i-1]:null;if(e){!function(t){for(var n,e=0,r=0,i=t.children,o=i.length;--o>=0;)(n=i[o]).z+=e,n.m+=e,e+=n.s+(r+=n.c)}(n);var o=(e[0].z+e[e.length-1].z)/2;i?(n.z=i.z+t(n._,i._),n.m=n.z-o):n.z=o}else i&&(n.z=i.z+t(n._,i._));n.parent.A=function(n,e,r){if(e){for(var i,o=n,a=n,u=e,c=o.parent.children[0],f=o.m,s=a.m,l=u.m,h=c.m;u=Rp(u),o=Dp(o),u&&o;)c=Dp(c),(a=Rp(a)).a=n,(i=u.z+l-o.z-f+t(u._,o._))>0&&(Fp(qp(u,n,r),n,i),f+=i,s+=i),l+=u.m,f+=o.m,h+=c.m,s+=a.m;u&&!Rp(a)&&(a.t=u,a.m+=l-s),o&&!Dp(c)&&(c.t=o,c.m+=f-h,r=n)}return r}(n,i,n.parent.A||r[0])}function a(t){t._.x=t.z+t.parent.m,t.m+=t.parent.m}function u(t){t.x*=n,t.y=t.depth*e}return i.separation=function(n){return arguments.length?(t=n,i):t},i.size=function(t){return arguments.length?(r=!1,n=+t[0],e=+t[1],i):r?null:[n,e]},i.nodeSize=function(t){return arguments.length?(r=!0,n=+t[0],e=+t[1],i):r?[n,e]:null},i},t.treemap=function(){var t=Yp,n=!1,e=1,r=1,i=[0],o=np,a=np,u=np,c=np,f=np;function s(t){return t.x0=t.y0=0,t.x1=e,t.y1=r,t.eachBefore(l),i=[0],n&&t.eachBefore(Tp),t}function l(n){var e=i[n.depth],r=n.x0+e,s=n.y0+e,l=n.x1-e,h=n.y1-e;l=e-1){var s=u[n];return s.x0=i,s.y0=o,s.x1=a,void(s.y1=c)}var l=f[n],h=r/2+l,d=n+1,p=e-1;for(;d>>1;f[g]c-o){var _=r?(i*v+a*y)/r:a;t(n,d,y,i,o,_,c),t(d,e,v,_,o,a,c)}else{var b=r?(o*v+c*y)/r:c;t(n,d,y,i,o,a,b),t(d,e,v,i,b,a,c)}}(0,c,t.value,n,e,r,i)},t.treemapDice=Ap,t.treemapResquarify=Lp,t.treemapSlice=Ip,t.treemapSliceDice=function(t,n,e,r,i){(1&t.depth?Ip:Ap)(t,n,e,r,i)},t.treemapSquarify=Yp,t.tsv=Mc,t.tsvFormat=lc,t.tsvFormatBody=hc,t.tsvFormatRow=pc,t.tsvFormatRows=dc,t.tsvFormatValue=gc,t.tsvParse=fc,t.tsvParseRows=sc,t.union=function(...t){const n=new InternSet;for(const e of t)for(const t of e)n.add(t);return n},t.unixDay=_y,t.unixDays=by,t.utcDay=yy,t.utcDays=vy,t.utcFriday=By,t.utcFridays=Vy,t.utcHour=hy,t.utcHours=dy,t.utcMillisecond=Wg,t.utcMilliseconds=Zg,t.utcMinute=cy,t.utcMinutes=fy,t.utcMonday=qy,t.utcMondays=jy,t.utcMonth=Qy,t.utcMonths=Jy,t.utcSaturday=Yy,t.utcSaturdays=Wy,t.utcSecond=iy,t.utcSeconds=oy,t.utcSunday=Fy,t.utcSundays=Ly,t.utcThursday=Oy,t.utcThursdays=Gy,t.utcTickInterval=av,t.utcTicks=ov,t.utcTuesday=Uy,t.utcTuesdays=Hy,t.utcWednesday=Iy,t.utcWednesdays=Xy,t.utcWeek=Fy,t.utcWeeks=Ly,t.utcYear=ev,t.utcYears=rv,t.variance=x,t.version="7.9.0",t.window=pn,t.xml=Sc,t.zip=function(){return gt(arguments)},t.zoom=function(){var t,n,e,r=Ew,i=Nw,o=zw,a=Cw,u=Pw,c=[0,1/0],f=[[-1/0,-1/0],[1/0,1/0]],s=250,l=ri,h=$t("start","zoom","end"),d=500,p=150,g=0,y=10;function v(t){t.property("__zoom",kw).on("wheel.zoom",T,{passive:!1}).on("mousedown.zoom",A).on("dblclick.zoom",S).filter(u).on("touchstart.zoom",E).on("touchmove.zoom",N).on("touchend.zoom touchcancel.zoom",k).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function _(t,n){return(n=Math.max(c[0],Math.min(c[1],n)))===t.k?t:new ww(n,t.x,t.y)}function b(t,n,e){var r=n[0]-e[0]*t.k,i=n[1]-e[1]*t.k;return r===t.x&&i===t.y?t:new ww(t.k,r,i)}function m(t){return[(+t[0][0]+ +t[1][0])/2,(+t[0][1]+ +t[1][1])/2]}function x(t,n,e,r){t.on("start.zoom",(function(){w(this,arguments).event(r).start()})).on("interrupt.zoom end.zoom",(function(){w(this,arguments).event(r).end()})).tween("zoom",(function(){var t=this,o=arguments,a=w(t,o).event(r),u=i.apply(t,o),c=null==e?m(u):"function"==typeof e?e.apply(t,o):e,f=Math.max(u[1][0]-u[0][0],u[1][1]-u[0][1]),s=t.__zoom,h="function"==typeof n?n.apply(t,o):n,d=l(s.invert(c).concat(f/s.k),h.invert(c).concat(f/h.k));return function(t){if(1===t)t=h;else{var n=d(t),e=f/n[2];t=new ww(e,c[0]-n[0]*e,c[1]-n[1]*e)}a.zoom(null,t)}}))}function w(t,n,e){return!e&&t.__zooming||new M(t,n)}function M(t,n){this.that=t,this.args=n,this.active=0,this.sourceEvent=null,this.extent=i.apply(t,n),this.taps=0}function T(t,...n){if(r.apply(this,arguments)){var e=w(this,n).event(t),i=this.__zoom,u=Math.max(c[0],Math.min(c[1],i.k*Math.pow(2,a.apply(this,arguments)))),s=ne(t);if(e.wheel)e.mouse[0][0]===s[0]&&e.mouse[0][1]===s[1]||(e.mouse[1]=i.invert(e.mouse[0]=s)),clearTimeout(e.wheel);else{if(i.k===u)return;e.mouse=[s,i.invert(s)],Gi(this),e.start()}Sw(t),e.wheel=setTimeout((function(){e.wheel=null,e.end()}),p),e.zoom("mouse",o(b(_(i,u),e.mouse[0],e.mouse[1]),e.extent,f))}}function A(t,...n){if(!e&&r.apply(this,arguments)){var i=t.currentTarget,a=w(this,n,!0).event(t),u=Zn(t.view).on("mousemove.zoom",(function(t){if(Sw(t),!a.moved){var n=t.clientX-s,e=t.clientY-l;a.moved=n*n+e*e>g}a.event(t).zoom("mouse",o(b(a.that.__zoom,a.mouse[0]=ne(t,i),a.mouse[1]),a.extent,f))}),!0).on("mouseup.zoom",(function(t){u.on("mousemove.zoom mouseup.zoom",null),ue(t.view,a.moved),Sw(t),a.event(t).end()}),!0),c=ne(t,i),s=t.clientX,l=t.clientY;ae(t.view),Aw(t),a.mouse=[c,this.__zoom.invert(c)],Gi(this),a.start()}}function S(t,...n){if(r.apply(this,arguments)){var e=this.__zoom,a=ne(t.changedTouches?t.changedTouches[0]:t,this),u=e.invert(a),c=e.k*(t.shiftKey?.5:2),l=o(b(_(e,c),a,u),i.apply(this,n),f);Sw(t),s>0?Zn(this).transition().duration(s).call(x,l,a,t):Zn(this).call(v.transform,l,a,t)}}function E(e,...i){if(r.apply(this,arguments)){var o,a,u,c,f=e.touches,s=f.length,l=w(this,i,e.changedTouches.length===s).event(e);for(Aw(e),a=0;a + + + + D3.js Interactive Horizontal Bar Graph + + + + + +

Life Cycle Cost for 50 Years

+
+ + + + + +""" + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Life Cycle Cost for 50 Years") + + view = QWebEngineView() + view.setHtml(html_content) + self.setCentralWidget(view) + self.resize(700, 600) + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = MainWindow() + window.show() + sys.exit(app.exec()) diff --git a/src/osbridgelcca/desktop_app/graphs/bubble_graph.py b/src/osbridgelcca/desktop_app/graphs/bubble_graph.py new file mode 100644 index 0000000..74c502e --- /dev/null +++ b/src/osbridgelcca/desktop_app/graphs/bubble_graph.py @@ -0,0 +1,231 @@ +import sys +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtWebEngineWidgets import QWebEngineView +import pandas as pd + +FILE_PATH = r"data.csv" +df = pd.read_csv(FILE_PATH) + +# using the d3js graphing library to plot the graph, it is downloaded locally and saved in the same directory as that of this graph script +with open(r"..\dependencies\d3js.js", "r", encoding="utf-8") as f: + d3_js = f.read() + +name = ['A - Initial Carbon Emission Cost\n', 'B - Carbon Emission due to Re-Routing', 'C - Maintenance Emission Costs', + "Embodied carbon emissions", "Additional CO2 e costs due to rerouting", "Periodic maintenance carbon emissions"] +percentage_list = [] +cost_list = [] + +for i, item in enumerate(name): + count = 0 + if i < 3: + for _, row in df.iterrows(): + if item in list(row): + if count == 0: + temp_row = list(row) + percentage_list.append(float(temp_row[temp_row.index(item) + 1])) + count += 1 + else: + for _, row in df.iterrows(): + if item in list(row): + if count == 0: + temp_row = list(row) + cost_list.append(float(temp_row[temp_row.index(item) + 1])) + count += 1 + +percentage_list = [round(item, 2) for item in percentage_list] +print(percentage_list) +val_a, val_b, val_c = percentage_list + +cost_list = [round(float(items) / 100000.0, 2) for items in cost_list] +cost_a, cost_b, cost_c = cost_list + + +html_content = f""" + + + + + Bubble Chart Layout + + + + +
+
+
+ A - Initial Carbon Emission Cost + B - Carbon Emission due to Re-Routing + C - Maintenance Emission Costs +
+
+
+ + + + +""" + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Bubble chart") + self.setGeometry(100, 100, 900, 550) + + view = QWebEngineView() + view.setHtml(html_content) + self.setCentralWidget(view) + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = MainWindow() + window.show() + sys.exit(app.exec()) diff --git a/src/osbridgelcca/desktop_app/graphs/data.csv b/src/osbridgelcca/desktop_app/graphs/data.csv new file mode 100644 index 0000000..0b4a8bb --- /dev/null +++ b/src/osbridgelcca/desktop_app/graphs/data.csv @@ -0,0 +1,29 @@ +These are the cordinates for plotting the life cycle cost analysis for 50 years the same format of inputs will be used for 100 years analysis as well. On X cordinate comes the magnitude of the amount and on the Y aixis come the different cost component of analysis,,,,,,,,,,,,, +,,Cost Compoents,Amount,%,Stage distribution,LCC Type,,Stage,Distribution,,,Carbon Emission Throughout the life cycle, +,,Initial construction cost,6182839.35,23.85836602,Initial Stage,Economic Cost,,Initial Stage,28.42910132,,,"A - Initial Carbon Emission Cost +",15.63380933 +,,Embodied carbon emissions,852863.518,3.291033266,Initial Stage,Environemntal Cost,,,54.92257883,,,B - Carbon Emission due to Re-Routing,53.2264347 +,,Time cost estimate,231856.4756,0.894688726,Initial Stage,Economic Cost,,,16.64831832,,,C - Maintenance Emission Costs,31.13975597 +,,Road user cost,12392640,47.82077042,Initial Stage,Social Cost,,Use Stage,48.11024121,,,, +,,Additional CO2 e costs due to rerouting,2903635.538,11.20456081,Initial Stage,Environemntal Cost,,,0,,,, +,,Periodic Maintenance costs,124402.59,0.480045228,Use Stage,Economic Cost,,,51.88975879,,,, +,,Periodic maintenance carbon emissions,1698751.806,6.555150485,Use Stage,Environemntal Cost,,End of Life Stage,100,,,, +,,Annual routine inspection costs,1273235.898,4.913167942,Use Stage,Economic Cost,,,0,,,, +,,Repair and rehabilitation costs,177380.5728,0.684476887,Use Stage,Economic Cost,,,0,,,, +,,Demolition and deconstruction costs,77158.678,0.297740225,End of Life Stage,Economic Cost,,Beyond Life Stage,0,,,, +,,Recycling costs,0,0,Beyond Life Stage,Economic Cost,,,0,,,, +,,Total Life Cycle Cost,25914764.43,100,,,,,0,,,, +,,,,,,,,,,,,, +,,Cost Compoents,Amount,%,Stage distribution,LCC Type,,Stage,Distribution,,,, +,,Initial construction cost,6701398.36,31.58858558,Initial Stage,Economic Cost,,Initial Stage,37.80673377,,,, +,,Embodied carbon emissions,1102094.341,5.194975666,Initial Stage,Environemntal Cost,,,,,,, +,,Time cost estimate,167534.959,0.78971464,Initial Stage,Economic Cost,,,,,,, +,,Road user cost,8261760,38.94370977,Initial Stage,Social Cost,,Use Stage,,,,, +,,Additional CO2e costs due to rerouting,1935757.025,9.124636854,Initial Stage,Environemntal Cost,,,,,,, +,,Periodic Maintenance costs,149545.738,0.704918301,Use Stage,Economic Cost,,,,,,, +,,Periodic maintenance carbon emissions,1767651.034,8.332230523,Use Stage,Environemntal Cost,,End of Life Stage,,,,, +,,Annual routine inspection costs,1380023.073,6.505056796,Use Stage,Economic Cost,,,,,,, +,,Repair and rehabilitation costs,0,0,Use Stage,Economic Cost,,,,,,, +,,Demolition and deconstruction costs,83630.03,0.39420942,End of Life Stage,Economic Cost,,Beyond Life Stage,,,,, +,,Recycling costs,-334774.6688,-1.578037554,Beyond Life Stage,Economic Cost,,,,,,, +,,Total Life Cycle Cost,21214619.89,100,,,,,,,,, diff --git a/src/osbridgelcca/desktop_app/graphs/data1.csv b/src/osbridgelcca/desktop_app/graphs/data1.csv new file mode 100644 index 0000000..d3235a9 --- /dev/null +++ b/src/osbridgelcca/desktop_app/graphs/data1.csv @@ -0,0 +1,29 @@ +These are the cordinates for plotting the life cycle cost analysis for 50 years the same format of inputs will be used for 100 years analysis as well. On X cordinate comes the magnitude of the amount and on the Y aixis come the different cost component of analysis,,,,,,,,,,,,, +,,Cost Compoents,Amount,%,Stage distribution,LCC Type,,Stage,Distribution,,,Carbon Emission Throughout the life cycle, +,,Initial construction cost,6182839.35,23.85836602,Initial Stage,Economic Cost,,Initial Stage,28.42910132,,,"A - Initial Carbon Emission Cost +",15.63380933 +,,Embodied carbon emissions,852863.518,3.291033266,Initial Stage,Environemntal Cost,,,54.92257883,,,B - Carbon Emission due to Re-Routing,53.2264347 +,,Time cost estimate,231856.4756,0.894688726,Initial Stage,Economic Cost,,,16.64831832,,,C - Maintenance Emission Costs,31.13975597 +,,Road user cost,12392640,47.82077042,Initial Stage,Social Cost,,Use Stage,48.11024121,,,, +,,Additional CO2 e costs due to rerouting,2903635.538,11.20456081,Initial Stage,Environemntal Cost,,,0,,,, +,,Periodic Maintenance costs,124402.59,0.480045228,Use Stage,Economic Cost,,,51.88975879,,,, +,,Periodic maintenance carbon emissions,1698751.806,6.555150485,Use Stage,Environemntal Cost,,End of Life Stage,100,,,, +,,Annual routine inspection costs,1273235.898,4.913167942,Use Stage,Economic Cost,,,0,,,, +,,Repair and rehabilitation costs,177380.5728,0.684476887,Use Stage,Economic Cost,,,0,,,, +,,Demolition and deconstruction costs,77158.678,0.297740225,End of Life Stage,Economic Cost,,Beyond Life Stage,0,,,, +,,Recycling costs,0,0,Beyond Life Stage,Economic Cost,,,0,,,, +,,Total Life Cycle Cost,25914764.43,100,,,,,0,,,, +,,,,,,,,,,,,, +,,Cost Compoents,Amount,%,Stage distribution,LCC Type,,Stage,Distribution,,,, +,,Initial construction cost,6701398.36,31.58858558,Initial Stage,Economic Cost,,Initial Stage,37.80673377,,,, +,,Embodied carbon emissions,1102094.341,5.194975666,Initial Stage,Environemntal Cost,,,,,,, +,,Time cost estimate,167534.959,0.78971464,Initial Stage,Economic Cost,,,,,,, +,,Road user cost,8261760,38.94370977,Initial Stage,Social Cost,,Use Stage,,,,, +,,Additional CO2 e costs due to rerouting,1935757.025,9.124636854,Initial Stage,Environemntal Cost,,,,,,, +,,Periodic Maintenance costs,149545.738,0.704918301,Use Stage,Economic Cost,,,,,,, +,,Periodic maintenance carbon emissions,1767651.034,8.332230523,Use Stage,Environemntal Cost,,End of Life Stage,,,,, +,,Annual routine inspection costs,1380023.073,6.505056796,Use Stage,Economic Cost,,,,,,, +,,Repair and rehabilitation costs,0,0,Use Stage,Economic Cost,,,,,,, +,,Demolition and deconstruction costs,83630.03,0.39420942,End of Life Stage,Economic Cost,,Beyond Life Stage,,,,, +,,Recycling costs,-334774.6688,-1.578037554,Beyond Life Stage,Economic Cost,,,,,,, +,,Total Life Cycle Cost,21214619.89,100,,,,,,,,, diff --git a/src/osbridgelcca/desktop_app/graphs/horizontal_bar_graph.py b/src/osbridgelcca/desktop_app/graphs/horizontal_bar_graph.py new file mode 100644 index 0000000..b10bffe --- /dev/null +++ b/src/osbridgelcca/desktop_app/graphs/horizontal_bar_graph.py @@ -0,0 +1,345 @@ +import sys +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtWebEngineWidgets import QWebEngineView +import pandas as pd + +FILE_PATH = r"data1.csv" +df = pd.read_csv(FILE_PATH) + +# using the d3js graphing library to plot the graph, it is downloaded locally and saved in the same directory as that of this graph script +with open(r"..\dependencies\d3js.js", "r", encoding="utf-8") as f: + d3_js = f.read() + +list_final = [ + "Initial construction cost", + "Embodied carbon emissions", + "Time cost estimate", + "Road user cost", + "Additional CO2 e costs due to rerouting", + "Periodic Maintenance costs", + "Periodic maintenance carbon emissions", + "Annual routine inspection costs", + "Repair and rehabilitation costs", + "Demolition and deconstruction costs", + "Recycling costs" +] + +# Extract values from DataFrame +psc_cost = [] +steel_cost = [] +for item in list_final: + count = 0 + for _, row in df.iterrows(): + if item in list(row): + temp_row = list(row) + if count == 0: + psc_cost.append(float(temp_row[temp_row.index(item) + 1])) + else: + steel_cost.append(float(temp_row[temp_row.index(item) + 1])) + count += 1 + +# Calculate Total Life-Cycle Cost +total_psc_cost = sum(psc_cost) +total_steel_cost = sum(steel_cost) + +# Prepare data for D3.js +js_data = [] +for i, label in enumerate(list_final): + # Adjust labels for better display in the chart + display_label = label.replace("Initial construction cost", "Initial Construction Cost") \ + .replace("Embodied carbon emissions", "Initial Carbon Emission Cost") \ + .replace("Time cost estimate", "Time Cost") \ + .replace("Road user cost", "Road User Cost") \ + .replace("Additional CO2 e costs due to rerouting", "Carbon Emission due to Re-Routing") \ + .replace("Periodic Maintenance costs", "Periodic Maintenance Costs") \ + .replace("Periodic maintenance carbon emissions", "Maintenance Emission Cost") \ + .replace("Annual routine inspection costs", "Routine Inspection Cost") \ + .replace("Repair and rehabilitation costs", "Repair & Rehabilitation Cost") \ + .replace("Demolition and deconstruction costs", "Demolition & Disposal Cost") \ + .replace("Recycling costs", "Recycling Cost") + + + js_data.append({ + "label": display_label, + "psc": psc_cost[i], + "steel": steel_cost[i] + }) + +# Add the "Total Life-Cycle Cost" entry +js_data.append({ + "label": "Total Life-Cycle Cost", + "psc": total_psc_cost, + "steel": total_steel_cost +}) + +# Convert Python list of dictionaries to a JavaScript array string +js_data_string = str(js_data).replace("'", '"') # Replace single quotes with double quotes for JSON compatibility + +html_content = f""" + + + + + Bridge Cost Comparison + + + + +
+
+ + + +""" + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Bridge Cost Comparison") + self.setGeometry(100, 100, 1200, 900) + + view = QWebEngineView() + view.setHtml(html_content) + self.setCentralWidget(view) + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = MainWindow() + window.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/graphs/pie_chart.py b/src/osbridgelcca/desktop_app/graphs/pie_chart.py new file mode 100644 index 0000000..9e01ce4 --- /dev/null +++ b/src/osbridgelcca/desktop_app/graphs/pie_chart.py @@ -0,0 +1,569 @@ +import pandas as pd +import json +from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout +from PySide6.QtWebEngineWidgets import QWebEngineView +from PySide6.QtCore import QUrl, QSize + +# using the d3js graphing library to plot the graph, it is downloaded locally and saved in the same directory as that of this graph script +with open(r"..\dependencies\d3js.js", "r", encoding="utf-8") as f: + d3_js = f.read() + +class D3PieChartViewer(QMainWindow): + def __init__(self, data_js): + super().__init__() + self.setWindowTitle("Cost Breakdown Pie Chart") + self.setMinimumSize(QSize(800, 600)) + + self.central_widget = QWidget() + self.setCentralWidget(self.central_widget) + + self.layout = QVBoxLayout(self.central_widget) + + self.web_view = QWebEngineView() + self.layout.addWidget(self.web_view) + + # Generate HTML content + html_content = self.generate_html(data_js) + + # Load the HTML content + self.web_view.setHtml(html_content, QUrl.fromLocalFile("")) + + def generate_html(self, data_js): + html = f""" + + + + Cost Breakdown Pie Chart + + + + +
+

Cost Breakdown (in Lakhs)

+
+
+
+ + + + + """ + return html + +def main(): + print("generating graph") + FILE_PATH = r"data1.csv" + + # --- Read CSV and Extract Data --- + try: + df = pd.read_csv(FILE_PATH) + if df.empty: + print(f"Warning: CSV file '{FILE_PATH}' is empty.") + except FileNotFoundError: + print(f"Error: CSV file not found at '{FILE_PATH}'.") + df = pd.DataFrame() + except Exception as e: + print(f"Error reading CSV: {e}") + df = pd.DataFrame() + + # Define labels and their specific colors + label_colors = { + "Road user cost": "#FF8C00", + "Time cost estimate": "#483D8B", + "Embodied carbon emissions": "#B22222", + "Initial construction cost": "#996633", + "Additional CO2 e costs due to rerouting": "#8B0000", + "Periodic Maintenance costs": "#F6FB05", + "Periodic maintenance carbon emissions": "#A52A2A", + "Annual routine inspection costs": "#4682B4", + "Repair and rehabilitation costs": "#008000", + "Demolition and deconstruction costs": "#800080" + } + + # Extract data + values_dict = {} + for item in label_colors.keys(): + found_value = False + if not df.empty: + for _, row in df.iterrows(): + row_list = [str(x) for x in row.values] + if item in row_list: + idx = row_list.index(item) + if idx + 1 < len(row_list): + values_dict[item] = row_list[idx + 1] + found_value = True + break + if not found_value: + values_dict[item] = "0.0" + + cost_list = [] + for key in label_colors.keys(): + try: + cost_list.append(float(values_dict.get(key, "0.0"))) + except: + cost_list.append(0.0) + + total_cost = sum(cost_list) + percentage_list = [(v / total_cost) * 100 if total_cost else 0 for v in cost_list] + cost_list_lakhs = [v / 100000 for v in cost_list] + + # Create data with specific color mapping + data_with_colors = [ + { + "label": name, + "cost": cost, + "percent": percent, + "color": label_colors[name], + "disabled": False + } + for name, cost, percent in zip(label_colors.keys(), cost_list_lakhs, percentage_list) + ] + + data_js = json.dumps(data_with_colors) + + # Create and show the application window + app = QApplication([]) + viewer = D3PieChartViewer(data_js) + viewer.show() + app.exec() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/graphs/radial_bar_graph.py b/src/osbridgelcca/desktop_app/graphs/radial_bar_graph.py new file mode 100644 index 0000000..eb26c46 --- /dev/null +++ b/src/osbridgelcca/desktop_app/graphs/radial_bar_graph.py @@ -0,0 +1,205 @@ +import sys +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtWebEngineWidgets import QWebEngineView +import pandas as pd +import copy + +FILE_PATH = r"data.csv" +df = pd.read_csv(FILE_PATH) + +# using the d3js graphing library to plot the graph, it is downloaded locally and saved in the same directory as that of this graph script +with open(r"..\dependencies\d3js.js", "r", encoding="utf-8") as f: + d3_js = f.read() + +# hexal color code for various concentric circles, used inside the HTML js script text(its inside the string html_content) +colors = ['#273B5C', '#2E5743', '#996515', '#36454F'] + +# labels used displaying purpose, used inside the HTML js script text(its inside the string html_content) +stage_label = ['Initial Stage', 'Use Stage', 'End of Life Stage', 'Beyond Life Stage'] +stage_label_condition = ['initialstage', 'usestage', 'endoflifestage', 'beyondlifestage'] +percentage = [] + +# for loop to extract the values, modify according to the style +# Fixed: Using .iloc for positional access instead of deprecated row[index] syntax +for ___, row in df.iterrows(): + if isinstance(row.iloc[8], str): + if row.iloc[8].lower().replace(" ", "") in stage_label_condition: + percentage.append(float(row.iloc[9])) +percentage = percentage[:4] + +# html + js + css script to generate the radial bar graph, we used python's format to plug the values which we have extracted from the csv file +html_content = f""" + + + + + + + + + +

Economic cost distribution across various stages for bridges for 50 years

+ +
+ + + + + + +""" + + +class MainWindow(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Economic Cost Distribution") + + view = QWebEngineView() + view.setHtml(html_content) + self.setCentralWidget(view) + self.resize(700, 600) + + +if __name__ == "__main__": + app = QApplication(sys.argv) + window = MainWindow() + window.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/main_template.py b/src/osbridgelcca/desktop_app/main_template.py new file mode 100644 index 0000000..565e804 --- /dev/null +++ b/src/osbridgelcca/desktop_app/main_template.py @@ -0,0 +1,518 @@ +""" +: Prerna Praveen Vidyarthi +: FOSSEE Summer Fellowship 2025 +: https://github.com/prerna2024-cyber +: vidyarthiprerna637@gmail.com +""" +from PySide6.QtCore import (QSize, Qt, QPropertyAnimation, QEasingCurve) +from PySide6.QtGui import (QAction,QFont, QFontDatabase, QIcon) +from PySide6.QtWidgets import (QApplication, QHBoxLayout, QTextEdit, QScrollArea, QSpacerItem, QSizePolicy, + QMenu, QMenuBar, QPushButton, QWidget, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QComboBox) + +from widgets.title_bar import CustomTitleBar +from widgets.project_details_right_widget import ProjectDetailsWidget +from widgets.tutorial_widget_left import TutorialWidget +from widgets.results_widget import ResultsWidget +from widgets.comparison_widget import ComparisonWidget +from widgets.structure_works_data.foundation_widget import Foundation +from widgets.structure_works_data.super_structure_widget import SuperStructure +from widgets.structure_works_data.sub_structure_widget import SubStructure +from widgets.structure_works_data.auxiliary_works_widget import AuxiliaryWorks +from widgets.financial_data import FinancialData +from widgets.carbon_emission_data.carbon_emission_data import CarbonEmissionData +from widgets.carbon_emission_data.carbon_emission_cost_data import CarbonEmissionCostData +from widgets.bridge_and_traffic_data import BridgeAndTrafficData +from widgets.maintenance_repair_data import MaintenanceRepairData +from widgets.demolition_and_recycling_data import DemolitionAndRecyclingData +from widgets.project_details_left_widget import ProjectDetailsLeft +from widgets.tab_widget import CustomTabWidget +from widgets.utils.data import * +from widgets.utils.database import DatabaseManager + +from PySide6.QtWidgets import QStackedWidget + +class UiMainWindow(object): + def setupUi(self, MainWindow): + self.database_manager = DatabaseManager() + # To check if tab widget is there or no + self.tabs_active = False + # Contains name of widget and its index in tabs + self.active_tab_widgets = {} + self.widget_map = { + KEY_STRUCTURE_WORKS_DATA: Foundation, + KEY_FOUNDATION: Foundation, + KEY_SUPERSTRUCTURE: SuperStructure, + KEY_SUBSTRUCTURE: SubStructure, + KEY_AUXILIARY: AuxiliaryWorks, + KEY_FINANCIAL: FinancialData, + KEY_CARBON_EMISSION: CarbonEmissionData, + KEY_CARBON_EMISSION_COST: CarbonEmissionCostData, + KEY_BRIDGE_TRAFFIC: BridgeAndTrafficData, + KEY_MAINTAINANCE_REPAIR: MaintenanceRepairData, + KEY_DEMOLITION_RECYCLE: DemolitionAndRecyclingData + } + + if not MainWindow.objectName(): + MainWindow.setObjectName(u"MainWindow") + + # Load and set the Alata Regular font + font_id = QFontDatabase.addApplicationFont("resources/AlataRegular.ttf") + if font_id != -1: + font_family = QFontDatabase.applicationFontFamilies(font_id)[0] + app_font = QFont(font_family, 10) + QApplication.setFont(app_font) + else: + print("Failed to load Alata font") + + MainWindow.setWindowTitle("Custom Title Bar App") + MainWindow.setWindowFlags(Qt.WindowType.FramelessWindowHint) + screen = QApplication.primaryScreen().geometry() + x = (screen.width()*1//8) + y = (screen.height()*1//8) + MainWindow.setGeometry(x, y, screen.width()*3//4, screen.height()*3//4) + + # Set window stylesheet + MainWindow.setStyleSheet(""" + QMainWindow { + border: none; + } + QMenuBar { + background-color: #FAFAFA; + border-bottom: 1px solid #d0d0d0; + border-left: 1px solid #285A23; + border-right: 1px solid #285A23; + } + QMenuBar::item { + padding: 4px 10px; + background-color: transparent; + border-bottom: 2px solid #FAFAFA; + margin: 2px; + } + QMenuBar::item:selected { + border-bottom: 2px solid #806C6C; + } + QMenu { + background-color: #f0f0f0; + border: 1px solid #d0d0d0; + } + QMenu::item { + padding: 4px 4px 4px 12px; + text-align: left; + color: #514E4E; + } + QMenu::item:selected { + background-color: #e0e0e0; + } + QMenu::separator { + height: 1px; + background-color: #d0d0d0; + margin: 4px 0px; + } + QMenu::icon { + padding-left: 5px; + width: 16px; + height: 16px; + } + QMenu::indicator { + width: 16px; + height: 16px; + } + QMenu::right-arrow { + margin-right: 5px; + } + """) + + # Create a central widget and main layout for the window + self.central_widget = QWidget() + self.central_widget.setStyleSheet("border: none;") + self.central_widget.setObjectName("central_widget") + MainWindow.setCentralWidget(self.central_widget) + main_layout = QVBoxLayout(self.central_widget) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + # Add the custom title bar to the top of the main layout + self.title_bar = CustomTitleBar(MainWindow) + main_layout.addWidget(self.title_bar) + + # Create and add menu bar directly to the central widget's main_layout + # DO NOT use MainWindow.setMenuBar() when using FramelessWindowHint + self.menubar = QMenuBar() + self.menubar.setObjectName(u"menubar") + main_layout.addWidget(self.menubar) # This is the crucial line for custom frame + + # Create menus + self.menuFile = QMenu("&File", self.menubar) + self.menuHome = QMenu("&Home", self.menubar) + self.menuReport = QMenu("&Report", self.menubar) + self.menuHelp = QMenu("&Help", self.menubar) + + # Add menus to menubar + self.menubar.addMenu(self.menuFile) + self.menubar.addMenu(self.menuHome) + self.menubar.addMenu(self.menuReport) + self.menubar.addMenu(self.menuHelp) + + # Create and add actions to File menu with icons + self.actionNew = QAction(QIcon("resources/new.svg"), "New", MainWindow) + self.actionOpen = QAction(QIcon("resources/open.svg"), "Open", MainWindow) + self.actionSave = QAction(QIcon("resources/save.svg"), "Save", MainWindow) + self.actionSaveAs = QAction(QIcon("resources/save_as.svg"), "Save As...", MainWindow) + self.actionCreateCopy = QAction(QIcon("resources/create_copy.svg"), "Create a Copy", MainWindow) + self.actionPrint = QAction(QIcon("resources/print.svg"), "Print", MainWindow) + self.actionRename = QAction(QIcon("resources/rename.svg"), "Rename", MainWindow) + self.actionExport = QAction(QIcon("resources/export.svg"), "Export", MainWindow) + self.actionVersionHistory = QAction(QIcon("resources/version_history.svg"), "Version History", MainWindow) + self.actionInfo = QAction(QIcon("resources/info.svg"), "Info", MainWindow) + + # Add actions to File menu + self.menuFile.addAction(self.actionNew) + self.menuFile.addAction(self.actionOpen) + self.menuFile.addSeparator() + self.menuFile.addAction(self.actionSave) + self.menuFile.addAction(self.actionSaveAs) + self.menuFile.addAction(self.actionCreateCopy) + self.menuFile.addAction(self.actionPrint) + self.menuFile.addSeparator() + self.menuFile.addAction(self.actionRename) + self.menuFile.addAction(self.actionExport) + self.menuFile.addAction(self.actionVersionHistory) + self.menuFile.addAction(self.actionInfo) + + # Create and add actions to Help menu with icons + self.actionDocumentation = QAction(QIcon("resources/contact.svg"), "Contact us", MainWindow) + self.actionFeedback = QAction(QIcon("resources/feedback.svg"), "Feedback", MainWindow) + self.actionVideoTutorial = QAction(QIcon("resources/video_tutorial.svg"), "Video Tutorials", MainWindow) + self.actionJoinCommunity = QAction(QIcon("resources/join_community.svg"), "Join our Community", MainWindow) + + # Add actions to Help menu + self.menuHelp.addAction(self.actionDocumentation) + self.menuHelp.addAction(self.actionFeedback) + self.menuHelp.addSeparator() + + self.menuHelp.addAction(self.actionVideoTutorial) + self.menuHelp.addAction(self.actionJoinCommunity) + + # Main content area below the menubar + self.main_content_area = QWidget() + # This widget needs to stretch to fill the remaining space + main_layout.addWidget(self.main_content_area, 1) # Add stretch factor of 1 + self.main_content_area.setObjectName("main_content_area") + self.main_content_area.setStyleSheet(""" + #main_content_area { + background-color: #FAFAFA; + border: 1px solid #285A23; + border-top: 1px solid #BBBBBB; + + } + QLabel { + color: #9F8888; + font-size: 14px; + border: none; + padding: 5px; + } + QPushButton { + background-color: #EDEDED; + border: 1px solid #d0d0d0; + padding: 6px 16px; + color: #514E4E; + } + QPushButton:hover { + background: qlineargradient( + x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #BBBBBB, + stop: 0.26 #E8E8E8, + stop: 1 #EDEDED + ); + border-color: #806C6C; + } + QPushButton:pressed { + background-color: #d0d0d0; + } + """) + + # Main vertical layout for content inside main_content_area + content_layout = QVBoxLayout(self.main_content_area) + content_layout.setContentsMargins(0,0,0,0) + content_layout.setSpacing(0) + + # Create horizontal layout for buttons + button_layout = QHBoxLayout() + button_layout.setSpacing(0) # Add spacing between all buttons + button_layout.setContentsMargins(5, 5, 5, 5) # Add some margin around the layout + + # ------------------------------------------------------------ + + # Add buttons to left container + self.edit_button = QPushButton() + self.edit_button.setObjectName(u"edit_button") + self.edit_button.setIcon(QIcon("resources/edit_button.png")) + self.edit_button.setFixedSize(60, 30) + self.edit_button.setIconSize(QSize(25, 25)) + self.edit_button.setStyleSheet(""" + QPushButton { + border-radius: 5px; + background-color: #EDEDED; + border: 1px solid #BBBBBB; + margin-right: 20px; + margin-left: 10px; + } + QPushButton:hover { + background-color: #FAFAFA; + border: 1px solid #888888; + } + """) + button_layout.addWidget(self.edit_button) + + self.file_button= QPushButton() + self.file_button.setObjectName(u"file_button") + self.file_button.setFixedSize(50, 30) + self.file_button.setIcon(QIcon("resources/file_button.png")) + self.file_button.setIconSize(QSize(30, 30)) + self.file_button.setStyleSheet(""" + QPushButton { + border-radius: 5px; + background-color: #EDEDED; + border: 1px solid #BBBBBB; + margin-right: 20px; + } + QPushButton:hover { + border: 1px solid #888888; + background-color: #FAFAFA; + } + """) + button_layout.addWidget(self.file_button) + + # Create save menu button + self.save_button = QPushButton() + self.save_button.setObjectName(u"save_button") + self.save_button.setFixedSize(40, 30) + self.save_button.setIcon(QIcon("resources/save_button.png")) + self.save_button.setIconSize(QSize(25, 25)) + self.save_button.setStyleSheet(""" + QPushButton { + border-radius: 5px; + background-color: #EDEDED; + border: 1px solid #BBBBBB; + } + QPushButton:hover { + background-color: #FAFAFA; + border: 1px solid #888888; + } + QPushButton::menu-indicator { + image: url(resources/arrow_down.png); + width: 8px; + height: 30px; + subcontrol-position: right center; + subcontrol-origin: padding; + margin-right: 2px; + margin-left: 2px; + border-left: 1px solid #BBBBBB; + } + """) + + # Create save menu + self.save_menu = QMenu(self.save_button) + self.save_menu.setStyleSheet(""" + QMenu { + background-color: #EDEDED; + border: 1px solid #BBBBBB; + padding: 2px; + color: #9F8888; + } + + QMenu::item { + padding: 2px 2px; + margin: 0px; + border: none; + min-height: 18px; + font-size: 11px; + } + + QMenu::item:selected { + background-color: #FAFAFA; + } + + QMenu::icon { + width: 0px; + } + """) + self.save_action = QAction("Save", self.save_menu) + self.save_as_action = QAction("Save As", self.save_menu) + self.save_menu.addAction(self.save_action) + self.save_menu.addAction(self.save_as_action) + self.save_button.setMenu(self.save_menu) + + button_layout.addWidget(self.save_button) + + # ------------------------------------------------------------ + + button_layout.addStretch() + + # Add label + self.windows = QLabel("Windows:") + button_layout.addWidget(self.windows) + + # add buttons to the right + self.tutorial_tab = QPushButton("Tutorials") + self.project_details_tab = QPushButton("Project Details") + self.results_tab = QPushButton("Results") + self.compare = QPushButton("Compare") + + # Add buttons to layout + button_layout.addWidget(self.tutorial_tab) + button_layout.addWidget(self.project_details_tab) + button_layout.addWidget(self.results_tab) + button_layout.addWidget(self.compare) + + button_layout.addStretch() + + # Add the button layout to the main content layout + content_layout.addLayout(button_layout) + + # ------------------------------------------------------------ + body_widget = QWidget() + body_widget.setObjectName("body_widget") + body_widget.setStyleSheet(""" + #body_widget { + border-top: 1px solid #d0d0d0; + } + """) + # ------------------------------------------------------------ + body_layout = QHBoxLayout(body_widget) + body_layout.setSpacing(20) + + # Placeholders for dynamic widgets + self.left_panel_placeholder = QWidget() + self.left_panel_placeholder.setLayout(QVBoxLayout()) + self.right_panel_placeholder = QWidget() + self.right_panel_placeholder.setLayout(QVBoxLayout()) + body_layout.addWidget(self.left_panel_placeholder, 1) + body_layout.addWidget(self.right_panel_placeholder, 4) + content_layout.addWidget(body_widget) + + # Store references to current widgets + self.current_left_widget = None + self.current_right_widget = None + + # Button click handlers + def show_tutorial_widget(): + if self.current_left_widget: + self.left_panel_placeholder.layout().removeWidget(self.current_left_widget) + self.current_left_widget.setParent(None) + self.current_left_widget = TutorialWidget() + self.left_panel_placeholder.layout().addWidget(self.current_left_widget) + self.current_left_widget.closed.connect(lambda: self.remove_left_widget()) + + def show_results_widget(): + if self.current_right_widget: + self.right_panel_placeholder.layout().removeWidget(self.current_right_widget) + self.current_right_widget.setParent(None) + self.current_right_widget = ResultsWidget() + self.right_panel_placeholder.layout().addWidget(self.current_right_widget) + self.current_right_widget.closed.connect(lambda: self.remove_right_widget()) + print("results widget") + self.results_tab.clicked.connect(show_results_widget) + + def show_comparison_widget(): + if self.current_right_widget: + self.right_panel_placeholder.layout().removeWidget(self.current_right_widget) + self.current_right_widget.setParent(None) + self.current_right_widget = ComparisonWidget() + self.right_panel_placeholder.layout().addWidget(self.current_right_widget) + self.current_right_widget.closed.connect(lambda: self.remove_right_widget()) + print("comparison widget") + self.compare.clicked.connect(show_comparison_widget) + + + + def show_project_details_widget(widget_name=None): + if widget_name and widget_name in self.widget_map: + # Tabs are already visible + if self.tabs_active: + if self.active_tab_widgets.get(widget_name) is not None: + # change active tab to that tab + index = self.active_tab_widgets[widget_name] + self.current_right_widget.activate_tab(index) + else: + # Flush active tabs + self.remove_right_widget() + self.active_tab_widgets = {} + self.tabs_active = True + self.current_right_widget = CustomTabWidget(parent=self) + + # Add Tab widget + self.right_panel_placeholder.layout().addWidget(self.current_right_widget) + + + # Remove left widget first + self.remove_left_widget() + # Create left panel + self.current_left_widget = ProjectDetailsLeft(self.widget_map, parent=self) + self.left_panel_placeholder.layout().addWidget(self.current_left_widget) + self.current_left_widget.closed.connect(lambda: self.remove_left_widget()) + self.current_left_widget.handle_button_selection(button_name=widget_name) + else: + # Switch to normal widget mode + if self.current_right_widget: + self.right_panel_placeholder.layout().removeWidget(self.current_right_widget) + self.current_right_widget.setParent(None) + self.tabs_active = False + self.active_tab_widgets = {} # Clear tab tracking + self.current_right_widget = ProjectDetailsWidget() + self.right_panel_placeholder.layout().addWidget(self.current_right_widget) + self.current_right_widget.closed.connect(lambda: self.remove_right_widget()) + + # Connect param_buttons if present + if hasattr(self.current_right_widget, 'param_buttons'): + for btn in self.current_right_widget.param_buttons: + btn.clicked.connect(lambda checked, b=btn: show_project_details_widget(b.text().strip())) + + def remove_right_widget(): + if self.current_right_widget: + self.right_panel_placeholder.layout().removeWidget(self.current_right_widget) + self.current_right_widget.setParent(None) + self.current_right_widget = None + self.remove_right_widget = remove_right_widget + + def remove_left_widget(): + if self.current_left_widget: + self.left_panel_placeholder.layout().removeWidget(self.current_left_widget) + self.current_left_widget.setParent(None) + self.current_left_widget = None + self.remove_left_widget = remove_left_widget + + self.tutorial_tab.clicked.connect(show_tutorial_widget) + self.project_details_tab.clicked.connect(lambda: show_project_details_widget()) + + def show_project_detail_widgets(self, widget_name): + """Public method to show project detail widgets""" + if widget_name and widget_name in self.widget_map: + # Tabs are already visible + if self.tabs_active: + if self.active_tab_widgets.get(widget_name) is not None: + # change active tab to that tab + index = self.active_tab_widgets[widget_name] + self.current_right_widget.activate_tab(index) + else: + # add new tab + widget = self.widget_map[widget_name](database=self.database_manager) + widget.next.connect(self.next_widget) + widget.back.connect(self.prev_widget) + index = self.current_right_widget.add_new_tab(widget, widget_name) + # add record of this widget + self.active_tab_widgets[widget_name] = index + + def next_widget(self, widget_name): + current = list(self.widget_map.keys()).index(widget_name) + next = list(self.widget_map.keys())[current + 1] + if self.current_left_widget and hasattr(self.current_left_widget, 'all_param_buttons'): + self.current_left_widget.all_param_buttons[next].click() + self.show_project_detail_widgets(next) + + def prev_widget(self, widget_name): + current = list(self.widget_map.keys()).index(widget_name) + prev = list(self.widget_map.keys())[current - 1] + if self.current_left_widget and hasattr(self.current_left_widget, 'all_param_buttons'): + self.current_left_widget.all_param_buttons[prev].click() + self.show_project_detail_widgets(prev) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/resources/AlataRegular.ttf b/src/osbridgelcca/desktop_app/resources/AlataRegular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..8533e4deb1bea99927c2b98564d70a7fe5fee1e4 GIT binary patch literal 348308 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 literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/resources/Group 4.svg b/src/osbridgelcca/desktop_app/resources/Group 4.svg new file mode 100644 index 0000000..cf7290c --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/Group 4.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/osbridgelcca/desktop_app/resources/arrow_down.png b/src/osbridgelcca/desktop_app/resources/arrow_down.png new file mode 100644 index 0000000000000000000000000000000000000000..e4d726673d77e74c768a594ec717ae65f7c5b92c GIT binary patch literal 3992 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 LzpH!){rrCc2U1_u literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/resources/arrow_down_selected.png b/src/osbridgelcca/desktop_app/resources/arrow_down_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..9a4ad609dd82e9b974cc7131e2b05f79f958535b GIT binary patch literal 4267 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 literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/resources/contact.svg b/src/osbridgelcca/desktop_app/resources/contact.svg new file mode 100644 index 0000000..5b860be --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/contact.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/osbridgelcca/desktop_app/resources/country_arrow.png b/src/osbridgelcca/desktop_app/resources/country_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..f43b06bf05465d1f96cfbccc1151afef45bc42bf GIT binary patch literal 216 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|CV9FzhIn|t z4RYjbP~b@p|N2Jwq3;Km^RG(N+=P-6W~&@Iy+dHqrFCx)@Y)&7xIeRD#s=3b@_{{t zObutN_ZR4BE|nF$E^sLFu&H&0-8qJWEiNV!+fw=*oc;Qa=&lmXJQ>QwqQ&{LwQa3G literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/resources/create_copy.svg b/src/osbridgelcca/desktop_app/resources/create_copy.svg new file mode 100644 index 0000000..7a1c424 --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/create_copy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osbridgelcca/desktop_app/resources/edit_button.png b/src/osbridgelcca/desktop_app/resources/edit_button.png new file mode 100644 index 0000000000000000000000000000000000000000..6c30c3f6c47bd739ee53753e8ca8b1746e0736f0 GIT binary patch literal 11726 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 + + + + + + + + + + diff --git a/src/osbridgelcca/desktop_app/resources/feedback.svg b/src/osbridgelcca/desktop_app/resources/feedback.svg new file mode 100644 index 0000000..d164afe --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/feedback.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridgelcca/desktop_app/resources/file_button.png b/src/osbridgelcca/desktop_app/resources/file_button.png new file mode 100644 index 0000000000000000000000000000000000000000..a6b579f2f748ebc18b4f1b8a4e4d6ed0dba70722 GIT binary patch literal 1402 zcmV-=1%>*FP)U43UqJg8s4Hw;bXc*F zh7yfi%FYc*_a<^#y0O!swX~gkyr)T3Na@R+nKREY-%mo)uz)Up^PJ~-PH(RW00000 z000000000000000000000000006-%n%0wfR1?gNYNDai_eH9C8N=0Cz&&SW0z+5$A zhtlt;CSh4)#muJvZftCFud*yNafuLBqoIL`zPLFY1i_^HQhYI{lsv7B5u9uq7MO#V zC$5UI-$-M`iOBVM;>&5lwuI6E(UtTFGE$PpDWsp23&oSCW$xjR4+LB0JuuP8%_s=X z?exo<_A1%f;o`|t^K%b>`bDrMfp`{lgg3-z&$79%T`wir60bds8<~UL(wyj1PL`4r zG{T$nT4BO)X;^eBm|S>wbq9nEc@2yWlOa*c?SRticSEotzdejK+CRb94qo}5&}_hO zg)z1XN`5j*XkN_wl++wtXvQD8eUac9ZGsnx(ofUfgO|t4?mkNDEWJycO>FW&`}W!E zSbgBi&61JeqF^I&TKG%9OV zkX%>K>Yr+~wY6V7`O*2);ud=*iIWw3Z|eL`?^|I4Q$t}}yA19>BD?k-max5DYAn6D zUA?@G(py{!LNju9>W^3|$=DC)c9z5wj-(nH+;>FY{NQ8Redu^S!m>V_h9?Ln&rQvF zo1zFzm0VRhH1xjgJ@&T0pghM~J1U-VBvm2T*(0x?cu%@|TjwV`sW2J2%F=3KaR^LC zN8cH4xv)3{CWF_G4z*-h90IdVuO50scI`hbt%|^GgA0orn_C%E(iXX6sFf?r8UnKk zx8mQOR+ir4fS?Hj)0N)MxQ4T%vs>z^Bfa~{_YKzl|7kW&5tusP(O?_Y2gcpec)g#& zhExZJsYD;OV>MyXNo8RAcSnRpDV2e7V=Q!17Z_Jva$StFTImbSo=BjVy1=ke)lF4k zs{P4&)St4z(7R}-Dli?LR2c23FxVDVf$7c{1-hvUOqk<>Q9E5n5Wy zrYp~TA*xUoPt*j4{v|srQukjxy+%!7*ui9HtjvoiY68O!CcDJgSUgb^7?jQI98#4k3*n4-8=}jt2~+Yp zQafIcr0GwJ<)?oy+!EBp^0Ih;Rk8OeDaiMXisw=AWn`@#oBpgAi)Y>g + + diff --git a/src/osbridgelcca/desktop_app/resources/join_community.svg b/src/osbridgelcca/desktop_app/resources/join_community.svg new file mode 100644 index 0000000..79f1447 --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/join_community.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/osbridgelcca/desktop_app/resources/new.svg b/src/osbridgelcca/desktop_app/resources/new.svg new file mode 100644 index 0000000..97b5791 --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/new.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridgelcca/desktop_app/resources/open.svg b/src/osbridgelcca/desktop_app/resources/open.svg new file mode 100644 index 0000000..4c57e2b --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/open.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osbridgelcca/desktop_app/resources/play-button-arrowhead.png b/src/osbridgelcca/desktop_app/resources/play-button-arrowhead.png new file mode 100644 index 0000000000000000000000000000000000000000..70e877065d054343b6218548c8dff6510d4ca500 GIT binary patch literal 6074 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 P`F+^#sBPf^%8ma68G@5X literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/resources/play-button-selected.png b/src/osbridgelcca/desktop_app/resources/play-button-selected.png new file mode 100644 index 0000000000000000000000000000000000000000..d27a4821e6fd3d22f7175481d1166a901eec581e GIT binary patch literal 4537 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 W{ov!T=FDTfhs+N9=2D<^_x}M+^Qh+l literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/resources/print.svg b/src/osbridgelcca/desktop_app/resources/print.svg new file mode 100644 index 0000000..89a50fc --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/print.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridgelcca/desktop_app/resources/rename.svg b/src/osbridgelcca/desktop_app/resources/rename.svg new file mode 100644 index 0000000..43a0af7 --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/rename.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osbridgelcca/desktop_app/resources/save.svg b/src/osbridgelcca/desktop_app/resources/save.svg new file mode 100644 index 0000000..4f1ac42 --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/save.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridgelcca/desktop_app/resources/save_as.svg b/src/osbridgelcca/desktop_app/resources/save_as.svg new file mode 100644 index 0000000..b51e900 --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/save_as.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridgelcca/desktop_app/resources/save_button.png b/src/osbridgelcca/desktop_app/resources/save_button.png new file mode 100644 index 0000000000000000000000000000000000000000..f035e0bc244de07dc4939029a87aa8c8669f0352 GIT binary patch literal 8849 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 AGynhq literal 0 HcmV?d00001 diff --git a/src/osbridgelcca/desktop_app/resources/tab_close.png b/src/osbridgelcca/desktop_app/resources/tab_close.png new file mode 100644 index 0000000000000000000000000000000000000000..394d5b1afe7012f3e13014fff50471123bb16022 GIT binary patch literal 9732 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 + + diff --git a/src/osbridgelcca/desktop_app/resources/video_tutorial.svg b/src/osbridgelcca/desktop_app/resources/video_tutorial.svg new file mode 100644 index 0000000..c90e743 --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/video_tutorial.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osbridgelcca/desktop_app/resources/window_close.svg b/src/osbridgelcca/desktop_app/resources/window_close.svg new file mode 100644 index 0000000..eeafc65 --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/window_close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/resources/window_maximize.svg b/src/osbridgelcca/desktop_app/resources/window_maximize.svg new file mode 100644 index 0000000..6adddd1 --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/window_maximize.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/osbridgelcca/desktop_app/resources/window_minimize.svg b/src/osbridgelcca/desktop_app/resources/window_minimize.svg new file mode 100644 index 0000000..9c2d49e --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/window_minimize.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/resources/window_restore.svg b/src/osbridgelcca/desktop_app/resources/window_restore.svg new file mode 100644 index 0000000..07a15be --- /dev/null +++ b/src/osbridgelcca/desktop_app/resources/window_restore.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/osbridgelcca/desktop_app/widgets/bridge_and_traffic_data.py b/src/osbridgelcca/desktop_app/widgets/bridge_and_traffic_data.py new file mode 100644 index 0000000..b629283 --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/bridge_and_traffic_data.py @@ -0,0 +1,429 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +from .utils.data import * +import sys +import os + +class BridgeAndTrafficData(QWidget): + closed = Signal() + next = Signal(str) + back = Signal(str) + def __init__(self, parent=None): + super().__init__() + self.data = bridge_traffic_data.get(KEY_BRIDGE_TRAFFIC) + self.traffic_widgets = [] + + self.text_box_width = 200 + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + + /* QComboBox global style with country_arrow.png as dropdown arrow */ + QComboBox { + border: 1px solid #DDDCE0; + border-radius: 10px; + color: #000000; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + color: #000000; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 18px; + height: 18px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + color: #000000; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item { + color: #000000; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + """) + + self.setObjectName("central_panel_widget") + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # --- Add General Info Form at the top of the scroll area --- + self.general_widget = QWidget() + self.general_layout = QVBoxLayout(self.general_widget) + self.general_layout.setContentsMargins(10, 20, 10, 10) + self.general_layout.setSpacing(10) + + grid_layout = QGridLayout() + grid_layout.setHorizontalSpacing(10) + grid_layout.setVerticalSpacing(20) + + # Number of Lanes + label = QLabel("Number of Lanes") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 0, 0, 1, 1) + + valuer_combo = QComboBox(self.general_widget) + valuer_combo.setFixedWidth(self.text_box_width) + valuer_combo.setPlaceholderText("Select") + valuer_combo.addItems(self.data[KEY_LANES][KEY_OPTIONS]) + self.traffic_widgets.append(valuer_combo) + grid_layout.addWidget(valuer_combo, 0, 1, 1, 1) + + info_icon = QLabel(" ") + info_icon.setStyleSheet("color: grey; font-size: 14px;") + info_icon.setAlignment(Qt.AlignmentFlag.AlignLeft) + grid_layout.addWidget(info_icon, 0, 2, 1, 1) + + # Additional Re-Route Distance + label = QLabel("Additional Re-Route Distance") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 1, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setFixedWidth(self.text_box_width) + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + self.traffic_widgets.append(input_widget) + grid_layout.addWidget(input_widget, 1, 1, 1, 1) + info_icon = QLabel("(km)") + info_icon.setStyleSheet("color: grey; font-size: 14px;") + info_icon.setAlignment(Qt.AlignmentFlag.AlignLeft) + grid_layout.addWidget(info_icon, 1, 2, 1, 1) + + # Road Roughness + label = QLabel("Road Roughness") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 3, 0, 1, 1) + + valuer_combo = QComboBox(self.general_widget) + valuer_combo.setFixedWidth(self.text_box_width) + valuer_combo.setPlaceholderText("Select") + valuer_combo.addItems(self.data[KEY_ROADROUGHNESS][KEY_OPTIONS]) + self.traffic_widgets.append(valuer_combo) + grid_layout.addWidget(valuer_combo, 3, 1, 1, 1) + + info_icon = QLabel("(mm/km)") + info_icon.setStyleSheet("color: grey; font-size: 14px;") + info_icon.setAlignment(Qt.AlignmentFlag.AlignLeft) + grid_layout.addWidget(info_icon, 3, 2, 1, 1) + + # Road Rise and Fall (RF) + label = QLabel("Road Rise and Fall (RF)") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 4, 0, 1, 1) + + valuer_combo = QComboBox(self.general_widget) + valuer_combo.setFixedWidth(self.text_box_width) + valuer_combo.setPlaceholderText("Select") + valuer_combo.addItems(self.data[KEY_ROAD_RISE_AND_FALL][KEY_OPTIONS]) + self.traffic_widgets.append(valuer_combo) + grid_layout.addWidget(valuer_combo, 4, 1, 1, 1) + + info_icon = QLabel("(m/km)") + info_icon.setStyleSheet("color: grey; font-size: 14px;") + info_icon.setAlignment(Qt.AlignmentFlag.AlignLeft) + grid_layout.addWidget(info_icon, 4, 2, 1, 1) + + + # Type of Road + label = QLabel("Type of Road") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 5, 0, 1, 1) + + valuer_combo = QComboBox(self.general_widget) + valuer_combo.setFixedWidth(self.text_box_width) + valuer_combo.setPlaceholderText("Select") + valuer_combo.addItems(self.data[KEY_TYPE_OF_ROAD][KEY_OPTIONS]) + grid_layout.addWidget(valuer_combo, 5, 1, 1, 1) + self.traffic_widgets.append(valuer_combo) + + info_icon = QLabel(" ") + info_icon.setStyleSheet("color: grey; font-size: 14px;") + info_icon.setAlignment(Qt.AlignmentFlag.AlignLeft) + grid_layout.addWidget(info_icon, 5, 2, 1, 1) + + # Annual Increase in Traffic + label = QLabel("Annual Increaase in Traffic if Re-Routing duration increases more than a year ") + label.setFixedWidth(200) + label.setWordWrap(True) + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 6, 0, 1, 1) + + valuer_combo = QComboBox(self.general_widget) + valuer_combo.setFixedWidth(self.text_box_width) + valuer_combo.setPlaceholderText("Select") + valuer_combo.addItems(self.data[KEY_ANNUAL_INCREASE][KEY_OPTIONS]) + self.traffic_widgets.append(valuer_combo) + grid_layout.addWidget(valuer_combo, 6, 1, 1, 1) + + info_icon = QLabel("(%)") + info_icon.setStyleSheet("color: grey; font-size: 14px; padding-top: 14px;") + info_icon.setAlignment(Qt.AlignmentFlag.AlignLeft) + grid_layout.addWidget(info_icon, 6, 2, 1, 1) + + # Composition of Various Vehicles + # Remove the old label and vehicle_widget from the grid + # Instead, create a horizontal layout for this row + composition_row_widget = QWidget(self.general_widget) + composition_row_layout = QHBoxLayout(composition_row_widget) + composition_row_layout.setContentsMargins(0, 0, 0, 0) + composition_row_layout.setSpacing(20) # Space between label and box + + # The label + composition_label = QLabel("Composition of Various Vehicles") + composition_label.setAlignment(Qt.AlignLeft | Qt.AlignTop) + composition_row_layout.addWidget(composition_label, alignment=Qt.AlignTop) + + # The white box (vehicle_widget) as before + vehicle_widget = QWidget(self.general_widget) + vehicle_widget.setStyleSheet("background-color: #FFFFFF; border-radius: 10px; border: 1px solid #DDDCE0") + vehicle_widget.setFixedWidth(400) + vehicle_widget.setFixedHeight(250) + vehicle_layout = QGridLayout(vehicle_widget) + vehicle_layout.setContentsMargins(0, 0, 0, 0) + # vehicle_layout.setHorizontalSpacing(7) + # vehicle_layout.setVerticalSpacing(8) + + vehicles = ["Cars", "Buses", "HCV", "MCV", "LCV"] + for i, vehicle in enumerate(vehicles): + v_label = QLabel(f"{vehicle}:") + v_label.setFixedHeight(40) + v_label.setFixedWidth(50) + v_label.setStyleSheet("background-color: #FFFFFF; border: 1px solid #FFFFFF; border-radius: 10px; padding: 10px 10px 10px 1px;") + v_input = QLineEdit() + self.traffic_widgets.append(v_input) + v_input.setFixedWidth(self.text_box_width) + v_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background: #FFFFFF; + } + """) + vehicle_layout.addWidget(v_label, i, 0) + vehicle_layout.addWidget(v_input, i, 1) + + v_label1 = QLabel("(PCU/D)") + v_label1.setStyleSheet(" padding: 10px 10px 10px 1px;") + + composition_row_layout.addWidget(vehicle_widget, alignment=Qt.AlignTop) + + # Add the composition_row_widget to the main grid, spanning columns 0-2 + grid_layout.addWidget(v_label1, 7, 3, 1, 3) + grid_layout.addWidget(composition_row_widget, 7, 0, 1, 3) + + self.general_layout.addLayout(grid_layout) + self.general_layout.addStretch(1) + self.scroll_content_layout.addWidget(self.general_widget, alignment=Qt.AlignLeft) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + back_button.clicked.connect(lambda: self.back.emit(KEY_BRIDGE_TRAFFIC)) + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + next_button.clicked.connect(self.collect_data) + next_button.clicked.connect(lambda: self.next.emit(KEY_BRIDGE_TRAFFIC)) + self.button_h_layout.addWidget(next_button) + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + + # --- Add a corner spacer to the scroll_content_layout --- + self.button_h_layout.addSpacerItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.scroll_content_layout.addSpacerItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) + + left_panel_vlayout.addWidget(self.scroll_area) + + def collect_data(self): + data = [] + for widget in self.traffic_widgets: + if isinstance(widget, QComboBox): + value = widget.currentText() + elif isinstance(widget, QLineEdit): + value = widget.text() + data.append(value) + print(data) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(BridgeAndTrafficData(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/carbon_emission_data/carbon_emission_cost_data.py b/src/osbridgelcca/desktop_app/widgets/carbon_emission_data/carbon_emission_cost_data.py new file mode 100644 index 0000000..4f82dfd --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/carbon_emission_data/carbon_emission_cost_data.py @@ -0,0 +1,291 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +from ..utils.data import * +import sys + +class CarbonEmissionCostData(QWidget): + closed = Signal() + next = Signal(str) + back = Signal(str) + def __init__(self, parent=None): + super().__init__(parent) + + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* (Removed: QComboBox and material grid element CSS) */ + """) + + self.setObjectName("central_panel_widget") + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # --- Add General Info Form at the top of the scroll area --- + self.general_widget = QWidget() + self.general_layout = QVBoxLayout(self.general_widget) + self.general_layout.setContentsMargins(10, 20, 10, 10) + self.general_layout.setSpacing(10) + + grid_layout = QGridLayout() + grid_layout.setHorizontalSpacing(10) + grid_layout.setVerticalSpacing(20) + + field_width = 200 # More compact width for input fields + + # Shared Socioeconomic Pathway Type (first row, word wrap enabled) + label = QLabel("Shared Socioeconomic Pathway\nType") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 0, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) + input_widget.setFixedWidth(field_width) + input_widget.setText("SSP2") # Set default text + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 0, 1, 1, 1, alignment=Qt.AlignLeft) + + # Representative Concentration Pathway Type + label = QLabel("Representative Concentration Pathway\nType") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 1, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) + input_widget.setFixedWidth(field_width) + input_widget.setText("RCP60") # Set default text + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 1, 1, 1, 1, alignment=Qt.AlignLeft) + + # Social Cost of Carbon (SCC) + label = QLabel("Social Cost of Carbon") # Removed "\n(SCC)" + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 2, 0, 1, 1) + + # Create a QWidget with a horizontal layout for the SCC input and label + scc_widget = QWidget(self.general_widget) + scc_h_layout = QHBoxLayout(scc_widget) + scc_h_layout.setContentsMargins(0,0,0,0) + scc_h_layout.setSpacing(10) + + input_widget = QLineEdit() + input_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) + input_widget.setFixedWidth(field_width) # Adjusted width for SCC input to match the image + input_widget.setText("6.3936") # Set default text + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + scc_h_layout.addWidget(input_widget) + + scc_suggested_label = QLabel("(INR/kgCO₂e)") + scc_suggested_label.setStyleSheet("color: #707070; font-size: 11px;") + scc_h_layout.addWidget(scc_suggested_label) + + scc_suggested_label = QLabel(" Suggested") + scc_suggested_label.setStyleSheet("color: #B3AEAE; font-size: 10px;") + scc_h_layout.addWidget(scc_suggested_label) + scc_h_layout.addStretch(1) # Add stretch to push content to the left + + grid_layout.addWidget(scc_widget, 2, 1, 1, 1, alignment=Qt.AlignLeft) + + self.general_layout.addLayout(grid_layout) + self.general_layout.addStretch(1) + self.scroll_content_layout.addWidget(self.general_widget, alignment=Qt.AlignLeft) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + back_button.clicked.connect(lambda: self.back.emit(KEY_CARBON_EMISSION_COST)) + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + next_button.clicked.connect(lambda: self.next.emit(KEY_CARBON_EMISSION_COST)) + self.button_h_layout.addWidget(next_button) + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + + # --- Add a corner spacer to the scroll_content_layout --- + self.button_h_layout.addSpacerItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.scroll_content_layout.addSpacerItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) + + left_panel_vlayout.addWidget(self.scroll_area) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(CarbonEmissionCostData(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) diff --git a/src/osbridgelcca/desktop_app/widgets/carbon_emission_data/carbon_emission_data.py b/src/osbridgelcca/desktop_app/widgets/carbon_emission_data/carbon_emission_data.py new file mode 100644 index 0000000..f5a05f4 --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/carbon_emission_data/carbon_emission_data.py @@ -0,0 +1,513 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +from ..utils.data import * +import sys + +class ComponentWidget(QWidget): + def __init__(self, database, parent=None): + super().__init__(parent) + self.database_manager = database + self.material_rows = [] # To store references to widgets in each material row + self.current_material_row_idx = 1 # Start index for material rows (0 is header) + + self.init_ui() + + def init_ui(self): + self.component_first_scroll_content_layout = QVBoxLayout(self) # Set QVBoxLayout directly on self + self.component_first_scroll_content_layout.setContentsMargins(10, 10, 10, 10) + self.component_first_scroll_content_layout.setSpacing(10) + + # --- Material Details Grid Layout --- + self.material_grid_layout = QGridLayout() + self.material_grid_layout.setHorizontalSpacing(10) + self.material_grid_layout.setVerticalSpacing(5) + + # Header Row - Updated headers for clarity (no specific unit in header now) + headers = ["Type of Material and Grade", "Quantity", "Unit", "Embodied Carbon Energy", "Carbon Emission Factor"] + for col, header_text in enumerate(headers): + label = QLabel(header_text) + label.setAlignment(Qt.AlignCenter) + label.setObjectName("MaterialGridLabel") + if header_text=="Embodied Carbon Energy": + self.material_grid_layout.addWidget(label, 0, col, alignment=Qt.AlignmentFlag.AlignRight) + else: + + self.material_grid_layout.addWidget(label, 0, col, alignment=Qt.AlignmentFlag.AlignCenter) + + self.component_first_scroll_content_layout.addLayout(self.material_grid_layout) + + # Add initial two material rows + self.add_material_row() + self.add_material_row() + + # --- Add Material Button --- + self.add_material_button = QPushButton("+ Add Material") + self.add_material_button.setObjectName("add_material_button") + self.add_material_button.clicked.connect(self.add_material_row) + self.component_first_scroll_content_layout.addWidget(self.add_material_button, alignment=Qt.AlignCenter) + + + def add_material_row(self): + row_widgets = {} + row_idx = self.current_material_row_idx + + # Set fixed width for input widgets. + fixed_input_width_combo = 80 # Width for individual combo boxes + fixed_input_width_line_edit = 80 # Width for individual line edits + + type_material_combo = QComboBox() + type_material_combo.addItems(["Sand", "Gravel", "Cement", "Water", "Admixture", "Rebar", "Other"]) + type_material_combo.setObjectName("MaterialGridInput") + type_material_combo.setFixedWidth(fixed_input_width_combo) + self.material_grid_layout.addWidget(type_material_combo, row_idx, 0, alignment=Qt.AlignmentFlag.AlignHCenter) + row_widgets[KEY_TYPE] = type_material_combo + + quantity_edit = QLineEdit() + quantity_edit.setPlaceholderText("0") + quantity_edit.setObjectName("MaterialGridInput") + quantity_edit.setFixedWidth(fixed_input_width_line_edit) + self.material_grid_layout.addWidget(quantity_edit, row_idx, 1, alignment=Qt.AlignmentFlag.AlignHCenter) + row_widgets[KEY_QUANTITY] = quantity_edit + + unit_combo_m3 = QComboBox() + unit_combo_m3.addItems(["m³", "ft³", "kg", "ton", "litre"]) + unit_combo_m3.setObjectName("MaterialGridInput") + unit_combo_m3.setFixedWidth(fixed_input_width_combo) + self.material_grid_layout.addWidget(unit_combo_m3, row_idx, 2, alignment=Qt.AlignmentFlag.AlignHCenter) + row_widgets[KEY_UNIT_M3] = unit_combo_m3 + + + # --- Embodied Carbon Energy (LineEdit + QLabel for unit text) - Column 3 --- + embodied_carbon_layout = QHBoxLayout() + embodied_carbon_layout.addStretch() + embodied_carbon_layout.setContentsMargins(0, 0, 0, 0) + embodied_carbon_layout.setSpacing(5) # Small spacing between line edit and label + + embodied_carbon_edit = QLineEdit() + embodied_carbon_edit.setPlaceholderText("0.00") + embodied_carbon_edit.setObjectName("MaterialGridInput") + embodied_carbon_edit.setFixedWidth(fixed_input_width_combo) # Changed to fixed_input_width_combo + embodied_carbon_layout.addWidget(embodied_carbon_edit) + + # Replaced QComboBox with QLabel for static text + embodied_carbon_unit_label = QLabel("(MJ/kg)") + embodied_carbon_unit_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + embodied_carbon_unit_label.setStyleSheet("color: #3F3E5E; font-size: 11px;") # Style to match other text + embodied_carbon_layout.addWidget(embodied_carbon_unit_label) + + + self.material_grid_layout.addLayout(embodied_carbon_layout, row_idx, 3, alignment=Qt.AlignmentFlag.AlignHCenter) + row_widgets['embodied_carbon_edit'] = embodied_carbon_edit + row_widgets['embodied_carbon_unit_label'] = embodied_carbon_unit_label + + + # --- Carbon Emission Factor (LineEdit + QLabel for unit text) - Column 4 --- + carbon_emission_layout = QHBoxLayout() + carbon_emission_layout.setContentsMargins(0, 0, 0, 0) + carbon_emission_layout.setSpacing(5) # Small spacing between line edit and label + + carbon_emission_layout.addStretch() + + carbon_emission_edit = QLineEdit() + carbon_emission_edit.setPlaceholderText("0.00") + carbon_emission_edit.setObjectName("MaterialGridInput") + carbon_emission_edit.setFixedWidth(fixed_input_width_combo) # Changed to fixed_input_width_combo + carbon_emission_layout.addWidget(carbon_emission_edit) + + # Replaced QComboBox with QLabel for static text + carbon_emission_unit_label = QLabel("kg CO2e/kg") + carbon_emission_unit_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + carbon_emission_unit_label.setStyleSheet("color: #3F3E5E; font-size: 11px;") # Style to match other text + carbon_emission_layout.addWidget(carbon_emission_unit_label) + + carbon_emission_layout.addStretch() + + self.material_grid_layout.addLayout(carbon_emission_layout, row_idx, 4) + row_widgets['carbon_emission_edit'] = carbon_emission_edit + row_widgets['carbon_emission_unit_label'] = carbon_emission_unit_label + + + self.material_rows.append(row_widgets) + self.current_material_row_idx += 1 + self.updateGeometry() + self.adjustSize() + + + def remove_material_row_by_widgets(self, row_widgets_to_remove): + if row_widgets_to_remove not in self.material_rows: + return + + row_idx_in_grid = -1 + for i, row_dict in enumerate(self.material_rows): + if row_dict == row_widgets_to_remove: + row_idx_in_grid = i + 1 # +1 because row 0 is header + break + + if row_idx_in_grid == -1: + return + + for col in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(row_idx_in_grid, col) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + widget.deleteLater() + elif item.layout(): + layout = item.layout() + # Iterate and delete widgets within the layout + while layout.count(): + sub_item = layout.takeAt(0) + if sub_item.widget(): + sub_item.widget().deleteLater() + self.material_grid_layout.removeItem(layout) # Remove the layout itself + + self.material_rows.remove(row_widgets_to_remove) + self.current_material_row_idx -= 1 + + # Re-arrange remaining rows + for r_idx in range(row_idx_in_grid, self.current_material_row_idx + 1): + for c_idx in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(r_idx + 1, c_idx) # Look at the row below + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + self.material_grid_layout.addWidget(widget, r_idx, c_idx, alignment=Qt.AlignmentFlag.AlignHCenter) # Re-add with original alignment + elif item.layout(): + layout = item.layout() + self.material_grid_layout.removeItem(layout) + self.material_grid_layout.addLayout(layout, r_idx, c_idx, alignment=Qt.AlignmentFlag.AlignHCenter) # Re-add with original alignment + + self.updateGeometry() + self.update() + self.material_grid_layout.invalidate() + self.adjustSize() + +class CarbonEmissionData(QWidget): + closed = Signal() + next = Signal(str) + back = Signal(str) + def __init__(self): + super().__init__() + + self.component_widgets = [] # To store references to each ComponentWidget instance + + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Styling for component_first_widget (the container for the nested scroll area) */ + #component_first_widget { + background-color: transparent; + margin-top: 10px; /* Add some space above each component block */ + } + + #component_first_scroll_content_widget { /* This now applies directly to ComponentWidget itself */ + background-color: #FFFFFF; + padding: 10px; + + border-radius: 8px; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* Styling for QComboBox */ + QComboBox { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 30px; + height: 30px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + + /* Styling for material grid elements */ + #MaterialGridLabel { + font-weight: bold; + color: #3F3E5E; + padding: 5px; + text-align: center; + } + #MaterialGridInput { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background-color: #FFFFFF; + } + #MaterialGridInput:focus { + border: 1px solid #DDDCE0; + background-color: #FFFFFF; + } + /* IMPROVED CSS FOR ADD MATERIAL/COMPONENT BUTTONS */ + QPushButton#add_material_button, QPushButton#add_component_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + } + QPushButton#add_material_button:hover, QPushButton#add_component_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#add_material_button:pressed, QPushButton#add_component_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* END IMPROVED CSS */ + """) + + self.setObjectName("central_panel_widget") + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + back_button.clicked.connect(lambda: self.back.emit(KEY_CARBON_EMISSION)) + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + next_button.clicked.connect(lambda: self.next.emit(KEY_CARBON_EMISSION)) + self.button_h_layout.addWidget(next_button) + + + # Add the initial component layout + self.add_component_layout() + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + + # --- Add a corner spacer to the scroll_content_layout --- + self.button_h_layout.addSpacerItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.scroll_content_layout.addSpacerItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) + + left_panel_vlayout.addWidget(self.scroll_area) + + + def add_component_layout(self): + new_component = ComponentWidget(self) + self.component_widgets.append(new_component) + + # Temporarily remove button_h_layout and the vertical spacer for insertion + # Find the vertical spacer and remove it if it exists + vertical_spacer_item = None + for i in range(self.scroll_content_layout.count()): + item = self.scroll_content_layout.itemAt(i) + if isinstance(item, QSpacerItem) and item.sizeHint().width() == 0: # This identifies the vertical spacer + vertical_spacer_item = self.scroll_content_layout.takeAt(i) + break + + if self.scroll_content_layout.indexOf(self.button_h_layout) != -1: + self.scroll_content_layout.removeItem(self.button_h_layout) + + # Insert the new component + self.scroll_content_layout.addWidget(new_component) + + # Re-add the navigation buttons layout + self.scroll_content_layout.addLayout(self.button_h_layout) + + # Re-add the vertical spacer if it was found + if vertical_spacer_item: + self.scroll_content_layout.addItem(vertical_spacer_item) + + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + + def remove_component_layout(self, component_to_remove): + if component_to_remove in self.component_widgets: + self.scroll_content_layout.removeWidget(component_to_remove) + self.component_widgets.remove(component_to_remove) + component_to_remove.deleteLater() + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + + def expand_scroll_area(self): + self.central_widget.layout().invalidate() + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(CarbonEmissionData(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/comparison_widget.py b/src/osbridgelcca/desktop_app/widgets/comparison_widget.py new file mode 100644 index 0000000..a64b680 --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/comparison_widget.py @@ -0,0 +1,873 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, QSize, Qt, QPropertyAnimation, QEasingCurve, Signal +from PySide6.QtGui import (QIcon) +from PySide6.QtWebEngineWidgets import QWebEngineView +from PySide6.QtWidgets import (QHBoxLayout, QTextEdit, QScrollArea, QSpacerItem, QSizePolicy, + QPushButton, QWidget, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QComboBox, QCheckBox) +import sys + +class ComparisonWidget(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("comparison_panel_widget") + self.setStyleSheet(""" + #comparison_panel_widget { + background-color: #FDEFEF; + border-radius: 8px; + } + #comparison_panel_widget QLabel { + color: #FDEFEF; + font-size: 12px; + } + #comparison_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #FDEFEF; + } + QScrollArea { + border: 1px solid #000000; + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + } + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + QPushButton#top_button_comparison_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_comparison_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_comparison_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + QPushButton#top_button_comparison_panel:hover QIcon { + color: red; + } + + /* Comparison cards */ + QWidget#comparison_card { + background-color: #F0F8FF; + border: 2px solid #4682B4; + border-radius: 10px; + margin: 10px; + } + QPushButton#close_comparison_card_button { + border: none; + background: transparent; + padding: 2px; + } + QPushButton#close_comparison_card_button:hover { + background: rgba(0,0,0,0.05); + border-radius: 4px; + } + + /* Comparison section headers */ + QWidget#comparison_section { + background-color: #E6F3FF; + border: 1px solid #87CEEB; + border-radius: 8px; + padding: 15px; + margin: 5px; + } + + /* Comparison buttons */ + QPushButton#comparison_button { + background-color: #4682B4; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-size: 14px; + font-weight: 500; + } + QPushButton#comparison_button:hover { + background-color: #5A9BD4; + } + QPushButton#comparison_button:pressed { + background-color: #36648B; + } + + """) + + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + # --- Top Section --- + top_h_layout = QHBoxLayout() + self.top_button_comparison_panel = QPushButton("Comparison Window ") + self.top_button_comparison_panel.setObjectName("top_button_comparison_panel") + self.top_button_comparison_panel.setIcon(QIcon("resources/close.png")) + self.top_button_comparison_panel.setIconSize(QSize(13, 13)) + self.top_button_comparison_panel.setLayoutDirection(Qt.LayoutDirection.RightToLeft) + self.top_button_comparison_panel.clicked.connect(self.close_widget) + + top_h_layout.addWidget(self.top_button_comparison_panel) + top_h_layout.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)) + main_layout.addLayout(top_h_layout) + + # --- Main Content Area --- + self.main_content_widget = QWidget() + self.main_content_widget.setObjectName("main_content_widget") + self.main_content_widget.setStyleSheet(""" + #main_content_widget { + background-color: #FDEFEF; + border: 1px solid #4682B4; + border-top: none; + } + """) + + # Create scroll area for the main content + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.scroll_area.setStyleSheet(""" + QScrollArea { + border: none; + background-color: transparent; + } + QScrollBar:vertical { + background: #F0F0F0; + width: 12px; + border-radius: 6px; + } + QScrollBar::handle:vertical { + background: #C0C0C0; + border-radius: 6px; + min-height: 20px; + } + QScrollBar::handle:vertical:hover { + background: #A0A0A0; + } + QScrollBar:horizontal { + background: #F0F0F0; + height: 12px; + border-radius: 6px; + } + QScrollBar::handle:horizontal { + background: #C0C0C0; + border-radius: 6px; + min-width: 20px; + } + QScrollBar::handle:horizontal:hover { + background: #A0A0A0; + } + """) + + # Create scrollable content widget + self.scroll_content_widget = QWidget() + self.scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_content_widget.setStyleSheet(""" + #scroll_content_widget { + background-color: #F0F8FF; + } + """) + + # Main content layout + content_layout = QVBoxLayout(self.scroll_content_widget) + content_layout.setContentsMargins(20, 20, 20, 20) + content_layout.setSpacing(20) + + # File selection section + self.file_selection_widget = self._create_file_selection() + content_layout.addWidget(self.file_selection_widget) + + # Comparison chart area + self.comparison_chart_widget = self._create_comparison_chart() + content_layout.addWidget(self.comparison_chart_widget) + + # Set the scroll content widget to the scroll area + self.scroll_area.setWidget(self.scroll_content_widget) + + # Add scroll area to main layout + main_layout.addWidget(self.scroll_area) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + + def _create_file_selection(self) -> QWidget: + """Create the file selection section matching the image""" + selection_widget = QWidget() + selection_widget.setStyleSheet(""" + QWidget { + background-color: #FFFFFF; + border: 1px solid #CCCCCC; + border-radius: 8px; + margin: 5px; + } + """) + + selection_layout = QVBoxLayout(selection_widget) + selection_layout.setContentsMargins(20, 15, 20, 15) + selection_layout.setSpacing(15) + + # Top section with description and browse button + top_layout = QHBoxLayout() + + # Description text + desc_label = QLabel("Select files to compare various\ncomponents of life-cycle cost analysis") + desc_label.setStyleSheet(""" + QLabel { + font-size: 14px; + color: #333333; + line-height: 1.4; + } + """) + top_layout.addWidget(desc_label) + + top_layout.addStretch(1) + + # Browse button + browse_button = QPushButton("Browse...") + browse_button.setStyleSheet(""" + QPushButton { + background-color: #E0E0E0; + border: 1px solid #CCCCCC; + border-radius: 4px; + padding: 8px 16px; + font-size: 14px; + color: #333333; + } + QPushButton:hover { + background-color: #D0D0D0; + } + QPushButton:pressed { + background-color: #C0C0C0; + } + """) + browse_button.setCursor(Qt.CursorShape.PointingHandCursor) + top_layout.addWidget(browse_button) + + selection_layout.addLayout(top_layout) + + # File checkboxes section + files_layout = QVBoxLayout() + files_layout.setSpacing(8) + + # PSC Bridge checkbox + psc_layout = QHBoxLayout() + psc_checkbox = QCheckBox() + psc_checkbox.setChecked(True) + psc_checkbox.setStyleSheet(""" + QCheckBox::indicator { + width: 16px; + height: 16px; + border: 2px solid #4CAF50; + border-radius: 3px; + background-color: #FFFFFF; + } + QCheckBox::indicator:checked { + background-color: #4CAF50; + border-color: #4CAF50; + } + QCheckBox::indicator:checked::before { + content: "✓"; + color: white; + font-size: 12px; + font-weight: bold; + text-align: center; + line-height: 16px; + } + """) + + psc_label = QLabel("PSC Bridge.os") + psc_label.setStyleSheet(""" + QLabel { + font-size: 14px; + color: #333333; + margin-left: 8px; + } + """) + + psc_import_label = QLabel("Imported") + psc_import_label.setStyleSheet(""" + QLabel { + font-size: 12px; + color: #888888; + margin-left: 10px; + } + """) + + psc_layout.addWidget(psc_checkbox) + psc_layout.addWidget(psc_label) + psc_layout.addWidget(psc_import_label) + psc_layout.addStretch(1) + + # Steel Bridge checkbox + steel_layout = QHBoxLayout() + steel_checkbox = QCheckBox() + steel_checkbox.setChecked(True) + steel_checkbox.setStyleSheet(""" + QCheckBox::indicator { + width: 16px; + height: 16px; + border: 2px solid #4CAF50; + border-radius: 3px; + background-color: #FFFFFF; + } + QCheckBox::indicator:checked { + background-color: #4CAF50; + border-color: #4CAF50; + } + QCheckBox::indicator:checked::before { + content: "✓"; + color: white; + font-size: 12px; + font-weight: bold; + text-align: center; + line-height: 16px; + } + """) + + steel_label = QLabel("Steel Bridge.os") + steel_label.setStyleSheet(""" + QLabel { + font-size: 14px; + color: #333333; + margin-left: 8px; + } + """) + + steel_import_label = QLabel("Imported") + steel_import_label.setStyleSheet(""" + QLabel { + font-size: 12px; + color: #888888; + margin-left: 10px; + } + """) + + steel_layout.addWidget(steel_checkbox) + steel_layout.addWidget(steel_label) + steel_layout.addWidget(steel_import_label) + steel_layout.addStretch(1) + + files_layout.addLayout(psc_layout) + files_layout.addLayout(steel_layout) + + selection_layout.addLayout(files_layout) + + return selection_widget + + + + def _create_comparison_chart(self) -> QWidget: + """Create the comparison chart area matching the image""" + chart_widget = QWidget() + chart_widget.setStyleSheet(""" + QWidget { + background-color: #FFFFFF; + border: 1px solid #CCCCCC; + border-radius: 8px; + margin: 5px; + } + """) + + chart_layout = QVBoxLayout(chart_widget) + chart_layout.setContentsMargins(20, 15, 20, 15) + chart_layout.setSpacing(15) + + # Chart area - full width + # Horizontal bar chart using QWebEngineView + chart_view = QWebEngineView() + chart_view.setHtml(self._build_comparison_chart_html()) + chart_view.setZoomFactor(0.9) + chart_view.setMinimumHeight(400) + chart_view.setMaximumHeight(600) + chart_view.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + + chart_layout.addWidget(chart_view) + + return chart_widget + + def _create_legend(self) -> QWidget: + """Create the legend widget matching the image""" + legend_widget = QWidget() + legend_widget.setStyleSheet(""" + QWidget { + background-color: #FDEFEF; + border: 1px solid #DDDDDD; + border-radius: 6px; + padding: 10px; + } + """) + + legend_layout = QVBoxLayout(legend_widget) + legend_layout.setContentsMargins(10, 10, 10, 10) + legend_layout.setSpacing(8) + + # PSC Bridge legend item + psc_layout = QHBoxLayout() + psc_color = QWidget() + psc_color.setFixedSize(16, 16) + psc_color.setStyleSheet("background-color: #87CEEB; border: 1px solid #5F9EA0;") + + psc_label = QLabel("PSC Bridge") + psc_label.setStyleSheet(""" + QLabel { + font-size: 12px; + color: #333333; + } + """) + + psc_layout.addWidget(psc_color) + psc_layout.addWidget(psc_label) + psc_layout.addStretch(1) + + # Steel Bridge legend item + steel_layout = QHBoxLayout() + steel_color = QWidget() + steel_color.setFixedSize(16, 16) + steel_color.setStyleSheet("background-color: #8B0000; border: 1px solid #654321;") + + steel_label = QLabel("Steel Bridge") + steel_label.setStyleSheet(""" + QLabel { + font-size: 12px; + color: #333333; + } + """) + + steel_layout.addWidget(steel_color) + steel_layout.addWidget(steel_label) + steel_layout.addStretch(1) + + legend_layout.addLayout(psc_layout) + legend_layout.addLayout(steel_layout) + + return legend_widget + + def _create_download_options(self) -> QWidget: + """Create the download options widget matching the image""" + download_widget = QWidget() + download_layout = QVBoxLayout(download_widget) + download_layout.setContentsMargins(0, 0, 0, 0) + download_layout.setSpacing(5) + + # Download options + options = ["Download as PNG", "Download as JPG", "Download as PDF", "View as Table"] + + for option in options: + option_label = QLabel(option) + option_label.setStyleSheet(""" + QLabel { + font-size: 12px; + color: #0066CC; + padding: 4px; + text-decoration: underline; + } + QLabel:hover { + background-color: #FDEFEF; + color: #0052A3; + } + """) + option_label.setCursor(Qt.CursorShape.PointingHandCursor) + download_layout.addWidget(option_label) + + return download_widget + + def _build_comparison_chart_html(self) -> str: + """Generate HTML for horizontal bar chart using exact code from Horizontal Bar Graph 2.py""" + # Sample data matching the original structure - you can replace this with actual CSV data later + list_final = [ + "Initial construction cost", + "Embodied carbon emissions", + "Time cost estimate", + "Road user cost", + "Additional CO2 e costs due to rerouting", + "Periodic Maintenance costs", + "Periodic maintenance carbon emissions", + "Annual routine inspection costs", + "Repair and rehabilitation costs", + "Demolition and deconstruction costs", + "Recycling costs" + ] + + # Sample data (you can replace these with actual values from CSV) + psc_cost = [61.83, 8.53, 2.32, 123.93, 29.03, 1.44, 16.99, 14.32, 1.99, 0.77, 0.0] + steel_cost = [61.83, 11.02, 1.68, 123.93, 29.03, 1.44, 16.99, 14.32, 1.99, 0.77, 0.0] + + # Calculate Total Life-Cycle Cost + total_psc_cost = sum(psc_cost) + total_steel_cost = sum(steel_cost) + + # Prepare data for D3.js + js_data = [] + for i, label in enumerate(list_final): + # Adjust labels for better display in the chart + display_label = label.replace("Initial construction cost", "Initial Construction Cost") \ + .replace("Embodied carbon emissions", "Initial Carbon Emission Cost") \ + .replace("Time cost estimate", "Time Cost") \ + .replace("Road user cost", "Road User Cost") \ + .replace("Additional CO2 e costs due to rerouting", "Carbon Emission due to Re-Routing") \ + .replace("Periodic Maintenance costs", "Periodic Maintenance Costs") \ + .replace("Periodic maintenance carbon emissions", "Maintenance Emission Cost") \ + .replace("Annual routine inspection costs", "Routine Inspection Cost") \ + .replace("Repair and rehabilitation costs", "Repair & Rehabilitation Cost") \ + .replace("Demolition and deconstruction costs", "Demolition & Disposal Cost") \ + .replace("Recycling costs", "Recycling Cost") + + js_data.append({ + "label": display_label, + "psc": psc_cost[i], + "steel": steel_cost[i] + }) + + # Add the "Total Life-Cycle Cost" entry + js_data.append({ + "label": "Total Life-Cycle Cost", + "psc": total_psc_cost, + "steel": total_steel_cost + }) + + # Convert Python list of dictionaries to a JavaScript array string + import json + js_data_string = json.dumps(js_data) + + html = f""" + + + + + Bridge Cost Comparison + + + + +

+
+ + + + """ + return html + + + +#----------------Standalone-Test-Code-------------------------------- + +class MyMainWindow(QMainWindow): + def __init__(self): + super().__init__() + + self.setStyleSheet("border: none") + + self.central_widget = QWidget() + self.central_widget.setObjectName("central_widget") + self.setCentralWidget(self.central_widget) + + self.main_h_layout = QHBoxLayout(self.central_widget) + self.main_h_layout.addStretch(1) + + self.main_h_layout.addWidget(ComparisonWidget(), 2) + + self.setWindowState(Qt.WindowState.WindowMaximized) + + +if __name__ == "__main__": + QCoreApplication.setAttribute(Qt.ApplicationAttribute.AA_DontShowIconsInMenus, False) + app = QApplication(sys.argv) + window = MyMainWindow() + window.show() + sys.exit(app.exec()) diff --git a/src/osbridgelcca/desktop_app/widgets/demolition_and_recycling_data.py b/src/osbridgelcca/desktop_app/widgets/demolition_and_recycling_data.py new file mode 100644 index 0000000..c286cd2 --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/demolition_and_recycling_data.py @@ -0,0 +1,317 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +from .utils.data import * +import sys +import os + +class DemolitionAndRecyclingData(QWidget): + closed = Signal() + next = Signal(str) + back = Signal(str) + def __init__(self, parent=None): + super().__init__() + self.widgets = [] + + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* (Removed: QComboBox and material grid element CSS) */ + """) + + self.setObjectName("central_panel_widget") + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # --- Add General Info Form at the top of the scroll area --- + self.general_widget = QWidget() + self.general_layout = QVBoxLayout(self.general_widget) + self.general_layout.setContentsMargins(10, 20, 10, 10) + self.general_layout.setSpacing(10) + + grid_layout = QGridLayout() + grid_layout.setHorizontalSpacing(10) + grid_layout.setVerticalSpacing(20) + + field_width = 200 # More compact width for input fields + + # 1. Demolition Cost rate as percentage to total construction cost + label = QLabel("Demolition Cost rate as\npercentage to total construction cost") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 0, 0, 1, 1) + demolition_widget = QWidget(self.general_widget) + demolition_layout = QHBoxLayout(demolition_widget) + demolition_layout.setContentsMargins(0,0,0,0) + demolition_layout.setSpacing(10) + demolition_input = QLineEdit() + self.widgets.append(demolition_input) + demolition_input.setAlignment(Qt.AlignmentFlag.AlignCenter) + demolition_input.setFixedWidth(field_width) + demolition_input.setText("10") + demolition_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + demolition_layout.addWidget(demolition_input) + demolition_layout.addWidget(QLabel("(%)")) + suggested_label = QLabel("Suggested") + suggested_label.setStyleSheet("color: #B3AEAE; font-size: 10px;") + demolition_layout.addWidget(suggested_label) + demolition_layout.addStretch(1) + grid_layout.addWidget(demolition_widget, 0, 1, 1, 1, alignment=Qt.AlignLeft) + + # 2. Scrap Value of Structural Steel + label = QLabel("Scrap Value of Structural Steel") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 1, 0, 1, 1) + scrap_value_widget = QWidget(self.general_widget) + scrap_value_layout = QHBoxLayout(scrap_value_widget) + scrap_value_layout.setContentsMargins(0,0,0,0) + scrap_value_layout.setSpacing(10) + scrap_value_input = QLineEdit() + self.widgets.append(scrap_value_input) + scrap_value_input.setAlignment(Qt.AlignmentFlag.AlignCenter) + scrap_value_input.setFixedWidth(field_width) + scrap_value_input.setText("50000") + scrap_value_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + scrap_value_layout.addWidget(scrap_value_input) + scrap_value_layout.addWidget(QLabel("(INR/MT)")) + suggested_label2 = QLabel("Suggested") + suggested_label2.setStyleSheet("color: #B3AEAE; font-size: 10px;") + scrap_value_layout.addWidget(suggested_label2) + scrap_value_layout.addStretch(1) + grid_layout.addWidget(scrap_value_widget, 1, 1, 1, 1, alignment=Qt.AlignLeft) + + # 3. Structural Steel Scrap + label = QLabel("Structural Steel Scrap") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 2, 0, 1, 1) + steel_scrap_widget = QWidget(self.general_widget) + steel_scrap_layout = QHBoxLayout(steel_scrap_widget) + steel_scrap_layout.setContentsMargins(0,0,0,0) + steel_scrap_layout.setSpacing(10) + steel_scrap_input = QLineEdit() + self.widgets.append(steel_scrap_input) + steel_scrap_input.setAlignment(Qt.AlignmentFlag.AlignCenter) + steel_scrap_input.setFixedWidth(field_width) + steel_scrap_input.setText("98") + steel_scrap_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + steel_scrap_layout.addWidget(steel_scrap_input) + steel_scrap_layout.addWidget(QLabel("(%)")) + suggested_label3 = QLabel("Suggested") + suggested_label3.setStyleSheet("color: #B3AEAE; font-size: 10px;") + steel_scrap_layout.addWidget(suggested_label3) + steel_scrap_layout.addStretch(1) + grid_layout.addWidget(steel_scrap_widget, 2, 1, 1, 1, alignment=Qt.AlignLeft) + + self.general_layout.addLayout(grid_layout) + self.general_layout.addStretch(1) + self.scroll_content_layout.addWidget(self.general_widget, alignment=Qt.AlignLeft) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + back_button.clicked.connect(lambda: self.back.emit(KEY_DEMOLITION_RECYCLE)) + self.button_h_layout.addWidget(back_button) + + calculate_button = QPushButton("Calculate") + calculate_button.clicked.connect(self.collect_data) + calculate_button.setObjectName("nav_button") + self.button_h_layout.addWidget(calculate_button) + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + + # --- Add a corner spacer to the scroll_content_layout --- + self.button_h_layout.addSpacerItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.scroll_content_layout.addSpacerItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) + + left_panel_vlayout.addWidget(self.scroll_area) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + + + def collect_data(self): + data = [] + for widget in self.widgets: + print(f"Collecting data from widget: {widget}") + if isinstance(widget, QComboBox): + value = widget.currentText() + elif isinstance(widget, QLineEdit): + value = widget.text() + data.append(value) + print("Collected Demolition and Recycling Data:") + print(data) + +#----------------Standalone-Test-Code-------------------------------- + +class MyMainWindow(QMainWindow): + def __init__(self): + super().__init__() + + self.setStyleSheet("border: none") + + self.central_widget = QWidget() + self.central_widget.setObjectName("central_widget") + self.setCentralWidget(self.central_widget) + + self.main_h_layout = QHBoxLayout(self.central_widget) + self.main_h_layout.addStretch(1) + + self.main_h_layout.addWidget(DemolitionAndRecyclingData(), 2) + + self.setWindowState(Qt.WindowMaximized) + + def close_widget(self): + self.closed.emit() + self.setParent(None) diff --git a/src/osbridgelcca/desktop_app/widgets/financial_data.py b/src/osbridgelcca/desktop_app/widgets/financial_data.py new file mode 100644 index 0000000..80a5ad2 --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/financial_data.py @@ -0,0 +1,331 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +from .utils.data import * +import sys +import os + +class FinancialData(QWidget): + closed = Signal() + next = Signal(str) + back = Signal(str) + def __init__(self, database, parent=None): + super().__init__() + self.database_manager = database + self.widgets = [] + + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* (Removed: QComboBox and material grid element CSS) */ + """) + + self.setObjectName("central_panel_widget") + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # --- Add General Info Form at the top of the scroll area --- + self.general_widget = QWidget() + self.general_layout = QVBoxLayout(self.general_widget) + self.general_layout.setContentsMargins(10, 20, 10, 10) + self.general_layout.setSpacing(10) + + # --- Financial Data Form --- + grid_layout = QGridLayout() + grid_layout.setHorizontalSpacing(10) + grid_layout.setVerticalSpacing(20) + field_width = 200 + + # 1. Real Discount Rate (with info icon) + label1_widget = QWidget() + label1_layout = QHBoxLayout(label1_widget) + label1_layout.setContentsMargins(0, 0, 0, 0) + label1_layout.setSpacing(4) + label1 = QLabel("Real Discount Rate") + label1.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + label1_layout.addWidget(label1) + info_icon = QLabel() + info_icon.setPixmap(QIcon("resources/info.svg").pixmap(16, 16)) + label1_layout.addWidget(info_icon) + label1_layout.addStretch(1) + input1 = QLineEdit() + self.widgets.append(input1) + input1.setAlignment(Qt.AlignmentFlag.AlignLeft) + input1.setFixedWidth(field_width) + input1.setText("4.2500") + input1.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + unit1 = QLabel("(%)") + suggested1 = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label1_widget, 0, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(input1, 0, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(unit1, 0, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(suggested1, 0, 3, alignment=Qt.AlignVCenter) + + # 2. Interest Rate + label2 = QLabel("Interest Rate") + label2.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + input2 = QLineEdit() + self.widgets.append(input2) + input2.setAlignment(Qt.AlignmentFlag.AlignLeft) + input2.setFixedWidth(field_width) + input2.setText("10") + input2.setStyleSheet(input1.styleSheet()) + unit2 = QLabel("(%)") + suggested2 = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label2, 1, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(input2, 1, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(unit2, 1, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(suggested2, 1, 3, alignment=Qt.AlignVCenter) + + # 3. Investment Ratio + label3 = QLabel("Investment Ratio") + label3.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + input3 = QLineEdit() + self.widgets.append(input3) + input3.setAlignment(Qt.AlignmentFlag.AlignLeft) + input3.setFixedWidth(field_width) + input3.setText("0.5000") + input3.setStyleSheet(input1.styleSheet()) + suggested3 = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label3, 2, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(input3, 2, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(QWidget(), 2, 2) # Empty cell for unit + grid_layout.addWidget(suggested3, 2, 3, alignment=Qt.AlignVCenter) + + # 4. Duration of Study + label4 = QLabel("Duration of Study") + label4.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + input4 = QLineEdit() + self.widgets.append(input4) + input4.setAlignment(Qt.AlignmentFlag.AlignLeft) + input4.setFixedWidth(field_width) + input4.setText("50 & 100") + input4.setStyleSheet(input1.styleSheet()) + unit4 = QLabel("(years)") + suggested4 = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label4, 3, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(input4, 3, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(unit4, 3, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(suggested4, 3, 3, alignment=Qt.AlignVCenter) + + # 5. Time for construction of Base Project + label5 = QLabel("Time for construction of Base Project") + label5.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + input5 = QLineEdit() + self.widgets.append(input5) + input5.setAlignment(Qt.AlignmentFlag.AlignLeft) + input5.setFixedWidth(field_width) + input5.setText("") + input5.setStyleSheet(input1.styleSheet()) + unit5 = QLabel("(years)") + grid_layout.addWidget(label5, 4, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(input5, 4, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(unit5, 4, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(QWidget(), 4, 3) # Empty cell for suggested + + self.general_layout.addLayout(grid_layout) + self.general_layout.addStretch(1) + self.scroll_content_layout.addWidget(self.general_widget, alignment=Qt.AlignLeft) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + back_button.clicked.connect(lambda: self.back.emit(KEY_FINANCIAL)) + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + next_button.clicked.connect(self.collect_data) + next_button.clicked.connect(lambda: self.next.emit(KEY_FINANCIAL)) + self.button_h_layout.addWidget(next_button) + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + + # --- Add a corner spacer to the scroll_content_layout --- + self.button_h_layout.addSpacerItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.scroll_content_layout.addSpacerItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) + + left_panel_vlayout.addWidget(self.scroll_area) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + + def collect_data(self): + data = [] + for widget in self.widgets: + print(f"Collecting data from widget: {widget}") + if isinstance(widget, QComboBox): + value = widget.currentText() + elif isinstance(widget, QLineEdit): + value = widget.text() + data.append(value) + print("Collected Demolition and Recycling Data:") + print(data) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(FinancialData(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/maintenance_repair_data.py b/src/osbridgelcca/desktop_app/widgets/maintenance_repair_data.py new file mode 100644 index 0000000..673bc4a --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/maintenance_repair_data.py @@ -0,0 +1,363 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +from .utils.data import * +import sys +import os + +class ComponentWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + + self.init_ui() + + + def init_ui(self): + self.component_first_scroll_content_layout = QVBoxLayout(self) # Set QVBoxLayout directly on self + self.component_first_scroll_content_layout.setContentsMargins(10, 10, 10, 10) + self.component_first_scroll_content_layout.setSpacing(10) + + + +class MaintenanceRepairData(QWidget): + closed = Signal() + next = Signal(str) + back = Signal(str) + def __init__(self, parent=None): + super().__init__() + self.widget = [] + + self.component_widgets = [] + + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* (Removed: QComboBox and material grid element CSS) */ + """) + + self.setObjectName("central_panel_widget") + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # --- Add General Info Form at the top of the scroll area --- + self.general_widget = QWidget() + self.general_layout = QVBoxLayout(self.general_widget) + self.general_layout.setContentsMargins(10, 20, 10, 10) + self.general_layout.setSpacing(10) + + grid_layout = QGridLayout() + grid_layout.setHorizontalSpacing(10) + grid_layout.setVerticalSpacing(20) + + field_width = 200 + + # 1. Periodic Maintenance Cost rate as percentage to total construction cost + label1 = QLabel("Periodic Maintenance Cost rate as\npercentage to total construction\ncost") + label1.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + pmc_input = QLineEdit() + self.widget.append(pmc_input) + pmc_input.setAlignment(Qt.AlignmentFlag.AlignTop) + pmc_input.setFixedWidth(field_width) + pmc_input.setText("0.555") + pmc_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + pmc_unit = QLabel("(%)") + pmc_suggested = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label1, 0, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(pmc_input, 0, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(pmc_unit, 0, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(pmc_suggested, 0, 3, alignment=Qt.AlignVCenter) + + # 2. Annual Routine Inspection cost rate as percentage of total construction cost + label2 = QLabel("Annual Routine Inspection cost rate\nas percentage of total construction\ncost") + label2.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + ari_input = QLineEdit() + self.widget.append(ari_input) + ari_input.setAlignment(Qt.AlignmentFlag.AlignTop) + ari_input.setFixedWidth(field_width) + ari_input.setText("1") + ari_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + ari_unit = QLabel("(%)") + ari_suggested = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label2, 1, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(ari_input, 1, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(ari_unit, 1, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(ari_suggested, 1, 3, alignment=Qt.AlignVCenter) + + # 3. Repair and Rehabilitation cost rate as percentage of total construction cost + label3 = QLabel("Repair and Rehabilitation cost rate as\npercentage of total construction cost") + label3.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + rr_input = QLineEdit() + self.widget.append(rr_input) + rr_input.setAlignment(Qt.AlignmentFlag.AlignTop) + rr_input.setFixedWidth(field_width) + rr_input.setText("10") + rr_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + rr_unit = QLabel("(%)") + rr_suggested = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label3, 2, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(rr_input, 2, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(rr_unit, 2, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(rr_suggested, 2, 3, alignment=Qt.AlignVCenter) + + # 4. Frequency of Periodic Maintenance + label4 = QLabel("Frequency of Periodic Maintenance") + label4.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + fpm_input = QLineEdit() + self.widget.append(fpm_input) + fpm_input.setAlignment(Qt.AlignmentFlag.AlignTop) + fpm_input.setFixedWidth(field_width) + fpm_input.setText("5") + fpm_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + fpm_unit = QLabel("(years)") + fpm_suggested = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label4, 3, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(fpm_input, 3, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(fpm_unit, 3, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(fpm_suggested, 3, 3, alignment=Qt.AlignVCenter) + + # 5. Frequency of Routine Inspection + label5 = QLabel("Frequency of Routine Inspection") + label5.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + fri_input = QLineEdit() + self.widget.append(fri_input) + fri_input.setAlignment(Qt.AlignmentFlag.AlignTop) + fri_input.setFixedWidth(field_width) + fri_input.setText("1") + fri_input.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + fri_unit = QLabel("(years)") + fri_suggested = QLabel("Suggested", parent=self.general_widget, styleSheet="color: #B3AEAE; font-size: 10px;") + grid_layout.addWidget(label5, 4, 0, alignment=Qt.AlignVCenter) + grid_layout.addWidget(fri_input, 4, 1, alignment=Qt.AlignVCenter) + grid_layout.addWidget(fri_unit, 4, 2, alignment=Qt.AlignVCenter) + grid_layout.addWidget(fri_suggested, 4, 3, alignment=Qt.AlignVCenter) + + self.general_layout.addLayout(grid_layout) + self.general_layout.addStretch(1) + self.scroll_content_layout.addWidget(self.general_widget, alignment=Qt.AlignLeft) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + back_button.clicked.connect(lambda: self.back.emit(KEY_MAINTAINANCE_REPAIR)) + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + next_button.clicked.connect(self.collect_data) + next_button.clicked.connect(lambda: self.next.emit(KEY_MAINTAINANCE_REPAIR)) + self.button_h_layout.addWidget(next_button) + + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + + # --- Add a corner spacer to the scroll_content_layout --- + self.button_h_layout.addSpacerItem(QSpacerItem(20, 0, QSizePolicy.Expanding, QSizePolicy.Minimum)) + self.scroll_content_layout.addSpacerItem(QSpacerItem(0, 20, QSizePolicy.Minimum, QSizePolicy.Expanding)) + + left_panel_vlayout.addWidget(self.scroll_area) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + + + def collect_data(self): + data = [] + for widget in self.widgets: + if isinstance(widget, QComboBox): + value = widget.currentText() + elif isinstance(widget, QLineEdit): + value = widget.text() + data.append(value) + print(data) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(MaintenanceRepairData(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/project_details_left_widget.py b/src/osbridgelcca/desktop_app/widgets/project_details_left_widget.py new file mode 100644 index 0000000..344c982 --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/project_details_left_widget.py @@ -0,0 +1,378 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, QPropertyAnimation, QEasingCurve, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QTextEdit, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy) +from PySide6.QtGui import QIcon, QTextDocument +import sys +import os +from widgets.structure_works_data.foundation_widget import Foundation +from widgets.structure_works_data.super_structure_widget import SuperStructure +from widgets.structure_works_data.sub_structure_widget import SubStructure +from widgets.structure_works_data.auxiliary_works_widget import AuxiliaryWorks +from PySide6.QtWidgets import QStackedWidget +from .utils.data import * + +class ProjectDetailsLeft(QWidget): + closed = Signal() + """ + The main application window that uses a custom title bar. + """ + def __init__(self, widget_map, parent): + super().__init__() + self.widget_map = widget_map + self.parent = parent + + self.current_selected_button = None + self.all_param_buttons = {} + + self.setObjectName("left_panel_widget") + self.setStyleSheet(""" + #left_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #left_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #left_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + QScrollArea { + border: 1px solid #000000; + background-color: transparent; + outline: none; + } + QScrollArea > QWidget { + background-color: transparent; + } + QScrollBar:vertical { + width: 12px; + margin: 0px; + border-radius: 6px; + } + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + QPushButton#top_button_left_panel { + background-color: #F0E6E6; + border: 1px solid #000000; + border-bottom: none; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #FDEFEF; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + } + #input_output_header { + background-color: #F0E6E6; + margin-top: 0px; + margin-bottom: 0px; + padding: 0px; + } + #input_output_header QLabel { + font-size: 15px; + font-weight: bold; + color: #333; + letter-spacing: 1px; + padding: 8px 0 8px 0; + background: transparent; + } + #input_output_separator { + background-color: #F0E6E6; + height: 10px; + border-bottom: 1px solid black; + } + QPushButton.category_button { + background-color: transparent; + border: none; + text-align: left; + padding: 6px 0px 6px 15px; + color: #000; + font-size: 13px; + } + QPushButton.category_button:hover { + background-color: #2A3F54; + color: #FFFFFF; + } + QPushButton.subcategory_button { + background-color: transparent; + border: none; + text-align: left; + padding: 4px 0px 4px 30px; + color: #000; + font-size: 12px; + } + QPushButton.subcategory_button:hover { + background-color: #2A3F54; + color: #FFFFFF; + } + QPushButton.category_button[selected="true"], + QPushButton.subcategory_button[selected="true"] { + background-color: #2A3F54; + color: #FFFFFF; + } + QLabel.output_label { + background-color: transparent; + color: #808080; + font-size: 12px; + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + top_h_layout_left_panel = QHBoxLayout() + top_button_left_panel = QPushButton("Project Details Window") + top_button_left_panel.setIcon(QIcon("resources/close.png")) + top_button_left_panel.setIconSize(QSize(13, 13)) + top_button_left_panel.setObjectName("top_button_left_panel") + top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + top_button_left_panel.clicked.connect(self.close_widget) + top_h_layout_left_panel.addWidget(top_button_left_panel, 2) + top_h_layout_left_panel.addStretch(1) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + bordered_spacer_widget = QWidget() + bordered_spacer_widget.setObjectName("bordered_spacer_widget") + bordered_spacer_widget.setFixedHeight(50) + bordered_spacer_widget.setStyleSheet(""" + #bordered_spacer_widget { + background-color: #F0E6E6; + border: 1px solid black; + border-bottom: none; + } + """) + left_panel_vlayout.addWidget(bordered_spacer_widget) + + scroll_area = QScrollArea() + scroll_area.setWidgetResizable(True) + scroll_content_widget = QWidget() + scroll_area.setWidget(scroll_content_widget) + scroll_content_layout = QVBoxLayout(scroll_content_widget) + scroll_content_layout.setContentsMargins(0, 0, 0, 0) + scroll_content_layout.setSpacing(0) + + input_param_header = QWidget() + input_param_header.setObjectName("input_param_header") + input_param_header.setStyleSheet(""" + #input_param_header { + background-color: #F0E6E6; + } + """) + input_param_header_layout = QVBoxLayout(input_param_header) + input_param_header_layout.setContentsMargins(0, 0, 0, 0) + input_param_header_layout.setSpacing(0) + input_param_label = QLabel("Input Parameters") + input_param_label.setAlignment(Qt.AlignCenter) + input_param_label.setStyleSheet("font-size: 15px; font-weight: bold; color: #333; padding: 8px 0 8px 0; background: transparent;border:none;") + input_param_header_layout.addWidget(input_param_label) + scroll_content_layout.addWidget(input_param_header) + + header_separator = QWidget() + header_separator.setObjectName("input_output_separator") + header_separator.setFixedHeight(2) + scroll_content_layout.addWidget(header_separator) + + # Define icons for the four states of category buttons + unselected_unexpanded_icon = QIcon("resources/play-button-arrowhead.png") + unselected_sub_icon = QIcon("resources/play-button-arrowhead.png") + + self.current_selected_button = None + self.all_param_buttons = {} + button_data = { + KEY_STRUCTURE_WORKS_DATA: [KEY_FOUNDATION, KEY_SUPERSTRUCTURE, KEY_SUBSTRUCTURE, KEY_AUXILIARY], + KEY_FINANCIAL: [], + KEY_CARBON_EMISSION: [KEY_CARBON_EMISSION_COST], + KEY_BRIDGE_TRAFFIC: [], + KEY_MAINTAINANCE_REPAIR: [], + KEY_DEMOLITION_RECYCLE: [] + } + for label, sublabels in button_data.items(): + btn = QPushButton(label) + btn.setProperty("class", "category_button") + btn.setProperty("selected", False) + btn.setProperty("expanded", False) + btn.setCursor(Qt.CursorShape.PointingHandCursor) + btn.setLayoutDirection(Qt.LeftToRight) + btn.setFocusPolicy(Qt.StrongFocus) + btn.setIcon(unselected_unexpanded_icon) + btn.setIconSize(QSize(10, 10)) + btn.clicked.connect(lambda checked ,b=btn ,name=label: self.show_structure_widget(name, b)) + scroll_content_layout.addWidget(btn) + self.all_param_buttons[label] = btn + if sublabels: + sub_widgets = [] + for sublabel in sublabels: + sub_btn = QPushButton(sublabel) + sub_btn.setProperty("class", "subcategory_button") + sub_btn.setProperty("selected", False) + sub_btn.setIcon(unselected_sub_icon) + sub_btn.setIconSize(QSize(10, 10)) + sub_btn.setFocusPolicy(Qt.StrongFocus) + sub_btn.setCursor(Qt.CursorShape.PointingHandCursor) + sub_btn.setLayoutDirection(Qt.LeftToRight) + sub_btn.setVisible(False) + sub_btn.clicked.connect(lambda checked ,b=sub_btn, name=sublabel: self.show_structure_widget(name,b)) + scroll_content_layout.addWidget(sub_btn) + sub_widgets.append(sub_btn) + self.all_param_buttons[sublabel] = sub_btn + + def make_toggle(button, sub_widgets): + def toggle(): + is_expanded = button.property("expanded") + for widget in sub_widgets: + widget.setVisible(not widget.isVisible()) + button.setProperty("expanded", not is_expanded) + self.update_button_icon(button) + self.handle_button_selection(button) + return toggle + btn.clicked.connect(lambda checked, b=btn, sw=sub_widgets: make_toggle(b, sw)()) + + input_output_middle_separator = QWidget() + input_output_middle_separator.setObjectName("input_output_separator") + input_output_middle_separator.setFixedHeight(2) + scroll_content_layout.addWidget(input_output_middle_separator) + + output_header = QWidget() + output_header.setObjectName("input_param_header") + output_header.setStyleSheet(""" + #input_param_header { + background-color: #F0E6E6; + } + """) + output_header_layout = QVBoxLayout(output_header) + output_header_layout.setContentsMargins(0, 0, 0, 0) + output_header_layout.setSpacing(0) + output_label = QLabel("Output") + output_label.setAlignment(Qt.AlignCenter) + output_label.setStyleSheet("font-size: 15px; font-weight: bold; color: #333; letter-spacing: 1px; padding: 8px 0 8px 0; background: transparent;") + output_header_layout.addWidget(output_label) + scroll_content_layout.addWidget(output_header) + + output_header_separator = QWidget() + output_header_separator.setObjectName("input_output_separator") + output_header_separator.setFixedHeight(2) + scroll_content_layout.addWidget(output_header_separator) + + output_labels_data = [ + "Initial Construction Cost", "Initial Carbon Emission Cost", "Time Cost", + "Road User Cost", "Carbon Emission due to Re-Routing", "Periodic Maintenance Costs", + "Maintenance Emission Costs", "Routine Inspection Costs", "Repair & Rehabilitation Costs", + "Reconstruction Costs", "Demolition & Disposal Cost", "Recycling Cost", "Total Life-Cycle Cost" + ] + output_layout = QVBoxLayout() + output_layout.setContentsMargins(10, 10, 10, 10) + for text in output_labels_data: + output_item = QLabel(text) + output_item.setProperty("class", "output_label") + output_layout.addWidget(output_item) + scroll_content_layout.addLayout(output_layout) + scroll_content_layout.addStretch(1) + + scroll_content_widget.setStyleSheet(""" + QPushButton[selected="true"] { + background-color: #2A3F54; + color: #FFFFFF; + } + QPushButton[selected="false"], QPushButton { + background-color: transparent; + color: #000000; + } + """) + left_panel_vlayout.addWidget(scroll_area) + + def handle_button_selection(self, button_clicked=None, button_name=None): + """ + Handles the visual selection state of buttons in the side panel. + """ + for b in self.all_param_buttons.values(): + b.setProperty("selected", False) + b.style().unpolish(b) + b.style().polish(b) + if b.property("class") == "subcategory_button": + b.setIcon(QIcon("resources/play-button-arrowhead.png")) + else: + self.update_button_icon(b) + + if button_name: + if b.text() == button_name: + button_clicked = b + button_clicked.click() + + button_clicked.setProperty("selected", True) + button_clicked.style().unpolish(button_clicked) + button_clicked.style().polish(button_clicked) + if button_clicked.property("class") == "subcategory_button": + button_clicked.setIcon(QIcon("resources/play-button-selected.png")) + else: + self.update_button_icon(button_clicked) + self.current_selected_button = button_clicked + + def update_button_icon(self, button): + """ + Updates the icon of a category button based on its selected and expanded state. + """ + if button.property("class") != "category_button": + return + is_selected = button.property("selected") + is_expanded = button.property("expanded") + if is_selected and is_expanded: + button.setIcon(QIcon("resources/arrow_down_selected.png")) + elif is_selected: + button.setIcon(QIcon("resources/play-button-selected.png")) + elif is_expanded: + button.setIcon(QIcon("resources/arrow_down.png")) + else: + button.setIcon(QIcon("resources/play-button-arrowhead.png")) + button.setIconSize(QSize(10, 10)) + + def show_structure_widget(self, name, btn): + self.parent.show_project_detail_widgets(name) + self.handle_button_selection(btn) + + def close_widget(self): + self.closed.emit() + self.setParent(None) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/project_details_right_widget.py b/src/osbridgelcca/desktop_app/widgets/project_details_right_widget.py new file mode 100644 index 0000000..419231c --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/project_details_right_widget.py @@ -0,0 +1,508 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, QSize, Qt, QPropertyAnimation, QEasingCurve, Signal +from PySide6.QtGui import (QIcon) +from PySide6.QtWidgets import (QHBoxLayout, QTextEdit, QScrollArea, QSpacerItem, QSizePolicy, + QPushButton, QWidget, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QComboBox) +from .utils.data import * +import sys + +class ProjectDetailsWidget(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("central_panel_widget") + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + QScrollArea { + border: 1px solid #000000; + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + } + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + QPushButton#general_info, QPushButton#parameter_button, QPushButton#output_button { + background-color: #FDEFEF; + border: 1px solid #000000; + text-align: left; + padding: 3px 10px; + color: #000000; + font-size: 14px; + } + QPushButton#general_info:hover, QPushButton#parameter_button:hover, QPushButton#output_button:hover { + background-color: #F0E6E6; + border: 1px solid #000000; + } + QPushButton#general_info:pressed, QPushButton#parameter_button:pressed, QPushButton#output_button:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + QPushButton#top_button_right_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_right_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_right_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + QPushButton#top_button_right_panel:hover QIcon { + color: red; + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + # --- Top Section --- + top_h_layout_left_panel = QHBoxLayout() + self.top_button_right_panel = QPushButton("Project Details Window ") + self.top_button_right_panel.setObjectName("top_button_right_panel") + self.top_button_right_panel.setIcon(QIcon("resources/close.png")) + self.top_button_right_panel.setIconSize(QSize(13, 13)) + self.top_button_right_panel.setLayoutDirection(Qt.RightToLeft) + self.top_button_right_panel.clicked.connect(self.close_widget) + top_h_layout_left_panel.addWidget(self.top_button_right_panel) + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + # --- Scroll Area Setup --- + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + scroll_content_widget = QWidget() + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + scroll_content_layout = QVBoxLayout(scroll_content_widget) + scroll_content_layout.addStretch(1) + + # --- General Information Section Button --- + self.general_info_button = QPushButton(" General Information") + self.general_info_button.setObjectName("general_info") + self.general_info_button.setCursor(Qt.PointingHandCursor) + self.unactive_arrow_icon = QIcon("resources/play-button-arrowhead.png") + self.active_arrow_icon = QIcon("resources/arrow_down.png") + self.general_info_button.setIcon(self.unactive_arrow_icon) + self.general_info_button.setIconSize(QSize(10, 10)) + self.general_info_button.setLayoutDirection(Qt.LeftToRight) + scroll_content_layout.insertWidget(0, self.general_info_button) + + # --- General Information Form Widget --- + self.general_button_active = False + self.general_widget = QWidget() + self.general_widget.setObjectName("general_info_form_widget") + self.general_widget.setStyleSheet(f""" + #general_info_form_widget QLineEdit, + #general_info_form_widget QTextEdit, + #general_info_form_widget QComboBox {{ + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background-color: #FFFFFF; + }} + #general_info_form_widget QTextEdit::viewport {{ + background-color: #FFFFFF; + }} + #general_info_form_widget QLineEdit:focus, + #general_info_form_widget QTextEdit:focus, + #general_info_form_widget QComboBox:focus {{ + border: 1px solid #DDDCE0; + background-color: #FFFFFF; + }} + #general_info_form_widget QTextEdit:focus::viewport {{ + background-color: #FFFFFF; + }} + #left_label {{ + margin-right: 20px; + }} + #info_button {{ + margin-right: {self.general_info_button.width()//2}px; + }} + """) + grid_layout = QGridLayout(self.general_widget) + grid_layout.setContentsMargins(30, 20, 30, 20) + grid_layout.setHorizontalSpacing(10) + grid_layout.setVerticalSpacing(10) + # Company Name + label = QLabel("Company Name") + label.setObjectName("left_label") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 0, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + grid_layout.addWidget(input_widget, 0, 1, 1, 2) + # Project Title + label = QLabel("Project Title") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 1, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + grid_layout.addWidget(input_widget, 1, 1, 1, 2) + # Project Description + label = QLabel("Project Description") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 2, 0, 1, 1) + input_widget = QTextEdit(self.general_widget) + input_widget.setPlaceholderText("Enter project description here...") + grid_layout.addWidget(input_widget, 2, 1, 1, 2) + # Name of Valuer + label = QLabel("Name of Valuer") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 3, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 3, 1, 1, 1) + # Job Number + label = QLabel("Job Number") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 4, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 4, 1, 1, 1) + # Client + label = QLabel("Client") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 5, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 5, 1, 1, 1) + # Country ComboBox + label = QLabel("Country") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 6, 0, 1, 1) + valuer_combo = QComboBox(self.general_widget) + valuer_combo.addItem("India") + valuer_combo.addItem("USA") + valuer_combo.addItem("UK") + valuer_combo.setStyleSheet(""" + QComboBox{ + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 18px; + height: 18px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + """) + grid_layout.addWidget(valuer_combo, 6, 1, 1, 1) + info_icon = QLabel("ⓘ") + info_icon.setStyleSheet("color: grey; font-size: 14px;") + info_icon.setObjectName("info_button") + info_icon.setToolTip("Social Cost of Carbon varies as per selected region") + info_icon.setCursor(Qt.PointingHandCursor) + info_icon.setAlignment(Qt.AlignRight) + grid_layout.addWidget(info_icon, 6, 2, 1, 1) + # Base Year + label = QLabel("Base Year") + label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) + grid_layout.addWidget(label, 7, 0, 1, 1) + input_widget = QLineEdit(self.general_widget) + input_widget.setStyleSheet(""" + QLineEdit { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + """) + grid_layout.addWidget(input_widget, 7, 1, 1, 1) + scroll_content_layout.insertWidget(1, self.general_widget) + self.general_widget.show() + self.general_widget.adjustSize() + self.original_general_widget_height = self.general_widget.sizeHint().height() + self.general_widget.hide() + self.general_widget_animation = QPropertyAnimation(self.general_widget, b"maximumHeight") + self.general_widget_animation.setDuration(300) + self.general_widget_animation.setEasingCurve(QEasingCurve.InOutQuad) + self.general_widget.setMaximumHeight(0) + self.general_widget.hide() + # Input Parameters Section + self.input_param_unactive_icon = QIcon("resources/play-button-arrowhead.png") + self.input_param_active_icon = QIcon("resources/arrow_down.png") + self.input_param_button_css_inactive = """ + QPushButton#parameter_button { + background-color: transparent; + border: 1px solid #000000; + text-align: left; + padding: 3px 10px; + color: #000000; + font-size: 16px; + } + QPushButton#parameter_button:hover { + background-color: #F0E6E6; + } + QPushButton#parameter_button:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + """ + self.input_param_button_css_active = """ + QPushButton#parameter_button { + background-color: transparent; + text-align: left; + padding: 3px 10px; + color: #000000; + font-size: 16px; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + } + QPushButton#parameter_button:hover { + background-color: #F0E6E6; + } + QPushButton#parameter_button:pressed { + background-color: #FFF3F3; + } + """ + self.input_param_widget = QWidget() + self.input_param_widget.setObjectName("input_param_widget") + self.input_param_widget.setStyleSheet(""" + #input_param_widget{ + background-color: #FDEFEF; + border: 1px solid #000000; + } + """) + self.input_param_layout = QVBoxLayout(self.input_param_widget) + self.input_param_layout.setContentsMargins(0, 0, 0, 0) + self.input_param_layout.setSpacing(0) + self.input_param_button = QPushButton(" Input Parameters") + self.input_param_button.setStyleSheet(self.input_param_button_css_inactive) + self.input_param_button.setObjectName("parameter_button") + self.input_param_button.setCursor(Qt.PointingHandCursor) + self.input_param_button.clicked.connect(self.input_button_toggle) + self.input_param_button.setIcon(self.input_param_unactive_icon) + self.input_param_button.setIconSize(QSize(10, 10)) + self.input_param_button.setLayoutDirection(Qt.LeftToRight) + self.input_param_layout.addWidget(self.input_param_button) + self.input_param_option_widget = QWidget() + self.input_param_option_widget.hide() + self.input_option_active = False + self.input_param_option_widget.setObjectName("input_param_option_widget") + self.input_param_option_layout = QVBoxLayout(self.input_param_option_widget) + self.input_param_option_layout.setContentsMargins(8, 8, 8, 8) + self.input_param_option_layout.setSpacing(2) + button_labels = [ + KEY_STRUCTURE_WORKS_DATA, + KEY_FINANCIAL, + KEY_CARBON_EMISSION, + KEY_BRIDGE_TRAFFIC, + KEY_MAINTAINANCE_REPAIR, + KEY_DEMOLITION_RECYCLE + ] + self.selected_icon = QIcon("resources/selected_icon.png") + self.param_buttons = [] + for label in button_labels: + btn = QPushButton(f" {label}") + btn.setObjectName("parameter_button") + btn.setIcon(QIcon("resources/play-button-arrowhead.png")) + btn.setIconSize(QSize(10, 10)) + btn.setCursor(Qt.PointingHandCursor) + btn.setLayoutDirection(Qt.LeftToRight) + btn.setStyleSheet(""" + QPushButton#parameter_button { + background-color: none; + border: none; + text-align: left; + padding: 2px 10px; + color: #000; + font-size: 12px; + margin-left: 40px + } + QPushButton#parameter_button:hover { + background-color: #F0E6E6; + } + """) + btn.clicked.connect(lambda checked, b=btn: self.select_param_button(b)) + self.input_param_option_layout.addWidget(btn) + self.param_buttons.append(btn) + self.input_param_layout.addWidget(self.input_param_option_widget) + scroll_content_layout.insertWidget(2, self.input_param_widget) + self.output_button = QPushButton(" Outputs") + self.output_button.setObjectName("output_button") + self.output_button.setIcon(QIcon("resources/play-button-arrowhead.png")) + self.output_button.setIconSize(QSize(10, 10)) + self.output_button.setLayoutDirection(Qt.LeftToRight) + scroll_content_layout.insertWidget(3, self.output_button) + left_panel_vlayout.addWidget(self.scroll_area) + self.bottom_widget = QWidget() + self.bottom_widget.setObjectName("bottom_widget") + self.bottom_widget.setStyleSheet(""" + #bottom_widget { + background-color: #F0E6E6; + border-left: 1px solid #000000; + border-bottom: 1px solid #000000; + border-right: 1px solid #000000; + } + """) + left_panel_vlayout.addWidget(self.bottom_widget) + self.general_info_button.clicked.connect(self.expand_general_area) + + def expand_general_area(self): + try: + self.general_widget_animation.finished.disconnect() + except RuntimeError: + pass + if self.general_button_active: + self.general_info_button.setIcon(self.unactive_arrow_icon) + self.general_widget_animation.setStartValue(self.general_widget.height()) + self.general_widget_animation.setEndValue(0) + self.general_widget_animation.finished.connect(self.general_widget.hide) + self.general_button_active = False + else: + self.general_info_button.setIcon(self.active_arrow_icon) + self.general_widget.show() + self.general_widget_animation.setStartValue(0) + self.general_widget_animation.setEndValue(self.original_general_widget_height) + self.general_widget_animation.finished.connect(lambda: self.general_widget.setMaximumHeight(16777215)) + self.general_button_active = True + self.general_widget_animation.start() + + def input_button_toggle(self): + if self.input_option_active: + self.input_param_option_widget.hide() + self.input_param_button.setStyleSheet(self.input_param_button_css_inactive) + self.input_param_button.setIcon(self.input_param_unactive_icon) + self.input_option_active = False + else: + self.input_param_option_widget.show() + self.input_option_active = True + self.input_param_button.setStyleSheet(self.input_param_button_css_active) + self.input_param_button.setIcon(self.input_param_active_icon) + + def select_param_button(self, selected_btn): + for btn in self.param_buttons: + if btn == selected_btn: + btn.setIcon(self.selected_icon) + else: + btn.setIcon(QIcon("resources/play-button-arrowhead.png")) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(ProjectDetailsWidget(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/results_widget.py b/src/osbridgelcca/desktop_app/widgets/results_widget.py new file mode 100644 index 0000000..cd29897 --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/results_widget.py @@ -0,0 +1,2117 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, QSize, Qt, QPropertyAnimation, QEasingCurve, Signal +from PySide6.QtGui import (QIcon) +from PySide6.QtWebEngineWidgets import QWebEngineView + +from PySide6.QtWidgets import (QHBoxLayout, QTextEdit, QScrollArea, QSpacerItem, QSizePolicy, + QPushButton, QWidget, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QComboBox) +import sys + +# using the d3js graphing library to plot the graph, it is downloaded locally and saved in the same directory as that of this graph script +with open(r"dependencies/d3js.js", "r", encoding="utf-8") as f: + d3_js = f.read() + +class ResultsWidget(QWidget): + closed = Signal() + def __init__(self, parent=None): + super().__init__(parent) + self.setObjectName("central_panel_widget") + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + QScrollArea { + border: 1px solid #000000; + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + } + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + QPushButton#top_button_right_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_right_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_right_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + QPushButton#top_button_right_panel:hover QIcon { + color: red; + } + + + QPushButton#top_button_right_panel_pressed { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + border-bottom: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_right_panel_pressed:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_right_panel_pressed:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + QPushButton#top_button_right_panel_pressed:hover QIcon { + color: red; + } + + /* Results cards */ + QWidget#result_card { + background-color: #FDEFEF; + border: 1px solid #000000; + border-radius: 8px; + height: 150px; + } + QPushButton#close_card_button { + border: none; + background: transparent; + padding: 2px; + } + QPushButton#close_card_button:hover { + background: rgba(0,0,0,0.05); + border-radius: 4px; + } + + /* Bottom navigation buttons */ + QPushButton#bottom_nav_button { + background-color: #FFFFFF; + border: 1px solid #E0E0E0; + border-radius: 6px; + color: #333333; + font-size: 14px; + font-weight: 500; + } + QPushButton#bottom_nav_button:hover { + background-color: #F8F8F8; + border-color: #C0C0C0; + } + QPushButton#bottom_nav_button:pressed { + background-color: #F0F0F0; + border-color: #A0A0A0; + } + + """) + + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + self.pressed=0 + self.current_page = 0 # 0: initial view, 1: pie chart, 2: 100-year graphs, 3: bubble graphs + self.max_page = 3 + + # --- Top Section --- + top_h_layout_left_panel = QHBoxLayout() + self.top_button_right_panel = QPushButton("Data Window ") + self.top_button_right_panel.setObjectName("top_button_right_panel") + self.top_button_right_panel.setIcon(QIcon("resources/close.png")) + self.top_button_right_panel.setIconSize(QSize(13, 13)) + self.top_button_right_panel.setLayoutDirection(Qt.LayoutDirection.RightToLeft) + self.top_button_right_panel.clicked.connect(self.close_widget) + + self.top_button_left_panel = QPushButton("Results Window ") + self.top_button_left_panel.setObjectName("top_button_right_panel") + self.top_button_left_panel.setIcon(QIcon("resources/close.png")) + self.top_button_left_panel.setIconSize(QSize(13, 13)) + self.top_button_left_panel.setLayoutDirection(Qt.LayoutDirection.RightToLeft) + self.top_button_left_panel.clicked.connect(self.switch_to_results_widget) + + top_h_layout_left_panel.addWidget(self.top_button_right_panel) + top_h_layout_left_panel.addWidget(self.top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + # --- Main Content Area --- + self.main_content_widget = QWidget() + self.main_content_widget.setObjectName("main_content_widget") + self.main_content_widget.setStyleSheet(""" + #main_content_widget { + background-color: #FDEFEF; + border: 1px solid #000000; + border-top: none; + } + """) + + # Create scroll area for the main content + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded) + self.scroll_area.setStyleSheet(""" + QScrollArea { + border: none; + background-color: transparent; + } + QScrollBar:vertical { + background: #F0F0F0; + width: 12px; + border-radius: 6px; + } + QScrollBar::handle:vertical { + background: #C0C0C0; + border-radius: 6px; + min-height: 20px; + } + QScrollBar::handle:vertical:hover { + background: #A0A0A0; + } + QScrollBar:horizontal { + background: #F0F0F0; + height: 12px; + border-radius: 6px; + } + QScrollBar::handle:horizontal { + background: #C0C0C0; + border-radius: 6px; + min-width: 20px; + } + QScrollBar::handle:horizontal:hover { + background: #A0A0A0; + } + """) + + # Create scrollable content widget + self.scroll_content_widget = QWidget() + self.scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_content_widget.setStyleSheet(""" + #scroll_content_widget { + background-color: #FDEFEF; + } + """) + + # Add a simple label to show this is the results area + content_layout = QVBoxLayout(self.scroll_content_widget) + content_layout.setContentsMargins(20, 20, 20, 20) + content_layout.setSpacing(20) + + self.welcome_label = QLabel("Results Area") + self.welcome_label.setStyleSheet(""" + QLabel { + font-size: 18px; + font-weight: bold; + color: #333333; + padding: 20px; + } + """) + self.welcome_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + content_layout.addWidget(self.welcome_label) + + # Container with three closable cards (initially hidden) + self.cards_container = QWidget() + self.cards_container.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + + # Create a flow layout for responsive card arrangement + self.cards_layout = QHBoxLayout(self.cards_container) + self.cards_layout.setContentsMargins(0, 0, 0, 0) + self.cards_layout.setSpacing(20) + self.cards_layout.setSizeConstraint(QHBoxLayout.SizeConstraint.SetNoConstraint) + + titles = [ + "Economic cost distribution across various stages for bridges for 50 years", + "Social cost distribution across stages for PSC bridges for 50 years", + "Environmental cost distribution across stages for PSC bridges for 50 years", + ] + + # Create and add cards with debug information + self.cards_list = [] # Store references to cards + for i, title in enumerate(titles): + card = self._create_result_card(title, [30, 45, 15, 10]) + self.cards_list.append(card) + self.cards_layout.addWidget(card) + print(f"Created card {i}: {title[:30]}...") + print(f"Card {i} visible: {card.isVisible()}") + print(f"Card {i} parent: {card.parent()}") + + self.cards_container.setVisible(False) + content_layout.addWidget(self.cards_container) + + print(f"Cards container created with {len(self.cards_list)} cards") + print(f"Cards container visible: {self.cards_container.isVisible()}") + print(f"Cards container parent: {self.cards_container.parent()}") + + # Shared legend below all cards + self.legend_widget = self._build_legend_widget() + self.legend_widget.setVisible(False) + content_layout.addWidget(self.legend_widget, 0, Qt.AlignmentFlag.AlignHCenter) + + # Bar graph below legend + self.bar_graph_widget = self._build_bar_graph() + self.bar_graph_widget.setVisible(False) + content_layout.addWidget(self.bar_graph_widget, 0, Qt.AlignmentFlag.AlignHCenter) + + # Pie chart widget (for page 1) + self.pie_chart_widget = self._build_pie_chart() + self.pie_chart_widget.setVisible(False) + content_layout.addWidget(self.pie_chart_widget, 0, Qt.AlignmentFlag.AlignHCenter) + + # 100-year widgets (for page 2) + self.cards_container_100 = QWidget() + self.cards_container_100.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + self.cards_layout_100 = QHBoxLayout(self.cards_container_100) + self.cards_layout_100.setContentsMargins(0, 0, 0, 0) + self.cards_layout_100.setSpacing(20) + self.cards_layout_100.setSizeConstraint(QHBoxLayout.SizeConstraint.SetNoConstraint) + + titles_100 = [ + "Economic cost distribution across various stages for bridges for 100 years", + "Social cost distribution across stages for PSC bridges for 100 years", + "Environmental cost distribution across stages for PSC bridges for 100 years", + ] + + self.cards_list_100 = [] + for i, title in enumerate(titles_100): + card = self._create_result_card(title, [30, 45, 15, 10]) + self.cards_list_100.append(card) + self.cards_layout_100.addWidget(card) + + self.cards_container_100.setVisible(False) + content_layout.addWidget(self.cards_container_100) + + self.legend_widget_100 = self._build_legend_widget() + self.legend_widget_100.setVisible(False) + content_layout.addWidget(self.legend_widget_100, 0, Qt.AlignmentFlag.AlignHCenter) + + self.bar_graph_widget_100 = self._build_bar_graph_100() + self.bar_graph_widget_100.setVisible(False) + content_layout.addWidget(self.bar_graph_widget_100, 0, Qt.AlignmentFlag.AlignHCenter) + + # Bubble graph widgets (for page 3) + self.bubble_cards_container = QWidget() + self.bubble_cards_container.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + self.bubble_cards_layout = QHBoxLayout(self.bubble_cards_container) + self.bubble_cards_layout.setContentsMargins(0, 0, 0, 0) + self.bubble_cards_layout.setSpacing(20) + + bubble_titles = [ + "Carbon Emission Cost Through-out Life-Cycle Stages for 50 years", + "Carbon Emission Cost Through-out Life-Cycle Stages for 100 years", + ] + + self.bubble_cards_list = [] + for i, title in enumerate(bubble_titles): + card = self._create_bubble_card(title) + self.bubble_cards_list.append(card) + self.bubble_cards_layout.addWidget(card) + + self.bubble_cards_container.setVisible(False) + content_layout.addWidget(self.bubble_cards_container) + + # Bottom navigation buttons + self.bottom_buttons_widget = self._build_bottom_buttons() + self.bottom_buttons_widget.setVisible(False) + content_layout.addWidget(self.bottom_buttons_widget, 0, Qt.AlignmentFlag.AlignHCenter) + + # Set the scroll content widget to the scroll area + self.scroll_area.setWidget(self.scroll_content_widget) + + # Add scroll area to main layout + left_panel_vlayout.addWidget(self.scroll_area) + + # Connect resize event to handle dynamic layout + self.resizeEvent = self.handle_resize + + + + + def close_widget(self): + self.closed.emit() + self.setParent(None) + + def _build_radial_bar_html(self, percentages): + # Stage labels and color palette copied from the original script + stage_label = ['Initial Stage', 'Use Stage', 'End of Life Stage', 'Beyond Life Stage'] + colors = ['#273B5C', '#2E5743', '#996515', '#36454F'] + + # Ensure exactly four entries + values = (percentages + [0, 0, 0, 0])[:4] + + # Size tuned to fit the card nicely with proper height for full visibility + width = 360 + height = 220 + body_bg = "#FDEFEF" + + html = f""" + + + + + + + + +
+ + + + +""" + return html + + def _build_legend_widget(self) -> QWidget: + stage_label = ['Initial Stage', 'Use Stage', 'End of Life Stage', 'Beyond Life Stage'] + colors = ['#273B5C', '#2E5743', '#996515', '#36454F'] + legend = QWidget() + layout = QHBoxLayout(legend) + layout.setContentsMargins(8, 0, 8, 6) + layout.setSpacing(16) + for name, color in zip(stage_label, colors): + item = QWidget() + item_layout = QHBoxLayout(item) + item_layout.setContentsMargins(0, 0, 0, 0) + item_layout.setSpacing(6) + swatch = QWidget() + swatch.setFixedSize(14, 14) + swatch.setStyleSheet(f"background:{color}; border-radius:3px;") + label = QLabel(name) + item_layout.addWidget(swatch) + item_layout.addWidget(label) + layout.addWidget(item) + layout.addStretch(1) + return legend + + def _build_bottom_buttons(self) -> QWidget: + """Create bottom navigation buttons widget""" + buttons_widget = QWidget() + buttons_layout = QHBoxLayout(buttons_widget) + buttons_layout.setContentsMargins(20, 15, 20, 15) + buttons_layout.setSpacing(15) + + # Back button + self.back_button = QPushButton("Back") + self.back_button.setObjectName("bottom_nav_button") + self.back_button.setFixedSize(80, 35) + self.back_button.setCursor(Qt.CursorShape.PointingHandCursor) + self.back_button.clicked.connect(self.go_back) + + # Next button + self.next_button = QPushButton("Next") + self.next_button.setObjectName("bottom_nav_button") + self.next_button.setFixedSize(80, 35) + self.next_button.setCursor(Qt.CursorShape.PointingHandCursor) + self.next_button.clicked.connect(self.go_next) + + # Add buttons to layout + buttons_layout.addStretch(1) + buttons_layout.addWidget(self.back_button) + buttons_layout.addWidget(self.next_button) + buttons_layout.addStretch(1) + + return buttons_widget + + def _build_bar_graph(self) -> QWidget: + """Create bar graph widget showing life-cycle costs""" + bar_graph_widget = QWidget() + bar_graph_layout = QVBoxLayout(bar_graph_widget) + bar_graph_layout.setContentsMargins(20, 15, 20, 15) + bar_graph_layout.setSpacing(10) + + # Title for the bar graph + title_label = QLabel("Life-Cycle Costs for 50 years") + title_label.setStyleSheet(""" + QLabel { + font-size: 16px; + font-weight: bold; + color: #333333; + text-align: center; + } + """) + title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + bar_graph_layout.addWidget(title_label) + + # Create the bar graph using QWebEngineView + graph_view = QWebEngineView() + graph_view.setHtml(self._build_bar_graph_html()) + graph_view.setZoomFactor(0.8) + graph_view.setMinimumHeight(300) + graph_view.setMaximumHeight(400) + graph_view.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + graph_view.setMinimumWidth(500) # Ensure minimum width for readability + bar_graph_layout.addWidget(graph_view) + + # Store reference for resize handling + self.bar_graph_view = graph_view + + return bar_graph_widget + + def _build_bar_graph_html(self) -> str: + """Generate HTML for the horizontal bar graph using exact data from bargraph.py""" + # Extract names and costs from the original bargraph.py data + name = [ + "Initial Construction Cost", "Initial Carbon Emission Cost", "Time Cost", + "Road User Cost", "Periodic Maintenance Costs", "Maintenance Emission Costs", + "Routine Inspection Costs", "Repair & Rehabilitation Costs", "Reconstruction Costs", + "Demolition & Disposal Cost", "Recycling Cost", "Total Life-Cycle Cost" + ] + cost = [ + 61.83, 29.03, 0.0, 123.93, 1.44, 0.0, 0.0, 0.0, 0.0, 18.69, 0.0, 0.0 + ] + + # Clean data (include all values except total, but handle zeros specially) + temp_name = [] + temp_cost = [] + for i in range(len(cost)): + if i < 11: # Exclude only the total, include all other values including zeros + temp_name.append(name[i]) + temp_cost.append(cost[i]) + + # Display adjustment for zero values (from original code) + temp_cost_display = [] + cost_list = temp_cost.copy() + if cost_list: + # Find minimum non-zero value for display adjustment + non_zero_costs = [c for c in cost_list if c > 0] + if non_zero_costs: + min_non_zero = min(non_zero_costs) + for j in temp_cost: + if j == 0.0: + temp_cost_display.append(min_non_zero / 2) + else: + temp_cost_display.append(j) + else: + temp_cost_display = temp_cost + else: + temp_cost_display = temp_cost + + # Color list from original bargraph.py + color_list = ['#638B48', '#6F6F6F', '#638B48', '#E09365', '#6F6F6F', '#638B48', + '#6F6F6F', '#638B48', '#638B48', '#638B48', '#638B48', '#ffffff'] + + # Calculate total cost + total_cost = sum(temp_cost) + + # Create data array for D3 + data = [] + for i, (n, c, col) in enumerate(zip(temp_name, temp_cost_display, color_list[:len(temp_name)])): + data.append({"name": n, "value": c, "color": col}) + + # Debug: Print the data being processed + print(f"Original cost data: {cost}") + print(f"Filtered names: {temp_name}") + print(f"Filtered costs: {temp_cost}") + print(f"Display costs: {temp_cost_display}") + print(f"Number of bars to display: {len(temp_name)}") + print(f"Data array for D3: {data}") + + html = f""" + + + + + D3.js Interactive Horizontal Bar Graph + + + + +

Life Cycle Cost for 50 Years

+
+ + + + + + +""" + return html + + def handle_resize(self, event): + """Handle window resize events to make layout more responsive""" + # Get the current width of the widget + current_width = self.width() + + # Only adjust card layout if cards are visible and we have the necessary attributes + if (hasattr(self, 'cards_container') and + hasattr(self, 'cards_layout') and + self.cards_container.isVisible() and + self.cards_container.layout() is not None): + + current_layout = self.cards_container.layout() + + if current_width < 1000: # If window is narrow + # Stack cards vertically + if isinstance(current_layout, QHBoxLayout): + # Convert to vertical layout + new_layout = QVBoxLayout() + new_layout.setContentsMargins(0, 0, 0, 0) + new_layout.setSpacing(20) + + # Move all cards to new layout + while current_layout.count(): + item = current_layout.takeAt(0) + if item.widget(): + new_layout.addWidget(item.widget()) + + # Replace the layout + self.cards_container.setLayout(new_layout) + self.cards_layout = new_layout + + # Ensure all cards remain visible after layout change + for i in range(new_layout.count()): + item = new_layout.itemAt(i) + if item and item.widget(): + item.widget().setVisible(True) + + else: # If window is wide + # Arrange cards horizontally + if isinstance(current_layout, QVBoxLayout): + # Convert to horizontal layout + new_layout = QHBoxLayout() + new_layout.setContentsMargins(0, 0, 0, 0) + new_layout.setSpacing(20) + new_layout.setSizeConstraint(QHBoxLayout.SizeConstraint.SetNoConstraint) + + # Move all cards to new layout + while current_layout.count(): + item = current_layout.takeAt(0) + if item.widget(): + new_layout.addWidget(item.widget()) + + # Replace the layout + self.cards_container.setLayout(new_layout) + self.cards_layout = new_layout + + # Ensure all cards remain visible after layout change + for i in range(new_layout.count()): + item = new_layout.itemAt(i) + if item and item.widget(): + item.widget().setVisible(True) + + # Call the parent's resize event handler + super().resizeEvent(event) + + # After resize, ensure cards remain visible + if hasattr(self, 'cards_container') and self.cards_container.isVisible(): + self.ensure_cards_visible() + + def ensure_cards_visible(self): + """Ensure all cards are visible and properly displayed""" + print("=== ensure_cards_visible called ===") + + if hasattr(self, 'cards_container'): + print(f"Cards container exists: {self.cards_container}") + print(f"Cards container visible: {self.cards_container.isVisible()}") + print(f"Cards container parent: {self.cards_container.parent()}") + + # Make sure the cards container itself is visible + self.cards_container.setVisible(True) + print(f"Cards container set visible: {self.cards_container.isVisible()}") + + # Check cards list if available + if hasattr(self, 'cards_list'): + print(f"Cards list has {len(self.cards_list)} cards") + for i, card in enumerate(self.cards_list): + print(f"Card {i} in list - visible: {card.isVisible()}, parent: {card.parent()}") + card.setVisible(True) + print(f"Card {i} set visible: {card.isVisible()}") + + # Ensure all child cards in layout are visible + if hasattr(self, 'cards_layout'): + layout = self.cards_layout + print(f"Layout has {layout.count()} items") + for i in range(layout.count()): + item = layout.itemAt(i) + if item and item.widget(): + widget = item.widget() + print(f"Layout item {i}: {widget}, visible: {widget.isVisible()}") + widget.setVisible(True) + print(f"Layout item {i} set visible: {widget.isVisible()}") + else: + print("ERROR: cards_container does not exist!") + + print("=== ensure_cards_visible finished ===") + + def _create_result_card(self, title: str, percentages=None) -> QWidget: + if percentages is None: + percentages = [30, 45, 15, 10] + card = QWidget() + card.setObjectName("result_card") + card.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + card.setMinimumWidth(300) + card.setMaximumHeight(280) + + print(f"Creating card with title: {title[:30]}...") + print(f"Card object: {card}") + print(f"Card visible by default: {card.isVisible()}") + + card_layout = QVBoxLayout(card) + card_layout.setContentsMargins(10, 8, 10, 10) + card_layout.setSpacing(6) + + header_layout = QHBoxLayout() + title_label = QLabel(title) + title_label.setTextFormat(Qt.TextFormat.RichText) + title_label.setWordWrap(True) + header_layout.addWidget(title_label) + header_layout.addStretch(1) + + close_btn = QPushButton() + close_btn.setObjectName("close_card_button") + close_btn.setIcon(QIcon("resources/close.png")) + close_btn.setIconSize(QSize(13, 13)) + header_layout.addWidget(close_btn, 0, Qt.AlignmentFlag.AlignRight) + + def remove_card(): + card.setParent(None) + card.deleteLater() + close_btn.clicked.connect(remove_card) + + card_layout.addLayout(header_layout) + + # Embed radial bar graph inside the card using QWebEngineView + graph = QWebEngineView(card) + graph.setHtml(self._build_radial_bar_html(percentages)) + graph.setZoomFactor(0.6) + graph.setMinimumHeight(180) + card_layout.addWidget(graph) + + print(f"Card created successfully with graph. Final card visible: {card.isVisible()}") + print(f"Card size: {card.size()}, Card geometry: {card.geometry()}") + + return card + + def switch_to_results_widget(self): + self.main_content_widget.setStyleSheet(""" + #main_content_widget { + background-color: #FDEFEF; + border: 1px solid #000000; + border-top: none; + } + """) + + if self.pressed==0: + print("=== Switching to results widget ===") + self.top_button_right_panel.setObjectName("top_button_right_panel_pressed") + self.welcome_label.setVisible(False) + + # Initialize navigation system + self.current_page = 0 + self.bottom_buttons_widget.setVisible(True) + self.update_page_display() + + self.pressed=1 + print("=== Finished switching to results widget ===") + else: + print("=== Switching back to welcome ===") + self.top_button_right_panel.setObjectName("top_button_right_panel") + self.hide_all_page_widgets() + self.bottom_buttons_widget.setVisible(False) + self.welcome_label.setVisible(True) + self.pressed=0 + print("=== Finished switching back to welcome ===") + + def go_next(self): + """Handle next button click""" + if self.current_page < self.max_page: + self.current_page += 1 + self.update_page_display() + + def go_back(self): + """Handle back button click""" + if self.current_page > 0: + self.current_page -= 1 + self.update_page_display() + + def update_page_display(self): + """Update the display based on current page""" + # Hide all widgets first + self.hide_all_page_widgets() + + # Update button states + self.back_button.setEnabled(self.current_page > 0) + self.next_button.setEnabled(self.current_page < self.max_page) + + if self.current_page == 0: + # Show initial 50-year graphs + self.cards_container.setVisible(True) + self.legend_widget.setVisible(True) + self.bar_graph_widget.setVisible(True) + self.ensure_cards_visible() + elif self.current_page == 1: + # Show pie chart with the original 3 radial cards + self.cards_container.setVisible(True) + self.legend_widget.setVisible(True) + self.pie_chart_widget.setVisible(True) + self.ensure_cards_visible() + elif self.current_page == 2: + # Show 100-year graphs + self.cards_container_100.setVisible(True) + self.legend_widget_100.setVisible(True) + self.bar_graph_widget_100.setVisible(True) + elif self.current_page == 3: + # Show bubble graphs + self.bubble_cards_container.setVisible(True) + + def hide_all_page_widgets(self): + """Hide all page-specific widgets""" + # Page 0 widgets + self.cards_container.setVisible(False) + self.legend_widget.setVisible(False) + self.bar_graph_widget.setVisible(False) + + # Page 1 widgets + self.pie_chart_widget.setVisible(False) + + # Page 2 widgets + self.cards_container_100.setVisible(False) + self.legend_widget_100.setVisible(False) + self.bar_graph_widget_100.setVisible(False) + + # Page 3 widgets + self.bubble_cards_container.setVisible(False) + + def _build_pie_chart(self) -> QWidget: + """Create pie chart widget""" + pie_chart_widget = QWidget() + pie_chart_layout = QVBoxLayout(pie_chart_widget) + pie_chart_layout.setContentsMargins(20, 15, 20, 15) + pie_chart_layout.setSpacing(10) + + # Title for the pie chart + title_label = QLabel("Cost Breakdown - Pie Chart") + title_label.setStyleSheet(""" + QLabel { + font-size: 16px; + font-weight: bold; + color: #333333; + text-align: center; + } + """) + title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + pie_chart_layout.addWidget(title_label) + + # Create the pie chart using QWebEngineView + pie_view = QWebEngineView() + pie_view.setHtml(self._build_pie_chart_html()) + pie_view.setZoomFactor(0.8) + pie_view.setMinimumHeight(400) + pie_view.setMaximumHeight(500) + pie_view.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + pie_view.setMinimumWidth(600) + pie_chart_layout.addWidget(pie_view) + + return pie_chart_widget + + def _build_pie_chart_html(self) -> str: + """Generate HTML for pie chart using code from Pie chart.py""" + # Sample data based on the pie chart.py format + data_with_colors = [ + { + "label": "Road user cost", + "cost": 123.93, + "percent": 47.5, + "color": "#FF8C00", + "disabled": False + }, + { + "label": "Time cost estimate", + "cost": 2.32, + "percent": 0.9, + "color": "#483D8B", + "disabled": False + }, + { + "label": "Embodied carbon emissions", + "cost": 8.53, + "percent": 3.3, + "color": "#B22222", + "disabled": False + }, + { + "label": "Initial construction cost", + "cost": 61.83, + "percent": 23.7, + "color": "#996633", + "disabled": False + }, + { + "label": "Additional CO2 e costs due to rerouting", + "cost": 29.03, + "percent": 11.1, + "color": "#8B0000", + "disabled": False + }, + { + "label": "Periodic Maintenance costs", + "cost": 1.44, + "percent": 0.4, + "color": "#F6FB05", + "disabled": False + }, + { + "label": "Periodic maintenance carbon emissions", + "cost": 16.99, + "percent": 6.4, + "color": "#A52A2A", + "disabled": False + }, + { + "label": "Annual routine inspection costs", + "cost": 14.32, + "percent": 5.5, + "color": "#4682B4", + "disabled": False + }, + { + "label": "Repair and rehabilitation costs", + "cost": 1.99, + "percent": 0.8, + "color": "#008000", + "disabled": False + }, + { + "label": "Demolition and deconstruction costs", + "cost": 0.77, + "percent": 0.3, + "color": "#800080", + "disabled": False + } + ] + + import json + data_js = json.dumps(data_with_colors) + + html = f""" + + + + Cost Breakdown Pie Chart + + + + +
+

Cost Breakdown (in Lakhs)

+
+
+
+ + + + + """ + return html + + def _build_bar_graph_100(self) -> QWidget: + """Create 100-year bar graph widget""" + bar_graph_widget = QWidget() + bar_graph_layout = QVBoxLayout(bar_graph_widget) + bar_graph_layout.setContentsMargins(20, 15, 20, 15) + bar_graph_layout.setSpacing(10) + + # Title for the bar graph + title_label = QLabel("Life-Cycle Costs for 100 years") + title_label.setStyleSheet(""" + QLabel { + font-size: 16px; + font-weight: bold; + color: #333333; + text-align: center; + } + """) + title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + bar_graph_layout.addWidget(title_label) + + # Create the bar graph using QWebEngineView + graph_view = QWebEngineView() + graph_view.setHtml(self._build_bar_graph_100_html()) + graph_view.setZoomFactor(0.8) + graph_view.setMinimumHeight(300) + graph_view.setMaximumHeight(400) + graph_view.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + graph_view.setMinimumWidth(500) + bar_graph_layout.addWidget(graph_view) + + return bar_graph_widget + + def _build_bar_graph_100_html(self) -> str: + """Generate HTML for the 100-year horizontal bar graph""" + # Use similar data but adjusted for 100 years + name = [ + "Initial Construction Cost", "Initial Carbon Emission Cost", "Time Cost", + "Road User Cost", "Periodic Maintenance Costs", "Maintenance Emission Costs", + "Routine Inspection Costs", "Repair & Rehabilitation Costs", "Reconstruction Costs", + "Demolition & Disposal Cost", "Recycling Cost" + ] + # Doubled costs for 100 years simulation + cost = [ + 61.83, 29.03, 0.0, 247.86, 2.88, 0.0, 0.0, 0.0, 0.0, 18.69, 0.0 + ] + + # Clean data + temp_name = name[:11] # Exclude total + temp_cost = cost[:11] + + # Display adjustment for zero values + temp_cost_display = [] + if temp_cost: + non_zero_costs = [c for c in temp_cost if c > 0] + if non_zero_costs: + min_non_zero = min(non_zero_costs) + for j in temp_cost: + if j == 0.0: + temp_cost_display.append(min_non_zero / 2) + else: + temp_cost_display.append(j) + else: + temp_cost_display = temp_cost + else: + temp_cost_display = temp_cost + + # Color list + color_list = ['#638B48', '#6F6F6F', '#638B48', '#E09365', '#6F6F6F', '#638B48', + '#6F6F6F', '#638B48', '#638B48', '#638B48', '#638B48'] + + # Create data array for D3 + data = [] + for i, (n, c, col) in enumerate(zip(temp_name, temp_cost_display, color_list[:len(temp_name)])): + data.append({"name": n, "value": c, "color": col}) + + html = f""" + + + + + D3.js Interactive Horizontal Bar Graph - 100 Years + + + +

Life Cycle Cost for 100 Years

+
+ + + + + """ + return html + + def _create_bubble_card(self, title: str) -> QWidget: + """Create a bubble chart card""" + card = QWidget() + card.setObjectName("result_card") + card.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred) + card.setMinimumWidth(420) + card.setMaximumHeight(380) + + card_layout = QVBoxLayout(card) + card_layout.setContentsMargins(10, 8, 10, 10) + card_layout.setSpacing(6) + + header_layout = QHBoxLayout() + title_label = QLabel(title) + title_label.setTextFormat(Qt.TextFormat.RichText) + title_label.setWordWrap(True) + header_layout.addWidget(title_label) + header_layout.addStretch(1) + + close_btn = QPushButton() + close_btn.setObjectName("close_card_button") + close_btn.setIcon(QIcon("resources/close.png")) + close_btn.setIconSize(QSize(13, 13)) + header_layout.addWidget(close_btn, 0, Qt.AlignmentFlag.AlignRight) + + def remove_card(): + card.setParent(None) + card.deleteLater() + close_btn.clicked.connect(remove_card) + + card_layout.addLayout(header_layout) + + # Embed bubble graph inside the card using QWebEngineView + graph = QWebEngineView(card) + graph.setHtml(self._build_bubble_graph_html()) + graph.setZoomFactor(0.8) + graph.setMinimumHeight(320) + card_layout.addWidget(graph) + + return card + + def _build_bubble_graph_html(self) -> str: + """Generate HTML for bubble graph using code from Bubble graph.py""" + # Sample data based on bubble graph.py + val_a, val_b, val_c = 53.71, 40.4, 6.4 + cost_a, cost_b, cost_c = 29.03, 19.36, 16.99 + + html = f""" + + + + + Bubble Chart Layout + + + + +
+
+
+ A - Initial Carbon Emission Cost + B - Carbon Emission due to Re-Routing + C - Maintenance Emission Costs +
+
+
+ + + + + """ + return html + +#----------------Standalone-Test-Code-------------------------------- + +class MyMainWindow(QMainWindow): + def __init__(self): + super().__init__() + + self.setStyleSheet("border: none") + + self.central_widget = QWidget() + self.central_widget.setObjectName("central_widget") + self.setCentralWidget(self.central_widget) + + self.main_h_layout = QHBoxLayout(self.central_widget) + self.main_h_layout.addStretch(1) + + self.main_h_layout.addWidget(ResultsWidget(), 2) + + self.setWindowState(Qt.WindowMaximized) + + +if __name__ == "__main__": + QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) + app = QApplication(sys.argv) + window = MyMainWindow() + window.show() + sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/structure_works_data/auxiliary_works_widget.py b/src/osbridgelcca/desktop_app/widgets/structure_works_data/auxiliary_works_widget.py new file mode 100644 index 0000000..4009a65 --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/structure_works_data/auxiliary_works_widget.py @@ -0,0 +1,568 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +from PySide6.QtGui import QDoubleValidator +from ..utils.data import * +import sys + +class ComponentWidget(QWidget): + def __init__(self, parent): + super().__init__(parent) + + self.data = construction_materials.get(KEY_AUXILIARY) + self.material_rows = [] + self.current_material_row_idx = 1 + + self.init_ui() + + + def collect_data(self): + rows_data = [] + for row in self.material_rows: + component = self.component_combobox.currentText() + material_type = row[KEY_TYPE].currentText() + material_grade = row[KEY_GRADE].currentText() + quantity = row[KEY_QUANTITY].text() + unit_m3 = row[KEY_UNIT_M3].currentText() + rate = row[KEY_RATE].text() + rate_data_source = row[KEY_RATE_DATA_SOURCE].text() + row_dict = { KEY_COMPONENT: component, + KEY_TYPE: material_type, + KEY_GRADE: material_grade, + KEY_QUANTITY: quantity if quantity.strip() else "0", + KEY_UNIT_M3: unit_m3, + KEY_RATE: rate if rate.strip() else "0.00", + KEY_RATE_DATA_SOURCE: rate_data_source + } + rows_data.append(row_dict) + return rows_data + + def init_ui(self): + self.component_first_scroll_content_layout = QVBoxLayout(self) + self.component_first_scroll_content_layout.setContentsMargins(10, 10, 10, 10) + self.component_first_scroll_content_layout.setSpacing(10) + + component_header_layout = QHBoxLayout() + component_label = QLabel("Component:") + component_label.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(component_label) + + self.component_combobox = QComboBox() + self.component_combobox.addItems(self.data.keys()) + self.component_combobox.currentTextChanged.connect(self.update_comp_material) + self.component_combobox.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(self.component_combobox) + + self.remove_component_button = QPushButton("x") + self.remove_component_button.setFixedSize(24, 24) + self.remove_component_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + line-height:12px; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + component_header_layout.addWidget(self.remove_component_button) + component_header_layout.addStretch(1) + + self.component_first_scroll_content_layout.addLayout(component_header_layout) + + self.material_grid_layout = QGridLayout() + self.material_grid_layout.setHorizontalSpacing(10) + self.material_grid_layout.setVerticalSpacing(5) + + headers = ["Type of Material", "Grade", "Quantity", "Unit", "Rate", "Rate Data Source"] + for col, header_text in enumerate(headers): + label = QLabel(header_text) + label.setAlignment(Qt.AlignCenter) + label.setObjectName("MaterialGridLabel") + self.material_grid_layout.addWidget(label, 0, col) + + self.component_first_scroll_content_layout.addLayout(self.material_grid_layout) + + self.add_material_row() + self.add_material_row() + + self.update_comp_material(self.component_combobox.currentText()) + + self.add_material_button = QPushButton("+ Add Material") + self.add_material_button.setObjectName("add_material_button") + self.add_material_button.clicked.connect(self.add_material_row) + self.component_first_scroll_content_layout.addWidget(self.add_material_button, alignment=Qt.AlignCenter) + + def update_comp_material(self, selected_component): + """Update all material combos when component changes""" + materials = list(self.data.get(selected_component, {}).keys()) + for i in range(len(self.material_rows)): + material_combo = self.material_rows[i][KEY_TYPE] + grade_combo = self.material_rows[i][KEY_GRADE] + + # Clear and repopulate materials + material_combo.clear() + material_combo.addItems(materials) + + # Update grades for the first material + if materials: + self.update_comp_grades(material_combo.currentText(), grade_combo) + + def update_comp_grades(self, selected_material, widget): + """Update grades based on selected material""" + selected_component = self.component_combobox.currentText() + grades = self.data.get(selected_component, {}).get(selected_material, {}).get(KEY_GRADE, []) + widget.clear() + widget.addItems(grades) + + def update_comp_units(self, selected_material, widget): + selected_component = self.component_combobox.currentText() + units = self.data.get(selected_component,{}).get(selected_material,{}).get(KEY_UNITS,[]) + widget.clear() + widget.addItems(units) + + def add_material_row(self): + validator = QDoubleValidator() + validator.setRange(0.0, 999999.99, 2) + validator.setBottom(0.0) + validator.setNotation(QDoubleValidator.Notation.StandardNotation) + + row_widgets = {} + row_idx = self.current_material_row_idx + fixed_input_width = 80 + + # Get current component and materials + selected_component = self.component_combobox.currentText() + materials = list(self.data.get(selected_component, {}).keys()) + + # Type of Material ComboBox + type_material_combo = QComboBox() + type_material_combo.addItems(materials) + type_material_combo.setObjectName("MaterialGridInput") + type_material_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(type_material_combo, row_idx, 0) + row_widgets[KEY_TYPE] = type_material_combo + + # Grade ComboBox + grade_combo = QComboBox() + grade_combo.setObjectName("MaterialGridInput") + type_material_combo.currentTextChanged.connect( + lambda text, widget=grade_combo: self.update_comp_grades(text, widget) + ) + grade_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(grade_combo, row_idx, 1) + row_widgets[KEY_GRADE] = grade_combo + + # Quantity + quantity_edit = QLineEdit() + quantity_edit.setValidator(validator) + quantity_edit.setPlaceholderText("0") + quantity_edit.setObjectName("MaterialGridInput") + quantity_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(quantity_edit, row_idx, 2) + row_widgets[KEY_QUANTITY] = quantity_edit + + # Unit + unit_combo_m3 = QComboBox() + type_material_combo.currentTextChanged.connect( + lambda text, widget=unit_combo_m3: self.update_comp_units(text, widget) + ) + unit_combo_m3.setObjectName("MaterialGridInput") + unit_combo_m3.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(unit_combo_m3, row_idx, 3) + row_widgets[KEY_UNIT_M3] = unit_combo_m3 + + # Rate + rate_edit = QLineEdit() + rate_edit.setValidator(validator) + rate_edit.setPlaceholderText("0.00") + rate_edit.setObjectName("MaterialGridInput") + rate_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_edit, row_idx, 4) + row_widgets[KEY_RATE] = rate_edit + + # Rate Data Source + rate_data_source_edit = QLineEdit() + rate_data_source_edit.setObjectName("MaterialGridInput") + rate_data_source_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_data_source_edit, row_idx, 5) + row_widgets[KEY_RATE_DATA_SOURCE] = rate_data_source_edit + + # Remove button + remove_button = QPushButton("x") + remove_button.setFixedSize(24, 24) + remove_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + line-height:12px; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + remove_button.clicked.connect(lambda: self.remove_material_row_by_widgets(row_widgets)) + self.material_grid_layout.addWidget(remove_button, row_idx, 6) + row_widgets['remove_button'] = remove_button + + self.material_rows.append(row_widgets) + self.current_material_row_idx += 1 + self.updateGeometry() + self.adjustSize() + + def remove_material_row_by_widgets(self, row_widgets_to_remove): + """Remove a material row from the grid""" + if row_widgets_to_remove not in self.material_rows: + return + + # Find the row index in the grid + row_idx_in_grid = -1 + for i, row_dict in enumerate(self.material_rows): + if row_dict == row_widgets_to_remove: + row_idx_in_grid = i + 1 # +1 because row 0 is header + break + + if row_idx_in_grid == -1: + return + + # Remove all widgets in the row + for col in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(row_idx_in_grid, col) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + widget.deleteLater() + elif item.layout(): + layout = item.layout() + while layout.count(): + sub_item = layout.takeAt(0) + if sub_item.widget(): + sub_item.widget().deleteLater() + self.material_grid_layout.removeItem(layout) + + # Remove from tracking list + self.material_rows.remove(row_widgets_to_remove) + self.current_material_row_idx -= 1 + + # Shift remaining rows up + for r_idx in range(row_idx_in_grid, self.current_material_row_idx + 1): + for c_idx in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(r_idx + 1, c_idx) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + self.material_grid_layout.addWidget(widget, r_idx, c_idx) + elif item.layout(): + layout = item.layout() + self.material_grid_layout.removeItem(layout) + self.material_grid_layout.addLayout(layout, r_idx, c_idx) + + self.updateGeometry() + self.update() + self.material_grid_layout.invalidate() + self.adjustSize() + + +class AuxiliaryWorks(QWidget): + closed = Signal() + next = Signal(str) + back = Signal(str) + def __init__(self, database, parent=None): + super().__init__(parent) + self.database_manager = database + self.setObjectName("central_panel_widget") + self.component_widgets = [] + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + #component_first_widget { + background-color: transparent; + margin-top: 10px; + } + + #component_first_scroll_content_widget { + background-color: #FFFFFF; + padding: 10px; + border-radius: 8px; + } + + QPushButton#nav_button { + background-color: #FFFFFF; + border: 1px solid #E0E0E0; + border-radius: 8px; + color: #3F3E5E; + padding: 6px 15px; + text-align: center; + min-width: 80px; + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; + border-color: #C0C0C0; + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; + border-color: #A0A0A0; + } + + QComboBox { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 30px; + height: 30px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + + #MaterialGridLabel { + font-weight: bold; + color: #3F3E5E; + padding: 5px; + text-align: center; + } + #MaterialGridInput { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background-color: #FFFFFF; + } + #MaterialGridInput:focus { + border: 1px solid #DDDCE0; + background-color: #FFFFFF; + } + + QPushButton#add_material_button, QPushButton#add_component_button { + background-color: #FFFFFF; + border: 1px solid #E0E0E0; + border-radius: 8px; + color: #3F3E5E; + padding: 6px 15px; + text-align: center; + } + QPushButton#add_material_button:hover, QPushButton#add_component_button:hover { + background-color: #F8F8F8; + border-color: #C0C0C0; + } + QPushButton#add_material_button:pressed, QPushButton#add_component_button:pressed { + background-color: #E8E8E8; + border-color: #A0A0A0; + } + """) + + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0, 0, 0, 0) + self.scroll_content_layout.setSpacing(0) + + self.add_component_button = QPushButton("+ Add Component") + self.add_component_button.setObjectName("add_component_button") + self.add_component_button.clicked.connect(self.add_component_layout) + + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10, 10, 10, 10) + self.button_h_layout.addStretch(6) + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + back_button.clicked.connect(lambda: self.back.emit(KEY_AUXILIARY)) + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + next_button.clicked.connect(lambda: self.next.emit(KEY_AUXILIARY)) + next_button.clicked.connect(self.save_data) + self.button_h_layout.addWidget(next_button) + + self.add_component_layout() + + self.scroll_content_layout.addLayout(self.button_h_layout) + left_panel_vlayout.addWidget(self.scroll_area) + + def add_component_layout(self): + new_component = ComponentWidget(self) + self.component_widgets.append(new_component) + new_component.remove_component_button.clicked.connect( + lambda: self.remove_component_layout(new_component) + ) + + if self.scroll_content_layout.indexOf(self.add_component_button) != -1: + self.scroll_content_layout.removeWidget(self.add_component_button) + if self.scroll_content_layout.indexOf(self.button_h_layout) != -1: + self.scroll_content_layout.removeItem(self.button_h_layout) + + self.scroll_content_layout.addWidget(new_component) + self.scroll_content_layout.addWidget(self.add_component_button, alignment=Qt.AlignCenter) + self.scroll_content_layout.addLayout(self.button_h_layout) + + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def remove_component_layout(self, component_to_remove): + if component_to_remove in self.component_widgets: + self.scroll_content_layout.removeWidget(component_to_remove) + self.component_widgets.remove(component_to_remove) + component_to_remove.deleteLater() + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def collect_data(self): + all_data = [] + for component_widget in self.component_widgets: + component_data = component_widget.collect_data() + all_data.append(component_data) + return all_data + + def save_data(self): + data = self.collect_data() + print("Collected Data:", data) + self.database_manager.input_data_row(KEY_SUPERSTRUCTURE, data) + + def expand_scroll_area(self): + self.central_widget.layout().invalidate() + + def close_widget(self): + self.closed.emit() + self.setParent(None) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/structure_works_data/foundation_widget.py b/src/osbridgelcca/desktop_app/widgets/structure_works_data/foundation_widget.py new file mode 100644 index 0000000..5921967 --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/structure_works_data/foundation_widget.py @@ -0,0 +1,588 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +from PySide6.QtGui import QDoubleValidator +from ..utils.data import * +import sys + +class ComponentWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.data = construction_materials.get(KEY_FOUNDATION) + self.material_rows = [] # To store references to widgets in each material row + self.current_material_row_idx = 1 # Start index for material rows (0 is header) + + self.init_ui() + + def init_ui(self): + self.component_first_scroll_content_layout = QVBoxLayout(self) # Set QVBoxLayout directly on self + self.component_first_scroll_content_layout.setContentsMargins(10, 10, 10, 10) + self.component_first_scroll_content_layout.setSpacing(10) + + # Component Label and Dropdown with a remove button + component_header_layout = QHBoxLayout() + component_label = QLabel("Component:") + component_label.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(component_label) + + self.component_combobox = QComboBox() + self.component_combobox.currentTextChanged.connect(self.update_comp_material) + self.component_combobox.addItems(self.data.keys()) + self.component_combobox.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(self.component_combobox) + + # Add a remove button for the component + self.remove_component_button = QPushButton("x") + self.remove_component_button.setFixedSize(24, 24) + self.remove_component_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + line-height:12px; + color: #CC0000; + padding: 0px; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + component_header_layout.addWidget(self.remove_component_button) + component_header_layout.addStretch(1) + + self.component_first_scroll_content_layout.addLayout(component_header_layout) + + # --- Material Details Grid Layout --- + self.material_grid_layout = QGridLayout() + self.material_grid_layout.setHorizontalSpacing(10) + self.material_grid_layout.setVerticalSpacing(5) + + # Header Row + headers = ["Type of Material", "Grade", "Quantity", "Unit", "Rate", "Rate Data Source"] + for col, header_text in enumerate(headers): + label = QLabel(header_text) + label.setAlignment(Qt.AlignCenter) + label.setObjectName("MaterialGridLabel") + self.material_grid_layout.addWidget(label, 0, col) + + # Column stretch factors are removed as all input widgets will now have fixed widths. + # This allows the grid to size columns based on the fixed widget sizes and spacing. + + self.component_first_scroll_content_layout.addLayout(self.material_grid_layout) + + # Add initial two material rows + self.add_material_row() + self.add_material_row() + + self.update_comp_material(self.component_combobox.currentText()) + + # --- Add Material Button --- + self.add_material_button = QPushButton("+ Add Material") + self.add_material_button.setObjectName("add_material_button") + self.add_material_button.clicked.connect(self.add_material_row) + self.component_first_scroll_content_layout.addWidget(self.add_material_button, alignment=Qt.AlignCenter) + + def collect_data(self): + rows_data = [] + for row in self.material_rows: + component = self.component_combobox.currentText() + material_type = row[KEY_TYPE].currentText() + material_grade = row[KEY_GRADE].currentText() + quantity = row[KEY_QUANTITY].text() + unit_m3 = row[KEY_UNIT_M3].currentText() + rate = row[KEY_RATE].text() + rate_data_source = row[KEY_RATE_DATA_SOURCE].text() + row_dict = { KEY_COMPONENT: component, + KEY_TYPE: material_type, + KEY_GRADE: material_grade, + KEY_QUANTITY: quantity if quantity.strip() else "0", + KEY_UNIT_M3: unit_m3, + KEY_RATE: rate if rate.strip() else "0.00", + KEY_RATE_DATA_SOURCE: rate_data_source + } + rows_data.append(row_dict) + return rows_data + + def update_comp_material(self, selected_component): + materials = self.data.get(selected_component).keys() + for i in range(len(self.material_rows)): + material_combo = self.material_rows[i][KEY_TYPE] + material_combo.clear() + material_combo.addItems(materials) + + def update_comp_grades(self, selected_material, widget): + selected_component = self.component_combobox.currentText() + grades = self.data.get(selected_component,{}).get(selected_material,{}).get(KEY_GRADE,[]) + widget.clear() + widget.addItems(grades) + + def update_comp_units(self, selected_material, widget): + selected_component = self.component_combobox.currentText() + units = self.data.get(selected_component,{}).get(selected_material,{}).get(KEY_UNITS,[]) + widget.clear() + widget.addItems(units) + + def add_material_row(self): + validator = QDoubleValidator() + validator.setRange(0.0, 999999.99, 2) + validator.setBottom(0.0) + validator.setNotation(QDoubleValidator.Notation.StandardNotation) + + row_widgets = {} + row_idx = self.current_material_row_idx + + # Set fixed width for all input widgets to 80px, as requested. + # Note: This might make wider text truncate or appear cramped depending on content. + fixed_input_width = 80 + + type_material_combo = QComboBox() + type_material_combo.setObjectName("MaterialGridInput") + type_material_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(type_material_combo, row_idx, 0) + row_widgets[KEY_TYPE] = type_material_combo + + grade_combo = QComboBox() + grade_combo.setObjectName("MaterialGridInput") + type_material_combo.currentTextChanged.connect( + lambda text, widget=grade_combo: self.update_comp_grades(text, widget) + ) + grade_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(grade_combo, row_idx, 1) + row_widgets[KEY_GRADE] = grade_combo + + quantity_edit = QLineEdit() + quantity_edit.setValidator(validator) + quantity_edit.setPlaceholderText("0") + quantity_edit.setObjectName("MaterialGridInput") + quantity_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(quantity_edit, row_idx, 2) + row_widgets[KEY_QUANTITY] = quantity_edit + + unit_combo_m3 = QComboBox() + unit_combo_m3.setObjectName("MaterialGridInput") + type_material_combo.currentTextChanged.connect( + lambda text, widget=unit_combo_m3: self.update_comp_units(text, widget) + ) + unit_combo_m3.setFixedWidth(fixed_input_width) # Previously 80, now consistent with others + self.material_grid_layout.addWidget(unit_combo_m3, row_idx, 3) # Directly add, no extra layout + row_widgets[KEY_UNIT_M3] = unit_combo_m3 + + rate_edit = QLineEdit() + rate_edit.setValidator(validator) + rate_edit.setPlaceholderText("0.00") + rate_edit.setObjectName("MaterialGridInput") + rate_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_edit, row_idx, 4) + row_widgets[KEY_RATE] = rate_edit + + rate_data_source_edit = QLineEdit() + rate_data_source_edit.setObjectName("MaterialGridInput") + rate_data_source_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_data_source_edit, row_idx, 5) + row_widgets[KEY_RATE_DATA_SOURCE] = rate_data_source_edit + + remove_button = QPushButton("x") + remove_button.setFixedSize(24, 24) + remove_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + line-height:12px; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + remove_button.clicked.connect(lambda: self.remove_material_row_by_widgets(row_widgets)) + self.material_grid_layout.addWidget(remove_button, row_idx, 6) + row_widgets['remove_button'] = remove_button + + self.material_rows.append(row_widgets) + self.current_material_row_idx += 1 + self.updateGeometry() # Call updateGeometry on self, not on the scroll content widget + self.adjustSize() # Adjust the size of the component widget + + + def remove_material_row_by_widgets(self, row_widgets_to_remove): + if row_widgets_to_remove not in self.material_rows: + return + + row_idx_in_grid = -1 + for i, row_dict in enumerate(self.material_rows): + if row_dict == row_widgets_to_remove: + row_idx_in_grid = i + 1 # +1 because row 0 is header + break + + if row_idx_in_grid == -1: + return + + for col in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(row_idx_in_grid, col) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + widget.deleteLater() + elif item.layout(): + layout = item.layout() + while layout.count(): + sub_item = layout.takeAt(0) + if sub_item.widget(): + sub_item.widget().deleteLater() + self.material_grid_layout.removeItem(layout) + + self.material_rows.remove(row_widgets_to_remove) + self.current_material_row_idx -= 1 + + for r_idx in range(row_idx_in_grid, self.current_material_row_idx + 1): + for c_idx in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(r_idx + 1, c_idx) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + self.material_grid_layout.addWidget(widget, r_idx, c_idx) + elif item.layout(): + layout = item.layout() + self.material_grid_layout.removeItem(layout) + self.material_grid_layout.addLayout(layout, r_idx, c_idx) + + self.updateGeometry() # Call updateGeometry on self + self.update() # Call update on self + self.material_grid_layout.invalidate() + self.adjustSize() # Adjust the size of the component widget + +class Foundation(QWidget): + closed = Signal() + next = Signal(str) + back = Signal(str) + def __init__(self, database, parent=None): + super().__init__(parent) + self.database_manager = database + self.setObjectName("central_panel_widget") + self.component_widgets = [] # To store references to each ComponentWidget instance + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + /* Styling for component_first_widget (the container for the nested scroll area) */ + #component_first_widget { + background-color: transparent; + margin-top: 10px; /* Add some space above each component block */ + } + + #component_first_scroll_content_widget { /* This now applies directly to ComponentWidget itself */ + background-color: #FFFFFF; + padding: 10px; + + border-radius: 8px; + } + + /* Updated Styling for navigation buttons to match the Add Material/Component buttons */ + QPushButton#nav_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + min-width: 80px; /* Ensure a minimum width */ + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + /* Styling for QComboBox */ + QComboBox { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 30px; + height: 30px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + + /* Styling for material grid elements */ + #MaterialGridLabel { + font-weight: bold; + color: #3F3E5E; + padding: 5px; + text-align: center; + } + #MaterialGridInput { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background-color: #FFFFFF; + } + #MaterialGridInput:focus { + border: 1px solid #DDDCE0; + background-color: #FFFFFF; + } + /* IMPROVED CSS FOR ADD MATERIAL/COMPONENT BUTTONS */ + QPushButton#add_material_button, QPushButton#add_component_button { + background-color: #FFFFFF; /* White background */ + border: 1px solid #E0E0E0; /* Light grey border */ + border-radius: 8px; /* Slightly more rounded corners */ + color: #3F3E5E; /* Dark text color */ + padding: 6px 15px; /* Increased padding */ + text-align: center; + } + QPushButton#add_material_button:hover, QPushButton#add_component_button:hover { + background-color: #F8F8F8; /* Very subtle light grey on hover */ + border-color: #C0C0C0; /* Darker border on hover */ + } + QPushButton#add_material_button:pressed, QPushButton#add_component_button:pressed { + background-color: #E8E8E8; /* Darker grey on pressed */ + border-color: #A0A0A0; /* Even darker border */ + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + # Create the Add Component button and connect it + self.add_component_button = QPushButton("+ Add Component") + self.add_component_button.setObjectName("add_component_button") + self.add_component_button.clicked.connect(self.add_component_layout) + + # Create the navigation buttons layout + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + # Adjust these stretch factors to control the position + self.button_h_layout.addStretch(6) # Larger stretch on the left to push it more right + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + next_button.clicked.connect(self.save_data) + next_button.clicked.connect(lambda: self.next.emit(KEY_FOUNDATION)) + self.button_h_layout.addWidget(next_button) + + # Add the initial component layout + self.add_component_layout() + + # Add initial spacing before the navigation buttons + self.scroll_content_layout.addLayout(self.button_h_layout) + left_panel_vlayout.addWidget(self.scroll_area) + + def collect_data(self): + all_data = [] + for component_widget in self.component_widgets: + component_data = component_widget.collect_data() + all_data.append(component_data) + return all_data + + def save_data(self): + data = self.collect_data() + print("Collected Data:", data) + self.database_manager.input_data_row(KEY_FOUNDATION, data) + + def add_component_layout(self): + new_component = ComponentWidget(self) + self.component_widgets.append(new_component) + new_component.remove_component_button.clicked.connect(lambda: self.remove_component_layout(new_component)) + + # Temporarily remove button_h_layout and add_component_button for insertion + if self.scroll_content_layout.indexOf(self.add_component_button) != -1: + self.scroll_content_layout.removeWidget(self.add_component_button) + if self.scroll_content_layout.indexOf(self.button_h_layout) != -1: + self.scroll_content_layout.removeItem(self.button_h_layout) + + # Insert the new component + self.scroll_content_layout.addWidget(new_component) + + # Re-add the 'Add Component' button + self.scroll_content_layout.addWidget(self.add_component_button, alignment=Qt.AlignCenter) + + # Re-add the navigation buttons layout + self.scroll_content_layout.addLayout(self.button_h_layout) + + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def remove_component_layout(self, component_to_remove): + if component_to_remove in self.component_widgets: + self.scroll_content_layout.removeWidget(component_to_remove) + self.component_widgets.remove(component_to_remove) + component_to_remove.deleteLater() + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def expand_scroll_area(self): + self.central_widget.layout().invalidate() + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addStretch(1) + +# self.main_h_layout.addWidget(Foundation(), 2) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/structure_works_data/sub_structure_widget.py b/src/osbridgelcca/desktop_app/widgets/structure_works_data/sub_structure_widget.py new file mode 100644 index 0000000..03f55dc --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/structure_works_data/sub_structure_widget.py @@ -0,0 +1,541 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +from PySide6.QtGui import QDoubleValidator +from ..utils.data import * +import sys + +class ComponentWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.data = construction_materials.get(KEY_SUBSTRUCTURE) + + self.material_rows = [] + self.current_material_row_idx = 1 + + self.init_ui() + + + def collect_data(self): + rows_data = [] + for row in self.material_rows: + component = self.component_combobox.currentText() + material_type = row[KEY_TYPE].currentText() + material_grade = row[KEY_GRADE].currentText() + quantity = row[KEY_QUANTITY].text() + unit_m3 = row[KEY_UNIT_M3].currentText() + rate = row[KEY_RATE].text() + rate_data_source = row[KEY_RATE_DATA_SOURCE].text() + row_dict = { KEY_COMPONENT: component, + KEY_TYPE: material_type, + KEY_GRADE: material_grade, + KEY_QUANTITY: quantity if quantity.strip() else "0", + KEY_UNIT_M3: unit_m3, + KEY_RATE: rate if rate.strip() else "0.00", + KEY_RATE_DATA_SOURCE: rate_data_source + } + rows_data.append(row_dict) + return rows_data + + def init_ui(self): + self.component_first_scroll_content_layout = QVBoxLayout(self) + self.component_first_scroll_content_layout.setContentsMargins(10, 10, 10, 10) + self.component_first_scroll_content_layout.setSpacing(10) + + component_header_layout = QHBoxLayout() + component_label = QLabel("Component:") + component_label.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(component_label) + + self.component_combobox = QComboBox() + self.component_combobox.addItems(self.data.keys()) + self.component_combobox.currentTextChanged.connect(self.update_comp_material) + self.component_combobox.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(self.component_combobox) + + self.remove_component_button = QPushButton("x") + self.remove_component_button.setFixedSize(24, 24) + self.remove_component_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + component_header_layout.addWidget(self.remove_component_button) + component_header_layout.addStretch(1) + + self.component_first_scroll_content_layout.addLayout(component_header_layout) + + self.material_grid_layout = QGridLayout() + self.material_grid_layout.setHorizontalSpacing(10) + self.material_grid_layout.setVerticalSpacing(5) + + headers = ["Type of Material", "Grade", "Quantity", "Unit", "Rate", "Rate Data Source"] + for col, header_text in enumerate(headers): + label = QLabel(header_text) + label.setAlignment(Qt.AlignCenter) + label.setObjectName("MaterialGridLabel") + self.material_grid_layout.addWidget(label, 0, col) + + self.component_first_scroll_content_layout.addLayout(self.material_grid_layout) + + self.add_material_row() + self.add_material_row() + + self.update_comp_material(self.component_combobox.currentText()) + + self.add_material_button = QPushButton("+ Add Material") + self.add_material_button.setObjectName("add_material_button") + self.add_material_button.clicked.connect(self.add_material_row) + self.component_first_scroll_content_layout.addWidget(self.add_material_button, alignment=Qt.AlignCenter) + + def update_comp_material(self, selected_component): + materials = self.data.get(selected_component).keys() + for i in range(len(self.material_rows)): + material_combo = self.material_rows[i][KEY_TYPE] + material_combo.clear() + material_combo.addItems(materials) + + def update_comp_grades(self, selected_material, widget): + selected_component = self.component_combobox.currentText() + grades = self.data.get(selected_component,{}).get(selected_material,{}).get(KEY_GRADE,[]) + widget.clear() + widget.addItems(grades) + + def update_comp_units(self, selected_material, widget): + selected_component = self.component_combobox.currentText() + units = self.data.get(selected_component,{}).get(selected_material,{}).get(KEY_UNITS,[]) + widget.clear() + widget.addItems(units) + + def add_material_row(self): + validator = QDoubleValidator() + validator.setRange(0.0, 999999.99, 2) + validator.setBottom(0.0) + validator.setNotation(QDoubleValidator.Notation.StandardNotation) + + row_widgets = {} + row_idx = self.current_material_row_idx + + fixed_input_width = 80 + + type_material_combo = QComboBox() + type_material_combo.setObjectName("MaterialGridInput") + type_material_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(type_material_combo, row_idx, 0) + row_widgets[KEY_TYPE] = type_material_combo + + grade_combo = QComboBox() + type_material_combo.currentTextChanged.connect( + lambda text, widget=grade_combo: self.update_comp_grades(text, widget) + ) + grade_combo.setObjectName("MaterialGridInput") + grade_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(grade_combo, row_idx, 1) + row_widgets[KEY_GRADE] = grade_combo + + quantity_edit = QLineEdit() + quantity_edit.setValidator(validator) + quantity_edit.setPlaceholderText("0") + quantity_edit.setObjectName("MaterialGridInput") + quantity_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(quantity_edit, row_idx, 2) + row_widgets[KEY_QUANTITY] = quantity_edit + + unit_combo_m3 = QComboBox() + type_material_combo.currentTextChanged.connect( + lambda text, widget=unit_combo_m3: self.update_comp_units(text, widget) + ) + unit_combo_m3.setObjectName("MaterialGridInput") + unit_combo_m3.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(unit_combo_m3, row_idx, 3) + row_widgets[KEY_UNIT_M3] = unit_combo_m3 + + rate_edit = QLineEdit() + rate_edit.setValidator(validator) + rate_edit.setPlaceholderText("0.00") + rate_edit.setObjectName("MaterialGridInput") + rate_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_edit, row_idx, 4) + row_widgets[KEY_RATE] = rate_edit + + rate_data_source_edit = QLineEdit() + rate_data_source_edit.setObjectName("MaterialGridInput") + rate_data_source_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_data_source_edit, row_idx, 5) + row_widgets[KEY_RATE_DATA_SOURCE] = rate_data_source_edit + + remove_button = QPushButton("x") + remove_button.setFixedSize(24, 24) + remove_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + remove_button.clicked.connect(lambda: self.remove_material_row_by_widgets(row_widgets)) + self.material_grid_layout.addWidget(remove_button, row_idx, 6) + row_widgets['remove_button'] = remove_button + + self.material_rows.append(row_widgets) + self.current_material_row_idx += 1 + self.updateGeometry() + self.adjustSize() + + + def remove_material_row_by_widgets(self, row_widgets_to_remove): + if row_widgets_to_remove not in self.material_rows: + return + + row_idx_in_grid = -1 + for i, row_dict in enumerate(self.material_rows): + if row_dict == row_widgets_to_remove: + row_idx_in_grid = i + 1 + break + + if row_idx_in_grid == -1: + return + + for col in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(row_idx_in_grid, col) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + widget.deleteLater() + elif item.layout(): + layout = item.layout() + while layout.count(): + sub_item = layout.takeAt(0) + if sub_item.widget(): + sub_item.widget().deleteLater() + self.material_grid_layout.removeItem(layout) + + self.material_rows.remove(row_widgets_to_remove) + self.current_material_row_idx -= 1 + + for r_idx in range(row_idx_in_grid, self.current_material_row_idx + 1): + for c_idx in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(r_idx + 1, c_idx) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + self.material_grid_layout.addWidget(widget, r_idx, c_idx) + elif item.layout(): + layout = item.layout() + self.material_grid_layout.removeItem(layout) + self.material_grid_layout.addLayout(layout, r_idx, c_idx) + + self.updateGeometry() + self.update() + self.material_grid_layout.invalidate() + self.adjustSize() + +class SubStructure(QWidget): + closed = Signal() + next = Signal(str) + back = Signal(str) + def __init__(self, database, parent=None): + super().__init__(parent) + self.database_manager = database + self.setObjectName("central_panel_widget") + self.component_widgets = [] + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + #component_first_widget { + background-color: transparent; + margin-top: 10px; + } + + #component_first_scroll_content_widget { + background-color: #FFFFFF; + padding: 10px; + + border-radius: 8px; + } + + QPushButton#nav_button { + background-color: #FFFFFF; + border: 1px solid #E0E0E0; + border-radius: 8px; + color: #3F3E5E; + padding: 6px 15px; + text-align: center; + min-width: 80px; + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; + border-color: #C0C0C0; + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; + border-color: #A0A0A0; + } + QComboBox { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 30px; + height: 30px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + + #MaterialGridLabel { + font-weight: bold; + color: #3F3E5E; + padding: 5px; + text-align: center; + } + #MaterialGridInput { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background-color: #FFFFFF; + } + #MaterialGridInput:focus { + border: 1px solid #DDDCE0; + background-color: #FFFFFF; + } + QPushButton#add_material_button, QPushButton#add_component_button { + background-color: #FFFFFF; + border: 1px solid #E0E0E0; + border-radius: 8px; + color: #3F3E5E; + padding: 6px 15px; + text-align: center; + } + QPushButton#add_material_button:hover, QPushButton#add_component_button:hover { + background-color: #F8F8F8; + border-color: #C0C0C0; + } + QPushButton#add_material_button:pressed, QPushButton#add_component_button:pressed { + background-color: #E8E8E8; + border-color: #A0A0A0; + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + self.add_component_button = QPushButton("+ Add Component") + self.add_component_button.setObjectName("add_component_button") + self.add_component_button.clicked.connect(self.add_component_layout) + + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + self.button_h_layout.addStretch(6) + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + back_button.clicked.connect(lambda: self.back.emit(KEY_SUBSTRUCTURE)) + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + next_button.clicked.connect(lambda: self.next.emit(KEY_SUBSTRUCTURE)) + next_button.clicked.connect(self.save_data) + self.button_h_layout.addWidget(next_button) + + self.add_component_layout() + + self.scroll_content_layout.addLayout(self.button_h_layout) + left_panel_vlayout.addWidget(self.scroll_area) + + def add_component_layout(self): + new_component = ComponentWidget(self) + self.component_widgets.append(new_component) + new_component.remove_component_button.clicked.connect(lambda: self.remove_component_layout(new_component)) + + if self.scroll_content_layout.indexOf(self.add_component_button) != -1: + self.scroll_content_layout.removeWidget(self.add_component_button) + if self.scroll_content_layout.indexOf(self.button_h_layout) != -1: + self.scroll_content_layout.removeItem(self.button_h_layout) + + self.scroll_content_layout.addWidget(new_component) + + self.scroll_content_layout.addWidget(self.add_component_button, alignment=Qt.AlignCenter) + + self.scroll_content_layout.addLayout(self.button_h_layout) + + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def remove_component_layout(self, component_to_remove): + if component_to_remove in self.component_widgets: + self.scroll_content_layout.removeWidget(component_to_remove) + self.component_widgets.remove(component_to_remove) + component_to_remove.deleteLater() + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def collect_data(self): + all_data = [] + for component_widget in self.component_widgets: + component_data = component_widget.collect_data() + all_data.append(component_data) + return all_data + + def save_data(self): + data = self.collect_data() + print("Collected Data:", data) + self.database_manager.input_data_row(KEY_SUBSTRUCTURE, data) + + def expand_scroll_area(self): + self.central_widget.layout().invalidate() + + def close_widget(self): + self.closed.emit() + self.setParent(None) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/structure_works_data/super_structure_widget.py b/src/osbridgelcca/desktop_app/widgets/structure_works_data/super_structure_widget.py new file mode 100644 index 0000000..dbdcf4a --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/structure_works_data/super_structure_widget.py @@ -0,0 +1,542 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, Qt, QSize, Signal +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLineEdit, QComboBox, QGridLayout, QWidget, QLabel, QVBoxLayout, QScrollArea, QSpacerItem, QSizePolicy, QFrame) +from PySide6.QtGui import QIcon +from PySide6.QtGui import QDoubleValidator +from ..utils.data import * +import sys + +class ComponentWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.data = construction_materials.get(KEY_SUPERSTRUCTURE) + self.material_rows = [] + self.current_material_row_idx = 1 + + self.init_ui() + + + def collect_data(self): + rows_data = [] + for row in self.material_rows: + component = self.component_combobox.currentText() + material_type = row[KEY_TYPE].currentText() + material_grade = row[KEY_GRADE].currentText() + quantity = row[KEY_QUANTITY].text() + unit_m3 = row[KEY_UNIT_M3].currentText() + rate = row[KEY_RATE].text() + rate_data_source = row[KEY_RATE_DATA_SOURCE].text() + row_dict = { KEY_COMPONENT: component, + KEY_TYPE: material_type, + KEY_GRADE: material_grade, + KEY_QUANTITY: quantity if quantity.strip() else "0", + KEY_UNIT_M3: unit_m3, + KEY_RATE: rate if rate.strip() else "0.00", + KEY_RATE_DATA_SOURCE: rate_data_source + } + rows_data.append(row_dict) + return rows_data + + def init_ui(self): + self.component_first_scroll_content_layout = QVBoxLayout(self) + self.component_first_scroll_content_layout.setContentsMargins(10, 10, 10, 10) + self.component_first_scroll_content_layout.setSpacing(10) + + component_header_layout = QHBoxLayout() + component_label = QLabel("Component:") + component_label.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(component_label) + + self.component_combobox = QComboBox() + self.component_combobox.currentTextChanged.connect(self.update_comp_material) + self.component_combobox.addItems(self.data.keys()) + self.component_combobox.setContentsMargins(0, 5, 0, 5) + component_header_layout.addWidget(self.component_combobox) + + self.remove_component_button = QPushButton("x") + self.remove_component_button.setFixedSize(24, 24) + self.remove_component_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + line-height:12px; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + component_header_layout.addWidget(self.remove_component_button) + component_header_layout.addStretch(1) + + self.component_first_scroll_content_layout.addLayout(component_header_layout) + + self.material_grid_layout = QGridLayout() + self.material_grid_layout.setHorizontalSpacing(10) + self.material_grid_layout.setVerticalSpacing(5) + + headers = ["Type of Material", "Grade", "Quantity", "Unit", "Rate", "Rate Data Source"] + for col, header_text in enumerate(headers): + label = QLabel(header_text) + label.setAlignment(Qt.AlignCenter) + label.setObjectName("MaterialGridLabel") + self.material_grid_layout.addWidget(label, 0, col) + + self.component_first_scroll_content_layout.addLayout(self.material_grid_layout) + + self.add_material_row() + self.add_material_row() + + self.update_comp_material(self.component_combobox.currentText()) + + self.add_material_button = QPushButton("+ Add Material") + self.add_material_button.setObjectName("add_material_button") + self.add_material_button.clicked.connect(self.add_material_row) + self.component_first_scroll_content_layout.addWidget(self.add_material_button, alignment=Qt.AlignCenter) + + def update_comp_material(self, selected_component): + materials = self.data.get(selected_component).keys() + for i in range(len(self.material_rows)): + material_combo = self.material_rows[i][KEY_TYPE] + material_combo.clear() + material_combo.addItems(materials) + + def update_comp_grades(self, selected_material, widget): + selected_component = self.component_combobox.currentText() + grades = self.data.get(selected_component,{}).get(selected_material,{}).get(KEY_GRADE,[]) + widget.clear() + widget.addItems(grades) + + def update_comp_units(self, selected_material, widget): + selected_component = self.component_combobox.currentText() + units = self.data.get(selected_component,{}).get(selected_material,{}).get(KEY_UNITS,[]) + widget.clear() + widget.addItems(units) + + def add_material_row(self): + validator = QDoubleValidator() + validator.setRange(0.0, 999999.99, 2) + validator.setBottom(0.0) + validator.setNotation(QDoubleValidator.Notation.StandardNotation) + + row_widgets = {} + row_idx = self.current_material_row_idx + + fixed_input_width = 80 + + type_material_combo = QComboBox() + type_material_combo.setObjectName("MaterialGridInput") + type_material_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(type_material_combo, row_idx, 0) + row_widgets[KEY_TYPE] = type_material_combo # CHANGE 7: Changed key to KEY_TYPE + + grade_combo = QComboBox() + type_material_combo.currentTextChanged.connect( + lambda text, widget=grade_combo: self.update_comp_grades(text, widget) + ) + grade_combo.setObjectName("MaterialGridInput") + grade_combo.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(grade_combo, row_idx, 1) + row_widgets[KEY_GRADE] = grade_combo + + quantity_edit = QLineEdit() + quantity_edit.setValidator(validator) + quantity_edit.setPlaceholderText("0") + quantity_edit.setObjectName("MaterialGridInput") + quantity_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(quantity_edit, row_idx, 2) + row_widgets[KEY_QUANTITY] = quantity_edit + + unit_combo_m3 = QComboBox() + type_material_combo.currentTextChanged.connect( + lambda text, widget=unit_combo_m3: self.update_comp_units(text, widget) + ) + unit_combo_m3.setObjectName("MaterialGridInput") + unit_combo_m3.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(unit_combo_m3, row_idx, 3) + row_widgets[KEY_UNIT_M3] = unit_combo_m3 + + rate_edit = QLineEdit() + rate_edit.setValidator(validator) + rate_edit.setPlaceholderText("0.00") + rate_edit.setObjectName("MaterialGridInput") + rate_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_edit, row_idx, 4) + row_widgets[KEY_RATE] = rate_edit + + rate_data_source_edit = QLineEdit() + rate_data_source_edit.setObjectName("MaterialGridInput") + rate_data_source_edit.setFixedWidth(fixed_input_width) + self.material_grid_layout.addWidget(rate_data_source_edit, row_idx, 5) + row_widgets[KEY_RATE_DATA_SOURCE] = rate_data_source_edit + + remove_button = QPushButton("x") + remove_button.setFixedSize(24, 24) + remove_button.setStyleSheet(""" + QPushButton { + background-color: #FFCCCC; + border: 1px solid #FF9999; + border-radius: 12px; + font-weight: bold; + line-height:12px; + padding: 0px; + color: #CC0000; + } + QPushButton:hover { + background-color: #FF9999; + color: white; + } + QPushButton:pressed { + background-color: #FF6666; + } + """) + remove_button.clicked.connect(lambda: self.remove_material_row_by_widgets(row_widgets)) + self.material_grid_layout.addWidget(remove_button, row_idx, 6) + row_widgets['remove_button'] = remove_button + + self.material_rows.append(row_widgets) + self.current_material_row_idx += 1 + self.updateGeometry() + self.adjustSize() + + + def remove_material_row_by_widgets(self, row_widgets_to_remove): + if row_widgets_to_remove not in self.material_rows: + return + + row_idx_in_grid = -1 + for i, row_dict in enumerate(self.material_rows): + if row_dict == row_widgets_to_remove: + row_idx_in_grid = i + 1 + break + + if row_idx_in_grid == -1: + return + + for col in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(row_idx_in_grid, col) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + widget.deleteLater() + elif item.layout(): + layout = item.layout() + while layout.count(): + sub_item = layout.takeAt(0) + if sub_item.widget(): + sub_item.widget().deleteLater() + self.material_grid_layout.removeItem(layout) + + self.material_rows.remove(row_widgets_to_remove) + self.current_material_row_idx -= 1 + + for r_idx in range(row_idx_in_grid, self.current_material_row_idx + 1): + for c_idx in range(self.material_grid_layout.columnCount()): + item = self.material_grid_layout.itemAtPosition(r_idx + 1, c_idx) + if item: + if item.widget(): + widget = item.widget() + self.material_grid_layout.removeWidget(widget) + self.material_grid_layout.addWidget(widget, r_idx, c_idx) + elif item.layout(): + layout = item.layout() + self.material_grid_layout.removeItem(layout) + self.material_grid_layout.addLayout(layout, r_idx, c_idx) + + self.updateGeometry() + self.update() + self.material_grid_layout.invalidate() + self.adjustSize() + +class SuperStructure(QWidget): + closed = Signal() + next = Signal(str) + back = Signal(str) + def __init__(self, database, parent=None): + super().__init__(parent) + self.database_manager = database + self.setObjectName("central_panel_widget") + self.component_widgets = [] + self.setStyleSheet(""" + #central_panel_widget { + background-color: #F8F8F8; + border-radius: 8px; + } + #central_panel_widget QLabel { + color: #333333; + font-size: 12px; + } + #central_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + QScrollArea { + + background-color: transparent; + outline: none; + } + #scroll_content_widget { + background-color: #FFF9F9; + border: 1px solid #000000; + padding-bottom: 20px; + } + + QScrollBar:vertical { + border: 1px solid #E0E0E0; + background: #F0F0F0; + width: 12px; + margin: 18px 0px 18px 0px; + border-radius: 6px; + } + + QScrollBar::handle:vertical { + background: #C0C0C0; + border: 1px solid #A0A0A0; + min-height: 20px; + border-radius: 5px; + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + QPushButton#top_button_left_panel { + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #F0E6E6; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + + #component_first_widget { + background-color: transparent; + margin-top: 10px; + } + + #component_first_scroll_content_widget { + background-color: #FFFFFF; + padding: 10px; + + border-radius: 8px; + } + + QPushButton#nav_button { + background-color: #FFFFFF; + border: 1px solid #E0E0E0; + border-radius: 8px; + color: #3F3E5E; + padding: 6px 15px; + text-align: center; + min-width: 80px; + } + QPushButton#nav_button:hover { + background-color: #F8F8F8; + border-color: #C0C0C0; + } + QPushButton#nav_button:pressed { + background-color: #E8E8E8; + border-color: #A0A0A0; + } + QComboBox { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + } + QComboBox::drop-down { + border: none; + padding-right: 5px; + } + QComboBox::down-arrow { + image: url(resources/country_arrow.png); + width: 30px; + height: 30px; + } + QComboBox QAbstractItemView { + border: 1px solid #DDDCE0; + border-radius: 5px; + background-color: #FFFFFF; + outline: none; + } + QComboBox QAbstractItemView::item:selected { + background-color: #FDEFEF; + color: #000000; + } + QComboBox QAbstractItemView::item:hover { + background-color: #FDEFEF; + } + + #MaterialGridLabel { + font-weight: bold; + color: #3F3E5E; + padding: 5px; + text-align: center; + } + #MaterialGridInput { + border: 1px solid #DDDCE0; + border-radius: 10px; + padding: 3px 10px; + background-color: #FFFFFF; + } + #MaterialGridInput:focus { + border: 1px solid #DDDCE0; + background-color: #FFFFFF; + } + QPushButton#add_material_button, QPushButton#add_component_button { + background-color: #FFFFFF; + border: 1px solid #E0E0E0; + border-radius: 8px; + color: #3F3E5E; + padding: 6px 15px; + text-align: center; + } + QPushButton#add_material_button:hover, QPushButton#add_component_button:hover { + background-color: #F8F8F8; + border-color: #C0C0C0; + } + QPushButton#add_material_button:pressed, QPushButton#add_component_button:pressed { + background-color: #E8E8E8; + border-color: #A0A0A0; + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) + left_panel_vlayout.setSpacing(0) + + self.scroll_area = QScrollArea() + self.scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_content_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) + scroll_content_widget.setObjectName("scroll_content_widget") + self.scroll_area.setWidget(scroll_content_widget) + + self.scroll_content_layout = QVBoxLayout(scroll_content_widget) + self.scroll_content_layout.setContentsMargins(0,0,0,0) + self.scroll_content_layout.setSpacing(0) + + self.add_component_button = QPushButton("+ Add Component") + self.add_component_button.setObjectName("add_component_button") + self.add_component_button.clicked.connect(self.add_component_layout) + + self.button_h_layout = QHBoxLayout() + self.button_h_layout.setSpacing(10) + self.button_h_layout.setContentsMargins(10,10,10,10) + + self.button_h_layout.addStretch(6) + + back_button = QPushButton("Back") + back_button.setObjectName("nav_button") + back_button.clicked.connect(lambda: self.back.emit(KEY_SUPERSTRUCTURE)) + self.button_h_layout.addWidget(back_button) + + next_button = QPushButton("Next") + next_button.setObjectName("nav_button") + next_button.clicked.connect(lambda: self.next.emit(KEY_SUPERSTRUCTURE)) + next_button.clicked.connect(self.save_data) + self.button_h_layout.addWidget(next_button) + + self.add_component_layout() + + self.scroll_content_layout.addLayout(self.button_h_layout) + left_panel_vlayout.addWidget(self.scroll_area) + + def add_component_layout(self): + new_component = ComponentWidget(self) + self.component_widgets.append(new_component) + new_component.remove_component_button.clicked.connect(lambda: self.remove_component_layout(new_component)) + + if self.scroll_content_layout.indexOf(self.add_component_button) != -1: + self.scroll_content_layout.removeWidget(self.add_component_button) + if self.scroll_content_layout.indexOf(self.button_h_layout) != -1: + self.scroll_content_layout.removeItem(self.button_h_layout) + + self.scroll_content_layout.addWidget(new_component) + + self.scroll_content_layout.addWidget(self.add_component_button, alignment=Qt.AlignCenter) + + self.scroll_content_layout.addLayout(self.button_h_layout) + + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def remove_component_layout(self, component_to_remove): + if component_to_remove in self.component_widgets: + self.scroll_content_layout.removeWidget(component_to_remove) + self.component_widgets.remove(component_to_remove) + component_to_remove.deleteLater() + self.scroll_area.widget().updateGeometry() + self.scroll_area.widget().adjustSize() + + def collect_data(self): + all_data = [] + for component_widget in self.component_widgets: + component_data = component_widget.collect_data() + all_data.append(component_data) + return all_data + + def save_data(self): + data = self.collect_data() + print("Collected Data:", data) + self.database_manager.input_data_row(KEY_SUPERSTRUCTURE, data) + + def expand_scroll_area(self): + self.central_widget.layout().invalidate() + + def close_widget(self): + self.closed.emit() + self.setParent(None) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/tab_widget.py b/src/osbridgelcca/desktop_app/widgets/tab_widget.py new file mode 100644 index 0000000..122bdea --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/tab_widget.py @@ -0,0 +1,214 @@ +from PySide6.QtWidgets import (QWidget, QVBoxLayout, QTabWidget, + QLabel, QMessageBox) +from PySide6.QtCore import Qt, Signal +import os + +class CustomTabWidget(QWidget): + """Combined widget that contains both tab bar and tab functionality""" + tab_close_requested = Signal(int) + + def __init__(self, parent): + super().__init__(None) + self.parent = parent + self.tab_counter = 0 + self.init_ui() + self.apply_styles() + + def init_ui(self): + """Initialize the user interface""" + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + # Create the tab widget + self.tab_widget = QTabWidget() + self.tab_widget.setTabsClosable(True) + self.tab_widget.setMovable(True) + + # Connect signals + self.tab_widget.tabCloseRequested.connect(self.close_tab) + layout.addWidget(self.tab_widget) + + def apply_styles(self): + """Apply custom styles to the widget""" + # Build absolute path for close button image + try: + base_dir = os.path.dirname(os.path.abspath(__file__)) + close_img_path = os.path.join(base_dir, "..", "resources", "close.png") + close_img_path = os.path.normpath(close_img_path) + close_img_url = close_img_path.replace("\\", "/") # for Windows compatibility + except: + close_img_url = "" # Fallback if image not found + + style = f""" + CustomTabWidget {{ + background-color: transparent; + }} + QTabWidget::pane {{ + border: none; + background-color: transparent; + }} + QTabBar::tab {{ + background-color: #FDEFEF; + border-top: 1px solid #E0E0E0; + border-left: 1px solid #E0E0E0; + border-right: 1px solid #E0E0E0; + text-align: left; + padding: 4px 10px; + color: #000000; + min-width: 80px; + }} + QTabBar::tab:selected {{ + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + color: #000000; + }} + QTabBar::tab:hover {{ + background-color: #F0E6E6; + border-color: #808080; + }} + QTabBar::tab:selected:hover {{ + background-color: #F0E6E6; + border-color: #808080; + }} + QTabBar::tab:pressed {{ + background-color: #FFF3F3; + border-color: #606060; + }} + QTabBar::tab:selected:pressed {{ + background-color: #FFF3F3; + border-color: #606060; + }} + QTabBar::close-button {{ + subcontrol-position: right; + width: 13px; + height: 13px; + margin-right: 6px; + border: 1px solid #ff5252; + border-radius: 6px; + color: white; + font-weight: bold; + }} + QTabBar::close-button:hover {{ + border-color: #f44336; + }} + """ + + # If image exists, use it; otherwise use colored button + if close_img_url and os.path.exists(close_img_url.replace("file:///", "")): + style = style.replace( + """QTabBar::close-button { + subcontrol-position: right; + width: 13px; + height: 13px; + margin-right: 6px; + border: 1px solid #ff5252; + border-radius: 6px; + color: white; + font-weight: bold; + }""", + f"""QTabBar::close-button {{ + image: url({close_img_url}); + subcontrol-position: right; + width: 13px; + height: 13px; + margin-right: 6px; + background: transparent; + border: none; + }}""" + ) + + self.setStyleSheet(style) + + def addWidget(self, widget, title): + """Add a new tab with the given widget and title""" + if not widget: + return -1 + + self.tab_counter += 1 + if not title: + title = f"Tab {self.tab_counter}" + + # Add the tab + index = self.tab_widget.addTab(widget, title) + self.tab_widget.setCurrentIndex(index) + return index + + def activate_tab(self, index): + """Activate the tab at the specified index.""" + if 0 <= index < self.tab_widget.count(): + self.tab_widget.setCurrentIndex(index) + else: + print(f"Warning: Invalid tab index: {index}. Must be between 0 and {self.tab_widget.count() - 1}.") + + def add_new_tab(self, widget=None, title=None): + """Add a new tab (alternative method name for compatibility)""" + if widget is None: + # Create a default widget if none provided + widget = QWidget() + layout = QVBoxLayout(widget) + label = QLabel(f"Content for {title or f'Tab {self.tab_counter + 1}'}") + label.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(label) + + return self.addWidget(widget, title) + + def close_tab(self, index): + """Close a tab at the given index""" + if index < 0 or index >= self.tab_widget.count(): + return False + + if self.tab_widget.count() <= 1: + QMessageBox.information( + self, + 'Close Tab', + 'This is the last tab. Do you want to close it?', + QMessageBox.StandardButton.Ok + ) + return False + + # get title + widget_name = self.tab_widget.tabText(index) + + # Fix: Check if key exists before deleting + if widget_name in self.parent.active_tab_widgets: + del self.parent.active_tab_widgets[widget_name] + + # Update indices for remaining tabs + for tab in list(self.parent.active_tab_widgets.keys()): + if self.parent.active_tab_widgets[tab] > index: + self.parent.active_tab_widgets[tab] -= 1 + + self.tab_widget.removeTab(index) + self.tab_close_requested.emit(index) + return True + + def removeTab(self, index): + """Remove tab at given index""" + self.tab_widget.removeTab(index) + + def setCurrentIndex(self, index): + """Set the current active tab""" + self.tab_widget.setCurrentIndex(index) + + def currentIndex(self): + """Get the current active tab index""" + return self.tab_widget.currentIndex() + + def count(self): + """Get the number of tabs""" + return self.tab_widget.count() + + def widget(self, index): + """Get widget at given index""" + return self.tab_widget.widget(index) + + def setTabText(self, index, text): + """Set text for tab at given index""" + self.tab_widget.setTabText(index, text) + + def tabText(self, index): + """Get text for tab at given index""" + return self.tab_widget.tabText(index) diff --git a/src/osbridgelcca/desktop_app/widgets/tab_widget_backup.py b/src/osbridgelcca/desktop_app/widgets/tab_widget_backup.py new file mode 100644 index 0000000..9fb82a3 --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/tab_widget_backup.py @@ -0,0 +1,205 @@ +from PySide6.QtWidgets import (QWidget, QVBoxLayout, QTabWidget, + QLabel, QMessageBox) +from PySide6.QtCore import Qt, Signal +import os + +class CustomTabWidget(QWidget): + """Combined widget that contains both tab bar and tab functionality""" + tab_close_requested = Signal(int) + + def __init__(self, parent): + super().__init__(None) + self.parent = parent + self.tab_counter = 0 + self.init_ui() + self.apply_styles() + + def init_ui(self): + """Initialize the user interface""" + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + # Create the tab widget + self.tab_widget = QTabWidget() + self.tab_widget.setTabsClosable(True) + self.tab_widget.setMovable(True) + + # Connect signals + self.tab_widget.tabCloseRequested.connect(self.close_tab) + layout.addWidget(self.tab_widget) + + def apply_styles(self): + """Apply custom styles to the widget""" + # Build absolute path for close button image + try: + base_dir = os.path.dirname(os.path.abspath(__file__)) + close_img_path = os.path.join(base_dir, "..", "resources", "close.png") + close_img_path = os.path.normpath(close_img_path) + close_img_url = close_img_path.replace("\\", "/") # for Windows compatibility + except: + close_img_url = "" # Fallback if image not found + + style = f""" + CustomTabWidget {{ + background-color: #f0f0f0; + }} + QTabWidget::pane {{ + border: 1px solid #c0c0c0; + background-color: white; + }} + QTabBar::tab {{ + background-color: #FDEFEF; + border-top: 1px solid #E0E0E0; + border-left: 1px solid #E0E0E0; + border-right: 1px solid #E0E0E0; + text-align: left; + padding: 4px 10px; + color: #000000; + min-width: 80px; + }} + QTabBar::tab:selected {{ + background-color: #FDEFEF; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + color: #000000; + }} + QTabBar::tab:hover {{ + background-color: #F0E6E6; + border-color: #808080; + }} + QTabBar::tab:selected:hover {{ + background-color: #F0E6E6; + border-color: #808080; + }} + QTabBar::tab:pressed {{ + background-color: #FFF3F3; + border-color: #606060; + }} + QTabBar::tab:selected:pressed {{ + background-color: #FFF3F3; + border-color: #606060; + }} + QTabBar::close-button {{ + subcontrol-position: right; + width: 13px; + height: 13px; + margin-right: 6px; + border: 1px solid #ff5252; + border-radius: 6px; + color: white; + font-weight: bold; + }} + QTabBar::close-button:hover {{ + border-color: #f44336; + }} + """ + + # If image exists, use it; otherwise use colored button + if close_img_url and os.path.exists(close_img_url.replace("file:///", "")): + style = style.replace( + """QTabBar::close-button { + subcontrol-position: right; + width: 13px; + height: 13px; + margin-right: 6px; + border: 1px solid #ff5252; + border-radius: 6px; + color: white; + font-weight: bold; + }""", + f"""QTabBar::close-button {{ + image: url({close_img_url}); + subcontrol-position: right; + width: 13px; + height: 13px; + margin-right: 6px; + background: transparent; + border: none; + }}""" + ) + + self.setStyleSheet(style) + + def addWidget(self, widget, title): + """Add a new tab with the given widget and title""" + self.tab_counter += 1 + if not title: + title = f"Tab {self.tab_counter}" + + # Add the tab + index = self.tab_widget.addTab(widget, title) + self.tab_widget.setCurrentIndex(index) + # return index + return index + + def activate_tab(self, index): + """Activate the tab at the specified index.""" + if 0 <= index < self.tab_counter: + self.setCurrentIndex(index) + else: + raise ValueError(f"Invalid tab index: {index}. Must be between 0 and {self.count() - 1}.") + + def add_new_tab(self, widget=None, title=None): + """Add a new tab (alternative method name for compatibility)""" + if widget is None: + # Create a default widget if none provided + widget = QWidget() + layout = QVBoxLayout(widget) + label = QLabel(f"Content for {title or f'Tab {self.tab_counter + 1}'}") + label.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(label) + self.addWidget(widget, title) + + def close_tab(self, index): + """Close a tab at the given index""" + if index < 0 or index >= self.tab_widget.count(): + return False + if self.tab_widget.count() <= 1: + QMessageBox.information( + self, + 'Close Tab', + 'This is the last tab. Do you want to close it?', + QMessageBox.StandardButton.Ok + ) + return False + + # get title + widget_name = self.tab_widget.tabText(index) + del self.parent.active_tab_widgets[widget_name] + + for tab in self.parent.active_tab_widgets.keys(): + if self.parent.active_tab_widgets[tab] > index: + self.parent.active_tab_widgets[tab] -= 1 + + self.tab_widget.removeTab(index) + self.tab_close_requested.emit(index) + + def removeTab(self, index): + """Remove tab at given index""" + self.tab_widget.removeTab(index) + + def setCurrentIndex(self, index): + """Set the current active tab""" + self.tab_widget.setCurrentIndex(index) + + def currentIndex(self): + """Get the current active tab index""" + return self.tab_widget.currentIndex() + + def count(self): + """Get the number of tabs""" + return self.tab_widget.count() + + def widget(self, index): + """Get widget at given index""" + return self.tab_widget.widget(index) + + def setTabText(self, index, text): + """Set text for tab at given index""" + self.tab_widget.setTabText(index, text) + + def tabText(self, index): + """Get text for tab at given index""" + return self.tab_widget.tabText(index) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/title_bar.py b/src/osbridgelcca/desktop_app/widgets/title_bar.py new file mode 100644 index 0000000..a19aefe --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/title_bar.py @@ -0,0 +1,135 @@ +from PySide6.QtCore import (QSize, Qt) +from PySide6.QtGui import (QIcon, QPixmap) +from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QWidget, QLabel) +from PySide6.QtGui import QMouseEvent + +class CustomTitleBar(QWidget): + """ + A custom title bar widget for a frameless QMainWindow. + It provides window dragging, minimize, maximize/restore, and close buttons + with custom styling and SVG icons. + """ + def __init__(self, parent=None): + super().__init__(parent) + self.parent_window = parent + self.setObjectName("custom_title_bar") + self.setFixedHeight(30) + # Set a single stylesheet for the entire title bar and all its children + self.setStyleSheet(""" + #custom_title_bar { + background-color: #45913E; + } + #custom_title_bar QLabel { + background-color: #45913E; + color: white; + } + #custom_title_bar QPushButton { + background-color: #45913E; + color: white; + border: none; + padding: 0px; + } + #custom_title_bar QPushButton:hover { + background-color: #55a04c; + } + #custom_title_bar QPushButton:pressed { + background-color: #3d7936; + } + #custom_title_bar QPushButton#close_button:hover { + background-color: #E81123; + } + #custom_title_bar QPushButton#close_button:pressed { + background-color: #F1707A; + } + """) + + # Main layout + self.layout = QHBoxLayout() + self.layout.setContentsMargins(0, 0, 0, 0) + self.layout.setSpacing(0) + self.setLayout(self.layout) + + # Left: Icon + self.icon_label = QLabel() + self.icon_label.setFixedSize(30, 30) + self.icon_label.setStyleSheet("padding: 5px;") + # self.icon_label.setPixmap(QPixmap("resources/osdag_logo.svg").scaled(20, 20, Qt.KeepAspectRatio, Qt.SmoothTransformation)) + self.layout.addWidget(self.icon_label) + + # Middle: Title (centered) + self.title_label = QLabel("My Custom App") + self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.title_label.setStyleSheet("font-weight: bold;") + self.layout.addWidget(self.title_label, 1) # Add stretch factor of 1 to center the title + + # Right: Control Buttons + self.btn_size = QSize(46, 30) + + # Helper function to create a styled button + def create_button(icon_svg, is_close=False): + btn = QPushButton() + btn.setFixedSize(self.btn_size) + btn.setIcon(QIcon(QPixmap.fromImage(QPixmap(icon_svg).toImage()))) + btn.setIconSize(QSize(14, 14)) + if is_close: + btn.setObjectName("close_button") + return btn + + # Control buttons + self.minimize_button = create_button("resources/window_minimize.svg") + self.minimize_button.clicked.connect(self.parent_window.showMinimized) + self.layout.addWidget(self.minimize_button) + + self.maximize_button = create_button("resources/window_maximize.svg") + self.maximize_button.clicked.connect(self.toggle_maximize_restore) + self.layout.addWidget(self.maximize_button) + + self.close_button = create_button("resources/window_close.svg", is_close=True) + self.close_button.clicked.connect(self.parent_window.close) + self.layout.addWidget(self.close_button) + + self.start_pos = None + self.start_geometry = None + + def set_maximize_icon(self): + self.maximize_button.setIcon(QIcon(QPixmap.fromImage(QPixmap("resources/window_maximize.svg").toImage()))) + + def set_restore_icon(self): + self.maximize_button.setIcon(QIcon(QPixmap.fromImage(QPixmap("resources/window_restore.svg").toImage()))) + + def toggle_maximize_restore(self): + """Toggles between maximized and normal window states and updates the icon.""" + if self.parent_window.isMaximized(): + self.parent_window.showNormal() + self.set_maximize_icon() + else: + self.parent_window.showMaximized() + self.set_restore_icon() + + # --- Window Dragging Functionality --- + def mousePressEvent(self, event: QMouseEvent): + """Records initial position for dragging.""" + if event.button() == Qt.LeftButton and not self.parent_window.isMaximized(): + self.start_pos = event.globalPosition().toPoint() + self.start_geometry = self.parent_window.geometry() + event.accept() + else: + event.ignore() + + def mouseMoveEvent(self, event: QMouseEvent): + """Moves the window based on mouse movement.""" + if event.buttons() == Qt.LeftButton and self.start_pos and not self.parent_window.isMaximized(): + delta = event.globalPosition().toPoint() - self.start_pos + new_x = self.start_geometry.x() + delta.x() + new_y = self.start_geometry.y() + delta.y() + self.parent_window.move(new_x, new_y) + event.accept() + else: + event.ignore() + + def mouseReleaseEvent(self, event: QMouseEvent): + """Resets dragging state.""" + if not self.parent_window.isMaximized(): + self.start_pos = None + self.start_geometry = None + event.accept() diff --git a/src/osbridgelcca/desktop_app/widgets/tutorial_widget_left.py b/src/osbridgelcca/desktop_app/widgets/tutorial_widget_left.py new file mode 100644 index 0000000..8beaa5b --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/tutorial_widget_left.py @@ -0,0 +1,282 @@ +from PySide6.QtWidgets import QApplication, QMainWindow +from PySide6.QtCore import QCoreApplication, QSize, Qt, Signal +from PySide6.QtGui import (QIcon) +from PySide6.QtWidgets import (QHBoxLayout, QTextEdit, QScrollArea, QSpacerItem, QSizePolicy, + QPushButton, QWidget, QLabel, QVBoxLayout) +import sys + +class TutorialWidget(QWidget): + closed = Signal() + def __init__(self): + super().__init__() + self.setObjectName("left_panel_widget") + self.setStyleSheet(""" + #left_panel_widget { + background-color: #F8F8F8; /* Light gray/off-white background */ + border-radius: 8px; /* Slightly rounded corners for the entire body_widget */ + border: none; + } + #left_panel_widget QLabel { + color: #333333; /* Darker text for content */ + font-size: 12px; + /* No border or padding here, managed by layout margins */ + } + #left_panel_widget QLabel#page_number_label { + font-size: 14px; + font-weight: bold; + color: #555555; + } + + /* Styling for the scroll area */ + QScrollArea { + border: 1px solid #000000; /* No border for the scroll area itself */ + background-color: transparent; /* Make background transparent to show widget's background */ + outline: none; + } + QScrollArea > QWidget { /* This targets the widget *inside* the scroll area */ + background-color: transparent; /* Inherit parent background */ + } + + /* Scrollbar styling */ + QScrollBar:vertical { + border: 1px solid #E0E0E0; /* Lighter border for scrollbar area */ + background: #F0F0F0; /* Light gray track */ + width: 12px; /* Width of the vertical scrollbar */ + margin: 18px 0px 18px 0px; /* Space for arrows */ + border-radius: 6px; /* Rounded track */ + } + + QScrollBar::handle:vertical { + background: #C0C0C0; /* Medium gray handle */ + border: 1px solid #A0A0A0; /* Darker border for handle */ + min-height: 20px; + border-radius: 5px; /* Rounded handle */ + } + + QScrollBar::add-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: bottom; + subcontrol-position: bottom; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + } + + QScrollBar::sub-line:vertical { + border: 1px solid #E0E0E0; + background: #E8E8E8; + height: 18px; + subcontrol-origin: top; + subcontrol-position: top; + border-top-left-radius: 6px; + border-top-right-radius: 6px; + } + + QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + width: 10px; + height: 10px; + } + + QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; + } + QScrollBar::up-arrow:vertical { + image: url(resources/arrow_up.png); /* You might need to provide these icons */ + } + QScrollBar::down-arrow:vertical { + image: url(resources/arrow_down.png); + } + + QScrollBar::add-line:vertical:hover, QScrollBar::sub-line:vertical:hover { + background: #D0D0D0; + } + + #left_panel_widget QPushButton#back_button { + background-color: #FFFFFF; + border: 1px solid #D0D0D0; + padding: 6px 8px; + margin: 8px; + color: #000000; + font-size: 13px; + border-radius: 8px; + } + #left_panel_widget QPushButton#back_button:hover { + background: qlineargradient( + x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #BBBBBB, + stop: 0.26 #E8E8E8, + stop: 1 #EDEDED + ); + border-color: #806C6C; + } + #left_panel_widget QPushButton#back_button:pressed { + background-color: #FFFFFF; + border-color: #606060; + } + + #left_panel_widget QPushButton#next_button { + background-color: #FFFFFF; + border: 1px solid #D0D0D0; + padding: 6px 8px; + color: #000000; + margin: 8px; + font-size: 13px; + border-radius: 8px; + } + #left_panel_widget QPushButton#next_button:hover { + background: qlineargradient( + x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #BBBBBB, + stop: 0.26 #E8E8E8, + stop: 1 #EDEDED + ); + border-color: #806C6C; + } + #left_panel_widget QPushButton#next_button:pressed { + background-color: #FFFFFF; + border-color: #606060; + } + + QPushButton#top_button_left_panel { + background-color: #F0E6E6; + border-top: 1px solid #000000; + border-left: 1px solid #000000; + border-right: 1px solid #000000; + text-align: left; + padding: 4px 10px; + color: #000000; + } + QPushButton#top_button_left_panel:hover { + background-color: #FDEFEF; + border-color: #808080; + } + QPushButton#top_button_left_panel:pressed { + background-color: #FFF3F3; + border-color: #606060; + } + QPushButton#top_button_left_panel:hover QIcon { + color: red; + } + """) + left_panel_vlayout = QVBoxLayout(self) + left_panel_vlayout.setContentsMargins(0, 0, 0, 0) # Add some padding inside the red area + left_panel_vlayout.setSpacing(0) # Spacing between major sections + + top_h_layout_left_panel = QHBoxLayout() + self.top_button_left_panel = QPushButton("Tutorials ") + self.top_button_left_panel.setIcon(QIcon("resources/close.png")) + self.top_button_left_panel.setIconSize(QSize(13, 13)) + self.top_button_left_panel.setObjectName("top_button_left_panel") + self.top_button_left_panel.setLayoutDirection(Qt.RightToLeft) + self.top_button_left_panel.clicked.connect(self.close_widget) + top_h_layout_left_panel.addWidget(self.top_button_left_panel) + + top_h_layout_left_panel.addSpacerItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) + left_panel_vlayout.addLayout(top_h_layout_left_panel) + + bordered_spacer_widget = QWidget() + bordered_spacer_widget.setObjectName("bordered_spacer_widget") # Give it an object name for CSS + bordered_spacer_widget.setFixedHeight(50) + bordered_spacer_widget.setStyleSheet(""" + #bordered_spacer_widget { + background-color: #F0E6E6; + border-left: 1px solid #000000; + border-top: 1px solid #000000; + border-right: 1px solid #000000; + } + """) + left_panel_vlayout.addWidget(bordered_spacer_widget) + + scroll_area = QScrollArea() + scroll_area.setWidgetResizable(True) + + scroll_content_widget = QWidget() + scroll_area.setWidget(scroll_content_widget) + + scroll_content_layout = QVBoxLayout(scroll_content_widget) + + middle_header_label = QLabel(""" +
+ 1/4
+ Welcome to
+ BLCCA Studio
+
+ """) + middle_header_label.setStyleSheet(""" + QLabel{ + font-size: 17px; + } + """) + middle_header_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + scroll_content_layout.addWidget(middle_header_label) + + + middle_body_label = QTextEdit() + middle_body_label.setReadOnly(True) + tutorial_text = """ + BLCCA Studio has a lot of features to offer. In the next few minutes, you will learn how to use BLCCA Studio efficiently, from setting up and managing projects to navigating the user interface. This tutorial will guide you through essential features, including customization options shortcuts, and export capabilities, ensuring a seamless workflow. Whether you're a beginner or an advanced user, this guide will help you unlock the full potential of BLCCA Studio and enhance your productivity. + """ + + middle_body_label.setHtml(tutorial_text) + middle_body_label.setAlignment(Qt.AlignmentFlag.AlignJustify) + middle_body_label.setStyleSheet(""" + QTextEdit { + font-size: 14px; + padding-top: 20px; + border-top: 2px solid #806C6C; + } + """) + scroll_content_layout.addWidget(middle_body_label) + scroll_content_widget.setStyleSheet("background-color: #FFF9F9;") + left_panel_vlayout.addWidget(scroll_area) + + bottom_widget = QWidget() + bottom_widget.setObjectName("bottom_widget") + bottom_widget.setStyleSheet(""" + #bottom_widget { + background-color: #F0E6E6; + border-left: 1px solid #000000; + border-bottom: 1px solid #000000; + border-right: 1px solid #000000; + } + """) + bottom_h_layout = QHBoxLayout(bottom_widget) + back_button = QPushButton("Back") + back_button.setObjectName("back_button") + next_button = QPushButton("Next") + next_button.setObjectName("next_button") + bottom_h_layout.addWidget(back_button) + bottom_h_layout.addWidget(next_button) + left_panel_vlayout.addWidget(bottom_widget) + + def close_widget(self): + self.closed.emit() + self.setParent(None) + +#----------------Standalone-Test-Code-------------------------------- + +# class MyMainWindow(QMainWindow): +# def __init__(self): +# super().__init__() + +# self.setStyleSheet("border: none") + +# self.central_widget = QWidget() +# self.central_widget.setObjectName("central_widget") +# self.setCentralWidget(self.central_widget) + +# self.main_h_layout = QHBoxLayout(self.central_widget) +# self.main_h_layout.addWidget(TutorialWidget(), 1) + +# self.main_h_layout.addStretch(4) + +# self.setWindowState(Qt.WindowMaximized) + + +# if __name__ == "__main__": +# QCoreApplication.setAttribute(Qt.AA_DontShowIconsInMenus, False) +# app = QApplication(sys.argv) +# window = MyMainWindow() +# window.show() +# sys.exit(app.exec()) \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/utils/data.py b/src/osbridgelcca/desktop_app/widgets/utils/data.py new file mode 100644 index 0000000..52312dc --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/utils/data.py @@ -0,0 +1,299 @@ +KEY_STRUCTURE_WORKS_DATA = "Structure Works Data" +KEY_FOUNDATION = "Foundation" +KEY_SUBSTRUCTURE = "Sub-Structure" +KEY_SUPERSTRUCTURE = "Super-Structure" +KEY_AUXILIARY = "Miscellaneous" +KEY_FINANCIAL = "Financial Data" +KEY_CARBON_EMISSION = "Carbon Emission Data" +KEY_CARBON_EMISSION_COST = "Carbon Emission Cost Data" +KEY_BRIDGE_TRAFFIC = "Bridge and Traffic Data" +KEY_MAINTAINANCE_REPAIR = "Maintenance and Repair" +KEY_DEMOLITION_RECYCLE = "Demolition and Recycling" + +KEY_GRADE = "grade" +KEY_TYPE = "type" +KEY_QUANTITY = "quantity" +KEY_UNIT_M3 = "unit_m3" +KEY_RATE = "rate" +KEY_RATE_DATA_SOURCE = "rate_data_source" +KEY_COMPONENT = "component" +KEY_UNITS = "units" +KEY_OPTIONS = "options" + + +KEY_LANES = "lanes" +KEY_ROADROUGHNESS = "road_roughness" +KEY_ROAD_RISE_AND_FALL = "road_rise_and_fall" +KEY_TYPE_OF_ROAD = "type_of_road" +KEY_ANNUAL_INCREASE = "annual_increase" + + +construction_materials = { + KEY_FOUNDATION: { + "Pile": { + "Steel Rebar": { + KEY_GRADE: ["Fe415", "Fe500", "Fe550"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Reinforced Cement Concrete": { + KEY_GRADE: ["M10", "M15", "M20", "M25", "M30", "M35", "M40", "M45", "M50", + "M55", "M60", "M65", "M70", "M75", "M80", "M85", "M90", "M95", "M100"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + } + }, + "Excavation": { + "Rock": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Soft Rock": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Medium Soil": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Clay": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Marshy Soil": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Soft Murrum": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Loam": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Stiff Clay": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Gravel": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Hard Laterite": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Marine Clay": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Other": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + }, + "Pile Cap": { + "Steel Rebar": { + KEY_GRADE: ["Fe415", "Fe500", "Fe550"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Reinforced Cement Concrete": { + KEY_GRADE: ["M10", "M15", "M20", "M25", "M30", "M35", "M40", "M45", "M50", + "M55", "M60", "M65", "M70", "M75", "M80", "M85", "M90", "M95", "M100"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + } + } + }, + + KEY_SUBSTRUCTURE: { + "Pier": { + "Steel Rebar": { + KEY_GRADE: ["Fe415", "Fe500", "Fe550"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Reinforced Cement Concrete": { + KEY_GRADE: ["M10", "M15", "M20", "M25", "M30", "M35", "M40", "M45", "M50", + "M55", "M60", "M65", "M70", "M75", "M80", "M85", "M90", "M95", "M100"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Paint": { + KEY_GRADE: ["Epoxy", "Oil Paint", "Primer"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + } + }, + "Pier Cap": { + "Steel Rebar": { + KEY_GRADE: ["Fe415", "Fe500", "Fe550"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Reinforced Cement Concrete": { + KEY_GRADE: ["M10", "M15", "M20", "M25", "M30", "M35", "M40", "M45", "M50", + "M55", "M60", "M65", "M70", "M75", "M80", "M85", "M90", "M95", "M100"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Paint": { + KEY_GRADE: ["Epoxy", "Oil Paint", "Primer"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Steel Anchor Rods": { + KEY_GRADE: ["E250", "E350"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + } + } + }, + + KEY_SUPERSTRUCTURE: { + "Girder": { + "Steel Rebar": { + KEY_GRADE: ["Fe415", "Fe500", "Fe550"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Reinforced Cement Concrete": { + KEY_GRADE: ["M10", "M15", "M20", "M25", "M30", "M35", "M40", "M45", "M50", + "M55", "M60", "M65", "M70", "M75", "M80", "M85", "M90", "M95", "M100"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Pre-stressed Cement Concrete": { + KEY_GRADE: ["M10", "M15", "M20", "M25", "M30", "M35", "M40", "M45", "M50", + "M55", "M60", "M65", "M70", "M75", "M80", "M85", "M90", "M95", "M100"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Tendons": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Structural Steel": { + KEY_GRADE: ["E250", "E350"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Shear Connectors": { + KEY_GRADE: ["E250", "E350"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Paint": { + KEY_GRADE: ["Epoxy", "Oil Paint", "Primer", "Anti-Corrosive Paint"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + } + }, + "Deck Slab": { + "Steel Rebar": { + KEY_GRADE: ["Fe415", "Fe500", "Fe550"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Reinforced Cement Concrete": { + KEY_GRADE: ["M10", "M15", "M20", "M25", "M30", "M35", "M40", "M45", "M50", + "M55", "M60", "M65", "M70", "M75", "M80", "M85", "M90", "M95", "M100"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + } + } + + }, + + KEY_AUXILIARY: { + "Bearings": { + "Structural Steel": { + KEY_GRADE: ["E250", "E350"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Rubber": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + } + }, + "Railing & Crash Barrier": { + "Reinforced Cement Concrete": { + KEY_GRADE: ["M10", "M15", "M20", "M25", "M30", "M35", "M40", "M45", "M50", + "M55", "M60", "M65", "M70", "M75", "M80", "M85", "M90", "M95", "M100"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Structural Steel": { + KEY_GRADE: ["E250", "E350"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Steel Rebar": { + KEY_GRADE: ["Fe415", "Fe500", "Fe550"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Paint": { + KEY_GRADE: ["Epoxy", "Oil Paint", "Primer", "Anti-Corrosive Paint"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + } + }, + "Drainage": { + "PVC": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Reinforced Cement Concrete": { + KEY_GRADE: ["M10", "M15", "M20", "M25", "M30", "M35", "M40", "M45", "M50", + "M55", "M60", "M65", "M70", "M75", "M80", "M85", "M90", "M95", "M100"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Structural Steel": { + KEY_GRADE: ["E250", "E350"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "FRP": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + } + }, + "Asphalt & Utilities": { + "Asphalt": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + }, + "Paint": { + KEY_GRADE: ["Epoxy", "Oil Paint", "Primer", "Anti-Corrosive Paint"], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + } + }, + "Waterproofing": { + KEY_GRADE: [], + KEY_UNITS: ["cum", "kg", "MT", "rmt", "sqm", "ltr"] + } + } + +} + + + +bridge_traffic_data = { + KEY_BRIDGE_TRAFFIC: { + KEY_LANES: { + KEY_OPTIONS: [ + "Single Lane", + "Intermediate Lane", + "Two Lane", + "Three Lane", + "Four Lane Divided Roads", + "Four Lane Divided Expressway" + ], + + }, + + KEY_ROADROUGHNESS: { + KEY_OPTIONS: ["2000", "3000", "4000", "5000", "6000", "7000", "8000", "9000", "10000"], + + }, + + KEY_ROAD_RISE_AND_FALL: { + KEY_OPTIONS: ["0", "5", "10", "15", "20", "25", "30", "35", "40", "45", "50", "55", "60", "65", "70", "75", "80", "85", "90", "95", "100"], + + }, + + KEY_TYPE_OF_ROAD: { + KEY_OPTIONS: [ + "Urban Road", + "Rural Road", + "Highway", + "Expressway" + ], + + }, + KEY_ANNUAL_INCREASE: { + KEY_OPTIONS: ["8", "9", "10", "12"], + + }, + } + +} \ No newline at end of file diff --git a/src/osbridgelcca/desktop_app/widgets/utils/database.py b/src/osbridgelcca/desktop_app/widgets/utils/database.py new file mode 100644 index 0000000..6505671 --- /dev/null +++ b/src/osbridgelcca/desktop_app/widgets/utils/database.py @@ -0,0 +1,358 @@ +import sqlite3 +from typing import List, Dict, Tuple +from .data import * + +class DatabaseManager: + """Database manager for Structure Works Data""" + + def __init__(self, db_path: str = "widgets/utils/structure_works.db", recreate: bool = True): + """ + Initialize database connection and create tables if they don't exist + + Args: + db_path: Path to the database file + recreate: If True, delete existing database and create fresh. If False, use existing database. + """ + self.db_path = db_path + self.conn = None + self.create_database(recreate=recreate) + + def create_database(self, recreate: bool = True): + """ + Create database tables with proper schema + + Args: + recreate: If True, delete existing database and create fresh + """ + import os + + # Ensure directory exists + os.makedirs(os.path.dirname(self.db_path), exist_ok=True) + + # Delete existing database if recreate is True + if recreate and os.path.exists(self.db_path): + os.remove(self.db_path) + print(f"Deleted existing database: {self.db_path}") + + self.conn = sqlite3.connect(self.db_path) + cursor = self.conn.cursor() + + # Create struct_works_data table first with comp_id as PRIMARY KEY + cursor.execute(''' + CREATE TABLE IF NOT EXISTS struct_works_data ( + comp_id INTEGER PRIMARY KEY AUTOINCREMENT, + type TEXT NOT NULL CHECK(type IN ( + 'Foundation', + 'Sub-Structure', + 'Super-Structure', + 'Miscellaneous' + )), + component_type TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # Create component table with comp_id as FOREIGN KEY + cursor.execute(''' + CREATE TABLE IF NOT EXISTS component ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + comp_id INTEGER NOT NULL, + type_material TEXT NOT NULL, + grade TEXT NOT NULL, + quantity REAL NOT NULL DEFAULT 0, + unit TEXT NOT NULL, + rate REAL NOT NULL DEFAULT 0.0, + rate_data_source TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (comp_id) REFERENCES struct_works_data(comp_id) ON DELETE CASCADE + ) + ''') + + self.conn.commit() + + def insert_structure_work(self, work_type: str, component_type: str) -> int: + """ + Insert a new structure work entry + + Args: + work_type: Type of structure work (Foundation, Sub-Structure, etc.) + component_type: Type of component (e.g., 'Pile', 'Beam', etc.) + + Returns: + comp_id: The auto-generated component ID (PRIMARY KEY) + """ + cursor = self.conn.cursor() + + cursor.execute(''' + INSERT INTO struct_works_data (type, component_type) + VALUES (?, ?) + ''', (work_type, component_type)) + + comp_id = cursor.lastrowid + self.conn.commit() + return comp_id + + def insert_component(self, comp_id: int, type_material: str, grade: str, + quantity: float, unit: str, rate: float, + rate_data_source: str = None) -> int: + """ + Insert a new component (material row) + + Args: + comp_id: Foreign key referencing struct_works_data.comp_id + type_material: Type of material (e.g., 'Steel Re', 'Concrete') + grade: Material grade (e.g., 'Fe415', 'M25') + quantity: Quantity of material + unit: Unit of measurement + rate: Rate per unit + rate_data_source: Source of rate data (optional) + + Returns: + id: The ID of the newly created component row + """ + cursor = self.conn.cursor() + cursor.execute(''' + INSERT INTO component (comp_id, type_material, grade, quantity, unit, rate, rate_data_source) + VALUES (?, ?, ?, ?, ?, ?, ?) + ''', (comp_id, type_material, grade, quantity, unit, rate, rate_data_source)) + + component_id = cursor.lastrowid + self.conn.commit() + return component_id + + def input_data_row(self, work_type: str, rows_data: List[Dict]) -> int: + """ + Input complete data row with structure work and multiple components + + Args: + work_type: Type of structure work (Foundation, Sub-Structure, etc.) + rows_data: List of dictionaries containing component data + + Returns: + comp_id: The auto-generated component ID from struct_works_data + + Example: + rows_data = [ + { + KEY_COMPONENT: "Pile", + KEY_TYPE: "Steel Re", + KEY_GRADE: "Fe415", + KEY_QUANTITY: "100", + KEY_UNIT_M3: "cum", + KEY_RATE: "5000.00", + KEY_RATE_DATA_SOURCE: "Market Survey" + }, + ... + ] + """ + if not rows_data: + raise ValueError("rows_data cannot be empty") + + for row in rows_data: + # Get component type from first row + component_type = row[0].get(KEY_COMPONENT, "Unknown") + + # Create structure work entry - this generates comp_id + comp_id = self.insert_structure_work(work_type, component_type) + + # Insert all component rows with the generated comp_id + for row_dict in row: + type_material = row_dict.get(KEY_TYPE, "") + grade = row_dict.get(KEY_GRADE, "") + quantity = float(row_dict.get(KEY_QUANTITY, 0)) + unit = row_dict.get(KEY_UNIT_M3, "") + rate = float(row_dict.get(KEY_RATE, 0.0)) + rate_data_source = row_dict.get(KEY_RATE_DATA_SOURCE, "") + + self.insert_component( + comp_id=comp_id, + type_material=type_material, + grade=grade, + quantity=quantity, + unit=unit, + rate=rate, + rate_data_source=rate_data_source + ) + + def retrieve_data_by_comp_id(self, comp_id: int) -> Dict: + """ + Retrieve complete data for a specific comp_id in the same format as input + + Args: + comp_id: The structure work ID (PRIMARY KEY) + + Returns: + Dictionary containing structure work info and list of component rows + """ + cursor = self.conn.cursor() + + # Get structure work data + cursor.execute(''' + SELECT comp_id, type, component_type, created_at, updated_at + FROM struct_works_data + WHERE comp_id = ? + ''', (comp_id,)) + + struct_work = cursor.fetchone() + if not struct_work: + return None + + # Get all components for this comp_id + cursor.execute(''' + SELECT id, type_material, grade, quantity, unit, rate, rate_data_source + FROM component + WHERE comp_id = ? + ORDER BY id + ''', (comp_id,)) + + components = cursor.fetchall() + + # Format as list of dictionaries (same as input format) + rows_data = [] + for comp in components: + row_dict = { + KEY_COMPONENT: struct_work[2], # component_type + KEY_TYPE: comp[1], # type_material + KEY_GRADE: comp[2], # grade + KEY_QUANTITY: str(comp[3]), # quantity + KEY_UNIT_M3: comp[4], # unit + KEY_RATE: str(comp[5]), # rate + KEY_RATE_DATA_SOURCE: comp[6] if comp[6] else "" # rate_data_source + } + rows_data.append(row_dict) + + return { + 'work_type': struct_work[1], + 'component_type': struct_work[2], + 'comp_id': struct_work[0], + 'rows_data': rows_data + } + + def retrieve_all_by_work_type(self, work_type: str) -> List[Dict]: + """ + Retrieve all structure works of a specific type + + Args: + work_type: Type of structure work (Foundation, Sub-Structure, etc.) + + Returns: + List of dictionaries containing all structure works of the specified type + """ + cursor = self.conn.cursor() + + cursor.execute(''' + SELECT comp_id + FROM struct_works_data + WHERE type = ? + ORDER BY comp_id + ''', (work_type,)) + + comp_ids = [row[0] for row in cursor.fetchall()] + + results = [] + for comp_id in comp_ids: + data = self.retrieve_data_by_comp_id(comp_id) + if data: + results.append(data) + + return results + + def update_component(self, component_id: int, **kwargs): + """ + Update specific fields of a component + + Args: + component_id: ID of the component to update + **kwargs: Fields to update (type_material, grade, quantity, unit, rate, rate_data_source) + """ + cursor = self.conn.cursor() + + allowed_fields = ['type_material', 'grade', 'quantity', 'unit', 'rate', 'rate_data_source'] + update_fields = {k: v for k, v in kwargs.items() if k in allowed_fields} + + if not update_fields: + return + + set_clause = ', '.join([f"{field} = ?" for field in update_fields.keys()]) + values = list(update_fields.values()) + values.append(component_id) + + query = f''' + UPDATE component + SET {set_clause}, updated_at = CURRENT_TIMESTAMP + WHERE id = ? + ''' + + cursor.execute(query, values) + self.conn.commit() + + def update_structure_work(self, comp_id: int, **kwargs): + """ + Update specific fields of a structure work + + Args: + comp_id: Component ID (PRIMARY KEY) + **kwargs: Fields to update (type, component_type) + """ + cursor = self.conn.cursor() + + allowed_fields = ['type', 'component_type'] + update_fields = {k: v for k, v in kwargs.items() if k in allowed_fields} + + if not update_fields: + return + + set_clause = ', '.join([f"{field} = ?" for field in update_fields.keys()]) + values = list(update_fields.values()) + values.append(comp_id) + + query = f''' + UPDATE struct_works_data + SET {set_clause}, updated_at = CURRENT_TIMESTAMP + WHERE comp_id = ? + ''' + + cursor.execute(query, values) + self.conn.commit() + + def delete_structure_work(self, comp_id: int): + """Delete a structure work and all its components (CASCADE)""" + cursor = self.conn.cursor() + cursor.execute('DELETE FROM struct_works_data WHERE comp_id = ?', (comp_id,)) + self.conn.commit() + + def delete_component(self, component_id: int): + """Delete a specific component by its ID""" + cursor = self.conn.cursor() + cursor.execute('DELETE FROM component WHERE id = ?', (component_id,)) + self.conn.commit() + + def get_all_structure_works(self) -> List[Tuple]: + """Get summary of all structure works""" + cursor = self.conn.cursor() + cursor.execute(''' + SELECT sw.comp_id, sw.type, sw.component_type, COUNT(c.id) as component_count + FROM struct_works_data sw + LEFT JOIN component c ON sw.comp_id = c.comp_id + GROUP BY sw.comp_id + ORDER BY sw.type, sw.comp_id + ''') + return cursor.fetchall() + + def get_components_by_comp_id(self, comp_id: int) -> List[Tuple]: + """Get all component rows for a specific comp_id""" + cursor = self.conn.cursor() + cursor.execute(''' + SELECT id, comp_id, type_material, grade, quantity, unit, rate, rate_data_source + FROM component + WHERE comp_id = ? + ORDER BY id + ''', (comp_id,)) + return cursor.fetchall() + + def close(self): + """Close database connection""" + if self.conn: + self.conn.close() \ No newline at end of file