BornAgain  1.19.0
Simulate and fit neutron and x-ray scattering at grazing incidence
DistributionWidget.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file GUI/coregui/Views/InfoWidgets/DistributionWidget.cpp
6 //! @brief Implements class DistributionWidget
7 //!
8 //! @homepage http://www.bornagainproject.org
9 //! @license GNU General Public License v3 or higher (see COPYING)
10 //! @copyright Forschungszentrum Jülich GmbH 2018
11 //! @authors Scientific Computing Group at MLZ (see CITATION, AUTHORS)
12 //
13 // ************************************************************************************************
14 
20 #include <QLabel>
21 #include <QVBoxLayout>
22 #include <algorithm>
23 #include <qcustomplot.h>
24 
25 namespace {
26 const QPair<double, double> default_xrange(-0.1, 0.1);
27 const QPair<double, double> default_yrange(0.0, 1.1);
28 
29 QPair<double, double> xRangeForValue(double value);
30 QPair<double, double> xRangeForValues(double value1, double value2);
31 QPair<double, double> xRangeForValues(const QVector<double>& xvec);
32 QPair<double, double> yRangeForValues(const QVector<double>& yvec);
33 double optimalBarWidth(double xmin, double xmax, int nbars = 1);
34 
35 } // namespace
36 
38  : QWidget(parent)
39  , m_plot(new QCustomPlot)
40  , m_item(0)
41  , m_label(new QLabel)
42  , m_resetAction(new QAction(this))
43  , m_warningSign(new WarningSign(this))
44 {
45  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
46 
47  m_resetAction->setText("Reset View");
48  connect(m_resetAction, &QAction::triggered, this, &DistributionWidget::resetView);
49 
50  m_label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
51  m_label->setStyleSheet("background-color:white;");
52  m_label->setMargin(3);
53 
54  QVBoxLayout* mainLayout = new QVBoxLayout;
55  mainLayout->setMargin(0);
56  mainLayout->setSpacing(0);
57  mainLayout->addWidget(m_plot, 1);
58  mainLayout->addWidget(m_label);
59  setLayout(mainLayout);
60 
61  setStyleSheet("background-color:white;");
62  connect(m_plot, &QCustomPlot::mousePress, this, &DistributionWidget::onMousePress);
63  connect(m_plot, &QCustomPlot::mouseMove, this, &DistributionWidget::onMouseMove);
64 }
65 
67 {
68  if (m_item)
69  m_item->mapper()->unsubscribe(this);
70 }
71 
73 {
74  if (m_item == item) {
75  return;
76 
77  } else {
78  if (m_item) {
79  disconnect();
80  m_item->mapper()->unsubscribe(this);
81  }
82 
83  m_item = item;
84  if (!m_item)
85  return;
86 
87  plotItem();
88 
89  m_item->mapper()->setOnPropertyChange([this](QString) { plotItem(); }, this);
90 
91  m_item->mapper()->setOnItemDestroy([this](SessionItem*) { m_item = 0; }, this);
92  }
93 }
94 
96 {
97  init_plot();
98 
99  try {
101 
102  } catch (const std::exception& ex) {
103  init_plot();
104 
105  QString message = QString("Wrong parameters\n\n").append(QString::fromStdString(ex.what()));
107  }
108 
109  m_plot->replot();
110 }
111 
112 //! Generates label with current mouse position.
113 
114 void DistributionWidget::onMouseMove(QMouseEvent* event)
115 {
116  QPoint point = event->pos();
117  double xPos = m_plot->xAxis->pixelToCoord(point.x());
118  double yPos = m_plot->yAxis->pixelToCoord(point.y());
119 
120  if (m_plot->xAxis->range().contains(xPos) && m_plot->yAxis->range().contains(yPos)) {
121  QString text = QString("[x:%1, y:%2]").arg(xPos).arg(yPos);
122  m_label->setText(text);
123  }
124 }
125 
126 void DistributionWidget::onMousePress(QMouseEvent* event)
127 {
128  if (event->button() == Qt::RightButton) {
129  QPoint point = event->globalPos();
130  QMenu menu;
131  menu.addAction(m_resetAction);
132  menu.exec(point);
133  }
134 }
135 
136 //! Reset zoom range to initial state.
137 
139 {
140  m_plot->xAxis->setRange(m_xRange);
141  m_plot->yAxis->setRange(m_yRange);
142  m_plot->replot();
143 }
144 
145 //! Clears all plottables, resets axes to initial state.
146 
148 {
149  m_warningSign->clear();
150 
151  m_plot->clearGraphs();
152  m_plot->clearItems();
153  m_plot->clearPlottables();
154  m_plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes
155  | QCP::iSelectLegend | QCP::iSelectPlottables);
156  m_plot->yAxis->setLabel("probability");
157  m_plot->xAxis2->setVisible(true);
158  m_plot->yAxis2->setVisible(true);
159  m_plot->xAxis2->setTickLabels(false);
160  m_plot->yAxis2->setTickLabels(false);
161  m_plot->xAxis2->setTicks(false);
162  m_plot->yAxis2->setTicks(false);
163 
164  setPlotRange(default_xrange, default_yrange);
165 }
166 
168 {
169  if (m_item->modelType() == "DistributionNone")
171 
172  else
174 }
175 
176 //! Plots a single bar corresponding to the value in DistributionNoteItem.
177 
179 {
180  ASSERT(m_item->displayName() == "DistributionNone");
181 
182  double value = m_item->getItemValue(DistributionNoneItem::P_MEAN).toDouble();
183 
184  QVector<double> xPos = QVector<double>() << value;
185  QVector<double> yPos = QVector<double>() << 1.0;
186  plotBars(xPos, yPos);
187 
188  plotVerticalLine(value, default_yrange.first, value, default_yrange.second);
189 }
190 
192 {
193  ASSERT(m_item->displayName() != "DistributionNone");
194 
195  int numberOfSamples = m_item->getItemValue(DistributionItem::P_NUMBER_OF_SAMPLES).toInt();
196  double sigmafactor(0.0);
198  sigmafactor = m_item->getItemValue(DistributionItem::P_SIGMA_FACTOR).toDouble();
199 
200  RealLimits limits;
203  limits = limitsItem.createRealLimits();
204  }
205  plotLimits(limits);
206 
207  auto dist = m_item->createDistribution();
208 
209  // Calculating bars
210  std::vector<double> xp = dist->equidistantPoints(numberOfSamples, sigmafactor, limits);
211  std::vector<double> yp(xp.size());
212  std::transform(xp.begin(), xp.end(), yp.begin(),
213  [&](double value) { return dist->probabilityDensity(value); });
214  double sumOfWeights = std::accumulate(yp.begin(), yp.end(), 0.0);
215  ASSERT(sumOfWeights != 0.0);
216 
217 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
218  QVector<double> xBar(xp.begin(), xp.end());
219 #else
220  QVector<double> xBar = QVector<double>::fromStdVector(xp);
221 #endif
222 
223  QVector<double> yBar(xBar.size());
224  std::transform(yp.begin(), yp.end(), yBar.begin(),
225  [&](double value) { return value / sumOfWeights; });
226 
227  plotBars(xBar, yBar);
228 
229  // calculating function points (for interval, bigger than bars)
230  auto xRange = xRangeForValues(xBar);
231  const int number_of_points = 400;
232  std::vector<double> xf =
233  dist->equidistantPointsInRange(number_of_points, xRange.first, xRange.second);
234  std::vector<double> yf(xf.size());
235  std::transform(xf.begin(), xf.end(), yf.begin(),
236  [&](double value) { return dist->probabilityDensity(value); });
237 
238 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
239  QVector<double> xFunc(xf.begin(), xf.end());
240 #else
241  QVector<double> xFunc = QVector<double>::fromStdVector(xf);
242 #endif
243  QVector<double> yFunc(xFunc.size());
244  std::transform(yf.begin(), yf.end(), yFunc.begin(),
245  [&](double value) { return value / sumOfWeights; });
246 
247  plotFunction(xFunc, yFunc);
248 }
249 
250 void DistributionWidget::setPlotRange(const QPair<double, double>& xRange,
251  const QPair<double, double>& yRange)
252 {
253  m_xRange = QCPRange(xRange.first, xRange.second);
254  m_yRange = QCPRange(yRange.first, yRange.second);
255  m_plot->xAxis->setRange(m_xRange);
256  m_plot->yAxis->setRange(m_yRange);
257 }
258 
259 void DistributionWidget::plotBars(const QVector<double>& xbars, const QVector<double>& ybars)
260 {
261  ASSERT(xbars.size() > 0);
262 
263  auto xRange = xRangeForValues(xbars);
264  auto yRange = yRangeForValues(ybars);
265  setPlotRange(xRange, yRange);
266 
267  double barWidth(0.0);
268  if (xbars.size() == 1)
269  barWidth = optimalBarWidth(xRange.first, xRange.second, xbars.size());
270  else
271  barWidth = optimalBarWidth(xbars.front(), xbars.back(), xbars.size());
272 
273  QCPBars* bars = new QCPBars(m_plot->xAxis, m_plot->yAxis);
274 
275  bars->setWidth(barWidth);
276  bars->setData(xbars, ybars);
277 }
278 
279 void DistributionWidget::plotFunction(const QVector<double>& xFunc, const QVector<double>& yFunc)
280 {
281  auto xRange = xRangeForValues(xFunc);
282  auto yRange = yRangeForValues(yFunc);
283  setPlotRange(xRange, yRange);
284 
285  m_plot->addGraph();
286  m_plot->graph(0)->setData(xFunc, yFunc);
287 }
288 
289 void DistributionWidget::plotVerticalLine(double xMin, double yMin, double xMax, double yMax,
290  const QColor& color)
291 {
292  QCPItemLine* line = new QCPItemLine(m_plot);
293 
294  QPen pen(color, 1, Qt::DashLine);
295  line->setPen(pen);
296  line->setSelectable(true);
297 
298  line->start->setCoords(xMin, yMin);
299  line->end->setCoords(xMax, yMax);
300 }
301 
302 //! Plots red line denoting lower and upper limits, if any.
303 
305 {
306  if (limits.hasLowerLimit()) {
307  double value = limits.lowerLimit();
308  plotVerticalLine(value, default_yrange.first, value, default_yrange.second, Qt::red);
309  }
310 
311  if (limits.hasUpperLimit()) {
312  double value = limits.upperLimit();
313  plotVerticalLine(value, default_yrange.first, value, default_yrange.second, Qt::red);
314  }
315 }
316 
317 void DistributionWidget::setXAxisName(const QString& xAxisName)
318 {
319  m_plot->xAxis->setLabel(xAxisName);
320 }
321 
322 namespace {
323 //! Returns (xmin, xmax) of x-axis to display single value.
324 QPair<double, double> xRangeForValue(double value)
325 {
326  const double range_factor(0.1);
327 
328  double dr = (value == 0.0 ? 1.0 * range_factor : std::abs(value) * range_factor);
329  double xmin = value - dr;
330  double xmax = value + dr;
331 
332  return QPair<double, double>(xmin, xmax);
333 }
334 
335 //! Returns (xmin, xmax) of x-axis to display two values.
336 
337 QPair<double, double> xRangeForValues(double value1, double value2)
338 {
339  const double range_factor(0.1);
340  double dr = (value2 - value1) * range_factor;
341  ASSERT(dr > 0.0);
342 
343  return QPair<double, double>(value1 - dr, value2 + dr);
344 }
345 
346 QPair<double, double> xRangeForValues(const QVector<double>& xvec)
347 {
348  ASSERT(!xvec.isEmpty());
349  return xvec.size() == 1 ? xRangeForValue(xvec.front())
350  : xRangeForValues(xvec.front(), xvec.back());
351 }
352 
353 QPair<double, double> yRangeForValues(const QVector<double>& yvec)
354 {
355  const double range_factor(1.1);
356  double ymax = *std::max_element(yvec.begin(), yvec.end());
357  return QPair<double, double>(default_yrange.first, ymax * range_factor);
358 }
359 
360 //! Returns width of the bar, which will be optimally looking for x-axis range (xmin, xmax)
361 
362 double optimalBarWidth(double xmin, double xmax, int nbars)
363 {
364  double optimalWidth = (xmax - xmin) / 40.;
365  double width = (xmax - xmin) / nbars;
366 
367  return optimalWidth < width ? optimalWidth : width;
368 }
369 
370 } // namespace
#define ASSERT(condition)
Definition: Assert.h:31
Defines class DistributionItem and several subclasses.
Defines class DistributionWidget.
Defines classes representing one-dimensional distributions.
Defines RealLimitsItems's classes.
Defines class WarningSign.
virtual std::unique_ptr< IDistribution1D > createDistribution(double scale=1.0) const =0
static const QString P_LIMITS
static const QString P_NUMBER_OF_SAMPLES
static const QString P_SIGMA_FACTOR
void plotFunction(const QVector< double > &xFunc, const QVector< double > &ybars)
void resetView()
Reset zoom range to initial state.
void onMousePress(QMouseEvent *event)
void plot_single_value()
Plots a single bar corresponding to the value in DistributionNoteItem.
void onMouseMove(QMouseEvent *event)
Generates label with current mouse position.
DistributionWidget(QWidget *parent=0)
void setItem(DistributionItem *item)
void plotVerticalLine(double xMin, double yMin, double xMax, double yMax, const QColor &color=Qt::blue)
void setPlotRange(const QPair< double, double > &xRange, const QPair< double, double > &yRange)
WarningSign * m_warningSign
void plotLimits(const RealLimits &limits)
Plots red line denoting lower and upper limits, if any.
DistributionItem * m_item
void plotBars(const QVector< double > &xbars, const QVector< double > &ybars)
void init_plot()
Clears all plottables, resets axes to initial state.
void setXAxisName(const QString &xAxisName)
void unsubscribe(const void *caller)
Cancells all subscribtion of given caller.
Definition: ModelMapper.cpp:98
void setOnItemDestroy(std::function< void(SessionItem *)> f, const void *caller=0)
Definition: ModelMapper.cpp:87
void setOnPropertyChange(std::function< void(QString)> f, const void *caller=0)
Definition: ModelMapper.cpp:35
Limits for a real fit parameter.
Definition: RealLimits.h:24
bool hasUpperLimit() const
if has upper limit
Definition: RealLimits.cpp:57
double upperLimit() const
Returns upper limit.
Definition: RealLimits.cpp:62
double lowerLimit() const
Returns lower limit.
Definition: RealLimits.cpp:40
bool hasLowerLimit() const
if has lower limit
Definition: RealLimits.cpp:35
bool isTag(const QString &name) const
Returns true if tag is available.
T & groupItem(const QString &groupName) const
Definition: SessionItem.h:168
QString displayName() const
Get display name of item, append index if ambigue.
QVariant getItemValue(const QString &tag) const
Directly access value of item under given tag.
ModelMapper * mapper()
Returns the current model mapper of this item. Creates new one if necessary.
QString modelType() const
Get model type.
static const QString P_MEAN
The WarningSign controls appearance of WarningSignWidget on top of parent widget.
Definition: WarningSign.h:25
void clear()
Clears warning message;.
Definition: WarningSign.cpp:40
void setWarningMessage(const QString &warningMessage)
Shows warning sign on the screen.
Definition: WarningSign.cpp:58
QVector< double > fromStdVector(const std::vector< double > &data)
Definition: GUIHelpers.cpp:225