From e097781145f2225e7154fbda505f966aee7b5fca Mon Sep 17 00:00:00 2001
From: Gennady Pospelov <g.pospelov@fz-juelich.de>
Date: Wed, 8 Feb 2017 16:51:27 +0100
Subject: [PATCH] Student's DistributionWidget was refactored.

---
 .../Views/InfoWidgets/DistributionWidget.cpp  | 359 +++++++++++-------
 .../Views/InfoWidgets/DistributionWidget.h    |  23 +-
 2 files changed, 230 insertions(+), 152 deletions(-)

diff --git a/GUI/coregui/Views/InfoWidgets/DistributionWidget.cpp b/GUI/coregui/Views/InfoWidgets/DistributionWidget.cpp
index cb5a12f1914..d68cd76d0d1 100644
--- a/GUI/coregui/Views/InfoWidgets/DistributionWidget.cpp
+++ b/GUI/coregui/Views/InfoWidgets/DistributionWidget.cpp
@@ -21,18 +21,21 @@
 #include "RealLimitsItems.h"
 #include <QLabel>
 #include <QVBoxLayout>
-#include <sstream>
+#include <algorithm>
 
 namespace
 {
-int number_of_points_for_smooth_plot = 100;
-double sigmafactor_for_smooth_plot = 3.5;
-double gap_between_bars = 0.05;
-double xRangeDivisor = 9;
-double xBarRange = 0.4;
-double percentage_for_yRange = 1.1;
-int warning_sign_xpos = 50;
-int warning_sign_ypos = 18;
+const QPair<double, double> default_xrange(-0.1, 0.1);
+const QPair<double, double> default_yrange(0.0, 1.1);
+const int warning_sign_xpos = 50;
+const int warning_sign_ypos = 18;
+
+QPair<double, double> xRangeForValue(double value);
+QPair<double, double> xRangeForValues(double value1, double value2);
+QPair<double, double> xRangeForValues(const QVector<double>& xvec);
+QPair<double, double> yRangeForValues(const QVector<double>& yvec);
+double optimalBarWidth(double xmin, double xmax, int nbars = 1);
+
 }
 
 DistributionWidget::DistributionWidget(QWidget *parent)
@@ -41,8 +44,6 @@ DistributionWidget::DistributionWidget(QWidget *parent)
     , m_item(0)
     , m_label(new QLabel)
     , m_resetAction(new QAction(this))
-    , m_xRange(new QCPRange)
-    , m_yRange(new QCPRange)
     , m_warningSign(0)
 {
     setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
@@ -98,39 +99,19 @@ void DistributionWidget::setItem(DistributionItem *item)
                     [this](SessionItem *) {
             m_item = 0;
         }, this);
-
     }
 
 }
 
 void DistributionWidget::plotItem()
 {
-    delete m_warningSign;
-    m_warningSign = 0;
-
-    m_plot->clearGraphs();
-    m_plot->clearItems();
-    m_plot->clearPlottables();
-    m_plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes
-                            | QCP::iSelectLegend | QCP::iSelectPlottables);
-    m_plot->yAxis->setLabel("probability");
-    m_plot->xAxis2->setVisible(true);
-    m_plot->yAxis2->setVisible(true);
-    m_plot->xAxis2->setTickLabels(false);
-    m_plot->yAxis2->setTickLabels(false);
-    m_plot->xAxis2->setTicks(false);
-    m_plot->yAxis2->setTicks(false);
+    init_plot();
 
-    std::unique_ptr<IDistribution1D> P_distribution {};
-    bool exceptionThrown = false;
     try {
-        P_distribution = m_item->createDistribution();
+        plot_distributions();
+
     } catch (const std::exception &ex) {
-        exceptionThrown = true;
-        Q_UNUSED(ex);
-        m_plot->clearGraphs();
-        m_plot->clearItems();
-        m_plot->clearPlottables();
+        init_plot();
 
         m_warningSign = new WarningSignWidget(this);
 
@@ -138,119 +119,29 @@ void DistributionWidget::plotItem()
             = QString("Wrong parameters\n").append(QString::fromStdString(ex.what()));
 
         m_warningSign->setWarningMessage(message);
-        QPoint pos = getPositionForWarningSign();
+        QPoint pos = positionForWarningSign();
         m_warningSign->setPosition(pos.x(), pos.y());
         m_warningSign->show();
     }
 
-    if (m_item->itemName() != Constants::DistributionNoneType && !exceptionThrown) {
-        int numberOfSamples
-            = m_item->getItemValue(DistributionItem::P_NUMBER_OF_SAMPLES).toInt();
-        double sigmafactor
-            = m_item->getItemValue(DistributionItem::P_SIGMA_FACTOR).toDouble();
-
-        RealLimits limits;
-        if(m_item->isTag(DistributionItem::P_LIMITS)) {
-            auto limitsItem = dynamic_cast<RealLimitsItem*>(
-                        m_item->getGroupItem(DistributionItem::P_LIMITS));
-            limits = limitsItem->createRealLimits();
-        }
-
-        QVector<double> xBar;
-        QVector<double> x;
-        xBar = xBar.fromStdVector(P_distribution->equidistantPoints(numberOfSamples, sigmafactor, limits));
-        x = x.fromStdVector(P_distribution->equidistantPoints(number_of_points_for_smooth_plot,
-                                                              sigmafactor_for_smooth_plot, limits));
-        QVector<double> yBar(xBar.size());
-        QVector<double> y(x.size());
-        double sumOfWeigths(0);
-
-        for (int i = 0; i < xBar.size(); ++i) {
-            yBar[i] = P_distribution->probabilityDensity(xBar[i]);
-            sumOfWeigths += yBar[i];
-        }
-        for (int i = 0; i < x.size(); ++i) {
-            y[i] = P_distribution->probabilityDensity(x[i]);
-        }
-        for (int i = 0; i < y.size(); ++i) {
-            y[i] = y[i] / sumOfWeigths;
-        }
-        for (int i = 0; i < yBar.size(); ++i) {
-            yBar[i] = yBar[i] / sumOfWeigths;
-        }
-        m_plot->addGraph();
-        m_plot->graph(0)->setData(x, y);
-        QCPBars *bars = new QCPBars(m_plot->xAxis, m_plot->yAxis);
-        bars->setWidth(getWidthOfBars(xBar[0], xBar[xBar.length() - 1], xBar.length()));
-        bars->setData(xBar, yBar);
-        double xRange = (x[x.size() - 1] - x[0]) / xRangeDivisor;
-        m_xRange = new QCPRange(x[0] - xRange, x[x.size() - 1] + xRange);
-        m_yRange = new QCPRange(0, y[getMaxYPosition(y.size())] * percentage_for_yRange);
-        m_plot->xAxis->setRange(*m_xRange);
-        m_plot->yAxis->setRange(*m_yRange);
-        m_plot->addPlottable(bars);
-        setVerticalDashedLine(xBar[0], 0, xBar[xBar.length() - 1], m_plot->yAxis->range().upper);
-    } else if(!exceptionThrown) {
-        QVector<double> xPos;
-        QVector<double> yPos;
-        xPos.push_back(m_item->getItemValue(DistributionNoneItem::P_VALUE).toDouble());
-        yPos.push_back(1);
-        QCPBars *bars = new QCPBars(m_plot->xAxis, m_plot->yAxis);
-        bars->setWidth(gap_between_bars);
-        bars->setData(xPos, yPos);
-        m_plot->addPlottable(bars);
-        m_xRange = new QCPRange(xPos[0] - xBarRange, xPos[0] + xBarRange);
-        m_yRange = new QCPRange(0, yPos[0] * percentage_for_yRange);
-        m_plot->xAxis->setRange(*m_xRange);
-        m_plot->yAxis->setRange(*m_yRange);
-        setVerticalDashedLine(xPos[0], 0, xPos[xPos.size() - 1], m_plot->yAxis->range().upper);
-    }
     m_plot->replot();
 }
 
-double DistributionWidget::getWidthOfBars(double min, double max, int samples)
-{
-    double widthConst = (max - min) * gap_between_bars;
-    double widthSample = (max - min) / samples;
-
-    if (widthConst > widthSample) {
-        return widthSample;
-    }
-    return widthConst;
-}
-
-void DistributionWidget::setVerticalDashedLine(double xMin, double yMin, double xMax, double yMax)
+void DistributionWidget::plotVerticalLine(double xMin, double yMin, double xMax, double yMax)
 {
-    QCPItemLine *min = new QCPItemLine(m_plot);
-    QCPItemLine *max = new QCPItemLine(m_plot);
+    QCPItemLine *line = new QCPItemLine(m_plot);
 
     QPen pen(Qt::blue, 1, Qt::DashLine);
-    min->setPen(pen);
-    max->setPen(pen);
-
-    min->setSelectable(true);
-    max->setSelectable(true);
+    line->setPen(pen);
+    line->setSelectable(true);
 
-    // Adding the vertical lines to the plot
-    m_plot->addItem(min);
-    min->start->setCoords(xMin, yMin);
-    min->end->setCoords(xMin, yMax);
-
-    m_plot->addItem(max);
-    max->start->setCoords(xMax, yMin);
-    max->end->setCoords(xMax, yMax);
+    m_plot->addItem(line);
+    line->start->setCoords(xMin, yMin);
+    line->end->setCoords(xMax, yMax);
 }
 
-int DistributionWidget::getMaxYPosition(int y)
-{
-    if ((y - 1) % 2 == 0) {
-        return (y - 1) / 2;
-    } else {
-        return (y / 2) - 1;
-    }
-}
+//! Generates label with current mouse position.
 
-// get current mouse position in plot
 void DistributionWidget::onMouseMove(QMouseEvent *event)
 {
     QPoint point = event->pos();
@@ -258,9 +149,8 @@ void DistributionWidget::onMouseMove(QMouseEvent *event)
     double yPos = m_plot->yAxis->pixelToCoord(point.y());
 
     if (m_plot->xAxis->range().contains(xPos) && m_plot->yAxis->range().contains(yPos)) {
-        std::stringstream labelText;
-        labelText << "[x: " << xPos << ",  y: " << yPos << "]";
-        m_label->setText(labelText.str().c_str());
+        QString text = QString("[x:%1, y:%2]").arg(xPos).arg(yPos);
+        m_label->setText(text);
     }
 }
 
@@ -274,21 +164,156 @@ void DistributionWidget::onMousePress(QMouseEvent *event)
     }
 }
 
+//! Reset zoom range to initial state.
+
 void DistributionWidget::resetView()
 {
-    m_plot->xAxis->setRange(*m_xRange);
-    m_plot->yAxis->setRange(*m_yRange);
+    m_plot->xAxis->setRange(m_xRange);
+    m_plot->yAxis->setRange(m_yRange);
     m_plot->replot();
 }
 
-void DistributionWidget::setXAxisName(QString xAxisName)
+//! Clears all plottables, resets axes to initial state.
+
+void DistributionWidget::init_plot()
+{
+    delete m_warningSign;
+    m_warningSign = 0;
+
+    m_plot->clearGraphs();
+    m_plot->clearItems();
+    m_plot->clearPlottables();
+    m_plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes
+                            | QCP::iSelectLegend | QCP::iSelectPlottables);
+    m_plot->yAxis->setLabel("probability");
+    m_plot->xAxis2->setVisible(true);
+    m_plot->yAxis2->setVisible(true);
+    m_plot->xAxis2->setTickLabels(false);
+    m_plot->yAxis2->setTickLabels(false);
+    m_plot->xAxis2->setTicks(false);
+    m_plot->yAxis2->setTicks(false);
+
+    setPlotRange(default_xrange, default_yrange);
+}
+
+void DistributionWidget::plot_distributions()
+{
+    if (m_item->modelType() == Constants::DistributionNoneType)
+        plot_single_value();
+
+    else
+        plot_multiple_values();
+}
+
+//! Plots a single bar corresponding to the value in DistributionNoteItem.
+
+void DistributionWidget::plot_single_value()
+{
+    Q_ASSERT(m_item->displayName() == Constants::DistributionNoneType);
+
+    double value = m_item->getItemValue(DistributionNoneItem::P_VALUE).toDouble();
+
+    QVector<double> xPos = QVector<double>() << value;
+    QVector<double> yPos = QVector<double>() << 1.0;
+    plotBars(xPos, yPos);
+
+    plotVerticalLine(value, default_yrange.first, value, default_yrange.second);
+}
+
+void DistributionWidget::plot_multiple_values()
+{
+    Q_ASSERT(m_item->displayName() != Constants::DistributionNoneType);
+
+    int numberOfSamples = m_item->getItemValue(DistributionItem::P_NUMBER_OF_SAMPLES).toInt();
+    double sigmafactor(0.0);
+    if (m_item->isTag(DistributionItem::P_SIGMA_FACTOR))
+        sigmafactor = m_item->getItemValue(DistributionItem::P_SIGMA_FACTOR).toDouble();
+
+    RealLimits limits;
+    if (m_item->isTag(DistributionItem::P_LIMITS)) {
+        auto limitsItem
+            = dynamic_cast<RealLimitsItem*>(m_item->getGroupItem(DistributionItem::P_LIMITS));
+        limits = limitsItem->createRealLimits();
+    }
+
+    auto dist = m_item->createDistribution();
+
+    // Calculating bars
+    std::vector<double> xp = dist->equidistantPoints(numberOfSamples, sigmafactor, limits);
+    std::vector<double> yp(xp.size());
+    std::transform(xp.begin(), xp.end(), yp.begin(),
+                   [&](double value) { return dist->probabilityDensity(value); });
+    double sumOfWeights = std::accumulate(yp.begin(), yp.end(), 0.0);
+    Q_ASSERT(sumOfWeights != 0.0);
+
+    QVector<double> xBar = QVector<double>::fromStdVector(xp);
+    QVector<double> yBar(xBar.size());
+    std::transform(yp.begin(), yp.end(), yBar.begin(),
+                   [&](double value) { return value / sumOfWeights; });
+
+    plotBars(xBar, yBar);
+
+    // calculating function points (for interval, bigger than bars)
+    auto xRange = xRangeForValues(xBar);
+    const int number_of_points = 100;
+    std::vector<double> xf
+        = dist->equidistantPointsInRange(number_of_points, xRange.first, xRange.second);
+    std::vector<double> yf(xf.size());
+    std::transform(xf.begin(), xf.end(), yf.begin(),
+                   [&](double value) { return dist->probabilityDensity(value); });
+
+    QVector<double> xFunc = QVector<double>::fromStdVector(xf);
+    QVector<double> yFunc(xFunc.size());
+    std::transform(yf.begin(), yf.end(), yFunc.begin(),
+                   [&](double value) { return value / sumOfWeights; });
+
+    plotFunction(xFunc, yFunc);
+}
+
+void DistributionWidget::setPlotRange(const QPair<double, double>& xRange,
+                                      const QPair<double, double>& yRange)
+{
+    m_xRange = QCPRange(xRange.first, xRange.second);
+    m_yRange = QCPRange(yRange.first, yRange.second);
+    m_plot->xAxis->setRange(m_xRange);
+    m_plot->yAxis->setRange(m_yRange);
+}
+
+void DistributionWidget::plotBars(const QVector<double>& xbars, const QVector<double>& ybars)
+{
+    Q_ASSERT(xbars.size() > 0);
+
+    auto xRange = xRangeForValues(xbars);
+    auto yRange = yRangeForValues(ybars);
+    setPlotRange(xRange, yRange);
+
+    double barWidth = optimalBarWidth(xRange.first, xRange.second, xbars.size());
+
+    QCPBars *bars = new QCPBars(m_plot->xAxis, m_plot->yAxis);
+
+    bars->setWidth(barWidth);
+    bars->setData(xbars, ybars);
+    m_plot->addPlottable(bars);
+}
+
+void DistributionWidget::plotFunction(const QVector<double>& xFunc, const QVector<double>& yFunc)
+{
+    auto xRange = xRangeForValues(xFunc);
+    auto yRange = yRangeForValues(yFunc);
+    setPlotRange(xRange, yRange);
+
+    m_plot->addGraph();
+    m_plot->graph(0)->setData(xFunc, yFunc);
+}
+
+void DistributionWidget::setXAxisName(const QString& xAxisName)
 {
     m_plot->xAxis->setLabel(xAxisName);
 }
 
 //! Returns position for warning sign at the bottom right corner of the editor. The position will
 //! be adjusted according to the visibility of scroll bars
-QPoint DistributionWidget::getPositionForWarningSign()
+QPoint DistributionWidget::positionForWarningSign()
 {
     int x = m_plot->geometry().topRight().x() - warning_sign_xpos;
     int y = m_plot->geometry().topRight().y() + warning_sign_ypos;
@@ -300,7 +325,57 @@ void DistributionWidget::resizeEvent(QResizeEvent *event)
 {
     Q_UNUSED(event);
     if (m_warningSign) {
-        QPoint pos = getPositionForWarningSign();
+        QPoint pos = positionForWarningSign();
         m_warningSign->setPosition(pos.x(), pos.y());
     }
 }
+
+namespace {
+//! Returns (xmin, xmax) of x-axis to display single value.
+QPair<double, double> xRangeForValue(double value)
+{
+    const double range_factor(0.1);
+
+    double dr = (value == 0.0 ? 1.0*range_factor : std::abs(value)*range_factor);
+    double xmin = value - dr;
+    double xmax = value + dr;
+
+    return QPair<double, double>(xmin, xmax);
+}
+
+//! Returns (xmin, xmax) of x-axis to display two values.
+
+QPair<double, double> xRangeForValues(double value1, double value2)
+{
+    const double range_factor(0.1);
+    double dr = (value2 - value1)*range_factor;
+    Q_ASSERT(dr > 0.0);
+
+    return QPair<double, double>(value1 - dr, value2 + dr);
+}
+
+QPair<double, double> xRangeForValues(const QVector<double>& xvec)
+{
+    Q_ASSERT(!xvec.isEmpty());
+    return xvec.size() == 1 ? xRangeForValue(xvec.front())
+                            : xRangeForValues(xvec.front(), xvec.back());
+}
+
+QPair<double, double> yRangeForValues(const QVector<double>& yvec)
+{
+    const double range_factor(1.1);
+    double ymax = *std::max_element(yvec.begin(), yvec.end());
+    return QPair<double, double>(default_yrange.first, ymax*range_factor);
+}
+
+//! Returns width of the bar, which will be optimally looking for x-axis range (xmin, xmax)
+
+double optimalBarWidth(double xmin, double xmax, int nbars)
+{
+    double optimalWidth = (xmax - xmin) / 40.;
+    double width = (xmax - xmin) / nbars;
+
+    return optimalWidth < width ? optimalWidth : width;
+}
+
+}
diff --git a/GUI/coregui/Views/InfoWidgets/DistributionWidget.h b/GUI/coregui/Views/InfoWidgets/DistributionWidget.h
index c128c7e3b36..daa24751122 100644
--- a/GUI/coregui/Views/InfoWidgets/DistributionWidget.h
+++ b/GUI/coregui/Views/InfoWidgets/DistributionWidget.h
@@ -20,14 +20,11 @@
 #include "WarningSignWidget.h"
 #include "qcustomplot.h"
 #include <QWidget>
-#include <memory>
 
 class SessionItem;
-class AwesomePropertyEditor;
 class QLabel;
 class QCustomPlot;
 class DistributionItem;
-class QCPRange;
 class QAction;
 
 //! The DistributionWidget class plots 1d functions corresponding to domain's Distribution1D
@@ -41,11 +38,9 @@ public:
 
     void setItem(DistributionItem *item);
     void plotItem();
-    double getWidthOfBars(double min, double max, int samples);
-    void setVerticalDashedLine(double xMin, double yMin, double xMax, double yMax);
-    int getMaxYPosition(int y);
-    void setXAxisName(QString xAxisName);
-    QPoint getPositionForWarningSign();
+    void plotVerticalLine(double xMin, double yMin, double xMax, double yMax);
+    void setXAxisName(const QString& xAxisName);
+    QPoint positionForWarningSign();
 
 public slots:
     void onMouseMove(QMouseEvent *event);
@@ -53,16 +48,24 @@ public slots:
 
 protected:
     void resizeEvent(QResizeEvent *event);
+
 private slots:
     void resetView();
 
 private:
+    void init_plot();
+    void plot_distributions();
+    void plot_single_value();
+    void plot_multiple_values();
+    void setPlotRange(const QPair<double, double>& xRange, const QPair<double, double>& yRange);
+    void plotBars(const QVector<double>& xbars, const QVector<double>& ybars);
+    void plotFunction(const QVector<double>& xFunc, const QVector<double>& ybars);
+
     QCustomPlot *m_plot;
     DistributionItem *m_item;
     QLabel *m_label;
     QAction *m_resetAction;
-    QCPRange *m_xRange;
-    QCPRange *m_yRange;
+    QCPRange m_xRange, m_yRange;
     WarningSignWidget *m_warningSign;
 };
 
-- 
GitLab