diff --git a/kstars/ekos/observatory/observatory.h b/kstars/ekos/observatory/observatory.h --- a/kstars/ekos/observatory/observatory.h +++ b/kstars/ekos/observatory/observatory.h @@ -88,8 +88,24 @@ void buttonPressed(QPushButton *button, QString title); // weather sensor data - QFormLayout* sensorDataBoxLayout; - QList*> sensorDataWidgets; + QGridLayout* sensorDataBoxLayout; + // map id -> (label, widget) + std::map*> sensorDataWidgets = {}; + // map id -> graph key x value vector + std::map*> sensorGraphData = {}; + + // map id -> range (+1: values only > 0, 0: values > 0 and < 0; -1: values < 0) + std::map sensorRanges = {}; + + // selected sensor for graph display + QString selectedSensorID = ""; + + // button group for sensor names to ensure, that only one button is pressed + QButtonGroup *sensorDataNamesGroup; + + void initSensorGraphs(); + void updateSensorData(std::vector weatherData); + void updateSensorGraph(QString label, QDateTime now, double value); private slots: @@ -101,11 +117,16 @@ void shutdownWeather(); void setWeatherStatus(ISD::Weather::Status status); + // sensor data graphs + void mouseOverLine(QMouseEvent *event); // reacting on weather changes void weatherWarningSettingsChanged(); void weatherAlertSettingsChanged(); + // reacting on sensor selection change + void selectedSensorChanged(QString id); + // reacting on observatory status changes void observatoryStatusChanged(bool ready); void domeAzimuthChanged(double position); diff --git a/kstars/ekos/observatory/observatory.cpp b/kstars/ekos/observatory/observatory.cpp --- a/kstars/ekos/observatory/observatory.cpp +++ b/kstars/ekos/observatory/observatory.cpp @@ -39,8 +39,10 @@ weatherWarningSchedulerCB->setVisible(false); weatherAlertSchedulerCB->setVisible(false); // initialize the weather sensor data group box - sensorDataBoxLayout = new QFormLayout(); - sensorDataBox->setLayout(sensorDataBoxLayout); + sensorDataBoxLayout = new QGridLayout(); + sensorData->setLayout(sensorDataBoxLayout); + + initSensorGraphs(); } void Observatory::setObseratoryStatusControl(ObservatoryStatusControl control) @@ -496,6 +498,130 @@ } +void Observatory::updateSensorGraph(QString label, QDateTime now, double value) +{ + // we assume that labels are unique and use the full label as identifier + QString id = label; + + // lazy instantiation of the sensor data storage + if (sensorGraphData[id] == nullptr) + { + sensorGraphData[id] = new QVector(); + sensorRanges[id] = value > 0 ? 1 : (value < 0 ? -1 : 0); + } + + // store the data + sensorGraphData[id]->append(QCPGraphData(static_cast(now.toTime_t()), value)); + + // add data for the graphs we display + if (selectedSensorID == id) + { + // display data point + sensorGraphs->graph()->addData(sensorGraphData[id]->last().key, sensorGraphData[id]->last().value); + sensorGraphs->rescaleAxes(); + // ensure that the 0-line is visible + if ((sensorRanges[id] > 0 && value < 0) || (sensorRanges[id] < 0 && value > 0)) + sensorRanges[id] = 0; + + // ensure visibility of the 0-line on the y-axis + if (sensorRanges[id] > 0) + sensorGraphs->yAxis->setRangeLower(0); + else if (sensorRanges[id] < 0) + sensorGraphs->yAxis->setRangeUpper(0); + sensorGraphs->replot(); + } +} + +void Observatory::updateSensorData(std::vector weatherData) +{ + std::vector::iterator it; + QDateTime now = KStarsData::Instance()->lt(); + + for (it=weatherData.begin(); it != weatherData.end(); ++it) + { + QString const id = it->label; + + if (sensorDataWidgets[id] == nullptr) + { + QPushButton* labelWidget = new QPushButton(it->label); + labelWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + labelWidget->setCheckable(true); + labelWidget->setStyleSheet("QPushButton:checked\n{\nbackground-color: maroon;\nborder: 1px outset;\nfont-weight:bold;\n}"); + // we need the object name since the label may contain '&' for keyboard shortcuts + labelWidget->setObjectName(it->label); + + QLineEdit* valueWidget = new QLineEdit(QString().setNum(it->value, 'f', 2)); + // fix width to enable stretching of the graph + valueWidget->setMinimumWidth(96); + valueWidget->setMaximumWidth(96); + valueWidget->setReadOnly(true); + valueWidget->setAlignment(Qt::AlignRight); + + sensorDataWidgets[id] = new QPair(labelWidget, valueWidget); + + sensorDataBoxLayout->addWidget(labelWidget, sensorDataBoxLayout->rowCount(), 0); + sensorDataBoxLayout->addWidget(valueWidget, sensorDataBoxLayout->rowCount()-1, 1); + + // initial graph selection + if (selectedSensorID == "" && id.indexOf('(') > 0 && id.indexOf('(') < id.indexOf(')')) + { + selectedSensorID = id; + labelWidget->setChecked(true); + } + + sensorDataNamesGroup->addButton(labelWidget); + } + else + { + sensorDataWidgets[id]->first->setText(QString(it->label)); + sensorDataWidgets[id]->second->setText(QString().setNum(it->value, 'f', 2)); + } + + // store sensor data unit if necessary + updateSensorGraph(it->label, now, it->value); + } +} + +void Observatory::mouseOverLine(QMouseEvent *event) +{ + double key = sensorGraphs->xAxis->pixelToCoord(event->localPos().x()); + QCPGraph *graph = qobject_cast(sensorGraphs->plottableAt(event->pos(), false)); + + if (graph) + { + int index = sensorGraphs->graph(0)->findBegin(key); + double value = sensorGraphs->graph(0)->dataMainValue(index); + + QToolTip::showText( + event->globalPos(), + i18n("%1 = %2", selectedSensorID, value)); + } + else { + QToolTip::hideText(); + } +} + + +void Observatory::selectedSensorChanged(QString id) +{ + QVector *data = sensorGraphData[id]; + + if (data != nullptr) + { + // copy the graph data to the graph container + QCPGraphDataContainer *container = new QCPGraphDataContainer(); + for (QVector::iterator it = data->begin(); it != data->end(); ++it) + container->add(QCPGraphData(it->key, it->value)); + + sensorGraphs->graph()->setData(QSharedPointer(container)); + sensorGraphs->rescaleAxes(); + sensorGraphs->replot(); + selectedSensorID = id; + } +} + + + void Observatory::setWeatherStatus(ISD::Weather::Status status) { std::string label; @@ -523,28 +649,71 @@ std::vector weatherData = getWeatherModel()->getWeatherData(); // update weather sensor data - int row_nr = 0; - std::vector::iterator it; - for (it=weatherData.begin(); it != weatherData.end(); ++it) - { - if (sensorDataBoxLayout->rowCount() > row_nr) - { - sensorDataWidgets.value(row_nr)->first->setText(QString(it->label)); - sensorDataWidgets.value(row_nr)->second->setText(QString().setNum(it->value, 'f', 2)); - } - else - { - QLabel* labelWidget = new QLabel(it->label); - QLineEdit* valueWidget = new QLineEdit(QString().setNum(it->value, 'f', 2)); - valueWidget->setReadOnly(false); - valueWidget->setAlignment(Qt::AlignRight); + updateSensorData(weatherData); - sensorDataWidgets.append(new QPair(labelWidget, valueWidget)); +} - sensorDataBoxLayout->addRow(labelWidget, valueWidget); - } - row_nr++; - } +void Observatory::initSensorGraphs() +{ + // set some pens, brushes and backgrounds: + sensorGraphs->xAxis->setBasePen(QPen(Qt::white, 1)); + sensorGraphs->yAxis->setBasePen(QPen(Qt::white, 1)); + sensorGraphs->xAxis->setTickPen(QPen(Qt::white, 1)); + sensorGraphs->yAxis->setTickPen(QPen(Qt::white, 1)); + sensorGraphs->xAxis->setSubTickPen(QPen(Qt::white, 1)); + sensorGraphs->yAxis->setSubTickPen(QPen(Qt::white, 1)); + sensorGraphs->xAxis->setTickLabelColor(Qt::white); + sensorGraphs->yAxis->setTickLabelColor(Qt::white); + sensorGraphs->xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); + sensorGraphs->yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine)); + sensorGraphs->xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); + sensorGraphs->yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine)); + sensorGraphs->xAxis->grid()->setSubGridVisible(true); + sensorGraphs->yAxis->grid()->setSubGridVisible(true); + sensorGraphs->xAxis->grid()->setZeroLinePen(Qt::NoPen); + sensorGraphs->yAxis->grid()->setZeroLinePen(Qt::NoPen); + sensorGraphs->xAxis->setUpperEnding(QCPLineEnding::esSpikeArrow); + sensorGraphs->yAxis->setUpperEnding(QCPLineEnding::esSpikeArrow); + QLinearGradient plotGradient; + plotGradient.setStart(0, 0); + plotGradient.setFinalStop(0, 350); + plotGradient.setColorAt(0, QColor(80, 80, 80)); + plotGradient.setColorAt(1, QColor(50, 50, 50)); + sensorGraphs->setBackground(plotGradient); + QLinearGradient axisRectGradient; + axisRectGradient.setStart(0, 0); + axisRectGradient.setFinalStop(0, 350); + axisRectGradient.setColorAt(0, QColor(80, 80, 80)); + axisRectGradient.setColorAt(1, QColor(30, 30, 30)); + sensorGraphs->axisRect()->setBackground(axisRectGradient); + + QSharedPointer dateTicker(new QCPAxisTickerDateTime); + dateTicker->setDateTimeFormat("hh:mm"); + dateTicker->setTickCount(2); + sensorGraphs->xAxis->setTicker(dateTicker); + + // allow dragging in all directions + sensorGraphs->setInteraction(QCP::iRangeDrag, true); + sensorGraphs->setInteraction(QCP::iRangeZoom); + + // create the universal graph + QCPGraph *graph = sensorGraphs->addGraph(); + graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(Qt::black, 0), QBrush(Qt::green), 5)); + graph->setPen(QPen(Qt::darkGreen)); + graph->setBrush(QColor(10, 100, 50, 70)); + + // ensure that the 0-line is visible + sensorGraphs->yAxis->setRangeLower(0); + + sensorDataNamesGroup = new QButtonGroup(); + // enable changing the displayed sensor + connect(sensorDataNamesGroup, static_cast(&QButtonGroup::buttonClicked), [this](QAbstractButton *button) + { + selectedSensorChanged(button->objectName()); + }); + + // show current temperature below the mouse + connect(sensorGraphs, &QCustomPlot::mouseMove, this, &Ekos::Observatory::mouseOverLine); } diff --git a/kstars/ekos/observatory/observatory.ui b/kstars/ekos/observatory/observatory.ui --- a/kstars/ekos/observatory/observatory.ui +++ b/kstars/ekos/observatory/observatory.ui @@ -14,7 +14,7 @@ Observatory - Status of the Observatory + @@ -126,7 +126,7 @@ - true + false Motion @@ -802,45 +802,7 @@ 3 - - - - - 0 - 0 - - - - - 48 - 48 - - - - - 48 - 48 - - - - - - - - - - - false - - - Display the weather status. The warning and alert limits are set in the INDI tab. - - - Weather Status: - - - - + false @@ -1064,10 +1026,61 @@ + + + + + 0 + 0 + + + + + 48 + 48 + + + + + 48 + 48 + + + + + + + + + + + false + + + Display the weather status. The warning and alert limits are set in the INDI tab. + + + Weather Status: + + + + + + + + 0 + 0 + + + + + + + - - - Sensor Data + + + <html><head/><body><p>Current data of the weather sensors. Click on the sensor name to display its data over time.</p></body></html> @@ -1091,6 +1104,14 @@ + + + QCustomPlot + QWidget +
auxiliary/qcustomplot.h
+ 1 +
+