Example #1
0
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)
Example #3
0
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)
Example #5
0
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)
Example #7
0
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()