Engauge Digitizer  2
 All Classes Functions Variables Typedefs Enumerations Friends Pages
FittingWindow.cpp
1 /******************************************************************************************************
2  * (C) 2016 markummitchell@github.com. This file is part of Engauge Digitizer, which is released *
3  * under GNU General Public License version 2 (GPLv2) or (at your option) any later version. See file *
4  * LICENSE or go to gnu.org/licenses for details. Distribution requires prior written permission. *
5  ******************************************************************************************************/
6 
7 #include "CmdMediator.h"
8 #include "Curve.h"
9 #include "CurveConnectAs.h"
10 #include "CurveStyle.h"
11 #include "EngaugeAssert.h"
12 #include "FittingCurveCoefficients.h"
13 #include "FittingModel.h"
14 #include "FittingStatistics.h"
15 #include "FittingWindow.h"
16 #include "GeometryModel.h"
17 #include "Logger.h"
18 #include "MainWindow.h"
19 #include "MainWindowModel.h"
20 #include <QApplication>
21 #include <QClipboard>
22 #include <QComboBox>
23 #include <QGridLayout>
24 #include <QItemSelectionModel>
25 #include <QLabel>
26 #include <QLineEdit>
27 #include <qmath.h>
28 #include "Transformation.h"
29 #include "WindowTable.h"
30 
31 const int COLUMN_COEFFICIENTS = 0;
32 const int COLUMN_POLYNOMIAL_TERMS = 1;
33 
35  WindowAbstractBase (mainWindow),
36  m_isLogXTheta (false),
37  m_isLogYRadius (false),
38  m_significantDigits (mainWindow->modelMainWindow().significantDigits ())
39 {
40  setVisible (false);
41  setAllowedAreas (Qt::AllDockWidgetAreas);
42  setWindowTitle (tr ("Curve Fitting Window")); // Appears in title bar when undocked
43  setStatusTip (tr ("Curve Fitting Window"));
44  setWhatsThis (tr ("Curve Fitting Window\n\n"
45  "This window applies a curve fit to the currently selected curve.\n\n"
46  "If drag-and-drop is disabled, a rectangular set of cells may be selected by clicking and dragging. Otherwise, if "
47  "drag-and-drop is enabled, a rectangular set of cells may be selected using Click then Shift+Click, since click and drag "
48  "starts the dragging operation. Drag-and-drop mode is set in the Main Window settings"));
49 
50  m_coefficients.resize (MAX_POLYNOMIAL_ORDER + 1);
51 
52  createWidgets (mainWindow);
53  initializeOrder ();
54  clear ();
55 }
56 
57 FittingWindow::~FittingWindow()
58 {
59 }
60 
61 void FittingWindow::calculateCurveFitAndStatistics ()
62 {
63  FittingStatistics fittingStatistics;
64 
65  double mse = 0, rms = 0, rSquared = 0;
66  fittingStatistics.calculateCurveFitAndStatistics (maxOrder (),
67  m_pointsConvenient,
68  m_coefficients,
69  mse,
70  rms,
71  rSquared,
72  m_significantDigits);
73 
74  m_lblMeanSquareError->setText (QString::number (mse));
75  m_lblRootMeanSquare->setText (QString::number (rms));
76  m_lblRSquared->setText (QString::number (rSquared));
77 
78  // Send coefficients to connected classes. Also send the first and last x values
79  if (m_pointsConvenient.size () > 0) {
80  int last = m_pointsConvenient.size () - 1;
81  emit signalCurveFit (m_coefficients,
82  m_pointsConvenient [0].x(),
83  m_pointsConvenient [last].x (),
84  m_isLogXTheta,
85  m_isLogYRadius);
86  } else {
87  emit signalCurveFit (m_coefficients,
88  0,
89  0,
90  false,
91  false);
92  }
93 
94  // Copy into displayed control
95  for (int row = 0, order = m_model->rowCount () - 1; row < m_model->rowCount (); row++, order--) {
96 
97  QStandardItem *item = new QStandardItem (QString::number (m_coefficients [order]));
98  m_model->setItem (row, COLUMN_COEFFICIENTS, item);
99  }
100 }
101 
103 {
104  m_labelY->setText ("");
105  m_model->setRowCount (0);
106  m_lblMeanSquareError->setText ("");
107  m_lblRootMeanSquare->setText ("");
108  m_lblRSquared->setText ("");
109 }
110 
111 void FittingWindow::closeEvent(QCloseEvent * /* event */)
112 {
113  LOG4CPP_INFO_S ((*mainCat)) << "FittingWindow::closeEvent";
114 
116 }
117 
118 void FittingWindow::createWidgets (MainWindow *mainWindow)
119 {
120  QWidget *widget = new QWidget;
121  setWidget (widget);
122 
123  QGridLayout *layout = new QGridLayout;
124  widget->setLayout (layout);
125  int row = 0;
126 
127  // Order row
128  QLabel *labelOrder = new QLabel (tr ("Order:"));
129  layout->addWidget (labelOrder, row, 0, 1, 1);
130 
131  m_cmbOrder = new QComboBox;
132  for (int order = 0; order <= MAX_POLYNOMIAL_ORDER; order++) {
133  m_cmbOrder->addItem (QString::number (order), QVariant (order));
134  }
135  connect (m_cmbOrder, SIGNAL (currentIndexChanged (int)), this, SLOT (slotCmbOrder (int)));
136  layout->addWidget (m_cmbOrder, row++, 1, 1, 1);
137 
138  // Y= row
139  m_labelY = new QLabel; // The text will be set in resizeTable
140  layout->addWidget (m_labelY, row++, 0, 1, 1);
141 
142  // Table row
143  m_model = new FittingModel;
144  m_model->setColumnCount (2);
145 
146  m_view = new WindowTable (*m_model);
147  connect (m_view, SIGNAL (signalTableStatusChange ()),
148  mainWindow, SLOT (slotTableStatusChange ()));
149 
150  layout->addWidget (m_view, row++, 0, 1, 2);
151 
152  // Statistics rows
153  QLabel *lblMeanSquareError = new QLabel (tr ("Mean square error:"));
154  layout->addWidget (lblMeanSquareError, row, 0, 1, 1);
155 
156  m_lblMeanSquareError = new QLineEdit;
157  m_lblMeanSquareError->setReadOnly (true);
158  m_lblMeanSquareError->setWhatsThis (tr ("Calculated mean square error statistic"));
159  layout->addWidget (m_lblMeanSquareError, row++, 1, 1, 1);
160 
161  QLabel *lblRootMeanSquare = new QLabel (tr ("Root mean square:"));
162  layout->addWidget (lblRootMeanSquare, row, 0, 1, 1);
163 
164  m_lblRootMeanSquare = new QLineEdit;
165  m_lblRootMeanSquare->setReadOnly (true);
166  m_lblRootMeanSquare->setWhatsThis (tr ("Calculated root mean square statistic. This is calculated as the square root of the mean square error"));
167  layout->addWidget (m_lblRootMeanSquare, row++, 1, 1, 1);
168 
169  QLabel *lblRSquared = new QLabel (tr ("R squared:"));
170  layout->addWidget (lblRSquared, row, 0, 1, 1);
171 
172  m_lblRSquared = new QLineEdit;
173  m_lblRSquared->setReadOnly (true);
174  m_lblRSquared->setWhatsThis (tr ("Calculated R squared statistic"));
175  layout->addWidget (m_lblRSquared, row++, 1, 1, 1);
176 }
177 
179 {
180  LOG4CPP_INFO_S ((*mainCat)) << "FittingWindow::doCopy";
181 
182  QString text = m_model->selectionAsText (m_modelExport.delimiter());
183 
184  if (!text.isEmpty ()) {
185 
186  // Save to clipboard
187  QApplication::clipboard ()->setText (text);
188 
189  }
190 }
191 
192 void FittingWindow::initializeOrder ()
193 {
194  const int SECOND_ORDER = 2;
195 
196  int index = m_cmbOrder->findData (QVariant (SECOND_ORDER));
197  m_cmbOrder->setCurrentIndex (index);
198 }
199 
200 int FittingWindow::maxOrder () const
201 {
202  return m_cmbOrder->currentData().toInt();
203 }
204 
205 void FittingWindow::refreshTable ()
206 {
207  int order = m_cmbOrder->currentData().toInt();
208 
209  // Table size may have to change
210  resizeTable (order);
211 
212  calculateCurveFitAndStatistics ();
213 }
214 
215 void FittingWindow::resizeTable (int order)
216 {
217  LOG4CPP_INFO_S ((*mainCat)) << "FittingWindow::resizeTable";
218 
219  m_model->setRowCount (order + 1);
220 
221  // Populate the Y= row. Base for log must be consistent with base used in update()
222  QString yTerm = QString ("%1%2%3")
223  .arg (m_curveSelected)
224  .arg (m_curveSelected.isEmpty () ?
225  "" :
226  ": ")
227  .arg (m_isLogYRadius ?
228  tr ("log10(Y)=") :
229  tr ("Y="));
230  m_labelY->setText (yTerm);
231 
232  // Populate polynomial terms. Base for log must be consistent with base used in update()
233  QString xString = (m_isLogXTheta ?
234  tr ("log10(X)") :
235  tr ("X"));
236  for (int row = 0, term = order; term >= 0; row++, term--) {
237 
238  // Entries are x^order, ..., x^2, x, 1
239  QString termString = QString ("%1%2%3%4")
240  .arg ((term > 0) ? xString : "")
241  .arg ((term > 1) ? "^" : "")
242  .arg ((term > 1) ? QString::number (term) : "")
243  .arg ((term > 0) ? "+" : "");
244 
245  QStandardItem *item = new QStandardItem (termString);
246  m_model->setItem (row, COLUMN_POLYNOMIAL_TERMS, item);
247  }
248 }
249 
250 void FittingWindow::slotCmbOrder(int /* index */)
251 {
252  refreshTable ();
253 }
254 
255 void FittingWindow::update (const CmdMediator &cmdMediator,
256  const MainWindowModel &modelMainWindow,
257  const QString &curveSelected,
258  const Transformation &transformation)
259 {
260  LOG4CPP_INFO_S ((*mainCat)) << "FittingWindow::update";
261 
262  // Save inputs
263  m_curveSelected = curveSelected;
264  m_modelExport = cmdMediator.document().modelExport();
265  m_model->setDelimiter (m_modelExport.delimiter());
266  m_isLogXTheta = (cmdMediator.document().modelCoords().coordScaleXTheta() == COORD_SCALE_LOG);
267  m_isLogYRadius = (cmdMediator.document().modelCoords().coordScaleYRadius() == COORD_SCALE_LOG);
268  m_view->setDragEnabled (modelMainWindow.dragDropExport());
269  m_significantDigits = modelMainWindow.significantDigits();
270 
271  m_pointsConvenient.clear ();
272 
273  if (transformation.transformIsDefined()) {
274 
275  // Gather and calculate geometry data
276  const Curve *curve = cmdMediator.document().curveForCurveName (curveSelected);
277 
278  ENGAUGE_CHECK_PTR (curve);
279 
280  if (curve->numPoints() > 0) {
281 
282  // Copy points to convenient list
283  const Points points = curve->points();
284  Points::const_iterator itr;
285  for (itr = points.begin (); itr != points.end (); itr++) {
286 
287  const Point &point = *itr;
288  QPointF posScreen = point.posScreen ();
289  QPointF posGraph;
290  transformation.transformScreenToRawGraph (posScreen,
291  posGraph);
292 
293  // Adjust for log coordinates
294  if (m_isLogXTheta) {
295  double x = qLn (posGraph.x()) / qLn (10.0); // Use base 10 consistent with text in resizeTable
296  posGraph.setX (x);
297  }
298  if (m_isLogYRadius) {
299  double y = qLn (posGraph.y()) / qLn (10.0); // Use base 10 consistent with text in resizeTable
300  posGraph.setY (y);
301  }
302 
303  m_pointsConvenient.append (posGraph);
304  }
305  }
306  }
307 
308  refreshTable ();
309 }
310 
311 QTableView *FittingWindow::view () const
312 {
313  return dynamic_cast<QTableView*> (m_view);
314 }
void transformScreenToRawGraph(const QPointF &coordScreen, QPointF &coordGraph) const
Transform from cartesian pixel screen coordinates to cartesian/polar graph coordinates.
const Points points() const
Return a shallow copy of the Points.
Definition: Curve.cpp:451
Dockable widget abstract base class.
void calculateCurveFitAndStatistics(unsigned int order, const FittingPointsConvenient &pointsConvenient, FittingCurveCoefficients &coefficients, double &mse, double &rms, double &rSquared, int significantDigits)
Compute the curve fit and the statistics for that curve fit.
CoordScale coordScaleYRadius() const
Get method for linear/log scale on y/radius.
int numPoints() const
Number of points.
Definition: Curve.cpp:432
DocumentModelCoords modelCoords() const
Get method for DocumentModelCoords.
Definition: Document.cpp:688
virtual void update(const CmdMediator &cmdMediator, const MainWindowModel &modelMainWindow, const QString &curveSelected, const Transformation &transformation)
Populate the table with the specified Curve.
Class that represents one digitized point. The screen-to-graph coordinate transformation is always ex...
Definition: Point.h:25
QPointF posScreen() const
Accessor for screen position.
Definition: Point.cpp:404
Document & document()
Provide the Document to commands, primarily for undo/redo processing.
Definition: CmdMediator.cpp:72
bool dragDropExport() const
Get method for drag and drop export.
FittingWindow(MainWindow *mainWindow)
Single constructor. Parent is needed or else this widget cannot be redocked after being undocked...
QString selectionAsText(ExportDelimiter delimiter) const
Convert the selection into exportable text which is good for text editors.
Model for FittingWindow.
Definition: FittingModel.h:14
int significantDigits() const
Get method for significant digits.
Affine transformation between screen and graph coordinates, based on digitized axis points...
virtual QTableView * view() const
QTableView-based class used by child class.
CoordScale coordScaleXTheta() const
Get method for linear/log scale on x/theta.
void setDelimiter(ExportDelimiter delimiter)
Save output delimiter.
Model for DlgSettingsMainWindow.
void signalFittingWindowClosed()
Signal that this QDockWidget was just closed.
ExportDelimiter delimiter() const
Get method for delimiter.
void signalCurveFit(FittingCurveCoefficients, double, double, bool, bool)
Signal containing coefficients from curve fit.
Table view class with support for both drag-and-drop and copy-and-paste.
Definition: WindowTable.h:17
Container for one set of digitized Points.
Definition: Curve.h:33
bool transformIsDefined() const
Transform is defined when at least three axis points have been digitized.
Command queue stack.
Definition: CmdMediator.h:23
const Curve * curveForCurveName(const QString &curveName) const
See CurvesGraphs::curveForCurveNames, although this also works for AXIS_CURVE_NAME.
Definition: Document.cpp:331
This class does the math to compute statistics for FittingWindow.
virtual void closeEvent(QCloseEvent *event)
Catch close event so corresponding menu item in MainWindow can be updated accordingly.
Main window consisting of menu, graphics scene, status bar and optional toolbars as a Single Document...
Definition: MainWindow.h:89
DocumentModelExportFormat modelExport() const
Get method for DocumentModelExportFormat.
Definition: Document.cpp:709
virtual void clear()
Clear stale information.
virtual void doCopy()
Copy the current selection to the clipboard.