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