Skip to content
Snippets Groups Projects
Commit e0977811 authored by Pospelov, Gennady's avatar Pospelov, Gennady
Browse files

Student's DistributionWidget was refactored.

parent 130a448d
No related branches found
No related tags found
No related merge requests found
......@@ -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;
}
}
......@@ -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;
};
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment