class DateFrame(BaseFrame): def __init__(self, recovered, parent=None): super().__init__(parent) self.label.setText("Date:") self.date_edit = QDateEdit(QDate.currentDate()) self.date_edit.setCalendarPopup(True) self.date_edit.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) if recovered: self.date_edit.setEnabled(False) self.layout.addWidget(self.label) self.layout.addWidget(self.date_edit) self.setLayout(self.layout)
class AddDecrementWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) # Creating and setting the components and their characteristics size = QSize(400, 250) self.setFixedSize(size) self.main_layout = QVBoxLayout() title: str = centralize_text("Добавить расход продукта") self.title = QLabel(title, parent=self) self.data_layout = QHBoxLayout() self.quantity_layout = QVBoxLayout() self.quantity_title = QLabel("Кол-во:", parent=self) self.quantity_value = QDoubleSpinBox(parent=self) self.quantity_value.setSingleStep(1.0) self.quantity_value.setRange(0.0, 1000000.0) self.quantity_value.setDecimals(3) self.quantity_checker = QCheckBox("На 1 ребёнка", parent=self) self.date_layout = QVBoxLayout() self.date_title = QLabel("Выберите дату:", parent=self) self.today_radio = QRadioButton("Сегодня", parent=self) self.today_radio.setChecked(True) self.today_radio.clicked.connect(self.disable_date_edit) self.otherday_radio = QRadioButton("Другой день", parent=self) self.otherday_radio.clicked.connect(self.enable_date_edit) self.otherday_edit = QDateEdit(date=date.today(), parent=self) self.otherday_edit.setDisabled(True) self.button = QPushButton("Добавить &расход", parent=self) # Locating the components in the widget self.setLayout(self.main_layout) self.main_layout.addWidget(self.title) self.main_layout.addLayout(self.data_layout) self.data_layout.addLayout(self.quantity_layout) self.quantity_layout.addWidget(self.quantity_title) self.quantity_layout.addWidget(self.quantity_value) self.quantity_layout.addWidget(self.quantity_checker) self.data_layout.addLayout(self.date_layout) self.date_layout.addWidget(self.date_title) self.date_layout.addWidget(self.today_radio) self.date_layout.addWidget(self.otherday_radio) self.date_layout.addWidget(self.otherday_edit) self.main_layout.addWidget(self.button) def enable_date_edit(self): # The user would choose another date self.otherday_edit.setEnabled(True) def disable_date_edit(self): # The user chose today self.otherday_edit.setDate(date.today()) self.otherday_edit.setDisabled(True)
class EgimDownloaderFrame(QFrame): """Frame to download data from EMSODEV servers""" # Signals msg2Statusbar = pyqtSignal(str) wf2plotSplitter = pyqtSignal(WaterFrame) class DownloadParameterThread(QThread): """ The process to download data from the API is very slow. We are going to use this thread to download data without block the app. """ def __init__(self, downloader): QThread.__init__(self) self.downloader = downloader def __del__(self): self.wait() def run(self): if self.downloader.instrumentList.currentItem().text() \ == "icListen-1636": date = datetime.datetime.strptime( self.downloader.dateList.currentItem().text(), "%Y-%m-%d").strftime("%d/%m/%Y") self.downloader.downloadAcoustic( date, self.downloader.hourMinuteList.currentItem().text()) else: parameters = [item.text() for item in self.downloader.parameterList.selectedItems()] for parameter in parameters: self.downloader.downloadParameter(parameter) def __init__(self): super().__init__() # Instance variables self.downloader = EGIM() self.wf = WaterFrame() self.metadata = dict() self.dates = [] self.myThread = None # Save the login of the EMSODEV API self.downloader.login = "******" self.downloader.password = "" self.initUI() def initUI(self): # Buttons downloadButton = QPushButton("Download", self) downloadButton.clicked.connect(self.downloadClick) downloadButton.setEnabled(False) closeButton = QPushButton("Close", self) closeButton.clicked.connect(self.hide) # Lists self.egimList = QListWidget(self) self.egimList.itemClicked.connect(self.loadInstruments) self.egimList.setMaximumWidth(200) self.instrumentList = QListWidget(self) self.instrumentList.itemClicked.connect(self.loadParameters) self.instrumentList.setMaximumWidth(290) self.metadataList = QListWidget(self) self.parameterList = QListWidget(self) self.parameterList.setSelectionMode( QAbstractItemView.ExtendedSelection) self.parameterList.itemClicked.connect( lambda: downloadButton.setEnabled(True)) self.dateList = QListWidget(self) self.dateList.itemClicked.connect(self.loadTimes) self.dateList.setMaximumWidth(150) self.hourMinuteList = QListWidget(self) self.hourMinuteList.itemClicked.connect( lambda: downloadButton.setEnabled(True)) self.hourMinuteList.setMaximumWidth(150) # Labels egimLabel = QLabel("EGIM", self) instrumentLabel = QLabel("Instrument", self) metadataLabel = QLabel("Metadata", self) parameterLabel = QLabel("Parameter", self) startDateLabel = QLabel("Start date", self) endDateLabel = QLabel("End date", self) limitLabel = QLabel("Get last X values", self) hourLabel = QLabel("Hour and minute (HHMM)", self) dateLabel = QLabel("Available dates", self) # Date edit self.startDateEdit = QDateEdit(self) self.startDateEdit.setCalendarPopup(True) self.startDateEdit.setDateTime(QDateTime(QDate(2017, 1, 27), QTime(0, 0, 0))) self.startDateEdit.setMinimumDateTime(QDateTime(QDate(2017, 1, 27), QTime(0, 0, 0))) self.endDateEdit = QDateEdit(self) self.endDateEdit.setCalendarPopup(True) self.endDateEdit.setDateTime(QDateTime(QDate(2017, 1, 27), QTime(0, 0, 0))) self.endDateEdit.setMinimumDateTime(QDateTime(QDate(2017, 1, 27), QTime(0, 0, 0))) # Spin box self.limitSpinBox = QSpinBox(self) self.limitSpinBox.setMinimum(0) self.limitSpinBox.setMaximum(9999999999) self.limitSpinBox.setSingleStep(100) self.limitSpinBox.valueChanged.connect(self.enableDate) # Custom Widgets # Widget for dates of the acoustic data self.acousticDateWidget = QWidget(self) # - Layout vAcousticDate = QVBoxLayout() vAcousticDate.addWidget(dateLabel) vAcousticDate.addWidget(self.dateList) vAcousticDate.addWidget(hourLabel) vAcousticDate.addWidget(self.hourMinuteList) self.acousticDateWidget.setLayout(vAcousticDate) self.acousticDateWidget.setMaximumWidth(175) self.acousticDateWidget.setEnabled(False) # Widget for dates of parameters self.parameterDateWidget = QWidget(self) # - Layout vparameterDate = QVBoxLayout() vparameterDate.addWidget(startDateLabel) vparameterDate.addWidget(self.startDateEdit) vparameterDate.addWidget(endDateLabel) vparameterDate.addWidget(self.endDateEdit) vparameterDate.addWidget(limitLabel) vparameterDate.addWidget(self.limitSpinBox) vparameterDate.addStretch() self.parameterDateWidget.setLayout(vparameterDate) self.parameterDateWidget.setEnabled(False) # Layout # - Vertical layout for EGIM -- vEgim = QVBoxLayout() vEgim.addWidget(egimLabel) vEgim.addWidget(self.egimList) # -- Vertical layout for instruments - vInstrument = QVBoxLayout() vInstrument.addWidget(instrumentLabel) vInstrument.addWidget(self.instrumentList) # - Vertical layout for parameters - vParameter = QVBoxLayout() vParameter.addWidget(metadataLabel) vParameter.addWidget(self.metadataList) vParameter.addWidget(parameterLabel) vParameter.addWidget(self.parameterList) # - Vertical layout for dates and buttons vButton = QVBoxLayout() vButton.addWidget(downloadButton) vButton.addWidget(closeButton) vButton.addStretch() # - Layout of the frame - hFrame = QHBoxLayout() hFrame.addLayout(vEgim) hFrame.addLayout(vInstrument) hFrame.addLayout(vParameter) hFrame.addWidget(self.parameterDateWidget) hFrame.addWidget(self.acousticDateWidget) hFrame.addLayout(vButton) self.setLayout(hFrame) def loadObservatories(self): """ It asks for the available EGIM observatories and write its names into self.egimList """ # Send a message for the statusbar self.msg2Statusbar.emit("Loading observatories") # Clear self.egimList self.egimList.clear() # Ask for the observatories code, observatoryList = self.downloader.observatories() if code: if code == 200: # It means that you are going good self.egimList.addItems(observatoryList) # Send a message for the statusbar self.msg2Statusbar.emit("Ready") elif code == 401: self.msg2Statusbar.emit( "Unauthorized to use the EMSODEV DMP API") self.downloader.password = None self.reload() elif code == 404: self.msg2Statusbar.emit("Not Found") elif code == 403: self.msg2Statusbar.emit("Forbidden") elif code == 500: self.msg2Statusbar.emit("EMSODEV API internal error") else: self.msg2Statusbar.emit("Unknown EMSODEV DMP API error") else: self.msg2Statusbar.emit( "Impossible to connect to the EMSODEV DMP API") def loadInstruments(self, observatory): """ It asks for the available instruments and write its names into self.instrumentList Parameters ---------- observatory: item item from self.observatoryList """ # Send a message for the statusbar self.msg2Statusbar.emit("Loading instruments") # Clear self.instrumentList self.instrumentList.clear() # Ask for instruments code, instrumentList_ = self.downloader.instruments(observatory.text()) if code: if code == 200: # It means that you are going good # Obtain all sensor names of instrumentList_ sensorType = [ instrument['name'] for instrument in instrumentList_] self.instrumentList.addItems(sensorType) # Add tooltip for i in range(self.instrumentList.count()): self.instrumentList.item(i).setToolTip( '<p><b>Sensor Type</b><br>' + '{}</p><p>'.format(instrumentList_[i]['sensorType']) + '<b>Long Name</b><br>' + '{}</p>'.format(instrumentList_[i]['sensorLongName']) + '<p></p><p><b>S/N</b><br>' + '{}</p>'.format(instrumentList_[i]['sn'])) # Send a message for the statusbar self.msg2Statusbar.emit("Ready") elif code == 401: self.msg2Statusbar.emit( "Unauthorized to use the EMSODEV DMP API") self.downloader.password = None self.reload() elif code == 404: self.msg2Statusbar.emit("Not Found") elif code == 403: self.msg2Statusbar.emit("Forbidden") elif code == 500: self.msg2Statusbar.emit("EMSODEV API internal error") else: self.msg2Statusbar.emit("Unknown EMSODEV DMP API error") else: self.msg2Statusbar.emit( "Impossible to connect to the EMSODEV DMP API") def loadParameters(self, instrument): """ It asks for the available parameters and metadata and write them into self.parameterList and self.metadataList """ # Send a message for the statusbar self.msg2Statusbar.emit("Loading parameters") # Clear self.parameterList and self.metadataList self.parameterList.clear() self.metadataList.clear() self.parameterDateWidget.setEnabled(False) self.acousticDateWidget.setEnabled(False) # If instrument is an icListener, check times if instrument.text() == "icListen-1636": self.acousticDateWidget.setEnabled(True) # Ask for dates code, self.dates = self.downloader.acoustic_date( self.egimList.currentItem().text(), instrument.text()) if code == 200: dateList = [ date['acousticObservationDate'] for date in self.dates] self.dateList.addItems(dateList) else: self.msg2Statusbar.emit( "Impossible to connect to the EMSODEV DMP API") return return self.parameterDateWidget.setEnabled(True) # Ask for metadata code, self.metadata = self.downloader.metadata( self.egimList.currentItem().text(), instrument.text()) if code == 200: items = [] for key, value in self.metadata.items(): items.append("{}: {}".format(key, value)) self.metadataList.addItems(items) else: self.msg2Statusbar.emit( "Impossible to connect to the EMSODEV DMP API") return # Ask for parameters code, parameterList_ = self.downloader.parameters( self.egimList.currentItem().text(), instrument.text()) if code: if code == 200: # It means that you are going good # Obtain all parameter names of parameterList_ names = [parameter['name'] for parameter in parameterList_] self.parameterList.addItems(names) self.parameterList.sortItems() # Add tooltip for i in range(self.parameterList.count()): self.parameterList.item(i).setToolTip( '<b>Units:</b> {}'.format(parameterList_[i]['uom'])) # Send a message for the statusbar self.msg2Statusbar.emit("Ready") elif code == 401: self.msg2Statusbar.emit( "Unauthorized to use the EMSODEV DMP API") self.downloader.password = None self.reload() elif code == 404: self.msg2Statusbar.emit("Not Found") elif code == 403: self.msg2Statusbar.emit("Forbidden") elif code == 500: self.msg2Statusbar.emit("EMSODEV API internal error") else: self.msg2Statusbar.emit("Unknown EMSODEV DMP API error") else: self.msg2Statusbar.emit( "Impossible to connect to the EMSODEV DMP API") def loadTimes(self, date_item): """ Write items into self.hourMinuteList QListWidget """ for date in self.dates: if date['acousticObservationDate'] == date_item.text(): timeList = [] for time in date['observationsHourMinuteList']: timeList.append(time['acousticObservationHourMinute']) self.hourMinuteList.addItems(timeList) def reload(self): """It clear all lists and load again the observatories.""" # Check the password of the API if self.downloader.password is None: self.msg2Statusbar.emit( "Password is required to download data from EMSODEV") text, ok = QInputDialog.getText(self, "Attention", "Password", QLineEdit.Password) if ok: self.downloader.password = text else: return self.loadObservatories() def downloadClick(self): """Function when user click download""" self.myThread = self.DownloadParameterThread(self) self.myThread.start() def downloadParameter(self, parameter): """It download data with the observation function of EGIM""" # Send a message for the statusbar self.msg2Statusbar.emit("Downloading {}".format(parameter)) code, df = self.downloader.observation( observatory=self.egimList.currentItem().text(), instrument=self.instrumentList.currentItem().text(), parameter=parameter, startDate=self.startDateEdit.text(), endDate=self.endDateEdit.text(), limit=self.limitSpinBox.text()) if code: if code == 200: self.msg2Statusbar.emit("Waterframe creation") # It means that you are going good wf = self.downloader.to_waterframe(data=df, metadata=self.metadata) # print(wf.data.head()) # Send a signal with the new WaterFrame self.wf2plotSplitter.emit(wf) self.msg2Statusbar.emit("Ready") elif code == 401: self.msg2Statusbar.emit( "Unauthorized to use the EMSODEV DMP API") self.downloader.password = None self.reload() elif code == 404: self.msg2Statusbar.emit("Not Found") elif code == 403: self.msg2Statusbar.emit("Forbidden") elif code == 500: self.msg2Statusbar.emit("EMSODEV API internal error") else: self.msg2Statusbar.emit("Unknown EMSODEV DMP API error") else: self.msg2Statusbar.emit( "Impossible to connect to the EMSODEV DMP API") def downloadAcoustic(self, date, time): # Send a message for the statusbar self.msg2Statusbar.emit( "Downloading acoustic file from {}, {}".format(date, time)) code, df, metadata = self.downloader.acoustic_observation( observatory=self.egimList.currentItem().text(), instrument=self.instrumentList.currentItem().text(), date=date, hour_minute=time) if code: if code == 200: self.msg2Statusbar.emit("Waterframe creation") # It means that you are going good wf = self.downloader.to_waterframe(data=df, metadata=metadata) # Send a signal with the new WaterFrame self.wf2plotSplitter.emit(wf) self.msg2Statusbar.emit("Ready") elif code == 401: self.msg2Statusbar.emit( "Unauthorized to use the EMSODEV DMP API") self.downloader.password = None self.reload() elif code == 404: self.msg2Statusbar.emit("Not Found") elif code == 403: self.msg2Statusbar.emit("Forbidden") elif code == 500: self.msg2Statusbar.emit("EMSODEV API internal error") else: self.msg2Statusbar.emit("Unknown EMSODEV DMP API error") else: self.msg2Statusbar.emit( "Impossible to connect to the EMSODEV DMP API") def enableDate(self): """Enable or disable date elements""" if int(self.limitSpinBox.text()) > 0: self.startDateEdit.setEnabled(False) self.endDateEdit.setEnabled(False) else: self.startDateEdit.setEnabled(True) self.endDateEdit.setEnabled(True)
class SetChartDetailPopup(QDialog): def __init__(self, table_id, *args, **kwargs): super(SetChartDetailPopup, self).__init__(*args, **kwargs) layout = QHBoxLayout(margin=2) # left_layout = QVBoxLayout() self.table_id = table_id self.table_data_frame = None # 表格数据的pandas Data Frame对象 # 图表参数设置的控件 self.chart_parameter = QWidget(parent=self) parameter_layout = QVBoxLayout(margin=0) parameter_layout.addWidget(QLabel('参数设置', objectName='widgetTip')) # 图表名称 chart_name_layout = QHBoxLayout() chart_name_layout.addWidget(QLabel('图表名称:', objectName='headTip')) self.chart_name = QLineEdit() chart_name_layout.addWidget(self.chart_name) parameter_layout.addLayout(chart_name_layout) # 图表类型 chart_category_layout = QHBoxLayout() chart_category_layout.addWidget(QLabel('图表类型:', objectName='headTip')) self.chart_category_combo = QComboBox() self.chart_category_combo.addItems([u'折线图', u'柱形图']) chart_category_layout.addWidget(self.chart_category_combo) chart_category_layout.addStretch() parameter_layout.addLayout(chart_category_layout) # 选择X轴 chart_xaxis_layout = QHBoxLayout() chart_xaxis_layout.addWidget(QLabel('X 轴列名:', objectName='headTip')) self.x_axis_combo = QComboBox(currentTextChanged=self.x_axis_changed) chart_xaxis_layout.addWidget(self.x_axis_combo) chart_xaxis_layout.addStretch() parameter_layout.addLayout(chart_xaxis_layout) # Y轴设置 parameter_layout.addWidget(QLabel('Y 轴列名:', objectName='headTip')) yaxis_layout = QHBoxLayout() left_yaxis_layout = QVBoxLayout() self.column_header_list = QListWidget() self.column_header_list.setMaximumWidth(180) left_yaxis_layout.addWidget(self.column_header_list) yaxis_layout.addLayout(left_yaxis_layout) # 中间按钮 middle_yasis_layout = QVBoxLayout() middle_yasis_layout.addWidget( QPushButton('左轴→', objectName='addAxis', clicked=self.add_y_left)) middle_yasis_layout.addWidget( QPushButton('右轴→', objectName='addAxis', clicked=self.add_y_right)) yaxis_layout.addLayout(middle_yasis_layout) # 右侧列头显示 right_yaxis_layout = QVBoxLayout() self.right_top_list = QListWidget( doubleClicked=self.remove_toplist_item) self.right_top_list.setMaximumWidth(180) right_yaxis_layout.addWidget(self.right_top_list) self.right_bottom_list = QListWidget( doubleClicked=self.remove_bottomlist_item) self.right_bottom_list.setMaximumWidth(180) right_yaxis_layout.addWidget(self.right_bottom_list) yaxis_layout.addLayout(right_yaxis_layout) parameter_layout.addLayout(yaxis_layout) # 轴名称设置 parameter_layout.addWidget(QLabel('轴名称设置:', objectName='headTip'), alignment=Qt.AlignLeft) # x轴 bottom_xaxis_name_layout = QHBoxLayout() bottom_xaxis_name_layout.addWidget(QLabel('X 轴:')) self.bottom_x_label_edit = QLineEdit(placeholderText='请输入轴名称') bottom_xaxis_name_layout.addWidget(self.bottom_x_label_edit) parameter_layout.addLayout(bottom_xaxis_name_layout) # Y轴 yaxis_name_layout = QHBoxLayout() yaxis_name_layout.addWidget(QLabel('左轴:')) self.left_y_label_edit = QLineEdit(placeholderText='请输入左轴名称') yaxis_name_layout.addWidget(self.left_y_label_edit) yaxis_name_layout.addWidget(QLabel('右轴:')) self.right_y_label_edit = QLineEdit(placeholderText='请输入右轴名称') yaxis_name_layout.addWidget(self.right_y_label_edit) parameter_layout.addLayout(yaxis_name_layout) # 数据范围 parameter_layout.addWidget(QLabel('数据范围设置:', objectName='headTip'), alignment=Qt.AlignLeft) chart_scope_layout1 = QHBoxLayout() chart_scope_layout1.addWidget(QLabel('起始日期:')) self.scope_start_date = QDateEdit() self.scope_start_date.setCalendarPopup(True) self.scope_start_date.setEnabled(False) chart_scope_layout1.addWidget(self.scope_start_date) chart_scope_layout1.addWidget(QLabel('截止日期:')) self.scope_end_date = QDateEdit() self.scope_end_date.setCalendarPopup(True) self.scope_end_date.setEnabled(False) chart_scope_layout1.addWidget(self.scope_end_date) parameter_layout.addLayout(chart_scope_layout1) parameter_layout.addWidget(QPushButton( '画图预览', clicked=self.review_chart_clicked), alignment=Qt.AlignRight) self.chart_parameter.setMaximumWidth(350) self.chart_parameter.setLayout(parameter_layout) # 参数设置控件加入布局 layout.addWidget(self.chart_parameter) # 预览控件 self.review_widget = QWidget(parent=self) review_layout = QVBoxLayout(margin=0) review_layout.addWidget(QLabel('图表预览', objectName='widgetTip')) self.review_chart = QChartView() self.review_chart.setRenderHint(QPainter.Antialiasing) review_layout.addWidget(self.review_chart) # # 图例显示区 # self.legend_view = QWidget(parent=self.review_widget) # legend_layout = QGridLayout() # self.legend_view.setLayout(legend_layout) # review_layout.addWidget(self.legend_view) # 确认设置 commit_layout = QHBoxLayout() commit_layout.addStretch() self.current_start = QCheckBox('当前起始') commit_layout.addWidget(self.current_start) self.current_end = QCheckBox('当前截止') commit_layout.addWidget(self.current_end) commit_layout.addWidget( QPushButton('确认设置', clicked=self.commit_add_chart)) review_layout.addLayout(commit_layout) # 表详情数据显示 self.detail_trend_table = QTableWidget() self.detail_trend_table.setMaximumHeight(200) review_layout.addWidget(self.detail_trend_table) self.review_widget.setLayout(review_layout) layout.addWidget(self.review_widget) self.setLayout(layout) self.setMinimumWidth(950) self.setMaximumHeight(550) self.has_review_chart = False self.setStyleSheet(""" #widgetTip{ color: rgb(50,80,180); font-weight:bold } #headTip{ font-weight:bold } #addAxis{ max-width:40px } """) # x轴选择改变 def x_axis_changed(self): self.column_header_list.clear() for header_index in range( self.detail_trend_table.horizontalHeader().count()): text = self.detail_trend_table.horizontalHeaderItem( header_index).text() if text == self.x_axis_combo.currentText(): continue self.column_header_list.addItem(text) # 清空左轴和右轴的设置 self.right_top_list.clear() self.right_bottom_list.clear() # 移除当前列表中的item def remove_toplist_item(self, index): row = self.right_top_list.currentRow() self.right_top_list.takeItem(row) def remove_bottomlist_item(self, index): row = self.right_bottom_list.currentRow() self.right_bottom_list.takeItem(row) # 加入左轴 def add_y_left(self): text_in = list() for i in range(self.right_top_list.count()): text_in.append(self.right_top_list.item(i).text()) item = self.column_header_list.currentItem() # 获取item if item is not None: if item.text() not in text_in: self.right_top_list.addItem(item.text()) # 加入右轴 def add_y_right(self): text_in = list() for i in range(self.right_bottom_list.count()): text_in.append(self.right_bottom_list.item(i).text()) item = self.column_header_list.currentItem() # 获取item if item is not None: if item.text() not in text_in: self.right_bottom_list.addItem(item.text()) # 预览数据 def review_chart_clicked(self): try: chart_name = self.chart_name.text() chart_category = self.chart_category_combo.currentText() # 根据设置从表格中获取画图源数据 x_axis = self.x_axis_combo.currentText() # x轴 left_y_axis = [ self.right_top_list.item(i).text() for i in range(self.right_top_list.count()) ] right_y_axis = [ self.right_bottom_list.item(i).text() for i in range(self.right_bottom_list.count()) ] # 根据表头将这些列名称换为索引 x_axis_col = list() left_y_cols = list() right_y_cols = list() header_data = list() for header_index in range( self.detail_trend_table.horizontalHeader().count()): text = self.detail_trend_table.horizontalHeaderItem( header_index).text() header_data.append(text) if text == x_axis: x_axis_col.append(header_index) for y_left in left_y_axis: if y_left == text: left_y_cols.append(header_index) for y_right in right_y_axis: if y_right == text: right_y_cols.append(header_index) # 判断是否选择了左轴 if not left_y_cols: popup = InformationPopup(message='请至少选择一列左轴数据。', parent=self) if not popup.exec_(): popup.deleteLater() del popup return # 根据设置画图 start_date = self.scope_start_date.date().toString('yyyy-MM-dd') start_date = pd.to_datetime(start_date) end_date = self.scope_end_date.date().toString('yyyy-MM-dd') end_date = pd.to_datetime(end_date) df = self.table_data_frame.copy() final_df = df.loc[(df[0] >= start_date) & (df[0] <= end_date)].copy() # 根据时间范围取数 # 根据类型进行画图 if chart_category == u'折线图': # 折线图 chart = lines_stacked(name=chart_name, table_df=final_df, x_bottom_cols=x_axis_col, y_left_cols=left_y_cols, y_right_cols=right_y_cols, legend_labels=header_data, tick_count=12) # 设置图例 # chart.legend().hide() # markers = chart.legend().markers() # print(markers) # row_index = 0 # col_index = 0 # for marker in chart.legend().markers(): # print(marker.series().name()) # # from PyQt5.QtChart import QLineSeries # # QLineSeries.name() # # 每条线设置一个label # legend_label = QLabel(marker.series().name()) # self.legend_view.layout().addWidget(legend_label, row_index, col_index) # col_index += 1 # if col_index >= 3: # row_index += 1 # col_index = 0 elif chart_category == u'柱形图': chart = bars_stacked(name=chart_name, table_df=final_df, x_bottom_cols=x_axis_col, y_left_cols=left_y_cols, y_right_cols=right_y_cols, legend_labels=header_data, tick_count=12) else: popup = InformationPopup(message='当前设置不适合作图或系统暂不支持作图。', parent=self) if not popup.exec_(): popup.deleteLater() del popup return self.review_chart.setChart(chart) self.has_review_chart = True except Exception as e: popup = InformationPopup(message=str(e), parent=self) if not popup.exec_(): popup.deleteLater() del popup # 确认添加本张图表 def commit_add_chart(self): if not self.has_review_chart: info = InformationPopup(message='请先预览图表,再进行设置!', parent=self) if not info.exec_(): info.deleteLater() del info return category_dict = { '折线图': 'line', '柱形图': 'bar', } chart_name = re.sub(r'\s+', '', self.chart_name.text()) category = category_dict.get(self.chart_category_combo.currentText(), None) if not all([chart_name, category]): info = InformationPopup(message='请设置图表名称和图表类型!', parent=self) if not info.exec_(): info.deleteLater() del info return # 根据设置从表格中获取数据把选择的列头变为索引 x_axis = self.x_axis_combo.currentText() # x轴 left_y_axis = [ self.right_top_list.item(i).text() for i in range(self.right_top_list.count()) ] right_y_axis = [ self.right_bottom_list.item(i).text() for i in range(self.right_bottom_list.count()) ] # 根据表头将这些列名称换为索引 x_axis_cols = list() left_y_cols = list() right_y_cols = list() for header_index in range( self.detail_trend_table.horizontalHeader().count()): text = self.detail_trend_table.horizontalHeaderItem( header_index).text() if text == x_axis: x_axis_cols.append(header_index) for y_left in left_y_axis: if y_left == text: left_y_cols.append(header_index) for y_right in right_y_axis: if y_right == text: right_y_cols.append(header_index) if not x_axis_cols: info = InformationPopup(message='请设置图表X轴!', parent=self) if not info.exec_(): info.deleteLater() del info return if not left_y_cols: info = InformationPopup(message='至少左轴要有一列数据!', parent=self) if not info.exec_(): info.deleteLater() del info return # 获取轴名称 x_bottom_labels = re.split(r';', self.bottom_x_label_edit.text()) y_left_labels = re.split(r';', self.left_y_label_edit.text()) y_right_labels = re.split(r';', self.right_y_label_edit.text()) chart_params = dict() chart_params['table_id'] = self.table_id chart_params['name'] = chart_name chart_params['category'] = category chart_params['x_bottom'] = x_axis_cols chart_params['x_bottom_label'] = x_bottom_labels chart_params['x_top'] = [] chart_params['x_top_label'] = [] chart_params['y_left'] = left_y_cols chart_params['y_left_label'] = y_left_labels chart_params['y_right'] = right_y_cols chart_params['y_right_label'] = y_right_labels chart_params['is_top'] = False if self.current_start.isChecked(): # print('设置当前起始范围') chart_params['start'] = self.scope_start_date.date().toString( 'yyyy-MM-dd') if self.current_end.isChecked(): # print('设置当前截止') chart_params['end'] = self.scope_end_date.date().toString( 'yyyy-MM-dd') # print(chart_params) # 上传数据 try: r = requests.post(url=settings.SERVER_ADDR + 'trend/chart/?mc=' + settings.app_dawn.value('machine'), headers={ 'AUTHORIZATION': settings.app_dawn.value('AUTHORIZATION') }, data=json.dumps(chart_params)) response = json.loads(r.content.decode('utf-8')) if r.status_code != 201: raise ValueError(response['message']) message = response['message'] except Exception as e: message = str(e) info = InformationPopup(message=message, parent=self) if not info.exec_(): info.deleteLater() del info # 获取当前表的数据(预用来设置显示图的数据) def getCurrentTrendTable(self): try: r = requests.get(url=settings.SERVER_ADDR + 'trend/table/' + str(self.table_id) + '/?look=1&mc=' + settings.app_dawn.value('machine')) response = json.loads(r.content.decode('utf-8')) if r.status_code != 200: raise ValueError(response['message']) except Exception: pass else: column_headers = response['data']['header_data'] column_headers.pop(0) self.x_axis_combo.addItems(column_headers) # X轴选择 for text in column_headers: if text in self.x_axis_combo.currentText(): continue self.column_header_list.addItem(text) # 列头待选表 # pandas处理数据 self.table_data_frame = self.pd_handler_data( response['data']['table_data']) # 填充表格 self.showTableData(column_headers, self.table_data_frame) # pandas 处理数据 def pd_handler_data(self, table_data): # 转为DF table_df = pd.DataFrame(table_data) table_df.drop(columns=[0], inplace=True) # 删除id列 table_df.columns = [i for i in range(table_df.shape[1])] # 重置列索引 table_df[0] = pd.to_datetime(table_df[0]) # 第一列转为时间类型 table_df.sort_values(by=0, inplace=True) # 根据时间排序数据 # 计算数据时间跨度大小显示在范围上 min_x, max_x = table_df[0].min(), table_df[0].max( ) # 第一列时间数据(x轴)的最大值和最小值 self.scope_start_date.setDateRange(QDate(min_x), QDate(max_x)) self.scope_end_date.setDateRange(QDate(min_x), QDate(max_x)) self.scope_start_date.setEnabled(True) self.scope_end_date.setEnabled(True) self.scope_end_date.setDate(self.scope_end_date.maximumDate()) return table_df # 设置表格显示表数据内容 def showTableData(self, headers, table_df): # 设置行列 self.detail_trend_table.setRowCount(table_df.shape[0]) col_count = len(headers) self.detail_trend_table.setColumnCount(col_count) self.detail_trend_table.setHorizontalHeaderLabels(headers) self.detail_trend_table.horizontalHeader().setSectionResizeMode( QHeaderView.ResizeToContents) for row, row_content in enumerate(table_df.values.tolist()): for col, value in enumerate(row_content): if col == 0: # 时间类 value = value.strftime('%Y-%m-%d') item = QTableWidgetItem(value) item.setTextAlignment(Qt.AlignCenter) self.detail_trend_table.setItem(row, col, item)
class PredWindowSelect(QGroupBox): """An extension of the QGroupBox widget. It allows the user to select a start date and end date for the prediction window in the Monte Carlo VaR calculation application. The start date of the prediction window is set to the end date of the historical window. The end date of the prediction window is five days later than the start date of the prediction window as a minimum. These restrictions are implemented through the QDateEdit widget's maximum and minimum date functions. """ def __init__(self, parent=None): super(PredWindowSelect, self).__init__(parent) self.setTitle('Prediction Window Select') grid = QGridLayout() lbl_start = QLabel('Start Date: ') lbl_end = QLabel('End Date: ') self.deStart = QDateEdit() self.deStart.setEnabled(False) self.deStart.setCalendarPopup(True) self.deStart.dateChanged.connect(self.setEndDateMinimum) self.deEnd = QDateEdit() self.deEnd.setCalendarPopup(True) self.deEnd.dateChanged.connect(self.onEndDateChange) self.setEndDateMinimum() lbl_disc = QLabel( 'Note: A minimum prediction window length <br>of 5 business days has been imposed.' ) grid.addWidget(lbl_start, 0, 0) grid.addWidget(lbl_end, 1, 0) grid.addWidget(self.deStart, 0, 1) grid.addWidget(self.deEnd, 1, 1) grid.addWidget(lbl_disc, 2, 1) self.setLayout(grid) @QtCore.pyqtSlot(dt.datetime) def setStart(self, date): """Slotted function to set the start date based on end date of the historical window. """ self.deStart.setDate(date) def onEndDateChange(self): """Checks if new end date is a weekend. If so, it adds the appropriate number of days to bring the end date to the following Monday. """ deEnd = dt.datetime.combine(self.deEnd.date().toPyDate(), dt.datetime.min.time()) if deEnd.weekday() == 5: deEnd = deEnd + timedelta(days=2) elif deEnd.weekday() == 6: deEnd = deEnd + timedelta(days=1) self.deEnd.setDate(deEnd) def setEndDateMinimum(self): """Updates the minimum value of the end date, which must be 5 business days after the start date. """ deStart = dt.datetime.combine(self.deStart.date().toPyDate(), dt.datetime.min.time()) end_min = self.getWeekdaysOut(deStart, 5) self.deEnd.setMinimumDateTime(end_min) if self.deEnd.date() < end_min: self.deEnd.setDate(end_min) def getWeekdaysOut(self, start_date, days_out): """Finds the date that is x weekdays later than the start date (where x = days_out). Args: start_date (datetime.datetime) - Start date from which to count weekdays out. days_out (int) - Number of days out. Returns: curr_date (datetime.datetime) - the date that is days_out weekdays later than start_date. Raises Error: None """ if start_date.weekday() == 5: start_date += timedelta(days=2) if start_date.weekday() == 6: start_date += timedelta(days=1) for i in range(days_out): if i == 0: curr_date = start_date next_date = curr_date + timedelta(days=1) if next_date.weekday() == 5: next_date = curr_date + timedelta(days=3) curr_date = next_date return (curr_date) def getDateWindow(self): """Getter function for the date window selected by the user. """ start_date = dt.datetime.combine(self.deStart.date().toPyDate(), dt.datetime.min.time()) end_date = dt.datetime.combine(self.deEnd.date().toPyDate(), dt.datetime.min.time()) return start_date, end_date def getNumDays(self): """Getter function for the number of weekdays between the start and end dates.""" start_date = dt.datetime.combine(self.deStart.date().toPyDate(), dt.datetime.min.time()) end_date = dt.datetime.combine(self.deEnd.date().toPyDate(), dt.datetime.min.time()) if start_date.weekday() == 5: start_date += timedelta(days=2) if start_date.weekday() == 6: start_date += timedelta(days=1) days = 0 curr_date = start_date while curr_date != end_date: curr_date += timedelta(days=1) if curr_date.weekday() == 5: continue elif curr_date.weekday() == 6: continue else: days += 1 return days
class AddIncrementWidget(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) # Creating and setting the components and their characteristics size = QSize(400, 370) self.setFixedSize(size) self.main_layout = QVBoxLayout() title: str = centralize_text("Добавление прихода продукта:") self.title = QLabel(title, parent=self) self.data_layout = QHBoxLayout() self.price_quantity_layout = QVBoxLayout() self.price_title = QLabel("Цена:", parent=self) self.price_value = QSpinBox() self.price_value.setValue(0) self.price_value.setRange(0, 1000000) self.price_value.setSingleStep(1000) self.quantity_title = QLabel("Кол-во:", parent=self) self.quantity_value = QDoubleSpinBox(parent=self) self.quantity_value.setSingleStep(1.0) self.quantity_value.setRange(0.0, 1000000.0) self.quantity_value.setDecimals(3) self.agreement_title = QLabel("Информация о доверенности:", parent=self) self.agreement_info = QPlainTextEdit(parent=self) self.date_layout = QVBoxLayout() self.date_title = QLabel("Выберите дату:", parent=self) self.today_radio = QRadioButton("Сегодня", parent=self) self.today_radio.setChecked(True) self.today_radio.clicked.connect(self.disable_date_edit) self.otherday_radio = QRadioButton("Другой день", parent=self) self.otherday_radio.clicked.connect(self.enable_date_edit) self.otherday_edit = QDateEdit(date=date.today(), parent=self) self.otherday_edit.setDisabled(True) self.invoice_title = QLabel("Информация о счёт-фактуре:", parent=self) self.invoice_info = QPlainTextEdit(parent=self) self.button = QPushButton("Добавить &приход", parent=self) # Locating the components in the widget self.setLayout(self.main_layout) self.main_layout.addWidget(self.title) self.main_layout.addLayout(self.data_layout) self.data_layout.addLayout(self.price_quantity_layout) self.price_quantity_layout.addWidget(self.price_title) self.price_quantity_layout.addWidget(self.price_value) self.price_quantity_layout.addWidget(self.quantity_title) self.price_quantity_layout.addWidget(self.quantity_value) self.price_quantity_layout.addWidget(self.agreement_title) self.price_quantity_layout.addWidget(self.agreement_info) self.data_layout.addLayout(self.date_layout) self.date_layout.addWidget(self.date_title) self.date_layout.addWidget(self.today_radio) self.date_layout.addWidget(self.otherday_radio) self.date_layout.addWidget(self.otherday_edit) self.date_layout.addWidget(self.invoice_title) self.date_layout.addWidget(self.invoice_info) self.main_layout.addWidget(self.button) def enable_date_edit(self): # The user would choose another date self.otherday_edit.setEnabled(True) def disable_date_edit(self): # The user chose today self.otherday_edit.setDate(date.today()) self.otherday_edit.setDisabled(True)
class WeatherDataGapfiller(QMainWindow): ConsoleSignal = QSignal(str) def __init__(self, parent=None): super().__init__(parent) self._workdir = None self._corrcoeff_update_inprogress = False self._pending_corrcoeff_update = None self._loading_data_inprogress = False self.__initUI__() # Setup the DataGapfillManager. self.gapfill_manager = DataGapfillManager() self.gapfill_manager.sig_task_progress.connect( self.progressbar.setValue) self.gapfill_manager.sig_status_message.connect( self.set_statusbar_text) def __initUI__(self): self.setWindowIcon(get_icon('master')) # Setup the toolbar at the bottom. self.btn_fill = QPushButton('Gapfill Data') self.btn_fill.setIcon(get_icon('fill_data')) self.btn_fill.setIconSize(get_iconsize('small')) self.btn_fill.setToolTip( "Fill the gaps in the daily weather data of the selected " "weather station.") self.btn_fill.clicked.connect(self._handle_gapfill_btn_clicked) widget_toolbar = QFrame() grid_toolbar = QGridLayout(widget_toolbar) grid_toolbar.addWidget(self.btn_fill, 0, 0) grid_toolbar.setContentsMargins(0, 0, 0, 0) grid_toolbar.setColumnStretch(0, 100) # ---- Target Station groupbox self.target_station = QComboBox() self.target_station.currentIndexChanged.connect( self._handle_target_station_changed) self.target_station_info = QTextEdit() self.target_station_info.setReadOnly(True) self.target_station_info.setMaximumHeight(110) self.btn_refresh_staList = QToolButton() self.btn_refresh_staList.setIcon(get_icon('refresh')) self.btn_refresh_staList.setToolTip( 'Force the reloading of the weather data files') self.btn_refresh_staList.setIconSize(get_iconsize('small')) self.btn_refresh_staList.setAutoRaise(True) self.btn_refresh_staList.clicked.connect( lambda: self.load_data_dir_content(force_reload=True)) self.btn_delete_data = QToolButton() self.btn_delete_data.setIcon(get_icon('delete_data')) self.btn_delete_data.setEnabled(False) self.btn_delete_data.setAutoRaise(True) self.btn_delete_data.setToolTip( 'Remove the currently selected dataset and delete the input ' 'datafile. However, raw datafiles will be kept.') self.btn_delete_data.clicked.connect(self.delete_current_dataset) # Generate the layout for the target station group widget. self.target_widget = QWidget() target_station_layout = QGridLayout(self.target_widget) target_station_layout.setHorizontalSpacing(1) target_station_layout.setColumnStretch(0, 1) target_station_layout.setContentsMargins(0, 0, 0, 0) widgets = [self.target_station, self.btn_refresh_staList, self.btn_delete_data] target_station_layout.addWidget(self.target_station, 1, 0) for col, widget in enumerate(widgets): target_station_layout.addWidget(widget, 1, col) # Setup the gapfill dates. label_From = QLabel('From : ') self.date_start_widget = QDateEdit() self.date_start_widget.setDisplayFormat('dd / MM / yyyy') self.date_start_widget.setEnabled(False) self.date_start_widget.dateChanged.connect( self._update_corrcoeff_table) label_To = QLabel('To : ') self.date_end_widget = QDateEdit() self.date_end_widget.setEnabled(False) self.date_end_widget.setDisplayFormat('dd / MM / yyyy') self.date_end_widget.dateChanged.connect( self._update_corrcoeff_table) self.fillDates_widg = QWidget() gapfilldates_layout = QGridLayout(self.fillDates_widg) gapfilldates_layout.addWidget(label_From, 0, 0) gapfilldates_layout.addWidget(self.date_start_widget, 0, 1) gapfilldates_layout.addWidget(label_To, 1, 0) gapfilldates_layout.addWidget(self.date_end_widget, 1, 1) gapfilldates_layout.setColumnStretch(2, 1) gapfilldates_layout.setContentsMargins(0, 0, 0, 0) # Create the gapfill target station groupbox. target_groupbox = QGroupBox("Fill data for weather station") target_layout = QGridLayout(target_groupbox) target_layout.addWidget(self.target_widget, 0, 0) target_layout.addWidget(self.target_station_info, 1, 0) target_layout.addWidget(self.fillDates_widg, 2, 0) # Setup the left panel. self._regression_model_groupbox = ( self._create_regression_model_settings()) self._station_selection_groupbox = ( self._create_station_selection_criteria()) self.left_panel = QFrame() left_panel_layout = QGridLayout(self.left_panel) left_panel_layout.addWidget(target_groupbox, 0, 0) left_panel_layout.addWidget(self._station_selection_groupbox, 3, 0) left_panel_layout.addWidget(self._regression_model_groupbox, 4, 0) left_panel_layout.addWidget(widget_toolbar, 5, 0) left_panel_layout.setRowStretch(6, 1) left_panel_layout.setContentsMargins(0, 0, 0, 0) # Setup the right panel. self.corrcoeff_textedit = QTextEdit() self.corrcoeff_textedit.setReadOnly(True) self.corrcoeff_textedit.setMinimumWidth(700) self.corrcoeff_textedit.setFrameStyle(0) self.corrcoeff_textedit.document().setDocumentMargin(10) self.sta_display_summary = QTextEdit() self.sta_display_summary.setReadOnly(True) self.sta_display_summary.setFrameStyle(0) self.sta_display_summary.document().setDocumentMargin(10) self.right_panel = QTabWidget() self.right_panel.addTab( self.corrcoeff_textedit, 'Correlation Coefficients') self.right_panel.addTab( self.sta_display_summary, 'Data Overview') # Setup the progressbar. self.progressbar = QProgressBar() self.progressbar.setValue(0) self.progressbar.hide() self.statustext = QLabel() self.statustext.setStyleSheet( "QLabel {background-color: transparent; padding: 0 0 0 3px;}") self.statustext.setMinimumHeight(self.progressbar.minimumHeight()) # Setup the main widget. main_widget = QWidget() main_grid = QGridLayout(main_widget) main_grid.addWidget(self.left_panel, 0, 0) main_grid.addWidget(self.right_panel, 0, 1) main_grid.addWidget(self.progressbar, 1, 0, 1, 2) main_grid.addWidget(self.statustext, 1, 0, 1, 2) main_grid.setColumnStretch(1, 500) main_grid.setRowStretch(0, 500) self.setCentralWidget(main_widget) def _create_station_selection_criteria(self): Nmax_label = QLabel('Nbr. of stations :') self.Nmax = QSpinBox() self.Nmax.setRange(0, 99) self.Nmax.setMinimum(1) self.Nmax.setValue(CONF.get('gapfill_data', 'nbr_of_station', 4)) self.Nmax.setAlignment(Qt.AlignCenter) ttip = ('<p>Distance limit beyond which neighboring stations' ' are excluded from the gapfilling procedure.</p>' '<p>This condition is ignored if set to -1.</p>') distlimit_label = QLabel('Max. Distance :') distlimit_label.setToolTip(ttip) self.distlimit = QSpinBox() self.distlimit.setRange(-1, 9999) self.distlimit.setSingleStep(1) self.distlimit.setValue( CONF.get('gapfill_data', 'max_horiz_dist', 100)) self.distlimit.setToolTip(ttip) self.distlimit.setSuffix(' km') self.distlimit.setAlignment(Qt.AlignCenter) self.distlimit.valueChanged.connect(self._update_corrcoeff_table) ttip = ('<p>Altitude difference limit over which neighboring ' ' stations are excluded from the gapfilling procedure.</p>' '<p>This condition is ignored if set to -1.</p>') altlimit_label = QLabel('Max. Elevation Diff. :') altlimit_label.setToolTip(ttip) self.altlimit = QSpinBox() self.altlimit.setRange(-1, 9999) self.altlimit.setSingleStep(1) self.altlimit.setValue( CONF.get('gapfill_data', 'max_vert_dist', 350)) self.altlimit.setToolTip(ttip) self.altlimit.setSuffix(' m') self.altlimit.setAlignment(Qt.AlignCenter) self.altlimit.valueChanged.connect(self._update_corrcoeff_table) # Setup the main widget. widget = QGroupBox('Stations Selection Criteria') layout = QGridLayout(widget) layout.addWidget(Nmax_label, 0, 0) layout.addWidget(self.Nmax, 0, 1) layout.addWidget(distlimit_label, 1, 0) layout.addWidget(self.distlimit, 1, 1) layout.addWidget(altlimit_label, 2, 0) layout.addWidget(self.altlimit, 2, 1) layout.setColumnStretch(0, 1) return widget def _create_advanced_settings(self): self.full_error_analysis = QCheckBox('Full Error Analysis.') self.full_error_analysis.setChecked(True) fig_opt_layout = QGridLayout() fig_opt_layout.addWidget(QLabel("Figure output format : "), 0, 0) fig_opt_layout.addWidget(self.fig_format, 0, 2) fig_opt_layout.addWidget(QLabel("Figure labels language : "), 1, 0) fig_opt_layout.addWidget(self.fig_language, 1, 2) fig_opt_layout.setContentsMargins(0, 0, 0, 0) fig_opt_layout.setColumnStretch(1, 100) # Setup the main layout. widget = QFrame() layout = QGridLayout(widget) layout.addWidget(self.full_error_analysis, 0, 0) layout.addLayout(fig_opt_layout, 2, 0) layout.setRowStretch(layout.rowCount(), 100) layout.setContentsMargins(10, 0, 10, 0) return widget def _create_regression_model_settings(self): self.RMSE_regression = QRadioButton('Ordinary Least Squares') self.RMSE_regression.setChecked( CONF.get('gapfill_data', 'regression_model', 'OLS') == 'OLS') self.ABS_regression = QRadioButton('Least Absolute Deviations') self.ABS_regression.setChecked( CONF.get('gapfill_data', 'regression_model', 'OLS') == 'LAD') widget = QGroupBox('Regression Model') layout = QGridLayout(widget) layout.addWidget(self.RMSE_regression, 0, 0) layout.addWidget(self.ABS_regression, 1, 0) return widget def set_statusbar_text(self, text): self.statustext.setText(text) @property def workdir(self): return self._workdir def set_workdir(self, dirname): """ Set the working directory to dirname. """ self._workdir = dirname self.gapfill_manager.set_workdir(dirname) self.load_data_dir_content() def delete_current_dataset(self): """ Delete the current dataset source file and force a reload of the input daily weather datafiles. """ current_index = self.target_station.currentIndex() if current_index != -1: basename = self.gapfill_manager.worker().wxdatasets.fnames[ current_index] filename = os.path.join(self.workdir, basename) delete_file(filename) self.load_data_dir_content() def _handle_target_station_changed(self): """Handle when the target station is changed by the user.""" self.btn_delete_data.setEnabled( self.target_station.currentIndex() != -1) self.update_corrcoeff() def get_dataset_names(self): """ Return a list of the names of the dataset that are loaded in memory and listed in the target station dropdown menu. """ return [self.target_station.itemText(i) for i in range(self.target_station.count())] # ---- Correlation coefficients def update_corrcoeff(self): """ Calculate the correlation coefficients and display the results in the GUI. """ if self.target_station.currentIndex() != -1: station_id = self.target_station.currentData() if self._corrcoeff_update_inprogress is True: self._pending_corrcoeff_update = station_id else: self._corrcoeff_update_inprogress = True self.corrcoeff_textedit.setText('') self.gapfill_manager.set_target_station( station_id, callback=self._handle_corrcoeff_updated) def _handle_corrcoeff_updated(self): self._corrcoeff_update_inprogress = False if self._pending_corrcoeff_update is None: self._update_corrcoeff_table() else: self._pending_corrcoeff_update = None self.update_corrcoeff() def _update_corrcoeff_table(self): """ This method plot the correlation coefficient table in the display area. It is separated from the method "update_corrcoeff" because red numbers and statistics regarding missing data for the selected time period can be updated in the table when the user changes the values without having to recalculate the correlation coefficient each time. """ if self.target_station.currentIndex() != -1: table, target_info = ( self.gapfill_manager.worker().generate_correlation_html_table( self.get_gapfill_parameters())) self.corrcoeff_textedit.setText(table) self.target_station_info.setText(target_info) # ---- Load Data def load_data_dir_content(self, force_reload=False): """ Load weater data from valid files contained in the working directory. """ self._pending_corrcoeff_update = None self._loading_data_inprogress = True self.left_panel.setEnabled(False) self.right_panel.setEnabled(False) self.corrcoeff_textedit.setText('') self.target_station_info.setText('') self.target_station.clear() self.gapfill_manager.load_data( force_reload=force_reload, callback=self._handle_data_dir_content_loaded) def _handle_data_dir_content_loaded(self): """ Handle when data finished loaded from valid files contained in the working directory. """ self.left_panel.setEnabled(True) self.right_panel.setEnabled(True) self.target_station.blockSignals(True) station_names = self.gapfill_manager.get_station_names() station_ids = self.gapfill_manager.get_station_ids() for station_name, station_id in zip(station_names, station_ids): self.target_station.addItem( '{} ({})'.format(station_name, station_id), userData=station_id) self.target_station.blockSignals(False) self.sta_display_summary.setHtml( self.gapfill_manager.worker().generate_html_summary_table()) if len(station_names) > 0: self._setup_fill_and_save_dates() self.target_station.blockSignals(True) self.target_station.setCurrentIndex(0) self.target_station.blockSignals(False) self._handle_target_station_changed() self._loading_data_inprogress = False def _setup_fill_and_save_dates(self): """ Set first and last dates of the 'Fill data for weather station'. """ if self.gapfill_manager.count(): self.date_start_widget.setEnabled(True) self.date_end_widget.setEnabled(True) mindate = ( self.gapfill_manager.worker() .wxdatasets.metadata['first_date'].min()) maxdate = ( self.gapfill_manager.worker() .wxdatasets.metadata['last_date'].max()) qdatemin = QDate(mindate.year, mindate.month, mindate.day) qdatemax = QDate(maxdate.year, maxdate.month, maxdate.day) self.date_start_widget.blockSignals(True) self.date_start_widget.setDate(qdatemin) self.date_start_widget.setMinimumDate(qdatemin) self.date_start_widget.setMaximumDate(qdatemax) self.date_start_widget.blockSignals(False) self.date_end_widget.blockSignals(True) self.date_end_widget.setDate(qdatemax) self.date_end_widget.setMinimumDate(qdatemin) self.date_end_widget.setMaximumDate(qdatemax) self.date_end_widget.blockSignals(False) # ---- Gapfill Data def get_gapfill_parameters(self): """ Return a dictionary containing the parameters that are set in the GUI for gapfilling weather data. """ return { 'limitDist': self.distlimit.value(), 'limitAlt': self.altlimit.value(), 'date_start': self.date_start_widget.date().toString('dd/MM/yyyy'), 'date_end': self.date_end_widget.date().toString('dd/MM/yyyy') } def _handle_gapfill_btn_clicked(self): """ Handle when the user clicked on the gapfill button. """ if self.gapfill_manager.count() == 0: QMessageBox.warning( self, 'Warning', "There is no data to fill.", QMessageBox.Ok) return # Check for dates errors. datetime_start = datetime_from_qdatedit(self.date_start_widget) datetime_end = datetime_from_qdatedit(self.date_end_widget) if datetime_start > datetime_end: QMessageBox.warning( self, 'Warning', ("<i>From</i> date is set to a later time than " "the <i>To</i> date."), QMessageBox.Ok) return if self.target_station.currentIndex() == -1: QMessageBox.warning( self, 'Warning', "No weather station is currently selected", QMessageBox.Ok) return self.start_gapfill_target() def _handle_gapfill_target_finished(self): """ Method initiated from an automatic return from the gapfilling process in batch mode. Iterate over the station list and continue process normally. """ self.btn_fill.setIcon(get_icon('fill_data')) self.btn_fill.setEnabled(True) self.target_widget.setEnabled(True) self.fillDates_widg.setEnabled(True) self._regression_model_groupbox.setEnabled(True) self._station_selection_groupbox.setEnabled(True) self.progressbar.setValue(0) QApplication.processEvents() self.progressbar.hide() def start_gapfill_target(self): # Update the gui. self.btn_fill.setEnabled(False) self.fillDates_widg.setEnabled(False) self.target_widget.setEnabled(False) self._regression_model_groupbox.setEnabled(False) self._station_selection_groupbox.setEnabled(False) self.progressbar.show() # Start the gapfill thread. self.gapfill_manager.gapfill_data( time_start=datetime_from_qdatedit(self.date_start_widget), time_end=datetime_from_qdatedit(self.date_end_widget), max_neighbors=self.Nmax.value(), hdist_limit=self.distlimit.value(), vdist_limit=self.altlimit.value(), regression_mode=self.RMSE_regression.isChecked(), callback=self._handle_gapfill_target_finished ) def close(self): CONF.set('gapfill_data', 'nbr_of_station', self.Nmax.value()) CONF.set('gapfill_data', 'max_horiz_dist', self.distlimit.value()) CONF.set('gapfill_data', 'max_vert_dist', self.altlimit.value()) CONF.set('gapfill_data', 'regression_model', 'OLS' if self.RMSE_regression.isChecked() else 'LAD') super().close()