BornAgain  1.19.79
Simulate and fit neutron and x-ray scattering at grazing incidence
DistributionPlot.cpp
Go to the documentation of this file.
1 // ************************************************************************************************
2 //
3 // BornAgain: simulate and fit reflection and scattering
4 //
5 //! @file GUI/View/Instrument/DistributionPlot.cpp
6 //! @brief Implements class DistributionPlot
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 
16 #include "Base/Util/Assert.h"
20 #include "Param/Distrib/Distributions.h"
21 #include <QLabel>
22 #include <QVBoxLayout>
23 #include <algorithm>
24 #include <qcustomplot.h>
25 
26 namespace {
27 
28 const QPair<double, double> default_xrange(-0.1, 0.1);
29 const QPair<double, double> default_yrange(0.0, 1.1);
30 
31 QPair<double, double> xRangeForValue(double value);
32 QPair<double, double> xRangeForValues(double value1, double value2);
33 QPair<double, double> xRangeForValues(const QVector<double>& xvec);
34 QPair<double, double> yRangeForValues(const QVector<double>& yvec);
35 double optimalBarWidth(double xmin, double xmax, int nbars = 1);
36 
37 } // namespace
38 
40  : QWidget(parent)
41  , m_plot(new QCustomPlot)
42  , m_item(nullptr)
43  , m_label(new QLabel)
44  , m_resetAction(new QAction(this))
45  , m_cautionSign(new CautionSign(this))
46 {
47  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
48 
49  m_resetAction->setText("Reset View");
50  connect(m_resetAction, &QAction::triggered, this, &DistributionPlot::resetView);
51 
52  m_label->setAlignment(Qt::AlignVCenter | Qt::AlignLeft);
53  m_label->setStyleSheet("background-color:white;");
54  m_label->setMargin(3);
55 
56  auto* mainLayout = new QVBoxLayout;
57  mainLayout->setMargin(0);
58  mainLayout->setSpacing(0);
59  mainLayout->addWidget(m_plot, 1);
60  mainLayout->addWidget(m_label);
61  m_plot->setAttribute(Qt::WA_NoMousePropagation, false);
62  setLayout(mainLayout);
63 
64  setStyleSheet("background-color:white;");
65  connect(m_plot, &QCustomPlot::mousePress, this, &DistributionPlot::onMousePress);
66  connect(m_plot, &QCustomPlot::mouseMove, this, &DistributionPlot::onMouseMove);
67 }
68 
70 {
71  ASSERT(item);
72  if (m_item == item)
73  return;
74 
75  m_item = item;
76  plotItem();
77 }
78 
80 {
81  init_plot();
82 
83  try {
85  } catch (const std::exception& ex) {
86  init_plot();
87 
88  QString message = QString("Wrong parameters\n\n").append(QString::fromStdString(ex.what()));
90  }
91 
92  m_plot->replot();
93 }
94 
95 //! Generates label with current mouse position.
96 
97 void DistributionPlot::onMouseMove(QMouseEvent* event)
98 {
99  QPoint point = event->pos();
100  double xPos = m_plot->xAxis->pixelToCoord(point.x());
101  double yPos = m_plot->yAxis->pixelToCoord(point.y());
102 
103  if (m_plot->xAxis->range().contains(xPos) && m_plot->yAxis->range().contains(yPos)) {
104  QString text = QString("[x:%1, y:%2]").arg(xPos).arg(yPos);
105  m_label->setText(text);
106  }
107 }
108 
109 void DistributionPlot::onMousePress(QMouseEvent* event)
110 {
111  if (event->button() == Qt::RightButton) {
112  QPoint point = event->globalPos();
113  QMenu menu;
114  menu.addAction(m_resetAction);
115  menu.exec(point);
116  }
117 }
118 
119 //! Reset zoom range to initial state.
120 
122 {
123  m_plot->xAxis->setRange(m_xRange);
124  m_plot->yAxis->setRange(m_yRange);
125  m_plot->replot();
126 }
127 
128 //! Clears all plottables, resets axes to initial state.
129 
131 {
132  m_cautionSign->clear();
133 
134  m_plot->clearGraphs();
135  m_plot->clearItems();
136  m_plot->clearPlottables();
137  m_plot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes
138  | QCP::iSelectLegend | QCP::iSelectPlottables);
139  m_plot->yAxis->setLabel("probability");
140  m_plot->xAxis2->setVisible(true);
141  m_plot->yAxis2->setVisible(true);
142  m_plot->xAxis2->setTickLabels(false);
143  m_plot->yAxis2->setTickLabels(false);
144  m_plot->xAxis2->setTicks(false);
145  m_plot->yAxis2->setTicks(false);
146 
147  setPlotRange(default_xrange, default_yrange);
148 }
149 
151 {
154 
155  else
157 }
158 
159 //! Plots a single bar corresponding to the value in DistributionNoteItem.
160 
162 {
163  ASSERT(m_item->is<DistributionNoneItem>());
164 
165  double value = dynamic_cast<SymmetricResolutionItem*>(m_item)->mean();
166 
167  QVector<double> xPos = QVector<double>() << value;
168  QVector<double> yPos = QVector<double>() << 1.0;
169  plotBars(xPos, yPos);
170 
171  plotVerticalLine(value, default_yrange.first, value, default_yrange.second);
172 }
173 
175 {
176  ASSERT(!m_item->is<DistributionNoneItem>());
177 
178  size_t numberOfSamples = m_item->numberOfSamples();
179  double sigmafactor(0.0);
180  if (m_item->hasSigmaFactor())
181  sigmafactor = m_item->sigmaFactor();
182 
184 
185  auto dist = m_item->createDistribution();
186 
187  // Calculating bars
188  std::vector<double> xp =
189  dist->equidistantPoints(numberOfSamples, sigmafactor, m_item->limits());
190  std::vector<double> yp(xp.size());
191  std::transform(xp.begin(), xp.end(), yp.begin(),
192  [&](double value) { return dist->probabilityDensity(value); });
193  double sumOfWeights = std::accumulate(yp.begin(), yp.end(), 0.0);
194  ASSERT(sumOfWeights != 0.0);
195 
196 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
197  QVector<double> xBar(xp.begin(), xp.end());
198 #else
199  QVector<double> xBar = QVector<double>::fromStdVector(xp);
200 #endif
201 
202  QVector<double> yBar(xBar.size());
203  std::transform(yp.begin(), yp.end(), yBar.begin(),
204  [&](double value) { return value / sumOfWeights; });
205 
206  plotBars(xBar, yBar);
207 
208  // calculating function points (for interval, bigger than bars)
209  auto xRange = xRangeForValues(xBar);
210  const int number_of_points = 400;
211  std::vector<double> xf =
212  dist->equidistantPointsInRange(number_of_points, xRange.first, xRange.second);
213  std::vector<double> yf(xf.size());
214  std::transform(xf.begin(), xf.end(), yf.begin(),
215  [&](double value) { return dist->probabilityDensity(value); });
216 
217 #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
218  QVector<double> xFunc(xf.begin(), xf.end());
219 #else
220  QVector<double> xFunc = QVector<double>::fromStdVector(xf);
221 #endif
222  QVector<double> yFunc(xFunc.size());
223  std::transform(yf.begin(), yf.end(), yFunc.begin(),
224  [&](double value) { return value / sumOfWeights; });
225 
226  plotFunction(xFunc, yFunc);
227 }
228 
229 void DistributionPlot::setPlotRange(const QPair<double, double>& xRange,
230  const QPair<double, double>& yRange)
231 {
232  m_xRange = QCPRange(xRange.first, xRange.second);
233  m_yRange = QCPRange(yRange.first, yRange.second);
234  m_plot->xAxis->setRange(m_xRange);
235  m_plot->yAxis->setRange(m_yRange);
236 }
237 
238 void DistributionPlot::plotBars(const QVector<double>& xbars, const QVector<double>& ybars)
239 {
240  ASSERT(!xbars.empty());
241 
242  auto xRange = xRangeForValues(xbars);
243  auto yRange = yRangeForValues(ybars);
244  setPlotRange(xRange, yRange);
245 
246  double barWidth(0.0);
247  if (xbars.size() == 1)
248  barWidth = optimalBarWidth(xRange.first, xRange.second, xbars.size());
249  else
250  barWidth = optimalBarWidth(xbars.front(), xbars.back(), xbars.size());
251 
252  auto* bars = new QCPBars(m_plot->xAxis, m_plot->yAxis);
253 
254  bars->setWidth(barWidth);
255  bars->setData(xbars, ybars);
256 }
257 
258 void DistributionPlot::plotFunction(const QVector<double>& xFunc, const QVector<double>& yFunc)
259 {
260  auto xRange = xRangeForValues(xFunc);
261  auto yRange = yRangeForValues(yFunc);
262  setPlotRange(xRange, yRange);
263 
264  m_plot->addGraph();
265  m_plot->graph(0)->setData(xFunc, yFunc);
266 }
267 
268 void DistributionPlot::plotVerticalLine(double xMin, double yMin, double xMax, double yMax,
269  const QColor& color)
270 {
271  auto* line = new QCPItemLine(m_plot);
272 
273  QPen pen(color, 1, Qt::DashLine);
274  line->setPen(pen);
275  line->setSelectable(true);
276 
277  line->start->setCoords(xMin, yMin);
278  line->end->setCoords(xMax, yMax);
279 }
280 
281 //! Plots red line denoting lower and upper limits, if any.
282 
283 void DistributionPlot::plotLimits(const RealLimits& limits)
284 {
285  if (limits.hasLowerLimit()) {
286  double value = limits.lowerLimit();
287  plotVerticalLine(value, default_yrange.first, value, default_yrange.second, Qt::red);
288  }
289 
290  if (limits.hasUpperLimit()) {
291  double value = limits.upperLimit();
292  plotVerticalLine(value, default_yrange.first, value, default_yrange.second, Qt::red);
293  }
294 }
295 
296 void DistributionPlot::setXAxisName(const QString& xAxisName)
297 {
298  m_plot->xAxis->setLabel(xAxisName);
299 }
300 
302 {
303  m_label->setVisible(b);
304 }
305 
306 namespace {
307 
308 //! Returns (xmin, xmax) of x-axis to display single value.
309 QPair<double, double> xRangeForValue(double value)
310 {
311  const double range_factor(0.1);
312 
313  double dr = (value == 0.0 ? 1.0 * range_factor : std::abs(value) * range_factor);
314  double xmin = value - dr;
315  double xmax = value + dr;
316 
317  return QPair<double, double>(xmin, xmax);
318 }
319 
320 //! Returns (xmin, xmax) of x-axis to display two values.
321 
322 QPair<double, double> xRangeForValues(double value1, double value2)
323 {
324  const double range_factor(0.1);
325  double dr = (value2 - value1) * range_factor;
326  ASSERT(dr > 0.0);
327 
328  return QPair<double, double>(value1 - dr, value2 + dr);
329 }
330 
331 QPair<double, double> xRangeForValues(const QVector<double>& xvec)
332 {
333  ASSERT(!xvec.isEmpty());
334  return xvec.size() == 1 ? xRangeForValue(xvec.front())
335  : xRangeForValues(xvec.front(), xvec.back());
336 }
337 
338 QPair<double, double> yRangeForValues(const QVector<double>& yvec)
339 {
340  const double range_factor(1.1);
341  double ymax = *std::max_element(yvec.begin(), yvec.end());
342  return QPair<double, double>(default_yrange.first, ymax * range_factor);
343 }
344 
345 //! Returns width of the bar, which will be optimally looking for x-axis range (xmin, xmax)
346 
347 double optimalBarWidth(double xmin, double xmax, int nbars)
348 {
349  double optimalWidth = (xmax - xmin) / 40.;
350  double width = (xmax - xmin) / nbars;
351 
352  return optimalWidth < width ? optimalWidth : width;
353 }
354 
355 } // namespace
Defines class CautionSign.
Defines class DistributionItem and several subclasses.
Defines class DistributionPlot.
Defines class DoubleDescriptor.
The CautionSign controls appearance of CautionSignWidget on top of parent widget.
Definition: CautionSign.h:25
void clear()
Clears caution message;.
Definition: CautionSign.cpp:42
void setCautionMessage(const QString &cautionMessage)
Shows caution sign on the screen. If clear of previous caution sign had happened just few msec ago,...
Definition: CautionSign.cpp:60
virtual std::unique_ptr< IDistribution1D > createDistribution(double scale=1.0) const =0
RealLimits limits() const
bool hasSigmaFactor() const
void plotFunction(const QVector< double > &xFunc, const QVector< double > &yFunc)
void setShowMouseCoords(bool b)
void plotLimits(const RealLimits &limits)
Plots red line denoting lower and upper limits, if any.
void plotVerticalLine(double xMin, double yMin, double xMax, double yMax, const QColor &color=Qt::blue)
CautionSign * m_cautionSign
void plot_single_value()
Plots a single bar corresponding to the value in DistributionNoteItem.
DistributionPlot(QWidget *parent=nullptr)
QAction * m_resetAction
void onMouseMove(QMouseEvent *event)
Generates label with current mouse position.
void init_plot()
Clears all plottables, resets axes to initial state.
void setXAxisName(const QString &xAxisName)
void plotBars(const QVector< double > &xbars, const QVector< double > &ybars)
DistributionItem * m_item
void setItem(DistributionItem *item)
void setPlotRange(const QPair< double, double > &xRange, const QPair< double, double > &yRange)
void onMousePress(QMouseEvent *event)
void resetView()
Reset zoom range to initial state.
QCustomPlot * m_plot