File indexing completed on 2024-05-05 15:55:12

0001 /*
0002     SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
0003     SPDX-FileCopyrightText: 2021 Wolfgang Reissenberger <sterne-jaeger@openfuture.de>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "focushfrvplot.h"
0009 
0010 #include "klocalizedstring.h"
0011 
0012 #include "curvefit.h"
0013 
0014 #define DEFAULT_BASIC_FONT_SIZE 10
0015 
0016 FocusHFRVPlot::FocusHFRVPlot(QWidget *parent) : QCustomPlot (parent)
0017 {
0018     setBackground(QBrush(Qt::black));
0019 
0020     xAxis->setBasePen(QPen(Qt::white, 1));
0021     yAxis->setBasePen(QPen(Qt::white, 1));
0022 
0023     xAxis->setTickPen(QPen(Qt::white, 1));
0024     yAxis->setTickPen(QPen(Qt::white, 1));
0025 
0026     xAxis->setSubTickPen(QPen(Qt::white, 1));
0027     yAxis->setSubTickPen(QPen(Qt::white, 1));
0028 
0029     xAxis->setTickLabelColor(Qt::white);
0030     yAxis->setTickLabelColor(Qt::white);
0031 
0032     xAxis->setLabelColor(Qt::white);
0033     yAxis->setLabelColor(Qt::white);
0034 
0035     xAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
0036     yAxis->grid()->setPen(QPen(QColor(140, 140, 140), 1, Qt::DotLine));
0037     xAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
0038     yAxis->grid()->setSubGridPen(QPen(QColor(80, 80, 80), 1, Qt::DotLine));
0039     xAxis->grid()->setZeroLinePen(Qt::NoPen);
0040     yAxis->grid()->setZeroLinePen(Qt::NoPen);
0041 
0042     yAxis->setLabel(i18n("Value"));
0043 
0044     setInteractions(QCP::iRangeZoom);
0045     setInteraction(QCP::iRangeDrag, true);
0046 
0047     polynomialGraph = addGraph();
0048     polynomialGraph->setLineStyle(QCPGraph::lsLine);
0049     polynomialGraph->setPen(QPen(QColor(140, 140, 140), 2, Qt::DotLine));
0050     polynomialGraph->setScatterStyle(QCPScatterStyle::ssNone);
0051 
0052     v_graph = addGraph();
0053     v_graph->setLineStyle(QCPGraph::lsNone);
0054     v_graph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, Qt::white, Qt::white, 14));
0055 
0056     focusPoint = addGraph();
0057     focusPoint->setLineStyle(QCPGraph::lsImpulse);
0058     focusPoint->setPen(QPen(QColor(140, 140, 140), 2, Qt::SolidLine));
0059     focusPoint->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, Qt::white, Qt::yellow, 10));
0060 
0061     // determine font size
0062     if (parent != nullptr)
0063         setBasicFontSize(parent->font().pointSize());
0064     else
0065         setBasicFontSize(DEFAULT_BASIC_FONT_SIZE);
0066 
0067     connect(this, &QCustomPlot::mouseMove, [this](QMouseEvent * event)
0068     {
0069         double key = xAxis->pixelToCoord(event->localPos().x());
0070         if (xAxis->range().contains(key))
0071         {
0072             QCPGraph *graph = qobject_cast<QCPGraph *>(plottableAt(event->pos(), false));
0073 
0074             if (graph)
0075             {
0076                 if(graph == v_graph)
0077                 {
0078                     int positionKey = v_graph->findBegin(key);
0079                     double focusPosition = v_graph->dataMainKey(positionKey);
0080                     double focusValue = v_graph->dataMainValue(positionKey);
0081                     QToolTip::showText(
0082                         event->globalPos(),
0083                         i18nc("Graphics tooltip; %1 is the Focus Position; %2 is the Focus Value;",
0084                               "<table>"
0085                               "<tr><td>POS:   </td><td>%1</td></tr>"
0086                               "<tr><td>VAL:   </td><td>%2</td></tr>"
0087                               "</table>",
0088                               QString::number(focusPosition, 'f', 0),
0089                               QString::number(focusValue, 'g', 3)));
0090                 }
0091                 else if (graph == focusPoint)
0092                 {
0093                     int positionKey = focusPoint->findBegin(key);
0094                     double focusPosition = focusPoint->dataMainKey(positionKey);
0095                     double focusValue = focusPoint->dataMainValue(positionKey);
0096                     QToolTip::showText(
0097                         event->globalPos(),
0098                         i18nc("Graphics tooltip; %1 is the Minimum Focus Position; %2 is the Focus Value;",
0099                               "<table>"
0100                               "<tr><td>MIN:   </td><td>%1</td></tr>"
0101                               "<tr><td>VAL:   </td><td>%2</td></tr>"
0102                               "</table>",
0103                               QString::number(focusPosition, 'f', 0),
0104                               QString::number(focusValue, 'g', 3)));
0105 
0106                 }
0107             }
0108         }
0109     });
0110     // Add the error bars
0111     errorBars = new QCPErrorBars((this)->xAxis, (this)->yAxis);
0112 }
0113 
0114 void FocusHFRVPlot::drawHFRIndices()
0115 {
0116     // Setup error bars
0117     QVector<double> err;
0118     if (m_useErrorBars)
0119     {
0120         errorBars->removeFromLegend();
0121         errorBars->setAntialiased(false);
0122         errorBars->setDataPlottable((this)->v_graph);
0123         errorBars->setPen(QPen(QColor(180, 180, 180)));
0124     }
0125 
0126     // Put the sample number inside the plot point's circle.
0127     for (int i = 0; i < m_position.size(); ++i)
0128     {
0129         QCPItemText *textLabel = new QCPItemText(this);
0130         textLabel->setPositionAlignment(Qt::AlignCenter | Qt::AlignHCenter);
0131         textLabel->position->setType(QCPItemPosition::ptPlotCoords);
0132         textLabel->position->setCoords(m_position[i], m_displayValue[i]);
0133         if (m_goodPosition[i])
0134         {
0135             textLabel->setText(QString::number(i + 1));
0136             textLabel->setFont(QFont(font().family(), (int) std::round(1.2 * basicFontSize())));
0137             textLabel->setColor(Qt::red);
0138         }
0139         else
0140         {
0141             textLabel->setText("X");
0142             textLabel->setFont(QFont(font().family(), (int) std::round(2 * basicFontSize())));
0143             textLabel->setColor(Qt::black);
0144         }
0145         textLabel->setPen(Qt::NoPen);
0146 
0147         if (m_useErrorBars)
0148             err.push_front(m_sigma[i]);
0149     }
0150     // Setup the error bar data if we're using it
0151     errorBars->setVisible(m_useErrorBars);
0152     if (m_useErrorBars)
0153         errorBars->setData(err);
0154 }
0155 
0156 void FocusHFRVPlot::init(QString yAxisLabel, double starUnits, bool minimum, bool useWeights, bool showPosition)
0157 {
0158     yAxis->setLabel(yAxisLabel);
0159     m_starUnits = starUnits;
0160     m_Minimum = minimum;
0161     m_showPositions = showPosition;
0162     m_position.clear();
0163     m_value.clear();
0164     m_displayValue.clear();
0165     m_sigma.clear();
0166     m_goodPosition.clear();
0167     polynomialGraph->data()->clear();
0168     focusPoint->data().clear();
0169     m_useErrorBars = useWeights;
0170     errorBars->data().clear();
0171     // the next step seems necessary (QCP bug?)
0172     v_graph->setData(QVector<double> {}, QVector<double> {});
0173     focusPoint->setData(QVector<double> {}, QVector<double> {});
0174     m_polynomialGraphIsVisible = false;
0175     m_isVShape = false;
0176     minValue = -1;
0177     maxValue = -1;
0178     FocusHFRVPlot::clearItems();
0179     replot();
0180 }
0181 
0182 void FocusHFRVPlot::drawHFRPlot(double currentValue, int pulseDuration)
0183 {
0184     // DrawHFRPlot is the base on which other things are built upon.
0185     // Clear any previous annotations.
0186     FocusHFRVPlot::clearItems();
0187 
0188     v_graph->setData(m_position, m_displayValue);
0189     drawHFRIndices();
0190 
0191     double currentDisplayValue = getDisplayValue(currentValue);
0192 
0193     if (minValue > currentDisplayValue || minValue < 0.0)
0194         minValue = currentDisplayValue;
0195     if (maxValue < currentDisplayValue)
0196         maxValue = currentDisplayValue;
0197 
0198     double minVal = currentDisplayValue / 2.5;
0199     if (m_displayValue.size() > 0)
0200         minVal = std::max(0.0, std::min(minValue, *std::min_element(m_displayValue.begin(), m_displayValue.end())));
0201 
0202     // True for the position-based algorithms and those that simulate position.
0203     if (m_showPositions)
0204     {
0205         const double minPosition = m_position.empty() ?
0206                                    0 : *std::min_element(m_position.constBegin(), m_position.constEnd());
0207         const double maxPosition = m_position.empty() ?
0208                                    1e6 : *std::max_element(m_position.constBegin(), m_position.constEnd());
0209         xAxis->setRange(minPosition - pulseDuration, maxPosition + pulseDuration);
0210     }
0211     else
0212     {
0213         //xAxis->setLabel(i18n("Iteration"));
0214         xAxis->setRange(1, m_displayValue.count() + 1);
0215     }
0216 
0217     if (m_displayValue.size() == 1)
0218         // 1 point gets placed in the middle vertically.
0219         yAxis->setRange(0, 2 * getDisplayValue(maxValue));
0220     else
0221     {
0222         double upper;
0223         m_Minimum ? upper = 1.5 * maxValue : upper = 1.2 * maxValue;
0224         yAxis->setRange(minVal - (0.25 * (upper - minVal)), upper);
0225     }
0226     replot();
0227 }
0228 
0229 void FocusHFRVPlot::addPosition(double pos, double newValue, double sigma, bool outlier, int pulseDuration, bool plot)
0230 {
0231     m_position.append(pos);
0232     m_value.append(newValue);
0233     m_displayValue.append(getDisplayValue(newValue));
0234     m_sigma.append(sigma);
0235     outlier ? m_goodPosition.append(false) : m_goodPosition.append(true);
0236 
0237     if (plot)
0238         drawHFRPlot(newValue, pulseDuration);
0239 }
0240 
0241 void FocusHFRVPlot::setTitle(const QString &title, bool plot)
0242 {
0243     plotTitle = new QCPItemText(this);
0244     plotTitle->setColor(QColor(255, 255, 255));
0245     plotTitle->setPositionAlignment(Qt::AlignTop | Qt::AlignHCenter);
0246     plotTitle->position->setType(QCPItemPosition::ptAxisRectRatio);
0247     plotTitle->position->setCoords(0.5, 0);
0248     plotTitle->setText("");
0249     plotTitle->setFont(QFont(font().family(), 11));
0250     plotTitle->setVisible(true);
0251 
0252     plotTitle->setText(title);
0253     if (plot) replot();
0254 }
0255 
0256 void FocusHFRVPlot::finalUpdates(const QString &title, bool plot)
0257 {
0258     // Update a previously set title without having to redraw everything
0259     if (plotTitle != nullptr)
0260     {
0261         plotTitle->setText(title);
0262         if (plot) replot();
0263     }
0264 }
0265 void FocusHFRVPlot::setSolutionVShape(bool isVShape)
0266 {
0267     m_isVShape = isVShape;
0268 
0269     QPen pen;
0270     pen.setWidth(1);
0271 
0272     if (isVShape)
0273     {
0274         pen.setColor(QColor(180, 180, 180));
0275     }
0276     else
0277     {
0278         pen.setColor(QColor(254, 0, 0));
0279         // clear focus point
0280         focusPoint->data().clear();
0281     }
0282 
0283     polynomialGraph->setPen(pen);
0284 }
0285 
0286 void FocusHFRVPlot::clearItems()
0287 {
0288     // Clear all the items on the HFR plot and reset pointers
0289     QCustomPlot::clearItems();
0290     plotTitle = nullptr;
0291     CFZ = nullptr;
0292 }
0293 
0294 void FocusHFRVPlot::drawMinimum(double solutionPosition, double solutionValue, bool plot)
0295 {
0296     focusPoint->data()->clear();
0297 
0298     // do nothing for invalid positions
0299     if (solutionPosition < 0)
0300         return;
0301 
0302     double displayValue = getDisplayValue(solutionValue);
0303     minValue = std::min(minValue, displayValue);
0304     maxValue = std::max(maxValue, displayValue);
0305 
0306     focusPoint->addData(solutionPosition, displayValue);
0307     QCPItemText *textLabel = new QCPItemText(this);
0308     textLabel->setPositionAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
0309     textLabel->setColor(Qt::red);
0310     textLabel->setPadding(QMargins(0, 0, 0, 0));
0311     textLabel->setBrush(Qt::white);
0312     textLabel->setPen(Qt::NoPen);
0313     textLabel->setFont(QFont(font().family(), (int) std::round(0.8 * basicFontSize())));
0314     textLabel->position->setType(QCPItemPosition::ptPlotCoords);
0315     textLabel->setText(QString::number(solutionPosition, 'f', 0));
0316     if (m_Minimum)
0317         textLabel->position->setCoords(solutionPosition, (maxValue + 2 * displayValue) / 3);
0318     else
0319         textLabel->position->setCoords(solutionPosition, (2 * displayValue + minValue) / 3);
0320     if (plot) replot();
0321 }
0322 
0323 void FocusHFRVPlot::drawCFZ(double solutionPosition, double solutionValue, int cfzSteps, bool plot)
0324 {
0325     // do nothing for invalid positions
0326     if (solutionPosition < 0 || solutionValue < 0)
0327         return;
0328 
0329     if (!plot)
0330     {
0331         if (CFZ)
0332             CFZ->setVisible(false);
0333     }
0334     else
0335     {
0336         if (!CFZ)
0337             CFZ = new QCPItemBracket(this);
0338 
0339         CFZ->left->setType(QCPItemPosition::ptPlotCoords);
0340         CFZ->right->setType(QCPItemPosition::ptPlotCoords);
0341 
0342         double displayValue = getDisplayValue(solutionValue);
0343         double y;
0344         if (m_Minimum)
0345             y = (7 * minValue - maxValue) / 6;
0346         else
0347             y = (maxValue + 2 * minValue) / 3;
0348 
0349         CFZ->left->setCoords(solutionPosition + cfzSteps / 2.0, y);
0350         CFZ->right->setCoords(solutionPosition - cfzSteps / 2.0, y);
0351         CFZ->setLength(15);
0352         CFZ->setAntialiased(false);
0353         CFZ->setPen(QPen(QColor(Qt::yellow)));
0354         CFZ->setVisible(true);
0355     }
0356     replot();
0357 }
0358 
0359 void FocusHFRVPlot::drawPolynomial(Ekos::PolynomialFit *polyFit, bool isVShape, bool makeVisible, bool plot)
0360 {
0361     if (polyFit == nullptr)
0362         return;
0363 
0364     // do nothing if graph is not visible and should not be made as such
0365     if(makeVisible)
0366         m_polynomialGraphIsVisible = true;
0367     else if (m_polynomialGraphIsVisible == false)
0368         return;
0369 
0370     setSolutionVShape(isVShape);
0371     if (polynomialGraph != nullptr)
0372     {
0373         polynomialGraph->data()->clear();
0374         QCPRange range = xAxis->range();
0375         double interval = range.size() / 20.0;
0376 
0377         for(double x = range.lower ; x < range.upper ; x += interval)
0378         {
0379             double y = getDisplayValue(polyFit->f(x));
0380             polynomialGraph->addData(x, y);
0381         }
0382         if (plot) replot();
0383     }
0384 }
0385 
0386 void FocusHFRVPlot::drawCurve(Ekos::CurveFitting *curveFit, bool isVShape, bool makeVisible, bool plot)
0387 {
0388     if (curveFit == nullptr)
0389         return;
0390 
0391     // do nothing if graph is not visible and should not be made as such
0392     if(makeVisible)
0393         m_polynomialGraphIsVisible = true;
0394     else if (!makeVisible || !m_polynomialGraphIsVisible)
0395         return;
0396 
0397     setSolutionVShape(isVShape);
0398     if (polynomialGraph != nullptr)
0399     {
0400         polynomialGraph->data()->clear();
0401         QCPRange range = xAxis->range();
0402         double interval = range.size() / 20.0;
0403 
0404         for(double x = range.lower ; x < range.upper ; x += interval)
0405         {
0406             double y = getDisplayValue(curveFit->f(x));
0407             polynomialGraph->addData(x, y);
0408         }
0409         if (plot) replot();
0410     }
0411 }
0412 
0413 void FocusHFRVPlot::redraw(Ekos::PolynomialFit *polyFit, double solutionPosition, double solutionValue)
0414 {
0415     if (m_value.empty() == false)
0416         drawHFRPlot(m_value.last(), 0);
0417 
0418     drawPolynomial(polyFit, m_isVShape, false);
0419     drawMinimum(solutionPosition, solutionValue);
0420 }
0421 
0422 void FocusHFRVPlot::redrawCurve(Ekos::CurveFitting *curveFit, double solutionPosition, double solutionValue)
0423 {
0424     if (m_value.empty() == false)
0425         drawHFRPlot(solutionValue, 0);
0426 
0427     drawCurve(curveFit, m_isVShape, false);
0428     drawMinimum(solutionPosition, solutionValue);
0429 }
0430 
0431 void FocusHFRVPlot::setBasicFontSize(int basicFontSize)
0432 {
0433     m_basicFontSize = basicFontSize;
0434 
0435     // Axis Labels Settings
0436     yAxis->setLabelFont(QFont(font().family(), basicFontSize));
0437     xAxis->setTickLabelFont(QFont(font().family(), (int) std::round(0.9 * basicFontSize)));
0438     yAxis->setTickLabelFont(QFont(font().family(), (int) std::round(0.9 * basicFontSize)));
0439 }
0440 
0441 // Internally calculations are done in units of pixels for HFR and FWHM
0442 // If user preference is arcsecs then convert values for display purposes.
0443 double FocusHFRVPlot::getDisplayValue(const double value)
0444 {
0445     return value * m_starUnits;
0446 }
0447