From d71fd4becf5e0f2d1b7c2ca3ee3ca3f0ff7638f5 Mon Sep 17 00:00:00 2001 From: KatKatKateryna Date: Fri, 3 Nov 2023 16:29:27 +0000 Subject: [PATCH] add dashboard ui --- qt_ui/dockwidget_main.py | 756 ++++++++++++++++++++++-------------- qt_ui/ui/dashboard.ui | 103 +++++ qt_ui/ui/dockwidget_main.ui | 17 + qt_ui/widget_dashboard.py | 231 +++++++++++ 4 files changed, 826 insertions(+), 281 deletions(-) create mode 100644 qt_ui/ui/dashboard.ui create mode 100644 qt_ui/widget_dashboard.py diff --git a/qt_ui/dockwidget_main.py b/qt_ui/dockwidget_main.py index 014d788..f91df6c 100644 --- a/qt_ui/dockwidget_main.py +++ b/qt_ui/dockwidget_main.py @@ -3,6 +3,7 @@ from copy import copy import inspect import os +from specklepy_qt_ui.qt_ui.widget_dashboard import SpeckleDashboard from specklepy_qt_ui.qt_ui.widget_transforms import MappingSendDialog from specklepy_qt_ui.qt_ui.LogWidget import LogWidget @@ -10,33 +11,49 @@ from specklepy_qt_ui.qt_ui.logger import logToUser from specklepy_qt_ui.qt_ui.utils import constructCommitURL from specklepy_qt_ui.qt_ui.DataStorage import DataStorage from specklepy_qt_ui.qt_ui.global_resources import ( - COLOR_HIGHLIGHT, - SPECKLE_COLOR, SPECKLE_COLOR_LIGHT, - ICON_OPEN_WEB, ICON_REPORT, - ICON_LOGO, ICON_SEARCH, ICON_DELETE, ICON_DELETE_BLUE, - ICON_SEND, ICON_RECEIVE, ICON_SEND_BLACK, ICON_RECEIVE_BLACK, - ICON_SEND_BLUE, ICON_RECEIVE_BLUE, - COLOR, BACKGR_COLOR, BACKGR_COLOR_LIGHT, - ICON_XXL, ICON_RASTER, ICON_POLYGON, ICON_LINE, ICON_POINT, ICON_GENERIC, + COLOR_HIGHLIGHT, + SPECKLE_COLOR, + SPECKLE_COLOR_LIGHT, + ICON_OPEN_WEB, + ICON_REPORT, + ICON_LOGO, + ICON_SEARCH, + ICON_DELETE, + ICON_DELETE_BLUE, + ICON_SEND, + ICON_RECEIVE, + ICON_SEND_BLACK, + ICON_RECEIVE_BLACK, + ICON_SEND_BLUE, + ICON_RECEIVE_BLUE, + COLOR, + BACKGR_COLOR, + BACKGR_COLOR_LIGHT, + ICON_XXL, + ICON_RASTER, + ICON_POLYGON, + ICON_LINE, + ICON_POINT, + ICON_GENERIC, ) -from specklepy.logging.exceptions import (SpeckleException, GraphQLException) +from specklepy.logging.exceptions import SpeckleException, GraphQLException from specklepy.logging import metrics from PyQt5 import QtWidgets, uic from PyQt5.QtGui import QIcon, QPixmap, QCursor -from PyQt5.QtWidgets import QCheckBox, QListWidgetItem, QHBoxLayout, QWidget +from PyQt5.QtWidgets import QCheckBox, QListWidgetItem, QHBoxLayout, QWidget from PyQt5 import QtCore -from PyQt5.QtCore import pyqtSignal +from PyQt5.QtCore import pyqtSignal, Qt # This loads your .ui file so that PyQt can populate your plugin with the elements from Qt Designer FORM_CLASS, _ = uic.loadUiType( - os.path.join(os.path.dirname(__file__), os.path.join("ui", "dockwidget_main.ui") ) + os.path.join(os.path.dirname(__file__), os.path.join("ui", "dockwidget_main.ui")) ) -class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): +class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): closingPlugin = pyqtSignal() streamList: QtWidgets.QComboBox sendModeButton: QtWidgets.QPushButton @@ -51,8 +68,10 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): experimental: QCheckBox msgLog: LogWidget = None dataStorage: DataStorage = None - mappingSendDialog = None - custom_crs_modal = None + mappingSendDialog = None + custom_crs_modal = None + dashboard = None + iface = None signal_1 = pyqtSignal(object) signal_2 = pyqtSignal(object) @@ -81,7 +100,7 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): self.streams_remove_button.setFlat(True) self.commit_web_view.setFlat(True) self.reportBtn.setFlat(True) - #self.saveSurveyPoint.setFlat(True) + # self.saveSurveyPoint.setFlat(True) self.saveLayerSelection.setFlat(True) self.reloadButton.setFlat(True) self.closeButton.setFlat(True) @@ -91,76 +110,118 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): backgr_image_del = f"border-image: url({ICON_DELETE_BLUE});" self.streams_add_button.setIcon(QIcon(ICON_SEARCH)) self.streams_add_button.setMaximumWidth(25) - self.streams_add_button.setStyleSheet("QPushButton {padding:3px;padding-left:5px;border: none; text-align: left;} QPushButton:hover { " + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + f"{COLOR}" + " }") - + self.streams_add_button.setStyleSheet( + "QPushButton {padding:3px;padding-left:5px;border: none; text-align: left;} QPushButton:hover { " + + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + + f"{COLOR}" + + " }" + ) + self.commit_web_view.setIcon(QIcon(ICON_OPEN_WEB)) self.commit_web_view.setMaximumWidth(25) - self.commit_web_view.setStyleSheet("QPushButton {padding:3px;padding-left:5px;border: none; text-align: left;} QPushButton:hover { " + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + f"{COLOR}" + " }") - + self.commit_web_view.setStyleSheet( + "QPushButton {padding:3px;padding-left:5px;border: none; text-align: left;} QPushButton:hover { " + + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + + f"{COLOR}" + + " }" + ) + self.reportBtn.setIcon(QIcon(ICON_REPORT)) self.reportBtn.setMaximumWidth(25) - self.reportBtn.setStyleSheet("QPushButton {padding:3px;padding-left:5px;border: none; text-align: left;} QPushButton:hover { " + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + f"{COLOR}" + " }") - + self.reportBtn.setStyleSheet( + "QPushButton {padding:3px;padding-left:5px;border: none; text-align: left;} QPushButton:hover { " + + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + + f"{COLOR}" + + " }" + ) + self.streams_remove_button.setIcon(QIcon(ICON_DELETE)) self.streams_remove_button.setMaximumWidth(25) - self.streams_remove_button.setStyleSheet("QPushButton {padding:3px;padding-left:5px;border: none; text-align: left; image-position:right} QPushButton:hover { " + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + f"{COLOR}" + " }") #+ f"{backgr_image_del}" + self.streams_remove_button.setStyleSheet( + "QPushButton {padding:3px;padding-left:5px;border: none; text-align: left; image-position:right} QPushButton:hover { " + + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + + f"{COLOR}" + + " }" + ) # + f"{backgr_image_del}" - self.saveLayerSelection.setStyleSheet("QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }") - #self.saveSurveyPoint.setStyleSheet("QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }") - self.reloadButton.setStyleSheet("QPushButton {text-align: left;} QPushButton:hover { " + f"{COLOR}" + " }") - self.closeButton.setStyleSheet("QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }") + self.saveLayerSelection.setStyleSheet( + "QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }" + ) + # self.saveSurveyPoint.setStyleSheet("QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }") + self.reloadButton.setStyleSheet( + "QPushButton {text-align: left;} QPushButton:hover { " + f"{COLOR}" + " }" + ) + self.closeButton.setStyleSheet( + "QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }" + ) - - self.sendModeButton.setStyleSheet("QPushButton {padding: 10px; border: 0px; " + f"color: rgba{str(SPECKLE_COLOR)};"+ "} QPushButton:hover { " + "}" ) + self.sendModeButton.setStyleSheet( + "QPushButton {padding: 10px; border: 0px; " + + f"color: rgba{str(SPECKLE_COLOR)};" + + "} QPushButton:hover { " + + "}" + ) self.sendModeButton.setIcon(QIcon(ICON_SEND_BLUE)) - + self.receiveModeButton.setFlat(True) - self.receiveModeButton.setStyleSheet("QPushButton {padding: 10px; border: 0px;}"+ "QPushButton:hover { " + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + "}" ) + self.receiveModeButton.setStyleSheet( + "QPushButton {padding: 10px; border: 0px;}" + + "QPushButton:hover { " + + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + + "}" + ) self.receiveModeButton.setIcon(QIcon(ICON_RECEIVE_BLACK)) - self.runButton.setStyleSheet("QPushButton {color: white;border: 0px;border-radius: 17px;padding: 10px;"+ f"{BACKGR_COLOR}" + "} QPushButton:hover { "+ f"{BACKGR_COLOR_LIGHT}" + " }") - #self.runButton.setGeometry(0, 0, 150, 30) + self.runButton.setStyleSheet( + "QPushButton {color: white;border: 0px;border-radius: 17px;padding: 10px;" + + f"{BACKGR_COLOR}" + + "} QPushButton:hover { " + + f"{BACKGR_COLOR_LIGHT}" + + " }" + ) + # self.runButton.setGeometry(0, 0, 150, 30) self.runButton.setMaximumWidth(200) self.runButton.setIcon(QIcon(ICON_SEND)) - # insert checkbox + # insert checkbox l = self.verticalLayout def runSetup(self, plugin): - - #self.addDataStorage(plugin) + # self.addDataStorage(plugin) self.addLabel(plugin) self.addProps(plugin) - #self.createMappingDialog() + # self.createMappingDialog() def addProps(self, plugin): - - # add widgets that will only show on event trigger + # add widgets that will only show on event trigger logWidget = LogWidget(parent=self) logWidget.dataStorage = self.dataStorage self.layout().addWidget(logWidget) - self.msgLog = logWidget - self.msgLog.dockwidget = self - + self.msgLog = logWidget + self.msgLog.dockwidget = self + self.msgLog.active_account = plugin.dataStorage.active_account self.msgLog.speckle_version = plugin.version self.setMapping.setFlat(True) - self.setMapping.setStyleSheet("QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }") - + self.setMapping.setStyleSheet( + "QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }" + ) + self.crsSettings.setFlat(True) - self.crsSettings.setStyleSheet("QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }") - + self.crsSettings.setStyleSheet( + "QPushButton {text-align: right;} QPushButton:hover { " + f"{COLOR}" + " }" + ) + def addDataStorage(self, plugin): self.dataStorage = plugin.dataStorage - try: + try: self.dataStorage.project = plugin.project except: self.dataStorage.project = plugin.qgis_project - - def createMappingDialog(self): + def createMappingDialog(self): if self.mappingSendDialog is None: self.mappingSendDialog = MappingSendDialog(None) self.mappingSendDialog.dataStorage = self.dataStorage @@ -172,51 +233,54 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): self.mappingSendDialog.runSetup() self.mappingSendDialog.show() - def addLabel(self, plugin): + def addLabel(self, plugin): try: exitIcon = QPixmap(ICON_LOGO) exitActIcon = QIcon(exitIcon) - # create a label + # create a label text_label = QtWidgets.QPushButton(" for QGIS") - text_label.setStyleSheet("border: 0px;" - "color: white;" - f"{BACKGR_COLOR}" - "top-margin: 40 px;" - "padding: 10px;" - "padding-left: 20px;" - "font-size: 15px;" - "height: 30px;" - "text-align: left;" - ) + text_label.setStyleSheet( + "border: 0px;" + "color: white;" + f"{BACKGR_COLOR}" + "top-margin: 40 px;" + "padding: 10px;" + "padding-left: 20px;" + "font-size: 15px;" + "height: 30px;" + "text-align: left;" + ) text_label.setIcon(exitActIcon) text_label.setIconSize(QtCore.QSize(300, 93)) text_label.setMinimumSize(QtCore.QSize(100, 40)) text_label.setMaximumWidth(200) version = "" - try: - if isinstance(plugin.version, str): version = str(plugin.version) - except: pass - + try: + if isinstance(plugin.version, str): + version = str(plugin.version) + except: + pass version_label = QtWidgets.QPushButton(version) - version_label.setStyleSheet("border: 0px;" - "color: white;" - f"{BACKGR_COLOR}" - "padding-top: 15px;" - "padding-left: 0px;" - "margin-left: 0px;" - "font-size: 10px;" - "height: 30px;" - "text-align: left;" - ) + version_label.setStyleSheet( + "border: 0px;" + "color: white;" + f"{BACKGR_COLOR}" + "padding-top: 15px;" + "padding-left: 0px;" + "margin-left: 0px;" + "font-size: 10px;" + "height: 30px;" + "text-align: left;" + ) widget = QWidget() widget.setStyleSheet(f"{BACKGR_COLOR}") boxLayout = QHBoxLayout(widget) - boxLayout.addWidget(text_label) #, alignment=Qt.AlignCenter) - boxLayout.addWidget(version_label) + boxLayout.addWidget(text_label) # , alignment=Qt.AlignCenter) + boxLayout.addWidget(version_label) boxLayout.setContentsMargins(0, 0, 0, 0) self.setWindowTitle("SpeckleQGIS") self.setTitleBarWidget(widget) @@ -229,8 +293,13 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): def resizeEvent(self, event): try: QtWidgets.QDockWidget.resizeEvent(self, event) - if self.msgLog.size().height() != 0: # visible - self.msgLog.setGeometry(0, 0, self.msgLog.parentWidget.frameSize().width(), self.msgLog.parentWidget.frameSize().height()) #.resize(self.frameSize().width(), self.frameSize().height()) + if self.msgLog.size().height() != 0: # visible + self.msgLog.setGeometry( + 0, + 0, + self.msgLog.parentWidget.frameSize().width(), + self.msgLog.parentWidget.frameSize().height(), + ) # .resize(self.frameSize().width(), self.frameSize().height()) except Exception as e: return @@ -239,29 +308,28 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): self.streamBranchDropdown.clear() self.commitDropdown.clear() except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return def reloadDialogUI(self, plugin): try: self.clearDropdown() - self.populateUI(plugin) + self.populateUI(plugin) self.enableElements(plugin) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return - - def run(self, plugin): + def run(self, plugin): try: # Setup events on first load only! self.setupOnFirstLoad(plugin) # Connect streams section events self.completeStreamSection(plugin) # Populate the UI dropdowns - self.populateUI(plugin) + self.populateUI(plugin) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return def closeEvent(self, event): @@ -269,92 +337,140 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): self.closingPlugin.emit() event.accept() except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return - - def addMsg(self, obj:dict): + + def addMsg(self, obj: dict): self.msgLog.addButton(obj) def setupOnFirstLoad(self, plugin): try: - #print("setupOnFirstLoad") + # print("setupOnFirstLoad") self.msgLog.sendMessage.connect(self.addMsg) self.setMapping.clicked.connect(self.showMappingDialog) - #print("before") - #print(self.reportBtn) - #print(self.msgLog) + # print("before") + # print(self.reportBtn) + # print(self.msgLog) self.reportBtn.clicked.connect(self.msgLog.showReport) - #print("after") + self.open_dashboard.clicked.connect(self.showDashboard) - self.streams_add_button.clicked.connect( plugin.onStreamAddButtonClicked ) - self.commit_web_view.clicked.connect( lambda: plugin.openUrl(constructCommitURL(plugin.active_stream, plugin.active_branch.id, plugin.active_commit.id)) ) + self.open_dashboard.setFlat(True) + self.open_dashboard.setStyleSheet( + "QPushButton {border: 0px; color: black; padding: 10px; } QPushButton:hover { " + + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + + " };" + ) + # print("after") + + self.streams_add_button.clicked.connect(plugin.onStreamAddButtonClicked) + self.commit_web_view.clicked.connect( + lambda: plugin.openUrl( + constructCommitURL( + plugin.active_stream, + plugin.active_branch.id, + plugin.active_commit.id, + ) + ) + ) self.reloadButton.clicked.connect(lambda: self.refreshClicked(plugin)) self.closeButton.clicked.connect(lambda: self.closeClicked(plugin)) self.sendModeButton.clicked.connect(lambda: self.setSendMode(plugin)) - self.layerSendModeDropdown.currentIndexChanged.connect( lambda: self.layerSendModeChange(plugin) ) + self.layerSendModeDropdown.currentIndexChanged.connect( + lambda: self.layerSendModeChange(plugin) + ) self.receiveModeButton.clicked.connect(lambda: self.setReceiveMode(plugin)) - #self.streamBranchDropdown.currentIndexChanged.connect( lambda: self.runBtnStatusChanged(plugin) ) - self.commitDropdown.currentIndexChanged.connect( lambda: self.setActiveCommit(plugin) ) - self.commitDropdown.currentIndexChanged.connect( lambda: self.runBtnStatusChanged(plugin) ) + # self.streamBranchDropdown.currentIndexChanged.connect( lambda: self.runBtnStatusChanged(plugin) ) + self.commitDropdown.currentIndexChanged.connect( + lambda: self.setActiveCommit(plugin) + ) + self.commitDropdown.currentIndexChanged.connect( + lambda: self.runBtnStatusChanged(plugin) + ) self.closingPlugin.connect(plugin.onClosePlugin) - return + return except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return def onClickLogo(self): import webbrowser + url = "https://speckle.systems/" webbrowser.open(url, new=0, autoraise=True) - - try: metrics.track("Connector Action", self.dataStorage.active_account, {"name": "Logo Click", "connector_version": str(self.dataStorage.plugin_version)}) - except Exception as e: print(e) - + + try: + metrics.track( + "Connector Action", + self.dataStorage.active_account, + { + "name": "Logo Click", + "connector_version": str(self.dataStorage.plugin_version), + }, + ) + except Exception as e: + print(e) def refreshClicked(self, plugin): try: try: - metrics.track("Connector Action", plugin.dataStorage.active_account, {"name": "Refresh", "connector_version": str(plugin.version)}) + metrics.track( + "Connector Action", + plugin.dataStorage.active_account, + {"name": "Refresh", "connector_version": str(plugin.version)}, + ) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=plugin.dockwidget ) - + logToUser( + e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget + ) + plugin.reloadUI() except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return def closeClicked(self, plugin): try: try: - metrics.track("Connector Action", plugin.dataStorage.active_account, {"name": "Close", "connector_version": str(plugin.version)}) + metrics.track( + "Connector Action", + plugin.dataStorage.active_account, + {"name": "Close", "connector_version": str(plugin.version)}, + ) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=plugin.dockwidget ) - + logToUser( + e, level=2, func=inspect.stack()[0][3], plugin=plugin.dockwidget + ) + plugin.onClosePlugin() except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return def setSendMode(self, plugin): try: - plugin.btnAction = 0 # send + plugin.btnAction = 0 # send color = f"color: rgba{str(SPECKLE_COLOR)};" - self.sendModeButton.setStyleSheet("border: 0px;" - f"color: rgba{str(SPECKLE_COLOR)};" - "padding: 10px;") + self.sendModeButton.setStyleSheet( + "border: 0px;" f"color: rgba{str(SPECKLE_COLOR)};" "padding: 10px;" + ) self.sendModeButton.setIcon(QIcon(ICON_SEND_BLUE)) self.sendModeButton.setFlat(False) self.receiveModeButton.setFlat(True) - self.receiveModeButton.setStyleSheet("QPushButton {border: 0px; color: black; padding: 10px; } QPushButton:hover { " + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + " };") + self.receiveModeButton.setStyleSheet( + "QPushButton {border: 0px; color: black; padding: 10px; } QPushButton:hover { " + + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + + " };" + ) self.receiveModeButton.setIcon(QIcon(ICON_RECEIVE_BLACK)) self.runButton.setProperty("text", " SEND") self.runButton.setIcon(QIcon(ICON_SEND)) - # enable sections only if in "saved streams" mode - if self.layerSendModeDropdown.currentIndex() == 1: self.layersWidget.setEnabled(True) + # enable sections only if in "saved streams" mode + if self.layerSendModeDropdown.currentIndex() == 1: + self.layersWidget.setEnabled(True) self.saveLayerSelection.setEnabled(True) self.commitLabel.setEnabled(False) self.commitDropdown.setEnabled(False) @@ -367,18 +483,22 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): self.runBtnStatusChanged(plugin) return except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return - + def setReceiveMode(self, plugin): try: - plugin.btnAction = 1 # receive + plugin.btnAction = 1 # receive color = f"color: rgba{str(SPECKLE_COLOR)};" - self.receiveModeButton.setStyleSheet("border: 0px;" - f"color: rgba{str(SPECKLE_COLOR)};" - "padding: 10px;") + self.receiveModeButton.setStyleSheet( + "border: 0px;" f"color: rgba{str(SPECKLE_COLOR)};" "padding: 10px;" + ) self.sendModeButton.setIcon(QIcon(ICON_SEND_BLACK)) - self.sendModeButton.setStyleSheet("QPushButton {border: 0px; color: black; padding: 10px;} QPushButton:hover { " + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + " };") + self.sendModeButton.setStyleSheet( + "QPushButton {border: 0px; color: black; padding: 10px;} QPushButton:hover { " + + f"background-color: rgba{str(COLOR_HIGHLIGHT)};" + + " };" + ) self.receiveModeButton.setIcon(QIcon(ICON_RECEIVE_BLUE)) self.sendModeButton.setFlat(True) self.receiveModeButton.setFlat(False) @@ -386,7 +506,7 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): self.runButton.setIcon(QIcon(ICON_RECEIVE)) self.commitLabel.setEnabled(True) self.commitDropdown.setEnabled(True) - + self.layersWidget.setEnabled(False) self.messageLabel.setEnabled(False) self.messageInput.setEnabled(False) @@ -398,7 +518,7 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): self.runBtnStatusChanged(plugin) return except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return def completeStreamSection(self, plugin): @@ -409,19 +529,22 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): self.populateLayerSendModeDropdown() self.populateProjectStreams(plugin) - #self.runBtnStatusChanged(plugin) - #self.runButton.setEnabled(False) - + # self.runBtnStatusChanged(plugin) + # self.runButton.setEnabled(False) + except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return - + def setActiveCommit(self, plugin): try: - #print("__setActiveCommit") - #print(plugin.active_commit) + # print("__setActiveCommit") + # print(plugin.active_commit) if plugin.active_branch is None: - if plugin.active_stream is not None and plugin.active_stream[1] is not None: + if ( + plugin.active_stream is not None + and plugin.active_stream[1] is not None + ): branchName = self.streamBranchDropdown.currentText() for b in plugin.active_stream[1].branches.items: if b.name == branchName: @@ -429,14 +552,15 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): plugin.active_branch = b break - if plugin.active_branch is None: return - #print(plugin.active_branch.name) + if plugin.active_branch is None: + return + # print(plugin.active_branch.name) current_id_text = str(self.commitDropdown.currentText()).split(" ")[0] - #print(current_id_text) - if current_id_text == "": # populate commits still in progress + # print(current_id_text) + if current_id_text == "": # populate commits still in progress return - + if len(plugin.active_branch.commits.items) > 0: if "Latest" in current_id_text: plugin.active_commit = plugin.active_branch.commits.items[0] @@ -444,115 +568,131 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): for c in plugin.active_branch.commits.items: if c.id == current_id_text: plugin.active_commit = c - return - # only if not found: + return + # only if not found: plugin.active_commit = plugin.active_branch.commits.items[0] else: plugin.active_commit = None except Exception as e: plugin.active_commit = None - print(e) + print(e) def runBtnStatusChanged(self, plugin): try: commitStr = str(self.commitDropdown.currentText()) branchStr = str(self.streamBranchDropdown.currentText()) - - if commitStr == "": # populate commits still in progress + + if commitStr == "": # populate commits still in progress return - if plugin.btnAction == 1: # on receive - if commitStr == "": - self.runButton.setEnabled(False) - else: - self.runButton.setEnabled(True) - - if plugin.btnAction == 0: # on send - if branchStr == "": - self.runButton.setEnabled(False) - elif self.layerSendModeDropdown.currentIndex() == 1 and len(plugin.dataStorage.current_layers) == 0: # saved layers; but the list is empty + if plugin.btnAction == 1: # on receive + if commitStr == "": + self.runButton.setEnabled(False) + else: + self.runButton.setEnabled(True) + + if plugin.btnAction == 0: # on send + if branchStr == "": + self.runButton.setEnabled(False) + elif ( + self.layerSendModeDropdown.currentIndex() == 1 + and len(plugin.dataStorage.current_layers) == 0 + ): # saved layers; but the list is empty self.runButton.setEnabled(False) else: self.runButton.setEnabled(True) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return - - def layerSendModeChange(self, plugin, runMode = None): + def layerSendModeChange(self, plugin, runMode=None): try: - if self.layerSendModeDropdown.currentIndex() == 0 or runMode == 1: # by manual selection OR receive mode + if ( + self.layerSendModeDropdown.currentIndex() == 0 or runMode == 1 + ): # by manual selection OR receive mode self.layersWidget.setEnabled(False) - - elif self.layerSendModeDropdown.currentIndex() == 1 and (runMode == 0 or runMode is None): # by saved AND when Send mode + + elif self.layerSendModeDropdown.currentIndex() == 1 and ( + runMode == 0 or runMode is None + ): # by saved AND when Send mode self.layersWidget.setEnabled(True) - + branchStr = str(self.streamBranchDropdown.currentText()) if self.layerSendModeDropdown.currentIndex() == 0: - if branchStr == "": self.runButton.setEnabled(False) # by manual selection - else: self.runButton.setEnabled(True) # by manual selection - elif self.layerSendModeDropdown.currentIndex() == 1: self.runBtnStatusChanged(plugin) # by saved + if branchStr == "": + self.runButton.setEnabled(False) # by manual selection + else: + self.runButton.setEnabled(True) # by manual selection + elif self.layerSendModeDropdown.currentIndex() == 1: + self.runBtnStatusChanged(plugin) # by saved except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return def populateSavedLayerDropdown(self, plugin): - try: - #print(self.dataStorage.saved_layers) - if not self: return + # print(self.dataStorage.saved_layers) + if not self: + return self.layersWidget.clear() - self.dataStorage.current_layers.clear() + self.dataStorage.current_layers.clear() layers = self.dataStorage.saved_layers - if not layers: return - + if not layers: + return + for i, layer in enumerate(layers): - self.dataStorage.current_layers.append(layer) + self.dataStorage.current_layers.append(layer) listItem = self.fillLayerList(layer[0], layer[2]) self.layersWidget.addItem(listItem) self.layersWidget.setIconSize(QtCore.QSize(20, 20)) self.runBtnStatusChanged(plugin) - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return def populateSelectedLayerDropdown(self, plugin): - try: - if not self: return + if not self: + return self.layersWidget.clear() for layer_tuple in plugin.dataStorage.current_layers: - listItem = self.fillLayerList(layer_tuple[0], layer_tuple[2]) + listItem = self.fillLayerList(layer_tuple[0], layer_tuple[2]) self.layersWidget.addItem(listItem) self.layersWidget.setIconSize(QtCore.QSize(20, 20)) self.runBtnStatusChanged(plugin) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return - def fillLayerList(self, layer, layerType = "generic"): + def fillLayerList(self, layer, layerType="generic"): try: - icon_xxl = os.path.join(os.path.dirname(os.path.abspath(__file__)), "assets", " size-xxl.png") - listItem = QListWidgetItem(layer.name()) + icon_xxl = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "assets", " size-xxl.png" + ) + listItem = QListWidgetItem(layer.name()) - try: # if QGIS - from qgis.core import QgsVectorLayer, QgsRasterLayer, QgsIconUtils + try: # if QGIS + from qgis.core import QgsVectorLayer, QgsRasterLayer, QgsIconUtils - if isinstance(layer, QgsRasterLayer) and layer.width()*layer.height() > 1000000: - listItem.setIcon(QIcon(icon_xxl)) - - elif isinstance(layer, QgsVectorLayer) and layer.featureCount() > 20000: - listItem.setIcon(QIcon(icon_xxl)) + if ( + isinstance(layer, QgsRasterLayer) + and layer.width() * layer.height() > 1000000 + ): + listItem.setIcon(QIcon(icon_xxl)) + + elif isinstance(layer, QgsVectorLayer) and layer.featureCount() > 20000: + listItem.setIcon(QIcon(icon_xxl)) else: - from qgis.core import QgsIconUtils + from qgis.core import QgsIconUtils + icon = QgsIconUtils().iconForLayer(layer) listItem.setIcon(icon) - #print(icon) + # print(icon) except Exception as e: print(e) icons = { @@ -564,13 +704,12 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): } icon = QIcon(icons[layerType]) listItem.setIcon(icon) - - return listItem - - except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) - return + return listItem + + except Exception as e: + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) + return def enableElements(self, plugin): try: @@ -580,7 +719,8 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): self.streams_add_button.setEnabled(plugin.is_setup) self.commit_web_view.setEnabled(plugin.active_commit is not None) self.reportBtn.setEnabled(False) - if plugin.is_setup is False: self.streams_remove_button.setEnabled(plugin.is_setup) + if plugin.is_setup is False: + self.streams_remove_button.setEnabled(plugin.is_setup) self.streamBranchDropdown.setEnabled(plugin.is_setup) self.layerSendModeDropdown.setEnabled(plugin.is_setup) self.commitLabel.setEnabled(False) @@ -588,116 +728,139 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): self.show() self.setSendMode(plugin) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return - + def populateProjectStreams(self, plugin): - return + return def onActiveStreamChanged(self, plugin): try: - if not self: return + if not self: + return index = self.streamList.currentIndex() - if (len(plugin.current_streams) == 0 and index ==1) or (len(plugin.current_streams)>0 and index == len(plugin.current_streams)): + if (len(plugin.current_streams) == 0 and index == 1) or ( + len(plugin.current_streams) > 0 and index == len(plugin.current_streams) + ): self.populateProjectStreams(plugin) plugin.onStreamCreateClicked() return - if len(plugin.current_streams) == 0: return - if index == -1: return + if len(plugin.current_streams) == 0: + return + if index == -1: + return - try: plugin.active_stream = plugin.current_streams[index] - except: plugin.active_stream = None + try: + plugin.active_stream = plugin.current_streams[index] + except: + plugin.active_stream = None self.populateActiveStreamBranchDropdown(plugin) self.populateActiveCommitDropdown(plugin) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return - + def populateLayerSendModeDropdown(self): - if not self: return + if not self: + return try: self.layerSendModeDropdown.clear() self.layerSendModeDropdown.addItems( ["Send selected layers", "Send saved layers"] ) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return def populateActiveStreamBranchDropdown(self, plugin): - if not self: return + if not self: + return try: - #print("___ populateActiveStreamBranchDropdown___") - #print(plugin.active_branch) - if plugin.active_stream is None: return + # print("___ populateActiveStreamBranchDropdown___") + # print(plugin.active_branch) + if plugin.active_stream is None: + return active_branch = copy(plugin.active_branch) active_commit = copy(plugin.active_commit) - keep_branch = True # case of search by URL - if active_branch is None or active_commit is None: # case of populating from Saved Streams + keep_branch = True # case of search by URL + if ( + active_branch is None or active_commit is None + ): # case of populating from Saved Streams keep_branch = False - #print(active_branch) + # print(active_branch) - #print(1) - self.streamBranchDropdown.clear() # activates "populate commit" - #print(2) - if isinstance(plugin.active_stream[1], SpeckleException): - logToUser("Some streams cannot be accessed", level = 1, plugin = self) + # print(1) + self.streamBranchDropdown.clear() # activates "populate commit" + # print(2) + if isinstance(plugin.active_stream[1], SpeckleException): + logToUser("Some streams cannot be accessed", level=1, plugin=self) return - elif plugin.active_stream is None or plugin.active_stream[1] is None or plugin.active_stream[1].branches is None: + elif ( + plugin.active_stream is None + or plugin.active_stream[1] is None + or plugin.active_stream[1].branches is None + ): return - #print(3) - #print(plugin.active_branch) + # print(3) + # print(plugin.active_branch) - # here the commit dropdown is triggered + # here the commit dropdown is triggered self.streamBranchDropdown.addItems( [f"{branch.name}" for branch in plugin.active_stream[1].branches.items] ) - #print(4) + # print(4) self.streamBranchDropdown.addItems(["Create New Branch"]) - #print(5) + # print(5) if keep_branch is True: plugin.active_branch = active_branch plugin.active_commit = active_commit - #print(plugin.active_branch) + # print(plugin.active_branch) - # set index to current (if added from URL) - if plugin.active_branch is not None and plugin.active_branch in plugin.active_stream[1].branches.items: - #print("__________SET BRANCH TEXT") - #print(plugin.active_branch.name) + # set index to current (if added from URL) + if ( + plugin.active_branch is not None + and plugin.active_branch in plugin.active_stream[1].branches.items + ): + # print("__________SET BRANCH TEXT") + # print(plugin.active_branch.name) if keep_branch is True: plugin.active_branch = active_branch plugin.active_commit = active_commit - #print(plugin.active_branch.name) - self.streamBranchDropdown.setCurrentText(plugin.active_branch.name) # activates "populate commit" - #print(6) - + # print(plugin.active_branch.name) + self.streamBranchDropdown.setCurrentText( + plugin.active_branch.name + ) # activates "populate commit" + # print(6) + except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) return def populateActiveCommitDropdown(self, plugin): - if not self: return + if not self: + return try: - #print("________populateActiveCommitDropdown") - #print(plugin.active_commit) - if plugin.active_stream is None: + # print("________populateActiveCommitDropdown") + # print(plugin.active_commit) + if plugin.active_stream is None: print("Active stream is None") return branchName = self.streamBranchDropdown.currentText() - #print(f"CURRENT BRANCH TEXT: {branchName}") - if branchName == "": return - if branchName == "Create New Branch": + # print(f"CURRENT BRANCH TEXT: {branchName}") + if branchName == "": + return + if branchName == "Create New Branch": self.streamBranchDropdown.setCurrentText("main") plugin.onBranchCreateClicked() return branch = None - - #print("__clear commit dropdwn") - #print(plugin.active_commit) + + # print("__clear commit dropdwn") + # print(plugin.active_commit) self.commitDropdown.clear() - if isinstance(plugin.active_stream[1], SpeckleException): - logToUser("Some streams cannot be accessed", level = 1, plugin = self) + if isinstance(plugin.active_stream[1], SpeckleException): + logToUser("Some streams cannot be accessed", level=1, plugin=self) return elif plugin.active_stream[1]: for b in plugin.active_stream[1].branches.items: @@ -705,47 +868,78 @@ class SpeckleQGISDialog(QtWidgets.QDockWidget, FORM_CLASS): branch = b plugin.active_branch = b break - - if len(branch.commits.items)>0: + + if len(branch.commits.items) > 0: commits = [] commits.append("") - #commits.append("Latest commit from this branch") - #self.commitDropdown.addItem("Latest commit from this branch") - - #commits = [] + # commits.append("Latest commit from this branch") + # self.commitDropdown.addItem("Latest commit from this branch") + + # commits = [] for commit in branch.commits.items: - sourceApp = str(commit.sourceApplication).replace(" ","").split(".")[0].split("-")[0] - commits.append(f"{commit.id}"+ " | " + f"{sourceApp}" + " | " + f"{commit.message}") + sourceApp = ( + str(commit.sourceApplication) + .replace(" ", "") + .split(".")[0] + .split("-")[0] + ) + commits.append( + f"{commit.id}" + + " | " + + f"{sourceApp}" + + " | " + + f"{commit.message}" + ) self.commitDropdown.addItems(commits) - # set index to current (if added from URL) - if plugin.active_commit is not None and plugin.active_commit in branch.commits.items: - #print("set index to current (if added from URL) ") - #print(plugin.active_commit) - self.commitDropdown.setCurrentText(f"{plugin.active_commit.id}"+ " | " + f"{plugin.active_commit.sourceApplication}" + " | " + f"{plugin.active_commit.message}") - else: #overwrite active commit if plugin.active_commit is None: - #print("set index to 1st") + # set index to current (if added from URL) + if ( + plugin.active_commit is not None + and plugin.active_commit in branch.commits.items + ): + # print("set index to current (if added from URL) ") + # print(plugin.active_commit) + self.commitDropdown.setCurrentText( + f"{plugin.active_commit.id}" + + " | " + + f"{plugin.active_commit.sourceApplication}" + + " | " + + f"{plugin.active_commit.message}" + ) + else: # overwrite active commit if plugin.active_commit is None: + # print("set index to 1st") plugin.active_commit = branch.commits.items[0] - else: + else: plugin.active_commit = None - + self.commitDropdown.setItemText(0, "Latest commit from this branch") - # enable or disable web view button - #print("_________ENABLE OR DISABLE") - #print(plugin.active_commit) - #print(f"CURRENT TEXT2: {self.streamBranchDropdown.currentText()}") - if plugin.active_commit is not None and plugin.btnAction == 1: + # enable or disable web view button + # print("_________ENABLE OR DISABLE") + # print(plugin.active_commit) + # print(f"CURRENT TEXT2: {self.streamBranchDropdown.currentText()}") + if plugin.active_commit is not None and plugin.btnAction == 1: self.commit_web_view.setEnabled(True) - else: + else: self.commit_web_view.setEnabled(False) except Exception as e: - logToUser(e, level = 2, func = inspect.stack()[0][3], plugin=self) - #print(str(e) + "::" + str(inspect.stack()[0][3])) + logToUser(e, level=2, func=inspect.stack()[0][3], plugin=self) + # print(str(e) + "::" + str(inspect.stack()[0][3])) return def onStreamRemoveButtonClicked(self, plugin): - return + return def cancelOperations(self): - return + return + + def showDashboard(self): + if self.dashboard is None: + self.dashboard = SpeckleDashboard() + self.dashboard.dataStorage = self.dataStorage + self.iface.addDockWidget(Qt.LeftDockWidgetArea, self.dashboard) + self.dashboard.update() + self.dashboard.show() + else: + self.dashboard.update() + self.dashboard.show() diff --git a/qt_ui/ui/dashboard.ui b/qt_ui/ui/dashboard.ui new file mode 100644 index 0000000..0199765 --- /dev/null +++ b/qt_ui/ui/dashboard.ui @@ -0,0 +1,103 @@ + + + SpeckleDashboard + + + + 0 + 0 + 575 + 651 + + + + + Speckle Dashboard + + + + + + + + + + + 10 + + + 10 + + + 10 + + + 10 + + + + + + + + + + + + Model properties + + + + + + + + + + + + QAbstractItemView::NoSelection + + + + 0 + 0 + + + + QAbstractScrollArea::AdjustToContents + + + QListView::Fixed + + + QListView::ListMode + + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + diff --git a/qt_ui/ui/dockwidget_main.ui b/qt_ui/ui/dockwidget_main.ui index 871efc3..d001808 100644 --- a/qt_ui/ui/dockwidget_main.ui +++ b/qt_ui/ui/dockwidget_main.ui @@ -334,6 +334,23 @@ + + + + + + + true + + + Open Dashboard + + + + + + + diff --git a/qt_ui/widget_dashboard.py b/qt_ui/widget_dashboard.py new file mode 100644 index 0000000..8eb8c4d --- /dev/null +++ b/qt_ui/widget_dashboard.py @@ -0,0 +1,231 @@ +import random +from qgis.PyQt import QtWidgets, uic +from qgis.PyQt import QtGui +from qgis.PyQt.QtGui import QIcon, QPixmap +from qgis.PyQt.QtWidgets import ( + QCheckBox, + QListWidgetItem, + QAction, + QDockWidget, + QVBoxLayout, + QHBoxLayout, + QWidget, + QLabel, +) +from qgis.PyQt import QtCore +from qgis.PyQt.QtCore import pyqtSignal, Qt + +# from qgis.PyQt import QtCore, QtWidgets #, QtWebEngineWidgets +from PyQt5 import * +from PyQt5.QtCore import QUrl +import plotly.express as px +from PyQt5.QtWebKitWidgets import QWebView +import pandas as pd +import os + +FORM_CLASS, _ = uic.loadUiType( + os.path.join(os.path.join(os.path.dirname(__file__), "ui", "dashboard.ui")) +) + + +class SpeckleDashboard(QtWidgets.QDockWidget, FORM_CLASS): + closingPlugin = pyqtSignal() + dataStorage = None + + dataNumeric: dict = {} + dataText: dict = {} + + current_filter: str = "" + current_index: int = -1 + selectionDropdown: QtWidgets.QComboBox + dataWidget: QtWidgets.QListWidget + chart: QWidget + browser = None + existing_web: int = 0 + + def __init__(self, parent=None): + """Constructor.""" + super(SpeckleDashboard, self).__init__(parent) + # Set up the user interface from Designer through FORM_CLASS. + # After self.setupUi() you can access any designer object by doing + # self., and you can use autoconnect slots - see + # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html + # #widgets-and-dialogs-with-auto-connect + + self.browser = QWebView(self) # https://github.com/qgis/QGIS/issues/26048 + self.setupUi(self) + + self.selectionDropdown.clear() + self.selectionDropdown.addItems(["area", "property value"]) + self.selectionDropdown.setCurrentIndex(0) + + self.selectionDropdown.currentIndexChanged.connect(self.populateUI) + + def setup(self): + self.dataWidget.clear() + for i, (key, val) in enumerate(self.dataNumeric.items()): + # property_filter = self.selectionDropdown.currentText() + if self.current_filter in key or key in self.current_filter: + listItem = QListWidgetItem(f"{key}: {val}") + self.dataWidget.addItem(listItem) + + def populateUI(self, force=0): + print(self.selectionDropdown.currentIndex()) + print(self.current_filter) + if self.selectionDropdown.currentText() == "": + self.current_filter = "area" + self.current_index = 0 + self.setup() + self.selectionDropdown.setIndex(0) + + self.createChart() + elif self.selectionDropdown.currentText() != self.current_filter: + self.current_filter = self.selectionDropdown.currentText() + self.current_index = self.selectionDropdown.currentIndex() + self.setup() + self.createChart() + if force == 1: + self.setup() + self.createChart() + + def update(self): + self.dataNumeric = {} + self.dataText = {} + + for i in range(10): + # keys = ["key1", "key2", "key3", "key4", "key5"] + # key = keys[random.randint(0, 4)] + value = random.randint(10, 100) + self.dataNumeric.update({"index": i, f"area {random.randint(1, 4)}": value}) + for i in range(10): + # keys = ["key1", "key2", "key3", "key4", "key5"] + # key = keys[random.randint(0, 4)] + value = random.randint(10, 100) + self.dataNumeric.update( + { + "index": i + 10, + f"property value {random.randint(1, 4)}": [ + value, + value * 0.5, + value * 2, + ], + } + ) + + r""" + layer = getLayerByName(self.dataStorage.project, "Speckle_dashboard") + fields = layer.fields() + for i, key in enumerate(fields.names()): + if key in ["Branch URL", "commit_id", "updated"]: + continue + + value = None + variant = fields.at(i).type() + + if "value" in key: + value = [] + if "area" in key: + value = 0 + + for feat in layer.getFeatures(): + if isinstance(value, List): + if feat[key] is not None: + list_vals = ( + feat[key] + .replace("[", "") + .replace("]", "") + .replace("'", "") + .split(",") + ) + value.extend([x for x in list_vals if x != ""]) + print(value) + + elif isinstance(value, float) or isinstance(value, int): + if feat[key] is not None: + value += feat[key] + + self.dataNumeric.update({key: value}) + """ + + self.populateUI(force=1) + + def createChart(self): + # https://stackoverflow.com/questions/60522103/how-to-have-plotly-graph-as-pyqt5-widget + print("PRINT DATAFRAME") + df = pd.DataFrame.from_dict(self.dataNumeric, orient="index", columns=["value"]) + df2 = df.reset_index() + print(df2) + + property_filter = str(self.selectionDropdown.currentText()) + if len(property_filter) <= 1: + property_filter = "area" + df2 = df2[df2["index"].str.lower().str.contains(property_filter)] + print(df2) + + if "area" in property_filter: + fig = px.pie( + df2, + values="value", + names="index", + title="Land use distribution", + hole=0.5, + ) + + elif "value" in property_filter: + all_column_vals = df2["value"].to_list() + + all_column_vals_separated = [] + [all_column_vals_separated.extend(x) for x in all_column_vals] + + all_vals = [float(x) for x in all_column_vals_separated] + df2 = pd.DataFrame([{property_filter: val} for val in all_vals]) + fig = px.histogram(df2, x=property_filter, title="Property values") + else: + df2 = df2.loc["area" in df2["index"]] + fig = px.pie( + df2, values="value", names="index", title="Land use distribution" + ) + print(df2) + + # remove all buttons + # try: + # for i in reversed(range(self.chart.layout.count())): + # self.chart.layout.itemAt(i).widget().setParent(None) + # except: pass + + if self.existing_web == 0: + self.browser = QWebView(self) + + self.browser.setHtml(fig.to_html(include_plotlyjs="cdn")) + # self.browser.setUrl(QUrl("https://speckle.xyz/streams/e2effcfa27/commits/f76cedd9a6")) + + self.chart.layout = QHBoxLayout(self.chart) + self.browser.setMaximumHeight(400) + + if self.existing_web == 0: + self.chart.layout.addWidget(self.browser) + self.existing_web = 1 + + return + # https://www.pythonguis.com/tutorials/plotting-pyqtgraph/ + + graphWidget = pg.PlotWidget() + + hour = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + temperature = [30, 32, 34, 32, 33, 31, 29, 32, 35, 45] + + # plot data: x, y values + graphWidget.plot(hour, temperature) + + # remove all buttons + try: + for i in reversed(range(self.chart.layout.count())): + self.chart.layout.itemAt(i).widget().setParent(None) + except: + pass + + self.chart.layout = QHBoxLayout(self.chart) + self.chart.layout.addWidget(graphWidget) + + # set the QWebEngineView instance as main widget + # self.setCentralWidget(plot_widget)