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