labplot  2.8.2
About: LabPlot is an application for plotting and analysis of 2D and 3D functions and data. It is a complete rewrite of LabPlot1 and lacks in the first release a lot of features available in the predecessor. On the other hand, the GUI and the usability is more superior.
  Fossies Dox: labplot-2.8.2.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

CartesianPlot.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  File : CartesianPlot.cpp
3  Project : LabPlot
4  Description : Cartesian plot
5  --------------------------------------------------------------------
6  Copyright : (C) 2011-2020 by Alexander Semke (alexander.semke@web.de)
7  Copyright : (C) 2016-2018 by Stefan Gerlach (stefan.gerlach@uni.kn)
8  Copyright : (C) 2017-2018 by Garvit Khatri (garvitdelhi@gmail.com)
9 
10  ***************************************************************************/
11 
12 /***************************************************************************
13  * *
14  * This program is free software; you can redistribute it and/or modify *
15  * it under the terms of the GNU General Public License as published by *
16  * the Free Software Foundation; either version 2 of the License, or *
17  * (at your option) any later version. *
18  * *
19  * This program is distributed in the hope that it will be useful, *
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
22  * GNU General Public License for more details. *
23  * *
24  * You should have received a copy of the GNU General Public License *
25  * along with this program; if not, write to the Free Software *
26  * Foundation, Inc., 51 Franklin Street, Fifth Floor, *
27  * Boston, MA 02110-1301 USA *
28  * *
29  ***************************************************************************/
30 
31 #include "CartesianPlot.h"
32 #include "CartesianPlotPrivate.h"
33 #include "XYCurve.h"
34 #include "Histogram.h"
35 #include "XYEquationCurve.h"
36 #include "XYDataReductionCurve.h"
37 #include "XYDifferentiationCurve.h"
38 #include "XYIntegrationCurve.h"
39 #include "XYInterpolationCurve.h"
40 #include "XYSmoothCurve.h"
41 #include "XYFitCurve.h"
42 #include "XYFourierFilterCurve.h"
44 #include "XYConvolutionCurve.h"
45 #include "XYCorrelationCurve.h"
46 #include "backend/core/Project.h"
60 #include "backend/lib/macros.h"
61 #include "backend/lib/trace.h"
62 #include "kdefrontend/spreadsheet/PlotDataDialog.h" //for PlotDataDialog::AnalysisAction. TODO: find a better place for this enum.
65 
66 #include <QDir>
67 #include <QDropEvent>
68 #include <QIcon>
69 #include <QMenu>
70 #include <QMimeData>
71 #include <QPainter>
72 #include <QWidgetAction>
73 
74 #include <array>
75 #include <cmath>
76 
77 #include <KConfig>
78 #include <KConfigGroup>
79 #include <KLocalizedString>
80 
81 /**
82  * \class CartesianPlot
83  * \brief A xy-plot.
84  *
85  *
86  */
87 CartesianPlot::CartesianPlot(const QString &name)
89 
90  init();
91 }
92 
94  : AbstractPlot(name, dd, AspectType::CartesianPlot) {
95 
96  init();
97 }
98 
100  if (m_menusInitialized) {
101  delete addNewMenu;
102  delete zoomMenu;
103  delete themeMenu;
104  }
105 
106  delete m_coordinateSystem;
107 
108  //don't need to delete objects added with addChild()
109 
110  //no need to delete the d-pointer here - it inherits from QGraphicsItem
111  //and is deleted during the cleanup in QGraphicsScene
112 }
113 
114 /*!
115  initializes all member variables of \c CartesianPlot
116 */
118  Q_D(CartesianPlot);
119 
120  d->cSystem = new CartesianCoordinateSystem(this);
121  m_coordinateSystem = d->cSystem;
122 
123  d->rangeType = RangeType::Free;
124  d->xRangeFormat = RangeFormat::Numeric;
125  d->yRangeFormat = RangeFormat::Numeric;
126  d->xRangeDateTimeFormat = "yyyy-MM-dd hh:mm:ss";
127  d->yRangeDateTimeFormat = "yyyy-MM-dd hh:mm:ss";
128  d->rangeFirstValues = 1000;
129  d->rangeLastValues = 1000;
130  d->autoScaleX = true;
131  d->autoScaleY = true;
132  d->xScale = Scale::Linear;
133  d->yScale = Scale::Linear;
134  d->xRangeBreakingEnabled = false;
135  d->yRangeBreakingEnabled = false;
136 
137  //the following factor determines the size of the offset between the min/max points of the curves
138  //and the coordinate system ranges, when doing auto scaling
139  //Factor 0 corresponds to the exact match - min/max values of the curves correspond to the start/end values of the ranges.
140  //TODO: make this factor optional.
141  //Provide in the UI the possibility to choose between "exact" or 0% offset, 2%, 5% and 10% for the auto fit option
142  d->autoScaleOffsetFactor = 0.0f;
143 
144  m_plotArea = new PlotArea(name() + " plot area", this);
146 
147  //Plot title
148  m_title = new TextLabel(this->name() + QLatin1String("- ") + i18n("Title"), TextLabel::Type::PlotTitle);
149  addChild(m_title);
150  m_title->setHidden(true);
152 
153  //offset between the plot area and the area defining the coordinate system, in scene units.
158  d->symmetricPadding = true;
159 
162 
163  graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true);
164  graphicsItem()->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
165  graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true);
166  graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
167  graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, true);
168 
169  //theme is not set at this point, initialize the color palette with default colors
170  this->setColorPalette(KConfig());
171 }
172 
173 /*!
174  initializes all children of \c CartesianPlot and
175  setups a default plot of type \c type with a plot title.
176 */
178  Q_D(CartesianPlot);
179 
180  d->type = type;
181 
182  switch (type) {
183  case Type::FourAxes: {
184  d->xMin = 0.0;
185  d->xMax = 1.0;
186  d->yMin = 0.0;
187  d->yMax = 1.0;
188 
189  //Axes
190  Axis* axis = new Axis(QLatin1String("x"), Axis::Orientation::Horizontal);
191  axis->setDefault(true);
192  axis->setSuppressRetransform(true);
193  addChild(axis);
194  axis->setPosition(Axis::Position::Bottom);
195  axis->setStart(0);
196  axis->setEnd(1);
197  axis->setMajorTicksDirection(Axis::ticksIn);
198  axis->setMajorTicksNumber(6);
199  axis->setMinorTicksDirection(Axis::ticksIn);
200  axis->setMinorTicksNumber(1);
201  axis->setSuppressRetransform(false);
202 
203  axis = new Axis(QLatin1String("x2"), Axis::Orientation::Horizontal);
204  axis->setDefault(true);
205  axis->setSuppressRetransform(true);
206  addChild(axis);
207  axis->setPosition(Axis::Position::Top);
208  axis->setStart(0);
209  axis->setEnd(1);
210  axis->setMajorTicksDirection(Axis::ticksIn);
211  axis->setMajorTicksNumber(6);
212  axis->setMinorTicksDirection(Axis::ticksIn);
213  axis->setMinorTicksNumber(1);
214  QPen pen = axis->minorGridPen();
215  pen.setStyle(Qt::NoPen);
216  axis->setMajorGridPen(pen);
217  pen = axis->minorGridPen();
218  pen.setStyle(Qt::NoPen);
219  axis->setMinorGridPen(pen);
220  axis->setLabelsPosition(Axis::LabelsPosition::NoLabels);
221  axis->title()->setText(QString());
222  axis->setSuppressRetransform(false);
223 
224  axis = new Axis(QLatin1String("y"), Axis::Orientation::Vertical);
225  axis->setDefault(true);
226  axis->setSuppressRetransform(true);
227  addChild(axis);
228  axis->setPosition(Axis::Position::Left);
229  axis->setStart(0);
230  axis->setEnd(1);
231  axis->setMajorTicksDirection(Axis::ticksIn);
232  axis->setMajorTicksNumber(6);
233  axis->setMinorTicksDirection(Axis::ticksIn);
234  axis->setMinorTicksNumber(1);
235  axis->setSuppressRetransform(false);
236 
237  axis = new Axis(QLatin1String("y2"), Axis::Orientation::Vertical);
238  axis->setDefault(true);
239  axis->setSuppressRetransform(true);
240  addChild(axis);
241  axis->setPosition(Axis::Position::Right);
242  axis->setStart(0);
243  axis->setEnd(1);
244  axis->setOffset(1);
245  axis->setMajorTicksDirection(Axis::ticksIn);
246  axis->setMajorTicksNumber(6);
247  axis->setMinorTicksDirection(Axis::ticksIn);
248  axis->setMinorTicksNumber(1);
249  pen = axis->minorGridPen();
250  pen.setStyle(Qt::NoPen);
251  axis->setMajorGridPen(pen);
252  pen = axis->minorGridPen();
253  pen.setStyle(Qt::NoPen);
254  axis->setMinorGridPen(pen);
255  axis->setLabelsPosition(Axis::LabelsPosition::NoLabels);
256  axis->title()->setText(QString());
257  axis->setSuppressRetransform(false);
258 
259  break;
260  }
261  case Type::TwoAxes: {
262  d->xMin = 0.0;
263  d->xMax = 1.0;
264  d->yMin = 0.0;
265  d->yMax = 1.0;
266 
267  Axis* axis = new Axis(QLatin1String("x"), Axis::Orientation::Horizontal);
268  axis->setDefault(true);
269  axis->setSuppressRetransform(true);
270  addChild(axis);
271  axis->setPosition(Axis::Position::Bottom);
272  axis->setStart(0);
273  axis->setEnd(1);
274  axis->setMajorTicksDirection(Axis::ticksBoth);
275  axis->setMajorTicksNumber(6);
276  axis->setMinorTicksDirection(Axis::ticksBoth);
277  axis->setMinorTicksNumber(1);
278  axis->setArrowType(Axis::ArrowType::FilledSmall);
279  axis->setSuppressRetransform(false);
280 
281  axis = new Axis(QLatin1String("y"), Axis::Orientation::Vertical);
282  axis->setDefault(true);
283  axis->setSuppressRetransform(true);
284  addChild(axis);
285  axis->setPosition(Axis::Position::Left);
286  axis->setStart(0);
287  axis->setEnd(1);
288  axis->setMajorTicksDirection(Axis::ticksBoth);
289  axis->setMajorTicksNumber(6);
290  axis->setMinorTicksDirection(Axis::ticksBoth);
291  axis->setMinorTicksNumber(1);
292  axis->setArrowType(Axis::ArrowType::FilledSmall);
293  axis->setSuppressRetransform(false);
294 
295  break;
296  }
297  case Type::TwoAxesCentered: {
298  d->xMin = -0.5;
299  d->xMax = 0.5;
300  d->yMin = -0.5;
301  d->yMax = 0.5;
302 
305 
306  QPen pen = m_plotArea->borderPen();
307  pen.setStyle(Qt::NoPen);
308  m_plotArea->setBorderPen(pen);
309 
310  Axis* axis = new Axis(QLatin1String("x"), Axis::Orientation::Horizontal);
311  axis->setDefault(true);
312  axis->setSuppressRetransform(true);
313  addChild(axis);
314  axis->setPosition(Axis::Position::Centered);
315  axis->setStart(-0.5);
316  axis->setEnd(0.5);
317  axis->setMajorTicksDirection(Axis::ticksBoth);
318  axis->setMajorTicksNumber(6);
319  axis->setMinorTicksDirection(Axis::ticksBoth);
320  axis->setMinorTicksNumber(1);
321  axis->setArrowType(Axis::ArrowType::FilledSmall);
322  axis->title()->setText(QString());
323  axis->setSuppressRetransform(false);
324 
325  axis = new Axis(QLatin1String("y"), Axis::Orientation::Vertical);
326  axis->setDefault(true);
327  axis->setSuppressRetransform(true);
328  addChild(axis);
329  axis->setPosition(Axis::Position::Centered);
330  axis->setStart(-0.5);
331  axis->setEnd(0.5);
332  axis->setMajorTicksDirection(Axis::ticksBoth);
333  axis->setMajorTicksNumber(6);
334  axis->setMinorTicksDirection(Axis::ticksBoth);
335  axis->setMinorTicksNumber(1);
336  axis->setArrowType(Axis::ArrowType::FilledSmall);
337  axis->title()->setText(QString());
338  axis->setSuppressRetransform(false);
339 
340  break;
341  }
343  d->xMin = -0.5;
344  d->xMax = 0.5;
345  d->yMin = -0.5;
346  d->yMax = 0.5;
347 
350 
351  QPen pen = m_plotArea->borderPen();
352  pen.setStyle(Qt::NoPen);
353  m_plotArea->setBorderPen(pen);
354 
355  Axis* axis = new Axis(QLatin1String("x"), Axis::Orientation::Horizontal);
356  axis->setDefault(true);
357  axis->setSuppressRetransform(true);
358  addChild(axis);
359  axis->setPosition(Axis::Position::Custom);
360  axis->setOffset(0);
361  axis->setStart(-0.5);
362  axis->setEnd(0.5);
363  axis->setMajorTicksDirection(Axis::ticksBoth);
364  axis->setMajorTicksNumber(6);
365  axis->setMinorTicksDirection(Axis::ticksBoth);
366  axis->setMinorTicksNumber(1);
367  axis->setArrowType(Axis::ArrowType::FilledSmall);
368  axis->title()->setText(QString());
369  axis->setSuppressRetransform(false);
370 
371  axis = new Axis(QLatin1String("y"), Axis::Orientation::Vertical);
372  axis->setDefault(true);
373  axis->setSuppressRetransform(true);
374  addChild(axis);
375  axis->setPosition(Axis::Position::Custom);
376  axis->setOffset(0);
377  axis->setStart(-0.5);
378  axis->setEnd(0.5);
379  axis->setMajorTicksDirection(Axis::ticksBoth);
380  axis->setMajorTicksNumber(6);
381  axis->setMinorTicksDirection(Axis::ticksBoth);
382  axis->setMinorTicksNumber(1);
383  axis->setArrowType(Axis::ArrowType::FilledSmall);
384  axis->title()->setText(QString());
385  axis->setSuppressRetransform(false);
386 
387  break;
388  }
389  }
390 
391  d->xMinPrev = d->xMin;
392  d->xMaxPrev = d->xMax;
393  d->yMinPrev = d->yMin;
394  d->yMaxPrev = d->yMax;
395 
396  //Geometry, specify the plot rect in scene coordinates.
397  //TODO: Use default settings for left, top, width, height and for min/max for the coordinate system
402 
403  //all plot children are initialized -> set the geometry of the plot in scene coordinates.
404  d->rect = QRectF(x,y,w,h);
405  d->retransform();
406 }
407 
409  Q_D(const CartesianPlot);
410  return d->type;
411 }
412 
414  //"add new" actions
415  addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), this);
416  addHistogramAction = new QAction(QIcon::fromTheme("view-object-histogram-linear"), i18n("Histogram"), this);
417  addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve from a mathematical Equation"), this);
418 // no icons yet
419  addDataReductionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Data Reduction"), this);
420  addDifferentiationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiation"), this);
421  addIntegrationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integration"), this);
422  addInterpolationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolation"), this);
423  addSmoothCurveAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), this);
424  addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("Fit"), this);
425  addFourierFilterCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), this);
426  addFourierTransformCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-transform-curve"), i18n("Fourier Transform"), this);
427  addConvolutionCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"),i18n("(De-)Convolution"), this);
428  addCorrelationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"),i18n("Auto-/Cross-Correlation"), this);
429 
430  addLegendAction = new QAction(QIcon::fromTheme("text-field"), i18n("Legend"), this);
431  if (children<CartesianPlotLegend>().size()>0)
432  addLegendAction->setEnabled(false); //only one legend is allowed -> disable the action
433 
434  addHorizontalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("Horizontal Axis"), this);
435  addVerticalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("Vertical Axis"), this);
436  addTextLabelAction = new QAction(QIcon::fromTheme("draw-text"), i18n("Text Label"), this);
437  addImageAction = new QAction(QIcon::fromTheme("viewimage"), i18n("Image"), this);
438  addCustomPointAction = new QAction(QIcon::fromTheme("draw-cross"), i18n("Custom Point"), this);
439  addReferenceLineAction = new QAction(QIcon::fromTheme("draw-line"), i18n("Reference Line"), this);
440 
441  connect(addCurveAction, &QAction::triggered, this, &CartesianPlot::addCurve);
442  connect(addHistogramAction,&QAction::triggered, this, &CartesianPlot::addHistogram);
443  connect(addEquationCurveAction, &QAction::triggered, this, &CartesianPlot::addEquationCurve);
444  connect(addDataReductionCurveAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve);
445  connect(addDifferentiationCurveAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve);
446  connect(addIntegrationCurveAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve);
447  connect(addInterpolationCurveAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve);
448  connect(addSmoothCurveAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve);
449  connect(addFitCurveAction, &QAction::triggered, this, &CartesianPlot::addFitCurve);
450  connect(addFourierFilterCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve);
451  connect(addFourierTransformCurveAction, &QAction::triggered, this, &CartesianPlot::addFourierTransformCurve);
452  connect(addConvolutionCurveAction, &QAction::triggered, this, &CartesianPlot::addConvolutionCurve);
453  connect(addCorrelationCurveAction, &QAction::triggered, this, &CartesianPlot::addCorrelationCurve);
454 
455  connect(addLegendAction, &QAction::triggered, this, static_cast<void (CartesianPlot::*)()>(&CartesianPlot::addLegend));
456  connect(addHorizontalAxisAction, &QAction::triggered, this, &CartesianPlot::addHorizontalAxis);
457  connect(addVerticalAxisAction, &QAction::triggered, this, &CartesianPlot::addVerticalAxis);
458  connect(addTextLabelAction, &QAction::triggered, this, &CartesianPlot::addTextLabel);
459  connect(addImageAction, &QAction::triggered, this, &CartesianPlot::addImage);
460  connect(addCustomPointAction, &QAction::triggered, this, &CartesianPlot::addCustomPoint);
461  connect(addReferenceLineAction, &QAction::triggered, this, &CartesianPlot::addReferenceLine);
462 
463  //Analysis menu actions
464 // addDataOperationAction = new QAction(i18n("Data Operation"), this);
465  addDataReductionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Data Reduction"), this);
466  addDifferentiationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Differentiate"), this);
467  addIntegrationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Integrate"), this);
468  addInterpolationAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("Interpolate"), this);
469  addSmoothAction = new QAction(QIcon::fromTheme("labplot-xy-smoothing-curve"), i18n("Smooth"), this);
470  addConvolutionAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Convolute/Deconvolute"), this);
471  addCorrelationAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("Auto-/Cross-Correlation"), this);
472 
473  QAction* fitAction = new QAction(i18n("Linear"), this);
474  fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitLinear));
475  addFitAction.append(fitAction);
476 
477  fitAction = new QAction(i18n("Power"), this);
478  fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitPower));
479  addFitAction.append(fitAction);
480 
481  fitAction = new QAction(i18n("Exponential (degree 1)"), this);
482  fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitExp1));
483  addFitAction.append(fitAction);
484 
485  fitAction = new QAction(i18n("Exponential (degree 2)"), this);
486  fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitExp2));
487  addFitAction.append(fitAction);
488 
489  fitAction = new QAction(i18n("Inverse exponential"), this);
490  fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitInvExp));
491  addFitAction.append(fitAction);
492 
493  fitAction = new QAction(i18n("Gauss"), this);
494  fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitGauss));
495  addFitAction.append(fitAction);
496 
497  fitAction = new QAction(i18n("Cauchy-Lorentz"), this);
498  fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitCauchyLorentz));
499  addFitAction.append(fitAction);
500 
501  fitAction = new QAction(i18n("Arc Tangent"), this);
502  fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitTan));
503  addFitAction.append(fitAction);
504 
505  fitAction = new QAction(i18n("Hyperbolic Tangent"), this);
506  fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitTanh));
507  addFitAction.append(fitAction);
508 
509  fitAction = new QAction(i18n("Error Function"), this);
510  fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitErrFunc));
511  addFitAction.append(fitAction);
512 
513  fitAction = new QAction(i18n("Custom"), this);
514  fitAction->setData(static_cast<int>(PlotDataDialog::AnalysisAction::FitCustom));
515  addFitAction.append(fitAction);
516 
517  addFourierFilterAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-filter-curve"), i18n("Fourier Filter"), this);
518  addFourierTransformAction = new QAction(QIcon::fromTheme("labplot-xy-fourier-transform-curve"), i18n("Fourier Transform"), this);
519 
520  connect(addDataReductionAction, &QAction::triggered, this, &CartesianPlot::addDataReductionCurve);
521  connect(addDifferentiationAction, &QAction::triggered, this, &CartesianPlot::addDifferentiationCurve);
522  connect(addIntegrationAction, &QAction::triggered, this, &CartesianPlot::addIntegrationCurve);
523  connect(addInterpolationAction, &QAction::triggered, this, &CartesianPlot::addInterpolationCurve);
524  connect(addSmoothAction, &QAction::triggered, this, &CartesianPlot::addSmoothCurve);
525  connect(addConvolutionAction, &QAction::triggered, this, &CartesianPlot::addConvolutionCurve);
526  connect(addCorrelationAction, &QAction::triggered, this, &CartesianPlot::addCorrelationCurve);
527  for (const auto& action : addFitAction)
528  connect(action, &QAction::triggered, this, &CartesianPlot::addFitCurve);
529  connect(addFourierFilterAction, &QAction::triggered, this, &CartesianPlot::addFourierFilterCurve);
530  connect(addFourierTransformAction, &QAction::triggered, this, &CartesianPlot::addFourierTransformCurve);
531 
532  //zoom/navigate actions
533  scaleAutoAction = new QAction(QIcon::fromTheme("labplot-auto-scale-all"), i18n("Auto Scale"), this);
534  scaleAutoXAction = new QAction(QIcon::fromTheme("labplot-auto-scale-x"), i18n("Auto Scale X"), this);
535  scaleAutoYAction = new QAction(QIcon::fromTheme("labplot-auto-scale-y"), i18n("Auto Scale Y"), this);
536  zoomInAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("Zoom In"), this);
537  zoomOutAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("Zoom Out"), this);
538  zoomInXAction = new QAction(QIcon::fromTheme("labplot-zoom-in-x"), i18n("Zoom In X"), this);
539  zoomOutXAction = new QAction(QIcon::fromTheme("labplot-zoom-out-x"), i18n("Zoom Out X"), this);
540  zoomInYAction = new QAction(QIcon::fromTheme("labplot-zoom-in-y"), i18n("Zoom In Y"), this);
541  zoomOutYAction = new QAction(QIcon::fromTheme("labplot-zoom-out-y"), i18n("Zoom Out Y"), this);
542  shiftLeftXAction = new QAction(QIcon::fromTheme("labplot-shift-left-x"), i18n("Shift Left X"), this);
543  shiftRightXAction = new QAction(QIcon::fromTheme("labplot-shift-right-x"), i18n("Shift Right X"), this);
544  shiftUpYAction = new QAction(QIcon::fromTheme("labplot-shift-up-y"), i18n("Shift Up Y"), this);
545  shiftDownYAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("Shift Down Y"), this);
546 
547  connect(scaleAutoAction, &QAction::triggered, this, &CartesianPlot::scaleAutoTriggered);
548  connect(scaleAutoXAction, &QAction::triggered, this, &CartesianPlot::scaleAutoTriggered);
549  connect(scaleAutoYAction, &QAction::triggered, this, &CartesianPlot::scaleAutoTriggered);
550  connect(zoomInAction, &QAction::triggered, this, &CartesianPlot::zoomIn);
551  connect(zoomOutAction, &QAction::triggered, this, &CartesianPlot::zoomOut);
552  connect(zoomInXAction, &QAction::triggered, this, &CartesianPlot::zoomInX);
553  connect(zoomOutXAction, &QAction::triggered, this, &CartesianPlot::zoomOutX);
554  connect(zoomInYAction, &QAction::triggered, this, &CartesianPlot::zoomInY);
555  connect(zoomOutYAction, &QAction::triggered, this, &CartesianPlot::zoomOutY);
556  connect(shiftLeftXAction, &QAction::triggered, this, &CartesianPlot::shiftLeftX);
557  connect(shiftRightXAction, &QAction::triggered, this, &CartesianPlot::shiftRightX);
558  connect(shiftUpYAction, &QAction::triggered, this, &CartesianPlot::shiftUpY);
559  connect(shiftDownYAction, &QAction::triggered, this, &CartesianPlot::shiftDownY);
560 
561  //visibility action
562  visibilityAction = new QAction(QIcon::fromTheme("view-visible"), i18n("Visible"), this);
563  visibilityAction->setCheckable(true);
564  connect(visibilityAction, &QAction::triggered, this, &CartesianPlot::visibilityChanged);
565 }
566 
568  initActions();
569 
570  addNewMenu = new QMenu(i18n("Add New"));
571  addNewMenu->setIcon(QIcon::fromTheme("list-add"));
572  addNewMenu->addAction(addCurveAction);
573  addNewMenu->addAction(addHistogramAction);
575  addNewMenu->addSeparator();
576 
577  addNewAnalysisMenu = new QMenu(i18n("Analysis Curve"));
579  addNewAnalysisMenu->addSeparator();
582  addNewAnalysisMenu->addSeparator();
585  addNewAnalysisMenu->addSeparator();
588  addNewAnalysisMenu->addSeparator();
591  addNewAnalysisMenu->addSeparator();
593  addNewMenu->addMenu(addNewAnalysisMenu);
594 
595  addNewMenu->addSeparator();
596  addNewMenu->addAction(addLegendAction);
597  addNewMenu->addSeparator();
599  addNewMenu->addAction(addVerticalAxisAction);
600  addNewMenu->addSeparator();
601  addNewMenu->addAction(addTextLabelAction);
602  addNewMenu->addAction(addImageAction);
603  addNewMenu->addSeparator();
604  addNewMenu->addAction(addCustomPointAction);
606 
607  zoomMenu = new QMenu(i18n("Zoom/Navigate"));
608  zoomMenu->setIcon(QIcon::fromTheme("zoom-draw"));
609  zoomMenu->addAction(scaleAutoAction);
610  zoomMenu->addAction(scaleAutoXAction);
611  zoomMenu->addAction(scaleAutoYAction);
612  zoomMenu->addSeparator();
613  zoomMenu->addAction(zoomInAction);
614  zoomMenu->addAction(zoomOutAction);
615  zoomMenu->addSeparator();
616  zoomMenu->addAction(zoomInXAction);
617  zoomMenu->addAction(zoomOutXAction);
618  zoomMenu->addSeparator();
619  zoomMenu->addAction(zoomInYAction);
620  zoomMenu->addAction(zoomOutYAction);
621  zoomMenu->addSeparator();
622  zoomMenu->addAction(shiftLeftXAction);
623  zoomMenu->addAction(shiftRightXAction);
624  zoomMenu->addSeparator();
625  zoomMenu->addAction(shiftUpYAction);
626  zoomMenu->addAction(shiftDownYAction);
627 
628  // Data manipulation menu
629 // QMenu* dataManipulationMenu = new QMenu(i18n("Data Manipulation"));
630 // dataManipulationMenu->setIcon(QIcon::fromTheme("zoom-draw"));
631 // dataManipulationMenu->addAction(addDataOperationAction);
632 // dataManipulationMenu->addAction(addDataReductionAction);
633 
634  // Data fit menu
635  QMenu* dataFitMenu = new QMenu(i18n("Fit"));
636  dataFitMenu->setIcon(QIcon::fromTheme("labplot-xy-fit-curve"));
637  dataFitMenu->addAction(addFitAction.at(0));
638  dataFitMenu->addAction(addFitAction.at(1));
639  dataFitMenu->addAction(addFitAction.at(2));
640  dataFitMenu->addAction(addFitAction.at(3));
641  dataFitMenu->addAction(addFitAction.at(4));
642  dataFitMenu->addSeparator();
643  dataFitMenu->addAction(addFitAction.at(5));
644  dataFitMenu->addAction(addFitAction.at(6));
645  dataFitMenu->addSeparator();
646  dataFitMenu->addAction(addFitAction.at(7));
647  dataFitMenu->addAction(addFitAction.at(8));
648  dataFitMenu->addAction(addFitAction.at(9));
649  dataFitMenu->addSeparator();
650  dataFitMenu->addAction(addFitAction.at(10));
651 
652  //analysis menu
653  dataAnalysisMenu = new QMenu(i18n("Analysis"));
654  dataAnalysisMenu->addMenu(dataFitMenu);
655  dataAnalysisMenu->addSeparator();
658  dataAnalysisMenu->addSeparator();
660  dataAnalysisMenu->addAction(addSmoothAction);
661  dataAnalysisMenu->addSeparator();
664  dataAnalysisMenu->addSeparator();
667  dataAnalysisMenu->addSeparator();
668 // dataAnalysisMenu->insertMenu(nullptr, dataManipulationMenu);
670 
671  //themes menu
672  themeMenu = new QMenu(i18n("Apply Theme"));
673  themeMenu->setIcon(QIcon::fromTheme("color-management"));
674  auto* themeWidget = new ThemesWidget(nullptr);
675  themeWidget->setFixedMode();
676  connect(themeWidget, &ThemesWidget::themeSelected, this, &CartesianPlot::loadTheme);
677  connect(themeWidget, &ThemesWidget::themeSelected, themeMenu, &QMenu::close);
678 
679  auto* widgetAction = new QWidgetAction(this);
680  widgetAction->setDefaultWidget(themeWidget);
681  themeMenu->addAction(widgetAction);
682 
683  m_menusInitialized = true;
684 }
685 
687  if (!m_menusInitialized)
688  initMenus();
689 
690  QMenu* menu = WorksheetElement::createContextMenu();
691  QAction* firstAction = menu->actions().at(1);
692 
693 
694  menu->insertMenu(firstAction, addNewMenu);
695  menu->insertSeparator(firstAction);
696  menu->insertMenu(firstAction, zoomMenu);
697  menu->insertSeparator(firstAction);
698  menu->insertMenu(firstAction, themeMenu);
699  menu->insertSeparator(firstAction);
700 
701  visibilityAction->setChecked(isVisible());
702  menu->insertAction(firstAction, visibilityAction);
703 
704  return menu;
705 }
706 
708  if (!m_menusInitialized)
709  initMenus();
710 
711  return dataAnalysisMenu;
712 }
713 
714 /*!
715  Returns an icon to be used in the project explorer.
716 */
717 QIcon CartesianPlot::icon() const {
718  return QIcon::fromTheme("office-chart-line");
719 }
720 
722  //aspects which the plotted data in the worksheet depends on (spreadsheets and later matrices)
723  QVector<AbstractAspect*> aspects;
724 
725  for (const auto* curve : children<XYCurve>()) {
726  if (curve->xColumn() && dynamic_cast<Spreadsheet*>(curve->xColumn()->parentAspect()) )
727  aspects << curve->xColumn()->parentAspect();
728 
729  if (curve->yColumn() && dynamic_cast<Spreadsheet*>(curve->yColumn()->parentAspect()) )
730  aspects << curve->yColumn()->parentAspect();
731  }
732 
733  return aspects;
734 }
735 
737  Q_D(CartesianPlot);
738  if (op == NavigationOperation::ScaleAuto) {
739  if (d->curvesXMinMaxIsDirty || d->curvesYMinMaxIsDirty || !autoScaleX() || !autoScaleY()) {
740  d->curvesXMinMaxIsDirty = true;
741  d->curvesYMinMaxIsDirty = true;
742  }
743  scaleAuto();
744  } else if (op == NavigationOperation::ScaleAutoX) setAutoScaleX(true);
745  else if (op == NavigationOperation::ScaleAutoY) setAutoScaleY(true);
746  else if (op == NavigationOperation::ZoomIn) zoomIn();
747  else if (op == NavigationOperation::ZoomOut) zoomOut();
748  else if (op == NavigationOperation::ZoomInX) zoomInX();
749  else if (op == NavigationOperation::ZoomOutX) zoomOutX();
750  else if (op == NavigationOperation::ZoomInY) zoomInY();
751  else if (op == NavigationOperation::ZoomOutY) zoomOutY();
754  else if (op == NavigationOperation::ShiftUpY) shiftUpY();
756 }
757 
759  Q_D(CartesianPlot);
760  d->suppressRetransform = value;
761 }
762 
764  PERFTRACE("CartesianPlot::processDropEvent");
765 
766  QVector<AbstractColumn*> columns;
767  for (auto a : vec) {
768  auto* aspect = (AbstractAspect*)a;
769  auto* column = dynamic_cast<AbstractColumn*>(aspect);
770  if (column)
771  columns << column;
772  }
773 
774  //return if there are no columns being dropped.
775  //TODO: extend this later when we allow to drag&drop plots, etc.
776  if (columns.isEmpty())
777  return;
778 
779  //determine the first column with "x plot designation" as the x-data column for all curves to be created
780  const AbstractColumn* xColumn = nullptr;
781  for (const auto* column : columns) {
782  if (column->plotDesignation() == AbstractColumn::PlotDesignation::X) {
783  xColumn = column;
784  break;
785  }
786  }
787 
788  //if no column with "x plot designation" is available, use the x-data column of the first curve in the plot,
789  if (xColumn == nullptr) {
790  QVector<XYCurve*> curves = children<XYCurve>();
791  if (!curves.isEmpty())
792  xColumn = curves.at(0)->xColumn();
793  }
794 
795  //use the first dropped column if no column with "x plot designation" nor curves are available
796  if (xColumn == nullptr)
797  xColumn = columns.at(0);
798 
799  //create curves
800  bool curvesAdded = false;
801  for (const auto* column : columns) {
802  if (column == xColumn)
803  continue;
804 
805  XYCurve* curve = new XYCurve(column->name());
806  curve->suppressRetransform(true); //suppress retransform, all curved will be recalculated at the end
807  curve->setXColumn(xColumn);
808  curve->setYColumn(column);
809  addChild(curve);
810  curve->suppressRetransform(false);
811  curvesAdded = true;
812  }
813 
814  if (curvesAdded)
815  dataChanged();
816 }
817 
819  Q_D(const CartesianPlot);
820  return d->panningStarted;
821 }
822 
824  Q_D(const CartesianPlot);
825  return d->m_hovered;
826 }
828  Q_D(const CartesianPlot);
829  return d->m_printing;
830 }
831 
833  Q_D(const CartesianPlot);
834  return d->isSelected();
835 }
836 
837 //##############################################################################
838 //################################ getter methods ############################
839 //##############################################################################
843 BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeLastValues, rangeLastValues)
844 BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeFirstValues, rangeFirstValues)
845 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleX, autoScaleX)
846 BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, xMin, xMin)
847 BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, xMax, xMax)
849 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, xRangeBreakingEnabled, xRangeBreakingEnabled)
851 
852 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleY, autoScaleY)
853 BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, yMin, yMin)
854 BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, yMax, yMax)
856 BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, yRangeBreakingEnabled, yRangeBreakingEnabled)
858 
859 CLASS_SHARED_D_READER_IMPL(CartesianPlot, QPen, cursorPen, cursorPen);
860 CLASS_SHARED_D_READER_IMPL(CartesianPlot, bool, cursor0Enable, cursor0Enable);
861 CLASS_SHARED_D_READER_IMPL(CartesianPlot, bool, cursor1Enable, cursor1Enable);
862 CLASS_SHARED_D_READER_IMPL(CartesianPlot, QString, theme, theme)
863 
864 /*!
865  returns the actual bounding rectangular of the plot area showing data (plot's rectangular minus padding)
866  in plot's coordinates
867  */
868 QRectF CartesianPlot::dataRect() const {
869  Q_D(const CartesianPlot);
870  return d->dataRect;
871 }
872 
874  Q_D(const CartesianPlot);
875  return d->mouseMode;
876 }
877 
878 const QString& CartesianPlot::xRangeDateTimeFormat() const {
879  Q_D(const CartesianPlot);
880  return d->xRangeDateTimeFormat;
881 }
882 
883 const QString& CartesianPlot::yRangeDateTimeFormat() const {
884  Q_D(const CartesianPlot);
885  return d->yRangeDateTimeFormat;
886 }
887 
888 //##############################################################################
889 //###################### setter methods and undo commands ####################
890 //##############################################################################
891 /*!
892  set the rectangular, defined in scene coordinates
893  */
894 class CartesianPlotSetRectCmd : public QUndoCommand {
895 public:
896  CartesianPlotSetRectCmd(CartesianPlotPrivate* private_obj, QRectF rect) : m_private(private_obj), m_rect(rect) {
897  setText(i18n("%1: change geometry rect", m_private->name()));
898  };
899 
900  void redo() override {
901 // const double horizontalRatio = m_rect.width() / m_private->rect.width();
902 // const double verticalRatio = m_rect.height() / m_private->rect.height();
903 
904  qSwap(m_private->rect, m_rect);
905 
906 // m_private->q->handleResize(horizontalRatio, verticalRatio, false);
909  };
910 
911  void undo() override {
912  redo();
913  }
914 
915 private:
917  QRectF m_rect;
918 };
919 
920 void CartesianPlot::setRect(const QRectF& rect) {
921  Q_D(CartesianPlot);
922  if (rect != d->rect)
924 }
925 
926 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeType, CartesianPlot::RangeType, rangeType, rangeChanged);
927 void CartesianPlot::setRangeType(RangeType type) {
928  Q_D(CartesianPlot);
929  if (type != d->rangeType)
930  exec(new CartesianPlotSetRangeTypeCmd(d, type, ki18n("%1: set range type")));
931 }
932 
933 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeFormat, CartesianPlot::RangeFormat, xRangeFormat, xRangeFormatChanged);
934 void CartesianPlot::setXRangeFormat(RangeFormat format) {
935  Q_D(CartesianPlot);
936  if (format != d->xRangeFormat)
937  exec(new CartesianPlotSetXRangeFormatCmd(d, format, ki18n("%1: set x-range format")));
938 }
939 
940 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeFormat, CartesianPlot::RangeFormat, yRangeFormat, yRangeFormatChanged);
941 void CartesianPlot::setYRangeFormat(RangeFormat format) {
942  Q_D(CartesianPlot);
943  if (format != d->yRangeFormat)
944  exec(new CartesianPlotSetYRangeFormatCmd(d, format, ki18n("%1: set y-range format")));
945 }
946 
947 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeLastValues, int, rangeLastValues, rangeChanged);
948 void CartesianPlot::setRangeLastValues(int values) {
949  Q_D(CartesianPlot);
950  if (values != d->rangeLastValues)
951  exec(new CartesianPlotSetRangeLastValuesCmd(d, values, ki18n("%1: set range")));
952 }
953 
954 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeFirstValues, int, rangeFirstValues, rangeChanged);
955 void CartesianPlot::setRangeFirstValues(int values) {
956  Q_D(CartesianPlot);
957  if (values != d->rangeFirstValues)
958  exec(new CartesianPlotSetRangeFirstValuesCmd(d, values, ki18n("%1: set range")));
959 }
960 
961 
962 class CartesianPlotSetAutoScaleXCmd : public QUndoCommand {
963 public:
964  CartesianPlotSetAutoScaleXCmd(CartesianPlotPrivate* private_obj, bool autoScale) :
965  m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) {
966  setText(i18n("%1: change x-range auto scaling", m_private->name()));
967  };
968 
969  void redo() override {
971  if (m_autoScale) {
974  m_private->q->scaleAutoX();
975  }
978  };
979 
980  void undo() override {
981  if (!m_autoScaleOld) {
985  }
988  }
989 
990 private:
994  double m_minOld;
995  double m_maxOld;
996 };
997 
998 void CartesianPlot::setAutoScaleX(bool autoScaleX) {
999  Q_D(CartesianPlot);
1000  if (autoScaleX != d->autoScaleX)
1001  exec(new CartesianPlotSetAutoScaleXCmd(d, autoScaleX));
1002 }
1003 
1004 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMin, double, xMin, retransformScales)
1005 void CartesianPlot::setXMin(double xMin) {
1006  Q_D(CartesianPlot);
1007  if (xMin != d->xMin && xMin != -INFINITY && xMin != INFINITY) {
1008  d->curvesYMinMaxIsDirty = true;
1009  exec(new CartesianPlotSetXMinCmd(d, xMin, ki18n("%1: set min x")));
1010  if (d->autoScaleY)
1011  scaleAutoY();
1012  }
1013 }
1014 
1015 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMax, double, xMax, retransformScales)
1016 void CartesianPlot::setXMax(double xMax) {
1017  Q_D(CartesianPlot);
1018  if (xMax != d->xMax && xMax != -INFINITY && xMax != INFINITY) {
1019  d->curvesYMinMaxIsDirty = true;
1020  exec(new CartesianPlotSetXMaxCmd(d, xMax, ki18n("%1: set max x")));
1021  if (d->autoScaleY)
1022  scaleAutoY();
1023  }
1024 }
1025 
1026 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXScale, CartesianPlot::Scale, xScale, retransformScales)
1027 void CartesianPlot::setXScale(Scale scale) {
1028  Q_D(CartesianPlot);
1029  if (scale != d->xScale)
1030  exec(new CartesianPlotSetXScaleCmd(d, scale, ki18n("%1: set x scale")));
1031 }
1032 
1033 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreakingEnabled, bool, xRangeBreakingEnabled, retransformScales)
1034 void CartesianPlot::setXRangeBreakingEnabled(bool enabled) {
1035  Q_D(CartesianPlot);
1036  if (enabled != d->xRangeBreakingEnabled)
1037  exec(new CartesianPlotSetXRangeBreakingEnabledCmd(d, enabled, ki18n("%1: x-range breaking enabled")));
1038 }
1039 
1040 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreaks, CartesianPlot::RangeBreaks, xRangeBreaks, retransformScales)
1041 void CartesianPlot::setXRangeBreaks(const RangeBreaks& breakings) {
1042  Q_D(CartesianPlot);
1043  exec(new CartesianPlotSetXRangeBreaksCmd(d, breakings, ki18n("%1: x-range breaks changed")));
1044 }
1045 
1046 class CartesianPlotSetAutoScaleYCmd : public QUndoCommand {
1047 public:
1048  CartesianPlotSetAutoScaleYCmd(CartesianPlotPrivate* private_obj, bool autoScale) :
1049  m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) {
1050  setText(i18n("%1: change y-range auto scaling", m_private->name()));
1051  };
1052 
1053  void redo() override {
1055  if (m_autoScale) {
1056  m_minOld = m_private->yMin;
1057  m_maxOld = m_private->yMax;
1058  m_private->q->scaleAutoY();
1059  }
1062  };
1063 
1064  void undo() override {
1065  if (!m_autoScaleOld) {
1066  m_private->yMin = m_minOld;
1067  m_private->yMax = m_maxOld;
1069  }
1072  }
1073 
1074 private:
1078  double m_minOld;
1079  double m_maxOld;
1080 };
1081 
1082 void CartesianPlot::setAutoScaleY(bool autoScaleY) {
1083  Q_D(CartesianPlot);
1084  if (autoScaleY != d->autoScaleY)
1085  exec(new CartesianPlotSetAutoScaleYCmd(d, autoScaleY));
1086 }
1087 
1088 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMin, double, yMin, retransformScales)
1089 void CartesianPlot::setYMin(double yMin) {
1090  Q_D(CartesianPlot);
1091  if (yMin != d->yMin) {
1092  d->curvesXMinMaxIsDirty = true;
1093  exec(new CartesianPlotSetYMinCmd(d, yMin, ki18n("%1: set min y")));
1094  if (d->autoScaleX)
1095  scaleAutoX();
1096  }
1097 }
1098 
1099 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMax, double, yMax, retransformScales)
1100 void CartesianPlot::setYMax(double yMax) {
1101  Q_D(CartesianPlot);
1102  if (yMax != d->yMax) {
1103  d->curvesXMinMaxIsDirty = true;
1104  exec(new CartesianPlotSetYMaxCmd(d, yMax, ki18n("%1: set max y")));
1105  if (d->autoScaleX)
1106  scaleAutoX();
1107  }
1108 }
1109 
1110 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYScale, CartesianPlot::Scale, yScale, retransformScales)
1111 void CartesianPlot::setYScale(Scale scale) {
1112  Q_D(CartesianPlot);
1113  if (scale != d->yScale)
1114  exec(new CartesianPlotSetYScaleCmd(d, scale, ki18n("%1: set y scale")));
1115 }
1116 
1117 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreakingEnabled, bool, yRangeBreakingEnabled, retransformScales)
1118 void CartesianPlot::setYRangeBreakingEnabled(bool enabled) {
1119  Q_D(CartesianPlot);
1120  if (enabled != d->yRangeBreakingEnabled)
1121  exec(new CartesianPlotSetYRangeBreakingEnabledCmd(d, enabled, ki18n("%1: y-range breaking enabled")));
1122 }
1123 
1124 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreaks, CartesianPlot::RangeBreaks, yRangeBreaks, retransformScales)
1125 void CartesianPlot::setYRangeBreaks(const RangeBreaks& breaks) {
1126  Q_D(CartesianPlot);
1127  exec(new CartesianPlotSetYRangeBreaksCmd(d, breaks, ki18n("%1: y-range breaks changed")));
1128 }
1129 
1130 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursorPen, QPen, cursorPen, update)
1131 void CartesianPlot::setCursorPen(const QPen &pen) {
1132  Q_D(CartesianPlot);
1133  if (pen != d->cursorPen)
1134  exec(new CartesianPlotSetCursorPenCmd(d, pen, ki18n("%1: y-range breaks changed")));
1135 }
1136 
1137 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursor0Enable, bool, cursor0Enable, updateCursor)
1138 void CartesianPlot::setCursor0Enable(const bool &enable) {
1139  Q_D(CartesianPlot);
1140  if (enable != d->cursor0Enable) {
1141  if (std::isnan(d->cursor0Pos.x())) { // if never set, set initial position
1142  d->cursor0Pos.setX(d->cSystem->mapSceneToLogical(QPointF(0,0)).x());
1143  mousePressCursorModeSignal(0, d->cursor0Pos); // simulate mousePress to update values in the cursor dock
1144  }
1145  exec(new CartesianPlotSetCursor0EnableCmd(d, enable, ki18n("%1: Cursor0 enable")));
1146  }
1147 }
1148 
1149 STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetCursor1Enable, bool, cursor1Enable, updateCursor)
1150 void CartesianPlot::setCursor1Enable(const bool &enable) {
1151  Q_D(CartesianPlot);
1152  if (enable != d->cursor1Enable) {
1153  if (std::isnan(d->cursor1Pos.x())) { // if never set, set initial position
1154  d->cursor1Pos.setX(d->cSystem->mapSceneToLogical(QPointF(0,0)).x());
1155  mousePressCursorModeSignal(1, d->cursor1Pos); // simulate mousePress to update values in the cursor dock
1156  }
1157  exec(new CartesianPlotSetCursor1EnableCmd(d, enable, ki18n("%1: Cursor1 enable")));
1158  }
1159 }
1160 
1161 STD_SETTER_CMD_IMPL_S(CartesianPlot, SetTheme, QString, theme)
1162 void CartesianPlot::setTheme(const QString& theme) {
1163  Q_D(CartesianPlot);
1164  if (theme != d->theme) {
1165  QString info;
1166  if (!theme.isEmpty())
1167  info = i18n("%1: load theme %2", name(), theme);
1168  else
1169  info = i18n("%1: load default theme", name());
1170  beginMacro(info);
1171  exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: set theme")));
1172  loadTheme(theme);
1173  endMacro();
1174  }
1175 }
1176 
1177 //################################################################
1178 //########################## Slots ###############################
1179 //################################################################
1181  Axis* axis = new Axis("x-axis", Axis::Orientation::Horizontal);
1182  if (axis->autoScale()) {
1183  axis->setUndoAware(false);
1184  axis->setStart(xMin());
1185  axis->setEnd(xMax());
1186  axis->setUndoAware(true);
1187  }
1188  addChild(axis);
1189 }
1190 
1192  Axis* axis = new Axis("y-axis", Axis::Orientation::Vertical);
1193  if (axis->autoScale()) {
1194  axis->setUndoAware(false);
1195  axis->setStart(yMin());
1196  axis->setEnd(yMax());
1197  axis->setUndoAware(true);
1198  }
1199  addChild(axis);
1200 }
1201 
1203  addChild(new XYCurve("xy-curve"));
1204 }
1205 
1207  addChild(new XYEquationCurve("f(x)"));
1208 }
1209 
1211  addChild(new Histogram("Histogram"));
1212 }
1213 
1214 /*!
1215  * returns the first selected XYCurve in the plot
1216  */
1218  for (const auto* curve : this->children<const XYCurve>()) {
1219  if (curve->graphicsItem()->isSelected())
1220  return curve;
1221  }
1222 
1223  return nullptr;
1224 }
1225 
1227  auto* curve = new XYDataReductionCurve("Data reduction");
1228  const XYCurve* curCurve = currentCurve();
1229  if (curCurve) {
1230  beginMacro( i18n("%1: reduce '%2'", name(), curCurve->name()) );
1231  curve->setName( i18n("Reduction of '%1'", curCurve->name()) );
1232  curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
1233  curve->setDataSourceCurve(curCurve);
1234  this->addChild(curve);
1235  curve->recalculate();
1236  emit curve->dataReductionDataChanged(curve->dataReductionData());
1237  } else {
1238  beginMacro(i18n("%1: add data reduction curve", name()));
1239  this->addChild(curve);
1240  }
1241 
1242  endMacro();
1243 }
1244 
1246  auto* curve = new XYDifferentiationCurve("Differentiation");
1247  const XYCurve* curCurve = currentCurve();
1248  if (curCurve) {
1249  beginMacro( i18n("%1: differentiate '%2'", name(), curCurve->name()) );
1250  curve->setName( i18n("Derivative of '%1'", curCurve->name()) );
1251  curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
1252  curve->setDataSourceCurve(curCurve);
1253  this->addChild(curve);
1254  curve->recalculate();
1255  emit curve->differentiationDataChanged(curve->differentiationData());
1256  } else {
1257  beginMacro(i18n("%1: add differentiation curve", name()));
1258  this->addChild(curve);
1259  }
1260 
1261  endMacro();
1262 }
1263 
1265  auto* curve = new XYIntegrationCurve("Integration");
1266  const XYCurve* curCurve = currentCurve();
1267  if (curCurve) {
1268  beginMacro( i18n("%1: integrate '%2'", name(), curCurve->name()) );
1269  curve->setName( i18n("Integral of '%1'", curCurve->name()) );
1270  curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
1271  curve->setDataSourceCurve(curCurve);
1272  this->addChild(curve);
1273  curve->recalculate();
1274  emit curve->integrationDataChanged(curve->integrationData());
1275  } else {
1276  beginMacro(i18n("%1: add integration curve", name()));
1277  this->addChild(curve);
1278  }
1279 
1280  endMacro();
1281 }
1282 
1284  auto* curve = new XYInterpolationCurve("Interpolation");
1285  const XYCurve* curCurve = currentCurve();
1286  if (curCurve) {
1287  beginMacro( i18n("%1: interpolate '%2'", name(), curCurve->name()) );
1288  curve->setName( i18n("Interpolation of '%1'", curCurve->name()) );
1289  curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
1290  curve->setDataSourceCurve(curCurve);
1291  curve->recalculate();
1292  this->addChild(curve);
1293  emit curve->interpolationDataChanged(curve->interpolationData());
1294  } else {
1295  beginMacro(i18n("%1: add interpolation curve", name()));
1296  this->addChild(curve);
1297  }
1298 
1299  endMacro();
1300 }
1301 
1303  auto* curve = new XYSmoothCurve("Smooth");
1304  const XYCurve* curCurve = currentCurve();
1305  if (curCurve) {
1306  beginMacro( i18n("%1: smooth '%2'", name(), curCurve->name()) );
1307  curve->setName( i18n("Smoothing of '%1'", curCurve->name()) );
1308  curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
1309  curve->setDataSourceCurve(curCurve);
1310  this->addChild(curve);
1311  curve->recalculate();
1312  emit curve->smoothDataChanged(curve->smoothData());
1313  } else {
1314  beginMacro(i18n("%1: add smoothing curve", name()));
1315  this->addChild(curve);
1316  }
1317 
1318  endMacro();
1319 }
1320 
1322  auto* curve = new XYFitCurve("fit");
1323  const XYCurve* curCurve = currentCurve();
1324  if (curCurve) {
1325  beginMacro( i18n("%1: fit to '%2'", name(), curCurve->name()) );
1326  curve->setName( i18n("Fit to '%1'", curCurve->name()) );
1327  curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
1328  curve->setDataSourceCurve(curCurve);
1329 
1330 
1331  //set the fit model category and type
1332  const auto* action = qobject_cast<const QAction*>(QObject::sender());
1334  curve->initFitData(type);
1335  curve->initStartValues(curCurve);
1336 
1337  //fit with weights for y if the curve has error bars for y
1338  if (curCurve->yErrorType() == XYCurve::ErrorType::Symmetric && curCurve->yErrorPlusColumn()) {
1339  XYFitCurve::FitData fitData = curve->fitData();
1341  curve->setFitData(fitData);
1342  curve->setYErrorColumn(curCurve->yErrorPlusColumn());
1343  }
1344 
1345  curve->recalculate();
1346 
1347  //add the child after the fit was calculated so the dock widgets gets the fit results
1348  //and call retransform() after this to calculate and to paint the data points of the fit-curve
1349  this->addChild(curve);
1350  curve->retransform();
1351  } else {
1352  beginMacro(i18n("%1: add fit curve", name()));
1353  this->addChild(curve);
1354  }
1355 
1356  endMacro();
1357 }
1358 
1360  auto* curve = new XYFourierFilterCurve("Fourier filter");
1361  const XYCurve* curCurve = currentCurve();
1362  if (curCurve) {
1363  beginMacro( i18n("%1: Fourier filtering of '%2'", name(), curCurve->name()) );
1364  curve->setName( i18n("Fourier filtering of '%1'", curCurve->name()) );
1365  curve->setDataSourceType(XYAnalysisCurve::DataSourceType::Curve);
1366  curve->setDataSourceCurve(curCurve);
1367  this->addChild(curve);
1368  } else {
1369  beginMacro(i18n("%1: add Fourier filter curve", name()));
1370  this->addChild(curve);
1371  }
1372 
1373  endMacro();
1374 }
1375 
1377  auto* curve = new XYFourierTransformCurve("Fourier transform");
1378  this->addChild(curve);
1379 }
1380 
1382  auto* curve = new XYConvolutionCurve("Convolution");
1383  this->addChild(curve);
1384 }
1385 
1387  auto* curve = new XYCorrelationCurve("Auto-/Cross-Correlation");
1388  this->addChild(curve);
1389 }
1390 
1391 /*!
1392  * public helper function to set a legend object created outside of CartesianPlot, e.g. in \c OriginProjectParser.
1393  */
1395  m_legend = legend;
1396  this->addChild(legend);
1397 }
1398 
1400  //don't do anything if there's already a legend
1401  if (m_legend)
1402  return;
1403 
1404  m_legend = new CartesianPlotLegend(this, "legend");
1405  this->addChild(m_legend);
1406  m_legend->retransform();
1407 
1408  //only one legend is allowed -> disable the action
1409  if (m_menusInitialized)
1410  addLegendAction->setEnabled(false);
1411 }
1412 
1414  auto* label = new TextLabel("text label");
1415  this->addChild(label);
1416  label->setParentGraphicsItem(graphicsItem());
1417 }
1418 
1420  auto* image = new Image("image");
1421  this->addChild(image);
1422 }
1423 
1425  auto* point = new CustomPoint(this, "custom point");
1426  this->addChild(point);
1427  point->retransform();
1428 }
1429 
1431  auto* line = new ReferenceLine(this, "reference line");
1432  this->addChild(line);
1433  line->retransform();
1434 }
1435 
1437  return children<XYCurve>().length();
1438 }
1439 
1441  return children<XYCurve>().at(index);
1442 }
1443 
1444 double CartesianPlot::cursorPos(int cursorNumber) {
1445  Q_D(CartesianPlot);
1446  if (cursorNumber == 0)
1447  return d->cursor0Pos.x();
1448  else
1449  return d->cursor1Pos.x();
1450 }
1451 
1453  Q_D(CartesianPlot);
1454 
1455  const bool firstCurve = (children<XYCurve>().size() + children<Histogram>().size() == 1);
1456  const auto* curve = qobject_cast<const XYCurve*>(child);
1457  if (curve) {
1458  connect(curve, &XYCurve::dataChanged, this, &CartesianPlot::dataChanged);
1459  connect(curve, &XYCurve::xColumnChanged, this, [this](const AbstractColumn* column) {
1460  const bool firstCurve = (children<XYCurve>().size() + children<Histogram>().size() == 1);
1461  if (firstCurve)
1463  });
1464  connect(curve, &XYCurve::xDataChanged, this, &CartesianPlot::xDataChanged);
1465  connect(curve, &XYCurve::xErrorTypeChanged, this, &CartesianPlot::dataChanged);
1468  connect(curve, &XYCurve::yDataChanged, this, &CartesianPlot::yDataChanged);
1469  connect(curve, &XYCurve::yColumnChanged, this, [this](const AbstractColumn* column) {
1470  const bool firstCurve = (children<XYCurve>().size() + children<Histogram>().size() == 1);
1471  if (firstCurve)
1473  });
1474  connect(curve, &XYCurve::yErrorTypeChanged, this, &CartesianPlot::dataChanged);
1477  connect(curve, QOverload<bool>::of(&XYCurve::visibilityChanged),
1479 
1480  //update the legend on changes of the name, line and symbol styles
1483  connect(curve, &XYCurve::lineTypeChanged, this, &CartesianPlot::updateLegend);
1484  connect(curve, &XYCurve::linePenChanged, this, &CartesianPlot::updateLegend);
1485  connect(curve, &XYCurve::linePenChanged, this, static_cast<void (CartesianPlot::*)(QPen)>(&CartesianPlot::curveLinePenChanged));
1493  connect(curve, &XYCurve::linePenChanged, this, QOverload<QPen>::of(&CartesianPlot::curveLinePenChanged)); // forward to Worksheet to update CursorDock
1494 
1495  updateLegend();
1496  d->curvesXMinMaxIsDirty = true;
1497  d->curvesYMinMaxIsDirty = true;
1498 
1499  //in case the first curve is added, check whether we start plotting datetime data
1500  if (firstCurve) {
1502  checkAxisFormat(curve->yColumn(), Axis::Orientation::Vertical);
1503  }
1504 
1505  emit curveAdded(curve);
1506 
1507  } else {
1508  const auto* hist = qobject_cast<const Histogram*>(child);
1509  if (hist) {
1510  connect(hist, &Histogram::dataChanged, this, &CartesianPlot::dataChanged);
1512 
1513  updateLegend();
1514  d->curvesXMinMaxIsDirty = true;
1515  d->curvesYMinMaxIsDirty = true;
1516 
1517  if (firstCurve)
1518  checkAxisFormat(hist->dataColumn(), Axis::Orientation::Horizontal);
1519  }
1520  // if an element is hovered, the curves which are handled manually in this class
1521  // must be unhovered
1522  const auto* element = static_cast<const WorksheetElement*>(child);
1523  connect(element, &WorksheetElement::hovered, this, &CartesianPlot::childHovered);
1524  }
1525 
1526  if (!isLoading()) {
1527  //if a theme was selected, apply the theme settings for newly added children,
1528  //load default theme settings otherwise.
1529  const auto* elem = dynamic_cast<const WorksheetElement*>(child);
1530  if (elem) {
1531  if (!d->theme.isEmpty()) {
1532  KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig);
1533  const_cast<WorksheetElement*>(elem)->loadThemeConfig(config);
1534  } else {
1535  KConfig config;
1536  const_cast<WorksheetElement*>(elem)->loadThemeConfig(config);
1537  }
1538  }
1539  }
1540 }
1541 
1543  const auto* col = dynamic_cast<const Column*>(column);
1544  if (!col)
1545  return;
1546 
1547  if (col->columnMode() == AbstractColumn::ColumnMode::DateTime) {
1548  setUndoAware(false);
1549  if (orientation == Axis::Orientation::Horizontal)
1550  setXRangeFormat(RangeFormat::DateTime);
1551  else
1552  setYRangeFormat(RangeFormat::DateTime);
1553  setUndoAware(true);
1554 
1555  //set column's datetime format for all horizontal axis
1556  Q_D(CartesianPlot);
1557  for (auto* axis : children<Axis>()) {
1558  if (axis->orientation() == orientation) {
1559  auto* filter = static_cast<DateTime2StringFilter*>(col->outputFilter());
1560  d->xRangeDateTimeFormat = filter->format();
1561  axis->setUndoAware(false);
1562  axis->setLabelsDateTimeFormat(d->xRangeDateTimeFormat);
1563  axis->setUndoAware(true);
1564  }
1565  }
1566  } else {
1567  setUndoAware(false);
1568  if (orientation == Axis::Orientation::Horizontal)
1569  setXRangeFormat(RangeFormat::Numeric);
1570  else
1571  setYRangeFormat(RangeFormat::Numeric);
1572  setUndoAware(true);
1573  }
1574 }
1575 
1576 void CartesianPlot::childRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) {
1577  Q_UNUSED(parent);
1578  Q_UNUSED(before);
1579  if (m_legend == child) {
1580  if (m_menusInitialized)
1581  addLegendAction->setEnabled(true);
1582  m_legend = nullptr;
1583  } else {
1584  const auto* curve = qobject_cast<const XYCurve*>(child);
1585  if (curve) {
1586  updateLegend();
1587  emit curveRemoved(curve);
1588  }
1589  }
1590 }
1591 
1592 /*!
1593  * \brief CartesianPlot::childHovered
1594  * Unhover all curves, when another child is hovered. The hover handling for the curves is done in their parent (CartesianPlot),
1595  * because the hover should set when the curve is hovered and not just the bounding rect (for more see hoverMoveEvent)
1596  */
1598  Q_D(CartesianPlot);
1599  bool curveSender = dynamic_cast<XYCurve*>(QObject::sender()) != nullptr;
1600  if (!d->isSelected()) {
1601  if (d->m_hovered)
1602  d->m_hovered = false;
1603  d->update();
1604  }
1605  if (!curveSender) {
1606  for (auto curve: children<XYCurve>())
1607  curve->setHover(false);
1608  }
1609 }
1610 
1612  if (m_legend)
1613  m_legend->retransform();
1614 }
1615 
1616 /*!
1617  called when in one of the curves the data was changed.
1618  Autoscales the coordinate system and the x-axes, when "auto-scale" is active.
1619 */
1621  if (project() && project()->isLoading())
1622  return;
1623 
1624  Q_D(CartesianPlot);
1625  d->curvesXMinMaxIsDirty = true;
1626  d->curvesYMinMaxIsDirty = true;
1627  bool updated = false;
1628  if (d->autoScaleX && d->autoScaleY)
1629  updated = scaleAuto();
1630  else if (d->autoScaleX)
1631  updated = scaleAutoX();
1632  else if (d->autoScaleY)
1633  updated = scaleAutoY();
1634 
1635  if (!updated || !QObject::sender()) {
1636  //even if the plot ranges were not changed, either no auto scale active or the new data
1637  //is within the current ranges and no change of the ranges is required,
1638  //retransform the curve in order to show the changes
1639  auto* curve = dynamic_cast<XYCurve*>(QObject::sender());
1640  if (curve)
1641  curve->retransform();
1642  else {
1643  auto* hist = dynamic_cast<Histogram*>(QObject::sender());
1644  if (hist)
1645  hist->retransform();
1646  else {
1647  //no sender available, the function was called directly in the file filter (live data source got new data)
1648  //or in Project::load() -> retransform all available curves since we don't know which curves are affected.
1649  //TODO: this logic can be very expensive
1650  for (auto* c : children<XYCurve>()) {
1651  c->recalcLogicalPoints();
1652  c->retransform();
1653  }
1654  }
1655  }
1656  }
1657 }
1658 
1659 /*!
1660  called when in one of the curves the x-data was changed.
1661  Autoscales the coordinate system and the x-axes, when "auto-scale" is active.
1662 */
1664  if (project() && project()->isLoading())
1665  return;
1666 
1667  Q_D(CartesianPlot);
1668  if (d->suppressRetransform)
1669  return;
1670 
1671  d->curvesXMinMaxIsDirty = true;
1672  bool updated = false;
1673  if (d->autoScaleX)
1674  updated = this->scaleAutoX();
1675 
1676  if (!updated) {
1677  //even if the plot ranges were not changed, either no auto scale active or the new data
1678  //is within the current ranges and no change of the ranges is required,
1679  //retransform the curve in order to show the changes
1680  auto* curve = dynamic_cast<XYCurve*>(QObject::sender());
1681  if (curve)
1682  curve->retransform();
1683  else {
1684  auto* hist = dynamic_cast<Histogram*>(QObject::sender());
1685  if (hist)
1686  hist->retransform();
1687  }
1688  }
1689 
1690  //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data
1691  if (children<XYCurve>().size() == 1) {
1692  auto* curve = dynamic_cast<XYCurve*>(QObject::sender());
1693  if (curve) {
1694  const AbstractColumn* col = curve->xColumn();
1695  if (col->columnMode() == AbstractColumn::ColumnMode::DateTime && d->xRangeFormat != RangeFormat::DateTime) {
1696  setUndoAware(false);
1697  setXRangeFormat(RangeFormat::DateTime);
1698  setUndoAware(true);
1699  }
1700  }
1701  }
1702  emit curveDataChanged(dynamic_cast<XYCurve*>(QObject::sender()));
1703 }
1704 
1705 /*!
1706  called when in one of the curves the x-data was changed.
1707  Autoscales the coordinate system and the x-axes, when "auto-scale" is active.
1708 */
1710  if (project() && project()->isLoading())
1711  return;
1712 
1713  Q_D(CartesianPlot);
1714  if (d->suppressRetransform)
1715  return;
1716 
1717  d->curvesYMinMaxIsDirty = true;
1718  bool updated = false;
1719  if (d->autoScaleY)
1720  updated = this->scaleAutoY();
1721 
1722  if (!updated) {
1723  //even if the plot ranges were not changed, either no auto scale active or the new data
1724  //is within the current ranges and no change of the ranges is required,
1725  //retransform the curve in order to show the changes
1726  auto* curve = dynamic_cast<XYCurve*>(QObject::sender());
1727  if (curve)
1728  curve->retransform();
1729  else {
1730  auto* hist = dynamic_cast<Histogram*>(QObject::sender());
1731  if (hist)
1732  hist->retransform();
1733  }
1734  }
1735 
1736  //in case there is only one curve and its column mode was changed, check whether we start plotting datetime data
1737  if (children<XYCurve>().size() == 1) {
1738  auto* curve = dynamic_cast<XYCurve*>(QObject::sender());
1739  if (curve) {
1740  const AbstractColumn* col = curve->yColumn();
1741  if (col->columnMode() == AbstractColumn::ColumnMode::DateTime && d->yRangeFormat != RangeFormat::DateTime) {
1742  setUndoAware(false);
1743  setYRangeFormat(RangeFormat::DateTime);
1744  setUndoAware(true);
1745  }
1746  }
1747  }
1748  emit curveDataChanged(dynamic_cast<XYCurve*>(QObject::sender()));
1749 }
1750 
1752  Q_D(CartesianPlot);
1753  d->curvesXMinMaxIsDirty = true;
1754  d->curvesYMinMaxIsDirty = true;
1755  updateLegend();
1756  if (d->autoScaleX && d->autoScaleY)
1757  this->scaleAuto();
1758  else if (d->autoScaleX)
1759  this->scaleAutoX();
1760  else if (d->autoScaleY)
1761  this->scaleAutoY();
1762 
1764 }
1765 
1767  const auto* curve = qobject_cast<const XYCurve*>(QObject::sender());
1768  emit curveLinePenChanged(pen, curve->name());
1769 }
1770 
1772  Q_D(CartesianPlot);
1773 
1774  d->mouseMode = mouseMode;
1775  d->setHandlesChildEvents(mouseMode != MouseMode::Selection);
1776 
1777  QList<QGraphicsItem*> items = d->childItems();
1778  if (d->mouseMode == MouseMode::Selection) {
1779  d->setZoomSelectionBandShow(false);
1780  d->setCursor(Qt::ArrowCursor);
1781  for (auto* item : items)
1782  item->setFlag(QGraphicsItem::ItemStacksBehindParent, false);
1783  } else {
1784  for (auto* item : items)
1785  item->setFlag(QGraphicsItem::ItemStacksBehindParent, true);
1786  }
1787 
1788  //when doing zoom selection, prevent the graphics item from being movable
1789  //if it's currently movable (no worksheet layout available)
1790  const auto* worksheet = dynamic_cast<const Worksheet*>(parentAspect());
1791  if (worksheet) {
1793  if (worksheet->layout() != Worksheet::Layout::NoLayout)
1794  graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false);
1795  else
1796  graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true);
1797  } else //zoom m_selection
1798  graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false);
1799  }
1800 
1802 }
1803 
1804 void CartesianPlot::setLocked(bool locked) {
1805  Q_D(CartesianPlot);
1806  d->locked = locked;
1807 }
1808 
1810  Q_D(const CartesianPlot);
1811  return d->locked;
1812 }
1813 
1815  Q_D(CartesianPlot);
1816  if (d->curvesXMinMaxIsDirty) {
1817  calculateCurvesXMinMax(false);
1818 
1819  /*
1820  //take the size of the error bar cap into account if error bars with caps are plotted
1821  double errorBarsCapSize = -1;
1822  for (auto* curve : this->children<const XYCurve>()) {
1823  if (curve->yErrorType() == XYCurve::ErrorType::NoError)
1824  continue;
1825 
1826  if (curve->errorBarsType() != XYCurve::ErrorBarsType::WithEnds)
1827  continue;
1828 
1829  if ( (curve->yErrorType() == XYCurve::ErrorType::Symmetric && curve->yErrorPlusColumn())
1830  || (curve->yErrorType() == XYCurve::ErrorType::Asymmetric && (curve->yErrorPlusColumn() && curve->yErrorMinusColumn())) )
1831  errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize());
1832  }
1833 
1834  if (errorBarsCapSize > 0) {
1835  // must be done, because retransformScales uses xMin/xMax
1836  if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY)
1837  d->xMin = d->curvesXMin;
1838 
1839  if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY)
1840  d->xMax = d->curvesXMax;
1841  // When the previous scale is completely different. The mapTo functions scale with wrong values. To prevent
1842  // this a rescale must be done.
1843  // The errorBarsCapSize is in Scene coordinates. So this value must be transformed into a logical value. Due
1844  // to nonlinear scalings it cannot only be multiplied with a scaling factor and depends on the position of the
1845  // column value
1846  // dirty hack: call setIsLoading(true) to suppress the call of retransform() in retransformScales() since a
1847  // retransform is already done at the end of this function
1848  setIsLoading(true);
1849  d->retransformScales();
1850  setIsLoading(false);
1851  QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMin, 0), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
1852  point.setX(point.x() - errorBarsCapSize/2.);
1853  point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
1854  // Problem is, when the scaling is not linear (for example log(x)) and the minimum is 0. In this
1855  // case mapLogicalToScene returns (0,0) which is smaller than the curves minimum
1856  if (point.x() < d->curvesXMin)
1857  d->curvesXMin = point.x();
1858 
1859  point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMax, 0), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
1860  point.setX(point.x() + errorBarsCapSize/2.);
1861  point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
1862  if (point.x() > d->curvesXMax)
1863  d->curvesXMax = point.x();
1864  }
1865  */
1866  d->curvesYMinMaxIsDirty = true;
1867  d->curvesXMinMaxIsDirty = false;
1868  }
1869 
1870  bool update = false;
1871  if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) {
1872  d->xMin = d->curvesXMin;
1873  update = true;
1874  }
1875 
1876  if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) {
1877  d->xMax = d->curvesXMax;
1878  update = true;
1879  }
1880 
1881  if (update) {
1882  if (d->xMax == d->xMin) {
1883  //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value
1884  if (d->xMax != 0) {
1885  d->xMax = d->xMax*1.1;
1886  d->xMin = d->xMin*0.9;
1887  } else {
1888  d->xMax = 0.1;
1889  d->xMin = -0.1;
1890  }
1891  } else {
1892  double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor;
1893  d->xMin -= offset;
1894  d->xMax += offset;
1895  }
1896  d->retransformScales();
1897  }
1898 
1899  return update;
1900 }
1901 
1903  Q_D(CartesianPlot);
1904 
1905  if (d->curvesYMinMaxIsDirty) {
1906  calculateCurvesYMinMax(false); // loop over all curves
1907 
1908  /*
1909  //take the size of the error bar cap into account if error bars with caps are plotted
1910  double errorBarsCapSize = -1;
1911  for (auto* curve : this->children<const XYCurve>()) {
1912  if (curve->xErrorType() == XYCurve::ErrorType::NoError)
1913  continue;
1914 
1915  if (curve->errorBarsType() != XYCurve::ErrorBarsType::WithEnds)
1916  continue;
1917 
1918  if ( (curve->xErrorType() == XYCurve::ErrorType::Symmetric && curve->xErrorPlusColumn())
1919  || (curve->xErrorType() == XYCurve::ErrorType::Asymmetric && (curve->xErrorPlusColumn() && curve->xErrorMinusColumn())) )
1920  errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize());
1921  }
1922 
1923  if (errorBarsCapSize > 0) {
1924  if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY)
1925  d->yMin = d->curvesYMin;
1926 
1927  if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY)
1928  d->yMax = d->curvesYMax;
1929  setIsLoading(true);
1930  d->retransformScales();
1931  setIsLoading(false);
1932  QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMin), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
1933  point.setY(point.y() + errorBarsCapSize);
1934  point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
1935  if (point.y() < d->curvesYMin)
1936  d->curvesYMin = point.y();
1937 
1938  point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMax), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
1939  point.setY(point.y() - errorBarsCapSize);
1940  point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
1941  if (point.y() > d->curvesYMax)
1942  d->curvesYMax = point.y();
1943  }
1944  */
1945 
1946  d->curvesXMinMaxIsDirty = true;
1947  d->curvesYMinMaxIsDirty = false;
1948  }
1949 
1950  bool update = false;
1951  if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) {
1952  d->yMin = d->curvesYMin;
1953  update = true;
1954  }
1955 
1956  if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) {
1957  d->yMax = d->curvesYMax;
1958  update = true;
1959  }
1960  if (update) {
1961  if (d->yMax == d->yMin) {
1962  //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value
1963  if (d->yMax != 0) {
1964  d->yMax = d->yMax*1.1;
1965  d->yMin = d->yMin*0.9;
1966  } else {
1967  d->yMax = 0.1;
1968  d->yMin = -0.1;
1969  }
1970  } else {
1971  double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor;
1972  d->yMin -= offset;
1973  d->yMax += offset;
1974  }
1975  d->retransformScales();
1976  }
1977 
1978  return update;
1979 }
1980 
1982  QAction* action = dynamic_cast<QAction*>(QObject::sender());
1983  if (!action)
1984  return;
1985 
1986  if (action == scaleAutoAction)
1987  scaleAuto();
1988  else if (action == scaleAutoXAction)
1989  setAutoScaleX(true);
1990  else if (action == scaleAutoYAction)
1991  setAutoScaleY(true);
1992 }
1993 
1995  Q_D(CartesianPlot);
1996 
1997  if (d->curvesXMinMaxIsDirty) {
1999 
2000  /*
2001  //take the size of the error bar cap into account if error bars with caps are plotted
2002  double errorBarsCapSize = -1;
2003  for (auto* curve : this->children<const XYCurve>()) {
2004  if (curve->yErrorType() == XYCurve::ErrorType::NoError)
2005  continue;
2006 
2007  if (curve->errorBarsType() != XYCurve::ErrorBarsType::WithEnds)
2008  continue;
2009 
2010  if ( (curve->yErrorType() == XYCurve::ErrorType::Symmetric && curve->yErrorPlusColumn())
2011  || (curve->yErrorType() == XYCurve::ErrorType::Asymmetric && (curve->yErrorPlusColumn() && curve->yErrorMinusColumn())) )
2012  errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize());
2013  }
2014 
2015  if (errorBarsCapSize > 0) {
2016  if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY)
2017  d->xMin = d->curvesXMin;
2018 
2019  if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY)
2020  d->xMax = d->curvesXMax;
2021  setIsLoading(true);
2022  d->retransformScales();
2023  setIsLoading(false);
2024  QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMin, 0), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
2025  point.setX(point.x() - errorBarsCapSize);
2026  point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
2027  if (point.x() < d->curvesXMin)
2028  d->curvesXMin = point.x();
2029 
2030  point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMax, 0), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
2031  point.setX(point.x() + errorBarsCapSize);
2032  point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
2033  if (point.x() > d->curvesXMax)
2034  d->curvesXMax = point.x();
2035  }
2036  */
2037  d->curvesXMinMaxIsDirty = false;
2038  }
2039 
2040  if (d->curvesYMinMaxIsDirty) {
2042 
2043  /*
2044  //take the size of the error bar cap into account if error bars with caps are plotted
2045  double errorBarsCapSize = -1;
2046  for (auto* curve : this->children<const XYCurve>()) {
2047  if (curve->xErrorType() == XYCurve::ErrorType::NoError)
2048  continue;
2049 
2050  if (curve->errorBarsType() != XYCurve::ErrorBarsType::WithEnds)
2051  continue;
2052 
2053  if ( (curve->xErrorType() == XYCurve::ErrorType::Symmetric && curve->xErrorPlusColumn())
2054  || (curve->xErrorType() == XYCurve::ErrorType::Asymmetric && (curve->xErrorPlusColumn() && curve->xErrorMinusColumn())) )
2055  errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize());
2056  }
2057 
2058  if (errorBarsCapSize > 0) {
2059  if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY)
2060  d->yMin = d->curvesYMin;
2061 
2062  if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY)
2063  d->yMax = d->curvesYMax;
2064  setIsLoading(true);
2065  d->retransformScales();
2066  setIsLoading(false);
2067  QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMin), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
2068  point.setY(point.y() + errorBarsCapSize);
2069  point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
2070  if (point.y() < d->curvesYMin)
2071  d->curvesYMin = point.y();
2072 
2073  point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMax), AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
2074  point.setY(point.y() - errorBarsCapSize);
2075  point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::MappingFlag::SuppressPageClipping);
2076  if (point.y() > d->curvesYMax)
2077  d->curvesYMax = point.y();
2078  }
2079  */
2080  d->curvesYMinMaxIsDirty = false;
2081  }
2082 
2083  bool updateX = false;
2084  bool updateY = false;
2085  if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) {
2086  d->xMin = d->curvesXMin;
2087  updateX = true;
2088  }
2089 
2090  if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) {
2091  d->xMax = d->curvesXMax;
2092  updateX = true;
2093  }
2094 
2095  if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) {
2096  d->yMin = d->curvesYMin;
2097  updateY = true;
2098  }
2099 
2100  if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) {
2101  d->yMax = d->curvesYMax;
2102  updateY = true;
2103  }
2104  DEBUG(Q_FUNC_INFO << ", xmin/xmax = " << d->xMin << '/' << d->xMax << ", ymin/ymax = " << d->yMin << '/' << d->yMax);
2105 
2106  if (updateX || updateY) {
2107  if (updateX) {
2108  if (d->xMax == d->xMin) {
2109  //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value
2110  if (d->xMax != 0) {
2111  d->xMax = d->xMax*1.1;
2112  d->xMin = d->xMin*0.9;
2113  } else {
2114  d->xMax = 0.1;
2115  d->xMin = -0.1;
2116  }
2117  } else {
2118  double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor;
2119  d->xMin -= offset;
2120  d->xMax += offset;
2121  }
2122  setAutoScaleX(true);
2123  }
2124  if (updateY) {
2125  if (d->yMax == d->yMin) {
2126  //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value
2127  if (d->yMax != 0) {
2128  d->yMax = d->yMax*1.1;
2129  d->yMin = d->yMin*0.9;
2130  } else {
2131  d->yMax = 0.1;
2132  d->yMin = -0.1;
2133  }
2134  } else {
2135  double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor;
2136  d->yMin -= offset;
2137  d->yMax += offset;
2138  }
2139  setAutoScaleY(true);
2140  }
2141  d->retransformScales();
2142  }
2143 
2144  return (updateX || updateY);
2145 }
2146 
2147 /*!
2148  * Calculates and sets curves y min and max. This function does not respect the range
2149  * of the y axis
2150  */
2151 void CartesianPlot::calculateCurvesXMinMax(bool completeRange) {
2152  Q_D(CartesianPlot);
2153 
2154  d->curvesXMin = INFINITY;
2155  d->curvesXMax = -INFINITY;
2156 
2157  //loop over all xy-curves and determine the maximum and minimum x-values
2158  for (const auto* curve : this->children<const XYCurve>()) {
2159  if (!curve->isVisible())
2160  continue;
2161 
2162  auto* xColumn = curve->xColumn();
2163  if (!xColumn)
2164  continue;
2165 
2166  double min = d->curvesXMin;
2167  double max = d->curvesXMax;
2168 
2169  int start =0;
2170  int end = 0;
2171  if (d->rangeType == RangeType::Free && curve->yColumn()
2172  && !completeRange) {
2173  curve->yColumn()->indicesMinMax(yMin(), yMax(), start, end);
2174  if (end < curve->yColumn()->rowCount())
2175  end ++;
2176  } else {
2177  switch (d->rangeType) {
2178  case RangeType::Free:
2179  start = 0;
2180  end = xColumn->rowCount();
2181  break;
2182  case RangeType::Last:
2183  start = xColumn->rowCount() - d->rangeLastValues;
2184  end = xColumn->rowCount();
2185  break;
2186  case RangeType::First:
2187  start = 0;
2188  end = d->rangeFirstValues;
2189  break;
2190  }
2191  }
2192 
2193  curve->minMaxX(start, end, min, max, true);
2194  if (min < d->curvesXMin)
2195  d->curvesXMin = min;
2196 
2197  if (max > d->curvesXMax)
2198  d->curvesXMax = max;
2199  }
2200 
2201  //loop over all histograms and determine the maximum and minimum x-values
2202  for (const auto* curve : this->children<const Histogram>()) {
2203  if (!curve->isVisible())
2204  continue;
2205  if (!curve->dataColumn())
2206  continue;
2207 
2208  const double min = curve->getXMinimum();
2209  if (d->curvesXMin > min)
2210  d->curvesXMin = min;
2211 
2212  const double max = curve->getXMaximum();
2213  if (max > d->curvesXMax)
2214  d->curvesXMax = max;
2215  }
2216 }
2217 
2218 /*!
2219  * Calculates and sets curves y min and max. This function does not respect the range
2220  * of the x axis
2221  */
2222 void CartesianPlot::calculateCurvesYMinMax(bool completeRange) {
2223  Q_D(CartesianPlot);
2224 
2225  d->curvesYMin = INFINITY;
2226  d->curvesYMax = -INFINITY;
2227 
2228  double min = d->curvesYMin;
2229  double max = d->curvesYMax;
2230 
2231 
2232  //loop over all xy-curves and determine the maximum and minimum y-values
2233  for (const auto* curve : this->children<const XYCurve>()) {
2234  if (!curve->isVisible())
2235  continue;
2236 
2237  auto* yColumn = curve->yColumn();
2238  if (!yColumn)
2239  continue;
2240 
2241  int start =0;
2242  int end = 0;
2243  if (d->rangeType == RangeType::Free && curve->xColumn() &&
2244  !completeRange) {
2245  curve->xColumn()->indicesMinMax(xMin(), xMax(), start, end);
2246  if (end < curve->xColumn()->rowCount())
2247  end ++; // because minMaxY excludes indexMax
2248  } else {
2249  switch (d->rangeType) {
2250  case RangeType::Free:
2251  start = 0;
2252  end = yColumn->rowCount();
2253  break;
2254  case RangeType::Last:
2255  start = yColumn->rowCount() - d->rangeLastValues;
2256  end = yColumn->rowCount();
2257  break;
2258  case RangeType::First:
2259  start = 0;
2260  end = d->rangeFirstValues;
2261  break;
2262  }
2263  }
2264 
2265  curve->minMaxY(start, end, min, max, true);
2266 
2267  if (min < d->curvesYMin)
2268  d->curvesYMin = min;
2269 
2270  if (max > d->curvesYMax)
2271  d->curvesYMax = max;
2272  }
2273 
2274  //loop over all histograms and determine the maximum y-value
2275  for (const auto* curve : this->children<const Histogram>()) {
2276  if (!curve->isVisible())
2277  continue;
2278 
2279  const double min = curve->getYMinimum();
2280  if (d->curvesYMin > min)
2281  d->curvesYMin = min;
2282 
2283  const double max = curve->getYMaximum();
2284  if (max > d->curvesYMax)
2285  d->curvesYMax = max;
2286  }
2287 }
2288 
2290  Q_D(CartesianPlot);
2291 
2292  setUndoAware(false);
2293  setAutoScaleX(false);
2294  setAutoScaleY(false);
2295  setUndoAware(true);
2296  d->curvesXMinMaxIsDirty = true;
2297  d->curvesYMinMaxIsDirty = true;
2298  zoom(true, true); //zoom in x
2299  zoom(false, true); //zoom in y
2300  d->retransformScales();
2301 }
2302 
2304  Q_D(CartesianPlot);
2305 
2306  setUndoAware(false);
2307  setAutoScaleX(false);
2308  setAutoScaleY(false);
2309  setUndoAware(true);
2310  d->curvesXMinMaxIsDirty = true;
2311  d->curvesYMinMaxIsDirty = true;
2312  zoom(true, false); //zoom out x
2313  zoom(false, false); //zoom out y
2314  d->retransformScales();
2315 }
2316 
2318  Q_D(CartesianPlot);
2319 
2320  setUndoAware(false);
2321  setAutoScaleX(false);
2322  setUndoAware(true);
2323  d->curvesYMinMaxIsDirty = true;
2324  zoom(true, true); //zoom in x
2325  if (d->autoScaleY && autoScaleY())
2326  return;
2327 
2328  d->retransformScales();
2329 }
2330 
2332  Q_D(CartesianPlot);
2333 
2334  setUndoAware(false);
2335  setAutoScaleX(false);
2336  setUndoAware(true);
2337  d->curvesYMinMaxIsDirty = true;
2338  zoom(true, false); //zoom out x
2339 
2340  if (d->autoScaleY && autoScaleY())
2341  return;
2342 
2343  d->retransformScales();
2344 }
2345 
2347  Q_D(CartesianPlot);
2348 
2349  setUndoAware(false);
2350  setAutoScaleY(false);
2351  setUndoAware(true);
2352  d->curvesYMinMaxIsDirty = true;
2353  zoom(false, true); //zoom in y
2354 
2355  if (d->autoScaleX && autoScaleX())
2356  return;
2357 
2358  d->retransformScales();
2359 }
2360 
2362  Q_D(CartesianPlot);
2363 
2364  setUndoAware(false);
2365  setAutoScaleY(false);
2366  setUndoAware(true);
2367  d->curvesYMinMaxIsDirty = true;
2368  zoom(false, false); //zoom out y
2369 
2370  if (d->autoScaleX && autoScaleX())
2371  return;
2372 
2373  d->retransformScales();
2374 }
2375 
2376 /*!
2377  * helper function called in other zoom*() functions
2378  * and doing the actual change of the data ranges.
2379  * @param x if set to \true the x-range is modified, the y-range for \c false
2380  * @param in the "zoom in" is performed if set to \c \true, "zoom out" for \c false
2381  */
2382 void CartesianPlot::zoom(bool x, bool in) {
2383  Q_D(CartesianPlot);
2384 
2385  double min;
2386  double max;
2387  CartesianPlot::Scale scale;
2388  if (x) {
2389  min = d->xMin;
2390  max = d->xMax;
2391  scale = d->xScale;
2392  } else {
2393  min = d->yMin;
2394  max = d->yMax;
2395  scale = d->yScale;
2396  }
2397 
2398  double factor = m_zoomFactor;
2399  if (in)
2400  factor = 1/factor;
2401 
2402  switch (scale) {
2403  case Scale::Linear: {
2404  double oldRange = max - min;
2405  double newRange = (max - min) * factor;
2406  max = max + (newRange - oldRange) / 2;
2407  min = min - (newRange - oldRange) / 2;
2408  break;
2409  }
2410  case Scale::Log10:
2411  case Scale::Log10Abs: {
2412  double oldRange = log10(max) - log10(min);
2413  double newRange = (log10(max) - log10(min)) * factor;
2414  max = max * pow(10, (newRange - oldRange) / 2.);
2415  min = min / pow(10, (newRange - oldRange) / 2.);
2416  break;
2417  }
2418  case Scale::Log2:
2419  case Scale::Log2Abs: {
2420  double oldRange = log2(max) - log2(min);
2421  double newRange = (log2(max) - log2(min)) * factor;
2422  max = max * pow(2, (newRange - oldRange) / 2.);
2423  min = min / pow(2, (newRange - oldRange) / 2.);
2424  break;
2425  }
2426  case Scale::Ln:
2427  case Scale::LnAbs: {
2428  double oldRange = log(max) - log(min);
2429  double newRange = (log(max) - log(min)) * factor;
2430  max = max * exp((newRange - oldRange) / 2.);
2431  min = min / exp((newRange - oldRange) / 2.);
2432  break;
2433  }
2434  case Scale::Sqrt:
2435  case Scale::X2:
2436  break;
2437  }
2438 
2439  if (!std::isnan(min) && !std::isnan(max) && std::isfinite(min) && std::isfinite(max)) {
2440  if (x) {
2441  d->xMin = min;
2442  d->xMax = max;
2443  } else {
2444  d->yMin = min;
2445  d->yMax = max;
2446  }
2447  }
2448 }
2449 
2450 /*!
2451  * helper function called in other shift*() functions
2452  * and doing the actual change of the data ranges.
2453  * @param x if set to \true the x-range is modified, the y-range for \c false
2454  * @param leftOrDown the "shift left" for x or "shift dows" for y is performed if set to \c \true,
2455  * "shift right" or "shift up" for \c false
2456  */
2457 void CartesianPlot::shift(bool x, bool leftOrDown) {
2458  Q_D(CartesianPlot);
2459 
2460  double min;
2461  double max;
2462  CartesianPlot::Scale scale;
2463  double offset = 0.0;
2464  double factor = 0.1;
2465  if (x) {
2466  min = d->xMin;
2467  max = d->xMax;
2468  scale = d->xScale;
2469  } else {
2470  min = d->yMin;
2471  max = d->yMax;
2472  scale = d->yScale;
2473  }
2474 
2475  if (leftOrDown)
2476  factor *= -1.;
2477 
2478  switch (scale) {
2479  case Scale::Linear: {
2480  offset = (max - min) * factor;
2481  min += offset;
2482  max += offset;
2483  break;
2484  }
2485  case Scale::Log10:
2486  case Scale::Log10Abs: {
2487  offset = (log10(max) - log10(min)) * factor;
2488  min *= pow(10, offset);
2489  max *= pow(10, offset);
2490  break;
2491  }
2492  case Scale::Log2:
2493  case Scale::Log2Abs: {
2494  offset = (log2(max) - log2(min)) * factor;
2495  min *= pow(2, offset);
2496  max *= pow(2, offset);
2497  break;
2498  }
2499  case Scale::Ln:
2500  case Scale::LnAbs: {
2501  offset = (log(max) - log(min)) * factor;
2502  min *= exp(offset);
2503  max *= exp(offset);
2504  break;
2505  }
2506  case Scale::Sqrt:
2507  case Scale::X2:
2508  break;
2509  }
2510 
2511  if (!std::isnan(min) && !std::isnan(max) && std::isfinite(min) && std::isfinite(max)) {
2512  if (x) {
2513  d->xMin = min;
2514  d->xMax = max;
2515  } else {
2516  d->yMin = min;
2517  d->yMax = max;
2518  }
2519  }
2520 }
2521 
2523  Q_D(CartesianPlot);
2524 
2525  setUndoAware(false);
2526  setAutoScaleX(false);
2527  setUndoAware(true);
2528  d->curvesYMinMaxIsDirty = true;
2529  shift(true, true);
2530 
2531  if (d->autoScaleY && scaleAutoY())
2532  return;
2533 
2534  d->retransformScales();
2535 }
2536 
2538  Q_D(CartesianPlot);
2539 
2540  setUndoAware(false);
2541  setAutoScaleX(false);
2542  setUndoAware(true);
2543  d->curvesYMinMaxIsDirty = true;
2544  shift(true, false);
2545 
2546  if (d->autoScaleY && scaleAutoY())
2547  return;
2548 
2549  d->retransformScales();
2550 }
2551 
2553  Q_D(CartesianPlot);
2554 
2555  setUndoAware(false);
2556  setAutoScaleY(false);
2557  setUndoAware(true);
2558  d->curvesXMinMaxIsDirty = true;
2559  shift(false, false);
2560 
2561  if (d->autoScaleX && scaleAutoX())
2562  return;
2563 
2564  d->retransformScales();
2565 }
2566 
2568  Q_D(CartesianPlot);
2569 
2570  setUndoAware(false);
2571  setAutoScaleY(false);
2572  setUndoAware(true);
2573  d->curvesXMinMaxIsDirty = true;
2574  shift(false, true);
2575 
2576  if (d->autoScaleX && scaleAutoX())
2577  return;
2578 
2579  d->retransformScales();
2580 }
2581 
2583  Q_D(CartesianPlot);
2584  d->retransformScales();
2585 }
2586 
2588  Q_D(CartesianPlot);
2589  d->mousePressZoomSelectionMode(logicPos);
2590 }
2591 void CartesianPlot::mousePressCursorMode(int cursorNumber, QPointF logicPos) {
2592  Q_D(CartesianPlot);
2593  d->mousePressCursorMode(cursorNumber, logicPos);
2594 }
2596  Q_D(CartesianPlot);
2597  d->mouseMoveZoomSelectionMode(logicPos);
2598 }
2599 void CartesianPlot::mouseMoveCursorMode(int cursorNumber, QPointF logicPos) {
2600  Q_D(CartesianPlot);
2601  d->mouseMoveCursorMode(cursorNumber, logicPos);
2602 }
2603 
2605  Q_D(CartesianPlot);
2606  d->mouseReleaseZoomSelectionMode();
2607 }
2608 
2610  Q_D(CartesianPlot);
2611  d->mouseHoverZoomSelectionMode(logicPos);
2612 }
2613 
2615  Q_D(CartesianPlot);
2616  d->mouseHoverOutsideDataRect();
2617 }
2618 
2619 //##############################################################################
2620 //###### SLOTs for changes triggered via QActions in the context menu ########
2621 //##############################################################################
2623  Q_D(CartesianPlot);
2624  this->setVisible(!d->isVisible());
2625 }
2626 
2627 //#####################################################################
2628 //################### Private implementation ##########################
2629 //#####################################################################
2631  setData(0, static_cast<int>(WorksheetElement::WorksheetElementName::NameCartesianPlot));
2632  m_cursor0Text.prepare();
2633  m_cursor1Text.prepare();
2634 }
2635 
2636 /*!
2637  updates the position of plot rectangular in scene coordinates to \c r and recalculates the scales.
2638  The size of the plot corresponds to the size of the plot area, the area which is filled with the background color etc.
2639  and which can pose the parent item for several sub-items (like TextLabel).
2640  Note, the size of the area used to define the coordinate system doesn't need to be equal to this plot area.
2641  Also, the size (=bounding box) of CartesianPlot can be greater than the size of the plot area.
2642  */
2644  if (suppressRetransform)
2645  return;
2646 
2647  PERFTRACE("CartesianPlotPrivate::retransform()");
2648  prepareGeometryChange();
2649  setPos(rect.x() + rect.width()/2, rect.y() + rect.height()/2);
2650 
2651  updateDataRect();
2653 
2654  //plotArea position is always (0, 0) in parent's coordinates, don't need to update here
2655  q->plotArea()->setRect(rect);
2656 
2657  //call retransform() for the title and the legend (if available)
2658  //when a predefined position relative to the (Left, Centered etc.) is used,
2659  //the actual position needs to be updated on plot's geometry changes.
2660  if (q->title())
2661  q->title()->retransform();
2662  if (q->m_legend)
2663  q->m_legend->retransform();
2664 
2666 }
2667 
2669  DEBUG(Q_FUNC_INFO << ", xmin/xmax = " << xMin << '/'<< xMax << ", ymin/ymax = " << yMin << '/' << yMax);
2670  PERFTRACE("CartesianPlotPrivate::retransformScales()");
2671 
2672  QVector<CartesianScale*> scales;
2673 
2674  //check ranges for log-scales
2676  checkXRange();
2677 
2678  //check whether we have x-range breaks - the first break, if available, should be valid
2679  bool hasValidBreak = (xRangeBreakingEnabled && !xRangeBreaks.list.isEmpty() && xRangeBreaks.list.first().isValid());
2680 
2681  static const int breakGap = 20;
2682  double sceneStart, sceneEnd, logicalStart, logicalEnd;
2683 
2684  //create x-scales
2685  int plotSceneStart = dataRect.x();
2686  int plotSceneEnd = dataRect.x() + dataRect.width();
2687  if (!hasValidBreak) {
2688  //no breaks available -> range goes from the plot beginning to the end of the plot
2689  sceneStart = plotSceneStart;
2690  sceneEnd = plotSceneEnd;
2691  logicalStart = xMin;
2692  logicalEnd = xMax;
2693 
2694  //TODO: how should we handle the case sceneStart == sceneEnd?
2695  //(to reproduce, create plots and adjust the spacing/pading to get zero size for the plots)
2696  if (sceneStart != sceneEnd)
2697  scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd);
2698  } else {
2699  int sceneEndLast = plotSceneStart;
2700  int logicalEndLast = xMin;
2701  for (const auto& rb : xRangeBreaks.list) {
2702  if (!rb.isValid())
2703  break;
2704 
2705  //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start
2706  sceneStart = sceneEndLast;
2707  if (&rb == &xRangeBreaks.list.first()) sceneStart += breakGap;
2708  sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position;
2709  logicalStart = logicalEndLast;
2710  logicalEnd = rb.start;
2711 
2712  if (sceneStart != sceneEnd)
2713  scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd);
2714 
2715  sceneEndLast = sceneEnd;
2716  logicalEndLast = rb.end;
2717  }
2718 
2719  //add the remaining range going from the last available range break to the end of the plot (=end of the x-data range)
2720  sceneStart = sceneEndLast+breakGap;
2721  sceneEnd = plotSceneEnd;
2722  logicalStart = logicalEndLast;
2723  logicalEnd = xMax;
2724 
2725  if (sceneStart != sceneEnd)
2726  scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd);
2727  }
2728 
2729  cSystem->setXScales(scales);
2730 
2731  //check ranges for log-scales
2733  checkYRange();
2734 
2735  //check whether we have y-range breaks - the first break, if available, should be valid
2736  hasValidBreak = (yRangeBreakingEnabled && !yRangeBreaks.list.isEmpty() && yRangeBreaks.list.first().isValid());
2737 
2738  //create y-scales
2739  scales.clear();
2740  plotSceneStart = dataRect.y() + dataRect.height();
2741  plotSceneEnd = dataRect.y();
2742  if (!hasValidBreak) {
2743  //no breaks available -> range goes from the plot beginning to the end of the plot
2744  sceneStart = plotSceneStart;
2745  sceneEnd = plotSceneEnd;
2746  logicalStart = yMin;
2747  logicalEnd = yMax;
2748 
2749  if (sceneStart != sceneEnd)
2750  scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd);
2751  } else {
2752  int sceneEndLast = plotSceneStart;
2753  int logicalEndLast = yMin;
2754  for (const auto& rb : yRangeBreaks.list) {
2755  if (!rb.isValid())
2756  break;
2757 
2758  //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start
2759  sceneStart = sceneEndLast;
2760  if (&rb == &yRangeBreaks.list.first()) sceneStart -= breakGap;
2761  sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position;
2762  logicalStart = logicalEndLast;
2763  logicalEnd = rb.start;
2764 
2765  if (sceneStart != sceneEnd)
2766  scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd);
2767 
2768  sceneEndLast = sceneEnd;
2769  logicalEndLast = rb.end;
2770  }
2771 
2772  //add the remaining range going from the last available range break to the end of the plot (=end of the y-data range)
2773  sceneStart = sceneEndLast-breakGap;
2774  sceneEnd = plotSceneEnd;
2775  logicalStart = logicalEndLast;
2776  logicalEnd = yMax;
2777 
2778  if (sceneStart != sceneEnd)
2779  scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd);
2780  }
2781 
2782  cSystem->setYScales(scales);
2783 
2784  //calculate the changes in x and y and save the current values for xMin, xMax, yMin, yMax
2785  double deltaXMin = 0;
2786  double deltaXMax = 0;
2787  double deltaYMin = 0;
2788  double deltaYMax = 0;
2789 
2790  if (xMin != xMinPrev) {
2791  deltaXMin = xMin - xMinPrev;
2792  emit q->xMinChanged(xMin);
2793  }
2794 
2795  if (xMax != xMaxPrev) {
2796  deltaXMax = xMax - xMaxPrev;
2797  emit q->xMaxChanged(xMax);
2798  }
2799 
2800  if (yMin != yMinPrev) {
2801  deltaYMin = yMin - yMinPrev;
2802  emit q->yMinChanged(yMin);
2803  }
2804 
2805  if (yMax != yMaxPrev) {
2806  deltaYMax = yMax - yMaxPrev;
2807  emit q->yMaxChanged(yMax);
2808  }
2809 
2810  xMinPrev = xMin;
2811  xMaxPrev = xMax;
2812  yMinPrev = yMin;
2813  yMaxPrev = yMax;
2814  //adjust auto-scale axes
2815  for (auto* axis : q->children<Axis>()) {
2816  if (!axis->autoScale())
2817  continue;
2818 
2819  if (axis->orientation() == Axis::Orientation::Horizontal) {
2820  if (deltaXMax != 0) {
2821  axis->setUndoAware(false);
2822  axis->setSuppressRetransform(true);
2823  axis->setEnd(xMax);
2824  axis->setUndoAware(true);
2825  axis->setSuppressRetransform(false);
2826  }
2827  if (deltaXMin != 0) {
2828  axis->setUndoAware(false);
2829  axis->setSuppressRetransform(true);
2830  axis->setStart(xMin);
2831  axis->setUndoAware(true);
2832  axis->setSuppressRetransform(false);
2833  }
2834  //TODO;
2835 // if (axis->position() == Axis::Position::Custom && deltaYMin != 0) {
2836 // axis->setOffset(axis->offset() + deltaYMin, false);
2837 // }
2838  } else {
2839  if (deltaYMax != 0) {
2840  axis->setUndoAware(false);
2841  axis->setSuppressRetransform(true);
2842  axis->setEnd(yMax);
2843  axis->setUndoAware(true);
2844  axis->setSuppressRetransform(false);
2845  }
2846  if (deltaYMin != 0) {
2847  axis->setUndoAware(false);
2848  axis->setSuppressRetransform(true);
2849  axis->setStart(yMin);
2850  axis->setUndoAware(true);
2851  axis->setSuppressRetransform(false);
2852  }
2853 
2854  //TODO;
2855 // if (axis->position() == Axis::Position::Custom && deltaXMin != 0) {
2856 // axis->setOffset(axis->offset() + deltaXMin, false);
2857 // }
2858  }
2859  }
2860  // call retransform() on the parent to trigger the update of all axes and curves.
2861  //no need to do this on load since all plots are retransformed again after the project is loaded.
2862  if (!q->isLoading())
2863  q->retransform();
2864 }
2865 
2866 /*
2867  * calculates the rectangular of the are showing the actual data (plot's rect minus padding),
2868  * in plot's coordinates.
2869  */
2871  dataRect = mapRectFromScene(rect);
2872 
2873  double paddingLeft = horizontalPadding;
2874  double paddingRight = rightPadding;
2875  double paddingTop = verticalPadding;
2876  double paddingBottom = bottomPadding;
2877  if (symmetricPadding) {
2878  paddingRight = horizontalPadding;
2879  paddingBottom = verticalPadding;
2880  }
2881 
2882  dataRect.setX(dataRect.x() + paddingLeft);
2883  dataRect.setY(dataRect.y() + paddingTop);
2884 
2885  double newHeight = dataRect.height() - paddingBottom;
2886  if (newHeight < 0)
2887  newHeight = 0;
2888  dataRect.setHeight(newHeight);
2889 
2890  double newWidth = dataRect.width() - paddingRight;
2891  if (newWidth < 0)
2892  newWidth = 0;
2893  dataRect.setWidth(newWidth);
2894 }
2895 
2897  curvesXMinMaxIsDirty = true;
2898  curvesYMinMaxIsDirty = true;
2899  if (autoScaleX && autoScaleY)
2900  q->scaleAuto();
2901  else if (autoScaleX)
2902  q->scaleAutoX();
2903  else if (autoScaleY)
2904  q->scaleAutoY();
2905 }
2906 
2908  for (auto* axis : q->children<Axis>()) {
2909  if (axis->orientation() == Axis::Orientation::Horizontal)
2910  axis->retransformTickLabelStrings();
2911  }
2912 }
2913 
2915  for (auto* axis : q->children<Axis>()) {
2916  if (axis->orientation() == Axis::Orientation::Vertical)
2917  axis->retransformTickLabelStrings();
2918  }
2919 }
2920 
2921 /*!
2922  * don't allow any negative values for the x range when log or sqrt scalings are used
2923  */
2925  double min = 0.01;
2926 
2927  if (xMin <= 0.0) {
2928  (min < xMax*min) ? xMin = min : xMin = xMax*min;
2929  emit q->xMinChanged(xMin);
2930  } else if (xMax <= 0.0) {
2931  (-min > xMin*min) ? xMax = -min : xMax = xMin*min;
2932  emit q->xMaxChanged(xMax);
2933  }
2934 }
2935 
2936 /*!
2937  * don't allow any negative values for the y range when log or sqrt scalings are used
2938  */
2940  double min = 0.01;
2941 
2942  if (yMin <= 0.0) {
2943  (min < yMax*min) ? yMin = min : yMin = yMax*min;
2944  emit q->yMinChanged(yMin);
2945  } else if (yMax <= 0.0) {
2946  (-min > yMin*min) ? yMax = -min : yMax = yMin*min;
2947  emit q->yMaxChanged(yMax);
2948  }
2949 }
2950 
2951 CartesianScale* CartesianPlotPrivate::createScale(CartesianPlot::Scale type, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd) {
2952  DEBUG(Q_FUNC_INFO << ", scene start/end = " << sceneStart << '/' << sceneEnd << ", logical start/end = " << logicalStart << '/' << logicalEnd);
2953 // Interval<double> interval (logicalStart-0.01, logicalEnd+0.01); //TODO: move this to CartesianScale
2954  Interval<double> interval (std::numeric_limits<double>::lowest(), std::numeric_limits<double>::max());
2955 // Interval<double> interval (logicalStart, logicalEnd);
2957  return CartesianScale::createLinearScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd);
2958  else
2959  return CartesianScale::createLogScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd, type);
2960 }
2961 
2962 /*!
2963  * Reimplemented from QGraphicsItem.
2964  */
2965 QVariant CartesianPlotPrivate::itemChange(GraphicsItemChange change, const QVariant &value) {
2966  if (change == QGraphicsItem::ItemPositionChange) {
2967  const QPointF& itemPos = value.toPointF();//item's center point in parent's coordinates;
2968  const qreal x = itemPos.x();
2969  const qreal y = itemPos.y();
2970 
2971  //calculate the new rect and forward the changes to the frontend
2972  QRectF newRect;
2973  const qreal w = rect.width();
2974  const qreal h = rect.height();
2975  newRect.setX(x-w/2);
2976  newRect.setY(y-h/2);
2977  newRect.setWidth(w);
2978  newRect.setHeight(h);
2979  emit q->rectChanged(newRect);
2980  }
2981  return QGraphicsItem::itemChange(change, value);
2982 }
2983 
2984 //##############################################################################
2985 //################################## Events ##################################
2986 //##############################################################################
2987 
2988 /*!
2989  * \brief CartesianPlotPrivate::mousePressEvent
2990  * In this function only basic stuff is done. The mousePressEvent is forwarded to the Worksheet, which
2991  * has access to all cartesian plots and can apply the changes to all plots if the option "applyToAll"
2992  * is set. The worksheet calls then the corresponding mousepressZoomMode/CursorMode function in this class
2993  * This is done for mousePress, mouseMove and mouseRelease event
2994  * This function sends a signal with the logical position, because this is the only value which is the same
2995  * in all plots. Using the scene coordinates is not possible
2996  * \param event
2997  */
2998 void CartesianPlotPrivate::mousePressEvent(QGraphicsSceneMouseEvent *event) {
2999 
3003  setCursor(Qt::SizeHorCursor);
3004  QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit);
3005  double cursorPenWidth2 = cursorPen.width()/2.;
3006  if (cursorPenWidth2 < 10.)
3007  cursorPenWidth2 = 10.;
3008  if (cursor0Enable && qAbs(event->pos().x()-cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)).x()) < cursorPenWidth2) {
3009  selectedCursor = 0;
3010  } else if (cursor1Enable && qAbs(event->pos().x()-cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)).x()) < cursorPenWidth2) {
3011  selectedCursor = 1;
3012  } else if (QApplication::keyboardModifiers() & Qt::ControlModifier){
3013  cursor1Enable = true;
3014  selectedCursor = 1;
3016  } else {
3017  cursor0Enable = true;
3018  selectedCursor = 0;
3020  }
3021  emit q->mousePressCursorModeSignal(selectedCursor, logicalPos);
3022 
3023  } else {
3024  if (!locked && dataRect.contains(event->pos())) {
3025  panningStarted = true;
3026  m_panningStart = event->pos();
3027  setCursor(Qt::ClosedHandCursor);
3028  }
3029  }
3030  QGraphicsItem::mousePressEvent(event);
3031 }
3032 
3035 
3036  if (logicalPos.x() < xMin)
3037  logicalPos.setX(xMin);
3038 
3039  if (logicalPos.x() > xMax)
3040  logicalPos.setX(xMax);
3041 
3042  if (logicalPos.y() < yMin)
3043  logicalPos.setY(yMin);
3044 
3045  if (logicalPos.y() > yMax)
3046  logicalPos.setY(yMax);
3047 
3049 
3051  logicalPos.setY(yMin); // must be done, because the other plots can have other ranges, value must be in the scenes
3053  m_selectionStart.setY(dataRect.y());
3055  logicalPos.setX(xMin); // must be done, because the other plots can have other ranges, value must be in the scenes
3056  m_selectionStart.setX(dataRect.x());
3058  }
3060  m_selectionBandIsShown = true;
3061 }
3062 
3063 void CartesianPlotPrivate::mousePressCursorMode(int cursorNumber, QPointF logicalPos) {
3064 
3065  cursorNumber == 0 ? cursor0Enable = true : cursor1Enable = true;
3066 
3067  QPointF p1(logicalPos.x(), yMin);
3068  QPointF p2(logicalPos.x(), yMax);
3069 
3070  if (cursorNumber == 0) {
3071  cursor0Pos.setX(logicalPos.x());
3072  cursor0Pos.setY(0);
3073  } else {
3074  cursor1Pos.setX(logicalPos.x());
3075  cursor1Pos.setY(0);
3076  }
3077  update();
3078 }
3079 
3081  update();
3082 }
3083 
3085  m_selectionBandIsShown = show;
3086 }
3087 
3088 void CartesianPlotPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
3090  if (panningStarted && dataRect.contains(event->pos()) ) {
3091  //don't retransform on small mouse movement deltas
3092  const int deltaXScene = (m_panningStart.x() - event->pos().x());
3093  const int deltaYScene = (m_panningStart.y() - event->pos().y());
3094  if (abs(deltaXScene) < 5 && abs(deltaYScene) < 5)
3095  return;
3096 
3097  const QPointF logicalEnd = cSystem->mapSceneToLogical(event->pos());
3098  const QPointF logicalStart = cSystem->mapSceneToLogical(m_panningStart);
3099 
3100  //handle the change in x
3101  switch (xScale) {
3103  const float deltaX = (logicalStart.x() - logicalEnd.x());
3104  xMax += deltaX;
3105  xMin += deltaX;
3106  break;
3107  }
3110  const float deltaX = log10(logicalStart.x()) - log10(logicalEnd.x());
3111  xMin *= pow(10, deltaX);
3112  xMax *= pow(10, deltaX);
3113  break;
3114  }
3117  const float deltaX = log2(logicalStart.x()) - log2(logicalEnd.x());
3118  xMin *= pow(2, deltaX);
3119  xMax *= pow(2, deltaX);
3120  break;
3121  }
3124  const float deltaX = log(logicalStart.x()) - log(logicalEnd.x());
3125  xMin *= exp(deltaX);
3126  xMax *= exp(deltaX);
3127  break;
3128  }
3131  break;
3132  }
3133 
3134  //handle the change in y
3135  switch (yScale) {
3137  const float deltaY = (logicalStart.y() - logicalEnd.y());
3138  yMax += deltaY;
3139  yMin += deltaY;
3140  break;
3141  }
3144  const float deltaY = log10(logicalStart.y()) - log10(logicalEnd.y());
3145  yMin *= pow(10, deltaY);
3146  yMax *= pow(10, deltaY);
3147  break;
3148  }
3151  const float deltaY = log2(logicalStart.y()) - log2(logicalEnd.y());
3152  yMin *= pow(2, deltaY);
3153  yMax *= pow(2, deltaY);
3154  break;
3155  }
3158  const float deltaY = log(logicalStart.y()) - log(logicalEnd.y());
3159  yMin *= exp(deltaY);
3160  yMax *= exp(deltaY);
3161  break;
3162  }
3165  break;
3166  }
3167 
3168  q->setUndoAware(false);
3169  q->setAutoScaleX(false);
3170  q->setAutoScaleY(false);
3171  q->setUndoAware(true);
3172 
3174  m_panningStart = event->pos();
3175  } else
3176  QGraphicsItem::mouseMoveEvent(event);
3178  QGraphicsItem::mouseMoveEvent(event);
3179  if ( !boundingRect().contains(event->pos()) ) {
3180  q->info(QString());
3181  return;
3182  }
3184 
3186  QGraphicsItem::mouseMoveEvent(event);
3187  if (!boundingRect().contains(event->pos())) {
3188  q->info(i18n("Not inside of the bounding rect"));
3189  return;
3190  }
3191  QPointF logicalPos = cSystem->mapSceneToLogical(event->pos(), AbstractCoordinateSystem::MappingFlag::Limit);
3192 
3193  // updating treeview data and cursor position
3194  // updatign cursor position is done in Worksheet, because
3195  // multiple plots must be updated
3196  emit q->mouseMoveCursorModeSignal(selectedCursor, logicalPos);
3197  }
3198 }
3199 
3201  QString info;
3205  QPointF logicalEnd = logicalPos;
3207  info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x());
3208  else
3209  info = i18n("from x=%1 to x=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.x()).toString(xRangeDateTimeFormat),
3210  QDateTime::fromMSecsSinceEpoch(logicalEnd.x()).toString(xRangeDateTimeFormat));
3211 
3212  info += QLatin1String(", ");
3214  info += QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y());
3215  else
3216  info += i18n("from y=%1 to y=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.y()).toString(xRangeDateTimeFormat),
3217  QDateTime::fromMSecsSinceEpoch(logicalEnd.y()).toString(xRangeDateTimeFormat));
3219  logicalPos.setY(yMin); // must be done, because the other plots can have other ranges, value must be in the scenes
3221  m_selectionEnd.setY(dataRect.bottom());
3222  QPointF logicalEnd = logicalPos;
3224  info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x());
3225  else
3226  info = i18n("from x=%1 to x=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.x()).toString(xRangeDateTimeFormat),
3227  QDateTime::fromMSecsSinceEpoch(logicalEnd.x()).toString(xRangeDateTimeFormat));
3229  m_selectionEnd.setX(dataRect.right());
3230  logicalPos.setX(xMin); // must be done, because the other plots can have other ranges, value must be in the scenes
3232  QPointF logicalEnd = logicalPos;
3234  info = QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y());
3235  else
3236  info = i18n("from y=%1 to y=%2", QDateTime::fromMSecsSinceEpoch(logicalStart.y()).toString(xRangeDateTimeFormat),
3237  QDateTime::fromMSecsSinceEpoch(logicalEnd.y()).toString(xRangeDateTimeFormat));
3238  }
3239  q->info(info);
3240  update();
3241 }
3242 
3243 void CartesianPlotPrivate::mouseMoveCursorMode(int cursorNumber, QPointF logicalPos) {
3244 
3245  QPointF p1(logicalPos.x(), 0);
3246  cursorNumber == 0 ? cursor0Pos = p1 : cursor1Pos = p1;
3247 
3248  QString info;
3250  info = QString::fromUtf8("x=") + QString::number(logicalPos.x());
3251  else
3252  info = i18n("x=%1", QDateTime::fromMSecsSinceEpoch(logicalPos.x()).toString(xRangeDateTimeFormat));
3253  q->info(info);
3254  update();
3255 }
3256 
3257 void CartesianPlotPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) {
3258  setCursor(Qt::ArrowCursor);
3260  panningStarted = false;
3261 
3262  //TODO: why do we do this all the time?!?!
3263  const QPointF& itemPos = pos();//item's center point in parent's coordinates;
3264  const qreal x = itemPos.x();
3265  const qreal y = itemPos.y();
3266 
3267  //calculate the new rect and set it
3268  QRectF newRect;
3269  const qreal w = rect.width();
3270  const qreal h = rect.height();
3271  newRect.setX(x-w/2);
3272  newRect.setY(y-h/2);
3273  newRect.setWidth(w);
3274  newRect.setHeight(h);
3275 
3276  suppressRetransform = true;
3277  q->setRect(newRect);
3278  suppressRetransform = false;
3279 
3280  QGraphicsItem::mouseReleaseEvent(event);
3283  }
3284 }
3285 
3287  //don't zoom if very small region was selected, avoid occasional/unwanted zooming
3288  if ( qAbs(m_selectionEnd.x()-m_selectionStart.x()) < 20 || qAbs(m_selectionEnd.y()-m_selectionStart.y()) < 20 ) {
3289  m_selectionBandIsShown = false;
3290  return;
3291  }
3292  bool retransformPlot = true;
3293 
3294  //determine the new plot ranges
3297  if (m_selectionEnd.x() > m_selectionStart.x()) {
3298  xMin = logicalZoomStart.x();
3299  xMax = logicalZoomEnd.x();
3300  } else {
3301  xMin = logicalZoomEnd.x();
3302  xMax = logicalZoomStart.x();
3303  }
3304 
3305  if (m_selectionEnd.y() > m_selectionStart.y()) {
3306  yMin = logicalZoomEnd.y();
3307  yMax = logicalZoomStart.y();
3308  } else {
3309  yMin = logicalZoomStart.y();
3310  yMax = logicalZoomEnd.y();
3311  }
3312 
3314  curvesXMinMaxIsDirty = true;
3315  curvesYMinMaxIsDirty = true;
3316  q->setAutoScaleX(false);
3317  q->setAutoScaleY(false);
3319  curvesYMinMaxIsDirty = true;
3320  q->setAutoScaleX(false);
3321  if (q->autoScaleY() && q->scaleAutoY())
3322  retransformPlot = false;
3324  curvesXMinMaxIsDirty = true;
3325  q->setAutoScaleY(false);
3326  if (q->autoScaleX() && q->scaleAutoX())
3327  retransformPlot = false;
3328  }
3329 
3330  if (retransformPlot)
3332 
3333  m_selectionBandIsShown = false;
3334 }
3335 
3336 void CartesianPlotPrivate::wheelEvent(QGraphicsSceneWheelEvent* event) {
3337  if (locked)
3338  return;
3339 
3340  //determine first, which axes are selected and zoom only in the corresponding direction.
3341  //zoom the entire plot if no axes selected.
3342  bool zoomX = false;
3343  bool zoomY = false;
3344  for (auto* axis : q->children<Axis>()) {
3345  if (!axis->graphicsItem()->isSelected() && !axis->isHovered())
3346  continue;
3347 
3348  if (axis->orientation() == Axis::Orientation::Horizontal)
3349  zoomX = true;
3350  else
3351  zoomY = true;
3352  }
3353 
3354  if (event->delta() > 0) {
3355  if (!zoomX && !zoomY) {
3356  //no special axis selected -> zoom in everything
3357  q->zoomIn();
3358  } else {
3359  if (zoomX) q->zoomInX();
3360  if (zoomY) q->zoomInY();
3361  }
3362  } else {
3363  if (!zoomX && !zoomY) {
3364  //no special axis selected -> zoom in everything
3365  q->zoomOut();
3366  } else {
3367  if (zoomX) q->zoomOutX();
3368  if (zoomY) q->zoomOutY();
3369  }
3370  }
3371 }
3372 
3373 void CartesianPlotPrivate::keyPressEvent(QKeyEvent* event) {
3374  if (event->key() == Qt::Key_Escape) {
3375  setCursor(Qt::ArrowCursor);
3377  m_selectionBandIsShown = false;
3378  } else if (event->key() == Qt::Key_Left || event->key() == Qt::Key_Right
3379  || event->key() == Qt::Key_Up ||event->key() == Qt::Key_Down) {
3380 
3381  const auto* worksheet = static_cast<const Worksheet*>(q->parentAspect());
3382  if (worksheet->layout() == Worksheet::Layout::NoLayout) {
3383  const int delta = 5;
3384  QRectF rect = q->rect();
3385 
3386  if (event->key() == Qt::Key_Left) {
3387  rect.setX(rect.x() - delta);
3388  rect.setWidth(rect.width() - delta);
3389  } else if (event->key() == Qt::Key_Right) {
3390  rect.setX(rect.x() + delta);
3391  rect.setWidth(rect.width() + delta);
3392  } else if (event->key() == Qt::Key_Up) {
3393  rect.setY(rect.y() - delta);
3394  rect.setHeight(rect.height() - delta);
3395  } else if (event->key() == Qt::Key_Down) {
3396  rect.setY(rect.y() + delta);
3397  rect.setHeight(rect.height() + delta);
3398  }
3399 
3400  q->setRect(rect);
3401  }
3402 
3403  }
3404  QGraphicsItem::keyPressEvent(event);
3405 }
3406 
3407 void CartesianPlotPrivate::hoverMoveEvent(QGraphicsSceneHoverEvent* event) {
3408  QPointF point = event->pos();
3409  QString info;
3410  if (dataRect.contains(point)) {
3411  QPointF logicalPoint = cSystem->mapSceneToLogical(point);
3412 
3415  info = "x=";
3417  info += QString::number(logicalPoint.x());
3418  else
3419  info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat);
3420 
3421  info += ", y=";
3423  info += QString::number(logicalPoint.y());
3424  else
3425  info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat);
3426  }
3427 
3429  emit q->mouseHoverZoomSelectionModeSignal(logicalPoint);
3431  info = "x=";
3433  info += QString::number(logicalPoint.x());
3434  else
3435  info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat);
3436  emit q->mouseHoverZoomSelectionModeSignal(logicalPoint);
3438  info = "y=";
3440  info += QString::number(logicalPoint.y());
3441  else
3442  info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat);
3443  emit q->mouseHoverZoomSelectionModeSignal(logicalPoint);
3445  // hover the nearest curve to the mousepointer
3446  // hovering curves is implemented in the parent, because no ignoreEvent() exists
3447  // for it. Checking all curves and hover the first
3448  bool curve_hovered = false;
3449  const auto& curves = q->children<Curve>();
3450  for (int i=curves.count() - 1; i >= 0; i--){ // because the last curve is above the other curves
3451  if (curve_hovered){ // if a curve is already hovered, disable hover for the rest
3452  curves[i]->setHover(false);
3453  continue;
3454  }
3455  if (curves[i]->activateCurve(event->pos())) {
3456  curves[i]->setHover(true);
3457  curve_hovered = true;
3458  continue;
3459  }
3460  curves[i]->setHover(false);
3461  }
3463  info = "x=";
3465  info += QString::number(logicalPoint.x());
3466  else
3467  info += QDateTime::fromMSecsSinceEpoch(logicalPoint.x()).toString(xRangeDateTimeFormat);
3468 
3469  double cursorPenWidth2 = cursorPen.width()/2.;
3470  if (cursorPenWidth2 < 10.)
3471  cursorPenWidth2 = 10.;
3472  if ((cursor0Enable && qAbs(point.x()-cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin)).x()) < cursorPenWidth2) ||
3473  (cursor1Enable && qAbs(point.x()-cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin)).x()) < cursorPenWidth2))
3474  setCursor(Qt::SizeHorCursor);
3475  else
3476  setCursor(Qt::ArrowCursor);
3477 
3478  update();
3479  }
3480  } else
3482 
3483  q->info(info);
3484 
3485  QGraphicsItem::hoverMoveEvent(event);
3486 }
3487 
3489  m_insideDataRect = false;
3490  update();
3491 }
3492 
3493 void CartesianPlotPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent* event) {
3494  QVector<XYCurve*> curves = q->children<XYCurve>();
3495  for (auto* curve : curves)
3496  curve->setHover(false);
3497 
3498  m_hovered = false;
3499  QGraphicsItem::hoverLeaveEvent(event);
3500 }
3501 
3503  m_insideDataRect = true;
3504 
3506 
3508  QPointF p1(logicPos.x(), yMin);
3509  QPointF p2(logicPos.x(), yMax);
3513  QPointF p1(xMin, logicPos.y());
3514  QPointF p2(xMax, logicPos.y());
3517  }
3518  update(); // because if previous another selection mode was selected, the lines must be deleted
3519 }
3520 
3521 void CartesianPlotPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
3522  Q_UNUSED(option)
3523  Q_UNUSED(widget)
3524 
3525  if (!isVisible())
3526  return;
3527 
3528  if (!m_printing) {
3529  painter->save();
3530 
3531  painter->setPen(cursorPen);
3532  QFont font = painter->font();
3533  font.setPointSize(font.pointSize() * 4);
3534  painter->setFont(font);
3535 
3536  QPointF p1 = cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMin));
3537  if (cursor0Enable && p1 != QPointF(0,0)){
3538  QPointF p2 = cSystem->mapLogicalToScene(QPointF(cursor0Pos.x(),yMax));
3539  painter->drawLine(p1,p2);
3540  QPointF textPos = p2;
3541  textPos.setX(p2.x() - m_cursor0Text.size().width()/2);
3542  textPos.setY(p2.y() - m_cursor0Text.size().height());
3543  if (textPos.y() < boundingRect().y())
3544  textPos.setY(boundingRect().y());
3545  painter->drawStaticText(textPos, m_cursor0Text);
3546  }
3547 
3548  p1 = cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMin));
3549  if (cursor1Enable && p1 != QPointF(0,0)){
3550  QPointF p2 = cSystem->mapLogicalToScene(QPointF(cursor1Pos.x(),yMax));
3551  painter->drawLine(p1,p2);
3552  QPointF textPos = p2;
3553  // TODO: Moving this stuff into other function to not calculate it every time
3554  textPos.setX(p2.x() - m_cursor1Text.size().width()/2);
3555  textPos.setY(p2.y() - m_cursor1Text.size().height());
3556  if (textPos.y() < boundingRect().y())
3557  textPos.setY(boundingRect().y());
3558  painter->drawStaticText(textPos, m_cursor1Text);
3559  }
3560 
3561  painter->restore();
3562  }
3563 
3564  painter->setPen(QPen(Qt::black, 3));
3567  painter->drawLine(m_selectionStartLine);
3568 
3569  if (m_selectionBandIsShown) {
3570  QPointF selectionStart = m_selectionStart;
3571  if (m_selectionStart.x() > dataRect.right())
3572  selectionStart.setX(dataRect.right());
3573  if (m_selectionStart.x() < dataRect.left())
3574  selectionStart.setX(dataRect.left());
3575  if (m_selectionStart.y() > dataRect.bottom())
3576  selectionStart.setY(dataRect.bottom());
3577  if (m_selectionStart.y() < dataRect.top())
3578  selectionStart.setY(dataRect.top());
3579 
3580  QPointF selectionEnd = m_selectionEnd;
3581  if (m_selectionEnd.x() > dataRect.right())
3582  selectionEnd.setX(dataRect.right());
3583  if (m_selectionEnd.x() < dataRect.left())
3584  selectionEnd.setX(dataRect.left());
3585  if (m_selectionEnd.y() > dataRect.bottom())
3586  selectionEnd.setY(dataRect.bottom());
3587  if (m_selectionEnd.y() < dataRect.top())
3588  selectionEnd.setY(dataRect.top());
3589  painter->save();
3590  painter->setPen(QPen(Qt::black, 5));
3591  painter->drawRect(QRectF(selectionStart, selectionEnd));
3592  painter->setBrush(Qt::blue);
3593  painter->setOpacity(0.2);
3594  painter->drawRect(QRectF(selectionStart, selectionEnd));
3595  painter->restore();
3596  }
3597 }
3598 
3599 //##############################################################################
3600 //################## Serialization/Deserialization ###########################
3601 //##############################################################################
3602 
3603 //! Save as XML
3604 void CartesianPlot::save(QXmlStreamWriter* writer) const {
3605  Q_D(const CartesianPlot);
3606 
3607  writer->writeStartElement( "cartesianPlot" );
3608  writeBasicAttributes(writer);
3609  writeCommentElement(writer);
3610 
3611  //applied theme
3612  if (!d->theme.isEmpty()) {
3613  writer->writeStartElement( "theme" );
3614  writer->writeAttribute("name", d->theme);
3615  writer->writeEndElement();
3616  }
3617 
3618  //cursor
3619  writer->writeStartElement( "cursor" );
3620  WRITE_QPEN(d->cursorPen);
3621  writer->writeEndElement();
3622  //geometry
3623  writer->writeStartElement( "geometry" );
3624  writer->writeAttribute( "x", QString::number(d->rect.x()) );
3625  writer->writeAttribute( "y", QString::number(d->rect.y()) );
3626  writer->writeAttribute( "width", QString::number(d->rect.width()) );
3627  writer->writeAttribute( "height", QString::number(d->rect.height()) );
3628  writer->writeAttribute( "visible", QString::number(d->isVisible()) );
3629  writer->writeEndElement();
3630 
3631  //coordinate system and padding
3632  writer->writeStartElement( "coordinateSystem" );
3633  writer->writeAttribute( "autoScaleX", QString::number(d->autoScaleX) );
3634  writer->writeAttribute( "autoScaleY", QString::number(d->autoScaleY) );
3635  writer->writeAttribute( "xMin", QString::number(d->xMin, 'g', 16));
3636  writer->writeAttribute( "xMax", QString::number(d->xMax, 'g', 16) );
3637  writer->writeAttribute( "yMin", QString::number(d->yMin, 'g', 16) );
3638  writer->writeAttribute( "yMax", QString::number(d->yMax, 'g', 16) );
3639  writer->writeAttribute( "xScale", QString::number(static_cast<int>(d->xScale)) );
3640  writer->writeAttribute( "yScale", QString::number(static_cast<int>(d->yScale)) );
3641  writer->writeAttribute( "xRangeFormat", QString::number(static_cast<int>(d->xRangeFormat)) );
3642  writer->writeAttribute( "yRangeFormat", QString::number(static_cast<int>(d->yRangeFormat)) );
3643  writer->writeAttribute( "xRangeDateTimeFormat", d->xRangeDateTimeFormat );
3644  writer->writeAttribute( "yRangeDateTimeFormat", d->yRangeDateTimeFormat );
3645  writer->writeAttribute( "horizontalPadding", QString::number(d->horizontalPadding) );
3646  writer->writeAttribute( "verticalPadding", QString::number(d->verticalPadding) );
3647  writer->writeAttribute( "rightPadding", QString::number(d->rightPadding) );
3648  writer->writeAttribute( "bottomPadding", QString::number(d->bottomPadding) );
3649  writer->writeAttribute( "symmetricPadding", QString::number(d->symmetricPadding));
3650  writer->writeEndElement();
3651 
3652  //x-scale breaks
3653  if (d->xRangeBreakingEnabled || !d->xRangeBreaks.list.isEmpty()) {
3654  writer->writeStartElement("xRangeBreaks");
3655  writer->writeAttribute( "enabled", QString::number(d->xRangeBreakingEnabled) );
3656  for (const auto& rb : d->xRangeBreaks.list) {
3657  writer->writeStartElement("xRangeBreak");
3658  writer->writeAttribute("start", QString::number(rb.start));
3659  writer->writeAttribute("end", QString::number(rb.end));
3660  writer->writeAttribute("position", QString::number(rb.position));
3661  writer->writeAttribute("style", QString::number(static_cast<int>(rb.style)));
3662  writer->writeEndElement();
3663  }
3664  writer->writeEndElement();
3665  }
3666 
3667  //y-scale breaks
3668  if (d->yRangeBreakingEnabled || !d->yRangeBreaks.list.isEmpty()) {
3669  writer->writeStartElement("yRangeBreaks");
3670  writer->writeAttribute( "enabled", QString::number(d->yRangeBreakingEnabled) );
3671  for (const auto& rb : d->yRangeBreaks.list) {
3672  writer->writeStartElement("yRangeBreak");
3673  writer->writeAttribute("start", QString::number(rb.start));
3674  writer->writeAttribute("end", QString::number(rb.end));
3675  writer->writeAttribute("position", QString::number(rb.position));
3676  writer->writeAttribute("style", QString::number(static_cast<int>(rb.style)));
3677  writer->writeEndElement();
3678  }
3679  writer->writeEndElement();
3680  }
3681 
3682  //serialize all children (plot area, title text label, axes and curves)
3683  for (auto* elem : children<WorksheetElement>(ChildIndexFlag::IncludeHidden))
3684  elem->save(writer);
3685 
3686  writer->writeEndElement(); // close "cartesianPlot" section
3687 }
3688 
3689 
3690 //! Load from XML
3691 bool CartesianPlot::load(XmlStreamReader* reader, bool preview) {
3692  Q_D(CartesianPlot);
3693 
3694  if (!readBasicAttributes(reader))
3695  return false;
3696 
3697  KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used");
3698  QXmlStreamAttributes attribs;
3699  QString str;
3700  bool titleLabelRead = false;
3701 
3702  while (!reader->atEnd()) {
3703  reader->readNext();
3704  if (reader->isEndElement() && reader->name() == "cartesianPlot")
3705  break;
3706 
3707  if (!reader->isStartElement())
3708  continue;
3709 
3710  if (reader->name() == "comment") {
3711  if (!readCommentElement(reader))
3712  return false;
3713  } else if (!preview && reader->name() == "theme") {
3714  attribs = reader->attributes();
3715  d->theme = attribs.value("name").toString();
3716  } else if (!preview && reader->name() == "cursor") {
3717  attribs = reader->attributes();
3718  QPen pen;
3719  pen.setWidth(attribs.value("width").toInt());
3720  pen.setStyle(static_cast<Qt::PenStyle>(attribs.value("style").toInt()));
3721  QColor color;
3722  color.setRed(attribs.value("color_r").toInt());
3723  color.setGreen(attribs.value("color_g").toInt());
3724  color.setBlue(attribs.value("color_b").toInt());
3725  pen.setColor(color);
3726  d->cursorPen = pen;
3727  } else if (!preview && reader->name() == "geometry") {
3728  attribs = reader->attributes();
3729 
3730  str = attribs.value("x").toString();
3731  if (str.isEmpty())
3732  reader->raiseWarning(attributeWarning.subs("x").toString());
3733  else
3734  d->rect.setX( str.toDouble() );
3735 
3736  str = attribs.value("y").toString();
3737  if (str.isEmpty())
3738  reader->raiseWarning(attributeWarning.subs("y").toString());
3739  else
3740  d->rect.setY( str.toDouble() );
3741 
3742  str = attribs.value("width").toString();
3743  if (str.isEmpty())
3744  reader->raiseWarning(attributeWarning.subs("width").toString());
3745  else
3746  d->rect.setWidth( str.toDouble() );
3747 
3748  str = attribs.value("height").toString();
3749  if (str.isEmpty())
3750  reader->raiseWarning(attributeWarning.subs("height").toString());
3751  else
3752  d->rect.setHeight( str.toDouble() );
3753 
3754  str = attribs.value("visible").toString();
3755  if (str.isEmpty())
3756  reader->raiseWarning(attributeWarning.subs("visible").toString());
3757  else
3758  d->setVisible(str.toInt());
3759  } else if (!preview && reader->name() == "coordinateSystem") {
3760  attribs = reader->attributes();
3761 
3762  READ_INT_VALUE("autoScaleX", autoScaleX, bool);
3763  READ_INT_VALUE("autoScaleY", autoScaleY, bool);
3764 
3765  str = attribs.value("xMin").toString();
3766  if (str.isEmpty())
3767  reader->raiseWarning(attributeWarning.subs("xMin").toString());
3768  else {
3769  d->xMin = str.toDouble();
3770  d->xMinPrev = d->xMin;
3771  }
3772 
3773  str = attribs.value("xMax").toString();
3774  if (str.isEmpty())
3775  reader->raiseWarning(attributeWarning.subs("xMax").toString());
3776  else {
3777  d->xMax = str.toDouble();
3778  d->xMaxPrev = d->xMax;
3779  }
3780 
3781  str = attribs.value("yMin").toString();
3782  if (str.isEmpty())
3783  reader->raiseWarning(attributeWarning.subs("yMin").toString());
3784  else {
3785  d->yMin = str.toDouble();
3786  d->yMinPrev = d->yMin;
3787  }
3788 
3789  str = attribs.value("yMax").toString();
3790  if (str.isEmpty())
3791  reader->raiseWarning(attributeWarning.subs("yMax").toString());
3792  else {
3793  d->yMax = str.toDouble();
3794  d->yMaxPrev = d->yMax;
3795  }
3796 
3797  READ_INT_VALUE("xScale", xScale, CartesianPlot::Scale);
3798  READ_INT_VALUE("yScale", yScale, CartesianPlot::Scale);
3799 
3800  READ_INT_VALUE("xRangeFormat", xRangeFormat, CartesianPlot::RangeFormat);
3801  READ_INT_VALUE("yRangeFormat", yRangeFormat, CartesianPlot::RangeFormat);
3802 
3803  str = attribs.value("xRangeDateTimeFormat").toString();
3804  if (!str.isEmpty())
3805  d->xRangeDateTimeFormat = str;
3806 
3807  str = attribs.value("yRangeDateTimeFormat").toString();
3808  if (!str.isEmpty())
3809  d->yRangeDateTimeFormat = str;
3810 
3811  READ_DOUBLE_VALUE("horizontalPadding", horizontalPadding);
3812  READ_DOUBLE_VALUE("verticalPadding", verticalPadding);
3813  READ_DOUBLE_VALUE("rightPadding", rightPadding);
3814  READ_DOUBLE_VALUE("bottomPadding", bottomPadding);
3815  READ_INT_VALUE("symmetricPadding", symmetricPadding, bool);
3816  } else if (!preview && reader->name() == "xRangeBreaks") {
3817  //delete default rang break
3818  d->xRangeBreaks.list.clear();
3819 
3820  attribs = reader->attributes();
3821  READ_INT_VALUE("enabled", xRangeBreakingEnabled, bool);
3822  } else if (!preview && reader->name() == "xRangeBreak") {
3823  attribs = reader->attributes();
3824 
3825  RangeBreak b;
3826  str = attribs.value("start").toString();
3827  if (str.isEmpty())
3828  reader->raiseWarning(attributeWarning.subs("start").toString());
3829  else
3830  b.start = str.toDouble();
3831 
3832  str = attribs.value("end").toString();
3833  if (str.isEmpty())
3834  reader->raiseWarning(attributeWarning.subs("end").toString());
3835  else
3836  b.end = str.toDouble();
3837 
3838  str = attribs.value("position").toString();
3839  if (str.isEmpty())
3840  reader->raiseWarning(attributeWarning.subs("position").toString());
3841  else
3842  b.position = str.toDouble();
3843 
3844  str = attribs.value("style").toString();
3845  if (str.isEmpty())
3846  reader->raiseWarning(attributeWarning.subs("style").toString());
3847  else
3848  b.style = CartesianPlot::RangeBreakStyle(str.toInt());
3849 
3850  d->xRangeBreaks.list << b;
3851  } else if (!preview && reader->name() == "yRangeBreaks") {
3852  //delete default rang break
3853  d->yRangeBreaks.list.clear();
3854 
3855  attribs = reader->attributes();
3856  READ_INT_VALUE("enabled", yRangeBreakingEnabled, bool);
3857  } else if (!preview && reader->name() == "yRangeBreak") {
3858  attribs = reader->attributes();
3859 
3860  RangeBreak b;
3861  str = attribs.value("start").toString();
3862  if (str.isEmpty())
3863  reader->raiseWarning(attributeWarning.subs("start").toString());
3864  else
3865  b.start = str.toDouble();
3866 
3867  str = attribs.value("end").toString();
3868  if (str.isEmpty())
3869  reader->raiseWarning(attributeWarning.subs("end").toString());
3870  else
3871  b.end = str.toDouble();
3872 
3873  str = attribs.value("position").toString();
3874  if (str.isEmpty())
3875  reader->raiseWarning(attributeWarning.subs("position").toString());
3876  else
3877  b.position = str.toDouble();
3878 
3879  str = attribs.value("style").toString();
3880  if (str.isEmpty())
3881  reader->raiseWarning(attributeWarning.subs("style").toString());
3882  else
3883  b.style = CartesianPlot::RangeBreakStyle(str.toInt());
3884 
3885  d->yRangeBreaks.list << b;
3886  } else if (reader->name() == "textLabel") {
3887  if (!titleLabelRead) {
3888  //the first text label is always the title label
3889  m_title->load(reader, preview);
3890  titleLabelRead = true;
3891 
3892  //TODO: the name is read in m_title->load() but we overwrite it here
3893  //since the old projects don't have this " - Title" appendix yet that we add in init().
3894  //can be removed in couple of releases
3895  m_title->setName(name() + QLatin1String(" - ") + i18n("Title"));
3896  } else {
3897  TextLabel* label = new TextLabel("text label");
3898  if (label->load(reader, preview)) {
3899  addChildFast(label);
3901  } else {
3902  delete label;
3903  return false;
3904  }
3905  }
3906  } else if (reader->name() == "image") {
3907  auto* image = new Image(QString());
3908  if (!image->load(reader, preview)) {
3909  delete image;
3910  return false;
3911  } else
3912  addChildFast(image);
3913  } else if (reader->name() == "plotArea")
3914  m_plotArea->load(reader, preview);
3915  else if (reader->name() == "axis") {
3916  auto* axis = new Axis(QString());
3917  if (axis->load(reader, preview))
3918  addChildFast(axis);
3919  else {
3920  delete axis;
3921  return false;
3922  }
3923  } else if (reader->name() == "xyCurve") {
3924  auto* curve = new XYCurve(QString());
3925  if (curve->load(reader, preview))
3926  addChildFast(curve);
3927  else {
3928  removeChild(curve);
3929  return false;
3930  }
3931  } else if (reader->name() == "xyEquationCurve") {
3932  auto* curve = new XYEquationCurve(QString());
3933  if (curve->load(reader, preview))
3934  addChildFast(curve);
3935  else {
3936  removeChild(curve);
3937  return false;
3938  }
3939  } else if (reader->name() == "xyDataReductionCurve") {
3940  auto* curve = new XYDataReductionCurve(QString());
3941  if (curve->load(reader, preview))
3942  addChildFast(curve);
3943  else {
3944  removeChild(curve);
3945  return false;
3946  }
3947  } else if (reader->name() == "xyDifferentiationCurve") {
3948  auto* curve = new XYDifferentiationCurve(QString());
3949  if (curve->load(reader, preview))
3950  addChildFast(curve);
3951  else {
3952  removeChild(curve);
3953  return false;
3954  }
3955  } else if (reader->name() == "xyIntegrationCurve") {
3956  auto* curve = new XYIntegrationCurve(QString());
3957  if (curve->load(reader, preview))
3958  addChildFast(curve);
3959  else {
3960  removeChild(curve);
3961  return false;
3962  }
3963  } else if (reader->name() == "xyInterpolationCurve") {
3964  auto* curve = new XYInterpolationCurve(QString());
3965  if (curve->load(reader, preview))
3966  addChildFast(curve);
3967  else {
3968  removeChild(curve);
3969  return false;
3970  }
3971  } else if (reader->name() == "xySmoothCurve") {
3972  auto* curve = new XYSmoothCurve(QString());
3973  if (curve->load(reader, preview))
3974  addChildFast(curve);
3975  else {
3976  removeChild(curve);
3977  return false;
3978  }
3979  } else if (reader->name() == "xyFitCurve") {
3980  auto* curve = new XYFitCurve(QString());
3981  if (curve->load(reader, preview))
3982  addChildFast(curve);
3983  else {
3984  removeChild(curve);
3985  return false;
3986  }
3987  } else if (reader->name() == "xyFourierFilterCurve") {
3988  auto* curve = new XYFourierFilterCurve(QString());
3989  if (curve->load(reader, preview))
3990  addChildFast(curve);
3991  else {
3992  removeChild(curve);
3993  return false;
3994  }
3995  } else if (reader->name() == "xyFourierTransformCurve") {
3996  auto* curve = new XYFourierTransformCurve(QString());
3997  if (curve->load(reader, preview))
3998  addChildFast(curve);
3999  else {
4000  removeChild(curve);
4001  return false;
4002  }
4003  } else if (reader->name() == "xyConvolutionCurve") {
4004  auto* curve = new XYConvolutionCurve(QString());
4005  if (curve->load(reader, preview))
4006  addChildFast(curve);
4007  else {
4008  removeChild(curve);
4009  return false;
4010  }
4011  } else if (reader->name() == "xyCorrelationCurve") {
4012  auto* curve = new XYCorrelationCurve(QString());
4013  if (curve->load(reader, preview))
4014  addChildFast(curve);
4015  else {
4016  removeChild(curve);
4017  return false;
4018  }
4019  } else if (reader->name() == "cartesianPlotLegend") {
4020  m_legend = new CartesianPlotLegend(this, QString());
4021  if (m_legend->load(reader, preview))
4023  else {
4024  delete m_legend;
4025  return false;
4026  }
4027  } else if (reader->name() == "customPoint") {
4028  auto* point = new CustomPoint(this, QString());
4029  if (point->load(reader, preview))
4030  addChildFast(point);
4031  else {
4032  delete point;
4033  return false;
4034  }
4035  } else if (reader->name() == "referenceLine") {
4036  auto* line = new ReferenceLine(this, QString());
4037  if (line->load(reader, preview))
4038  addChildFast(line);
4039  else {
4040  delete line;
4041  return false;
4042  }
4043  } else if (reader->name() == "Histogram") {
4044  auto* curve = new Histogram("Histogram");
4045  if (curve->load(reader, preview))
4046  addChildFast(curve);
4047  else {
4048  removeChild(curve);
4049  return false;
4050  }
4051  } else { // unknown element
4052  reader->raiseWarning(i18n("unknown cartesianPlot element '%1'", reader->name().toString()));
4053  if (!reader->skipToEndElement()) return false;
4054  }
4055  }
4056 
4057  if (preview)
4058  return true;
4059 
4060  d->retransform();
4061 
4062  //if a theme was used, initialize the color palette
4063  if (!d->theme.isEmpty()) {
4064  //TODO: check whether the theme config really exists
4065  KConfig config( ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig );
4066  this->setColorPalette(config);
4067  } else {
4068  //initialize the color palette with default colors
4069  this->setColorPalette(KConfig());
4070  }
4071 
4072  return true;
4073 }
4074 
4075 //##############################################################################
4076 //######################### Theme management ##################################
4077 //##############################################################################
4078 void CartesianPlot::loadTheme(const QString& theme) {
4079  if (!theme.isEmpty()) {
4080  KConfig config(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig);
4081  loadThemeConfig(config);
4082  } else {
4083  KConfig config;
4084  loadThemeConfig(config);
4085  }
4086 }
4087 
4088 void CartesianPlot::loadThemeConfig(const KConfig& config) {
4089  Q_D(CartesianPlot);
4090 
4091  QString theme = QString();
4092  if (config.hasGroup(QLatin1String("Theme"))) {
4093  theme = config.name();
4094 
4095  // theme path is saved with UNIX dir separator
4096  theme = theme.right(theme.length() - theme.lastIndexOf(QLatin1Char('/')) - 1);
4097  DEBUG(Q_FUNC_INFO << ", set theme to " << STDSTRING(theme));
4098  }
4099 
4100  //loadThemeConfig() can be called from
4101  //1. CartesianPlot::setTheme() when the user changes the theme for the plot
4102  //2. Worksheet::setTheme() -> Worksheet::loadTheme() when the user changes the theme for the worksheet
4103  //In the second case (i.e. when d->theme is not equal to theme yet),
4104  ///we need to put the new theme name on the undo-stack.
4105  if (theme != d->theme)
4106  exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: set theme")));
4107 
4108  //load the color palettes for the curves
4109  this->setColorPalette(config);
4110 
4111  //load the theme for all the children
4112  for (auto* child : children<WorksheetElement>(ChildIndexFlag::IncludeHidden))
4113  child->loadThemeConfig(config);
4114 
4115  d->update(this->rect());
4116 }
4117 
4118 void CartesianPlot::saveTheme(KConfig &config) {
4119  const QVector<Axis*>& axisElements = children<Axis>(ChildIndexFlag::IncludeHidden);
4120  const QVector<PlotArea*>& plotAreaElements = children<PlotArea>(ChildIndexFlag::IncludeHidden);
4121  const QVector<TextLabel*>& textLabelElements = children<TextLabel>(ChildIndexFlag::IncludeHidden);
4122 
4123  axisElements.at(0)->saveThemeConfig(config);
4124  plotAreaElements.at(0)->saveThemeConfig(config);
4125  textLabelElements.at(0)->saveThemeConfig(config);
4126 
4127  for (auto *child : children<XYCurve>(ChildIndexFlag::IncludeHidden))
4128  child->saveThemeConfig(config);
4129 }
4130 
4131 //Generating colors from 5-color theme palette
4132 void CartesianPlot::setColorPalette(const KConfig& config) {
4133  if (config.hasGroup(QLatin1String("Theme"))) {
4134  KConfigGroup group = config.group(QLatin1String("Theme"));
4135 
4136  //read the five colors defining the palette
4137  m_themeColorPalette.clear();
4138  m_themeColorPalette.append(group.readEntry("ThemePaletteColor1", QColor()));
4139  m_themeColorPalette.append(group.readEntry("ThemePaletteColor2", QColor()));
4140  m_themeColorPalette.append(group.readEntry("ThemePaletteColor3", QColor()));
4141  m_themeColorPalette.append(group.readEntry("ThemePaletteColor4", QColor()));
4142  m_themeColorPalette.append(group.readEntry("ThemePaletteColor5", QColor()));
4143  } else {
4144  //no theme is available, provide 5 "default colors"
4145  m_themeColorPalette.clear();
4146  m_themeColorPalette.append(QColor(25, 25, 25));
4147  m_themeColorPalette.append(QColor(0, 0, 127));
4148  m_themeColorPalette.append(QColor(127 ,0, 0));
4149  m_themeColorPalette.append(QColor(0, 127, 0));
4150  m_themeColorPalette.append(QColor(85, 0, 127));
4151  }
4152 
4153  //generate 30 additional shades if the color palette contains more than one color
4154  if (m_themeColorPalette.at(0) != m_themeColorPalette.at(1)) {
4155  QColor c;
4156 
4157  //3 factors to create shades from theme's palette
4158  std::array<float, 3> fac = {0.25f, 0.45f, 0.65f};
4159 
4160  //Generate 15 lighter shades
4161  for (int i = 0; i < 5; i++) {
4162  for (int j = 1; j < 4; j++) {
4163  c.setRed( m_themeColorPalette.at(i).red()*(1-fac[j-1]) );
4164  c.setGreen( m_themeColorPalette.at(i).green()*(1-fac[j-1]) );
4165  c.setBlue( m_themeColorPalette.at(i).blue()*(1-fac[j-1]) );
4166  m_themeColorPalette.append(c);
4167  }
4168  }
4169 
4170  //Generate 15 darker shades
4171  for (int i = 0; i < 5; i++) {
4172  for (int j = 4; j < 7; j++) {
4173  c.setRed( m_themeColorPalette.at(i).red()+((255-m_themeColorPalette.at(i).red())*fac[j-4]) );
4174  c.setGreen( m_themeColorPalette.at(i).green()+((255-m_themeColorPalette.at(i).green())*fac[j-4]) );
4175  c.setBlue( m_themeColorPalette.at(i).blue()+((255-m_themeColorPalette.at(i).blue())*fac[j-4]) );
4176  m_themeColorPalette.append(c);
4177  }
4178  }
4179  }
4180 }
4181 
4183  return m_themeColorPalette;
4184 }
AspectType
@ XYFourierTransformCurve
@ XYFourierFilterCurve
@ XYIntegrationCurve
@ XYDifferentiationCurve
@ XYInterpolationCurve
@ XYCorrelationCurve
@ CartesianPlotLegend
@ XYDataReductionCurve
STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeType, CartesianPlot::RangeType, rangeType, rangeChanged)
CLASS_SHARED_D_READER_IMPL(CartesianPlot, QPen, cursorPen, cursorPen)
static const QRgb black
Definition: ImageEditor.cpp:38
Base class of all persistent objects in a Project.
void info(const QString &text)
Implementations should call this whenever status information should be given to the user.
void setUndoAware(bool)
void aspectAdded(const AbstractAspect *)
Emitted after a new Aspect has been added to the tree.
bool readCommentElement(XmlStreamReader *)
Load comment from an XML element.
@ IncludeHidden
Include aspects marked as "hidden" in numbering or listing children.
void aspectDescriptionChanged(const AbstractAspect *)
Emitted after the name, comment or caption spec have changed.
void addChildFast(AbstractAspect *)
Add the given Aspect to my list of children without any checks and without putting this step onto the...
void addChild(AbstractAspect *)
Add the given Aspect to my list of children.
AbstractAspect * parent(AspectType type) const
In the parent-child hierarchy, return the first parent of type.
QString name() const
void writeBasicAttributes(QXmlStreamWriter *) const
Save name and creation time to XML.
T * child(int index, ChildIndexFlags flags={}) const
bool setName(const QString &, bool autoUnique=true)
AbstractAspect::setName sets the name of the abstract aspect.
void aspectRemoved(const AbstractAspect *parent, const AbstractAspect *before, const AbstractAspect *child)
Emitted from the parent after removing a child.
AbstractAspect * parentAspect() const
Return my parent Aspect or 0 if I currently don't have one.
void beginMacro(const QString &text)
Begin an undo stack macro (series of commands)
void exec(QUndoCommand *)
Execute the given command, pushing it on the undoStack() if available.
bool isLoading() const
QVector< AbstractAspect * > children(AspectType type, ChildIndexFlags flags={}) const
bool readBasicAttributes(XmlStreamReader *)
Load name and creation time from XML.
void writeCommentElement(QXmlStreamWriter *) const
Save the comment to XML.
void removeChild(AbstractAspect *)
Remove the given Aspect from my list of children.
virtual Project * project()
Return the Project this Aspect belongs to, or 0 if it is currently not part of one.
AbstractAspectPrivate * d
void endMacro()
End the current undo stack macro.
void setHidden(bool)
Set "hidden" property, i.e. whether to exclude this aspect from being shown in the explorer.
Interface definition for data with column logic.
virtual ColumnMode columnMode() const =0
Return the column mode.
virtual QString name() const
Second level container in a Worksheet for logical grouping.
Definition: AbstractPlot.h:41
TextLabel * title()
PlotArea * plotArea()
PlotArea * m_plotArea
Definition: AbstractPlot.h:64
AbstractCoordinateSystem * m_coordinateSystem
Definition: AbstractPlot.h:63
TextLabel * m_title
Definition: AbstractPlot.h:65
Axis for cartesian coordinate systems.
Definition: Axis.h:42
void setDefault(bool)
Definition: Axis.cpp:486
@ ticksIn
Definition: Axis.h:50
@ ticksBoth
Definition: Axis.h:52
void setOffset(const double, const bool=true)
Definition: Axis.cpp:528
void setSuppressRetransform(bool)
Definition: Axis.cpp:362
Cartesian coordinate system for plots.
bool setYScales(const QVector< CartesianScale * > &)
bool setXScales(const QVector< CartesianScale * > &)
QVector< QPointF > mapLogicalToScene(const QVector< QPointF > &, MappingFlags flags=MappingFlag::DefaultMapping) const override
QVector< QPointF > mapSceneToLogical(const QVector< QPointF > &, MappingFlags flags=MappingFlag::DefaultMapping) const override
Legend for the cartesian plot.
void retransform() override
Tell the element to newly transform its graphics item into its coordinate system.
bool load(XmlStreamReader *, bool preview) override
Load from XML.
void hoverLeaveEvent(QGraphicsSceneHoverEvent *) override
void mouseReleaseEvent(QGraphicsSceneMouseEvent *) override
CartesianPlot::RangeBreaks yRangeBreaks
void mousePressCursorMode(int cursorNumber, QPointF logicalPos)
void retransform() override
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override
CartesianPlot *const q
void mousePressEvent(QGraphicsSceneMouseEvent *) override
CartesianPlotPrivate::mousePressEvent In this function only basic stuff is done. The mousePressEvent ...
void mouseHoverZoomSelectionMode(QPointF logicPos)
void keyPressEvent(QKeyEvent *) override
CartesianPlotPrivate(CartesianPlot *)
void mousePressZoomSelectionMode(QPointF logicalPos)
CartesianCoordinateSystem * cSystem
void mouseMoveEvent(QGraphicsSceneMouseEvent *) override
void mouseMoveZoomSelectionMode(QPointF logicalPos)
CartesianPlot::RangeFormat xRangeFormat
void wheelEvent(QGraphicsSceneWheelEvent *) override
CartesianPlot::Type type
CartesianPlot::MouseMode mouseMode
CartesianScale * createScale(CartesianPlot::Scale type, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd)
CartesianPlot::Scale yScale
CartesianPlot::RangeFormat yRangeFormat
CartesianPlot::RangeBreaks xRangeBreaks
void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *widget=nullptr) override
void hoverMoveEvent(QGraphicsSceneHoverEvent *) override
void setZoomSelectionBandShow(bool show)
CartesianPlot::Scale xScale
void mouseMoveCursorMode(int cursorNumber, QPointF logicalPos)
CartesianPlotSetAutoScaleXCmd(CartesianPlotPrivate *private_obj, bool autoScale)
CartesianPlotPrivate * m_private
CartesianPlotSetAutoScaleYCmd(CartesianPlotPrivate *private_obj, bool autoScale)
CartesianPlotPrivate * m_private
CartesianPlotPrivate * m_private
CartesianPlotSetRectCmd(CartesianPlotPrivate *private_obj, QRectF rect)
A xy-plot.
Definition: CartesianPlot.h:58
bool isPanningActive() const
void curveLinePenChanged(QPen)
void setType(Type type)
QMenu * createContextMenu() override
Return a new context menu.
QAction * zoomOutYAction
QAction * addIntegrationCurveAction
void addHorizontalAxis()
void mouseMoveZoomSelectionModeSignal(QPointF logicPos)
QAction * addLegendAction
void setMouseMode(MouseMode)
void checkAxisFormat(const AbstractColumn *, Axis::Orientation)
QAction * addCurveAction
QAction * addFitCurveAction
QIcon icon() const override
const QString & yRangeDateTimeFormat() const
QAction * addEquationCurveAction
QMenu * analysisMenu()
void mouseMoveCursorModeSignal(int cursorNumber, QPointF logicPos)
QAction * addCorrelationAction
void save(QXmlStreamWriter *) const override
Save as XML.
QVector< QAction * > addFitAction
QAction * addDataReductionAction
void cursor1EnableChanged(bool enable)
void addIntegrationCurve()
QAction * addConvolutionAction
void curveRemoved(const XYCurve *)
const QList< QColor > & themeColorPalette() const
QAction * addHorizontalAxisAction
double cursorPos(int cursorNumber)
void childRemoved(const AbstractAspect *parent, const AbstractAspect *before, const AbstractAspect *child)
void addCorrelationCurve()
bool m_menusInitialized
QAction * addConvolutionCurveAction
~CartesianPlot() override
QMenu * zoomMenu
void addFourierTransformCurve()
QAction * addDataReductionCurveAction
QAction * addFourierFilterCurveAction
QAction * zoomInAction
void mouseHoverOutsideDataRect()
void addInterpolationCurve()
QAction * addFourierTransformAction
void loadThemeConfig(const KConfig &) override
void mouseReleaseZoomSelectionMode()
CartesianPlot(const QString &name)
void mousePressCursorMode(int cursorNumber, QPointF logicPos)
bool load(XmlStreamReader *, bool preview) override
Load from XML.
void visibilityChanged()
void setRect(const QRectF &) override
void mouseHoverZoomSelectionModeSignal(QPointF logicalPoint)
void scaleAutoTriggered()
void yMinChanged(double)
QAction * addCorrelationCurveAction
void zoom(bool x, bool in)
void navigate(NavigationOperation)
QAction * addInterpolationCurveAction
QAction * addReferenceLineAction
void setColorPalette(const KConfig &)
void mouseMoveZoomSelectionMode(QPointF logicPos)
void calculateCurvesYMinMax(bool completeRange=true)
QAction * addInterpolationAction
QAction * shiftUpYAction
QAction * zoomInYAction
bool isHovered() const
QString theme() const
void addDifferentiationCurve()
double m_zoomFactor
const XYCurve * currentCurve() const
QAction * addSmoothCurveAction
void setLocked(bool)
void curveAdded(const XYCurve *)
QAction * visibilityAction
void curveNameChanged(const AbstractAspect *curve)
void curveVisibilityChanged()
QAction * addVerticalAxisAction
const XYCurve * getCurve(int index)
void mouseModeChanged(CartesianPlot::MouseMode)
void mouseReleaseZoomSelectionModeSignal()
QAction * addImageAction
QAction * addTextLabelAction
QAction * addIntegrationAction
void mousePressZoomSelectionMode(QPointF logicPos)
void loadTheme(const QString &)
void mouseHoverOutsideDataRectSignal()
QAction * shiftLeftXAction
QAction * shiftRightXAction
void mousePressZoomSelectionModeSignal(QPointF logicPos)
QAction * scaleAutoAction
bool isPrinted() const
QAction * addSmoothAction
void setSuppressDataChangedSignal(bool)
void childHovered()
CartesianPlot::childHovered Unhover all curves, when another child is hovered. The hover handling for...
void xMinChanged(double)
QAction * addCustomPointAction
QMenu * addNewAnalysisMenu
void yAutoScaleChanged(bool)
QAction * addHistogramAction
void mouseHoverZoomSelectionMode(QPointF logicPos)
void processDropEvent(const QVector< quintptr > &) override
bool isLocked() const
CartesianPlotLegend * m_legend
MouseMode mouseMode() const
void addConvolutionCurve()
void shift(bool x, bool leftOrDown)
bool isSelected() const
void childAdded(const AbstractAspect *)
void xAutoScaleChanged(bool)
void mouseMoveCursorMode(int cursorNumber, QPointF logicPos)
Type type() const
void yMaxChanged(double)
void saveTheme(KConfig &config)
void xMaxChanged(double)
void addFourierFilterCurve()
const QString & xRangeDateTimeFormat() const
QAction * shiftDownYAction
QAction * addDifferentiationAction
QAction * scaleAutoXAction
QList< QColor > m_themeColorPalette
QMenu * addNewMenu
void cursor0EnableChanged(bool enable)
QAction * addFourierFilterAction
QAction * zoomOutXAction
QAction * zoomInXAction
void addDataReductionCurve()
QVector< AbstractAspect * > dependsOn() const override
void curveVisibilityChangedSignal()
QAction * scaleAutoYAction
QMenu * dataAnalysisMenu
void curveDataChanged(const XYCurve *)
void rectChanged(QRectF &)
QAction * zoomOutAction
void mousePressCursorModeSignal(int cursorNumber, QPointF logicPos)
void calculateCurvesXMinMax(bool completeRange=true)
QAction * addFourierTransformCurveAction
QAction * addDifferentiationCurveAction
QMenu * themeMenu
Base class for cartesian coordinate system scales.
static CartesianScale * createLogScale(const Interval< double > &interval, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd, CartesianPlot::Scale)
static CartesianScale * createLinearScale(const Interval< double > &interval, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd)
Aspect that manages a column.
Definition: Column.h:42
Definition: Curve.h:34